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

Continue refactoring and moving plugin code

This commit is contained in:
chylex 2019-05-27 19:46:39 +02:00
parent 108a0fefc3
commit 50bd526025
15 changed files with 155 additions and 134 deletions

View File

@ -65,7 +65,7 @@ public FormBrowser(){
Text = Program.BrandName; Text = Program.BrandName;
this.plugins = new PluginManager(this, Program.Config.Plugins, Program.PluginPath); this.plugins = new PluginManager(this, Program.Config.Plugins, Program.PluginPath, Program.PluginDataPath);
this.plugins.Reloaded += plugins_Reloaded; this.plugins.Reloaded += plugins_Reloaded;
this.plugins.Executed += plugins_Executed; this.plugins.Executed += plugins_Executed;
this.plugins.Reload(); this.plugins.Reload();

View File

@ -82,7 +82,7 @@ public static AnalyticsReport Create(AnalyticsFile file, ExternalInfo info, Plug
{ "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) }, { "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) },
0, 0,
{ "Plugins All" , List(plugins.Plugins.Select(Plugin)) }, { "Plugins All" , List(plugins.Plugins.Select(Plugin)) },
{ "Plugins Enabled" , List(plugins.Plugins.Where(plugin => plugins.Config.IsEnabled(plugin)).Select(Plugin)) }, { "Plugins Enabled" , List(plugins.Plugins.Where(plugins.Config.IsEnabled).Select(Plugin)) },
0, 0,
{ "Theme" , Dict(editLayoutDesign, "_theme", "light/def") }, { "Theme" , Dict(editLayoutDesign, "_theme", "light/def") },
{ "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") }, { "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") },

View File

@ -1,4 +1,6 @@
namespace TweetDuck.Core.Other { using TweetDuck.Plugins;
namespace TweetDuck.Core.Other {
partial class FormPlugins { partial class FormPlugins {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.
@ -27,7 +29,7 @@ private void InitializeComponent() {
this.btnClose = new System.Windows.Forms.Button(); this.btnClose = new System.Windows.Forms.Button();
this.btnReload = new System.Windows.Forms.Button(); this.btnReload = new System.Windows.Forms.Button();
this.btnOpenFolder = new System.Windows.Forms.Button(); this.btnOpenFolder = new System.Windows.Forms.Button();
this.flowLayoutPlugins = new TweetDuck.Plugins.Controls.PluginListFlowLayout(); this.flowLayoutPlugins = new PluginListFlowLayout();
this.timerLayout = new System.Windows.Forms.Timer(this.components); this.timerLayout = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout(); this.SuspendLayout();
// //
@ -117,7 +119,7 @@ private void InitializeComponent() {
private System.Windows.Forms.Button btnClose; private System.Windows.Forms.Button btnClose;
private System.Windows.Forms.Button btnReload; private System.Windows.Forms.Button btnReload;
private System.Windows.Forms.Button btnOpenFolder; private System.Windows.Forms.Button btnOpenFolder;
private Plugins.Controls.PluginListFlowLayout flowLayoutPlugins; private PluginListFlowLayout flowLayoutPlugins;
private System.Windows.Forms.Timer timerLayout; private System.Windows.Forms.Timer timerLayout;
} }
} }

View File

@ -5,7 +5,6 @@
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Controls;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
namespace TweetDuck.Core.Other{ namespace TweetDuck.Core.Other{

View File

@ -1,4 +1,4 @@
namespace TweetDuck.Plugins.Controls { namespace TweetDuck.Plugins {
partial class PluginControl { partial class PluginControl {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.

View File

@ -6,7 +6,7 @@
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums; using TweetLib.Core.Features.Plugins.Enums;
namespace TweetDuck.Plugins.Controls{ namespace TweetDuck.Plugins{
sealed partial class PluginControl : UserControl{ sealed partial class PluginControl : UserControl{
private readonly PluginManager pluginManager; private readonly PluginManager pluginManager;
private readonly Plugin plugin; private readonly Plugin plugin;
@ -56,13 +56,13 @@ private void timerLayout_Tick(object sender, EventArgs e){
private void panelDescription_Resize(object sender, EventArgs e){ private void panelDescription_Resize(object sender, EventArgs e){
SuspendLayout(); SuspendLayout();
int maxWidth = panelDescription.Width-(panelDescription.VerticalScroll.Visible ? SystemInformation.VerticalScrollBarWidth : 0); int maxWidth = panelDescription.Width - (panelDescription.VerticalScroll.Visible ? SystemInformation.VerticalScrollBarWidth : 0);
labelDescription.MaximumSize = new Size(maxWidth, int.MaxValue); labelDescription.MaximumSize = new Size(maxWidth, int.MaxValue);
Font font = labelDescription.Font; Font font = labelDescription.Font;
int descriptionLines = TextRenderer.MeasureText(labelDescription.Text, font, new Size(maxWidth, int.MaxValue), TextFormatFlags.WordBreak).Height/(font.Height-1); int descriptionLines = TextRenderer.MeasureText(labelDescription.Text, font, new Size(maxWidth, int.MaxValue), TextFormatFlags.WordBreak).Height / (font.Height - 1);
int requiredLines = Math.Max(descriptionLines, 1+(string.IsNullOrEmpty(labelVersion.Text) ? 0 : 1)+(isConfigurable ? 1 : 0)); int requiredLines = Math.Max(descriptionLines, 1 + (string.IsNullOrEmpty(labelVersion.Text) ? 0 : 1) + (isConfigurable ? 1 : 0));
nextHeight = requiredLines switch{ nextHeight = requiredLines switch{
1 => MaximumSize.Height - 2 * (font.Height - 1), 1 => MaximumSize.Height - 2 * (font.Height - 1),

View File

@ -1,7 +1,7 @@
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Plugins.Controls{ namespace TweetDuck.Plugins{
sealed class PluginListFlowLayout : FlowLayoutPanel{ sealed class PluginListFlowLayout : FlowLayoutPanel{
public PluginListFlowLayout(){ public PluginListFlowLayout(){
FlowDirection = FlowDirection.TopDown; FlowDirection = FlowDirection.TopDown;

View File

@ -5,6 +5,7 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Resources; using TweetDuck.Resources;
using TweetLib.Core.Data; using TweetLib.Core.Data;
@ -14,21 +15,21 @@
using TweetLib.Core.Features.Plugins.Events; using TweetLib.Core.Features.Plugins.Events;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins{
sealed class PluginManager{ sealed class PluginManager : IPluginManager{
private static readonly IReadOnlyDictionary<PluginEnvironment, string> PluginSetupScriptNames = PluginEnvironmentExtensions.Map(null, "plugins.browser.js", "plugins.notification.js"); private const string SetupScriptPrefix = "plugins.";
public string PathOfficialPlugins => Path.Combine(rootPath, "official"); public string PathCustomPlugins => Path.Combine(pluginFolder, PluginGroup.Custom.GetSubFolder());
public string PathCustomPlugins => Path.Combine(rootPath, "user");
public IEnumerable<Plugin> Plugins => plugins; public IEnumerable<Plugin> Plugins => plugins;
public IEnumerable<InjectedHTML> NotificationInjections => bridge.NotificationInjections; public IEnumerable<InjectedHTML> NotificationInjections => bridge.NotificationInjections;
public IPluginConfig Config { get; } public IPluginConfig Config { get; }
public event EventHandler<PluginErrorEventArgs> Reloaded; public event EventHandler<PluginErrorEventArgs> Reloaded;
public event EventHandler<PluginErrorEventArgs> Executed; public event EventHandler<PluginErrorEventArgs> Executed;
private readonly string rootPath; private readonly string pluginFolder;
private readonly string pluginDataFolder;
private readonly Control sync; private readonly Control sync;
private readonly PluginBridge bridge; private readonly PluginBridge bridge;
@ -39,11 +40,12 @@ sealed class PluginManager{
private IWebBrowser mainBrowser; private IWebBrowser mainBrowser;
public PluginManager(Control sync, IPluginConfig config, string rootPath){ public PluginManager(Control sync, IPluginConfig config, string pluginFolder, string pluginDataFolder){
this.Config = config; this.Config = config;
this.Config.PluginChangedState += Config_PluginChangedState; this.Config.PluginChangedState += Config_PluginChangedState;
this.rootPath = rootPath; this.pluginFolder = pluginFolder;
this.pluginDataFolder = pluginDataFolder;
this.sync = sync; this.sync = sync;
this.bridge = new PluginBridge(this); this.bridge = new PluginBridge(this);
@ -87,10 +89,10 @@ public void ConfigurePlugin(Plugin plugin){
} }
else if (plugin.HasConfig){ else if (plugin.HasConfig){
if (File.Exists(plugin.ConfigPath)){ if (File.Exists(plugin.ConfigPath)){
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){} using(Process.Start("explorer.exe", "/select,\"" + plugin.ConfigPath.Replace('/', '\\') + "\"")){}
} }
else{ else{
using(Process.Start("explorer.exe", '"'+plugin.GetPluginFolder(PluginFolder.Data).Replace('/', '\\')+'"')){} using(Process.Start("explorer.exe", '"' + plugin.GetPluginFolder(PluginFolder.Data).Replace('/', '\\') + '"')){}
} }
} }
} }
@ -109,7 +111,7 @@ public int GetTokenFromPlugin(Plugin plugin){
}while(tokens.ContainsKey(token) && --attempts >= 0); }while(tokens.ContainsKey(token) && --attempts >= 0);
if (attempts < 0){ if (attempts < 0){
token = -tokens.Count-1; token = -tokens.Count - 1;
} }
tokens[token] = plugin; tokens[token] = plugin;
@ -124,42 +126,22 @@ public void Reload(){
plugins.Clear(); plugins.Clear();
tokens.Clear(); tokens.Clear();
List<string> loadErrors = new List<string>(2); List<string> loadErrors = new List<string>(1);
IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){ foreach(var result in PluginGroupExtensions.Values.SelectMany(group => PluginLoader.AllInFolder(pluginFolder, pluginDataFolder, group))){
if (!Directory.Exists(path)){ if (result.HasValue){
yield break; plugins.Add(result.Value);
} }
else{
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){ loadErrors.Add(result.Exception.Message);
string name = Path.GetFileName(fullDir);
if (string.IsNullOrEmpty(name)){
loadErrors.Add($"{group.GetIdentifierPrefix()}(?): Could not extract directory name from path: {fullDir}");
continue;
}
Plugin plugin;
try{
plugin = PluginLoader.FromFolder(name, fullDir, Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name), group);
}catch(Exception e){
loadErrors.Add($"{group.GetIdentifierPrefix()}{name}: {e.Message}");
continue;
}
yield return plugin;
} }
} }
plugins.UnionWith(LoadPluginsFrom(PathOfficialPlugins, PluginGroup.Official));
plugins.UnionWith(LoadPluginsFrom(PathCustomPlugins, PluginGroup.Custom));
Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors)); Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors));
} }
private void ExecutePlugins(IFrame frame, PluginEnvironment environment){ private void ExecutePlugins(IFrame frame, PluginEnvironment environment){
if (!HasAnyPlugin(environment) || !ScriptLoader.ExecuteFile(frame, PluginSetupScriptNames[environment], sync)){ if (!HasAnyPlugin(environment) || !ScriptLoader.ExecuteFile(frame, SetupScriptPrefix + environment.GetPluginScriptFile(), sync)){
return; return;
} }
@ -183,11 +165,11 @@ private void ExecutePlugins(IFrame frame, PluginEnvironment environment){
try{ try{
script = File.ReadAllText(path); script = File.ReadAllText(path);
}catch(Exception e){ }catch(Exception e){
failedPlugins.Add(plugin.Identifier+" ("+Path.GetFileName(path)+"): "+e.Message); failedPlugins.Add($"{plugin.Identifier} ({Path.GetFileName(path)}): {e.Message}");
continue; continue;
} }
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, GetTokenFromPlugin(plugin), environment), "plugin:"+plugin); ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, GetTokenFromPlugin(plugin), environment), $"plugin:{plugin}");
} }
sync.InvokeAsyncSafe(() => { sync.InvokeAsyncSafe(() => {

View File

@ -145,7 +145,7 @@ public static void HotSwap(){
// ReSharper disable PossibleNullReferenceException // ReSharper disable PossibleNullReferenceException
object instPluginManager = typeFormBrowser.GetField("plugins", flagsInstance).GetValue(FormManager.TryFind<FormBrowser>()); object instPluginManager = typeFormBrowser.GetField("plugins", flagsInstance).GetValue(FormManager.TryFind<FormBrowser>());
typePluginManager.GetField("rootPath", flagsInstance).SetValue(instPluginManager, newPluginRoot); typePluginManager.GetField("pluginFolder", flagsInstance).SetValue(instPluginManager, newPluginRoot);
Debug.WriteLine("Reloading hot swapped plugins..."); Debug.WriteLine("Reloading hot swapped plugins...");
((PluginManager)instPluginManager).Reload(); ((PluginManager)instPluginManager).Reload();

View File

@ -238,16 +238,15 @@
<Compile Include="Core\Other\FormSettings.Designer.cs"> <Compile Include="Core\Other\FormSettings.Designer.cs">
<DependentUpon>FormSettings.cs</DependentUpon> <DependentUpon>FormSettings.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Plugins\Controls\PluginControl.cs"> <Compile Include="Plugins\PluginControl.cs">
<SubType>UserControl</SubType> <SubType>UserControl</SubType>
</Compile> </Compile>
<Compile Include="Plugins\Controls\PluginControl.Designer.cs"> <Compile Include="Plugins\PluginControl.Designer.cs">
<DependentUpon>PluginControl.cs</DependentUpon> <DependentUpon>PluginControl.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Plugins\Controls\PluginListFlowLayout.cs"> <Compile Include="Plugins\PluginListFlowLayout.cs">
<SubType>Component</SubType> <SubType>Component</SubType>
</Compile> </Compile>
<Compile Include="Plugins\PluginBridge.cs" />
<Compile Include="Configuration\PluginConfig.cs" /> <Compile Include="Configuration\PluginConfig.cs" />
<Compile Include="Plugins\PluginManager.cs" /> <Compile Include="Plugins\PluginManager.cs" />
<Compile Include="Properties\Resources.Designer.cs"> <Compile Include="Properties\Resources.Designer.cs">

View File

@ -1,8 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace TweetLib.Core.Features.Plugins.Enums{ namespace TweetLib.Core.Features.Plugins.Enums{
[Flags] [Flags]
@ -13,12 +10,10 @@ public enum PluginEnvironment{
} }
public static class PluginEnvironmentExtensions{ public static class PluginEnvironmentExtensions{
public static IEnumerable<PluginEnvironment> Values{ public static IEnumerable<PluginEnvironment> Values { get; } = new PluginEnvironment[]{
get{ PluginEnvironment.Browser,
yield return PluginEnvironment.Browser; PluginEnvironment.Notification
yield return PluginEnvironment.Notification; };
}
}
public static bool IncludesDisabledPlugins(this PluginEnvironment environment){ public static bool IncludesDisabledPlugins(this PluginEnvironment environment){
return environment == PluginEnvironment.Browser; return environment == PluginEnvironment.Browser;
@ -28,7 +23,7 @@ public static bool IncludesDisabledPlugins(this PluginEnvironment environment){
return environment switch{ return environment switch{
PluginEnvironment.Browser => "browser.js", PluginEnvironment.Browser => "browser.js",
PluginEnvironment.Notification => "notification.js", PluginEnvironment.Notification => "notification.js",
_ => null _ => throw new InvalidOperationException($"Invalid plugin environment: {environment}")
}; };
} }
@ -39,50 +34,5 @@ public static string GetPluginScriptVariables(this PluginEnvironment environment
_ => string.Empty _ => string.Empty
}; };
} }
public static IReadOnlyDictionary<PluginEnvironment, T> Map<T>(T forNone, T forBrowser, T forNotification){
return new PluginEnvironmentDictionary<T>(forNone, forBrowser, forNotification);
}
[SuppressMessage("ReSharper", "MemberHidesStaticFromOuterClass")]
private sealed class PluginEnvironmentDictionary<T> : IReadOnlyDictionary<PluginEnvironment, T>{
private const int TotalKeys = 3;
public IEnumerable<PluginEnvironment> Keys => Enum.GetValues(typeof(PluginEnvironment)).Cast<PluginEnvironment>();
public IEnumerable<T> Values => data;
public int Count => TotalKeys;
public T this[PluginEnvironment key] => data[(int)key];
private readonly T[] data;
public PluginEnvironmentDictionary(T forNone, T forBrowser, T forNotification){
this.data = new T[TotalKeys];
this.data[(int)PluginEnvironment.None] = forNone;
this.data[(int)PluginEnvironment.Browser] = forBrowser;
this.data[(int)PluginEnvironment.Notification] = forNotification;
}
public bool ContainsKey(PluginEnvironment key){
return key >= 0 && (int)key < TotalKeys;
}
public bool TryGetValue(PluginEnvironment key, out T value){
if (ContainsKey(key)){
value = this[key];
return true;
}
else{
value = default;
return false;
}
}
public IEnumerator<KeyValuePair<PluginEnvironment, T>> GetEnumerator(){
return Keys.Select(key => new KeyValuePair<PluginEnvironment, T>(key, this[key])).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
} }
} }

View File

@ -1,9 +1,25 @@
namespace TweetLib.Core.Features.Plugins.Enums{ using System;
using System.Collections.Generic;
namespace TweetLib.Core.Features.Plugins.Enums{
public enum PluginGroup{ public enum PluginGroup{
Official, Custom Official, Custom
} }
public static class PluginGroupExtensions{ public static class PluginGroupExtensions{
public static IEnumerable<PluginGroup> Values { get; } = new PluginGroup[]{
PluginGroup.Official,
PluginGroup.Custom
};
public static string GetSubFolder(this PluginGroup group){
return group switch{
PluginGroup.Official => "official",
PluginGroup.Custom => "user",
_ => throw new InvalidOperationException($"Invalid plugin group: {group}")
};
}
public static string GetIdentifierPrefix(this PluginGroup group){ public static string GetIdentifierPrefix(this PluginGroup group){
return group switch{ return group switch{
PluginGroup.Official => "official/", PluginGroup.Official => "official/",

View File

@ -0,0 +1,14 @@
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

@ -5,26 +5,21 @@
using System.Text; using System.Text;
using TweetLib.Core.Collections; using TweetLib.Core.Collections;
using TweetLib.Core.Data; using TweetLib.Core.Data;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums; using TweetLib.Core.Features.Plugins.Enums;
using TweetLib.Core.Features.Plugins.Events; using TweetLib.Core.Features.Plugins.Events;
using TweetLib.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Plugins{ namespace TweetLib.Core.Features.Plugins{
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
sealed class PluginBridge{ public sealed class PluginBridge{
private static string SanitizeCacheKey(string key){ private readonly IPluginManager manager;
return key.Replace('\\', '/').Trim(); private readonly FileCache fileCache = new FileCache();
}
private readonly PluginManager manager;
private readonly TwoKeyDictionary<int, string, string> fileCache = new TwoKeyDictionary<int, string, string>(4, 2);
private readonly TwoKeyDictionary<int, string, InjectedHTML> notificationInjections = new TwoKeyDictionary<int, string, InjectedHTML>(4, 1); private readonly TwoKeyDictionary<int, string, InjectedHTML> notificationInjections = new TwoKeyDictionary<int, string, InjectedHTML>(4, 1);
public IEnumerable<InjectedHTML> NotificationInjections => notificationInjections.InnerValues; public IEnumerable<InjectedHTML> NotificationInjections => notificationInjections.InnerValues;
public HashSet<Plugin> WithConfigureFunction { get; } = new HashSet<Plugin>(); public ISet<Plugin> WithConfigureFunction { get; } = new HashSet<Plugin>();
public PluginBridge(PluginManager manager){ public PluginBridge(IPluginManager manager){
this.manager = manager; this.manager = manager;
this.manager.Reloaded += manager_Reloaded; this.manager.Reloaded += manager_Reloaded;
this.manager.Config.PluginChangedState += Config_PluginChangedState; this.manager.Config.PluginChangedState += Config_PluginChangedState;
@ -55,7 +50,7 @@ private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
switch(folder){ switch(folder){
case PluginFolder.Data: throw new ArgumentException("File path has to be relative to the plugin data folder."); case PluginFolder.Data: throw new ArgumentException("File path has to be relative to the plugin data folder.");
case PluginFolder.Root: throw new ArgumentException("File path has to be relative to the plugin root folder."); case PluginFolder.Root: throw new ArgumentException("File path has to be relative to the plugin root folder.");
default: throw new ArgumentException("Invalid folder type "+folder+", this is a TweetDuck error."); default: throw new ArgumentException($"Invalid folder type {folder}, this is a TweetDuck error.");
} }
} }
else{ else{
@ -63,15 +58,15 @@ private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
} }
} }
private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool readCached){ private string ReadFileUnsafe(int token, PluginFolder folder, string path, bool readCached){
cacheKey = SanitizeCacheKey(cacheKey); string fullPath = GetFullPathOrThrow(token, folder, path);
if (readCached && fileCache.TryGetValue(token, cacheKey, out string cachedContents)){ if (readCached && fileCache.TryGetValue(token, folder, path, out string cachedContents)){
return cachedContents; return cachedContents;
} }
try{ try{
return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8); return fileCache[token, folder, path] = File.ReadAllText(fullPath, Encoding.UTF8);
}catch(FileNotFoundException){ }catch(FileNotFoundException){
throw new FileNotFoundException("File not found."); throw new FileNotFoundException("File not found.");
}catch(DirectoryNotFoundException){ }catch(DirectoryNotFoundException){
@ -86,17 +81,17 @@ public void WriteFile(int token, string path, string contents){
FileUtils.CreateDirectoryForFile(fullPath); FileUtils.CreateDirectoryForFile(fullPath);
File.WriteAllText(fullPath, contents, Encoding.UTF8); File.WriteAllText(fullPath, contents, Encoding.UTF8);
fileCache[token, SanitizeCacheKey(path)] = contents; fileCache[token, PluginFolder.Data, path] = contents;
} }
public string ReadFile(int token, string path, bool cache){ public string ReadFile(int token, string path, bool cache){
return ReadFileUnsafe(token, path, GetFullPathOrThrow(token, PluginFolder.Data, path), cache); return ReadFileUnsafe(token, PluginFolder.Data, path, cache);
} }
public void DeleteFile(int token, string path){ public void DeleteFile(int token, string path){
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path); string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
fileCache.Remove(token, SanitizeCacheKey(path)); fileCache.Remove(token, PluginFolder.Data, path);
File.Delete(fullPath); File.Delete(fullPath);
} }
@ -105,7 +100,7 @@ public bool CheckFileExists(int token, string path){
} }
public string ReadFileRoot(int token, string path){ public string ReadFileRoot(int token, string path){
return ReadFileUnsafe(token, "root*"+path, GetFullPathOrThrow(token, PluginFolder.Root, path), true); return ReadFileUnsafe(token, PluginFolder.Root, path, true);
} }
public bool CheckFileExistsRoot(int token, string path){ public bool CheckFileExistsRoot(int token, string path){
@ -127,5 +122,39 @@ public void SetConfigurable(int token){
WithConfigureFunction.Add(plugin); WithConfigureFunction.Add(plugin);
} }
} }
private sealed class FileCache{
private readonly TwoKeyDictionary<int, string, string> cache = new TwoKeyDictionary<int, string, string>(4, 2);
public string this[int token, PluginFolder folder, string path]{
set => cache[token, Key(folder, path)] = value;
}
public void Clear(){
cache.Clear();
}
public bool TryGetValue(int token, PluginFolder folder, string path, out string contents){
return cache.TryGetValue(token, Key(folder, path), out contents);
}
public void Remove(int token){
cache.Remove(token);
}
public void Remove(int token, PluginFolder folder, string path){
cache.Remove(token, Key(folder, path));
}
private static string Key(PluginFolder folder, string path){
string prefix = folder switch{
PluginFolder.Root => "root/",
PluginFolder.Data => "data/",
_ => throw new InvalidOperationException($"Invalid folder type {folder}, this is a TweetDuck error.")
};
return prefix + path.Replace('\\', '/').Trim();
}
}
} }
} }

View File

@ -1,13 +1,43 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using TweetLib.Core.Data;
using TweetLib.Core.Features.Plugins.Enums; using TweetLib.Core.Features.Plugins.Enums;
namespace TweetLib.Core.Features.Plugins{ namespace TweetLib.Core.Features.Plugins{
public static class PluginLoader{ public static class PluginLoader{
private static readonly string[] EndTag = { "[END]" }; private static readonly string[] EndTag = { "[END]" };
public static IEnumerable<Result<Plugin>> AllInFolder(string pluginFolder, string pluginDataFolder, PluginGroup group){
string path = Path.Combine(pluginFolder, group.GetSubFolder());
if (!Directory.Exists(path)){
yield break;
}
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
string name = Path.GetFileName(fullDir);
string prefix = group.GetIdentifierPrefix();
if (string.IsNullOrEmpty(name)){
yield return new Result<Plugin>(new DirectoryNotFoundException($"{prefix}(?): Could not extract directory name from path: {fullDir}"));
continue;
}
Result<Plugin> result;
try{
result = new Result<Plugin>(FromFolder(name, fullDir, Path.Combine(pluginDataFolder, prefix, name), group));
}catch(Exception e){
result = new Result<Plugin>(new Exception($"{prefix}{name}: {e.Message}", e));
}
yield return result;
}
}
public static Plugin FromFolder(string name, string pathRoot, string pathData, PluginGroup group){ public static Plugin FromFolder(string name, string pathRoot, string pathData, PluginGroup group){
Plugin.Builder builder = new Plugin.Builder(group, name, pathRoot, pathData); Plugin.Builder builder = new Plugin.Builder(group, name, pathRoot, pathData);