1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2024-09-21 05:42:46 +02:00
TweetDuck/Plugins/Plugin.cs

231 lines
8.3 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using TweetDuck.Plugins.Enums;
namespace TweetDuck.Plugins{
sealed class Plugin{
private static readonly Version AppVersion = new Version(Program.VersionTag);
private const string VersionWildcard = "*";
public string Identifier { get; }
public PluginGroup Group { get; }
public PluginEnvironment Environments { get; private set; }
public string Name => metadata["NAME"];
public string Description => metadata["DESCRIPTION"];
public string Author => metadata["AUTHOR"];
public string Version => metadata["VERSION"];
public string Website => metadata["WEBSITE"];
public string ConfigFile => metadata["CONFIGFILE"];
public string ConfigDefault => metadata["CONFIGDEFAULT"];
public string RequiredVersion => metadata["REQUIRES"];
public bool CanRun{
get => canRun ?? (canRun = CheckRequiredVersion(RequiredVersion)).Value;
}
public bool HasConfig{
get => ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
}
public string ConfigPath{
get => HasConfig ? Path.Combine(GetPluginFolder(PluginFolder.Data), ConfigFile) : string.Empty;
}
public bool HasDefaultConfig{
get => ConfigDefault.Length > 0 && GetFullPathIfSafe(PluginFolder.Root, ConfigDefault).Length > 0;
}
public string DefaultConfigPath{
get => HasDefaultConfig ? Path.Combine(GetPluginFolder(PluginFolder.Root), ConfigDefault) : string.Empty;
}
private readonly string pathRoot;
private readonly string pathData;
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(8){
{ "NAME", "" },
{ "DESCRIPTION", "" },
{ "AUTHOR", "(anonymous)" },
{ "VERSION", "(unknown)" },
{ "WEBSITE", "" },
{ "CONFIGFILE", "" },
{ "CONFIGDEFAULT", "" },
{ "REQUIRES", VersionWildcard }
};
private bool? canRun;
private Plugin(string path, PluginGroup group){
string name = Path.GetFileName(path);
System.Diagnostics.Debug.Assert(name != null);
this.pathRoot = path;
this.pathData = Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name);
this.Identifier = group.GetIdentifierPrefix()+name;
this.Group = group;
this.Environments = PluginEnvironment.None;
}
private void OnMetadataLoaded(){
string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath;
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
string dataFolder = GetPluginFolder(PluginFolder.Data);
try{
Directory.CreateDirectory(dataFolder);
File.Copy(defaultConfigPath, configPath, false);
}catch(Exception e){
Program.Reporter.HandleException("Plugin Loading Error", "Could not generate a configuration file for '"+Identifier+"' plugin.", true, e);
}
}
}
public string GetScriptPath(PluginEnvironment environment){
if (Environments.HasFlag(environment)){
string file = environment.GetPluginScriptFile();
return file != null ? Path.Combine(pathRoot, file) : string.Empty;
}
else{
return string.Empty;
}
}
public string GetPluginFolder(PluginFolder folder){
switch(folder){
case PluginFolder.Root: return pathRoot;
case PluginFolder.Data: return pathData;
default: return string.Empty;
}
}
public string GetFullPathIfSafe(PluginFolder folder, string relativePath){
string rootFolder = GetPluginFolder(folder);
string fullPath = Path.Combine(rootFolder, relativePath);
try{
string folderPathName = new DirectoryInfo(rootFolder).FullName;
DirectoryInfo currentInfo = new DirectoryInfo(fullPath);
while(currentInfo.Parent != null){
if (currentInfo.Parent.FullName == folderPathName){
return fullPath;
}
currentInfo = currentInfo.Parent;
}
}
catch{
// ignore
}
return string.Empty;
}
public override string ToString(){
return Identifier;
}
public override int GetHashCode(){
return Identifier.GetHashCode();
}
public override bool Equals(object obj){
return obj is Plugin plugin && plugin.Identifier.Equals(Identifier);
}
public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){
Plugin plugin = new Plugin(path, group);
if (!LoadMetadata(path, plugin, out error)){
return null;
}
if (!LoadEnvironments(path, plugin, out error)){
return null;
}
error = string.Empty;
return plugin;
}
private static bool LoadEnvironments(string path, Plugin plugin, out string error){
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
plugin.Environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal));
}
if (plugin.Environments == PluginEnvironment.None){
error = "Plugin has no script files.";
return false;
}
error = string.Empty;
return true;
}
private static readonly string[] endTag = { "[END]" };
private static bool LoadMetadata(string path, Plugin plugin, out string error){
string metaFile = Path.Combine(path, ".meta");
if (!File.Exists(metaFile)){
error = "Missing .meta file.";
return false;
}
string[] lines = File.ReadAllLines(metaFile, Encoding.UTF8);
string currentTag = null, currentContents = "";
foreach(string line in lines.Concat(endTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){
if (line[0] == '[' && line[line.Length-1] == ']'){
if (currentTag != null){
plugin.metadata[currentTag] = currentContents;
}
currentTag = line.Substring(1, line.Length-2).ToUpper();
currentContents = "";
if (line.Equals(endTag[0])){
break;
}
if (!plugin.metadata.ContainsKey(currentTag)){
error = "Invalid metadata tag: "+currentTag;
return false;
}
}
else if (currentTag != null){
currentContents = currentContents.Length == 0 ? line : currentContents+"\r\n"+line;
}
else{
error = "Missing metadata tag before value: "+line;
return false;
}
}
if (plugin.Name.Length == 0){
error = "Plugin is missing a name in the .meta file.";
return false;
}
if (plugin.RequiredVersion.Length == 0 || !(plugin.RequiredVersion == VersionWildcard || System.Version.TryParse(plugin.RequiredVersion, out Version _))){
error = "Plugin contains invalid version: "+plugin.RequiredVersion;
return false;
}
plugin.OnMetadataLoaded();
error = string.Empty;
return true;
}
private static bool CheckRequiredVersion(string requires){
return requires == VersionWildcard || AppVersion >= new Version(requires);
}
}
}