1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-05-12 14:34:08 +02:00

Refactor awful plugin loading and management code

This commit is contained in:
chylex 2017-10-31 07:13:17 +01:00
parent 54c1137927
commit 7d8d0bd43b
4 changed files with 84 additions and 123 deletions

View File

@ -62,9 +62,7 @@ private void btnOpenConfig_Click(object sender, EventArgs e){
}
private void btnToggleState_Click(object sender, EventArgs e){
bool newState = !pluginManager.Config.IsEnabled(plugin);
pluginManager.Config.SetEnabled(plugin, newState);
pluginManager.Config.ToggleEnabled(plugin);
UpdatePluginState();
}

View File

@ -7,12 +7,11 @@
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 PluginEnvironment Environments { get; }
public string Name => metadata["NAME"];
public string Description => metadata["DESCRIPTION"];
@ -23,9 +22,7 @@ sealed class Plugin{
public string ConfigDefault => metadata["CONFIGDEFAULT"];
public string RequiredVersion => metadata["REQUIRES"];
public bool CanRun{
get => canRun ?? (canRun = CheckRequiredVersion(RequiredVersion)).Value;
}
public bool CanRun { get; private set; }
public bool HasConfig{
get => ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
@ -56,21 +53,18 @@ public string DefaultConfigPath{
{ "REQUIRES", VersionWildcard }
};
private bool? canRun;
private Plugin(string path, PluginGroup group){
string name = Path.GetFileName(path);
System.Diagnostics.Debug.Assert(name != null);
private Plugin(string path, string name, PluginGroup group, PluginEnvironment environments){
this.pathRoot = path;
this.pathData = Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name);
this.Identifier = group.GetIdentifierPrefix()+name;
this.Group = group;
this.Environments = PluginEnvironment.None;
this.Environments = environments;
}
private void OnMetadataLoaded(){
CanRun = CheckRequiredVersion(RequiredVersion);
string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath;
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
@ -80,7 +74,7 @@ private void OnMetadataLoaded(){
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);
throw new IOException("Could not generate a configuration file for '"+Identifier+"' plugin: "+e.Message, e);
}
}
}
@ -138,89 +132,74 @@ 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);
// Static
private static readonly Version AppVersion = new Version(Program.VersionTag);
private static readonly string[] EndTag = { "[END]" };
if (!LoadMetadata(path, plugin, out error)){
return null;
}
if (!LoadEnvironments(path, plugin, out error)){
return null;
}
error = string.Empty;
public static Plugin CreateFromFolder(string path, PluginGroup group){
Plugin plugin = new Plugin(path, Path.GetFileName(path), group, LoadEnvironments(path));
LoadMetadata(path, plugin);
return plugin;
}
private static bool LoadEnvironments(string path, Plugin plugin, out string error){
private static PluginEnvironment LoadEnvironments(string path){
PluginEnvironment environments = PluginEnvironment.None;
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));
environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal));
}
if (plugin.Environments == PluginEnvironment.None){
error = "Plugin has no script files.";
return false;
if (environments == PluginEnvironment.None){
throw new ArgumentException("Plugin has no script files");
}
error = string.Empty;
return true;
return environments;
}
private static readonly string[] endTag = { "[END]" };
private static bool LoadMetadata(string path, Plugin plugin, out string error){
private static void LoadMetadata(string path, Plugin plugin){
string metaFile = Path.Combine(path, ".meta");
if (!File.Exists(metaFile)){
error = "Missing .meta file.";
return false;
throw new ArgumentException("Missing .meta file");
}
string currentTag = null, currentContents = string.Empty;
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)){
foreach(string line in File.ReadAllLines(metaFile, Encoding.UTF8).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 = "";
currentContents = string.Empty;
if (line.Equals(endTag[0])){
if (line.Equals(EndTag[0])){
break;
}
if (!plugin.metadata.ContainsKey(currentTag)){
error = "Invalid metadata tag: "+currentTag;
return false;
throw new FormatException("Invalid metadata tag: "+currentTag);
}
}
else if (currentTag != null){
currentContents = currentContents.Length == 0 ? line : currentContents+"\r\n"+line;
currentContents = currentContents.Length == 0 ? line : currentContents+Environment.NewLine+line;
}
else{
error = "Missing metadata tag before value: "+line;
return false;
throw new FormatException("Missing metadata tag before value: "+line);
}
}
if (plugin.Name.Length == 0){
error = "Plugin is missing a name in the .meta file.";
return false;
throw new FormatException("Plugin is missing a name in the .meta file");
}
if (plugin.RequiredVersion.Length == 0 || !(plugin.RequiredVersion == VersionWildcard || System.Version.TryParse(plugin.RequiredVersion, out Version _))){
error = "Plugin contains invalid version: "+plugin.RequiredVersion;
return false;
throw new FormatException("Plugin contains invalid version: "+plugin.RequiredVersion);
}
plugin.OnMetadataLoaded();
error = string.Empty;
return true;
}
private static bool CheckRequiredVersion(string requires){

View File

@ -24,6 +24,10 @@ public void SetEnabled(Plugin plugin, bool enabled){
}
}
public void ToggleEnabled(Plugin plugin){
SetEnabled(plugin, !IsEnabled(plugin));
}
public bool IsEnabled(Plugin plugin){
return !disabled.Contains(plugin.Identifier);
}
@ -43,11 +47,7 @@ public void Load(string file){
}
}catch(FileNotFoundException){
disabled.Clear();
foreach(string identifier in DefaultDisabled){
disabled.Add(identifier);
}
disabled.UnionWith(DefaultDisabled);
Save(file);
}catch(DirectoryNotFoundException){
}catch(Exception e){

View File

@ -9,8 +9,6 @@
namespace TweetDuck.Plugins{
sealed class PluginManager{
private const int InvalidToken = 0;
private static readonly Dictionary<PluginEnvironment, string> PluginSetupScripts = new Dictionary<PluginEnvironment, string>(4){
{ PluginEnvironment.None, ScriptLoader.LoadResource("plugins.js") },
{ PluginEnvironment.Browser, ScriptLoader.LoadResource("plugins.browser.js") },
@ -35,8 +33,6 @@ sealed class PluginManager{
private readonly Dictionary<int, Plugin> tokens = new Dictionary<int, Plugin>();
private readonly Random rand = new Random();
private List<string> loadErrors;
public PluginManager(string rootPath, string configPath){
this.rootPath = rootPath;
this.configPath = configPath;
@ -68,7 +64,18 @@ public int GetTokenFromPlugin(Plugin plugin){
}
}
return InvalidToken;
int token, attempts = 1000;
do{
token = rand.Next();
}while(tokens.ContainsKey(token) && --attempts >= 0);
if (attempts < 0){
token = -tokens.Count-1;
}
tokens[token] = plugin;
return token;
}
public Plugin GetPluginFromToken(int token){
@ -81,28 +88,41 @@ public void Reload(){
plugins.Clear();
tokens.Clear();
loadErrors = new List<string>(2);
foreach(Plugin plugin in LoadPluginsFrom(PathOfficialPlugins, PluginGroup.Official)){
plugins.Add(plugin);
}
List<string> loadErrors = new List<string>(2);
foreach(Plugin plugin in LoadPluginsFrom(PathCustomPlugins, PluginGroup.Custom)){
plugins.Add(plugin);
IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
if (!Directory.Exists(path)){
yield break;
}
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
Plugin plugin;
try{
plugin = Plugin.CreateFromFolder(fullDir, group);
}catch(Exception e){
loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+e.Message);
continue;
}
yield return plugin;
}
}
plugins.UnionWith(LoadPluginsFrom(PathOfficialPlugins, PluginGroup.Official));
plugins.UnionWith(LoadPluginsFrom(PathCustomPlugins, PluginGroup.Custom));
Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors));
}
public void ExecutePlugins(IFrame frame, PluginEnvironment environment){
if (HasAnyPlugin(environment)){
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[environment], environment.GetScriptIdentifier());
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[PluginEnvironment.None], PluginEnvironment.None.GetScriptIdentifier());
ExecutePluginScripts(frame, environment);
if (!HasAnyPlugin(environment)){
return;
}
}
private void ExecutePluginScripts(IFrame frame, PluginEnvironment environment){
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[environment], environment.GetScriptIdentifier());
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[PluginEnvironment.None], PluginEnvironment.None.GetScriptIdentifier());
bool includeDisabled = environment.IncludesDisabledPlugins();
if (includeDisabled){
@ -113,7 +133,10 @@ private void ExecutePluginScripts(IFrame frame, PluginEnvironment environment){
foreach(Plugin plugin in Plugins){
string path = plugin.GetScriptPath(environment);
if (string.IsNullOrEmpty(path) || (!includeDisabled && !Config.IsEnabled(plugin)) || !plugin.CanRun)continue;
if (string.IsNullOrEmpty(path) || (!includeDisabled && !Config.IsEnabled(plugin)) || !plugin.CanRun){
continue;
}
string script;
@ -123,50 +146,11 @@ private void ExecutePluginScripts(IFrame frame, PluginEnvironment environment){
failedPlugins.Add(plugin.Identifier+" ("+Path.GetFileName(path)+"): "+e.Message);
continue;
}
int token;
if (tokens.ContainsValue(plugin)){
token = GetTokenFromPlugin(plugin);
}
else{
token = GenerateToken();
tokens[token] = plugin;
}
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, token, environment), "plugin:"+plugin);
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, GetTokenFromPlugin(plugin), environment), "plugin:"+plugin);
}
Executed?.Invoke(this, new PluginErrorEventArgs(failedPlugins));
}
private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
if (!Directory.Exists(path)){
yield break;
}
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
Plugin plugin = Plugin.CreateFromFolder(fullDir, group, out string error);
if (plugin == null){
loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+error);
}
else{
yield return plugin;
}
}
}
private int GenerateToken(){
for(int attempt = 0; attempt < 1000; attempt++){
int token = rand.Next();
if (!tokens.ContainsKey(token) && token != InvalidToken){
return token;
}
}
return -tokens.Count;
}
}
}