1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2024-11-23 08:42:47 +01:00
TweetDuck/Plugins/Plugin.cs

196 lines
7.5 KiB
C#

using System;
using System.IO;
using TweetDuck.Plugins.Enums;
namespace TweetDuck.Plugins{
sealed class Plugin{
private static readonly Version AppVersion = new Version(Program.VersionTag);
public string Identifier { get; }
public PluginGroup Group { get; }
public PluginEnvironment Environments { get; }
public string Name { get; }
public string Description { get; }
public string Author { get; }
public string Version { get; }
public string Website { get; }
public string ConfigFile { get; }
public string ConfigDefault { get; }
public Version RequiredVersion { get; }
public bool CanRun { get; }
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 Plugin(PluginGroup group, string identifier, string pathRoot, string pathData, Builder builder){
this.pathRoot = pathRoot;
this.pathData = pathData;
this.Group = group;
this.Identifier = identifier;
this.Environments = builder.Environments;
this.Name = builder.Name;
this.Description = builder.Description;
this.Author = builder.Author;
this.Version = builder.Version;
this.Website = builder.Website;
this.ConfigFile = builder.ConfigFile;
this.ConfigDefault = builder.ConfigDefault;
this.RequiredVersion = builder.RequiredVersion;
this.CanRun = AppVersion >= RequiredVersion;
}
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); // initially points to the file, which is convenient for the Attributes check below
DirectoryInfo parentInfo = currentInfo.Parent;
while(parentInfo != null){
if (currentInfo.Exists && currentInfo.Attributes.HasFlag(FileAttributes.ReparsePoint)){
return string.Empty; // no reason why a plugin should have files/folders with symlinks, junctions, or any other crap
}
if (parentInfo.FullName == folderPathName){
return fullPath;
}
currentInfo = parentInfo;
parentInfo = 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);
}
// Builder
public sealed class Builder{
private static readonly Version DefaultRequiredVersion = new Version(0, 0, 0, 0);
public string Name { get; set; }
public string Description { get; set; } = string.Empty;
public string Author { get; set; } = "(anonymous)";
public string Version { get; set; } = string.Empty;
public string Website { get; set; } = string.Empty;
public string ConfigFile { get; set; } = string.Empty;
public string ConfigDefault { get; set; } = string.Empty;
public Version RequiredVersion { get; set; } = DefaultRequiredVersion;
public PluginEnvironment Environments { get; private set; } = PluginEnvironment.None;
private readonly PluginGroup group;
private readonly string pathRoot;
private readonly string pathData;
private readonly string identifier;
public Builder(PluginGroup group, string name, string pathRoot, string pathData){
this.group = group;
this.pathRoot = pathRoot;
this.pathData = pathData;
this.identifier = group.GetIdentifierPrefix()+name;
}
public void AddEnvironment(PluginEnvironment environment){
this.Environments |= environment;
}
public Plugin BuildAndSetup(){
Plugin plugin = new Plugin(group, identifier, pathRoot, pathData, this);
if (plugin.Name.Length == 0){
throw new InvalidOperationException("Plugin is missing a name in the .meta file");
}
if (plugin.Environments == PluginEnvironment.None){
throw new InvalidOperationException("Plugin has no script files");
}
if (plugin.Group == PluginGroup.Official){
if (plugin.RequiredVersion != AppVersion){
throw new InvalidOperationException("Plugin is not supported in this version of TweetDuck, this may indicate a failed update or an unsupported plugin that was not removed automatically");
}
else if (!string.IsNullOrEmpty(plugin.Version)){
throw new InvalidOperationException("Official plugins cannot have a version identifier");
}
}
// setup
string configPath = plugin.ConfigPath, defaultConfigPath = plugin.DefaultConfigPath;
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
string dataFolder = plugin.GetPluginFolder(PluginFolder.Data);
try{
Directory.CreateDirectory(dataFolder);
File.Copy(defaultConfigPath, configPath, false);
}catch(Exception e){
throw new IOException($"Could not generate a configuration file for '{plugin.Identifier}' plugin: {e.Message}", e);
}
}
// done
return plugin;
}
}
}
}