diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 3b697a1ee..239f0499d 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
@@ -32,7 +33,7 @@ namespace Flow.Launcher.Core.Plugin
///
/// Directories that will hold Flow Launcher plugin directory
///
- private static readonly string[] Directories = { Constant.PreinstalledDirectory, DataLocation.PluginsDirectory };
+ private static readonly string[] Directories = {Constant.PreinstalledDirectory, DataLocation.PluginsDirectory};
private static void DeletePythonBinding()
{
@@ -52,12 +53,19 @@ namespace Flow.Launcher.Core.Plugin
}
}
- public static void ReloadData()
+ public static async Task ReloadData()
{
foreach(var plugin in AllPlugins)
{
- var reloadablePlugin = plugin.Plugin as IReloadable;
- reloadablePlugin?.ReloadData();
+ switch (plugin.Plugin)
+ {
+ case IReloadable p:
+ p.ReloadData();
+ break;
+ case IAsyncReloadable p:
+ await p.ReloadDataAsync();
+ break;
+ }
}
}
@@ -86,24 +94,50 @@ namespace Flow.Launcher.Core.Plugin
/// Call initialize for all plugins
///
/// return the list of failed to init plugins or null for none
- public static void InitializePlugins(IPublicAPI api)
+ public static async Task InitializePlugins(IPublicAPI api)
{
API = api;
var failedPlugins = new ConcurrentQueue();
- Parallel.ForEach(AllPlugins, pair =>
+
+ var InitTasks = AllPlugins.Select(pair => Task.Run(async delegate
{
try
{
- var milliseconds = Stopwatch.Debug($"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>", () =>
+ long milliseconds;
+
+ switch (pair.Plugin)
{
- pair.Plugin.Init(new PluginInitContext
- {
- CurrentPluginMetadata = pair.Metadata,
- API = API
- });
- });
+ case IAsyncPlugin plugin:
+ milliseconds = await Stopwatch.DebugAsync(
+ $"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>",
+ async delegate
+ {
+ await plugin.InitAsync(new PluginInitContext
+ {
+ CurrentPluginMetadata = pair.Metadata,
+ API = API
+ });
+ });
+ break;
+ case IPlugin plugin:
+ milliseconds = Stopwatch.Debug(
+ $"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>",
+ () =>
+ {
+ plugin.Init(new PluginInitContext
+ {
+ CurrentPluginMetadata = pair.Metadata,
+ API = API
+ });
+ });
+ break;
+ default:
+ throw new ArgumentException();
+ }
+
pair.Metadata.InitTime += milliseconds;
- Log.Info($"|PluginManager.InitializePlugins|Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>");
+ Log.Info(
+ $"|PluginManager.InitializePlugins|Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>");
}
catch (Exception e)
{
@@ -111,25 +145,33 @@ namespace Flow.Launcher.Core.Plugin
pair.Metadata.Disabled = true;
failedPlugins.Enqueue(pair);
}
- });
+ }));
+
+ await Task.WhenAll(InitTasks);
_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);
+ foreach (var actionKeyword in plugin.Metadata.ActionKeywords)
+ {
+ switch (actionKeyword)
+ {
+ case Query.GlobalPluginWildcardSign:
+ GlobalPlugins.Add(plugin);
+ break;
+ default:
+ NonGlobalPlugins[actionKeyword] = plugin;
+ break;
+ }
+ }
}
if (failedPlugins.Any())
{
var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name));
- API.ShowMsg($"Fail to Init Plugins", $"Plugins: {failed} - fail to load and would be disabled, please contact plugin creator for help", "", false);
+ API.ShowMsg($"Fail to Init Plugins",
+ $"Plugins: {failed} - fail to load and would be disabled, please contact plugin creator for help",
+ "", false);
}
}
@@ -138,7 +180,7 @@ namespace Flow.Launcher.Core.Plugin
if (NonGlobalPlugins.ContainsKey(query.ActionKeyword))
{
var plugin = NonGlobalPlugins[query.ActionKeyword];
- return new List { plugin };
+ return new List {plugin};
}
else
{
@@ -146,25 +188,42 @@ namespace Flow.Launcher.Core.Plugin
}
}
- public static List QueryForPlugin(PluginPair pair, Query query)
+ public static async Task> QueryForPlugin(PluginPair pair, Query query, CancellationToken token)
{
var results = new List();
try
{
var metadata = pair.Metadata;
- var milliseconds = Stopwatch.Debug($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", () =>
- {
- results = pair.Plugin.Query(query) ?? new List();
- UpdatePluginMetadata(results, metadata, query);
- });
+ var milliseconds = await Stopwatch.DebugAsync($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}",
+ async () =>
+ {
+ switch (pair.Plugin)
+ {
+ case IAsyncPlugin plugin:
+ results = await plugin.QueryAsync(query, token).ConfigureAwait(false) ??
+ new List();
+ UpdatePluginMetadata(results, metadata, query);
+ break;
+ case IPlugin plugin:
+ results = await Task.Run(() => plugin.Query(query), token).ConfigureAwait(false) ??
+ new List();
+ UpdatePluginMetadata(results, metadata, query);
+ break;
+ }
+ });
metadata.QueryCount += 1;
- metadata.AvgQueryTime = metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2;
+ metadata.AvgQueryTime =
+ metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2;
}
catch (Exception e)
{
- Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e);
+ Log.Exception(
+ $"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>",
+ e);
}
- return results;
+
+ // null will be fine since the results will only be added into queue if the token hasn't been cancelled
+ return token.IsCancellationRequested ? results = null : results;
}
public static void UpdatePluginMetadata(List results, PluginMetadata metadata, Query query)
@@ -182,11 +241,6 @@ namespace Flow.Launcher.Core.Plugin
}
}
- private static bool IsGlobalPlugin(PluginMetadata metadata)
- {
- return metadata.ActionKeywords.Contains(Query.GlobalPluginWildcardSign);
- }
-
///
/// get specified plugin, return null if not found
///
@@ -208,7 +262,7 @@ namespace Flow.Launcher.Core.Plugin
var pluginPair = _contextMenuPlugins.FirstOrDefault(o => o.Metadata.ID == result.PluginID);
if (pluginPair != null)
{
- var plugin = (IContextMenu)pluginPair.Plugin;
+ var plugin = (IContextMenu) pluginPair.Plugin;
try
{
@@ -222,16 +276,19 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
- Log.Exception($"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{pluginPair.Metadata.Name}>", e);
+ Log.Exception(
+ $"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{pluginPair.Metadata.Name}>",
+ e);
}
}
+
return results;
}
public static bool ActionKeywordRegistered(string actionKeyword)
{
return actionKeyword != Query.GlobalPluginWildcardSign
- && NonGlobalPlugins.ContainsKey(actionKeyword);
+ && NonGlobalPlugins.ContainsKey(actionKeyword);
}
///
@@ -249,6 +306,7 @@ namespace Flow.Launcher.Core.Plugin
{
NonGlobalPlugins[newActionKeyword] = plugin;
}
+
plugin.Metadata.ActionKeywords.Add(newActionKeyword);
}
@@ -262,9 +320,9 @@ namespace Flow.Launcher.Core.Plugin
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)
+ .Where(x => x == Query.GlobalPluginWildcardSign)
+ .ToList()
+ .Count == 1)
{
GlobalPlugins.Remove(plugin);
}
@@ -285,4 +343,4 @@ namespace Flow.Launcher.Core.Plugin
}
}
}
-}
+}
\ No newline at end of file
diff --git a/Flow.Launcher.Infrastructure/Stopwatch.cs b/Flow.Launcher.Infrastructure/Stopwatch.cs
index d39d90e81..dd6edaff9 100644
--- a/Flow.Launcher.Infrastructure/Stopwatch.cs
+++ b/Flow.Launcher.Infrastructure/Stopwatch.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
namespace Flow.Launcher.Infrastructure
@@ -22,7 +23,22 @@ namespace Flow.Launcher.Infrastructure
Log.Debug(info);
return milliseconds;
}
-
+
+ ///
+ /// This stopwatch will appear only in Debug mode
+ ///
+ public static async Task DebugAsync(string message, Func action)
+ {
+ var stopWatch = new System.Diagnostics.Stopwatch();
+ stopWatch.Start();
+ await action();
+ stopWatch.Stop();
+ var milliseconds = stopWatch.ElapsedMilliseconds;
+ string info = $"{message} <{milliseconds}ms>";
+ Log.Debug(info);
+ return milliseconds;
+ }
+
public static long Normal(string message, Action action)
{
var stopWatch = new System.Diagnostics.Stopwatch();
@@ -34,6 +50,20 @@ namespace Flow.Launcher.Infrastructure
Log.Info(info);
return milliseconds;
}
+
+ public static async Task NormalAsync(string message, Func action)
+ {
+ var stopWatch = new System.Diagnostics.Stopwatch();
+ stopWatch.Start();
+ await action();
+ stopWatch.Stop();
+ var milliseconds = stopWatch.ElapsedMilliseconds;
+ string info = $"{message} <{milliseconds}ms>";
+ Log.Info(info);
+ return milliseconds;
+ }
+
+
public static void StartCount(string name, Action action)
{
diff --git a/Flow.Launcher.Plugin/IAsyncPlugin.cs b/Flow.Launcher.Plugin/IAsyncPlugin.cs
new file mode 100644
index 000000000..36f098e7d
--- /dev/null
+++ b/Flow.Launcher.Plugin/IAsyncPlugin.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Flow.Launcher.Plugin
+{
+ public interface IAsyncPlugin
+ {
+ Task> QueryAsync(Query query, CancellationToken token);
+ Task InitAsync(PluginInitContext context);
+ }
+}
\ No newline at end of file
diff --git a/Flow.Launcher.Plugin/IPlugin.cs b/Flow.Launcher.Plugin/IPlugin.cs
index 8f7d279fa..4cc6d8d40 100644
--- a/Flow.Launcher.Plugin/IPlugin.cs
+++ b/Flow.Launcher.Plugin/IPlugin.cs
@@ -5,6 +5,7 @@ namespace Flow.Launcher.Plugin
public interface IPlugin
{
List Query(Query query);
+
void Init(PluginInitContext context);
}
}
\ No newline at end of file
diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs
index ccc00d5e9..12e430e07 100644
--- a/Flow.Launcher.Plugin/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/IPublicAPI.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
namespace Flow.Launcher.Plugin
{
@@ -34,7 +35,7 @@ namespace Flow.Launcher.Plugin
/// Plugin's in memory data with new content
/// added by user.
///
- void ReloadAllPluginData();
+ Task ReloadAllPluginData();
///
/// Check for new Flow Launcher update
diff --git a/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs b/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs
new file mode 100644
index 000000000..9c922f667
--- /dev/null
+++ b/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Flow.Launcher.Plugin
+{
+ public interface IAsyncReloadable
+ {
+ Task ReloadDataAsync();
+ }
+}
\ No newline at end of file
diff --git a/Flow.Launcher.Plugin/PluginPair.cs b/Flow.Launcher.Plugin/PluginPair.cs
index 910367ec6..e8954b7a0 100644
--- a/Flow.Launcher.Plugin/PluginPair.cs
+++ b/Flow.Launcher.Plugin/PluginPair.cs
@@ -2,7 +2,7 @@
{
public class PluginPair
{
- public IPlugin Plugin { get; internal set; }
+ public object Plugin { get; internal set; }
public PluginMetadata Metadata { get; internal set; }
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 90d4fff63..bcf147be7 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -78,9 +78,9 @@ namespace Flow.Launcher
ImageLoader.Save();
}
- public void ReloadAllPluginData()
+ public async Task ReloadAllPluginData()
{
- PluginManager.ReloadData();
+ await PluginManager.ReloadData();
}
public void ShowMsg(string title, string subTitle = "", string iconPath = "")
diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs
index eed30f377..bc68eb6d6 100644
--- a/Flow.Launcher/ViewModel/MainViewModel.cs
+++ b/Flow.Launcher/ViewModel/MainViewModel.cs
@@ -405,45 +405,47 @@ namespace Flow.Launcher.ViewModel
}, currentCancellationToken);
var plugins = PluginManager.ValidPluginsForQuery(query);
- Task.Run(() =>
+ Task.Run(async () =>
{
// so looping will stop once it was cancelled
+
+ Task[] tasks = new Task[plugins.Count];
var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken };
try
{
- Parallel.ForEach(plugins, parallelOptions, plugin =>
+ Parallel.For(0, plugins.Count, parallelOptions, i =>
{
- if (!plugin.Metadata.Disabled)
+ if (!plugins[i].Metadata.Disabled)
{
- try
- {
- var results = PluginManager.QueryForPlugin(plugin, query);
- UpdateResultView(results, plugin.Metadata, query);
- }
- catch(Exception e)
- {
- Log.Exception("MainViewModel", $"Exception when querying the plugin {plugin.Metadata.Name}", e, "QueryResults");
- }
+ tasks[i] = QueryTask(i, query, currentCancellationToken);
}
+ else tasks[i] = Task.CompletedTask; // Avoid Null
});
+
+ // Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first
+ await Task.WhenAll(tasks);
}
catch (OperationCanceledException)
{
// nothing to do here
}
-
// this should happen once after all queries are done so progress bar should continue
// until the end of all querying
_isQueryRunning = false;
- if (currentUpdateSource == _updateSource)
+ if (!currentCancellationToken.IsCancellationRequested)
{ // update to hidden if this is still the current query
ProgressBarVisibility = Visibility.Hidden;
}
- }, currentCancellationToken).ContinueWith(t =>
- {
- Log.Exception("MainViewModel", "Error when querying plugins", t.Exception?.InnerException, "QueryResults");
- }, TaskContinuationOptions.OnlyOnFaulted);
+
+ async Task QueryTask(int pairIndex, Query query, CancellationToken token)
+ {
+ var result = await PluginManager.QueryForPlugin(plugins[pairIndex], query, token);
+ UpdateResultView(result, plugins[pairIndex].Metadata, query);
+ }
+
+ }, currentCancellationToken).ContinueWith(t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception),
+ TaskContinuationOptions.OnlyOnFaulted);
}
}
else
diff --git a/Plugins/Flow.Launcher.Plugin.ControlPanel/ControlPanelList.cs b/Plugins/Flow.Launcher.Plugin.ControlPanel/ControlPanelList.cs
index fdcffb0b3..70afda536 100644
--- a/Plugins/Flow.Launcher.Plugin.ControlPanel/ControlPanelList.cs
+++ b/Plugins/Flow.Launcher.Plugin.ControlPanel/ControlPanelList.cs
@@ -38,7 +38,7 @@ namespace Flow.Launcher.Plugin.ControlPanel
int cxDesired, int cyDesired, uint fuLoad);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
- extern static bool DestroyIcon(IntPtr handle);
+ static extern bool DestroyIcon(IntPtr handle);
[DllImport("kernel32.dll")]
static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType);
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
index 5642b62ed..7ebab91a1 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
@@ -67,13 +68,15 @@ namespace Flow.Launcher.Plugin.Sys
{
c.TitleHighlightData = titleMatch.MatchData;
}
- else
+ else
{
c.SubTitleHighlightData = subTitleMatch.MatchData;
}
+
results.Add(c);
}
}
+
return results;
}
@@ -94,13 +97,15 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\shutdown.png",
Action = c =>
{
- var reuslt = MessageBox.Show(context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_shutdown_computer"),
- context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"),
- MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ var reuslt = MessageBox.Show(
+ context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_shutdown_computer"),
+ context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"),
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (reuslt == MessageBoxResult.Yes)
{
Process.Start("shutdown", "/s /t 0");
}
+
return true;
}
},
@@ -111,13 +116,15 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\restart.png",
Action = c =>
{
- var result = MessageBox.Show(context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer"),
- context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"),
- MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ var result = MessageBox.Show(
+ context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer"),
+ context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"),
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
{
Process.Start("shutdown", "/r /t 0");
}
+
return true;
}
},
@@ -163,14 +170,16 @@ namespace Flow.Launcher.Plugin.Sys
// http://www.pinvoke.net/default.aspx/shell32/SHEmptyRecycleBin.html
// FYI, couldn't find documentation for this but if the recycle bin is already empty, it will return -2147418113 (0x8000FFFF (E_UNEXPECTED))
// 0 for nothing
- var result = SHEmptyRecycleBin(new WindowInteropHelper(Application.Current.MainWindow).Handle, 0);
- if (result != (uint) HRESULT.S_OK && result != (uint)0x8000FFFF)
+ var result = SHEmptyRecycleBin(new WindowInteropHelper(Application.Current.MainWindow).Handle,
+ 0);
+ if (result != (uint) HRESULT.S_OK && result != (uint) 0x8000FFFF)
{
MessageBox.Show($"Error emptying recycle bin, error code: {result}\n" +
"please refer to https://msdn.microsoft.com/en-us/library/windows/desktop/aa378137",
- "Error",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ "Error",
+ MessageBoxButton.OK, MessageBoxImage.Error);
}
+
return true;
}
},
@@ -229,9 +238,13 @@ namespace Flow.Launcher.Plugin.Sys
{
// Hide the window first then show msg after done because sometimes the reload could take a while, so not to make user think it's frozen.
Application.Current.MainWindow.Hide();
- context.API.ReloadAllPluginData();
- context.API.ShowMsg(context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"),
- context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded"));
+
+ context.API.ReloadAllPluginData().ContinueWith(_ =>
+ context.API.ShowMsg(
+ context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"),
+ context.API.GetTranslation(
+ "flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded")));
+
return true;
}
},