Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
3cd6093
Plugin Async ModelAdd Full Async model, including AsyncPlugin and Asy…
taooceros Jan 2, 2021
b8f7d89
Allows Loading both IPlugin and IAsyncPlugin
taooceros Jan 2, 2021
f768b08
Move Program Plugin to Async model
taooceros Jan 2, 2021
6326d6f
Startup async
taooceros Jan 2, 2021
7be2a95
Use cancallationToken.IsCancellationRequested instead of comparing cu…
taooceros Jan 2, 2021
280b98b
Move the creation of Window later due to async operation
taooceros Jan 2, 2021
4cb4aa8
change onstartup name to async
taooceros Jan 2, 2021
d7805d7
Make Explorer plugin completely async
taooceros Jan 2, 2021
43cee65
Move PluginManagers to Async Model
taooceros Jan 2, 2021
69cb8e6
Reload IAsyncReloadable concurrently
taooceros Jan 2, 2021
6e9e51e
Error handling for OperationCancelledException in Program plugin
taooceros Jan 3, 2021
1c20069
Rebase to Dev
taooceros Jan 3, 2021
731c3cd
Use OperationCancelledException instead of catch Exception and check
taooceros Jan 3, 2021
ecf2a7a
change plugin type in pluginLoader for Release
taooceros Jan 3, 2021
63e32f1
fix testing
taooceros Jan 3, 2021
29ab9db
fix reloaddata not working and refactor Init code
taooceros Jan 6, 2021
c129b7b
Add constrctor for PluginInitContext
taooceros Jan 6, 2021
63a9e03
format QueryForPlugin code
taooceros Jan 6, 2021
8b602ce
add default constructor for plugin init context to solve error in tes…
taooceros Jan 7, 2021
d0d938b
Add Cancellationtoken for Http.cs
taooceros Jan 7, 2021
a4edbc2
Move WebSearch to Async model
taooceros Jan 7, 2021
f72b716
use string.empty instead of null
taooceros Jan 7, 2021
2b085db
Merge dev
taooceros Jan 7, 2021
a6609d6
Add Cancellationtoken for downloadasync
taooceros Jan 7, 2021
8a5f98a
Manually handling TaskCancelledException in search suggestion to avio…
taooceros Jan 7, 2021
86a9cf3
Optimize Websearch code
taooceros Jan 7, 2021
971d374
Merge Dev
taooceros Jan 7, 2021
919d5d5
add using for Bing search source
taooceros Jan 7, 2021
1003ce4
move using to more specific place in Google.cs
taooceros Jan 7, 2021
c939924
optimize code in searchsuggestions
taooceros Jan 7, 2021
92be6fd
Use Task.Yield to avoid using Parallel.For
taooceros Jan 9, 2021
8fe1fbc
Merge dev
taooceros Jan 14, 2021
a882dcd
Merge remote-tracking branch 'upstream/dev' into PluginAsyncModel
taooceros Jan 14, 2021
763b518
Add comment in IPublic, IAsyncPlugin, IReloadable, IAsyncReloadable
taooceros Jan 14, 2021
114c12b
formatting and description
jjw24 Jan 17, 2021
d741a98
fixed last index time during init
jjw24 Jan 17, 2021
969265c
fix formatting
jjw24 Jan 17, 2021
539f4bf
add eol
jjw24 Jan 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal PluginAssemblyLoader(string assemblyFilePath)
dependencyResolver = new AssemblyDependencyResolver(assemblyFilePath);
assemblyName = new AssemblyName(Path.GetFileNameWithoutExtension(assemblyFilePath));

referencedPluginPackageDependencyResolver =
referencedPluginPackageDependencyResolver =
new AssemblyDependencyResolver(Path.Combine(Constant.ProgramDirectory, "Flow.Launcher.Plugin.dll"));
}

Expand All @@ -38,15 +38,15 @@ protected override Assembly Load(AssemblyName assemblyName)
// that use Newtonsoft.Json
if (assemblyPath == null || ExistsInReferencedPluginPackage(assemblyName))
return null;

return LoadFromAssemblyPath(assemblyPath);
}

internal Type FromAssemblyGetTypeOfInterface(Assembly assembly, Type type)
internal Type FromAssemblyGetTypeOfInterface(Assembly assembly, params Type[] types)
{
var allTypes = assembly.ExportedTypes;

return allTypes.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(type));
return allTypes.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Intersect(types).Any());
}

internal bool ExistsInReferencedPluginPackage(AssemblyName assemblyName)
Expand Down
123 changes: 79 additions & 44 deletions Flow.Launcher.Core/Plugin/PluginManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
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;
Expand Down Expand Up @@ -52,13 +53,14 @@ public static void Save()
}
}

public static void ReloadData()
public static async Task ReloadData()
{
foreach(var plugin in AllPlugins)
await Task.WhenAll(AllPlugins.Select(plugin => plugin.Plugin switch
{
var reloadablePlugin = plugin.Plugin as IReloadable;
reloadablePlugin?.ReloadData();
}
IReloadable p => Task.Run(p.ReloadData),
IAsyncReloadable p => p.ReloadDataAsync(),
_ => Task.CompletedTask,
}).ToArray());
}

static PluginManager()
Expand Down Expand Up @@ -86,50 +88,62 @@ public static void LoadPlugins(PluginsSettings settings)
/// Call initialize for all plugins
/// </summary>
/// <returns>return the list of failed to init plugins or null for none</returns>
public static void InitializePlugins(IPublicAPI api)
public static async Task InitializePlugins(IPublicAPI api)
{
API = api;
var failedPlugins = new ConcurrentQueue<PluginPair>();
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}>", () =>
var milliseconds = pair.Plugin switch
{
pair.Plugin.Init(new PluginInitContext
{
CurrentPluginMetadata = pair.Metadata,
API = API
});
});
IAsyncPlugin plugin
=> await Stopwatch.DebugAsync($"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>",
() => plugin.InitAsync(new PluginInitContext(pair.Metadata, API))),
IPlugin plugin
=> Stopwatch.Debug($"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>",
() => plugin.Init(new PluginInitContext(pair.Metadata, API))),
_ => 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)
{
Log.Exception(nameof(PluginManager), $"Fail to Init plugin: {pair.Metadata.Name}", e);
pair.Metadata.Disabled = true;
pair.Metadata.Disabled = true;
failedPlugins.Enqueue(pair);
}
});
}));

await Task.WhenAll(InitTasks);

_contextMenuPlugins = GetPluginsForInterface<IContextMenu>();
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);
}
}

Expand All @@ -146,24 +160,46 @@ public static List<PluginPair> ValidPluginsForQuery(Query query)
}
}

public static List<Result> QueryForPlugin(PluginPair pair, Query query)
public static async Task<List<Result>> QueryForPlugin(PluginPair pair, Query query, CancellationToken token)
{
var results = new List<Result>();
try
{
var metadata = pair.Metadata;
var milliseconds = Stopwatch.Debug($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", () =>

long milliseconds = -1L;

switch (pair.Plugin)
{
results = pair.Plugin.Query(query) ?? new List<Result>();
UpdatePluginMetadata(results, metadata, query);
});
case IAsyncPlugin plugin:
milliseconds = await Stopwatch.DebugAsync($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}",
async () => results = await plugin.QueryAsync(query, token).ConfigureAwait(false));
break;
case IPlugin plugin:
await Task.Run(() => milliseconds = Stopwatch.Debug($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}",
() => results = plugin.Query(query)), token).ConfigureAwait(false);
break;
default:
throw new ArgumentOutOfRangeException();
}
token.ThrowIfCancellationRequested();
UpdatePluginMetadata(results, metadata, query);

metadata.QueryCount += 1;
metadata.AvgQueryTime = metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2;
metadata.AvgQueryTime =
metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2;
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
{
// null will be fine since the results will only be added into queue if the token hasn't been cancelled
return results = null;
}
catch (Exception e)
{
Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e);
}

return results;
}

Expand All @@ -182,11 +218,6 @@ public static void UpdatePluginMetadata(List<Result> results, PluginMetadata met
}
}

private static bool IsGlobalPlugin(PluginMetadata metadata)
{
return metadata.ActionKeywords.Contains(Query.GlobalPluginWildcardSign);
}

/// <summary>
/// get specified plugin, return null if not found
/// </summary>
Expand Down Expand Up @@ -222,16 +253,19 @@ public static List<Result> GetContextMenusForPlugin(Result result)
}
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);
}

/// <summary>
Expand All @@ -249,6 +283,7 @@ public static void AddActionKeyword(string id, string newActionKeyword)
{
NonGlobalPlugins[newActionKeyword] = plugin;
}

plugin.Metadata.ActionKeywords.Add(newActionKeyword);
}

Expand All @@ -262,16 +297,16 @@ public static void RemoveActionKeyword(string id, string oldActionkeyword)
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);
}

if (oldActionkeyword != Query.GlobalPluginWildcardSign)
NonGlobalPlugins.Remove(oldActionkeyword);


plugin.Metadata.ActionKeywords.Remove(oldActionkeyword);
}
Expand Down
106 changes: 54 additions & 52 deletions Flow.Launcher.Core/Plugin/PluginsLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,56 +37,59 @@ public static IEnumerable<PluginPair> DotNetPlugins(List<PluginMetadata> source)

foreach (var metadata in metadatas)
{
var milliseconds = Stopwatch.Debug($"|PluginsLoader.DotNetPlugins|Constructor init cost for {metadata.Name}", () =>
{

#if DEBUG
var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath);
var assembly = assemblyLoader.LoadAssemblyAndDependencies();
var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin));
var plugin = (IPlugin)Activator.CreateInstance(type);
#else
Assembly assembly = null;
IPlugin plugin = null;

try
var milliseconds = Stopwatch.Debug(
$"|PluginsLoader.DotNetPlugins|Constructor init cost for {metadata.Name}", () =>
{
#if DEBUG
var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath);
assembly = assemblyLoader.LoadAssemblyAndDependencies();

var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin));

plugin = (IPlugin)Activator.CreateInstance(type);
}
catch (Exception e) when (assembly == null)
{
Log.Exception($"|PluginsLoader.DotNetPlugins|Couldn't load assembly for the plugin: {metadata.Name}", e);
}
catch (InvalidOperationException e)
{
Log.Exception($"|PluginsLoader.DotNetPlugins|Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
}
catch (ReflectionTypeLoadException e)
{
Log.Exception($"|PluginsLoader.DotNetPlugins|The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
}
catch (Exception e)
{
Log.Exception($"|PluginsLoader.DotNetPlugins|The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
}
var assembly = assemblyLoader.LoadAssemblyAndDependencies();
var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin),
typeof(IAsyncPlugin));

if (plugin == null)
{
erroredPlugins.Add(metadata.Name);
return;
}
var plugin = Activator.CreateInstance(type);
#else
Assembly assembly = null;
object plugin = null;

try
{
var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath);
assembly = assemblyLoader.LoadAssemblyAndDependencies();

var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin),
typeof(IAsyncPlugin));

plugin = Activator.CreateInstance(type);
}
catch (Exception e) when (assembly == null)
{
Log.Exception($"|PluginsLoader.DotNetPlugins|Couldn't load assembly for the plugin: {metadata.Name}", e);
}
catch (InvalidOperationException e)
{
Log.Exception($"|PluginsLoader.DotNetPlugins|Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
}
catch (ReflectionTypeLoadException e)
{
Log.Exception($"|PluginsLoader.DotNetPlugins|The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
}
catch (Exception e)
{
Log.Exception($"|PluginsLoader.DotNetPlugins|The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
}

if (plugin == null)
{
erroredPlugins.Add(metadata.Name);
return;
}
#endif
plugins.Add(new PluginPair
{
Plugin = plugin,
Metadata = metadata
plugins.Add(new PluginPair
{
Plugin = plugin,
Metadata = metadata
});
});
});
metadata.InitTime += milliseconds;
}

Expand All @@ -95,15 +98,15 @@ public static IEnumerable<PluginPair> DotNetPlugins(List<PluginMetadata> source)
var errorPluginString = String.Join(Environment.NewLine, erroredPlugins);

var errorMessage = "The following "
+ (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ")
+ "errored and cannot be loaded:";
+ (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ")
+ "errored and cannot be loaded:";

Task.Run(() =>
{
MessageBox.Show($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
$"Please refer to the logs for more information","",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
$"Please refer to the logs for more information", "",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
});
}

Expand Down Expand Up @@ -179,6 +182,5 @@ public static IEnumerable<PluginPair> ExecutablePlugins(IEnumerable<PluginMetada
Metadata = metadata
});
}

}
}
}
Loading