Merge remote-tracking branch 'upstream/dev' into fix_win32_loading

This commit is contained in:
张弘韬 2021-01-17 23:37:13 +08:00
commit 61c1f2c620
115 changed files with 1866 additions and 1795 deletions

View file

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using Newtonsoft.Json;
using Flow.Launcher.Infrastructure.Exception; using Flow.Launcher.Infrastructure.Exception;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
@ -65,7 +65,7 @@ namespace Flow.Launcher.Core.Plugin
{ {
List<Result> results = new List<Result>(); List<Result> results = new List<Result>();
JsonRPCQueryResponseModel queryResponseModel = JsonConvert.DeserializeObject<JsonRPCQueryResponseModel>(output); JsonRPCQueryResponseModel queryResponseModel = JsonSerializer.Deserialize<JsonRPCQueryResponseModel>(output);
if (queryResponseModel.Result == null) return null; if (queryResponseModel.Result == null) return null;
foreach (JsonRPCResult result in queryResponseModel.Result) foreach (JsonRPCResult result in queryResponseModel.Result)
@ -84,7 +84,7 @@ namespace Flow.Launcher.Core.Plugin
else else
{ {
string actionReponse = ExecuteCallback(result1.JsonRPCAction); string actionReponse = ExecuteCallback(result1.JsonRPCAction);
JsonRPCRequestModel jsonRpcRequestModel = JsonConvert.DeserializeObject<JsonRPCRequestModel>(actionReponse); JsonRPCRequestModel jsonRpcRequestModel = JsonSerializer.Deserialize<JsonRPCRequestModel>(actionReponse);
if (jsonRpcRequestModel != null if (jsonRpcRequestModel != null
&& !String.IsNullOrEmpty(jsonRpcRequestModel.Method) && !String.IsNullOrEmpty(jsonRpcRequestModel.Method)
&& jsonRpcRequestModel.Method.StartsWith("Flow.Launcher.")) && jsonRpcRequestModel.Method.StartsWith("Flow.Launcher."))

View file

@ -20,7 +20,7 @@ namespace Flow.Launcher.Core.Plugin
dependencyResolver = new AssemblyDependencyResolver(assemblyFilePath); dependencyResolver = new AssemblyDependencyResolver(assemblyFilePath);
assemblyName = new AssemblyName(Path.GetFileNameWithoutExtension(assemblyFilePath)); assemblyName = new AssemblyName(Path.GetFileNameWithoutExtension(assemblyFilePath));
referencedPluginPackageDependencyResolver = referencedPluginPackageDependencyResolver =
new AssemblyDependencyResolver(Path.Combine(Constant.ProgramDirectory, "Flow.Launcher.Plugin.dll")); new AssemblyDependencyResolver(Path.Combine(Constant.ProgramDirectory, "Flow.Launcher.Plugin.dll"));
} }
@ -38,15 +38,15 @@ namespace Flow.Launcher.Core.Plugin
// that use Newtonsoft.Json // that use Newtonsoft.Json
if (assemblyPath == null || ExistsInReferencedPluginPackage(assemblyName)) if (assemblyPath == null || ExistsInReferencedPluginPackage(assemblyName))
return null; return null;
return LoadFromAssemblyPath(assemblyPath); return LoadFromAssemblyPath(assemblyPath);
} }
internal Type FromAssemblyGetTypeOfInterface(Assembly assembly, Type type) internal Type FromAssemblyGetTypeOfInterface(Assembly assembly, params Type[] types)
{ {
var allTypes = assembly.ExportedTypes; 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) internal bool ExistsInReferencedPluginPackage(AssemblyName assemblyName)

View file

@ -2,10 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.IO; using System.IO;
using Newtonsoft.Json;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using System.Text.Json;
namespace Flow.Launcher.Core.Plugin namespace Flow.Launcher.Core.Plugin
{ {
@ -61,7 +61,7 @@ namespace Flow.Launcher.Core.Plugin
PluginMetadata metadata; PluginMetadata metadata;
try try
{ {
metadata = JsonConvert.DeserializeObject<PluginMetadata>(File.ReadAllText(configPath)); metadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(configPath));
metadata.PluginDirectory = pluginDirectory; metadata.PluginDirectory = pluginDirectory;
// for plugins which doesn't has ActionKeywords key // for plugins which doesn't has ActionKeywords key
metadata.ActionKeywords = metadata.ActionKeywords ?? new List<string> { metadata.ActionKeyword }; metadata.ActionKeywords = metadata.ActionKeywords ?? new List<string> { metadata.ActionKeyword };

View file

@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
@ -52,13 +53,14 @@ namespace Flow.Launcher.Core.Plugin
} }
} }
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; IReloadable p => Task.Run(p.ReloadData),
reloadablePlugin?.ReloadData(); IAsyncReloadable p => p.ReloadDataAsync(),
} _ => Task.CompletedTask,
}).ToArray());
} }
static PluginManager() static PluginManager()
@ -86,50 +88,62 @@ namespace Flow.Launcher.Core.Plugin
/// Call initialize for all plugins /// Call initialize for all plugins
/// </summary> /// </summary>
/// <returns>return the list of failed to init plugins or null for none</returns> /// <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; API = api;
var failedPlugins = new ConcurrentQueue<PluginPair>(); var failedPlugins = new ConcurrentQueue<PluginPair>();
Parallel.ForEach(AllPlugins, pair =>
var InitTasks = AllPlugins.Select(pair => Task.Run(async delegate
{ {
try 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 IAsyncPlugin plugin
{ => await Stopwatch.DebugAsync($"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>",
CurrentPluginMetadata = pair.Metadata, () => plugin.InitAsync(new PluginInitContext(pair.Metadata, API))),
API = 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; 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) catch (Exception e)
{ {
Log.Exception(nameof(PluginManager), $"Fail to Init plugin: {pair.Metadata.Name}", e); Log.Exception(nameof(PluginManager), $"Fail to Init plugin: {pair.Metadata.Name}", e);
pair.Metadata.Disabled = true; pair.Metadata.Disabled = true;
failedPlugins.Enqueue(pair); failedPlugins.Enqueue(pair);
} }
}); }));
await Task.WhenAll(InitTasks);
_contextMenuPlugins = GetPluginsForInterface<IContextMenu>(); _contextMenuPlugins = GetPluginsForInterface<IContextMenu>();
foreach (var plugin in AllPlugins) foreach (var plugin in AllPlugins)
{ {
if (IsGlobalPlugin(plugin.Metadata)) foreach (var actionKeyword in plugin.Metadata.ActionKeywords)
GlobalPlugins.Add(plugin); {
switch (actionKeyword)
// Plugins may have multiple ActionKeywords, eg. WebSearch {
plugin.Metadata.ActionKeywords case Query.GlobalPluginWildcardSign:
.Where(x => x != Query.GlobalPluginWildcardSign) GlobalPlugins.Add(plugin);
.ToList() break;
.ForEach(x => NonGlobalPlugins[x] = plugin); default:
NonGlobalPlugins[actionKeyword] = plugin;
break;
}
}
} }
if (failedPlugins.Any()) if (failedPlugins.Any())
{ {
var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name)); 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);
} }
} }
@ -146,24 +160,46 @@ namespace Flow.Launcher.Core.Plugin
} }
} }
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>(); var results = new List<Result>();
try try
{ {
var metadata = pair.Metadata; 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>(); case IAsyncPlugin plugin:
UpdatePluginMetadata(results, metadata, query); 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.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) 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; return results;
} }
@ -182,11 +218,6 @@ namespace Flow.Launcher.Core.Plugin
} }
} }
private static bool IsGlobalPlugin(PluginMetadata metadata)
{
return metadata.ActionKeywords.Contains(Query.GlobalPluginWildcardSign);
}
/// <summary> /// <summary>
/// get specified plugin, return null if not found /// get specified plugin, return null if not found
/// </summary> /// </summary>
@ -222,16 +253,19 @@ namespace Flow.Launcher.Core.Plugin
} }
catch (Exception e) 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; return results;
} }
public static bool ActionKeywordRegistered(string actionKeyword) public static bool ActionKeywordRegistered(string actionKeyword)
{ {
return actionKeyword != Query.GlobalPluginWildcardSign return actionKeyword != Query.GlobalPluginWildcardSign
&& NonGlobalPlugins.ContainsKey(actionKeyword); && NonGlobalPlugins.ContainsKey(actionKeyword);
} }
/// <summary> /// <summary>
@ -249,6 +283,7 @@ namespace Flow.Launcher.Core.Plugin
{ {
NonGlobalPlugins[newActionKeyword] = plugin; NonGlobalPlugins[newActionKeyword] = plugin;
} }
plugin.Metadata.ActionKeywords.Add(newActionKeyword); plugin.Metadata.ActionKeywords.Add(newActionKeyword);
} }
@ -262,16 +297,16 @@ namespace Flow.Launcher.Core.Plugin
if (oldActionkeyword == Query.GlobalPluginWildcardSign if (oldActionkeyword == Query.GlobalPluginWildcardSign
&& // Plugins may have multiple ActionKeywords that are global, eg. WebSearch && // Plugins may have multiple ActionKeywords that are global, eg. WebSearch
plugin.Metadata.ActionKeywords plugin.Metadata.ActionKeywords
.Where(x => x == Query.GlobalPluginWildcardSign) .Where(x => x == Query.GlobalPluginWildcardSign)
.ToList() .ToList()
.Count == 1) .Count == 1)
{ {
GlobalPlugins.Remove(plugin); GlobalPlugins.Remove(plugin);
} }
if (oldActionkeyword != Query.GlobalPluginWildcardSign) if (oldActionkeyword != Query.GlobalPluginWildcardSign)
NonGlobalPlugins.Remove(oldActionkeyword); NonGlobalPlugins.Remove(oldActionkeyword);
plugin.Metadata.ActionKeywords.Remove(oldActionkeyword); plugin.Metadata.ActionKeywords.Remove(oldActionkeyword);
} }

View file

@ -37,56 +37,59 @@ namespace Flow.Launcher.Core.Plugin
foreach (var metadata in metadatas) foreach (var metadata in metadatas)
{ {
var milliseconds = Stopwatch.Debug($"|PluginsLoader.DotNetPlugins|Constructor init cost for {metadata.Name}", () => var milliseconds = Stopwatch.Debug(
{ $"|PluginsLoader.DotNetPlugins|Constructor init cost for {metadata.Name}", () =>
{
#if DEBUG #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 assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath); var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath);
assembly = assemblyLoader.LoadAssemblyAndDependencies(); var assembly = assemblyLoader.LoadAssemblyAndDependencies();
var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin),
typeof(IAsyncPlugin));
var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin)); var plugin = Activator.CreateInstance(type);
#else
Assembly assembly = null;
object plugin = null;
plugin = (IPlugin)Activator.CreateInstance(type); try
} {
catch (Exception e) when (assembly == null) var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath);
{ assembly = assemblyLoader.LoadAssemblyAndDependencies();
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) var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin),
{ typeof(IAsyncPlugin));
erroredPlugins.Add(metadata.Name);
return; 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 #endif
plugins.Add(new PluginPair plugins.Add(new PluginPair
{ {
Plugin = plugin, Plugin = plugin,
Metadata = metadata Metadata = metadata
});
}); });
});
metadata.InitTime += milliseconds; metadata.InitTime += milliseconds;
} }
@ -95,15 +98,15 @@ namespace Flow.Launcher.Core.Plugin
var errorPluginString = String.Join(Environment.NewLine, erroredPlugins); var errorPluginString = String.Join(Environment.NewLine, erroredPlugins);
var errorMessage = "The following " var errorMessage = "The following "
+ (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ") + (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ")
+ "errored and cannot be loaded:"; + "errored and cannot be loaded:";
Task.Run(() => Task.Run(() =>
{ {
MessageBox.Show($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" + MessageBox.Show($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" + $"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
$"Please refer to the logs for more information","", $"Please refer to the logs for more information", "",
MessageBoxButtons.OK, MessageBoxIcon.Warning); MessageBoxButtons.OK, MessageBoxIcon.Warning);
}); });
} }
@ -179,6 +182,5 @@ namespace Flow.Launcher.Core.Plugin
Metadata = metadata Metadata = metadata
}); });
} }
} }
} }

View file

@ -8,15 +8,14 @@ using System.Threading.Tasks;
using System.Windows; using System.Windows;
using JetBrains.Annotations; using JetBrains.Annotations;
using Squirrel; using Squirrel;
using Newtonsoft.Json;
using Flow.Launcher.Core.Resource; using Flow.Launcher.Core.Resource;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using System.IO;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using System.Text.Json.Serialization;
namespace Flow.Launcher.Core namespace Flow.Launcher.Core
{ {
@ -29,101 +28,80 @@ namespace Flow.Launcher.Core
GitHubRepository = gitHubRepository; GitHubRepository = gitHubRepository;
} }
public async Task UpdateApp(IPublicAPI api , bool silentUpdate = true) public async Task UpdateApp(IPublicAPI api, bool silentUpdate = true)
{ {
UpdateManager updateManager;
UpdateInfo newUpdateInfo;
if (!silentUpdate)
api.ShowMsg("Please wait...", "Checking for new update");
try try
{ {
updateManager = await GitHubUpdateManager(GitHubRepository); UpdateInfo newUpdateInfo;
}
catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException)
{
Log.Exception($"|Updater.UpdateApp|Please check your connection and proxy settings to api.github.com.", e);
return;
}
try
{
// UpdateApp CheckForUpdate will return value only if the app is squirrel installed
newUpdateInfo = await updateManager.CheckForUpdate().NonNull();
}
catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException)
{
Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to api.github.com.", e);
updateManager.Dispose();
return;
}
var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
var currentVersion = Version.Parse(Constant.Version);
Log.Info($"|Updater.UpdateApp|Future Release <{newUpdateInfo.FutureReleaseEntry.Formatted()}>");
if (newReleaseVersion <= currentVersion)
{
if (!silentUpdate) if (!silentUpdate)
MessageBox.Show("You already have the latest Flow Launcher version"); api.ShowMsg("Please wait...", "Checking for new update");
updateManager.Dispose();
return;
}
if (!silentUpdate) using var updateManager = await GitHubUpdateManager(GitHubRepository).ConfigureAwait(false);
api.ShowMsg("Update found", "Updating...");
try
{ // UpdateApp CheckForUpdate will return value only if the app is squirrel installed
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply); newUpdateInfo = await updateManager.CheckForUpdate().NonNull().ConfigureAwait(false);
var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
var currentVersion = Version.Parse(Constant.Version);
Log.Info($"|Updater.UpdateApp|Future Release <{newUpdateInfo.FutureReleaseEntry.Formatted()}>");
if (newReleaseVersion <= currentVersion)
{
if (!silentUpdate)
MessageBox.Show("You already have the latest Flow Launcher version");
return;
}
if (!silentUpdate)
api.ShowMsg("Update found", "Updating...");
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false);
await updateManager.ApplyReleases(newUpdateInfo).ConfigureAwait(false);
if (DataLocation.PortableDataLocationInUse())
{
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination);
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination))
MessageBox.Show("Flow Launcher was not able to move your user profile data to the new update version. Please manually " +
$"move your profile data folder from {DataLocation.PortableDataPath} to {targetDestination}");
}
else
{
await updateManager.CreateUninstallerRegistryEntry().ConfigureAwait(false);
}
var newVersionTips = NewVersinoTips(newReleaseVersion.ToString());
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
if (MessageBox.Show(newVersionTips, "New Update", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
} }
catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException)
{ {
Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e); Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
updateManager.Dispose(); api.ShowMsg("Update Failed", "Check your connection and try updating proxy settings to github-cloud.s3.amazonaws.com.");
return; return;
} }
await updateManager.ApplyReleases(newUpdateInfo);
if (DataLocation.PortableDataLocationInUse())
{
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination);
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination))
MessageBox.Show("Flow Launcher was not able to move your user profile data to the new update version. Please manually " +
$"move your profile data folder from {DataLocation.PortableDataPath} to {targetDestination}");
}
else
{
await updateManager.CreateUninstallerRegistryEntry();
}
var newVersionTips = NewVersinoTips(newReleaseVersion.ToString());
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
// always dispose UpdateManager
updateManager.Dispose();
if (MessageBox.Show(newVersionTips, "New Update", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
} }
[UsedImplicitly] [UsedImplicitly]
private class GithubRelease private class GithubRelease
{ {
[JsonProperty("prerelease")] [JsonPropertyName("prerelease")]
public bool Prerelease { get; [UsedImplicitly] set; } public bool Prerelease { get; [UsedImplicitly] set; }
[JsonProperty("published_at")] [JsonPropertyName("published_at")]
public DateTime PublishedAt { get; [UsedImplicitly] set; } public DateTime PublishedAt { get; [UsedImplicitly] set; }
[JsonProperty("html_url")] [JsonPropertyName("html_url")]
public string HtmlUrl { get; [UsedImplicitly] set; } public string HtmlUrl { get; [UsedImplicitly] set; }
} }
@ -133,13 +111,13 @@ namespace Flow.Launcher.Core
var uri = new Uri(repository); var uri = new Uri(repository);
var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases"; var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases";
var json = await Http.Get(api); var jsonStream = await Http.GetStreamAsync(api).ConfigureAwait(false);
var releases = JsonConvert.DeserializeObject<List<GithubRelease>>(json); var releases = await System.Text.Json.JsonSerializer.DeserializeAsync<List<GithubRelease>>(jsonStream).ConfigureAwait(false);
var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First(); var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First();
var latestUrl = latest.HtmlUrl.Replace("/tag/", "/download/"); var latestUrl = latest.HtmlUrl.Replace("/tag/", "/download/");
var client = new WebClient { Proxy = Http.WebProxy() }; var client = new WebClient { Proxy = Http.WebProxy };
var downloader = new FileDownloader(client); var downloader = new FileDownloader(client);
var manager = new UpdateManager(latestUrl, urlDownloader: downloader); var manager = new UpdateManager(latestUrl, urlDownloader: downloader);

View file

@ -30,10 +30,12 @@ namespace Flow.Launcher.Infrastructure
public static string PythonPath; public static string PythonPath;
public static readonly string QueryTextBoxIconImagePath = $"{ProgramDirectory}\\Images\\mainsearch.png"; public static readonly string QueryTextBoxIconImagePath = $"{ProgramDirectory}\\Images\\mainsearch.svg";
public const string DefaultTheme = "Darker"; public const string DefaultTheme = "Darker";
public const string Themes = "Themes"; public const string Themes = "Themes";
public const string Website = "https://flow-launcher.github.io";
} }
} }

View file

@ -49,7 +49,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog.Schema" Version="4.7.0-rc1" /> <PackageReference Include="NLog.Schema" Version="4.7.0-rc1" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.9.0" /> <PackageReference Include="NLog.Web.AspNetCore" Version="4.9.0" />
<PackageReference Include="System.Drawing.Common" Version="4.7.0" /> <PackageReference Include="System.Drawing.Common" Version="4.7.0" />

View file

@ -1,12 +1,18 @@
using System; using System;
using System.IO; using System.IO;
using Newtonsoft.Json; using System.Runtime.CompilerServices;
using Newtonsoft.Json.Converters; using System.Text.Json;
using System.Text.Json.Serialization;
namespace Flow.Launcher.Infrastructure namespace Flow.Launcher.Infrastructure
{ {
public static class Helper public static class Helper
{ {
static Helper()
{
jsonFormattedSerializerOptions.Converters.Add(new JsonStringEnumConverter());
}
/// <summary> /// <summary>
/// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy /// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy
/// </summary> /// </summary>
@ -65,13 +71,18 @@ namespace Flow.Launcher.Infrastructure
} }
} }
private static readonly JsonSerializerOptions jsonFormattedSerializerOptions = new JsonSerializerOptions
{
WriteIndented = true
};
public static string Formatted<T>(this T t) public static string Formatted<T>(this T t)
{ {
var formatted = JsonConvert.SerializeObject( var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions
t, {
Formatting.Indented, WriteIndented = true
new StringEnumConverter() });
);
return formatted; return formatted;
} }
} }

View file

@ -6,6 +6,9 @@ using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using System;
using System.ComponentModel;
using System.Threading;
namespace Flow.Launcher.Infrastructure.Http namespace Flow.Launcher.Infrastructure.Http
{ {
@ -13,6 +16,14 @@ namespace Flow.Launcher.Infrastructure.Http
{ {
private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko"; private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko";
private static HttpClient client;
private static SocketsHttpHandler socketsHttpHandler = new SocketsHttpHandler()
{
UseProxy = true,
Proxy = WebProxy
};
static Http() static Http()
{ {
// need to be added so it would work on a win10 machine // need to be added so it would work on a win10 machine
@ -20,58 +31,117 @@ namespace Flow.Launcher.Infrastructure.Http
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls
| SecurityProtocolType.Tls11 | SecurityProtocolType.Tls11
| SecurityProtocolType.Tls12; | SecurityProtocolType.Tls12;
client = new HttpClient(socketsHttpHandler, false);
client.DefaultRequestHeaders.Add("User-Agent", UserAgent);
} }
public static HttpProxy Proxy { private get; set; } private static HttpProxy proxy;
public static IWebProxy WebProxy()
public static HttpProxy Proxy
{ {
if (Proxy != null && Proxy.Enabled && !string.IsNullOrEmpty(Proxy.Server)) private get { return proxy; }
set
{ {
if (string.IsNullOrEmpty(Proxy.UserName) || string.IsNullOrEmpty(Proxy.Password)) proxy = value;
proxy.PropertyChanged += UpdateProxy;
}
}
public static WebProxy WebProxy { get; } = new WebProxy();
/// <summary>
/// Update the Address of the Proxy to modify the client Proxy
/// </summary>
public static void UpdateProxy(ProxyProperty property)
{
(WebProxy.Address, WebProxy.Credentials) = property switch
{
ProxyProperty.Enabled => Proxy.Enabled switch
{ {
var webProxy = new WebProxy(Proxy.Server, Proxy.Port); true => Proxy.UserName switch
return webProxy; {
var userName when !string.IsNullOrEmpty(userName) =>
(new Uri($"http://{Proxy.Server}:{Proxy.Port}"), null),
_ => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"),
new NetworkCredential(Proxy.UserName, Proxy.Password))
},
false => (null, null)
},
ProxyProperty.Server => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), WebProxy.Credentials),
ProxyProperty.Port => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), WebProxy.Credentials),
ProxyProperty.UserName => (WebProxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)),
ProxyProperty.Password => (WebProxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)),
_ => throw new ArgumentOutOfRangeException()
};
}
public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default)
{
try
{
using var response = await client.GetAsync(url, token);
if (response.StatusCode == HttpStatusCode.OK)
{
await using var fileStream = new FileStream(filePath, FileMode.CreateNew);
await response.Content.CopyToAsync(fileStream);
} }
else else
{ {
var webProxy = new WebProxy(Proxy.Server, Proxy.Port) throw new HttpRequestException($"Error code <{response.StatusCode}> returned from <{url}>");
{
Credentials = new NetworkCredential(Proxy.UserName, Proxy.Password)
};
return webProxy;
} }
} }
catch (HttpRequestException e)
{
Log.Exception("Infrastructure.Http", "Http Request Error", e, "DownloadAsync");
throw;
}
}
/// <summary>
/// Asynchrously get the result as string from url.
/// When supposing the result larger than 83kb, try using GetStreamAsync to avoid reading as string
/// </summary>
/// <param name="url"></param>
/// <returns>The Http result as string. Null if cancellation requested</returns>
public static Task<string> GetAsync([NotNull] string url, CancellationToken token = default)
{
Log.Debug($"|Http.Get|Url <{url}>");
return GetAsync(new Uri(url.Replace("#", "%23")), token);
}
/// <summary>
///
/// </summary>
/// <param name="url"></param>
/// <param name="token"></param>
/// <returns>The Http result as string. Null if cancellation requested</returns>
public static async Task<string> GetAsync([NotNull] Uri url, CancellationToken token = default)
{
Log.Debug($"|Http.Get|Url <{url}>");
using var response = await client.GetAsync(url, token);
var content = await response.Content.ReadAsStringAsync();
if (response.StatusCode == HttpStatusCode.OK)
{
return content;
}
else else
{ {
return WebRequest.GetSystemWebProxy(); throw new HttpRequestException(
$"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>");
} }
} }
public static void Download([NotNull] string url, [NotNull] string filePath) /// <summary>
{ /// Asynchrously get the result as stream from url.
var client = new WebClient { Proxy = WebProxy() }; /// </summary>
client.Headers.Add("user-agent", UserAgent); /// <param name="url"></param>
client.DownloadFile(url, filePath); /// <returns></returns>
} public static async Task<Stream> GetStreamAsync([NotNull] string url, CancellationToken token = default)
public static async Task<string> Get([NotNull] string url, string encoding = "UTF-8")
{ {
Log.Debug($"|Http.Get|Url <{url}>"); Log.Debug($"|Http.Get|Url <{url}>");
var request = WebRequest.CreateHttp(url); var response = await client.GetAsync(url, token);
request.Method = "GET"; return await response.Content.ReadAsStreamAsync();
request.Timeout = 1000;
request.Proxy = WebProxy();
request.UserAgent = UserAgent;
var response = await request.GetResponseAsync() as HttpWebResponse;
response = response.NonNull();
var stream = response.GetResponseStream().NonNull();
using var reader = new StreamReader(stream, Encoding.GetEncoding(encoding));
var content = await reader.ReadToEndAsync();
if (response.StatusCode != HttpStatusCode.OK)
throw new HttpRequestException($"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>");
return content;
} }
} }
} }

View file

@ -128,7 +128,7 @@ namespace Flow.Launcher.Infrastructure.Logger
public static void Exception(string message, System.Exception e) public static void Exception(string message, System.Exception e)
{ {
#if DEBUG #if DEBUG
throw e; throw e;
#else #else
if (FormatValid(message)) if (FormatValid(message))
{ {

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
namespace Flow.Launcher.Infrastructure namespace Flow.Launcher.Infrastructure
@ -22,7 +23,22 @@ namespace Flow.Launcher.Infrastructure
Log.Debug(info); Log.Debug(info);
return milliseconds; return milliseconds;
} }
/// <summary>
/// This stopwatch will appear only in Debug mode
/// </summary>
public static async Task<long> DebugAsync(string message, Func<Task> 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) public static long Normal(string message, Action action)
{ {
var stopWatch = new System.Diagnostics.Stopwatch(); var stopWatch = new System.Diagnostics.Stopwatch();
@ -34,6 +50,20 @@ namespace Flow.Launcher.Infrastructure
Log.Info(info); Log.Info(info);
return milliseconds; return milliseconds;
} }
public static async Task<long> NormalAsync(string message, Func<Task> 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) public static void StartCount(string name, Action action)
{ {

View file

@ -1,7 +1,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using Newtonsoft.Json; using System.Text.Json;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
namespace Flow.Launcher.Infrastructure.Storage namespace Flow.Launcher.Infrastructure.Storage
@ -9,9 +9,9 @@ namespace Flow.Launcher.Infrastructure.Storage
/// <summary> /// <summary>
/// Serialize object using json format. /// Serialize object using json format.
/// </summary> /// </summary>
public class JsonStrorage<T> public class JsonStrorage<T> where T : new()
{ {
private readonly JsonSerializerSettings _serializerSettings; private readonly JsonSerializerOptions _serializerSettings;
private T _data; private T _data;
// need a new directory name // need a new directory name
public const string DirectoryName = "Settings"; public const string DirectoryName = "Settings";
@ -24,10 +24,9 @@ namespace Flow.Launcher.Infrastructure.Storage
{ {
// use property initialization instead of DefaultValueAttribute // use property initialization instead of DefaultValueAttribute
// easier and flexible for default value of object // easier and flexible for default value of object
_serializerSettings = new JsonSerializerSettings _serializerSettings = new JsonSerializerOptions
{ {
ObjectCreationHandling = ObjectCreationHandling.Replace, IgnoreNullValues = false
NullValueHandling = NullValueHandling.Ignore
}; };
} }
@ -56,7 +55,7 @@ namespace Flow.Launcher.Infrastructure.Storage
{ {
try try
{ {
_data = JsonConvert.DeserializeObject<T>(searlized, _serializerSettings); _data = JsonSerializer.Deserialize<T>(searlized, _serializerSettings);
} }
catch (JsonException e) catch (JsonException e)
{ {
@ -77,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Storage
BackupOriginFile(); BackupOriginFile();
} }
_data = JsonConvert.DeserializeObject<T>("{}", _serializerSettings); _data = new T();
Save(); Save();
} }
@ -94,7 +93,8 @@ namespace Flow.Launcher.Infrastructure.Storage
public void Save() public void Save()
{ {
string serialized = JsonConvert.SerializeObject(_data, Formatting.Indented); string serialized = JsonSerializer.Serialize(_data, new JsonSerializerOptions() { WriteIndented = true });
File.WriteAllText(FilePath, serialized); File.WriteAllText(FilePath, serialized);
} }
} }

View file

@ -1,11 +1,80 @@
namespace Flow.Launcher.Infrastructure.UserSettings using System.ComponentModel;
namespace Flow.Launcher.Infrastructure.UserSettings
{ {
public enum ProxyProperty
{
Enabled,
Server,
Port,
UserName,
Password
}
public class HttpProxy public class HttpProxy
{ {
public bool Enabled { get; set; } = false; private bool _enabled = false;
public string Server { get; set; } private string _server;
public int Port { get; set; } private int _port;
public string UserName { get; set; } private string _userName;
public string Password { get; set; } private string _password;
public bool Enabled
{
get => _enabled;
set
{
_enabled = value;
OnPropertyChanged(ProxyProperty.Enabled);
}
}
public string Server
{
get => _server;
set
{
_server = value;
OnPropertyChanged(ProxyProperty.Server);
}
}
public int Port
{
get => _port;
set
{
_port = value;
OnPropertyChanged(ProxyProperty.Port);
}
}
public string UserName
{
get => _userName;
set
{
_userName = value;
OnPropertyChanged(ProxyProperty.UserName);
}
}
public string Password
{
get => _password;
set
{
_password = value;
OnPropertyChanged(ProxyProperty.Password);
}
}
public delegate void ProxyPropertyChangedHandler(ProxyProperty property);
public event ProxyPropertyChangedHandler PropertyChanged;
private void OnPropertyChanged(ProxyProperty property)
{
PropertyChanged?.Invoke(property);
}
} }
} }

View file

@ -31,6 +31,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
metadata.ActionKeyword = settings.ActionKeywords[0]; metadata.ActionKeyword = settings.ActionKeywords[0];
} }
metadata.Disabled = settings.Disabled; metadata.Disabled = settings.Disabled;
metadata.Priority = settings.Priority;
} }
else else
{ {
@ -40,7 +41,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Name = metadata.Name, Name = metadata.Name,
Version = metadata.Version, Version = metadata.Version,
ActionKeywords = metadata.ActionKeywords, ActionKeywords = metadata.ActionKeywords,
Disabled = metadata.Disabled Disabled = metadata.Disabled,
Priority = metadata.Priority
}; };
} }
} }
@ -52,6 +54,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public string Name { get; set; } public string Name { get; set; }
public string Version { get; set; } public string Version { get; set; }
public List<string> ActionKeywords { get; set; } // a reference of the action keywords from plugin manager public List<string> ActionKeywords { get; set; } // a reference of the action keywords from plugin manager
public int Priority { get; set; }
/// <summary> /// <summary>
/// Used only to save the state of the plugin in settings /// Used only to save the state of the plugin in settings

View file

@ -1,8 +1,7 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Drawing; using System.Drawing;
using Newtonsoft.Json; using System.Text.Json.Serialization;
using Newtonsoft.Json.Converters;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings namespace Flow.Launcher.Infrastructure.UserSettings
@ -16,7 +15,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public bool ShowOpenResultHotkey { get; set; } = true; public bool ShowOpenResultHotkey { get; set; } = true;
public string Language public string Language
{ {
get => language; set { get => language; set
{
language = value; language = value;
OnPropertyChanged(); OnPropertyChanged();
} }
@ -73,9 +73,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public int MaxResultsToShow { get; set; } = 5; public int MaxResultsToShow { get; set; } = 5;
public int ActivateTimes { get; set; } public int ActivateTimes { get; set; }
// Order defaults to 0 or -1, so 1 will let this property appear last
[JsonProperty(Order = 1)]
public PluginsSettings PluginSettings { get; set; } = new PluginsSettings();
public ObservableCollection<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>(); public ObservableCollection<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>();
public bool DontPromptUpdateMsg { get; set; } public bool DontPromptUpdateMsg { get; set; }
@ -100,8 +98,12 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public HttpProxy Proxy { get; set; } = new HttpProxy(); public HttpProxy Proxy { get; set; } = new HttpProxy();
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(JsonStringEnumConverter))]
public LastQueryMode LastQueryMode { get; set; } = LastQueryMode.Selected; public LastQueryMode LastQueryMode { get; set; } = LastQueryMode.Selected;
// This needs to be loaded last by staying at the bottom
public PluginsSettings PluginSettings { get; set; } = new PluginsSettings();
} }
public enum LastQueryMode public enum LastQueryMode

View file

@ -14,10 +14,10 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<Version>1.3.0</Version> <Version>1.3.1</Version>
<PackageVersion>1.3.0</PackageVersion> <PackageVersion>1.3.1</PackageVersion>
<AssemblyVersion>1.3.0</AssemblyVersion> <AssemblyVersion>1.3.1</AssemblyVersion>
<FileVersion>1.3.0</FileVersion> <FileVersion>1.3.1</FileVersion>
<PackageId>Flow.Launcher.Plugin</PackageId> <PackageId>Flow.Launcher.Plugin</PackageId>
<Authors>Flow-Launcher</Authors> <Authors>Flow-Launcher</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
@ -62,7 +62,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" /> <PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Flow.Launcher.Plugin
{
/// <summary>
/// Asynchronous Plugin Model for Flow Launcher
/// </summary>
public interface IAsyncPlugin
{
/// <summary>
/// Asynchronous Querying
/// </summary>
/// <para>
/// If the Querying or Init method requires high IO transmission
/// or performing CPU intense jobs (performing better with cancellation), please use this IAsyncPlugin interface
/// </para>
/// <param name="query">Query to search</param>
/// <param name="token">Cancel when querying job is obsolete</param>
/// <returns></returns>
Task<List<Result>> QueryAsync(Query query, CancellationToken token);
/// <summary>
/// Initialize plugin asynchrously (will still wait finish to continue)
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task InitAsync(PluginInitContext context);
}
}

View file

@ -2,9 +2,30 @@
namespace Flow.Launcher.Plugin namespace Flow.Launcher.Plugin
{ {
/// <summary>
/// Synchronous Plugin Model for Flow Launcher
/// <para>
/// If the Querying or Init method requires high IO transmission
/// or performaing CPU intense jobs (performing better with cancellation), please try the IAsyncPlugin interface
/// </para>
/// </summary>
public interface IPlugin public interface IPlugin
{ {
/// <summary>
/// Querying when user's search changes
/// <para>
/// This method will be called within a Task.Run,
/// so please avoid synchrously wait for long.
/// </para>
/// </summary>
/// <param name="query">Query to search</param>
/// <returns></returns>
List<Result> Query(Query query); List<Result> Query(Query query);
/// <summary>
/// Initialize plugin
/// </summary>
/// <param name="context"></param>
void Init(PluginInitContext context); void Init(PluginInitContext context);
} }
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
namespace Flow.Launcher.Plugin namespace Flow.Launcher.Plugin
{ {
@ -34,7 +35,7 @@ namespace Flow.Launcher.Plugin
/// Plugin's in memory data with new content /// Plugin's in memory data with new content
/// added by user. /// added by user.
/// </summary> /// </summary>
void ReloadAllPluginData(); Task ReloadAllPluginData();
/// <summary> /// <summary>
/// Check for new Flow Launcher update /// Check for new Flow Launcher update

View file

@ -0,0 +1,20 @@
using System.Threading.Tasks;
namespace Flow.Launcher.Plugin
{
/// <summary>
/// This interface is to indicate and allow plugins to asyncronously reload their
/// in memory data cache or other mediums when user makes a new change
/// that is not immediately captured. For example, for BrowserBookmark and Program
/// plugin does not automatically detect when a user added a new bookmark or program,
/// so this interface's function is exposed to allow user manually do the reloading after
/// those new additions.
///
/// The command that allows user to manual reload is exposed via Plugin.Sys, and
/// it will call the plugins that have implemented this interface.
/// </summary>
public interface IAsyncReloadable
{
Task ReloadDataAsync();
}
}

View file

@ -1,7 +1,7 @@
namespace Flow.Launcher.Plugin namespace Flow.Launcher.Plugin
{ {
/// <summary> /// <summary>
/// This interface is to indicate and allow plugins to reload their /// This interface is to indicate and allow plugins to synchronously reload their
/// in memory data cache or other mediums when user makes a new change /// in memory data cache or other mediums when user makes a new change
/// that is not immediately captured. For example, for BrowserBookmark and Program /// that is not immediately captured. For example, for BrowserBookmark and Program
/// plugin does not automatically detect when a user added a new bookmark or program, /// plugin does not automatically detect when a user added a new bookmark or program,
@ -10,6 +10,10 @@
/// ///
/// The command that allows user to manual reload is exposed via Plugin.Sys, and /// The command that allows user to manual reload is exposed via Plugin.Sys, and
/// it will call the plugins that have implemented this interface. /// it will call the plugins that have implemented this interface.
///
/// <para>
/// If requiring reloading data asynchronously, please use the IAsyncReloadable interface
/// </para>
/// </summary> /// </summary>
public interface IReloadable public interface IReloadable
{ {

View file

@ -4,6 +4,16 @@ namespace Flow.Launcher.Plugin
{ {
public class PluginInitContext public class PluginInitContext
{ {
public PluginInitContext()
{
}
public PluginInitContext(PluginMetadata currentPluginMetadata, IPublicAPI api)
{
CurrentPluginMetadata = currentPluginMetadata;
API = api;
}
public PluginMetadata CurrentPluginMetadata { get; internal set; } public PluginMetadata CurrentPluginMetadata { get; internal set; }
/// <summary> /// <summary>

View file

@ -1,11 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Newtonsoft.Json; using System.Text.Json.Serialization;
namespace Flow.Launcher.Plugin namespace Flow.Launcher.Plugin
{ {
[JsonObject(MemberSerialization.OptOut)]
public class PluginMetadata : BaseModel public class PluginMetadata : BaseModel
{ {
private string _pluginDirectory; private string _pluginDirectory;
@ -37,12 +36,15 @@ namespace Flow.Launcher.Plugin
public List<string> ActionKeywords { get; set; } public List<string> ActionKeywords { get; set; }
public string IcoPath { get; set;} public string IcoPath { get; set;}
public override string ToString() public override string ToString()
{ {
return Name; return Name;
} }
[JsonIgnore]
public int Priority { get; set; }
/// <summary> /// <summary>
/// Init time include both plugin load time and init time /// Init time include both plugin load time and init time
/// </summary> /// </summary>

View file

@ -2,7 +2,7 @@
{ {
public class PluginPair public class PluginPair
{ {
public IPlugin Plugin { get; internal set; } public object Plugin { get; internal set; }
public PluginMetadata Metadata { get; internal set; } public PluginMetadata Metadata { get; internal set; }

View file

@ -7,6 +7,8 @@ using Flow.Launcher.Plugin.SharedCommands;
using NUnit.Framework; using NUnit.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Flow.Launcher.Test.Plugins namespace Flow.Launcher.Test.Plugins
{ {
@ -17,15 +19,15 @@ namespace Flow.Launcher.Test.Plugins
[TestFixture] [TestFixture]
public class ExplorerTest public class ExplorerTest
{ {
private List<Result> MethodWindowsIndexSearchReturnsZeroResults(Query dummyQuery, string dummyString) private async Task<List<Result>> MethodWindowsIndexSearchReturnsZeroResultsAsync(Query dummyQuery, string dummyString, CancellationToken dummyToken)
{ {
return new List<Result>(); return new List<Result>();
} }
private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString) private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString)
{ {
return new List<Result> return new List<Result>
{ {
new Result new Result
{ {
Title="Result 1" Title="Result 1"
@ -64,10 +66,10 @@ namespace Flow.Launcher.Test.Plugins
{ {
// Given // Given
var queryConstructor = new QueryConstructor(new Settings()); var queryConstructor = new QueryConstructor(new Settings());
//When //When
var queryString = queryConstructor.QueryForTopLevelDirectorySearch(folderPath); var queryString = queryConstructor.QueryForTopLevelDirectorySearch(folderPath);
// Then // Then
Assert.IsTrue(queryString == expectedString, Assert.IsTrue(queryString == expectedString,
$"Expected string: {expectedString}{Environment.NewLine} " + $"Expected string: {expectedString}{Environment.NewLine} " +
@ -112,7 +114,7 @@ namespace Flow.Launcher.Test.Plugins
} }
[TestCase("scope='file:'")] [TestCase("scope='file:'")]
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString) public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString)
{ {
// Given // Given
var queryConstructor = new QueryConstructor(new Settings()); var queryConstructor = new QueryConstructor(new Settings());
@ -130,7 +132,7 @@ namespace Flow.Launcher.Test.Plugins
"FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " + "FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:'")] "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:'")]
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString( public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString(
string userSearchString, string expectedString) string userSearchString, string expectedString)
{ {
// Given // Given
var queryConstructor = new QueryConstructor(new Settings()); var queryConstructor = new QueryConstructor(new Settings());
@ -145,18 +147,19 @@ namespace Flow.Launcher.Test.Plugins
} }
[TestCase] [TestCase]
public void GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldContinueDirectoryInfoClassSearch() public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldContinueDirectoryInfoClassSearch()
{ {
// Given // Given
var searchManager = new SearchManager(new Settings(), new PluginInitContext()); var searchManager = new SearchManager(new Settings(), new PluginInitContext());
// When // When
var results = searchManager.TopLevelDirectorySearchBehaviour( var results = await searchManager.TopLevelDirectorySearchBehaviourAsync(
MethodWindowsIndexSearchReturnsZeroResults, MethodWindowsIndexSearchReturnsZeroResultsAsync,
MethodDirectoryInfoClassSearchReturnsTwoResults, MethodDirectoryInfoClassSearchReturnsTwoResults,
false, false,
new Query(), new Query(),
"string not used"); "string not used",
default);
// Then // Then
Assert.IsTrue(results.Count == 2, Assert.IsTrue(results.Count == 2,
@ -165,18 +168,19 @@ namespace Flow.Launcher.Test.Plugins
} }
[TestCase] [TestCase]
public void GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearch() public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearch()
{ {
// Given // Given
var searchManager = new SearchManager(new Settings(), new PluginInitContext()); var searchManager = new SearchManager(new Settings(), new PluginInitContext());
// When // When
var results = searchManager.TopLevelDirectorySearchBehaviour( var results = await searchManager.TopLevelDirectorySearchBehaviourAsync(
MethodWindowsIndexSearchReturnsZeroResults, MethodWindowsIndexSearchReturnsZeroResultsAsync,
MethodDirectoryInfoClassSearchReturnsTwoResults, MethodDirectoryInfoClassSearchReturnsTwoResults,
true, true,
new Query(), new Query(),
"string not used"); "string not used",
default);
// Then // Then
Assert.IsTrue(results.Count == 0, Assert.IsTrue(results.Count == 0,
@ -223,7 +227,7 @@ namespace Flow.Launcher.Test.Plugins
var query = new Query { ActionKeyword = "doc:", Search = "search term" }; var query = new Query { ActionKeyword = "doc:", Search = "search term" };
var searchManager = new SearchManager(new Settings(), new PluginInitContext()); var searchManager = new SearchManager(new Settings(), new PluginInitContext());
// When // When
var result = searchManager.IsFileContentSearch(query.ActionKeyword); var result = searchManager.IsFileContentSearch(query.ActionKeyword);
@ -250,7 +254,7 @@ namespace Flow.Launcher.Test.Plugins
$"Actual check result is {result} {Environment.NewLine}"); $"Actual check result is {result} {Environment.NewLine}");
} }
[TestCase(@"C:\SomeFolder\SomeApp", true, @"C:\SomeFolder\")] [TestCase(@"C:\SomeFolder\SomeApp", true, @"C:\SomeFolder\")]
[TestCase(@"C:\SomeFolder\SomeApp\SomeFile", true, @"C:\SomeFolder\SomeApp\")] [TestCase(@"C:\SomeFolder\SomeApp\SomeFile", true, @"C:\SomeFolder\SomeApp\")]
[TestCase(@"C:\NonExistentFolder\SomeApp", false, "")] [TestCase(@"C:\NonExistentFolder\SomeApp", false, "")]
@ -294,7 +298,7 @@ namespace Flow.Launcher.Test.Plugins
[TestCase("c:\\SomeFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' " + [TestCase("c:\\SomeFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' " +
"OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND " + "OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND " +
"scope='file:c:\\SomeFolder'")] "scope='file:c:\\SomeFolder'")]
public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString(string path, string expectedString) public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString(string path, string expectedString)
{ {
// Given // Given
var queryConstructor = new QueryConstructor(new Settings()); var queryConstructor = new QueryConstructor(new Settings());
@ -308,7 +312,7 @@ namespace Flow.Launcher.Test.Plugins
$"Actual string was: {resultString}{Environment.NewLine}"); $"Actual string was: {resultString}{Environment.NewLine}");
} }
[TestCase("c:\\somefolder\\>somefile","*somefile*")] [TestCase("c:\\somefolder\\>somefile", "*somefile*")]
[TestCase("c:\\somefolder\\somefile", "somefile*")] [TestCase("c:\\somefolder\\somefile", "somefile*")]
[TestCase("c:\\somefolder\\", "*")] [TestCase("c:\\somefolder\\", "*")]
public void GivenDirectoryInfoSearch_WhenSearchPatternHotKeyIsSearchAll_ThenSearchCriteriaShouldUseCriteriaString(string path, string expectedString) public void GivenDirectoryInfoSearch_WhenSearchPatternHotKeyIsSearchAll_ThenSearchCriteriaShouldUseCriteriaString(string path, string expectedString)

View file

@ -20,7 +20,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher", "Flow.Launc
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {F9C4C081-4CC3-4146-95F1-E102B4E10A5F} {F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}
{59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E} {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E}
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}
{F35190AA-4758-4D9E-A193-E3BDF6AD3567} = {F35190AA-4758-4D9E-A193-E3BDF6AD3567}
{9B130CC5-14FB-41FF-B310-0A95B6894C37} = {9B130CC5-14FB-41FF-B310-0A95B6894C37} {9B130CC5-14FB-41FF-B310-0A95B6894C37} = {9B130CC5-14FB-41FF-B310-0A95B6894C37}
{FDED22C8-B637-42E8-824A-63B5B6E05A3A} = {FDED22C8-B637-42E8-824A-63B5B6E05A3A} {FDED22C8-B637-42E8-824A-63B5B6E05A3A} = {FDED22C8-B637-42E8-824A-63B5B6E05A3A}
{A3DCCBCA-ACC1-421D-B16E-210896234C26} = {A3DCCBCA-ACC1-421D-B16E-210896234C26} {A3DCCBCA-ACC1-421D-B16E-210896234C26} = {A3DCCBCA-ACC1-421D-B16E-210896234C26}
@ -44,8 +43,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Sys",
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Url", "Plugins\Flow.Launcher.Plugin.Url\Flow.Launcher.Plugin.Url.csproj", "{A3DCCBCA-ACC1-421D-B16E-210896234C26}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Url", "Plugins\Flow.Launcher.Plugin.Url\Flow.Launcher.Plugin.Url.csproj", "{A3DCCBCA-ACC1-421D-B16E-210896234C26}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Color", "Plugins\Flow.Launcher.Plugin.Color\Flow.Launcher.Plugin.Color.csproj", "{F35190AA-4758-4D9E-A193-E3BDF6AD3567}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FFD651C7-0546-441F-BC8C-D4EE8FD01EA7}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FFD651C7-0546-441F-BC8C-D4EE8FD01EA7}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.gitattributes = .gitattributes .gitattributes = .gitattributes
@ -214,18 +211,6 @@ Global
{A3DCCBCA-ACC1-421D-B16E-210896234C26}.Release|x64.Build.0 = Release|Any CPU {A3DCCBCA-ACC1-421D-B16E-210896234C26}.Release|x64.Build.0 = Release|Any CPU
{A3DCCBCA-ACC1-421D-B16E-210896234C26}.Release|x86.ActiveCfg = Release|Any CPU {A3DCCBCA-ACC1-421D-B16E-210896234C26}.Release|x86.ActiveCfg = Release|Any CPU
{A3DCCBCA-ACC1-421D-B16E-210896234C26}.Release|x86.Build.0 = Release|Any CPU {A3DCCBCA-ACC1-421D-B16E-210896234C26}.Release|x86.Build.0 = Release|Any CPU
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|x64.ActiveCfg = Debug|Any CPU
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|x64.Build.0 = Debug|Any CPU
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|x86.ActiveCfg = Debug|Any CPU
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|x86.Build.0 = Debug|Any CPU
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|Any CPU.Build.0 = Release|Any CPU
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|x64.ActiveCfg = Release|Any CPU
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|x64.Build.0 = Release|Any CPU
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|x86.ActiveCfg = Release|Any CPU
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|x86.Build.0 = Release|Any CPU
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|x64.ActiveCfg = Debug|Any CPU {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -309,7 +294,6 @@ Global
{FDED22C8-B637-42E8-824A-63B5B6E05A3A} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {FDED22C8-B637-42E8-824A-63B5B6E05A3A} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{0B9DE348-9361-4940-ADB6-F5953BFFCCEC} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {0B9DE348-9361-4940-ADB6-F5953BFFCCEC} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{A3DCCBCA-ACC1-421D-B16E-210896234C26} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {A3DCCBCA-ACC1-421D-B16E-210896234C26} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{F35190AA-4758-4D9E-A193-E3BDF6AD3567} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{9B130CC5-14FB-41FF-B310-0A95B6894C37} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {9B130CC5-14FB-41FF-B310-0A95B6894C37} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{59BD9891-3837-438A-958D-ADC7F91F6F7E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}

View file

@ -3,7 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.modernwpf.com/2019" xmlns:ui="http://schemas.modernwpf.com/2019"
ShutdownMode="OnMainWindowClose" ShutdownMode="OnMainWindowClose"
Startup="OnStartup"> Startup="OnStartupAsync">
<Application.Resources> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>

View file

@ -45,9 +45,9 @@ namespace Flow.Launcher
} }
} }
private void OnStartup(object sender, StartupEventArgs e) private async void OnStartupAsync(object sender, StartupEventArgs e)
{ {
Stopwatch.Normal("|App.OnStartup|Startup cost", () => await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
{ {
_portable.PreStartCleanUpAfterPortabilityUpdate(); _portable.PreStartCleanUpAfterPortabilityUpdate();
@ -68,9 +68,10 @@ namespace Flow.Launcher
PluginManager.LoadPlugins(_settings.PluginSettings); PluginManager.LoadPlugins(_settings.PluginSettings);
_mainVM = new MainViewModel(_settings); _mainVM = new MainViewModel(_settings);
var window = new MainWindow(_settings, _mainVM);
API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet); API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet);
PluginManager.InitializePlugins(API); await PluginManager.InitializePlugins(API);
var window = new MainWindow(_settings, _mainVM);
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}"); Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
Current.MainWindow = window; Current.MainWindow = window;

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> <Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
@ -60,6 +60,12 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Images\*.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\*.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -78,7 +84,8 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="PropertyChanged.Fody" Version="2.5.13" /> <PackageReference Include="PropertyChanged.Fody" Version="3.3.1" />
<PackageReference Include="SharpVectors" Version="1.7.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -87,116 +94,7 @@
<ProjectReference Include="..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" /> <ProjectReference Include="..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="Images\app.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Resource Include="Images\app.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Resource>
<None Update="Images\app_error.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\Browser.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\calculator.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\cancel.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\close.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\cmd.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\color.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\copy.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\down.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\EXE.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\file.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\find.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\folder.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\history.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\image.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\Link.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\lock.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\logoff.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\mainsearch.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\New Message.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\ok.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\open.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\plugin.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\recyclebin.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\restart.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\search.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\settings.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\shutdown.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\sleep.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\up.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\update.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\warning.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent"> <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="taskkill /f /fi &quot;IMAGENAME eq Flow.Launcher.exe&quot;" /> <Exec Command="taskkill /f /fi &quot;IMAGENAME eq Flow.Launcher.exe&quot;" />
</Target> </Target>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="powershell.exe -NoProfile -ExecutionPolicy Bypass -File $(SolutionDir)Scripts\post_build.ps1 $(ConfigurationName) $(SolutionDir) $(TargetPath)" />
</Target>
</Project> </Project>

View file

@ -0,0 +1,10 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="1200px" height="1200px" viewBox="0 0 12000 12000" preserveAspectRatio="xMidYMid meet">
<g id="layer101" fill="#000000" stroke="none">
</g>
<g id="layer102" fill="#555555" stroke="none">
<path d="M10354 10962 c-28 -11 -75 -35 -105 -55 -33 -21 -773 -754 -1879 -1861 -1004 -1004 -1829 -1826 -1834 -1826 -4 0 -38 22 -75 48 -248 179 -578 351 -869 453 -398 139 -790 198 -1232 185 -748 -20 -1407 -259 -2020 -732 -209 -161 -508 -475 -676 -709 -270 -377 -476 -847 -567 -1295 -53 -261 -67 -406 -67 -700 0 -340 26 -556 105 -861 128 -499 376 -976 715 -1374 86 -101 312 -324 410 -406 521 -434 1162 -709 1830 -784 181 -20 577 -20 758 0 657 75 1252 323 1782 744 144 114 451 426 556 566 176 233 281 404 393 635 223 465 332 947 332 1470 0 394 -50 705 -174 1082 -53 160 -62 182 -135 343 -85 186 -212 407 -332 575 -28 39 -50 73 -50 78 0 4 826 833 1835 1842 1386 1386 1843 1849 1869 1894 21 34 42 90 52 134 14 64 15 85 4 146 -28 163 -140 311 -290 383 -69 34 -83 37 -180 40 -85 3 -115 0 -156 -15z m-5669 -3912 c529 -49 1009 -241 1415 -566 109 -88 296 -275 384 -384 667 -833 762 -1990 237 -2910 -352 -619 -923 -1053 -1621 -1234 -394 -101 -878 -101 -1270 1 -382 99 -690 253 -992 496 -501 402 -828 974 -930 1627 -31 194 -31 576 0 770 40 255 120 520 225 740 326 682 944 1192 1677 1383 157 41 275 61 480 80 81 8 293 6 395 -3z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -43,6 +43,8 @@
<system:String x:Key="actionKeywords">Action keyword:</system:String> <system:String x:Key="actionKeywords">Action keyword:</system:String>
<system:String x:Key="currentActionKeywords">Current action keyword:</system:String> <system:String x:Key="currentActionKeywords">Current action keyword:</system:String>
<system:String x:Key="newActionKeyword">New action keyword:</system:String> <system:String x:Key="newActionKeyword">New action keyword:</system:String>
<system:String x:Key="currentPriority">Current Priority: </system:String>
<system:String x:Key="newPriority">New Priority: </system:String>
<system:String x:Key="pluginDirectory">Plugin Directory</system:String> <system:String x:Key="pluginDirectory">Plugin Directory</system:String>
<system:String x:Key="author">Author</system:String> <system:String x:Key="author">Author</system:String>
<system:String x:Key="plugin_init_time">Init time:</system:String> <system:String x:Key="plugin_init_time">Init time:</system:String>
@ -72,7 +74,7 @@
<system:String x:Key="deleteCustomHotkeyWarning">Are you sure you want to delete {0} plugin hotkey?</system:String> <system:String x:Key="deleteCustomHotkeyWarning">Are you sure you want to delete {0} plugin hotkey?</system:String>
<system:String x:Key="queryWindowShadowEffect">Query window shadow effect</system:String> <system:String x:Key="queryWindowShadowEffect">Query window shadow effect</system:String>
<system:String x:Key="shadowEffectCPUUsage">Shadow effect has a substantial usage of GPU.</system:String> <system:String x:Key="shadowEffectCPUUsage">Shadow effect has a substantial usage of GPU.</system:String>
<system:String x:Key="shadowEffectPerformance">Not recommended if you computer performance is limited.</system:String> <system:String x:Key="shadowEffectPerformance">Not recommended if your computer performance is limited.</system:String>
<!--Setting Proxy--> <!--Setting Proxy-->
<system:String x:Key="proxy">HTTP Proxy</system:String> <system:String x:Key="proxy">HTTP Proxy</system:String>
@ -104,6 +106,10 @@
</system:String> </system:String>
<system:String x:Key="releaseNotes">Release Notes</system:String> <system:String x:Key="releaseNotes">Release Notes</system:String>
<!--Priority Setting Dialog-->
<system:String x:Key="priority_tips">Greater the number, the higher the result will be ranked. Try setting it as 5. If you want the results to be lower than any other plugin's, provide a negative number</system:String>
<system:String x:Key="invalidPriority">Please provide an valid integer for Priority!</system:String>
<!--Action Keyword Setting Dialog--> <!--Action Keyword Setting Dialog-->
<system:String x:Key="oldActionKeywords">Old Action Keyword</system:String> <system:String x:Key="oldActionKeywords">Old Action Keyword</system:String>
<system:String x:Key="newActionKeywords">New Action Keyword</system:String> <system:String x:Key="newActionKeywords">New Action Keyword</system:String>

View file

@ -45,8 +45,8 @@
<system:String x:Key="newActionKeyword">Nová akcia skratky:</system:String> <system:String x:Key="newActionKeyword">Nová akcia skratky:</system:String>
<system:String x:Key="pluginDirectory">Priečinok s pluginmi</system:String> <system:String x:Key="pluginDirectory">Priečinok s pluginmi</system:String>
<system:String x:Key="author">Autor</system:String> <system:String x:Key="author">Autor</system:String>
<system:String x:Key="plugin_init_time">Príprava: {0}ms</system:String> <system:String x:Key="plugin_init_time">Príprava:</system:String>
<system:String x:Key="plugin_query_time">Čas dopytu: {0}ms</system:String> <system:String x:Key="plugin_query_time">Čas dopytu:</system:String>
<!--Setting Theme--> <!--Setting Theme-->
<system:String x:Key="theme">Motív</system:String> <system:String x:Key="theme">Motív</system:String>

View file

@ -6,6 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converters="clr-namespace:Flow.Launcher.Converters" xmlns:converters="clr-namespace:Flow.Launcher.Converters"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
mc:Ignorable="d" mc:Ignorable="d"
Title="Flow Launcher" Title="Flow Launcher"
Topmost="True" Topmost="True"
@ -92,7 +93,8 @@
</ContextMenu> </ContextMenu>
</TextBox.ContextMenu> </TextBox.ContextMenu>
</TextBox> </TextBox>
<Image Source="{Binding Image, IsAsync=True}" Width="48" HorizontalAlignment="Right" /> <svgc:SvgControl Source="{Binding Image}" HorizontalAlignment="Right" Width="48" Height="48"
Background="Transparent"/>
</Grid> </Grid>
<Line x:Name="ProgressBar" HorizontalAlignment="Right" <Line x:Name="ProgressBar" HorizontalAlignment="Right"
Style="{DynamicResource PendingLineStyle}" Visibility="{Binding ProgressBarVisibility, Mode=TwoWay}" Style="{DynamicResource PendingLineStyle}" Visibility="{Binding ProgressBarVisibility, Mode=TwoWay}"

View file

@ -0,0 +1,45 @@
<Window x:Class="Flow.Launcher.PriorityChangeWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Flow.Launcher"
Loaded="PriorityChangeWindow_Loaded"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="PriorityChangeWindow" Height="250" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="60"/>
<RowDefinition Height="75"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock FontSize="14" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"
HorizontalAlignment="Left" Text="{DynamicResource currentPriority}" />
<TextBlock x:Name="OldPriority" Grid.Row="0" Grid.Column="1" Margin="170 10 10 10" FontSize="14"
VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock FontSize="14" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center"
HorizontalAlignment="Left" Text="{DynamicResource newPriority}" />
<StackPanel Grid.Row="1" Orientation="Horizontal" Grid.Column="1">
<TextBox x:Name="tbAction" Margin="140 10 15 10" Width="105" VerticalAlignment="Center" HorizontalAlignment="Left" />
</StackPanel>
<TextBlock Grid.Row="2" Grid.ColumnSpan="1" Grid.Column="1" Foreground="Gray"
Text="{DynamicResource priority_tips}" TextWrapping="Wrap"
Margin="0,0,20,0"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="3" Grid.Column="1">
<Button x:Name="btnCancel" Click="BtnCancel_OnClick" Margin="10 0 10 0" Width="80" Height="30"
Content="{DynamicResource cancel}" />
<Button x:Name="btnDone" Margin="10 0 10 0" Width="80" Height="30" Click="btnDone_OnClick">
<TextBlock x:Name="lblAdd" Text="{DynamicResource done}" />
</Button>
</StackPanel>
</Grid>
</Window>

View file

@ -0,0 +1,69 @@
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Flow.Launcher
{
/// <summary>
/// Interaction Logic of PriorityChangeWindow.xaml
/// </summary>
public partial class PriorityChangeWindow : Window
{
private readonly PluginPair plugin;
private Settings settings;
private readonly Internationalization translater = InternationalizationManager.Instance;
private readonly PluginViewModel pluginViewModel;
public PriorityChangeWindow(string pluginId, Settings settings, PluginViewModel pluginViewModel)
{
InitializeComponent();
plugin = PluginManager.GetPluginForId(pluginId);
this.settings = settings;
this.pluginViewModel = pluginViewModel;
if (plugin == null)
{
MessageBox.Show(translater.GetTranslation("cannotFindSpecifiedPlugin"));
Close();
}
}
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
{
Close();
}
private void btnDone_OnClick(object sender, RoutedEventArgs e)
{
if (int.TryParse(tbAction.Text.Trim(), out var newPriority))
{
pluginViewModel.ChangePriority(newPriority);
Close();
}
else
{
string msg = translater.GetTranslation("invalidPriority");
MessageBox.Show(msg);
}
}
private void PriorityChangeWindow_Loaded(object sender, RoutedEventArgs e)
{
OldPriority.Text = pluginViewModel.Priority.ToString();
tbAction.Focus();
}
}
}

View file

@ -78,9 +78,9 @@ namespace Flow.Launcher
ImageLoader.Save(); ImageLoader.Save();
} }
public void ReloadAllPluginData() public Task ReloadAllPluginData()
{ {
PluginManager.ReloadData(); return PluginManager.ReloadData();
} }
public void ShowMsg(string title, string subTitle = "", string iconPath = "") public void ShowMsg(string title, string subTitle = "", string iconPath = "")
@ -92,7 +92,7 @@ namespace Flow.Launcher
{ {
Application.Current.Dispatcher.Invoke(() => Application.Current.Dispatcher.Invoke(() =>
{ {
var msg = useMainWindowAsOwner ? new Msg {Owner = Application.Current.MainWindow} : new Msg(); var msg = useMainWindowAsOwner ? new Msg { Owner = Application.Current.MainWindow } : new Msg();
msg.Show(title, subTitle, iconPath); msg.Show(title, subTitle, iconPath);
}); });
} }

View file

@ -8,6 +8,7 @@
xmlns:userSettings="clr-namespace:Flow.Launcher.Infrastructure.UserSettings;assembly=Flow.Launcher.Infrastructure" xmlns:userSettings="clr-namespace:Flow.Launcher.Infrastructure.UserSettings;assembly=Flow.Launcher.Infrastructure"
xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ui="http://schemas.modernwpf.com/2019" xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
x:Class="Flow.Launcher.SettingWindow" x:Class="Flow.Launcher.SettingWindow"
mc:Ignorable="d" mc:Ignorable="d"
Icon="Images\app.png" Icon="Images\app.png"
@ -172,10 +173,14 @@
<TextBlock Text="{Binding PluginPair.Metadata.Description}" <TextBlock Text="{Binding PluginPair.Metadata.Description}"
Grid.Row="1" Opacity="0.5" /> Grid.Row="1" Opacity="0.5" />
<DockPanel Grid.Row="2" Margin="0 10 0 8" HorizontalAlignment="Right"> <DockPanel Grid.Row="2" Margin="0 10 0 8" HorizontalAlignment="Right">
<TextBlock Text="Priority" Margin="20,0,0,0"/>
<TextBlock Text="{Binding Priority}"
ToolTip="Change Plugin Results Priority"
Margin="5 0 0 0" Cursor="Hand" Foreground="Blue"
MouseUp="OnPluginPriorityClick"/>
<TextBlock Text="{DynamicResource actionKeywords}" <TextBlock Text="{DynamicResource actionKeywords}"
Visibility="{Binding ActionKeywordsVisibility}" Visibility="{Binding ActionKeywordsVisibility}"
Margin="20 0 0 0"/> Margin="5 0 0 0"/>
<TextBlock Text="{Binding ActionKeywordsText}" <TextBlock Text="{Binding ActionKeywordsText}"
Visibility="{Binding ActionKeywordsVisibility}" Visibility="{Binding ActionKeywordsVisibility}"
ToolTip="Change Action Keywords" ToolTip="Change Action Keywords"
@ -252,7 +257,8 @@
Text="{DynamicResource hiThere}" IsReadOnly="True" Text="{DynamicResource hiThere}" IsReadOnly="True"
Style="{DynamicResource QueryBoxStyle}" Style="{DynamicResource QueryBoxStyle}"
Margin="18 0 56 0" /> Margin="18 0 56 0" />
<Image Source="{Binding ThemeImage}" HorizontalAlignment="Right" /> <svgc:SvgControl Source="{Binding ThemeImage}" Height="48" Width="48" HorizontalAlignment="Right"
Background="Transparent" />
<ContentControl Grid.Row="1"> <ContentControl Grid.Row="1">
<flowlauncher:ResultListBox DataContext="{Binding PreviewResults, Mode=OneTime}" Visibility="Visible" /> <flowlauncher:ResultListBox DataContext="{Binding PreviewResults, Mode=OneTime}" Visibility="Visible" />
</ContentControl> </ContentControl>
@ -429,8 +435,8 @@
<TextBlock Grid.Row="0" Grid.ColumnSpan="2" Text="{Binding ActivatedTimes, Mode=OneWay}" FontSize="12" /> <TextBlock Grid.Row="0" Grid.ColumnSpan="2" Text="{Binding ActivatedTimes, Mode=OneWay}" FontSize="12" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{DynamicResource website}"/> <TextBlock Grid.Row="1" Grid.Column="0" Text="{DynamicResource website}"/>
<TextBlock Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"> <TextBlock Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left">
<Hyperlink NavigateUri="{Binding Github, Mode=OneWay}" RequestNavigate="OnRequestNavigate"> <Hyperlink NavigateUri="{Binding Website, Mode=OneWay}" RequestNavigate="OnRequestNavigate">
<Run Text="{Binding Github, Mode=OneWay}" /> <Run Text="{Binding Website, Mode=OneWay}" />
</Hyperlink> </Hyperlink>
</TextBlock> </TextBlock>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Version" /> <TextBlock Grid.Row="2" Grid.Column="0" Text="Version" />

View file

@ -206,7 +206,16 @@ namespace Flow.Launcher
{ {
var id = viewModel.SelectedPlugin.PluginPair.Metadata.ID; var id = viewModel.SelectedPlugin.PluginPair.Metadata.ID;
// used to sync the current status from the plugin manager into the setting to keep consistency after save // used to sync the current status from the plugin manager into the setting to keep consistency after save
settings.PluginSettings.Plugins[id].Disabled = viewModel.SelectedPlugin.PluginPair.Metadata.Disabled; settings.PluginSettings.Plugins[id].Disabled = viewModel.SelectedPlugin.PluginPair.Metadata.Disabled;
}
private void OnPluginPriorityClick(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(viewModel.SelectedPlugin.PluginPair.Metadata.ID, settings, viewModel.SelectedPlugin);
priorityChangeWindow.ShowDialog();
}
} }
private void OnPluginActionKeywordsClick(object sender, MouseButtonEventArgs e) private void OnPluginActionKeywordsClick(object sender, MouseButtonEventArgs e)
@ -281,5 +290,6 @@ namespace Flow.Launcher
{ {
FilesFolders.OpenPath(Path.Combine(DataLocation.DataDirectory(), Constant.Themes)); FilesFolders.OpenPath(Path.Combine(DataLocation.DataDirectory(), Constant.Themes));
} }
} }
} }

View file

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
namespace Flow.Launcher.Storage namespace Flow.Launcher.Storage

View file

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using System.Text.Json;
using System.Text.Json.Serialization;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
namespace Flow.Launcher.Storage namespace Flow.Launcher.Storage
@ -8,21 +9,24 @@ namespace Flow.Launcher.Storage
// todo this class is not thread safe.... but used from multiple threads. // todo this class is not thread safe.... but used from multiple threads.
public class TopMostRecord public class TopMostRecord
{ {
[JsonProperty] /// <summary>
private Dictionary<string, Record> records = new Dictionary<string, Record>(); /// You should not directly access this field
/// <para>
/// It is public due to System.Text.Json limitation in version 3.1
/// </para>
/// </summary>
/// TODO: Set it to private
public Dictionary<string, Record> records { get; set; } = new Dictionary<string, Record>();
internal bool IsTopMost(Result result) internal bool IsTopMost(Result result)
{ {
if (records.Count == 0) if (records.Count == 0 || !records.ContainsKey(result.OriginQuery.RawQuery))
{ {
return false; return false;
} }
// since this dictionary should be very small (or empty) going over it should be pretty fast. // since this dictionary should be very small (or empty) going over it should be pretty fast.
return records.Any(o => o.Value.Title == result.Title return records[result.OriginQuery.RawQuery].Equals(result);
&& o.Value.SubTitle == result.SubTitle
&& o.Value.PluginID == result.PluginID
&& o.Key == result.OriginQuery.RawQuery);
} }
internal void Remove(Result result) internal void Remove(Result result)
@ -54,5 +58,12 @@ namespace Flow.Launcher.Storage
public string Title { get; set; } public string Title { get; set; }
public string SubTitle { get; set; } public string SubTitle { get; set; }
public string PluginID { get; set; } public string PluginID { get; set; }
public bool Equals(Result r)
{
return Title == r.Title
&& SubTitle == r.SubTitle
&& PluginID == r.PluginID;
}
} }
} }

View file

@ -1,5 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using System.Text.Json.Serialization;
using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
@ -7,15 +7,27 @@ namespace Flow.Launcher.Storage
{ {
public class UserSelectedRecord public class UserSelectedRecord
{ {
[JsonProperty] /// <summary>
private Dictionary<string, int> records = new Dictionary<string, int>(); /// You should not directly access this field
/// <para>
/// It is public due to System.Text.Json limitation in version 3.1
/// </para>
/// </summary>
/// TODO: Set it to private
[JsonPropertyName("records")]
public Dictionary<string, int> records { get; set; }
public UserSelectedRecord()
{
records = new Dictionary<string, int>();
}
public void Add(Result result) public void Add(Result result)
{ {
var key = result.ToString(); var key = result.ToString();
if (records.TryGetValue(key, out int value)) if (records.ContainsKey(key))
{ {
records[key] = value + 1; records[key]++;
} }
else else
{ {

View file

@ -21,6 +21,7 @@ using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Storage; using Flow.Launcher.Storage;
using System.Windows.Media; using System.Windows.Media;
using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.Logger;
namespace Flow.Launcher.ViewModel namespace Flow.Launcher.ViewModel
{ {
@ -85,7 +86,7 @@ namespace Flow.Launcher.ViewModel
{ {
foreach (var pair in PluginManager.GetPluginsForInterface<IResultUpdated>()) foreach (var pair in PluginManager.GetPluginsForInterface<IResultUpdated>())
{ {
var plugin = (IResultUpdated)pair.Plugin; var plugin = (IResultUpdated) pair.Plugin;
plugin.ResultsUpdated += (s, e) => plugin.ResultsUpdated += (s, e) =>
{ {
Task.Run(() => Task.Run(() =>
@ -112,25 +113,13 @@ namespace Flow.Launcher.ViewModel
} }
}); });
SelectNextItemCommand = new RelayCommand(_ => SelectNextItemCommand = new RelayCommand(_ => { SelectedResults.SelectNextResult(); });
{
SelectedResults.SelectNextResult();
});
SelectPrevItemCommand = new RelayCommand(_ => SelectPrevItemCommand = new RelayCommand(_ => { SelectedResults.SelectPrevResult(); });
{
SelectedResults.SelectPrevResult();
});
SelectNextPageCommand = new RelayCommand(_ => SelectNextPageCommand = new RelayCommand(_ => { SelectedResults.SelectNextPage(); });
{
SelectedResults.SelectNextPage();
});
SelectPrevPageCommand = new RelayCommand(_ => SelectPrevPageCommand = new RelayCommand(_ => { SelectedResults.SelectPrevPage(); });
{
SelectedResults.SelectPrevPage();
});
SelectFirstResultCommand = new RelayCommand(_ => SelectedResults.SelectFirstResult()); SelectFirstResultCommand = new RelayCommand(_ => SelectedResults.SelectFirstResult());
@ -208,6 +197,7 @@ namespace Flow.Launcher.ViewModel
public ResultsViewModel History { get; private set; } public ResultsViewModel History { get; private set; }
private string _queryText; private string _queryText;
public string QueryText public string QueryText
{ {
get { return _queryText; } get { return _queryText; }
@ -228,10 +218,12 @@ namespace Flow.Launcher.ViewModel
QueryTextCursorMovedToEnd = true; QueryTextCursorMovedToEnd = true;
QueryText = queryText; QueryText = queryText;
} }
public bool LastQuerySelected { get; set; } public bool LastQuerySelected { get; set; }
public bool QueryTextCursorMovedToEnd { get; set; } public bool QueryTextCursorMovedToEnd { get; set; }
private ResultsViewModel _selectedResults; private ResultsViewModel _selectedResults;
private ResultsViewModel SelectedResults private ResultsViewModel SelectedResults
{ {
get { return _selectedResults; } get { return _selectedResults; }
@ -263,6 +255,7 @@ namespace Flow.Launcher.ViewModel
QueryText = string.Empty; QueryText = string.Empty;
} }
} }
_selectedResults.Visbility = Visibility.Visible; _selectedResults.Visbility = Visibility.Visible;
} }
} }
@ -284,7 +277,7 @@ namespace Flow.Launcher.ViewModel
public string OpenResultCommandModifiers { get; private set; } public string OpenResultCommandModifiers { get; private set; }
public ImageSource Image => ImageLoader.Load(Constant.QueryTextBoxIconImagePath); public string Image => Constant.QueryTextBoxIconImagePath;
#endregion #endregion
@ -323,7 +316,7 @@ namespace Flow.Launcher.ViewModel
var filtered = results.Where var filtered = results.Where
( (
r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet()
|| StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() || StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet()
).ToList(); ).ToList();
ContextMenu.AddResults(filtered, id); ContextMenu.AddResults(filtered, id);
} }
@ -350,7 +343,7 @@ namespace Flow.Launcher.ViewModel
Title = string.Format(title, h.Query), Title = string.Format(title, h.Query),
SubTitle = string.Format(time, h.ExecutedDateTime), SubTitle = string.Format(time, h.ExecutedDateTime),
IcoPath = "Images\\history.png", IcoPath = "Images\\history.png",
OriginQuery = new Query { RawQuery = h.Query }, OriginQuery = new Query {RawQuery = h.Query},
Action = _ => Action = _ =>
{ {
SelectedResults = Results; SelectedResults = Results;
@ -396,7 +389,8 @@ namespace Flow.Launcher.ViewModel
_lastQuery = query; _lastQuery = query;
Task.Delay(200, currentCancellationToken).ContinueWith(_ => Task.Delay(200, currentCancellationToken).ContinueWith(_ =>
{ // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet {
// start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet
if (currentUpdateSource == _updateSource && _isQueryRunning) if (currentUpdateSource == _updateSource && _isQueryRunning)
{ {
ProgressBarVisibility = Visibility.Visible; ProgressBarVisibility = Visibility.Visible;
@ -404,35 +398,56 @@ namespace Flow.Launcher.ViewModel
}, currentCancellationToken); }, currentCancellationToken);
var plugins = PluginManager.ValidPluginsForQuery(query); var plugins = PluginManager.ValidPluginsForQuery(query);
Task.Run(() => Task.Run(async () =>
{ {
// so looping will stop once it was cancelled // so looping will stop once it was cancelled
var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken };
Task[] tasks = new Task[plugins.Count];
try try
{ {
Parallel.ForEach(plugins, parallelOptions, plugin => for (var i = 0; i < plugins.Count; i++)
{ {
if (!plugin.Metadata.Disabled) if (!plugins[i].Metadata.Disabled)
{ {
var results = PluginManager.QueryForPlugin(plugin, query); tasks[i] = QueryTask(plugins[i], query, currentCancellationToken);
UpdateResultView(results, plugin.Metadata, query);
} }
}); 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) catch (OperationCanceledException)
{ {
// nothing to do here // nothing to do here
} }
// this should happen once after all queries are done so progress bar should continue // this should happen once after all queries are done so progress bar should continue
// until the end of all querying // until the end of all querying
_isQueryRunning = false; _isQueryRunning = false;
if (currentUpdateSource == _updateSource) if (!currentCancellationToken.IsCancellationRequested)
{ // update to hidden if this is still the current query {
// update to hidden if this is still the current query
ProgressBarVisibility = Visibility.Hidden; ProgressBarVisibility = Visibility.Hidden;
} }
}, currentCancellationToken);
// Local function
async Task QueryTask(PluginPair plugin, Query query, CancellationToken token)
{
// Since it is wrapped within a Task.Run, the synchronous context is null
// Task.Yield will force it to run in ThreadPool
await Task.Yield();
var results = await PluginManager.QueryForPlugin(plugin, query, token);
if (!currentCancellationToken.IsCancellationRequested)
UpdateResultView(results, plugin.Metadata, query);
}
}, currentCancellationToken).ContinueWith(
t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception),
TaskContinuationOptions.OnlyOnFaulted);
} }
} }
else else
@ -499,6 +514,7 @@ namespace Flow.Launcher.ViewModel
} }
}; };
} }
return menu; return menu;
} }
@ -544,6 +560,7 @@ namespace Flow.Launcher.ViewModel
var selected = SelectedResults == History; var selected = SelectedResults == History;
return selected; return selected;
} }
#region Hotkey #region Hotkey
private void SetHotkey(string hotkeyStr, EventHandler<HotkeyEventArgs> action) private void SetHotkey(string hotkeyStr, EventHandler<HotkeyEventArgs> action)
@ -562,7 +579,8 @@ namespace Flow.Launcher.ViewModel
catch (Exception) catch (Exception)
{ {
string errorMsg = string errorMsg =
string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr); string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"),
hotkeyStr);
MessageBox.Show(errorMsg); MessageBox.Show(errorMsg);
} }
} }
@ -612,7 +630,6 @@ namespace Flow.Launcher.ViewModel
{ {
if (!ShouldIgnoreHotkeys()) if (!ShouldIgnoreHotkeys())
{ {
if (_settings.LastQueryMode == LastQueryMode.Empty) if (_settings.LastQueryMode == LastQueryMode.Empty)
{ {
ChangeQueryText(string.Empty); ChangeQueryText(string.Empty);
@ -676,7 +693,8 @@ namespace Flow.Launcher.ViewModel
} }
else else
{ {
result.Score += _userSelectedRecord.GetSelectedCount(result) * 5; var priorityScore = metadata.Priority * 150;
result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + priorityScore;
} }
} }

View file

@ -26,6 +26,7 @@ namespace Flow.Launcher.ViewModel
public string InitilizaTime => PluginPair.Metadata.InitTime.ToString() + "ms"; public string InitilizaTime => PluginPair.Metadata.InitTime.ToString() + "ms";
public string QueryTime => PluginPair.Metadata.AvgQueryTime + "ms"; public string QueryTime => PluginPair.Metadata.AvgQueryTime + "ms";
public string ActionKeywordsText => string.Join(Query.ActionKeywordSeperater, PluginPair.Metadata.ActionKeywords); public string ActionKeywordsText => string.Join(Query.ActionKeywordSeperater, PluginPair.Metadata.ActionKeywords);
public int Priority => PluginPair.Metadata.Priority;
public void ChangeActionKeyword(string newActionKeyword, string oldActionKeyword) public void ChangeActionKeyword(string newActionKeyword, string oldActionKeyword)
{ {
@ -34,6 +35,12 @@ namespace Flow.Launcher.ViewModel
OnPropertyChanged(nameof(ActionKeywordsText)); OnPropertyChanged(nameof(ActionKeywordsText));
} }
public void ChangePriority(int newPriority)
{
PluginPair.Metadata.Priority = newPriority;
OnPropertyChanged(nameof(Priority));
}
public bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword); public bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword);
} }
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
@ -12,9 +13,9 @@ namespace Flow.Launcher.ViewModel
{ {
public class ResultViewModel : BaseModel public class ResultViewModel : BaseModel
{ {
public class LazyAsync<T> : Lazy<Task<T>> public class LazyAsync<T> : Lazy<ValueTask<T>>
{ {
private T defaultValue; private readonly T defaultValue;
private readonly Action _updateCallback; private readonly Action _updateCallback;
public new T Value public new T Value
@ -23,21 +24,27 @@ namespace Flow.Launcher.ViewModel
{ {
if (!IsValueCreated) if (!IsValueCreated)
{ {
base.Value.ContinueWith(_ => _ = Exercute(); // manually use callback strategy
{
_updateCallback();
});
return defaultValue; return defaultValue;
} }
if (!base.Value.IsCompleted || base.Value.IsFaulted) if (!base.Value.IsCompletedSuccessfully)
return defaultValue; return defaultValue;
return base.Value.Result; return base.Value.Result;
// If none of the variables captured by the local function are captured by other lambdas,
// the compiler can avoid heap allocations.
async ValueTask Exercute()
{
await base.Value.ConfigureAwait(false);
_updateCallback();
}
} }
} }
public LazyAsync(Func<Task<T>> factory, T defaultValue, Action updateCallback) : base(factory) public LazyAsync(Func<ValueTask<T>> factory, T defaultValue, Action updateCallback) : base(factory)
{ {
if (defaultValue != null) if (defaultValue != null)
{ {
@ -55,13 +62,13 @@ namespace Flow.Launcher.ViewModel
Result = result; Result = result;
Image = new LazyAsync<ImageSource>( Image = new LazyAsync<ImageSource>(
SetImage, SetImage,
ImageLoader.DefaultImage, ImageLoader.DefaultImage,
() => () =>
{ {
OnPropertyChanged(nameof(Image)); OnPropertyChanged(nameof(Image));
}); });
} }
Settings = settings; Settings = settings;
} }
@ -82,7 +89,7 @@ namespace Flow.Launcher.ViewModel
public LazyAsync<ImageSource> Image { get; set; } public LazyAsync<ImageSource> Image { get; set; }
private async Task<ImageSource> SetImage() private async ValueTask<ImageSource> SetImage()
{ {
var imagePath = Result.IcoPath; var imagePath = Result.IcoPath;
if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) if (string.IsNullOrEmpty(imagePath) && Result.Icon != null)
@ -94,7 +101,7 @@ namespace Flow.Launcher.ViewModel
catch (Exception e) catch (Exception e)
{ {
Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e); Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e);
imagePath = Constant.MissingImgIcon; return ImageLoader.DefaultImage;
} }
} }

View file

@ -88,6 +88,7 @@ namespace Flow.Launcher.ViewModel
var id = vm.PluginPair.Metadata.ID; var id = vm.PluginPair.Metadata.ID;
Settings.PluginSettings.Plugins[id].Disabled = vm.PluginPair.Metadata.Disabled; Settings.PluginSettings.Plugins[id].Disabled = vm.PluginPair.Metadata.Disabled;
Settings.PluginSettings.Plugins[id].Priority = vm.Priority;
} }
PluginManager.Save(); PluginManager.Save();
@ -213,7 +214,7 @@ namespace Flow.Launcher.ViewModel
#region plugin #region plugin
public static string Plugin => "http://www.wox.one/plugin"; public static string Plugin => @"https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest";
public PluginViewModel SelectedPlugin { get; set; } public PluginViewModel SelectedPlugin { get; set; }
public IList<PluginViewModel> PluginViewModels public IList<PluginViewModel> PluginViewModels
@ -438,7 +439,7 @@ namespace Flow.Launcher.ViewModel
} }
} }
public ImageSource ThemeImage => ImageLoader.Load(Constant.QueryTextBoxIconImagePath); public string ThemeImage => Constant.QueryTextBoxIconImagePath;
#endregion #endregion
@ -450,7 +451,7 @@ namespace Flow.Launcher.ViewModel
#region about #region about
public string Github => _updater.GitHubRepository; public string Website => Constant.Website;
public string ReleaseNotes => _updater.GitHubRepository + @"/releases/latest"; public string ReleaseNotes => _updater.GitHubRepository + @"/releases/latest";
public static string Version => Constant.Version; public static string Version => Constant.Version;
public string ActivatedTimes => string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes); public string ActivatedTimes => string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes);

View file

@ -40,14 +40,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Images\bookmark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Languages\en.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x64\SQLite.Interop.dll"> <Content Include="x64\SQLite.Interop.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
@ -60,13 +52,16 @@
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" /> <ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" /> <ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Languages\tr.xaml"> <Content Include="Languages\*.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Images\*.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -4,7 +4,7 @@
"Name": "Browser Bookmarks", "Name": "Browser Bookmarks",
"Description": "Search your browser bookmarks", "Description": "Search your browser bookmarks",
"Author": "qianlifeng, Ioannis G.", "Author": "qianlifeng, Ioannis G.",
"Version": "1.3.1", "Version": "1.3.2",
"Language": "csharp", "Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher", "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll", "ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll",

View file

@ -11,6 +11,7 @@
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -43,57 +44,14 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Flow.Launcher.Core\Flow.Launcher.Core.csproj" /> <ProjectReference Include="..\..\Flow.Launcher.Core\Flow.Launcher.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Images\calculator.png"> <Content Include="Languages\*.xaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\en.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> <Content Include="Images\*.png">
<ItemGroup>
<Content Include="Languages\zh-cn.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\zh-tw.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\de.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\pl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\tr.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>

View file

@ -91,7 +91,7 @@ namespace Flow.Launcher.Plugin.Caculator
}; };
} }
} }
catch catch (Exception)
{ {
// ignored // ignored
} }

View file

@ -4,7 +4,7 @@
"Name": "Calculator", "Name": "Calculator",
"Description": "Provide mathematical calculations.(Try 5*3-2 in Flow Launcher)", "Description": "Provide mathematical calculations.(Try 5*3-2 in Flow Launcher)",
"Author": "cxfksword", "Author": "cxfksword",
"Version": "1.1.3", "Version": "1.1.4",
"Language": "csharp", "Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher", "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Caculator.dll", "ExecuteFileName": "Flow.Launcher.Plugin.Caculator.dll",

View file

@ -1,101 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<ProjectGuid>{F35190AA-4758-4D9E-A193-E3BDF6AD3567}</ProjectGuid>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Flow.Launcher.Plugin.Color</RootNamespace>
<AssemblyName>Flow.Launcher.Plugin.Color</AssemblyName>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Color\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\Output\Release\Plugins\Flow.Launcher.Plugin.Color\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<None Include="Images\color.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="Languages\en.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\zh-cn.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\zh-tw.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\de.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\pl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\tr.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,8 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_color_plugin_name">Farben</system:String>
<system:String x:Key="flowlauncher_plugin_color_plugin_description">Stellt eine HEX-Farben Vorschau bereit. (Versuche #000 in Flow Launcher)</system:String>
</ResourceDictionary>

View file

@ -1,8 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_color_plugin_name">Colors</system:String>
<system:String x:Key="flowlauncher_plugin_color_plugin_description">Allows to preview colors using hex values.(Try #000 in Flow Launcher)</system:String>
</ResourceDictionary>

View file

@ -1,8 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_color_plugin_name">Kolory</system:String>
<system:String x:Key="flowlauncher_plugin_color_plugin_description">Podgląd kolorów po wpisaniu ich kodu szesnastkowego. (Spróbuj wpisać #000 w oknie Flow Launchera)</system:String>
</ResourceDictionary>

View file

@ -1,8 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_color_plugin_name">Farby</system:String>
<system:String x:Key="flowlauncher_plugin_color_plugin_description">Zobrazuje náhľad farieb v HEX formáte. (Skúste #000 vo Flow Launcheri)</system:String>
</ResourceDictionary>

View file

@ -1,8 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_color_plugin_name">Renkler</system:String>
<system:String x:Key="flowlauncher_plugin_color_plugin_description">Hex kodunu girdiğiniz renkleri görüntülemeye yarar.(#000 yazmayı deneyin)</system:String>
</ResourceDictionary>

View file

@ -1,8 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_color_plugin_name">颜色</system:String>
<system:String x:Key="flowlauncher_plugin_color_plugin_description">提供在Flow Launcher查询hex颜色。(尝试在Flow Launcher中输入#000)</system:String>
</ResourceDictionary>

View file

@ -1,7 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_color_plugin_name">顏色</system:String>
<system:String x:Key="flowlauncher_plugin_color_plugin_description">提供在 Flow Launcher 查詢 hex 顏色。(試著在 Flow Launcher 中輸入 #000)</system:String>
</ResourceDictionary>

View file

@ -1,121 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Windows;
namespace Flow.Launcher.Plugin.Color
{
public sealed class ColorsPlugin : IPlugin, IPluginI18n
{
private string DIR_PATH = Path.Combine(Path.GetTempPath(), @"Plugins\Colors\");
private PluginInitContext context;
private const int IMG_SIZE = 32;
private DirectoryInfo ColorsDirectory { get; set; }
public ColorsPlugin()
{
if (!Directory.Exists(DIR_PATH))
{
ColorsDirectory = Directory.CreateDirectory(DIR_PATH);
}
else
{
ColorsDirectory = new DirectoryInfo(DIR_PATH);
}
}
public List<Result> Query(Query query)
{
var raw = query.Search;
if (!IsAvailable(raw)) return new List<Result>(0);
try
{
var cached = Find(raw);
if (cached.Length == 0)
{
var path = CreateImage(raw);
return new List<Result>
{
new Result
{
Title = raw,
IcoPath = path,
Action = _ =>
{
Clipboard.SetText(raw);
return true;
}
}
};
}
return cached.Select(x => new Result
{
Title = raw,
IcoPath = x.FullName,
Action = _ =>
{
Clipboard.SetText(raw);
return true;
}
}).ToList();
}
catch (Exception exception)
{
// todo: log
return new List<Result>(0);
}
}
private bool IsAvailable(string query)
{
// todo: rgb, names
var length = query.Length - 1; // minus `#` sign
return query.StartsWith("#") && (length == 3 || length == 6);
}
public FileInfo[] Find(string name)
{
var file = string.Format("{0}.png", name.Substring(1));
return ColorsDirectory.GetFiles(file, SearchOption.TopDirectoryOnly);
}
private string CreateImage(string name)
{
using (var bitmap = new Bitmap(IMG_SIZE, IMG_SIZE))
using (var graphics = Graphics.FromImage(bitmap))
{
var color = ColorTranslator.FromHtml(name);
graphics.Clear(color);
var path = CreateFileName(name);
bitmap.Save(path, ImageFormat.Png);
return path;
}
}
private string CreateFileName(string name)
{
return string.Format("{0}{1}.png", ColorsDirectory.FullName, name.Substring(1));
}
public void Init(PluginInitContext context)
{
this.context = context;
}
public string GetTranslatedPluginTitle()
{
return context.API.GetTranslation("flowlauncher_plugin_color_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return context.API.GetTranslation("flowlauncher_plugin_color_plugin_description");
}
}
}

View file

@ -1,12 +0,0 @@
{
"ID": "9B36CE6181FC47FBB597AA2C29CD9B0A",
"ActionKeyword": "*",
"Name": "Colors",
"Description": "Provide hex color preview.(Try #000 in Flow Launcher)",
"Author": "qianlifeng",
"Version": "1.1.1",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Color.dll",
"IcoPath": "Images\\color.png"
}

View file

@ -38,7 +38,7 @@ namespace Flow.Launcher.Plugin.ControlPanel
int cxDesired, int cyDesired, uint fuLoad); int cxDesired, int cyDesired, uint fuLoad);
[DllImport("user32.dll", CharSet = CharSet.Auto)] [DllImport("user32.dll", CharSet = CharSet.Auto)]
extern static bool DestroyIcon(IntPtr handle); static extern bool DestroyIcon(IntPtr handle);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType); static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType);

View file

@ -45,53 +45,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Images\ControlPanel.png"> <Content Include="Images\*.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\en.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> <Content Include="Languages\*.xaml">
<ItemGroup>
<Content Include="Languages\zh-cn.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\zh-tw.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\de.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\pl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\tr.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View file

@ -4,7 +4,7 @@
"Name": "Control Panel", "Name": "Control Panel",
"Description": "Search within the Control Panel.", "Description": "Search within the Control Panel.",
"Author": "CoenraadS", "Author": "CoenraadS",
"Version": "1.1.1", "Version": "1.1.2",
"Language": "csharp", "Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher", "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.ControlPanel.dll", "ExecuteFileName": "Flow.Launcher.Plugin.ControlPanel.dll",

View file

@ -7,6 +7,7 @@
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
</PropertyGroup> </PropertyGroup>
@ -26,73 +27,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Images\explorer.png"> <Content Include="Images\*.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Images\index.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Images\excludeindexpath.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Images\copy.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Images\deletefilefolder.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Content Include="Images\file.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Languages\*.xaml">
<None Include="Images\folder.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Images\user.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Images\windowsindexingoptions.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="Languages\en.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\zh-cn.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\zh-tw.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\de.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\pl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\tr.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View file

@ -3,11 +3,13 @@ using Flow.Launcher.Plugin.Explorer.Search;
using Flow.Launcher.Plugin.Explorer.ViewModels; using Flow.Launcher.Plugin.Explorer.ViewModels;
using Flow.Launcher.Plugin.Explorer.Views; using Flow.Launcher.Plugin.Explorer.Views;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls; using System.Windows.Controls;
namespace Flow.Launcher.Plugin.Explorer namespace Flow.Launcher.Plugin.Explorer
{ {
public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu, IPluginI18n public class Main : ISettingProvider, IAsyncPlugin, ISavable, IContextMenu, IPluginI18n
{ {
internal PluginInitContext Context { get; set; } internal PluginInitContext Context { get; set; }
@ -17,17 +19,21 @@ namespace Flow.Launcher.Plugin.Explorer
private IContextMenu contextMenu; private IContextMenu contextMenu;
private SearchManager searchManager;
public Control CreateSettingPanel() public Control CreateSettingPanel()
{ {
return new ExplorerSettings(viewModel); return new ExplorerSettings(viewModel);
} }
public void Init(PluginInitContext context) public async Task InitAsync(PluginInitContext context)
{ {
Context = context; Context = context;
viewModel = new SettingsViewModel(context); viewModel = new SettingsViewModel(context);
await viewModel.LoadStorage();
Settings = viewModel.Settings; Settings = viewModel.Settings;
contextMenu = new ContextMenu(Context, Settings); contextMenu = new ContextMenu(Context, Settings);
searchManager = new SearchManager(Settings, Context);
} }
public List<Result> LoadContextMenus(Result selectedResult) public List<Result> LoadContextMenus(Result selectedResult)
@ -35,9 +41,9 @@ namespace Flow.Launcher.Plugin.Explorer
return contextMenu.LoadContextMenus(selectedResult); return contextMenu.LoadContextMenus(selectedResult);
} }
public List<Result> Query(Query query) public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
{ {
return new SearchManager(Settings, Context).Search(query); return await searchManager.SearchAsync(query, token);
} }
public void Save() public void Save()

View file

@ -22,7 +22,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > search.LastIndexOf(Constants.DirectorySeperator)) if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > search.LastIndexOf(Constants.DirectorySeperator))
return DirectorySearch(SearchOption.AllDirectories, query, search, criteria); return DirectorySearch(SearchOption.AllDirectories, query, search, criteria);
return DirectorySearch(SearchOption.TopDirectoryOnly, query, search, criteria); return DirectorySearch(SearchOption.TopDirectoryOnly, query, search, criteria);
} }
@ -57,9 +57,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
try try
{ {
var directoryInfo = new System.IO.DirectoryInfo(path); var directoryInfo = new System.IO.DirectoryInfo(path);
var fileSystemInfos = directoryInfo.GetFileSystemInfos(searchCriteria, searchOption);
foreach (var fileSystemInfo in fileSystemInfos) foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, searchOption))
{ {
if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue; if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue;

View file

@ -1,15 +1,15 @@
using Newtonsoft.Json; using System;
using System;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
{ {
[JsonObject(MemberSerialization.OptIn)]
public class FolderLink public class FolderLink
{ {
[JsonProperty]
public string Path { get; set; } public string Path { get; set; }
[JsonIgnore]
public string Nickname public string Nickname
{ {
get get

View file

@ -5,6 +5,8 @@ using Flow.Launcher.Plugin.SharedCommands;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Flow.Launcher.Plugin.Explorer.Search namespace Flow.Launcher.Plugin.Explorer.Search
{ {
@ -28,20 +30,20 @@ namespace Flow.Launcher.Plugin.Explorer.Search
this.settings = settings; this.settings = settings;
} }
internal List<Result> Search(Query query) internal async Task<List<Result>> SearchAsync(Query query, CancellationToken token)
{ {
var results = new List<Result>(); var results = new List<Result>();
var querySearch = query.Search; var querySearch = query.Search;
if (IsFileContentSearch(query.ActionKeyword)) if (IsFileContentSearch(query.ActionKeyword))
return WindowsIndexFileContentSearch(query, querySearch); return await WindowsIndexFileContentSearchAsync(query, querySearch, token).ConfigureAwait(false);
// This allows the user to type the assigned action keyword and only see the list of quick folder links // This allows the user to type the assigned action keyword and only see the list of quick folder links
if (settings.QuickFolderAccessLinks.Count > 0 if (settings.QuickFolderAccessLinks.Count > 0
&& query.ActionKeyword == settings.SearchActionKeyword && query.ActionKeyword == settings.SearchActionKeyword
&& string.IsNullOrEmpty(query.Search)) && string.IsNullOrEmpty(query.Search))
return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks, context); return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks, context);
var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks, context); var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks, context);
@ -54,11 +56,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, context); return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, context);
// Query is a location path with a full environment variable, eg. %appdata%\somefolder\ // Query is a location path with a full environment variable, eg. %appdata%\somefolder\
var isEnvironmentVariablePath = querySearch.Substring(1).Contains("%\\"); var isEnvironmentVariablePath = querySearch[1..].Contains("%\\");
if (!FilesFolders.IsLocationPathString(querySearch) && !isEnvironmentVariablePath) if (!FilesFolders.IsLocationPathString(querySearch) && !isEnvironmentVariablePath)
{ {
results.AddRange(WindowsIndexFilesAndFoldersSearch(query, querySearch)); results.AddRange(await WindowsIndexFilesAndFoldersSearchAsync(query, querySearch, token).ConfigureAwait(false));
return results; return results;
} }
@ -72,29 +74,34 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return results; return results;
var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath); var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath);
results.Add(resultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch)); results.Add(resultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch));
results.AddRange(TopLevelDirectorySearchBehaviour(WindowsIndexTopLevelFolderSearch, if (token.IsCancellationRequested)
return null;
results.AddRange(await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync,
DirectoryInfoClassSearch, DirectoryInfoClassSearch,
useIndexSearch, useIndexSearch,
query, query,
locationPath)); locationPath,
token).ConfigureAwait(false));
return results; return results;
} }
private List<Result> WindowsIndexFileContentSearch(Query query, string querySearchString) private async Task<List<Result>> WindowsIndexFileContentSearchAsync(Query query, string querySearchString, CancellationToken token)
{ {
var queryConstructor = new QueryConstructor(settings); var queryConstructor = new QueryConstructor(settings);
if (string.IsNullOrEmpty(querySearchString)) if (string.IsNullOrEmpty(querySearchString))
return new List<Result>(); return new List<Result>();
return indexSearch.WindowsIndexSearch(querySearchString, return await indexSearch.WindowsIndexSearchAsync(querySearchString,
queryConstructor.CreateQueryHelper().ConnectionString, queryConstructor.CreateQueryHelper().ConnectionString,
queryConstructor.QueryForFileContentSearch, queryConstructor.QueryForFileContentSearch,
query); query,
token).ConfigureAwait(false);
} }
public bool IsFileContentSearch(string actionKeyword) public bool IsFileContentSearch(string actionKeyword)
@ -109,37 +116,40 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch); return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch);
} }
public List<Result> TopLevelDirectorySearchBehaviour( public async Task<List<Result>> TopLevelDirectorySearchBehaviourAsync(
Func<Query, string, List<Result>> windowsIndexSearch, Func<Query, string, CancellationToken, Task<List<Result>>> windowsIndexSearch,
Func<Query, string, List<Result>> directoryInfoClassSearch, Func<Query, string, List<Result>> directoryInfoClassSearch,
bool useIndexSearch, bool useIndexSearch,
Query query, Query query,
string querySearchString) string querySearchString,
CancellationToken token)
{ {
if (!useIndexSearch) if (!useIndexSearch)
return directoryInfoClassSearch(query, querySearchString); return directoryInfoClassSearch(query, querySearchString);
return windowsIndexSearch(query, querySearchString); return await windowsIndexSearch(query, querySearchString, token);
} }
private List<Result> WindowsIndexFilesAndFoldersSearch(Query query, string querySearchString) private async Task<List<Result>> WindowsIndexFilesAndFoldersSearchAsync(Query query, string querySearchString, CancellationToken token)
{ {
var queryConstructor = new QueryConstructor(settings); var queryConstructor = new QueryConstructor(settings);
return indexSearch.WindowsIndexSearch(querySearchString, return await indexSearch.WindowsIndexSearchAsync(querySearchString,
queryConstructor.CreateQueryHelper().ConnectionString, queryConstructor.CreateQueryHelper().ConnectionString,
queryConstructor.QueryForAllFilesAndFolders, queryConstructor.QueryForAllFilesAndFolders,
query); query,
token).ConfigureAwait(false);
} }
private List<Result> WindowsIndexTopLevelFolderSearch(Query query, string path) private async Task<List<Result>> WindowsIndexTopLevelFolderSearchAsync(Query query, string path, CancellationToken token)
{ {
var queryConstructor = new QueryConstructor(settings); var queryConstructor = new QueryConstructor(settings);
return indexSearch.WindowsIndexSearch(path, return await indexSearch.WindowsIndexSearchAsync(path,
queryConstructor.CreateQueryHelper().ConnectionString, queryConstructor.CreateQueryHelper().ConnectionString,
queryConstructor.QueryForTopLevelDirectorySearch, queryConstructor.QueryForTopLevelDirectorySearch,
query); query,
token).ConfigureAwait(false);
} }
private bool UseWindowsIndexForDirectorySearch(string locationPath) private bool UseWindowsIndexForDirectorySearch(string locationPath)

View file

@ -5,19 +5,13 @@ using System.Collections.Generic;
using System.Data.OleDb; using System.Data.OleDb;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
{ {
internal class IndexSearch internal class IndexSearch
{ {
private readonly object _lock = new object();
private OleDbConnection conn;
private OleDbCommand command;
private OleDbDataReader dataReaderResults;
private readonly ResultManager resultManager; private readonly ResultManager resultManager;
// Reserved keywords in oleDB // Reserved keywords in oleDB
@ -28,7 +22,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
resultManager = new ResultManager(context); resultManager = new ResultManager(context);
} }
internal List<Result> ExecuteWindowsIndexSearch(string indexQueryString, string connectionString, Query query) internal async Task<List<Result>> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token)
{ {
var folderResults = new List<Result>(); var folderResults = new List<Result>();
var fileResults = new List<Result>(); var fileResults = new List<Result>();
@ -36,47 +30,49 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
try try
{ {
using (conn = new OleDbConnection(connectionString)) using var conn = new OleDbConnection(connectionString);
await conn.OpenAsync(token);
token.ThrowIfCancellationRequested();
using var command = new OleDbCommand(indexQueryString, conn);
// Results return as an OleDbDataReader.
using var dataReaderResults = await command.ExecuteReaderAsync(token) as OleDbDataReader;
token.ThrowIfCancellationRequested();
if (dataReaderResults.HasRows)
{ {
conn.Open(); while (await dataReaderResults.ReadAsync(token))
using (command = new OleDbCommand(indexQueryString, conn))
{ {
// Results return as an OleDbDataReader. token.ThrowIfCancellationRequested();
using (dataReaderResults = command.ExecuteReader()) if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value)
{ {
if (dataReaderResults.HasRows) // # is URI syntax for the fragment component, need to be encoded so LocalPath returns complete path
{ var encodedFragmentPath = dataReaderResults
while (dataReaderResults.Read()) .GetString(1)
{ .Replace("#", "%23", StringComparison.OrdinalIgnoreCase);
if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value)
{
// # is URI syntax for the fragment component, need to be encoded so LocalPath returns complete path
var encodedFragmentPath = dataReaderResults
.GetString(1)
.Replace("#", "%23", StringComparison.OrdinalIgnoreCase);
var path = new Uri(encodedFragmentPath).LocalPath;
if (dataReaderResults.GetString(2) == "Directory") var path = new Uri(encodedFragmentPath).LocalPath;
{
folderResults.Add(resultManager.CreateFolderResult( if (dataReaderResults.GetString(2) == "Directory")
dataReaderResults.GetString(0), {
path, folderResults.Add(resultManager.CreateFolderResult(
path, dataReaderResults.GetString(0),
query, true, true)); path,
} path,
else query, true, true));
{ }
fileResults.Add(resultManager.CreateFileResult(path, query, true, true)); else
} {
} fileResults.Add(resultManager.CreateFileResult(path, query, true, true));
}
} }
} }
} }
} }
} }
catch (OperationCanceledException)
{
return new List<Result>(); // The source code indicates that without adding members, it won't allocate an array
}
catch (InvalidOperationException e) catch (InvalidOperationException e)
{ {
// Internal error from ExecuteReader(): Connection closed. // Internal error from ExecuteReader(): Connection closed.
@ -91,18 +87,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
return results.Concat(folderResults.OrderBy(x => x.Title)).Concat(fileResults.OrderBy(x => x.Title)).ToList(); ; return results.Concat(folderResults.OrderBy(x => x.Title)).Concat(fileResults.OrderBy(x => x.Title)).ToList(); ;
} }
internal List<Result> WindowsIndexSearch(string searchString, string connectionString, Func<string, string> constructQuery, Query query) internal async Task<List<Result>> WindowsIndexSearchAsync(string searchString, string connectionString,
Func<string, string> constructQuery, Query query,
CancellationToken token)
{ {
var regexMatch = Regex.Match(searchString, reservedStringPattern); var regexMatch = Regex.Match(searchString, reservedStringPattern);
if (regexMatch.Success) if (regexMatch.Success)
return new List<Result>(); return new List<Result>();
lock (_lock) var constructedQuery = constructQuery(searchString);
{ return await ExecuteWindowsIndexSearchAsync(constructedQuery, connectionString, query, token);
var constructedQuery = constructQuery(searchString);
return ExecuteWindowsIndexSearch(constructedQuery, connectionString, query);
}
} }
internal bool PathIsIndexed(string path) internal bool PathIsIndexed(string path)

View file

@ -1,28 +1,22 @@
using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search;
using Flow.Launcher.Plugin.Explorer.Search.FolderLinks; using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Flow.Launcher.Plugin.Explorer namespace Flow.Launcher.Plugin.Explorer
{ {
public class Settings public class Settings
{ {
[JsonProperty]
public int MaxResult { get; set; } = 100; public int MaxResult { get; set; } = 100;
[JsonProperty]
public List<FolderLink> QuickFolderAccessLinks { get; set; } = new List<FolderLink>(); public List<FolderLink> QuickFolderAccessLinks { get; set; } = new List<FolderLink>();
[JsonProperty]
public bool UseWindowsIndexForDirectorySearch { get; set; } = true; public bool UseWindowsIndexForDirectorySearch { get; set; } = true;
[JsonProperty]
public List<FolderLink> IndexSearchExcludedSubdirectoryPaths { get; set; } = new List<FolderLink>(); public List<FolderLink> IndexSearchExcludedSubdirectoryPaths { get; set; } = new List<FolderLink>();
[JsonProperty]
public string SearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign; public string SearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign;
[JsonProperty]
public string FileContentSearchActionKeyword { get; set; } = Constants.DefaultContentSearchActionKeyword; public string FileContentSearchActionKeyword { get; set; } = Constants.DefaultContentSearchActionKeyword;
} }
} }

View file

@ -3,6 +3,7 @@ using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search;
using Flow.Launcher.Plugin.Explorer.Search.FolderLinks; using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks;
namespace Flow.Launcher.Plugin.Explorer.ViewModels namespace Flow.Launcher.Plugin.Explorer.ViewModels
{ {
@ -21,6 +22,11 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
Settings = storage.Load(); Settings = storage.Load();
} }
public Task LoadStorage()
{
return Task.Run(() => Settings = storage.Load());
}
public void Save() public void Save()
{ {
storage.Save(); storage.Save();

View file

@ -7,7 +7,7 @@
"Name": "Explorer", "Name": "Explorer",
"Description": "Search and manage files and folders. Explorer utilises Windows Index Search", "Description": "Search and manage files and folders. Explorer utilises Windows Index Search",
"Author": "Jeremy Wu", "Author": "Jeremy Wu",
"Version": "1.2.5", "Version": "1.2.6",
"Language": "csharp", "Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher", "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll", "ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",

View file

@ -10,6 +10,7 @@
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -46,55 +47,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Images\work.png"> <Content Include="Languages\*.xaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\en.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> <Content Include="Images\*.png">
<ItemGroup>
<Content Include="Languages\zh-cn.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\zh-tw.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\de.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\pl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\tr.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>

View file

@ -4,7 +4,7 @@
"Name": "Plugin Indicator", "Name": "Plugin Indicator",
"Description": "Provide plugin actionword suggestion", "Description": "Provide plugin actionword suggestion",
"Author": "qianlifeng", "Author": "qianlifeng",
"Version": "1.1.1", "Version": "1.1.2",
"Language": "csharp", "Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher", "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.PluginIndicator.dll", "ExecuteFileName": "Flow.Launcher.Plugin.PluginIndicator.dll",

View file

@ -1,4 +1,5 @@
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.PluginsManager.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
@ -9,21 +10,67 @@ namespace Flow.Launcher.Plugin.PluginsManager
{ {
private PluginInitContext Context { get; set; } private PluginInitContext Context { get; set; }
private Settings Settings { get; set; } public ContextMenu(PluginInitContext context)
public ContextMenu(PluginInitContext context, Settings settings)
{ {
Context = context; Context = context;
Settings = settings;
} }
public List<Result> LoadContextMenus(Result selectedResult) public List<Result> LoadContextMenus(Result selectedResult)
{ {
// Open website var pluginManifestInfo = selectedResult.ContextData as UserPlugin;
// Go to source code
// Report an issue? return new List<Result>
// Request a feature? {
return new List<Result>(); new Result
{
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_openwebsite_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_openwebsite_subtitle"),
IcoPath = "Images\\website.png",
Action = _ =>
{
SharedCommands.SearchWeb.NewTabInBrowser(pluginManifestInfo.Website);
return true;
}
},
new Result
{
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_subtitle"),
IcoPath = "Images\\sourcecode.png",
Action = _ =>
{
SharedCommands.SearchWeb.NewTabInBrowser(pluginManifestInfo.UrlSourceCode);
return true;
}
},
new Result
{
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_subtitle"),
IcoPath = "Images\\request.png",
Action = _ =>
{
// standard UrlSourceCode format in PluginsManifest's plugins.json file: https://github.com/jjw24/WoxDictionary/tree/master
var link = pluginManifestInfo.UrlSourceCode.StartsWith("https://github.com")
? pluginManifestInfo.UrlSourceCode.Replace("/tree/master", "/issues/new/choose")
: pluginManifestInfo.UrlSourceCode;
SharedCommands.SearchWeb.NewTabInBrowser(link);
return true;
}
},
new Result
{
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle"),
IcoPath = selectedResult.IcoPath,
Action = _ =>
{
SharedCommands.SearchWeb.NewTabInBrowser("https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest");
return true;
}
}
};
} }
} }
} }

View file

@ -27,15 +27,12 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="Images\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Languages\**"> <Content Include="Languages\*.xaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\*.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

View file

@ -1,4 +1,4 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"> xmlns:system="clr-namespace:System;assembly=mscorlib">
@ -11,6 +11,15 @@
<system:String x:Key="plugin_pluginsmanager_install_title">Plugin Install</system:String> <system:String x:Key="plugin_pluginsmanager_install_title">Plugin Install</system:String>
<system:String x:Key="plugin_pluginsmanager_uninstall_title">Plugin Uninstall</system:String> <system:String x:Key="plugin_pluginsmanager_uninstall_title">Plugin Uninstall</system:String>
<system:String x:Key="plugin_pluginsmanager_install_errormetadatafile">Install failed: unable to find the plugin.json metadata file from the new plugin</system:String> <system:String x:Key="plugin_pluginsmanager_install_errormetadatafile">Install failed: unable to find the plugin.json metadata file from the new plugin</system:String>
<system:String x:Key="plugin_pluginsmanager_install_error_title">Error installing plugin</system:String>
<system:String x:Key="plugin_pluginsmanager_install_error_subtitle">Error occured while trying to install {0}</system:String>
<system:String x:Key="plugin_pluginsmanager_update_noresult_title">No update available</system:String>
<system:String x:Key="plugin_pluginsmanager_update_noresult_subtitle">All plugins are up to date</system:String>
<system:String x:Key="plugin_pluginsmanager_update_prompt">{0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart.</system:String>
<system:String x:Key="plugin_pluginsmanager_update_title">Plugin Update</system:String>
<system:String x:Key="plugin_pluginsmanager_update_exists">This plugin has an update, would you like to see it?</system:String>
<system:String x:Key="plugin_pluginsmanager_update_alreadyexists">This plugin is already installed</system:String>
<!--Controls--> <!--Controls-->
<!--Plugin Infos--> <!--Plugin Infos-->
@ -18,5 +27,13 @@
<system:String x:Key="plugin_pluginsmanager_plugin_description">Management of installing, uninstalling or updating Flow Launcher plugins</system:String> <system:String x:Key="plugin_pluginsmanager_plugin_description">Management of installing, uninstalling or updating Flow Launcher plugins</system:String>
<!--Context menu items--> <!--Context menu items-->
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_openwebsite_title">Open website</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_openwebsite_subtitle">Visit the plugin's website</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_title">See source code</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_subtitle">See the plugin's source code</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_newissue_title">Suggest an enhancement or submit an issue</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_newissue_subtitle">Suggest an enhancement or submit an issue to the plugin developer</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title">Go to Flow's plugins repository</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle">Visit the PluginsManifest repository to see comunity-made plugin submissions</system:String>
</ResourceDictionary> </ResourceDictionary>

View file

@ -0,0 +1,39 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<!--Dialogues-->
<system:String x:Key="plugin_pluginsmanager_downloading_plugin">Sťahovanie pluginu</system:String>
<system:String x:Key="plugin_pluginsmanager_please_wait">Čakajte, prosím…</system:String>
<system:String x:Key="plugin_pluginsmanager_download_success">Úspešne stiahnuté</system:String>
<system:String x:Key="plugin_pluginsmanager_uninstall_prompt">{0} od {1} {2}{3}Chcete odinštalovať tento plugin? Po odinštalovaní sa Flow automaticky reštartuje.</system:String>
<system:String x:Key="plugin_pluginsmanager_install_prompt">{0} by {1} {2}{3}Chcete nainštalovať tento plugin? Po odinštalovaní sa Flow automaticky reštartuje.</system:String>
<system:String x:Key="plugin_pluginsmanager_install_title">Inštalovať plugin</system:String>
<system:String x:Key="plugin_pluginsmanager_uninstall_title">Odinštalovať plugin</system:String>
<system:String x:Key="plugin_pluginsmanager_install_errormetadatafile">Inštalácia zlyhala: nepodarilo sa nájsť metadáta súboru plugin.json nového pluginu</system:String>
<system:String x:Key="plugin_pluginsmanager_install_error_title">Chyba inštalácie pluginu</system:String>
<system:String x:Key="plugin_pluginsmanager_install_error_subtitle">Nastala chyba počas inštaláciu pluginu {0}</system:String>
<system:String x:Key="plugin_pluginsmanager_update_noresult_title">Nie je k dispozícii žiadna aktualizácia</system:String>
<system:String x:Key="plugin_pluginsmanager_update_noresult_subtitle">Všetky pluginy sú aktuálne</system:String>
<system:String x:Key="plugin_pluginsmanager_update_prompt">{0} od {1} {2}{3}Chcete aktualizovať tento plugin? Po odinštalovaní sa Flow automaticky reštartuje.</system:String>
<system:String x:Key="plugin_pluginsmanager_update_title">Aktualizácia pluginu</system:String>
<system:String x:Key="plugin_pluginsmanager_update_exists">Tento plugin má dostupnú aktualizáciu, chcete ju zobraziť?</system:String>
<system:String x:Key="plugin_pluginsmanager_update_alreadyexists">Tento plugin je už nainštalovaný</system:String>
<!--Controls-->
<!--Plugin Infos-->
<system:String x:Key="plugin_pluginsmanager_plugin_name">Správca pluginov</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_description">Správa inštalácie, odinštalácie alebo aktualizácie pluginov programu Flow Launcher</system:String>
<!--Context menu items-->
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_openwebsite_title">Prejsť na webovú stránku</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_openwebsite_subtitle">Prejsť na webovú stránku pluginu</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_title">Zobraziť zdrojový kód</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_subtitle">Zobraziť zdrojový kód pluginu</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_newissue_title">Navrhnúť vylepšenie alebo nahlásiť chybu</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_newissue_subtitle">Navrhnúť vylepšenie alebo nahlásiť chybu vývojárovi pluginu</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title">Prejsť na repozitár pluginov spúšťača Flow</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle">Prejsť na repozitár pluginov spúšťača Flow a zobraziť príspevky komunity</system:String>
</ResourceDictionary>

View file

@ -1,13 +1,17 @@
using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.PluginsManager.ViewModels; using Flow.Launcher.Plugin.PluginsManager.ViewModels;
using Flow.Launcher.Plugin.PluginsManager.Views; using Flow.Launcher.Plugin.PluginsManager.Views;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls; using System.Windows.Controls;
using Flow.Launcher.Infrastructure;
using System;
using System.Threading.Tasks;
using System.Threading;
namespace Flow.Launcher.Plugin.PluginsManager namespace Flow.Launcher.Plugin.PluginsManager
{ {
public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu, IPluginI18n public class Main : ISettingProvider, IAsyncPlugin, ISavable, IContextMenu, IPluginI18n, IAsyncReloadable
{ {
internal PluginInitContext Context { get; set; } internal PluginInitContext Context { get; set; }
@ -17,17 +21,24 @@ namespace Flow.Launcher.Plugin.PluginsManager
private IContextMenu contextMenu; private IContextMenu contextMenu;
internal PluginsManager pluginManager;
private DateTime lastUpdateTime = DateTime.MinValue;
public Control CreateSettingPanel() public Control CreateSettingPanel()
{ {
return new PluginsManagerSettings(viewModel); return new PluginsManagerSettings(viewModel);
} }
public void Init(PluginInitContext context) public async Task InitAsync(PluginInitContext context)
{ {
Context = context; Context = context;
viewModel = new SettingsViewModel(context); viewModel = new SettingsViewModel(context);
Settings = viewModel.Settings; Settings = viewModel.Settings;
contextMenu = new ContextMenu(Context, Settings); contextMenu = new ContextMenu(Context);
pluginManager = new PluginsManager(Context, Settings);
await pluginManager.UpdateManifest();
lastUpdateTime = DateTime.Now;
} }
public List<Result> LoadContextMenus(Result selectedResult) public List<Result> LoadContextMenus(Result selectedResult)
@ -35,17 +46,30 @@ namespace Flow.Launcher.Plugin.PluginsManager
return contextMenu.LoadContextMenus(selectedResult); return contextMenu.LoadContextMenus(selectedResult);
} }
public List<Result> Query(Query query) public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
{ {
var search = query.Search.ToLower(); var search = query.Search.ToLower();
var pluginManager = new PluginsManager(Context, Settings); if (string.IsNullOrWhiteSpace(search))
return pluginManager.GetDefaultHotKeys();
if (!string.IsNullOrEmpty(search) if ((DateTime.Now - lastUpdateTime).TotalHours > 12) // 12 hours
&& ($"{Settings.UninstallHotkey} ".StartsWith(search) || search.StartsWith($"{Settings.UninstallHotkey} "))) {
return pluginManager.RequestUninstall(search); await pluginManager.UpdateManifest();
lastUpdateTime = DateTime.Now;
return pluginManager.RequestInstallOrUpdate(search); }
return search switch
{
var s when s.StartsWith(Settings.HotKeyInstall) => pluginManager.RequestInstallOrUpdate(s),
var s when s.StartsWith(Settings.HotkeyUninstall) => pluginManager.RequestUninstall(s),
var s when s.StartsWith(Settings.HotkeyUpdate) => pluginManager.RequestUpdate(s),
_ => pluginManager.GetDefaultHotKeys().Where(hotkey =>
{
hotkey.Score = StringMatcher.FuzzySearch(search, hotkey.Title).Score;
return hotkey.Score > 0;
}).ToList()
};
} }
public void Save() public void Save()
@ -62,5 +86,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
{ {
return Context.API.GetTranslation("plugin_pluginsmanager_plugin_description"); return Context.API.GetTranslation("plugin_pluginsmanager_plugin_description");
} }
public async Task ReloadDataAsync()
{
await pluginManager.UpdateManifest();
lastUpdateTime = DateTime.Now;
}
} }
} }

View file

@ -1,32 +1,24 @@
using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Flow.Launcher.Plugin.PluginsManager.Models namespace Flow.Launcher.Plugin.PluginsManager.Models
{ {
internal class PluginsManifest internal class PluginsManifest
{ {
internal List<UserPlugin> UserPlugins { get; private set; } internal List<UserPlugin> UserPlugins { get; private set; } = new List<UserPlugin>();
internal PluginsManifest()
{
DownloadManifest();
}
private void DownloadManifest() internal async Task DownloadManifest()
{ {
var json = string.Empty;
try try
{ {
var t = Task.Run( await using var jsonStream = await Http.GetStreamAsync("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/main/plugins.json")
async () => .ConfigureAwait(false);
json = await Http.Get("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/main/plugins.json"));
t.Wait(); UserPlugins = await JsonSerializer.DeserializeAsync<List<UserPlugin>>(jsonStream).ConfigureAwait(false);
UserPlugins = JsonConvert.DeserializeObject<List<UserPlugin>>(json);
} }
catch (Exception e) catch (Exception e)
{ {
@ -34,7 +26,6 @@ namespace Flow.Launcher.Plugin.PluginsManager.Models
UserPlugins = new List<UserPlugin>(); UserPlugins = new List<UserPlugin>();
} }
} }
} }
} }

View file

@ -1,7 +1,4 @@
using System; 
using System.Collections.Generic;
using System.Text;
namespace Flow.Launcher.Plugin.PluginsManager.Models namespace Flow.Launcher.Plugin.PluginsManager.Models
{ {
public class UserPlugin public class UserPlugin

View file

@ -7,18 +7,35 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
namespace Flow.Launcher.Plugin.PluginsManager namespace Flow.Launcher.Plugin.PluginsManager
{ {
internal class PluginsManager internal class PluginsManager
{ {
private readonly PluginsManifest pluginsManifest; private PluginsManifest pluginsManifest;
private PluginInitContext Context { get; set; } private PluginInitContext Context { get; set; }
private Settings Settings { get; set; } private Settings Settings { get; set; }
private bool shouldHideWindow = true;
private bool ShouldHideWindow
{
set { shouldHideWindow = value; }
get
{
var setValue = shouldHideWindow;
// Default value for hide main window is true. Revert after get call.
// This ensures when set by another method to false, it is only used once.
shouldHideWindow = true;
return setValue;
}
}
private readonly string icoPath = "Images\\pluginsmanager.png"; private readonly string icoPath = "Images\\pluginsmanager.png";
internal PluginsManager(PluginInitContext context, Settings settings) internal PluginsManager(PluginInitContext context, Settings settings)
@ -27,47 +44,202 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context = context; Context = context;
Settings = settings; Settings = settings;
} }
internal void InstallOrUpdate(UserPlugin plugin)
internal async Task UpdateManifest()
{
await pluginsManifest.DownloadManifest();
}
internal List<Result> GetDefaultHotKeys()
{
return new List<Result>()
{
new Result()
{
Title = Settings.HotKeyInstall,
IcoPath = icoPath,
Action = _ =>
{
Context.API.ChangeQuery("pm install ");
return false;
}
},
new Result()
{
Title = Settings.HotkeyUninstall,
IcoPath = icoPath,
Action = _ =>
{
Context.API.ChangeQuery("pm uninstall ");
return false;
}
},
new Result()
{
Title = Settings.HotkeyUpdate,
IcoPath = icoPath,
Action = _ =>
{
Context.API.ChangeQuery("pm update ");
return false;
}
}
};
}
internal async Task InstallOrUpdate(UserPlugin plugin)
{ {
if (PluginExists(plugin.ID)) if (PluginExists(plugin.ID))
{ {
Context.API.ShowMsg("Plugin already installed"); if (Context.API.GetAllPlugins()
.Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version.CompareTo(plugin.Version) < 0))
{
if (MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_update_exists"),
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
Context
.API
.ChangeQuery(
$"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.HotkeyUpdate} {plugin.Name}");
Application.Current.MainWindow.Show();
shouldHideWindow = false;
return;
}
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_alreadyexists"));
return; return;
} }
var message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"), var message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"),
plugin.Name, plugin.Author, plugin.Name, plugin.Author,
Environment.NewLine, Environment.NewLine); Environment.NewLine, Environment.NewLine);
if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"), MessageBoxButton.YesNo) == MessageBoxResult.No) if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"),
MessageBoxButton.YesNo) == MessageBoxResult.No)
return; return;
var filePath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}{plugin.ID}.zip"); var filePath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}-{plugin.Version}.zip");
try try
{ {
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
Context.API.GetTranslation("plugin_pluginsmanager_please_wait")); Context.API.GetTranslation("plugin_pluginsmanager_please_wait"));
Http.Download(plugin.UrlDownload, filePath); await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
Context.API.GetTranslation("plugin_pluginsmanager_download_success")); Context.API.GetTranslation("plugin_pluginsmanager_download_success"));
Install(plugin, filePath);
} }
catch (Exception e) catch (Exception e)
{ {
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
Context.API.GetTranslation("plugin_pluginsmanager_download_success")); string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), plugin.Name));
Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "PluginDownload"); Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "InstallOrUpdate");
return;
} }
Application.Current.Dispatcher.Invoke(() => Install(plugin, filePath)); Context.API.RestartApp();
} }
internal void Update() internal List<Result> RequestUpdate(string search)
{ {
throw new NotImplementedException(); var autocompletedResults = AutoCompleteReturnAllResults(search,
Settings.HotkeyUpdate,
"Update",
"Select a plugin to update");
if (autocompletedResults.Any())
return autocompletedResults;
var uninstallSearch = search.Replace(Settings.HotkeyUpdate, string.Empty).TrimStart();
var resultsForUpdate =
from existingPlugin in Context.API.GetAllPlugins()
join pluginFromManifest in pluginsManifest.UserPlugins
on existingPlugin.Metadata.ID equals pluginFromManifest.ID
where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) < 0 // if current version precedes manifest version
select
new
{
pluginFromManifest.Name,
pluginFromManifest.Author,
CurrentVersion = existingPlugin.Metadata.Version,
NewVersion = pluginFromManifest.Version,
existingPlugin.Metadata.IcoPath,
PluginExistingMetadata = existingPlugin.Metadata,
PluginNewUserPlugin = pluginFromManifest
};
if (!resultsForUpdate.Any())
return new List<Result>
{
new Result
{
Title = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_subtitle"),
IcoPath = icoPath
}
};
var results = resultsForUpdate
.Select(x =>
new Result
{
Title = $"{x.Name} by {x.Author}",
SubTitle = $"Update from version {x.CurrentVersion} to {x.NewVersion}",
IcoPath = x.IcoPath,
Action = e =>
{
string message = string.Format(
Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"),
x.Name, x.Author,
Environment.NewLine, Environment.NewLine);
if (MessageBox.Show(message,
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
Uninstall(x.PluginExistingMetadata);
var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory,
$"{x.Name}-{x.NewVersion}.zip");
Task.Run(async delegate
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
Context.API.GetTranslation("plugin_pluginsmanager_please_wait"));
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath).ConfigureAwait(false);
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
Context.API.GetTranslation("plugin_pluginsmanager_download_success"));
Install(x.PluginNewUserPlugin, downloadToFilePath);
Context.API.RestartApp();
}).ContinueWith(t =>
{
Log.Exception("PluginsManager", $"Update failed for {x.Name}", t.Exception.InnerException, "RequestUpdate");
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), x.Name));
}, TaskContinuationOptions.OnlyOnFaulted);
return true;
}
return false;
}
});
return Search(results, uninstallSearch);
} }
internal bool PluginExists(string id) internal bool PluginExists(string id)
@ -75,51 +247,46 @@ namespace Flow.Launcher.Plugin.PluginsManager
return Context.API.GetAllPlugins().Any(x => x.Metadata.ID == id); return Context.API.GetAllPlugins().Any(x => x.Metadata.ID == id);
} }
internal void PluginsManifestSiteOpen() internal List<Result> Search(IEnumerable<Result> results, string searchName)
{
//Open from context menu https://git.vcmq.workers.dev/Flow-Launcher/Flow.Launcher.PluginsManifest
throw new NotImplementedException();
}
internal List<Result> Search(List<Result> results, string searchName)
{ {
if (string.IsNullOrEmpty(searchName)) if (string.IsNullOrEmpty(searchName))
return results; return results.ToList();
return results return results
.Where(x => .Where(x =>
{ {
var matchResult = StringMatcher.FuzzySearch(searchName, x.Title); var matchResult = StringMatcher.FuzzySearch(searchName, x.Title);
if (matchResult.IsSearchPrecisionScoreMet()) if (matchResult.IsSearchPrecisionScoreMet())
x.Score = matchResult.Score; x.Score = matchResult.Score;
return matchResult.IsSearchPrecisionScoreMet(); return matchResult.IsSearchPrecisionScoreMet();
}) })
.ToList(); .ToList();
} }
internal List<Result> RequestInstallOrUpdate(string searchName) internal List<Result> RequestInstallOrUpdate(string searchName)
{ {
var searchNameWithoutKeyword = searchName.Replace(Settings.HotKeyInstall, string.Empty).Trim();
var results = var results =
pluginsManifest pluginsManifest
.UserPlugins .UserPlugins
.Select(x => .Select(x =>
new Result new Result
{
Title = $"{x.Name} by {x.Author}",
SubTitle = x.Description,
IcoPath = icoPath,
Action = e =>
{ {
Application.Current.MainWindow.Hide(); Title = $"{x.Name} by {x.Author}",
InstallOrUpdate(x); SubTitle = x.Description,
IcoPath = icoPath,
Action = e =>
{
Application.Current.MainWindow.Hide();
_ = InstallOrUpdate(x); // No need to wait
return ShouldHideWindow;
},
ContextData = x
});
return true; return Search(results, searchNameWithoutKeyword);
}
})
.ToList();
return Search(results, searchName);
} }
private void Install(UserPlugin plugin, string downloadedFilePath) private void Install(UserPlugin plugin, string downloadedFilePath)
@ -153,32 +320,82 @@ namespace Flow.Launcher.Plugin.PluginsManager
return; return;
} }
string newPluginPath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}{plugin.ID}"); string newPluginPath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}-{plugin.Version}");
Directory.Move(pluginFolderPath, newPluginPath); Directory.Move(pluginFolderPath, newPluginPath);
Context.API.RestartApp();
} }
internal List<Result> RequestUninstall(string search) internal List<Result> RequestUninstall(string search)
{
var autocompletedResults = AutoCompleteReturnAllResults(search,
Settings.HotkeyUninstall,
"Uninstall",
"Select a plugin to uninstall");
if (autocompletedResults.Any())
return autocompletedResults;
var uninstallSearch = search.Replace(Settings.HotkeyUninstall, string.Empty).TrimStart();
var results = Context.API
.GetAllPlugins()
.Select(x =>
new Result
{
Title = $"{x.Metadata.Name} by {x.Metadata.Author}",
SubTitle = x.Metadata.Description,
IcoPath = x.Metadata.IcoPath,
Action = e =>
{
string message = string.Format(
Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"),
x.Metadata.Name, x.Metadata.Author,
Environment.NewLine, Environment.NewLine);
if (MessageBox.Show(message,
Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
Application.Current.MainWindow.Hide();
Uninstall(x.Metadata);
Context.API.RestartApp();
return true;
}
return false;
}
});
return Search(results, uninstallSearch);
}
private void Uninstall(PluginMetadata plugin)
{
// Marked for deletion. Will be deleted on next start up
using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
}
private List<Result> AutoCompleteReturnAllResults(string search, string hotkey, string title, string subtitle)
{ {
if (!string.IsNullOrEmpty(search) if (!string.IsNullOrEmpty(search)
&& Settings.UninstallHotkey.StartsWith(search) && hotkey.StartsWith(search)
&& (Settings.UninstallHotkey != search || !search.StartsWith(Settings.UninstallHotkey))) && (hotkey != search || !search.StartsWith(hotkey)))
{ {
return return
new List<Result> new List<Result>
{ {
new Result new Result
{ {
Title = "Uninstall", Title = title,
IcoPath = icoPath, IcoPath = icoPath,
SubTitle = "Select a plugin to uninstall", SubTitle = subtitle,
Action = e => Action = e =>
{ {
Context Context
.API .API
.ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UninstallHotkey} "); .ChangeQuery(
$"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {hotkey} ");
return false; return false;
} }
@ -186,42 +403,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}; };
} }
var uninstallSearch = search.Replace(Settings.UninstallHotkey, string.Empty).TrimStart(); return new List<Result>();
var results= Context.API
.GetAllPlugins()
.Select(x =>
new Result
{
Title = $"{x.Metadata.Name} by {x.Metadata.Author}",
SubTitle = x.Metadata.Description,
IcoPath = x.Metadata.IcoPath,
Action = e =>
{
Application.Current.MainWindow.Hide();
Uninstall(x.Metadata);
return true;
}
})
.ToList();
return Search(results, uninstallSearch);
}
private void Uninstall(PluginMetadata plugin)
{
string message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"),
plugin.Name, plugin.Author,
Environment.NewLine, Environment.NewLine);
if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
Context.API.RestartApp();
}
} }
} }
} }

View file

@ -6,6 +6,9 @@ namespace Flow.Launcher.Plugin.PluginsManager
{ {
internal class Settings internal class Settings
{ {
internal string UninstallHotkey { get; set; } = "uninstall"; internal string HotKeyInstall { get; set; } = "install";
internal string HotkeyUninstall { get; set; } = "uninstall";
internal string HotkeyUpdate { get; set; } = "update";
} }
} }

View file

@ -6,7 +6,7 @@
"Name": "Plugins Manager", "Name": "Plugins Manager",
"Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins",
"Author": "Jeremy Wu", "Author": "Jeremy Wu",
"Version": "1.1.0", "Version": "1.5.0",
"Language": "csharp", "Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher", "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll",

View file

@ -36,14 +36,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="Images\app.png"> <Content Include="Languages\*.xaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\en.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Images\*.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="plugin.json"> <Content Include="plugin.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>

View file

@ -4,7 +4,7 @@
"Name":"Process Killer", "Name":"Process Killer",
"Description":"kill running processes from Flow", "Description":"kill running processes from Flow",
"Author":"Flow-Launcher", "Author":"Flow-Launcher",
"Version":"1.2.1", "Version":"1.2.2",
"Language":"csharp", "Language":"csharp",
"Website":"https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller", "Website":"https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller",
"IcoPath":"Images\\app.png", "IcoPath":"Images\\app.png",

View file

@ -49,52 +49,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Images\program.png"> <Content Include="Languages\*.xaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Images\cmd.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Images\folder.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Images\disable.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Images\user.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Content Include="Languages\en.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> <Content Include="Images\*.png">
<ItemGroup>
<Content Include="Languages\zh-cn.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\zh-tw.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\de.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\pl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\tr.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Controls; using System.Windows.Controls;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
@ -12,9 +13,8 @@ using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
namespace Flow.Launcher.Plugin.Program namespace Flow.Launcher.Plugin.Program
{ {
public class Main : ISettingProvider, IPlugin, IPluginI18n, IContextMenu, ISavable, IReloadable public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable
{ {
private static readonly object IndexLock = new object();
internal static Win32[] _win32s { get; set; } internal static Win32[] _win32s { get; set; }
internal static UWP.Application[] _uwps { get; set; } internal static UWP.Application[] _uwps { get; set; }
internal static Settings _settings { get; set; } internal static Settings _settings { get; set; }
@ -30,33 +30,6 @@ namespace Flow.Launcher.Plugin.Program
public Main() public Main()
{ {
_settingsStorage = new PluginJsonStorage<Settings>(); _settingsStorage = new PluginJsonStorage<Settings>();
_settings = _settingsStorage.Load();
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", () =>
{
_win32Storage = new BinaryStorage<Win32[]>("Win32");
_win32s = _win32Storage.TryLoad(new Win32[] { });
_uwpStorage = new BinaryStorage<UWP.Application[]>("UWP");
_uwps = _uwpStorage.TryLoad(new UWP.Application[] { });
});
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>");
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>");
var a = Task.Run(() =>
{
if (IsStartupIndexProgramsRequired || !_win32s.Any())
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs);
});
var b = Task.Run(() =>
{
if (IsStartupIndexProgramsRequired || !_uwps.Any())
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUWPPrograms);
});
Task.WaitAll(a, b);
_settings.LastIndexTime = DateTime.Today;
} }
public void Save() public void Save()
@ -66,7 +39,7 @@ namespace Flow.Launcher.Plugin.Program
_uwpStorage.Save(_uwps); _uwpStorage.Save(_uwps);
} }
public List<Result> Query(Query query) public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
{ {
Win32[] win32; Win32[] win32;
UWP.Application[] uwps; UWP.Application[] uwps;
@ -74,20 +47,81 @@ namespace Flow.Launcher.Plugin.Program
win32 = _win32s; win32 = _win32s;
uwps = _uwps; uwps = _uwps;
var result = win32.Cast<IProgram>() try
.Concat(uwps) {
.AsParallel() var result = await Task.Run(delegate
.Where(p => p.Enabled) {
.Select(p => p.Result(query.Search, _context.API)) try
.Where(r => r?.Score > 0) {
.ToList(); return win32.Cast<IProgram>()
.Concat(uwps)
.AsParallel()
.WithCancellation(token)
.Where(p => p.Enabled)
.Select(p => p.Result(query.Search, _context.API))
.Where(r => r?.Score > 0)
.ToList();
}
catch (OperationCanceledException)
{
return null;
}
}, token).ConfigureAwait(false);
return result; token.ThrowIfCancellationRequested();
return result;
}
catch (OperationCanceledException)
{
return null;
}
} }
public void Init(PluginInitContext context) public async Task InitAsync(PluginInitContext context)
{ {
_context = context; _context = context;
await Task.Run(() =>
{
_settings = _settingsStorage.Load();
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", () =>
{
_win32Storage = new BinaryStorage<Win32[]>("Win32");
_win32s = _win32Storage.TryLoad(new Win32[] { });
_uwpStorage = new BinaryStorage<UWP.Application[]>("UWP");
_uwps = _uwpStorage.TryLoad(new UWP.Application[] { });
});
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>");
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>");
});
bool indexedWinApps = false;
bool indexedUWPApps = false;
var a = Task.Run(() =>
{
if (IsStartupIndexProgramsRequired || !_win32s.Any())
{
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs);
indexedWinApps = true;
}
});
var b = Task.Run(() =>
{
if (IsStartupIndexProgramsRequired || !_uwps.Any())
{
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUwpPrograms);
indexedUWPApps = true;
}
});
await Task.WhenAll(a, b);
if (indexedWinApps && indexedUWPApps)
_settings.LastIndexTime = DateTime.Today;
} }
public static void IndexWin32Programs() public static void IndexWin32Programs()
@ -95,10 +129,9 @@ namespace Flow.Launcher.Plugin.Program
var win32S = Win32.All(_settings); var win32S = Win32.All(_settings);
_win32s = win32S; _win32s = win32S;
} }
public static void IndexUWPPrograms() public static void IndexUwpPrograms()
{ {
var windows10 = new Version(10, 0); var windows10 = new Version(10, 0);
var support = Environment.OSVersion.Version.Major >= windows10.Major; var support = Environment.OSVersion.Version.Major >= windows10.Major;
@ -106,16 +139,15 @@ namespace Flow.Launcher.Plugin.Program
var applications = support ? UWP.All() : new UWP.Application[] { }; var applications = support ? UWP.All() : new UWP.Application[] { };
_uwps = applications; _uwps = applications;
} }
public static void IndexPrograms() public static async Task IndexPrograms()
{ {
var t1 = Task.Run(() => IndexWin32Programs()); var t1 = Task.Run(IndexWin32Programs);
var t2 = Task.Run(() => IndexUWPPrograms()); var t2 = Task.Run(IndexUwpPrograms);
Task.WaitAll(t1, t2); await Task.WhenAll(t1, t2);
_settings.LastIndexTime = DateTime.Today; _settings.LastIndexTime = DateTime.Today;
} }
@ -145,19 +177,21 @@ namespace Flow.Launcher.Plugin.Program
} }
menuOptions.Add( menuOptions.Add(
new Result new Result
{ {
Title = _context.API.GetTranslation("flowlauncher_plugin_program_disable_program"), Title = _context.API.GetTranslation("flowlauncher_plugin_program_disable_program"),
Action = c => Action = c =>
{ {
DisableProgram(program); DisableProgram(program);
_context.API.ShowMsg(_context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_success"), _context.API.ShowMsg(
_context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_success_message")); _context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_success"),
return false; _context.API.GetTranslation(
}, "flowlauncher_plugin_program_disable_dlgtitle_success_message"));
IcoPath = "Images/disable.png" return false;
} },
); IcoPath = "Images/disable.png"
}
);
return menuOptions; return menuOptions;
} }
@ -168,21 +202,25 @@ namespace Flow.Launcher.Plugin.Program
return; return;
if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
_uwps.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier).FirstOrDefault().Enabled = false; _uwps.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)
.FirstOrDefault()
.Enabled = false;
if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
_win32s.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier).FirstOrDefault().Enabled = false; _win32s.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)
.FirstOrDefault()
.Enabled = false;
_settings.DisabledProgramSources _settings.DisabledProgramSources
.Add( .Add(
new Settings.DisabledProgramSource new Settings.DisabledProgramSource
{ {
Name = programToDelete.Name, Name = programToDelete.Name,
Location = programToDelete.Location, Location = programToDelete.Location,
UniqueIdentifier = programToDelete.UniqueIdentifier, UniqueIdentifier = programToDelete.UniqueIdentifier,
Enabled = false Enabled = false
} }
); );
} }
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info) public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
@ -200,9 +238,9 @@ namespace Flow.Launcher.Plugin.Program
} }
} }
public void ReloadData() public async Task ReloadDataAsync()
{ {
IndexPrograms(); await IndexPrograms();
} }
} }
} }

View file

@ -4,7 +4,7 @@
"Name": "Program", "Name": "Program",
"Description": "Search programs in Flow.Launcher", "Description": "Search programs in Flow.Launcher",
"Author": "qianlifeng", "Author": "qianlifeng",
"Version": "1.2.2", "Version": "1.2.3",
"Language": "csharp", "Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher", "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Program.dll", "ExecuteFileName": "Flow.Launcher.Plugin.Program.dll",

View file

@ -33,32 +33,6 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Include="Images\user.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Content Include="Languages\en.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\zh-cn.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\zh-tw.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\de.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" /> <ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
@ -72,20 +46,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Images\shell.png"> <Content Include="Languages\*.xaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="Languages\pl.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Languages\tr.xaml"> <Content Include="Images\*.png">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Page Include="ShellSetting.xaml"> <Page Include="ShellSetting.xaml">

View file

@ -4,7 +4,7 @@
"Name": "Shell", "Name": "Shell",
"Description": "Provide executing commands from Flow Launcher. Commands should start with >", "Description": "Provide executing commands from Flow Launcher. Commands should start with >",
"Author": "qianlifeng", "Author": "qianlifeng",
"Version": "1.1.1", "Version": "1.1.2",
"Language": "csharp", "Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher", "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Shell.dll", "ExecuteFileName": "Flow.Launcher.Plugin.Shell.dll",

View file

@ -40,46 +40,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Images\checkupdate.png"> <Content Include="Languages\*.xaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="Images\recyclebin.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="Images\shutdown.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="Images\sleep.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\en.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Languages\zh-cn.xaml"> <Content Include="Images\*.png">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\zh-tw.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\de.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\pl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\tr.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Page Include="SysSettings.xaml"> <Page Include="SysSettings.xaml">
@ -93,39 +59,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="Images\lock.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="Images\logoff.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="Images\close.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="Images\app.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="Images\restart.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Images\Images\" />
</ItemGroup>
</Project> </Project>

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Forms; using System.Windows.Forms;
using System.Windows.Interop; using System.Windows.Interop;
@ -67,13 +68,15 @@ namespace Flow.Launcher.Plugin.Sys
{ {
c.TitleHighlightData = titleMatch.MatchData; c.TitleHighlightData = titleMatch.MatchData;
} }
else else
{ {
c.SubTitleHighlightData = subTitleMatch.MatchData; c.SubTitleHighlightData = subTitleMatch.MatchData;
} }
results.Add(c); results.Add(c);
} }
} }
return results; return results;
} }
@ -94,13 +97,15 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\shutdown.png", IcoPath = "Images\\shutdown.png",
Action = c => Action = c =>
{ {
var reuslt = MessageBox.Show(context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_shutdown_computer"), var reuslt = MessageBox.Show(
context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"), context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_shutdown_computer"),
MessageBoxButton.YesNo, MessageBoxImage.Warning); context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"),
MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (reuslt == MessageBoxResult.Yes) if (reuslt == MessageBoxResult.Yes)
{ {
Process.Start("shutdown", "/s /t 0"); Process.Start("shutdown", "/s /t 0");
} }
return true; return true;
} }
}, },
@ -111,13 +116,15 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\restart.png", IcoPath = "Images\\restart.png",
Action = c => Action = c =>
{ {
var result = MessageBox.Show(context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer"), var result = MessageBox.Show(
context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"), context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer"),
MessageBoxButton.YesNo, MessageBoxImage.Warning); context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"),
MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes) if (result == MessageBoxResult.Yes)
{ {
Process.Start("shutdown", "/r /t 0"); Process.Start("shutdown", "/r /t 0");
} }
return true; return true;
} }
}, },
@ -164,13 +171,14 @@ namespace Flow.Launcher.Plugin.Sys
// FYI, couldn't find documentation for this but if the recycle bin is already empty, it will return -2147418113 (0x8000FFFF (E_UNEXPECTED)) // 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 // 0 for nothing
var result = SHEmptyRecycleBin(new WindowInteropHelper(Application.Current.MainWindow).Handle, 0); var result = SHEmptyRecycleBin(new WindowInteropHelper(Application.Current.MainWindow).Handle, 0);
if (result != (uint) HRESULT.S_OK && result != (uint)0x8000FFFF) if (result != (uint) HRESULT.S_OK && result != (uint) 0x8000FFFF)
{ {
MessageBox.Show($"Error emptying recycle bin, error code: {result}\n" + MessageBox.Show($"Error emptying recycle bin, error code: {result}\n" +
"please refer to https://msdn.microsoft.com/en-us/library/windows/desktop/aa378137", "please refer to https://msdn.microsoft.com/en-us/library/windows/desktop/aa378137",
"Error", "Error",
MessageBoxButton.OK, MessageBoxImage.Error); MessageBoxButton.OK, MessageBoxImage.Error);
} }
return true; return true;
} }
}, },
@ -229,9 +237,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. // 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(); Application.Current.MainWindow.Hide();
context.API.ReloadAllPluginData();
context.API.ShowMsg(context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"), context.API.ReloadAllPluginData().ContinueWith(_ =>
context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded")); context.API.ShowMsg(
context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_success"),
context.API.GetTranslation(
"flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded")));
return true; return true;
} }
}, },

View file

@ -4,7 +4,7 @@
"Name": "System Commands", "Name": "System Commands",
"Description": "Provide System related commands. e.g. shutdown,lock,setting etc.", "Description": "Provide System related commands. e.g. shutdown,lock,setting etc.",
"Author": "qianlifeng", "Author": "qianlifeng",
"Version": "1.1.1", "Version": "1.1.2",
"Language": "csharp", "Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher", "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Sys.dll", "ExecuteFileName": "Flow.Launcher.Plugin.Sys.dll",

Some files were not shown because too many files have changed in this diff Show more