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

Move PluginManager to Core lib & refactor plugin enums

This commit is contained in:
chylex 2019-07-14 20:41:18 +02:00
parent c2f7e52d13
commit 26d2d7a51e
26 changed files with 260 additions and 244 deletions

View File

@ -14,9 +14,9 @@
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetDuck.Resources;
using TweetDuck.Updates;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Events;
using TweetLib.Core.Features.Updates;
@ -65,7 +65,7 @@ public FormBrowser(){
Text = Program.BrandName;
this.plugins = new PluginManager(this, Program.Config.Plugins, Program.PluginPath, Program.PluginDataPath);
this.plugins = new PluginManager(Program.Config.Plugins, Program.PluginPath, Program.PluginDataPath);
this.plugins.Reloaded += plugins_Reloaded;
this.plugins.Executed += plugins_Executed;
this.plugins.Reload();
@ -236,7 +236,9 @@ private void trayIcon_ClickClose(object sender, EventArgs e){
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
if (e.HasErrors){
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
this.InvokeAsyncSafe(() => { // TODO not needed but makes code consistent...
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n" + string.Join("\n\n", e.Errors), FormMessage.OK);
});
}
if (isLoaded){
@ -244,9 +246,11 @@ private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
}
}
private static void plugins_Executed(object sender, PluginErrorEventArgs e){
private void plugins_Executed(object sender, PluginErrorEventArgs e){
if (e.HasErrors){
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
this.InvokeAsyncSafe(() => {
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n" + string.Join("\n\n", e.Errors), FormMessage.OK);
});
}
}

View File

@ -3,7 +3,6 @@
using System.IO;
using System.Linq;
using TweetDuck.Core.Other;
using TweetDuck.Plugins;
using TweetLib.Core.Data;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums;
@ -141,7 +140,7 @@ public bool Import(Items items){
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
if (!plugins.IsPluginInstalled(value[0])){
if (!plugins.Plugins.Any(plugin => plugin.Identifier.Equals(value[0]))){
missingPlugins.Add(value[0]);
}
}

View File

@ -2,8 +2,8 @@
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Controls;
using TweetDuck.Plugins;
using TweetDuck.Resources;
using TweetLib.Core.Features.Plugins;
namespace TweetDuck.Core.Notification.Example{
sealed class FormNotificationExample : FormNotificationMain{

View File

@ -9,6 +9,7 @@
using TweetDuck.Plugins;
using TweetDuck.Resources;
using TweetLib.Core.Data;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums;
namespace TweetDuck.Core.Notification{
@ -73,7 +74,7 @@ protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, b
browser.LoadingStateChanged += Browser_LoadingStateChanged;
browser.FrameLoadEnd += Browser_FrameLoadEnd;
plugins.Register(browser, PluginEnvironment.Notification);
plugins.Register(PluginEnvironment.Notification, new PluginDispatcher(this, browser));
mouseHookDelegate = MouseHookProc;
Disposed += (sender, args) => StopMouseHook(true);

View File

@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using TweetDuck.Plugins;
using System.Windows.Forms;
using TweetDuck.Core.Utils;
using TweetLib.Core.Features.Plugins;
namespace TweetDuck.Core.Notification{
sealed partial class FormNotificationTweet : FormNotificationMain{

View File

@ -6,9 +6,9 @@
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetDuck.Resources;
using TweetLib.Core.Data;
using TweetLib.Core.Features.Plugins;
namespace TweetDuck.Core.Notification.Screenshot{
sealed class FormNotificationScreenshotable : FormNotificationBase{

View File

@ -9,7 +9,7 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Plugins;
using TweetLib.Core.Features.Plugins;
#if GEN_SCREENSHOT_FRAMES
using System.Drawing.Imaging;

View File

@ -8,8 +8,8 @@
using System.Timers;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetLib.Core;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Utils;
namespace TweetDuck.Core.Other.Analytics{

View File

@ -10,7 +10,6 @@
using System.Text.RegularExpressions;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetLib.Core;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums;

View File

@ -9,7 +9,7 @@
using TweetDuck.Core.Other.Settings;
using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Updates;
namespace TweetDuck.Core.Other{

View File

@ -4,7 +4,7 @@
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Core.Management;
using TweetDuck.Plugins;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Utils;
namespace TweetDuck.Core.Other.Settings.Dialogs{

View File

@ -3,7 +3,7 @@
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetLib.Core.Features.Plugins;
namespace TweetDuck.Core.Other.Settings{
sealed partial class TabSettingsFeedback : BaseTabSettings{

View File

@ -13,6 +13,7 @@
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetDuck.Resources;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums;
namespace TweetDuck.Core{
@ -77,7 +78,7 @@ public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridg
this.browser.SetupZoomEvents();
owner.Controls.Add(browser);
plugins.Register(browser, PluginEnvironment.Browser, true);
plugins.Register(PluginEnvironment.Browser, new PluginDispatcher(owner, browser));
Config.MuteToggled += Config_MuteToggled;
Config.SoundNotificationChanged += Config_SoundNotificationInfoChanged;

View File

@ -0,0 +1,35 @@
using System;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Adapters;
using TweetDuck.Core.Utils;
using TweetLib.Core.Browser;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Events;
namespace TweetDuck.Plugins{
sealed class PluginDispatcher : IPluginDispatcher{
public event EventHandler<PluginDispatchEventArgs> Ready;
private readonly IWebBrowser browser;
private readonly IScriptExecutor executor;
public PluginDispatcher(Control sync, IWebBrowser browser){
this.browser = browser;
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
this.executor = new CefScriptExecutor(sync, browser);
}
void IPluginDispatcher.AttachBridge(string name, object bridge){
browser.RegisterAsyncJsObject(name, bridge);
}
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
IFrame frame = e.Frame;
if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){
Ready?.Invoke(this, new PluginDispatchEventArgs(executor));
}
}
}
}

View File

@ -1,180 +0,0 @@
using CefSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using TweetDuck.Resources;
using TweetLib.Core.Data;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Config;
using TweetLib.Core.Features.Plugins.Enums;
using TweetLib.Core.Features.Plugins.Events;
namespace TweetDuck.Plugins{
sealed class PluginManager : IPluginManager{
private const string SetupScriptPrefix = "plugins.";
public string PathCustomPlugins => Path.Combine(pluginFolder, PluginGroup.Custom.GetSubFolder());
public IEnumerable<Plugin> Plugins => plugins;
public IEnumerable<InjectedHTML> NotificationInjections => bridge.NotificationInjections;
public IPluginConfig Config { get; }
public event EventHandler<PluginErrorEventArgs> Reloaded;
public event EventHandler<PluginErrorEventArgs> Executed;
private readonly string pluginFolder;
private readonly string pluginDataFolder;
private readonly Control sync;
private readonly PluginBridge bridge;
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
private readonly Dictionary<int, Plugin> tokens = new Dictionary<int, Plugin>();
private readonly Random rand = new Random();
private IWebBrowser mainBrowser;
public PluginManager(Control sync, IPluginConfig config, string pluginFolder, string pluginDataFolder){
this.Config = config;
this.Config.PluginChangedState += Config_PluginChangedState;
this.pluginFolder = pluginFolder;
this.pluginDataFolder = pluginDataFolder;
this.sync = sync;
this.bridge = new PluginBridge(this);
}
public void Register(IWebBrowser browser, PluginEnvironment environment, bool asMainBrowser = false){
browser.FrameLoadEnd += (sender, args) => {
IFrame frame = args.Frame;
if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){
ExecutePlugins(frame, environment);
}
};
browser.RegisterAsyncJsObject("$TDP", bridge);
if (asMainBrowser){
mainBrowser = browser;
}
}
private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e){
mainBrowser?.ExecuteScriptAsync("TDPF_setPluginState", e.Plugin, e.IsEnabled);
}
public bool IsPluginInstalled(string identifier){
return plugins.Any(plugin => plugin.Identifier.Equals(identifier));
}
public bool HasAnyPlugin(PluginEnvironment environment){
return plugins.Any(plugin => plugin.Environments.HasFlag(environment));
}
public bool IsPluginConfigurable(Plugin plugin){
return plugin.HasConfig || bridge.WithConfigureFunction.Contains(plugin);
}
public void ConfigurePlugin(Plugin plugin){
if (bridge.WithConfigureFunction.Contains(plugin)){
mainBrowser?.ExecuteScriptAsync("TDPF_configurePlugin", plugin);
}
else if (plugin.HasConfig){
if (File.Exists(plugin.ConfigPath)){
using(Process.Start("explorer.exe", "/select,\"" + plugin.ConfigPath.Replace('/', '\\') + "\"")){}
}
else{
using(Process.Start("explorer.exe", '"' + plugin.GetPluginFolder(PluginFolder.Data).Replace('/', '\\') + '"')){}
}
}
}
public int GetTokenFromPlugin(Plugin plugin){
foreach(KeyValuePair<int, Plugin> kvp in tokens){
if (kvp.Value.Equals(plugin)){
return kvp.Key;
}
}
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){
return tokens.TryGetValue(token, out Plugin plugin) ? plugin : null;
}
public void Reload(){
plugins.Clear();
tokens.Clear();
List<string> loadErrors = new List<string>(1);
foreach(var result in PluginGroupExtensions.Values.SelectMany(group => PluginLoader.AllInFolder(pluginFolder, pluginDataFolder, group))){
if (result.HasValue){
plugins.Add(result.Value);
}
else{
loadErrors.Add(result.Exception.Message);
}
}
Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors));
}
private void ExecutePlugins(IFrame frame, PluginEnvironment environment){
if (!HasAnyPlugin(environment) || !ScriptLoader.ExecuteFile(frame, SetupScriptPrefix + environment.GetPluginScriptFile(), sync)){
return;
}
bool includeDisabled = environment.IncludesDisabledPlugins();
if (includeDisabled){
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GenerateConfig(Config), "gen:pluginconfig");
}
List<string> failedPlugins = new List<string>(1);
foreach(Plugin plugin in Plugins){
string path = plugin.GetScriptPath(environment);
if (string.IsNullOrEmpty(path) || (!includeDisabled && !Config.IsEnabled(plugin)) || !plugin.CanRun){
continue;
}
string script;
try{
script = File.ReadAllText(path);
}catch(Exception e){
failedPlugins.Add($"{plugin.Identifier} ({Path.GetFileName(path)}): {e.Message}");
continue;
}
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, GetTokenFromPlugin(plugin), environment), $"plugin:{plugin}");
}
sync.InvokeAsyncSafe(() => {
Executed?.Invoke(this, new PluginErrorEventArgs(failedPlugins));
});
}
}
}

View File

@ -11,7 +11,7 @@
using System.Diagnostics;
using System.Reflection;
using TweetDuck.Core;
using TweetDuck.Plugins;
using TweetLib.Core.Features.Plugins;
#endif
namespace TweetDuck.Resources{

View File

@ -246,11 +246,11 @@
<Compile Include="Plugins\PluginControl.Designer.cs">
<DependentUpon>PluginControl.cs</DependentUpon>
</Compile>
<Compile Include="Plugins\PluginDispatcher.cs" />
<Compile Include="Plugins\PluginListFlowLayout.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Configuration\PluginConfig.cs" />
<Compile Include="Plugins\PluginManager.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>

View File

@ -2,23 +2,17 @@
using System.Collections.Generic;
namespace TweetLib.Core.Features.Plugins.Enums{
[Flags]
public enum PluginEnvironment{
None = 0,
Browser = 1,
Notification = 2
Browser,
Notification
}
public static class PluginEnvironmentExtensions{
public static IEnumerable<PluginEnvironment> Values { get; } = new PluginEnvironment[]{
public static class PluginEnvironments{
public static IEnumerable<PluginEnvironment> All { get; } = new PluginEnvironment[]{
PluginEnvironment.Browser,
PluginEnvironment.Notification
};
public static bool IncludesDisabledPlugins(this PluginEnvironment environment){
return environment == PluginEnvironment.Browser;
}
public static string? GetPluginScriptFile(this PluginEnvironment environment){
return environment switch{
PluginEnvironment.Browser => "browser.js",

View File

@ -6,8 +6,8 @@ public enum PluginGroup{
Official, Custom
}
public static class PluginGroupExtensions{
public static IEnumerable<PluginGroup> Values { get; } = new PluginGroup[]{
public static class PluginGroups{
public static IEnumerable<PluginGroup> All { get; } = new PluginGroup[]{
PluginGroup.Official,
PluginGroup.Custom
};

View File

@ -0,0 +1,12 @@
using System;
using TweetLib.Core.Browser;
namespace TweetLib.Core.Features.Plugins.Events{
public sealed class PluginDispatchEventArgs : EventArgs{
public IScriptExecutor Executor { get; }
public PluginDispatchEventArgs(IScriptExecutor executor){
this.Executor = executor;
}
}
}

View File

@ -0,0 +1,9 @@
using System;
using TweetLib.Core.Features.Plugins.Events;
namespace TweetLib.Core.Features.Plugins{
public interface IPluginDispatcher{
event EventHandler<PluginDispatchEventArgs> Ready;
void AttachBridge(string name, object bridge);
}
}

View File

@ -1,14 +0,0 @@
using System;
using TweetLib.Core.Features.Plugins.Config;
using TweetLib.Core.Features.Plugins.Events;
namespace TweetLib.Core.Features.Plugins{
public interface IPluginManager{
IPluginConfig Config { get; }
event EventHandler<PluginErrorEventArgs> Reloaded;
int GetTokenFromPlugin(Plugin plugin);
Plugin GetPluginFromToken(int token);
}
}

View File

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using TweetLib.Core.Features.Plugins.Enums;
namespace TweetLib.Core.Features.Plugins{
@ -8,7 +10,6 @@ public sealed class Plugin{
public string Identifier { get; }
public PluginGroup Group { get; }
public PluginEnvironment Environments { get; }
public string Name { get; }
public string Description { get; }
@ -39,14 +40,15 @@ public string DefaultConfigPath{
private readonly string pathRoot;
private readonly string pathData;
private readonly ISet<PluginEnvironment> environments;
private Plugin(PluginGroup group, string identifier, string pathRoot, string pathData, Builder builder){
this.pathRoot = pathRoot;
this.pathData = pathData;
this.environments = builder.Environments;
this.Group = group;
this.Identifier = identifier;
this.Environments = builder.Environments;
this.Name = builder.Name;
this.Description = builder.Description;
@ -60,8 +62,12 @@ private Plugin(PluginGroup group, string identifier, string pathRoot, string pat
this.CanRun = AppVersion >= RequiredVersion;
}
public bool HasEnvironment(PluginEnvironment environment){
return environments.Contains(environment);
}
public string GetScriptPath(PluginEnvironment environment){
if (Environments.HasFlag(environment)){
if (environments.Contains(environment)){
string? file = environment.GetPluginScriptFile();
return file != null ? Path.Combine(pathRoot, file) : string.Empty;
}
@ -133,7 +139,7 @@ public sealed class Builder{
public string ConfigDefault { get; set; } = string.Empty;
public Version RequiredVersion { get; set; } = DefaultRequiredVersion;
public PluginEnvironment Environments { get; private set; } = PluginEnvironment.None;
public ISet<PluginEnvironment> Environments { get; } = new HashSet<PluginEnvironment>();
private readonly PluginGroup group;
private readonly string pathRoot;
@ -148,7 +154,7 @@ public Builder(PluginGroup group, string name, string pathRoot, string pathData)
}
public void AddEnvironment(PluginEnvironment environment){
this.Environments |= environment;
Environments.Add(environment);
}
public Plugin BuildAndSetup(){
@ -158,7 +164,7 @@ public Plugin BuildAndSetup(){
throw new InvalidOperationException("Plugin is missing a name in the .meta file");
}
if (plugin.Environments == PluginEnvironment.None){
if (!PluginEnvironments.All.Any(plugin.HasEnvironment)){
throw new InvalidOperationException("Plugin has no script files");
}

View File

@ -11,29 +11,56 @@
namespace TweetLib.Core.Features.Plugins{
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public sealed class PluginBridge{
private readonly IPluginManager manager;
internal sealed class PluginBridge{
private readonly Dictionary<int, Plugin> tokens = new Dictionary<int, Plugin>();
private readonly Random rand = new Random();
private readonly FileCache fileCache = new FileCache();
private readonly TwoKeyDictionary<int, string, InjectedHTML> notificationInjections = new TwoKeyDictionary<int, string, InjectedHTML>(4, 1);
public IEnumerable<InjectedHTML> NotificationInjections => notificationInjections.InnerValues;
public ISet<Plugin> WithConfigureFunction { get; } = new HashSet<Plugin>();
internal IEnumerable<InjectedHTML> NotificationInjections => notificationInjections.InnerValues;
internal ISet<Plugin> WithConfigureFunction { get; } = new HashSet<Plugin>();
public PluginBridge(IPluginManager manager){
this.manager = manager;
this.manager.Reloaded += manager_Reloaded;
this.manager.Config.PluginChangedState += Config_PluginChangedState;
public PluginBridge(PluginManager manager){
manager.Reloaded += manager_Reloaded;
manager.Config.PluginChangedState += Config_PluginChangedState;
}
internal int GetTokenFromPlugin(Plugin plugin){
foreach(KeyValuePair<int, Plugin> kvp in tokens){
if (kvp.Value.Equals(plugin)){
return kvp.Key;
}
}
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;
}
private Plugin? GetPluginFromToken(int token){
return tokens.TryGetValue(token, out Plugin plugin) ? plugin : null;
}
// Event handlers
private void manager_Reloaded(object sender, PluginErrorEventArgs e){
tokens.Clear();
fileCache.Clear();
}
private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e){
if (!e.IsEnabled){
int token = manager.GetTokenFromPlugin(e.Plugin);
int token = GetTokenFromPlugin(e.Plugin);
fileCache.Remove(token);
notificationInjections.Remove(token);
@ -43,7 +70,7 @@ private void Config_PluginChangedState(object sender, PluginChangedStateEventArg
// Utility methods
private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
Plugin plugin = manager.GetPluginFromToken(token);
Plugin? plugin = GetPluginFromToken(token);
string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
if (fullPath.Length == 0){
@ -116,7 +143,7 @@ public void InjectIntoNotificationsAfter(int token, string key, string search, s
}
public void SetConfigurable(int token){
Plugin plugin = manager.GetPluginFromToken(token);
Plugin? plugin = GetPluginFromToken(token);
if (plugin != null){
WithConfigureFunction.Add(plugin);

View File

@ -79,7 +79,7 @@ public static Plugin FromFolder(string name, string pathRoot, string pathData, P
}
private static PluginEnvironment EnvironmentFromFileName(string file){
return PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal));
return PluginEnvironments.All.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal));
}
private static void SetProperty(Plugin.Builder builder, string tag, string value){

View File

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using TweetLib.Core.Browser;
using TweetLib.Core.Data;
using TweetLib.Core.Features.Plugins.Config;
using TweetLib.Core.Features.Plugins.Enums;
using TweetLib.Core.Features.Plugins.Events;
namespace TweetLib.Core.Features.Plugins{
public sealed class PluginManager{
public string PathCustomPlugins => Path.Combine(pluginFolder, PluginGroup.Custom.GetSubFolder());
public IEnumerable<Plugin> Plugins => plugins;
public IEnumerable<InjectedHTML> NotificationInjections => bridge.NotificationInjections;
public IPluginConfig Config { get; }
public event EventHandler<PluginErrorEventArgs> Reloaded;
public event EventHandler<PluginErrorEventArgs> Executed;
private readonly string pluginFolder;
private readonly string pluginDataFolder;
private readonly PluginBridge bridge;
private IScriptExecutor? browserExecutor;
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
public PluginManager(IPluginConfig config, string pluginFolder, string pluginDataFolder){
this.Config = config;
this.Config.PluginChangedState += Config_PluginChangedState;
this.pluginFolder = pluginFolder;
this.pluginDataFolder = pluginDataFolder;
this.bridge = new PluginBridge(this);
}
public void Register(PluginEnvironment environment, IPluginDispatcher dispatcher){
dispatcher.AttachBridge("$TDP", bridge);
dispatcher.Ready += (sender, args) => {
IScriptExecutor executor = args.Executor;
if (environment == PluginEnvironment.Browser){
browserExecutor = executor;
}
Execute(environment, executor);
};
}
public void Reload(){
plugins.Clear();
List<string> errors = new List<string>(1);
foreach(var result in PluginGroups.All.SelectMany(group => PluginLoader.AllInFolder(pluginFolder, pluginDataFolder, group))){
if (result.HasValue){
plugins.Add(result.Value);
}
else{
errors.Add(result.Exception.Message);
}
}
Reloaded?.Invoke(this, new PluginErrorEventArgs(errors));
}
private void Execute(PluginEnvironment environment, IScriptExecutor executor){
if (!plugins.Any(plugin => plugin.HasEnvironment(environment)) || !executor.RunFile($"plugins.{environment.GetPluginScriptFile()}")){
return;
}
bool includeDisabled = environment == PluginEnvironment.Browser;
if (includeDisabled){
executor.RunScript("gen:pluginconfig", PluginScriptGenerator.GenerateConfig(Config));
}
List<string> errors = new List<string>(1);
foreach(Plugin plugin in Plugins){
string path = plugin.GetScriptPath(environment);
if (string.IsNullOrEmpty(path) || (!includeDisabled && !Config.IsEnabled(plugin)) || !plugin.CanRun){
continue;
}
string script;
try{
script = File.ReadAllText(path);
}catch(Exception e){
errors.Add($"{plugin.Identifier} ({Path.GetFileName(path)}): {e.Message}");
continue;
}
executor.RunScript($"plugin:{plugin}", PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, bridge.GetTokenFromPlugin(plugin), environment));
}
Executed?.Invoke(this, new PluginErrorEventArgs(errors));
}
private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e){
browserExecutor?.RunFunction("TDPF_setPluginState", e.Plugin, e.IsEnabled);
}
public bool IsPluginConfigurable(Plugin plugin){
return plugin.HasConfig || bridge.WithConfigureFunction.Contains(plugin);
}
public void ConfigurePlugin(Plugin plugin){
if (bridge.WithConfigureFunction.Contains(plugin) && browserExecutor != null){
browserExecutor.RunFunction("TDPF_configurePlugin", plugin);
}
else if (plugin.HasConfig){
App.SystemHandler.OpenFileExplorer(File.Exists(plugin.ConfigPath) ? plugin.ConfigPath : plugin.GetPluginFolder(PluginFolder.Data));
}
}
}
}