using System; using System.Collections.Generic; using System.IO; using System.Text; using TweetDuck.Core.Utils; using TweetDuck.Data; using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Events; namespace TweetDuck.Plugins{ 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); 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 PluginBridge(PluginManager manager){ this.manager = manager; this.manager.Reloaded += manager_Reloaded; this.manager.Config.PluginChangedState += Config_PluginChangedState; } // Event handlers private void manager_Reloaded(object sender, PluginErrorEventArgs e){ fileCache.Clear(); } private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e){ if (!e.IsEnabled){ int token = manager.GetTokenFromPlugin(e.Plugin); fileCache.Remove(token); notificationInjections.Remove(token); } } // Utility methods private string GetFullPathOrThrow(int token, PluginFolder folder, string path){ Plugin plugin = manager.GetPluginFromToken(token); string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path); if (fullPath.Length == 0){ 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."); } } else{ return fullPath; } } private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool readCached){ cacheKey = SanitizeCacheKey(cacheKey); if (readCached && fileCache.TryGetValue(token, cacheKey, out string cachedContents)){ return cachedContents; } try{ return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8); }catch(FileNotFoundException){ throw new FileNotFoundException("File not found."); }catch(DirectoryNotFoundException){ throw new DirectoryNotFoundException("Directory not found."); } } // Public methods public void WriteFile(int token, string path, string contents){ string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path); WindowsUtils.CreateDirectoryForFile(fullPath); File.WriteAllText(fullPath, contents, Encoding.UTF8); fileCache[token, SanitizeCacheKey(path)] = contents; } public string ReadFile(int token, string path, bool cache){ return ReadFileUnsafe(token, path, GetFullPathOrThrow(token, PluginFolder.Data, path), cache); } public void DeleteFile(int token, string path){ string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path); fileCache.Remove(token, SanitizeCacheKey(path)); File.Delete(fullPath); } public bool CheckFileExists(int token, string path){ return File.Exists(GetFullPathOrThrow(token, PluginFolder.Data, path)); } public string ReadFileRoot(int token, string path){ return ReadFileUnsafe(token, "root*"+path, GetFullPathOrThrow(token, PluginFolder.Root, path), true); } public bool CheckFileExistsRoot(int token, string path){ return File.Exists(GetFullPathOrThrow(token, PluginFolder.Root, path)); } public void InjectIntoNotificationsBefore(int token, string key, string search, string html){ notificationInjections[token, key] = new InjectedHTML(InjectedHTML.Position.Before, search, html); } public void InjectIntoNotificationsAfter(int token, string key, string search, string html){ notificationInjections[token, key] = new InjectedHTML(InjectedHTML.Position.After, search, html); } public void SetConfigurable(int token){ Plugin plugin = manager.GetPluginFromToken(token); if (plugin != null){ WithConfigureFunction.Add(plugin); } } } }