using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Wox.Infrastructure; using Wox.Infrastructure.Logger; using Wox.Infrastructure.Storage; using Wox.Infrastructure.UserSettings; using Wox.Plugin; namespace Wox.Core.Plugin { /// /// The entry for managing Wox plugins /// public static class PluginManager { private static IEnumerable _contextMenuPlugins; /// /// Directories that will hold Wox plugin directory /// public static List AllPlugins { get; private set; } public static readonly List GlobalPlugins = new List(); public static readonly Dictionary NonGlobalPlugins = new Dictionary(); public static IPublicAPI API { private set; get; } // todo happlebao, this should not be public, the indicator function should be embeded public static PluginsSettings Settings; private static List _metadatas; private static readonly string[] Directories = { Constant.PreinstalledDirectory, Constant.PluginsDirectory }; private static void ValidateUserDirectory() { if (!Directory.Exists(Constant.PluginsDirectory)) { Directory.CreateDirectory(Constant.PluginsDirectory); } } private static void DeletePythonBinding() { const string binding = "wox.py"; var directory = Constant.PluginsDirectory; foreach (var subDirectory in Directory.GetDirectories(directory)) { var path = Path.Combine(subDirectory, binding); if (File.Exists(path)) { File.Delete(path); } } } public static void Save() { foreach (var plugin in AllPlugins) { var savable = plugin.Plugin as ISavable; savable?.Save(); } } public static void ReloadData() { foreach(var plugin in AllPlugins) { var reloadablePlugin = plugin.Plugin as IReloadable; reloadablePlugin?.ReloadData(); } } static PluginManager() { ValidateUserDirectory(); // force old plugins use new python binding DeletePythonBinding(); } /// /// because InitializePlugins needs API, so LoadPlugins needs to be called first /// todo happlebao The API should be removed /// /// public static void LoadPlugins(PluginsSettings settings) { _metadatas = PluginConfig.Parse(Directories); Settings = settings; Settings.UpdatePluginSettings(_metadatas); AllPlugins = PluginsLoader.Plugins(_metadatas, Settings); } public static void InitializePlugins(IPublicAPI api) { API = api; Parallel.ForEach(AllPlugins, pair => { var milliseconds = Stopwatch.Debug($"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>", () => { pair.Plugin.Init(new PluginInitContext { CurrentPluginMetadata = pair.Metadata, API = API }); }); pair.Metadata.InitTime += milliseconds; Log.Info($"|PluginManager.InitializePlugins|Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>"); }); _contextMenuPlugins = GetPluginsForInterface(); foreach (var plugin in AllPlugins) { if (IsGlobalPlugin(plugin.Metadata)) GlobalPlugins.Add(plugin); // Plugins may have multiple ActionKeywords, eg. WebSearch plugin.Metadata.ActionKeywords.Where(x => x != Query.GlobalPluginWildcardSign) .ToList() .ForEach(x => NonGlobalPlugins[x] = plugin); } } public static void InstallPlugin(string path) { PluginInstaller.Install(path); } public static Query QueryInit(string text) //todo is that possible to move it into type Query? { // replace multiple white spaces with one white space var terms = text.Split(new[] { Query.TermSeperater }, StringSplitOptions.RemoveEmptyEntries); var rawQuery = string.Join(Query.TermSeperater, terms); var actionKeyword = string.Empty; var search = rawQuery; List actionParameters = terms.ToList(); if (terms.Length == 0) return null; if (NonGlobalPlugins.ContainsKey(terms[0]) && !Settings.Plugins[NonGlobalPlugins[terms[0]].Metadata.ID].Disabled) { actionKeyword = terms[0]; actionParameters = terms.Skip(1).ToList(); search = string.Join(Query.TermSeperater, actionParameters.ToArray()); } var query = new Query { Terms = terms, RawQuery = rawQuery, ActionKeyword = actionKeyword, Search = search, // Obsolete value initialisation ActionName = actionKeyword, ActionParameters = actionParameters }; return query; } public static List ValidPluginsForQuery(Query query) { if (NonGlobalPlugins.ContainsKey(query.ActionKeyword)) { var plugin = NonGlobalPlugins[query.ActionKeyword]; return new List { plugin }; } else { return GlobalPlugins; } } public static List QueryForPlugin(PluginPair pair, Query query) { var results = new List(); try { var metadata = pair.Metadata; var milliseconds = Stopwatch.Debug($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", () => { results = pair.Plugin.Query(query) ?? results; UpdatePluginMetadata(results, metadata, query); }); metadata.QueryCount += 1; metadata.AvgQueryTime = metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2; return results; } catch (Exception e) { Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e); return new List(); } } public static void UpdatePluginMetadata(List results, PluginMetadata metadata, Query query) { foreach (var r in results) { r.PluginDirectory = metadata.PluginDirectory; r.PluginID = metadata.ID; r.OriginQuery = query; } } private static bool IsGlobalPlugin(PluginMetadata metadata) { return metadata.ActionKeywords.Contains(Query.GlobalPluginWildcardSign); } /// /// get specified plugin, return null if not found /// /// /// public static PluginPair GetPluginForId(string id) { return AllPlugins.FirstOrDefault(o => o.Metadata.ID == id); } public static IEnumerable GetPluginsForInterface() where T : IFeatures { return AllPlugins.Where(p => p.Plugin is T); } public static List GetContextMenusForPlugin(Result result) { var pluginPair = _contextMenuPlugins.FirstOrDefault(o => o.Metadata.ID == result.PluginID); if (pluginPair != null) { var metadata = pluginPair.Metadata; var plugin = (IContextMenu)pluginPair.Plugin; try { var results = plugin.LoadContextMenus(result); foreach (var r in results) { r.PluginDirectory = metadata.PluginDirectory; r.PluginID = metadata.ID; r.OriginQuery = result.OriginQuery; } return results; } catch (Exception e) { Log.Exception($"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{metadata.Name}>", e); return new List(); } } else { return new List(); } } public static bool ActionKeywordRegistered(string actionKeyword) { if (actionKeyword != Query.GlobalPluginWildcardSign && NonGlobalPlugins.ContainsKey(actionKeyword)) { return true; } else { return false; } } /// /// used to add action keyword for multiple action keyword plugin /// e.g. web search /// public static void AddActionKeyword(string id, string newActionKeyword) { var plugin = GetPluginForId(id); if (newActionKeyword == Query.GlobalPluginWildcardSign) { GlobalPlugins.Add(plugin); } else { NonGlobalPlugins[newActionKeyword] = plugin; } plugin.Metadata.ActionKeywords.Add(newActionKeyword); } /// /// used to add action keyword for multiple action keyword plugin /// e.g. web search /// public static void RemoveActionKeyword(string id, string oldActionkeyword) { var plugin = GetPluginForId(id); if (oldActionkeyword == Query.GlobalPluginWildcardSign && // Plugins may have multiple ActionKeywords that are global, eg. WebSearch plugin.Metadata.ActionKeywords .Where(x => x == Query.GlobalPluginWildcardSign) .ToList() .Count == 1) { GlobalPlugins.Remove(plugin); } if(oldActionkeyword != Query.GlobalPluginWildcardSign) NonGlobalPlugins.Remove(oldActionkeyword); plugin.Metadata.ActionKeywords.Remove(oldActionkeyword); } public static void ReplaceActionKeyword(string id, string oldActionKeyword, string newActionKeyword) { if (oldActionKeyword != newActionKeyword) { AddActionKeyword(id, newActionKeyword); RemoveActionKeyword(id, oldActionKeyword); } } } }