mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-05-06 23:34:05 +02:00
Continue refactoring and moving plugin code
This commit is contained in:
parent
108a0fefc3
commit
50bd526025
@ -65,7 +65,7 @@ public FormBrowser(){
|
||||
|
||||
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.Executed += plugins_Executed;
|
||||
this.plugins.Reload();
|
||||
|
@ -82,7 +82,7 @@ public static AnalyticsReport Create(AnalyticsFile file, ExternalInfo info, Plug
|
||||
{ "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) },
|
||||
0,
|
||||
{ "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,
|
||||
{ "Theme" , Dict(editLayoutDesign, "_theme", "light/def") },
|
||||
{ "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") },
|
||||
|
8
Core/Other/FormPlugins.Designer.cs
generated
8
Core/Other/FormPlugins.Designer.cs
generated
@ -1,4 +1,6 @@
|
||||
namespace TweetDuck.Core.Other {
|
||||
using TweetDuck.Plugins;
|
||||
|
||||
namespace TweetDuck.Core.Other {
|
||||
partial class FormPlugins {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
@ -27,7 +29,7 @@ private void InitializeComponent() {
|
||||
this.btnClose = new System.Windows.Forms.Button();
|
||||
this.btnReload = 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.SuspendLayout();
|
||||
//
|
||||
@ -117,7 +119,7 @@ private void InitializeComponent() {
|
||||
private System.Windows.Forms.Button btnClose;
|
||||
private System.Windows.Forms.Button btnReload;
|
||||
private System.Windows.Forms.Button btnOpenFolder;
|
||||
private Plugins.Controls.PluginListFlowLayout flowLayoutPlugins;
|
||||
private PluginListFlowLayout flowLayoutPlugins;
|
||||
private System.Windows.Forms.Timer timerLayout;
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Controls;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
|
@ -1,4 +1,4 @@
|
||||
namespace TweetDuck.Plugins.Controls {
|
||||
namespace TweetDuck.Plugins {
|
||||
partial class PluginControl {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
@ -6,7 +6,7 @@
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Plugins.Controls{
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed partial class PluginControl : UserControl{
|
||||
private readonly PluginManager pluginManager;
|
||||
private readonly Plugin plugin;
|
||||
@ -56,13 +56,13 @@ private void timerLayout_Tick(object sender, EventArgs e){
|
||||
private void panelDescription_Resize(object sender, EventArgs e){
|
||||
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);
|
||||
|
||||
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{
|
||||
1 => MaximumSize.Height - 2 * (font.Height - 1),
|
@ -1,7 +1,7 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Plugins.Controls{
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed class PluginListFlowLayout : FlowLayoutPanel{
|
||||
public PluginListFlowLayout(){
|
||||
FlowDirection = FlowDirection.TopDown;
|
@ -5,6 +5,7 @@
|
||||
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;
|
||||
@ -14,21 +15,21 @@
|
||||
using TweetLib.Core.Features.Plugins.Events;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed class PluginManager{
|
||||
private static readonly IReadOnlyDictionary<PluginEnvironment, string> PluginSetupScriptNames = PluginEnvironmentExtensions.Map(null, "plugins.browser.js", "plugins.notification.js");
|
||||
sealed class PluginManager : IPluginManager{
|
||||
private const string SetupScriptPrefix = "plugins.";
|
||||
|
||||
public string PathOfficialPlugins => Path.Combine(rootPath, "official");
|
||||
public string PathCustomPlugins => Path.Combine(rootPath, "user");
|
||||
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 rootPath;
|
||||
private readonly string pluginFolder;
|
||||
private readonly string pluginDataFolder;
|
||||
|
||||
private readonly Control sync;
|
||||
private readonly PluginBridge bridge;
|
||||
@ -39,11 +40,12 @@ sealed class PluginManager{
|
||||
|
||||
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.PluginChangedState += Config_PluginChangedState;
|
||||
|
||||
this.rootPath = rootPath;
|
||||
this.pluginFolder = pluginFolder;
|
||||
this.pluginDataFolder = pluginDataFolder;
|
||||
|
||||
this.sync = sync;
|
||||
this.bridge = new PluginBridge(this);
|
||||
@ -87,10 +89,10 @@ public void ConfigurePlugin(Plugin plugin){
|
||||
}
|
||||
else if (plugin.HasConfig){
|
||||
if (File.Exists(plugin.ConfigPath)){
|
||||
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){}
|
||||
using(Process.Start("explorer.exe", "/select,\"" + plugin.ConfigPath.Replace('/', '\\') + "\"")){}
|
||||
}
|
||||
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);
|
||||
|
||||
if (attempts < 0){
|
||||
token = -tokens.Count-1;
|
||||
token = -tokens.Count - 1;
|
||||
}
|
||||
|
||||
tokens[token] = plugin;
|
||||
@ -124,42 +126,22 @@ public void Reload(){
|
||||
plugins.Clear();
|
||||
tokens.Clear();
|
||||
|
||||
List<string> loadErrors = new List<string>(2);
|
||||
List<string> loadErrors = new List<string>(1);
|
||||
|
||||
IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
|
||||
if (!Directory.Exists(path)){
|
||||
yield break;
|
||||
foreach(var result in PluginGroupExtensions.Values.SelectMany(group => PluginLoader.AllInFolder(pluginFolder, pluginDataFolder, group))){
|
||||
if (result.HasValue){
|
||||
plugins.Add(result.Value);
|
||||
}
|
||||
|
||||
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
|
||||
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;
|
||||
else{
|
||||
loadErrors.Add(result.Exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
plugins.UnionWith(LoadPluginsFrom(PathOfficialPlugins, PluginGroup.Official));
|
||||
plugins.UnionWith(LoadPluginsFrom(PathCustomPlugins, PluginGroup.Custom));
|
||||
|
||||
Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -183,11 +165,11 @@ private void ExecutePlugins(IFrame frame, PluginEnvironment environment){
|
||||
try{
|
||||
script = File.ReadAllText(path);
|
||||
}catch(Exception e){
|
||||
failedPlugins.Add(plugin.Identifier+" ("+Path.GetFileName(path)+"): "+e.Message);
|
||||
failedPlugins.Add($"{plugin.Identifier} ({Path.GetFileName(path)}): {e.Message}");
|
||||
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(() => {
|
||||
|
@ -145,7 +145,7 @@ public static void HotSwap(){
|
||||
|
||||
// ReSharper disable PossibleNullReferenceException
|
||||
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...");
|
||||
((PluginManager)instPluginManager).Reload();
|
||||
|
@ -238,16 +238,15 @@
|
||||
<Compile Include="Core\Other\FormSettings.Designer.cs">
|
||||
<DependentUpon>FormSettings.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Plugins\Controls\PluginControl.cs">
|
||||
<Compile Include="Plugins\PluginControl.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Plugins\Controls\PluginControl.Designer.cs">
|
||||
<Compile Include="Plugins\PluginControl.Designer.cs">
|
||||
<DependentUpon>PluginControl.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Plugins\Controls\PluginListFlowLayout.cs">
|
||||
<Compile Include="Plugins\PluginListFlowLayout.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Plugins\PluginBridge.cs" />
|
||||
<Compile Include="Configuration\PluginConfig.cs" />
|
||||
<Compile Include="Plugins\PluginManager.cs" />
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
|
@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace TweetLib.Core.Features.Plugins.Enums{
|
||||
[Flags]
|
||||
@ -13,12 +10,10 @@ public enum PluginEnvironment{
|
||||
}
|
||||
|
||||
public static class PluginEnvironmentExtensions{
|
||||
public static IEnumerable<PluginEnvironment> Values{
|
||||
get{
|
||||
yield return PluginEnvironment.Browser;
|
||||
yield return PluginEnvironment.Notification;
|
||||
}
|
||||
}
|
||||
public static IEnumerable<PluginEnvironment> Values { get; } = new PluginEnvironment[]{
|
||||
PluginEnvironment.Browser,
|
||||
PluginEnvironment.Notification
|
||||
};
|
||||
|
||||
public static bool IncludesDisabledPlugins(this PluginEnvironment environment){
|
||||
return environment == PluginEnvironment.Browser;
|
||||
@ -28,7 +23,7 @@ public static bool IncludesDisabledPlugins(this PluginEnvironment environment){
|
||||
return environment switch{
|
||||
PluginEnvironment.Browser => "browser.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
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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{
|
||||
Official, Custom
|
||||
}
|
||||
|
||||
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){
|
||||
return group switch{
|
||||
PluginGroup.Official => "official/",
|
||||
|
14
lib/TweetLib.Core/Features/Plugins/IPluginManager.cs
Normal file
14
lib/TweetLib.Core/Features/Plugins/IPluginManager.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -5,26 +5,21 @@
|
||||
using System.Text;
|
||||
using TweetLib.Core.Collections;
|
||||
using TweetLib.Core.Data;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Enums;
|
||||
using TweetLib.Core.Features.Plugins.Events;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
namespace TweetLib.Core.Features.Plugins{
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
sealed class PluginBridge{
|
||||
private static string SanitizeCacheKey(string key){
|
||||
return key.Replace('\\', '/').Trim();
|
||||
}
|
||||
|
||||
private readonly PluginManager manager;
|
||||
private readonly TwoKeyDictionary<int, string, string> fileCache = new TwoKeyDictionary<int, string, string>(4, 2);
|
||||
public sealed class PluginBridge{
|
||||
private readonly IPluginManager manager;
|
||||
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 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.Reloaded += manager_Reloaded;
|
||||
this.manager.Config.PluginChangedState += Config_PluginChangedState;
|
||||
@ -55,7 +50,7 @@ private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
|
||||
switch(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.");
|
||||
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{
|
||||
@ -63,15 +58,15 @@ private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
|
||||
}
|
||||
}
|
||||
|
||||
private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool readCached){
|
||||
cacheKey = SanitizeCacheKey(cacheKey);
|
||||
|
||||
if (readCached && fileCache.TryGetValue(token, cacheKey, out string cachedContents)){
|
||||
private string ReadFileUnsafe(int token, PluginFolder folder, string path, bool readCached){
|
||||
string fullPath = GetFullPathOrThrow(token, folder, path);
|
||||
|
||||
if (readCached && fileCache.TryGetValue(token, folder, path, out string cachedContents)){
|
||||
return cachedContents;
|
||||
}
|
||||
|
||||
try{
|
||||
return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8);
|
||||
return fileCache[token, folder, path] = File.ReadAllText(fullPath, Encoding.UTF8);
|
||||
}catch(FileNotFoundException){
|
||||
throw new FileNotFoundException("File not found.");
|
||||
}catch(DirectoryNotFoundException){
|
||||
@ -86,17 +81,17 @@ public void WriteFile(int token, string path, string contents){
|
||||
|
||||
FileUtils.CreateDirectoryForFile(fullPath);
|
||||
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){
|
||||
return ReadFileUnsafe(token, path, GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
|
||||
return ReadFileUnsafe(token, PluginFolder.Data, path, cache);
|
||||
}
|
||||
|
||||
public void DeleteFile(int token, string path){
|
||||
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
||||
|
||||
fileCache.Remove(token, SanitizeCacheKey(path));
|
||||
fileCache.Remove(token, PluginFolder.Data, path);
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
|
||||
@ -105,7 +100,7 @@ public bool CheckFileExists(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){
|
||||
@ -127,5 +122,39 @@ public void SetConfigurable(int token){
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TweetLib.Core.Data;
|
||||
using TweetLib.Core.Features.Plugins.Enums;
|
||||
|
||||
namespace TweetLib.Core.Features.Plugins{
|
||||
public static class PluginLoader{
|
||||
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){
|
||||
Plugin.Builder builder = new Plugin.Builder(group, name, pathRoot, pathData);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user