// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Loader; using System.Windows; using Microsoft.PowerToys.Settings.UI.Library; using Wox.Plugin.Logger; using Wox.Plugin.Properties; namespace Wox.Plugin { public class PluginPair { public IPlugin Plugin { get; internal set; } public PluginMetadata Metadata { get; internal set; } public PluginPair(PluginMetadata metadata) { this.Metadata = metadata; LoadPlugin(); } public bool IsPluginInitialized { get; set; } public void InitializePlugin(IPublicAPI api) { if (Metadata.Disabled) { Log.Info($"Do not initialize {Metadata.Name} as it is disabled.", GetType()); return; } if (IsPluginInitialized) { Log.Info($"{Metadata.Name} plugin is already initialized", GetType()); return; } var stopWatch = new Stopwatch(); stopWatch.Start(); if (!InitPlugin(api)) { return; } stopWatch.Stop(); IsPluginInitialized = true; Metadata.InitTime += stopWatch.ElapsedMilliseconds; Log.Info($"Total initialize cost for <{Metadata.Name}> is <{Metadata.InitTime}ms>", GetType()); return; } public void Update(PowerLauncherPluginSettings setting, IPublicAPI api, Action refreshPluginsOverviewCallback) { if (setting == null || api == null) { return; } bool refreshOverview = false; if (Metadata.Disabled != setting.Disabled || Metadata.ActionKeyword != setting.ActionKeyword) { refreshOverview = true; } // If the enabled state is policy managed then we skip the update of the disabled state as it must be a manual settings.json manipulation. if (!Metadata.IsEnabledPolicyConfigured) { if (Metadata.Disabled && !setting.Disabled) { Metadata.Disabled = false; InitializePlugin(api); if (!IsPluginInitialized) { string title = Resources.FailedToLoadPluginTitle.ToString().Replace("{0}", Constant.Version); string description = $"{Resources.FailedToLoadPluginDescription} {Metadata.Name} ({Metadata.ExecuteFileVersion})\n\n{Resources.FailedToLoadPluginDescriptionPartTwo}"; Application.Current.Dispatcher.InvokeAsync(() => api.ShowMsg(title, description, string.Empty, false)); } } else { Metadata.Disabled = setting.Disabled; } } Metadata.ActionKeyword = setting.ActionKeyword; Metadata.WeightBoost = setting.WeightBoost; Metadata.IsGlobal = setting.IsGlobal; (Plugin as ISettingProvider)?.UpdateSettings(setting); if (IsPluginInitialized && !Metadata.Disabled) { (Plugin as IReloadable)?.ReloadData(); } if (refreshOverview) { refreshPluginsOverviewCallback?.Invoke(); } } public override string ToString() { return Metadata.Name; } public override bool Equals(object obj) { if (obj is PluginPair r) { // Using Ordinal since this is used internally return string.Equals(r.Metadata.ID, Metadata.ID, StringComparison.Ordinal); } else { return false; } } public override int GetHashCode() { // Using Ordinal since this is used internally var hashcode = Metadata.ID?.GetHashCode(StringComparison.Ordinal) ?? 0; return hashcode; } private void LoadPlugin() { var stopWatch = new Stopwatch(); stopWatch.Start(); CreatePluginInstance(); stopWatch.Stop(); Metadata.InitTime += stopWatch.ElapsedMilliseconds; Log.Info($"Load cost for <{Metadata.Name}> is <{Metadata.InitTime}ms>", GetType()); } private bool CreatePluginInstance() { if (Plugin != null) { Log.Warn($"{Metadata.Name} plugin was already loaded", GetType()); return true; } try { if (Metadata.DynamicLoading) { var loadContext = new PluginLoadContext(Metadata.ExecuteFilePath); _assembly = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(Metadata.ExecuteFilePath))); } else { _assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Metadata.ExecuteFilePath); } } catch (Exception e) { Log.Exception($"Couldn't load assembly for {Metadata.Name} in {Metadata.ExecuteFilePath}", e, MethodBase.GetCurrentMethod().DeclaringType); return false; } Type[] types; try { types = _assembly.GetTypes(); } catch (ReflectionTypeLoadException e) { Log.Exception($"Couldn't get assembly types for {Metadata.Name} in {Metadata.ExecuteFilePath}. The plugin might be corrupted. Uninstall PowerToys, manually delete the install folder and reinstall.", e, MethodBase.GetCurrentMethod().DeclaringType); return false; } Type type; try { type = types.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(typeof(IPlugin))); } catch (InvalidOperationException e) { Log.Exception($"Can't find class implement IPlugin for <{Metadata.Name}> in {Metadata.ExecuteFilePath}", e, MethodBase.GetCurrentMethod().DeclaringType); return false; } // Validate plugin ID to prevent bypassing the GPO by changing the ID in the plugin.json file. string pluginID = (string)type.GetProperty("PluginID", BindingFlags.Public | BindingFlags.Static)?.GetValue(null); if (pluginID == null) { Log.Error($"Can't validate plugin ID of plugin <{Metadata.Name}> in {Metadata.ExecuteFilePath}: The static property was not found.", MethodBase.GetCurrentMethod().DeclaringType); return false; } else if (pluginID != Metadata.ID) { Log.Error($"Wrong plugin ID found in plugin.json of plugin <{Metadata.Name}>. ('{Metadata.ID}' != '{pluginID}')", MethodBase.GetCurrentMethod().DeclaringType); return false; } try { Plugin = (IPlugin)Activator.CreateInstance(type); } catch (Exception e) { Log.Exception($"Can't create instance for <{Metadata.Name}> in {Metadata.ExecuteFilePath}", e, MethodBase.GetCurrentMethod().DeclaringType); return false; } return true; } private bool InitPlugin(IPublicAPI api) { if (Plugin == null) { Log.Warn($"Cannot initialize {Metadata.Name} plugin as it was not loaded", GetType()); return false; } try { Plugin.Init(new PluginInitContext { CurrentPluginMetadata = Metadata, API = api, }); } catch (Exception e) { Log.Exception($"Fail to Init plugin: {Metadata.Name}", e, GetType()); return false; } return true; } private Assembly _assembly; } }