diff --git a/Doc/Default Icons/app_missing_img.png b/Artworks/Default Icons/app_missing_img.png similarity index 100% rename from Doc/Default Icons/app_missing_img.png rename to Artworks/Default Icons/app_missing_img.png diff --git a/Doc/Default Icons/app_missing_img_01.png b/Artworks/Default Icons/app_missing_img_01.png similarity index 100% rename from Doc/Default Icons/app_missing_img_01.png rename to Artworks/Default Icons/app_missing_img_01.png diff --git a/Doc/Default Icons/app_missing_img_01.svg b/Artworks/Default Icons/app_missing_img_01.svg similarity index 100% rename from Doc/Default Icons/app_missing_img_01.svg rename to Artworks/Default Icons/app_missing_img_01.svg diff --git a/Doc/Default Icons/app_missing_img_02.png b/Artworks/Default Icons/app_missing_img_02.png similarity index 100% rename from Doc/Default Icons/app_missing_img_02.png rename to Artworks/Default Icons/app_missing_img_02.png diff --git a/Doc/Default Icons/app_missing_img_02.svg b/Artworks/Default Icons/app_missing_img_02.svg similarity index 100% rename from Doc/Default Icons/app_missing_img_02.svg rename to Artworks/Default Icons/app_missing_img_02.svg diff --git a/Doc/Default Icons/app_missing_img_03.png b/Artworks/Default Icons/app_missing_img_03.png similarity index 100% rename from Doc/Default Icons/app_missing_img_03.png rename to Artworks/Default Icons/app_missing_img_03.png diff --git a/Doc/Default Icons/app_missing_img_03.svg b/Artworks/Default Icons/app_missing_img_03.svg similarity index 100% rename from Doc/Default Icons/app_missing_img_03.svg rename to Artworks/Default Icons/app_missing_img_03.svg diff --git a/Doc/Default Icons/app_missing_img_buttons.png b/Artworks/Default Icons/app_missing_img_buttons.png similarity index 100% rename from Doc/Default Icons/app_missing_img_buttons.png rename to Artworks/Default Icons/app_missing_img_buttons.png diff --git a/Doc/Default Icons/app_missing_img_buttons.svg b/Artworks/Default Icons/app_missing_img_buttons.svg similarity index 100% rename from Doc/Default Icons/app_missing_img_buttons.svg rename to Artworks/Default Icons/app_missing_img_buttons.svg diff --git a/Doc/Default Icons/app_missing_img_fluent.png b/Artworks/Default Icons/app_missing_img_fluent.png similarity index 100% rename from Doc/Default Icons/app_missing_img_fluent.png rename to Artworks/Default Icons/app_missing_img_fluent.png diff --git a/Doc/Default Icons/app_missing_img_fluent.svg b/Artworks/Default Icons/app_missing_img_fluent.svg similarity index 100% rename from Doc/Default Icons/app_missing_img_fluent.svg rename to Artworks/Default Icons/app_missing_img_fluent.svg diff --git a/Doc/Default Icons/app_missing_img_huge.png b/Artworks/Default Icons/app_missing_img_huge.png similarity index 100% rename from Doc/Default Icons/app_missing_img_huge.png rename to Artworks/Default Icons/app_missing_img_huge.png diff --git a/Doc/Default Icons/app_missing_img_huge.svg b/Artworks/Default Icons/app_missing_img_huge.svg similarity index 100% rename from Doc/Default Icons/app_missing_img_huge.svg rename to Artworks/Default Icons/app_missing_img_huge.svg diff --git a/Doc/Default Icons/app_missing_img_lightblue.png b/Artworks/Default Icons/app_missing_img_lightblue.png similarity index 100% rename from Doc/Default Icons/app_missing_img_lightblue.png rename to Artworks/Default Icons/app_missing_img_lightblue.png diff --git a/Doc/Default Icons/app_missing_img_lightblue.svg b/Artworks/Default Icons/app_missing_img_lightblue.svg similarity index 100% rename from Doc/Default Icons/app_missing_img_lightblue.svg rename to Artworks/Default Icons/app_missing_img_lightblue.svg diff --git a/Doc/Default Icons/app_missing_img_lightblue_buttons.png b/Artworks/Default Icons/app_missing_img_lightblue_buttons.png similarity index 100% rename from Doc/Default Icons/app_missing_img_lightblue_buttons.png rename to Artworks/Default Icons/app_missing_img_lightblue_buttons.png diff --git a/Doc/Default Icons/app_missing_img_lightblue_buttons.svg b/Artworks/Default Icons/app_missing_img_lightblue_buttons.svg similarity index 100% rename from Doc/Default Icons/app_missing_img_lightblue_buttons.svg rename to Artworks/Default Icons/app_missing_img_lightblue_buttons.svg diff --git a/Doc/Default Icons/app_missing_img_minimal.png b/Artworks/Default Icons/app_missing_img_minimal.png similarity index 100% rename from Doc/Default Icons/app_missing_img_minimal.png rename to Artworks/Default Icons/app_missing_img_minimal.png diff --git a/Doc/Default Icons/app_missing_img_minimal.svg b/Artworks/Default Icons/app_missing_img_minimal.svg similarity index 100% rename from Doc/Default Icons/app_missing_img_minimal.svg rename to Artworks/Default Icons/app_missing_img_minimal.svg diff --git a/Doc/Default Icons/app_missing_img_minimal_buttons.png b/Artworks/Default Icons/app_missing_img_minimal_buttons.png similarity index 100% rename from Doc/Default Icons/app_missing_img_minimal_buttons.png rename to Artworks/Default Icons/app_missing_img_minimal_buttons.png diff --git a/Doc/Default Icons/app_missing_img_minimal_buttons.svg b/Artworks/Default Icons/app_missing_img_minimal_buttons.svg similarity index 100% rename from Doc/Default Icons/app_missing_img_minimal_buttons.svg rename to Artworks/Default Icons/app_missing_img_minimal_buttons.svg diff --git a/Doc/Logo/app_error.png b/Artworks/Logo/app_error.png similarity index 100% rename from Doc/Logo/app_error.png rename to Artworks/Logo/app_error.png diff --git a/Doc/Logo/logo.ico b/Artworks/Logo/logo.ico similarity index 100% rename from Doc/Logo/logo.ico rename to Artworks/Logo/logo.ico diff --git a/Doc/Logo/logo.png b/Artworks/Logo/logo.png similarity index 100% rename from Doc/Logo/logo.png rename to Artworks/Logo/logo.png diff --git a/Doc/Logo/logo.svg b/Artworks/Logo/logo.svg similarity index 100% rename from Doc/Logo/logo.svg rename to Artworks/Logo/logo.svg diff --git a/Doc/Logo/logo128.png b/Artworks/Logo/logo128.png similarity index 100% rename from Doc/Logo/logo128.png rename to Artworks/Logo/logo128.png diff --git a/Doc/Logo/logo16.png b/Artworks/Logo/logo16.png similarity index 100% rename from Doc/Logo/logo16.png rename to Artworks/Logo/logo16.png diff --git a/Doc/Logo/logo256.png b/Artworks/Logo/logo256.png similarity index 100% rename from Doc/Logo/logo256.png rename to Artworks/Logo/logo256.png diff --git a/Doc/Logo/logo32.png b/Artworks/Logo/logo32.png similarity index 100% rename from Doc/Logo/logo32.png rename to Artworks/Logo/logo32.png diff --git a/Doc/Logo/logo48.png b/Artworks/Logo/logo48.png similarity index 100% rename from Doc/Logo/logo48.png rename to Artworks/Logo/logo48.png diff --git a/Doc/Logo/logo512.png b/Artworks/Logo/logo512.png similarity index 100% rename from Doc/Logo/logo512.png rename to Artworks/Logo/logo512.png diff --git a/Doc/Logo/logo64.png b/Artworks/Logo/logo64.png similarity index 100% rename from Doc/Logo/logo64.png rename to Artworks/Logo/logo64.png diff --git a/Doc/Logo/resources/flow-header-landscape-transparent.png b/Artworks/Logo/resources/flow-header-landscape-transparent.png similarity index 100% rename from Doc/Logo/resources/flow-header-landscape-transparent.png rename to Artworks/Logo/resources/flow-header-landscape-transparent.png diff --git a/Doc/Logo/resources/flow-header-landscape.png b/Artworks/Logo/resources/flow-header-landscape.png similarity index 100% rename from Doc/Logo/resources/flow-header-landscape.png rename to Artworks/Logo/resources/flow-header-landscape.png diff --git a/Doc/Logo/resources/flow-header-square-transparent.png b/Artworks/Logo/resources/flow-header-square-transparent.png similarity index 100% rename from Doc/Logo/resources/flow-header-square-transparent.png rename to Artworks/Logo/resources/flow-header-square-transparent.png diff --git a/Doc/Logo/resources/flow-header-square.png b/Artworks/Logo/resources/flow-header-square.png similarity index 100% rename from Doc/Logo/resources/flow-header-square.png rename to Artworks/Logo/resources/flow-header-square.png diff --git a/Doc/Logo/resources/flow-logo.ai b/Artworks/Logo/resources/flow-logo.ai similarity index 100% rename from Doc/Logo/resources/flow-logo.ai rename to Artworks/Logo/resources/flow-logo.ai diff --git a/Doc/Logo/resources/preview.pdf b/Artworks/Logo/resources/preview.pdf similarity index 100% rename from Doc/Logo/resources/preview.pdf rename to Artworks/Logo/resources/preview.pdf diff --git a/Doc/app.ico b/Artworks/app.ico similarity index 100% rename from Doc/app.ico rename to Artworks/app.ico diff --git a/Doc/app.png b/Artworks/app.png similarity index 100% rename from Doc/app.png rename to Artworks/app.png diff --git a/Doc/app_error.png b/Artworks/app_error.png similarity index 100% rename from Doc/app_error.png rename to Artworks/app_error.png diff --git a/Doc/app_missing_img.png b/Artworks/app_missing_img.png similarity index 100% rename from Doc/app_missing_img.png rename to Artworks/app_missing_img.png diff --git a/Doc/mainsearch.png b/Artworks/mainsearch.png similarity index 100% rename from Doc/mainsearch.png rename to Artworks/mainsearch.png diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index 6b8902b7b..2b2bef4cf 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -16,7 +16,7 @@ true - full + portable false ..\Output\Debug\ DEBUG;TRACE diff --git a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs index 6a96a94b7..13b6ac968 100644 --- a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs +++ b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs @@ -1,5 +1,8 @@ using System; using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; using Flow.Launcher.Plugin; namespace Flow.Launcher.Core.Plugin @@ -21,17 +24,17 @@ namespace Flow.Launcher.Core.Plugin }; } - protected override string ExecuteQuery(Query query) + protected override Task ExecuteQueryAsync(Query query, CancellationToken token) { JsonRPCServerRequestModel request = new JsonRPCServerRequestModel { Method = "query", - Parameters = new object[] { query.Search }, + Parameters = new object[] {query.Search}, }; _startInfo.Arguments = $"\"{request}\""; - return Execute(_startInfo); + return ExecuteAsync(_startInfo, token); } protected override string ExecuteCallback(JsonRPCRequestModel rpcRequest) @@ -40,10 +43,12 @@ namespace Flow.Launcher.Core.Plugin return Execute(_startInfo); } - protected override string ExecuteContextMenu(Result selectedResult) { - JsonRPCServerRequestModel request = new JsonRPCServerRequestModel { + protected override string ExecuteContextMenu(Result selectedResult) + { + JsonRPCServerRequestModel request = new JsonRPCServerRequestModel + { Method = "contextmenu", - Parameters = new object[] { selectedResult.ContextData }, + Parameters = new object[] {selectedResult.ContextData}, }; _startInfo.Arguments = $"\"{request}\""; diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs index 247fe1889..b1c8ff6ea 100644 --- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs +++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs @@ -45,6 +45,8 @@ namespace Flow.Launcher.Core.Plugin { [JsonPropertyName("result")] public new List Result { get; set; } + + public string DebugMessage { get; set; } } public class JsonRPCRequestModel : JsonRPCModelBase @@ -58,13 +60,12 @@ namespace Flow.Launcher.Core.Plugin string rpc = string.Empty; if (Parameters != null && Parameters.Length > 0) { - string parameters = Parameters.Aggregate("[", (current, o) => current + (GetParameterByType(o) + ",")); - parameters = parameters.Substring(0, parameters.Length - 1) + "]"; - rpc = string.Format(@"{{\""method\"":\""{0}\"",\""parameters\"":{1}", Method, parameters); + string parameters = $"[{string.Join(',', Parameters.Select(GetParameterByType))}]"; + rpc = $@"{{\""method\"":\""{Method}\"",\""parameters\"":{parameters}"; } else { - rpc = string.Format(@"{{\""method\"":\""{0}\"",\""parameters\"":[]", Method); + rpc = $@"{{\""method\"":\""{Method}\"",\""parameters\"":[]"; } return rpc; @@ -72,26 +73,16 @@ namespace Flow.Launcher.Core.Plugin } private string GetParameterByType(object parameter) + => parameter switch { - if (parameter == null) { - return "null"; - } - if (parameter is string) - { - return string.Format(@"\""{0}\""", ReplaceEscapes(parameter.ToString())); - } - if (parameter is int || parameter is float || parameter is double) - { - return string.Format(@"{0}", parameter); - } - if (parameter is bool) - { - return string.Format(@"{0}", parameter.ToString().ToLower()); - } - return parameter.ToString(); - } + null => "null", + string _ => $@"\""{ReplaceEscapes(parameter.ToString())}\""", + bool _ => $@"{parameter.ToString().ToLower()}", + _ => parameter.ToString() + }; - private string ReplaceEscapes(string str) + + private string ReplaceEscapes(string str) { return str.Replace(@"\", @"\\") //Escapes in ProcessStartInfo .Replace(@"\", @"\\") //Escapes itself when passed to client diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index c7ad70391..7a088bd09 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -7,9 +7,9 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; -using Flow.Launcher.Infrastructure.Exception; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin; +using JetBrains.Annotations; namespace Flow.Launcher.Core.Plugin { @@ -17,7 +17,7 @@ namespace Flow.Launcher.Core.Plugin /// Represent the plugin that using JsonPRC /// every JsonRPC plugin should has its own plugin instance /// - internal abstract class JsonRPCPlugin : IPlugin, IContextMenu + internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu { protected PluginInitContext context; public const string JsonRPC = "JsonRPC"; @@ -27,24 +27,10 @@ namespace Flow.Launcher.Core.Plugin /// public abstract string SupportedLanguage { get; set; } - protected abstract string ExecuteQuery(Query query); + protected abstract Task ExecuteQueryAsync(Query query, CancellationToken token); protected abstract string ExecuteCallback(JsonRPCRequestModel rpcRequest); protected abstract string ExecuteContextMenu(Result selectedResult); - public List Query(Query query) - { - string output = ExecuteQuery(query); - try - { - return DeserializedResult(output); - } - catch (Exception e) - { - Log.Exception($"|JsonRPCPlugin.Query|Exception when query <{query}>", e); - return null; - } - } - public List LoadContextMenus(Result selectedResult) { string output = ExecuteContextMenu(selectedResult); @@ -59,50 +45,71 @@ namespace Flow.Launcher.Core.Plugin } } + + + private async Task> DeserializedResultAsync(Stream output) + { + if (output == Stream.Null) return null; + + JsonRPCQueryResponseModel queryResponseModel = await + JsonSerializer.DeserializeAsync(output); + + return ParseResults(queryResponseModel); + } + private List DeserializedResult(string output) { - if (!String.IsNullOrEmpty(output)) + if (string.IsNullOrEmpty(output)) return null; + + JsonRPCQueryResponseModel queryResponseModel = + JsonSerializer.Deserialize(output); + return ParseResults(queryResponseModel); + } + + private List ParseResults(JsonRPCQueryResponseModel queryResponseModel) + { + var results = new List(); + if (queryResponseModel.Result == null) return null; + + if(!string.IsNullOrEmpty(queryResponseModel.DebugMessage)) { - List results = new List(); + context.API.ShowMsg(queryResponseModel.DebugMessage); + } - JsonRPCQueryResponseModel queryResponseModel = JsonSerializer.Deserialize(output); - if (queryResponseModel.Result == null) return null; - - foreach (JsonRPCResult result in queryResponseModel.Result) + foreach (JsonRPCResult result in queryResponseModel.Result) + { + result.Action = c => { - JsonRPCResult result1 = result; - result.Action = c => - { - if (result1.JsonRPCAction == null) return false; + if (result.JsonRPCAction == null) return false; - if (!String.IsNullOrEmpty(result1.JsonRPCAction.Method)) + if (!string.IsNullOrEmpty(result.JsonRPCAction.Method)) + { + if (result.JsonRPCAction.Method.StartsWith("Flow.Launcher.")) { - if (result1.JsonRPCAction.Method.StartsWith("Flow.Launcher.")) + ExecuteFlowLauncherAPI(result.JsonRPCAction.Method.Substring(4), + result.JsonRPCAction.Parameters); + } + else + { + string actionReponse = ExecuteCallback(result.JsonRPCAction); + JsonRPCRequestModel jsonRpcRequestModel = + JsonSerializer.Deserialize(actionReponse); + if (jsonRpcRequestModel != null + && !string.IsNullOrEmpty(jsonRpcRequestModel.Method) + && jsonRpcRequestModel.Method.StartsWith("Flow.Launcher.")) { - ExecuteFlowLauncherAPI(result1.JsonRPCAction.Method.Substring(4), result1.JsonRPCAction.Parameters); - } - else - { - string actionReponse = ExecuteCallback(result1.JsonRPCAction); - JsonRPCRequestModel jsonRpcRequestModel = JsonSerializer.Deserialize(actionReponse); - if (jsonRpcRequestModel != null - && !String.IsNullOrEmpty(jsonRpcRequestModel.Method) - && jsonRpcRequestModel.Method.StartsWith("Flow.Launcher.")) - { - ExecuteFlowLauncherAPI(jsonRpcRequestModel.Method.Substring(4), jsonRpcRequestModel.Parameters); - } + ExecuteFlowLauncherAPI(jsonRpcRequestModel.Method.Substring(4), + jsonRpcRequestModel.Parameters); } } - return !result1.JsonRPCAction.DontHideAfterAction; - }; - results.Add(result); - } - return results; - } - else - { - return null; + } + + return !result.JsonRPCAction.DontHideAfterAction; + }; + results.Add(result); } + + return results; } private void ExecuteFlowLauncherAPI(string method, object[] parameters) @@ -117,9 +124,7 @@ namespace Flow.Launcher.Core.Plugin catch (Exception) { #if (DEBUG) - { - throw; - } + throw; #endif } } @@ -130,17 +135,20 @@ namespace Flow.Launcher.Core.Plugin /// /// /// + /// Cancellation Token /// - protected string Execute(string fileName, string arguments) + protected Task ExecuteAsync(string fileName, string arguments, CancellationToken token = default) { - ProcessStartInfo start = new ProcessStartInfo(); - start.FileName = fileName; - start.Arguments = arguments; - start.UseShellExecute = false; - start.CreateNoWindow = true; - start.RedirectStandardOutput = true; - start.RedirectStandardError = true; - return Execute(start); + ProcessStartInfo start = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + return ExecuteAsync(start, token); } protected string Execute(ProcessStartInfo startInfo) @@ -148,52 +156,102 @@ namespace Flow.Launcher.Core.Plugin try { using var process = Process.Start(startInfo); - if (process == null) - { - Log.Error("|JsonRPCPlugin.Execute|Can't start new process"); - return string.Empty; - } + if (process == null) return string.Empty; using var standardOutput = process.StandardOutput; var result = standardOutput.ReadToEnd(); + if (string.IsNullOrEmpty(result)) { - using (var standardError = process.StandardError) + using var standardError = process.StandardError; + var error = standardError.ReadToEnd(); + if (!string.IsNullOrEmpty(error)) { - var error = standardError.ReadToEnd(); - if (!string.IsNullOrEmpty(error)) - { - Log.Error($"|JsonRPCPlugin.Execute|{error}"); - return string.Empty; - } - else - { - Log.Error("|JsonRPCPlugin.Execute|Empty standard output and standard error."); - return string.Empty; - } + Log.Error($"|JsonRPCPlugin.Execute|{error}"); + return string.Empty; } + + Log.Error("|JsonRPCPlugin.Execute|Empty standard output and standard error."); + return string.Empty; } - else if (result.StartsWith("DEBUG:")) + + if (result.StartsWith("DEBUG:")) { MessageBox.Show(new Form { TopMost = true }, result.Substring(6)); return string.Empty; } - else - { - return result; - } + + return result; } catch (Exception e) { - Log.Exception($"|JsonRPCPlugin.Execute|Exception for filename <{startInfo.FileName}> with argument <{startInfo.Arguments}>", e); + Log.Exception( + $"|JsonRPCPlugin.Execute|Exception for filename <{startInfo.FileName}> with argument <{startInfo.Arguments}>", + e); return string.Empty; } } - public void Init(PluginInitContext ctx) + protected async Task ExecuteAsync(ProcessStartInfo startInfo, CancellationToken token = default) { - context = ctx; + try + { + using var process = Process.Start(startInfo); + if (process == null) + { + Log.Error("|JsonRPCPlugin.ExecuteAsync|Can't start new process"); + return Stream.Null; + } + + var result = process.StandardOutput.BaseStream; + + token.ThrowIfCancellationRequested(); + + if (!process.StandardError.EndOfStream) + { + using var standardError = process.StandardError; + var error = await standardError.ReadToEndAsync(); + + if (!string.IsNullOrEmpty(error)) + { + Log.Error($"|JsonRPCPlugin.ExecuteAsync|{error}"); + return Stream.Null; + } + + Log.Error("|JsonRPCPlugin.ExecuteAsync|Empty standard output and standard error."); + return Stream.Null; + } + + return result; + } + catch (Exception e) + { + Log.Exception( + $"|JsonRPCPlugin.ExecuteAsync|Exception for filename <{startInfo.FileName}> with argument <{startInfo.Arguments}>", + e); + return Stream.Null; + } + } + + public async Task> QueryAsync(Query query, CancellationToken token) + { + var output = await ExecuteQueryAsync(query, token); + try + { + return await DeserializedResultAsync(output); + } + catch (Exception e) + { + Log.Exception($"|JsonRPCPlugin.Query|Exception when query <{query}>", e); + return null; + } + } + + public Task InitAsync(PluginInitContext context) + { + this.context = context; + return Task.CompletedTask; } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 700a7d509..66eb0f0ec 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -195,11 +195,11 @@ namespace Flow.Launcher.Core.Plugin 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; + return null; } 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; diff --git a/Flow.Launcher.Core/Plugin/PythonPlugin.cs b/Flow.Launcher.Core/Plugin/PythonPlugin.cs index 3c5a3a699..356a68a81 100644 --- a/Flow.Launcher.Core/Plugin/PythonPlugin.cs +++ b/Flow.Launcher.Core/Plugin/PythonPlugin.cs @@ -1,6 +1,8 @@ using System; using System.Diagnostics; using System.IO; +using System.Threading; +using System.Threading.Tasks; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin; @@ -28,7 +30,7 @@ namespace Flow.Launcher.Core.Plugin } - protected override string ExecuteQuery(Query query) + protected override Task ExecuteQueryAsync(Query query, CancellationToken token) { JsonRPCServerRequestModel request = new JsonRPCServerRequestModel { @@ -40,13 +42,14 @@ namespace Flow.Launcher.Core.Plugin // todo happlebao why context can't be used in constructor _startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory; - return Execute(_startInfo); + return ExecuteAsync(_startInfo, token); } protected override string ExecuteCallback(JsonRPCRequestModel rpcRequest) { _startInfo.Arguments = $"-B \"{context.CurrentPluginMetadata.ExecuteFilePath}\" \"{rpcRequest}\""; _startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory; + // TODO: Async Action return Execute(_startInfo); } @@ -58,6 +61,7 @@ namespace Flow.Launcher.Core.Plugin _startInfo.Arguments = $"-B \"{context.CurrentPluginMetadata.ExecuteFilePath}\" \"{request}\""; _startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory; + // TODO: Async Action return Execute(_startInfo); } } diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 44c34968c..8f0de798c 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -87,7 +87,7 @@ namespace Flow.Launcher.Core 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 || e is TaskCanceledException) { Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e); api.ShowMsg(api.GetTranslation("update_flowlauncher_fail"), diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index eda00b435..2f919b5c9 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -15,7 +15,7 @@ true - full + portable false ..\Output\Debug\ DEBUG;TRACE diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 3a3e770a5..b45b6adcd 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -9,6 +9,8 @@ using Flow.Launcher.Infrastructure.UserSettings; using System; using System.ComponentModel; using System.Threading; +using System.Windows.Interop; +using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.Http { @@ -18,6 +20,8 @@ namespace Flow.Launcher.Infrastructure.Http private static HttpClient client = new HttpClient(); + public static IPublicAPI API { get; set; } + static Http() { // need to be added so it would work on a win10 machine @@ -50,25 +54,36 @@ namespace Flow.Launcher.Infrastructure.Http /// public static void UpdateProxy(ProxyProperty property) { - (WebProxy.Address, WebProxy.Credentials) = property switch + if (string.IsNullOrEmpty(Proxy.Server)) + return; + + try { - ProxyProperty.Enabled => Proxy.Enabled switch + (WebProxy.Address, WebProxy.Credentials) = property switch { - true => Proxy.UserName switch + ProxyProperty.Enabled => Proxy.Enabled switch { - 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)) + true when !string.IsNullOrEmpty(Proxy.Server) => Proxy.UserName switch + { + 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)) + }, + _ => (null, null) }, - 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() - }; + 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() + }; + } + catch(UriFormatException e) + { + API.ShowMsg("Please try again", "Unable to parse Http Proxy"); + Log.Exception("Flow.Launcher.Infrastructure.Http", "Unable to parse Uri", e); + } } public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default) diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index f2ee5f1af..e3d26f5d3 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -34,7 +34,7 @@ true - full + portable false ..\Output\Debug\ DEBUG;TRACE diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj index c9f495bfe..1d0dce5e7 100644 --- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj +++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj @@ -15,7 +15,7 @@ true - full + portable false bin\Debug\ DEBUG;TRACE diff --git a/Flow.Launcher.Test/HttpTest.cs b/Flow.Launcher.Test/HttpTest.cs new file mode 100644 index 000000000..637747a07 --- /dev/null +++ b/Flow.Launcher.Test/HttpTest.cs @@ -0,0 +1,33 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Text; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Infrastructure.Http; + +namespace Flow.Launcher.Test +{ + [TestFixture] + class HttpTest + { + [Test] + public void GivenHttpProxy_WhenUpdated_ThenWebProxyShouldAlsoBeUpdatedToTheSame() + { + HttpProxy proxy = new HttpProxy(); + Http.Proxy = proxy; + + proxy.Enabled = true; + proxy.Server = "127.0.0.1"; + Assert.AreEqual(Http.WebProxy.Address, new Uri($"http://{proxy.Server}:{proxy.Port}")); + Assert.IsNull(Http.WebProxy.Credentials); + + proxy.UserName = "test"; + Assert.NotNull(Http.WebProxy.Credentials); + Assert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").UserName, proxy.UserName); + Assert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").Password, ""); + + proxy.Password = "test password"; + Assert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").Password, proxy.Password); + } + } +} diff --git a/Flow.Launcher.Test/UrlPluginTest.cs b/Flow.Launcher.Test/Plugins/UrlPluginTest.cs similarity index 100% rename from Flow.Launcher.Test/UrlPluginTest.cs rename to Flow.Launcher.Test/Plugins/UrlPluginTest.cs diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 7c4c6a367..a2907c927 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -61,7 +61,6 @@ namespace Flow.Launcher _settingsVM = new SettingWindowViewModel(_updater, _portable); _settings = _settingsVM.Settings; - Http.Proxy = _settings.Proxy; _alphabet.Initialize(_settings); _stringMatcher = new StringMatcher(_alphabet); @@ -71,6 +70,10 @@ namespace Flow.Launcher PluginManager.LoadPlugins(_settings.PluginSettings); _mainVM = new MainViewModel(_settings); API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet); + + Http.API = API; + Http.Proxy = _settings.Proxy; + await PluginManager.InitializePlugins(API); var window = new MainWindow(_settings, _mainVM); diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 3b9a56652..f3885ccfd 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -17,7 +17,7 @@ AnyCPU true - full + portable false ..\Output\Debug\ DEBUG;TRACE diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 41f16f4f2..0766c7bbc 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -1,16 +1,13 @@ -using System; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Threading; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; -using Flow.Launcher.Infrastructure.UserSettings; -using Flow.Launcher.Plugin; namespace Flow.Launcher.ViewModel { diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj index d67aae1c9..fc4547556 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -15,7 +15,7 @@ true - full + portable false ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.BrowserBookmark\ DEBUG;TRACE diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj index b3212794b..159f6a728 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj @@ -16,7 +16,7 @@ true - full + portable false ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Caculator\ DEBUG;TRACE diff --git a/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj b/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj index d69547c6c..ff4a10d69 100644 --- a/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj +++ b/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj @@ -14,7 +14,7 @@ true - full + portable false ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.ControlPanel\ DEBUG;TRACE diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 2af09bf2c..d5f882d5c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -22,9 +22,24 @@ namespace Flow.Launcher.Plugin.Explorer.Search this.settings = settings; } + private class PathEqualityComparator : IEqualityComparer + { + private static PathEqualityComparator instance; + public static PathEqualityComparator Instance => instance ??= new PathEqualityComparator(); + public bool Equals(Result x, Result y) + { + return x.SubTitle == y.SubTitle; + } + + public int GetHashCode(Result obj) + { + return obj.SubTitle.GetHashCode(); + } + } + internal async Task> SearchAsync(Query query, CancellationToken token) { - var results = new List(); + var results = new HashSet(PathEqualityComparator.Instance); var querySearch = query.Search; @@ -38,7 +53,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search var quickaccessLinks = QuickAccess.AccessLinkListMatched(query, settings.QuickAccessLinks); if (quickaccessLinks.Count > 0) - results.AddRange(quickaccessLinks); + results.UnionWith(quickaccessLinks); var isEnvironmentVariable = EnvironmentVariables.IsEnvironmentVariableSearch(querySearch); @@ -50,9 +65,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (!querySearch.IsLocationPathString() && !isEnvironmentVariablePath) { - results.AddRange(await WindowsIndexFilesAndFoldersSearchAsync(query, querySearch, token).ConfigureAwait(false)); + results.UnionWith(await WindowsIndexFilesAndFoldersSearchAsync(query, querySearch, token).ConfigureAwait(false)); - return results; + return results.ToList(); } var locationPath = querySearch; @@ -62,7 +77,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search // Check that actual location exists, otherwise directory search will throw directory not found exception if (!FilesFolders.LocationExists(FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath))) - return results; + return results.ToList(); var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath); @@ -79,9 +94,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search token.ThrowIfCancellationRequested(); - results.AddRange(directoryResult); + results.UnionWith(directoryResult); - return results; + return results.ToList(); } private async Task> WindowsIndexFileContentSearchAsync(Query query, string querySearchString, CancellationToken token) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json index 9aa54fb83..63ca66a1e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json @@ -7,7 +7,7 @@ "Name": "Explorer", "Description": "Search and manage files and folders. Explorer utilises Windows Index Search", "Author": "Jeremy Wu", - "Version": "1.7.0", + "Version": "1.7.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll", diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj index 54f0bbd9b..fd1f460b7 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj @@ -15,7 +15,7 @@ true - full + portable false ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.PluginIndicator\ DEBUG;TRACE diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index c2e392b51..e671e2e91 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -233,7 +233,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Context.API.GetTranslation("plugin_pluginsmanager_update_title"), MessageBoxButton.YesNo) == MessageBoxResult.Yes) { - Uninstall(x.PluginExistingMetadata); + Uninstall(x.PluginExistingMetadata, false); var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory, $"{x.Name}-{x.NewVersion}.zip"); @@ -414,10 +414,13 @@ namespace Flow.Launcher.Plugin.PluginsManager return Search(results, uninstallSearch); } - private void Uninstall(PluginMetadata plugin) + private void Uninstall(PluginMetadata plugin, bool removedSetting = true) { - PluginManager.Settings.Plugins.Remove(plugin.ID); - PluginManager.AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); + if (removedSetting) + { + PluginManager.Settings.Plugins.Remove(plugin.ID); + PluginManager.AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); + } // Marked for deletion. Will be deleted on next start up using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")); diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index f95c0d60d..7d32a40c5 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.7.0", + "Version": "1.7.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj index f7a33a94b..799230b10 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj @@ -16,7 +16,7 @@ true - full + portable false ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.ProcessKiller\ DEBUG;TRACE diff --git a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj index 1bd39ba73..2eb04b5a9 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj +++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj @@ -16,7 +16,7 @@ true - full + portable false ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Program\ DEBUG;TRACE diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 372d36524..773d0c6b6 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -14,11 +14,12 @@ using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Infrastructure.Logger; using System.Diagnostics; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; +using System.Diagnostics.CodeAnalysis; namespace Flow.Launcher.Plugin.Program.Programs { [Serializable] - public class Win32 : IProgram + public class Win32 : IProgram, IEquatable { public string Name { get; set; } public string UniqueIdentifier { get; set; } @@ -35,6 +36,20 @@ namespace Flow.Launcher.Plugin.Program.Programs private const string ShortcutExtension = "lnk"; private const string ExeExtension = "exe"; + private static readonly Win32 Default = new Win32() + { + Name = string.Empty, + Description = string.Empty, + IcoPath = string.Empty, + FullPath = string.Empty, + LnkResolvedPath = null, + ParentDirectory = string.Empty, + ExecutableName = null, + UniqueIdentifier = string.Empty, + Valid = false, + Enabled = false + }; + public Result Result(string query, IPublicAPI api) { @@ -423,12 +438,12 @@ namespace Flow.Launcher.Plugin.Program.Programs private static Win32 GetProgramFromPath(string path) { if (string.IsNullOrEmpty(path)) - return null; + return Default; path = Environment.ExpandEnvironmentVariables(path); if (!File.Exists(path)) - return null; + return Default; var entry = Win32Program(path); @@ -470,14 +485,15 @@ namespace Flow.Launcher.Plugin.Program.Programs } } - private static Win32[] ProgramsHasher(IEnumerable programs) + private static IEnumerable ProgramsHasher(IEnumerable programs) { return programs.GroupBy(p => p.FullPath.ToLower()) .SelectMany(g => { - if (g.Count() > 1) - return DistinctBy(g.Where(p => !string.IsNullOrEmpty(p.Description)), x => x.Description); - return g; + var temp = g.Where(g => !string.IsNullOrEmpty(g.Description)).ToList(); + if (temp.Any()) + return DistinctBy(temp, x => x.Description); + return g.Take(1); }).ToArray(); } @@ -489,22 +505,26 @@ namespace Flow.Launcher.Plugin.Program.Programs var programs = Enumerable.Empty(); var unregistered = UnregisteredPrograms(settings.ProgramSources, settings.ProgramSuffixes); + programs = programs.Concat(unregistered); + var autoIndexPrograms = Enumerable.Empty(); + if (settings.EnableRegistrySource) { var appPaths = AppPathsPrograms(settings.ProgramSuffixes); - programs = programs.Concat(appPaths); + autoIndexPrograms = autoIndexPrograms.Concat(appPaths); } if (settings.EnableStartMenuSource) { var startMenu = StartMenuPrograms(settings.ProgramSuffixes); - programs = programs.Concat(startMenu); + autoIndexPrograms = autoIndexPrograms.Concat(startMenu); } + autoIndexPrograms = ProgramsHasher(autoIndexPrograms); - return ProgramsHasher(programs.Where(p => p != null)); + return programs.Concat(autoIndexPrograms).Distinct().ToArray(); } #if DEBUG //This is to make developer aware of any unhandled exception and add in handling. catch (Exception e) @@ -522,5 +542,18 @@ namespace Flow.Launcher.Plugin.Program.Programs } #endif } + + public override int GetHashCode() + { + return UniqueIdentifier.GetHashCode(); + } + + public bool Equals([AllowNull] Win32 other) + { + if (other == null) + return false; + + return UniqueIdentifier == other.UniqueIdentifier; + } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Program/plugin.json b/Plugins/Flow.Launcher.Plugin.Program/plugin.json index d110124ff..082f0d853 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Program/plugin.json @@ -4,7 +4,7 @@ "Name": "Program", "Description": "Search programs in Flow.Launcher", "Author": "qianlifeng", - "Version": "1.4.2", + "Version": "1.4.3", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Program.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj index 1caf2499d..03b2d5a40 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj +++ b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj @@ -15,7 +15,7 @@ true - full + portable false ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Shell\ DEBUG;TRACE diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj index f4e3d1f36..01827370a 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj +++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj @@ -15,7 +15,7 @@ true - full + portable false ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Sys\ DEBUG;TRACE diff --git a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj index 4fb0b1205..ea0b4b7d2 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj +++ b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj @@ -15,7 +15,7 @@ true - full + portable false ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Url\ DEBUG;TRACE diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj b/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj index 9fc1552ce..b1660045c 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj @@ -16,7 +16,7 @@ true - full + portable false ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.WebSearch\ DEBUG;TRACE diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs index f76e28112..4135bb59a 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs @@ -123,12 +123,12 @@ namespace Flow.Launcher.Plugin.WebSearch var source = _settings.SelectedSuggestion; if (source != null) { - var suggestions = await source.Suggestions(keyword, token); + var suggestions = await source.Suggestions(keyword, token).ConfigureAwait(false); if (token.IsCancellationRequested) return null; - var resultsFromSuggestion = suggestions.Select(o => new Result + var resultsFromSuggestion = suggestions?.Select(o => new Result { Title = o, SubTitle = subtitle, diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs index b7e2017f9..385a9f8b5 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs @@ -32,7 +32,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources catch (HttpRequestException e) { Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e); - return new List(); + return null; } if (string.IsNullOrEmpty(result)) return new List(); diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs index ffde2fda2..e505d78cf 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs @@ -24,16 +24,13 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources const string api = "https://api.bing.com/qsonhs.aspx?q="; using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query), token).ConfigureAwait(false); - - if (resultStream.Length == 0) - return new List(); // this handles the cancellation - + json = (await JsonDocument.ParseAsync(resultStream, cancellationToken: token)).RootElement.GetProperty("AS"); } catch (TaskCanceledException) { - return null; + return new List(); } catch (HttpRequestException e) { diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index c33ebd7e1..c075f4d5d 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -24,15 +24,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false); - if (resultStream.Length == 0) - return new List(); - json = await JsonDocument.ParseAsync(resultStream); } catch (TaskCanceledException) { - return null; + return new List(); } catch (HttpRequestException e) { diff --git a/README.md b/README.md index 7c24261e9..4d712ccf6 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,12 @@

-![Maintenance](https://img.shields.io/maintenance/yes/2021) +![Maintenance](https://img.shields.io/maintenance/yes/3000) [![Build status](https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true&retina=true)](https://ci.appveyor.com/project/JohnTheGr8/flow-launcher/branch/dev) [![Github All Releases](https://img.shields.io/github/downloads/Flow-Launcher/Flow.Launcher/total.svg)](https://github.com/Flow-Launcher/Flow.Launcher/releases) -[![GitHub release (latest by date)](https://img.shields.io/github/v/release/Flow-Launcher/Flow.Launcher)](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest) ![GitHub Release Date](https://img.shields.io/github/release-date/Flow-Launcher/Flow.Launcher) +[![GitHub release (latest by date)](https://img.shields.io/github/v/release/Flow-Launcher/Flow.Launcher)](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest) +[![Documentation](https://img.shields.io/badge/Documentation-7389D8)](https://flow-launcher.github.io/docs) [![Discord](https://img.shields.io/discord/727828229250875472?color=7389D8&labelColor=6A7EC2&label=Community&logo=discord&logoColor=white)](https://discord.gg/AvgAQgh) Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at being more than an app launcher, it searches, integrates and expands on functionalities. Flow will continue to evolve, designed to be open and built with the community at heart. @@ -16,6 +17,7 @@ Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at be Remember to star it, flow will love you more :) ## Features + ![The Flow](https://user-images.githubusercontent.com/26427004/82151677-fa9c7100-989f-11ea-9143-81de60aaf07d.gif) - Search everything from applications, files, bookmarks, YouTube, Twitter and more. All from the comfort of your keyboard without ever touching the mouse. @@ -37,27 +39,27 @@ Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at be Windows may complain about security due to code not being signed, this will be completed at a later stage. If you downloaded from this repo, you are good to continue the set up. -**Integrations** - - Python plugins: - - Once a Python plugin has been detected at start up, you will be prompted to either select the location or allow Python 3.9.1 to automatic download and install. +### Integrations + - If you use Python plugins: + - Install [Python3](https://www.python.org/downloads/), download `.exe` installer. - Add Python to `%PATH%` or set it in flow's settings. - Use `pip` to install `flowlauncher`, open cmd and type `pip install flowlauncher`. - The Python plugin may require additional modules to be installed, please ensure you check by visiting the plugin's website via `pm install` + plugin name, go to context menu and select `Open website`. - Start to launch your Python plugins. - Flow searches files and contents via Windows Index Search, to use Everything, download the plugin [here](https://github.com/Flow-Launcher/Flow.Launcher.Plugin.Everything/releases/latest). -**Usage** +### Usage - Open flow's search window: Alt+Space is the default hotkey. -- Search programs, bookmarks and control panel items with acronyms eg. `gk` or `gp` for GitKraken Preview; fuzzy search eg. `code` or `vis` for Visual Studio Code. - Open context menu: on the selected result, press Ctrl+O/Shift+Enter. - Cancel/Return to previous screen: Esc. -- Install/Uninstall/Update plugins: in the search window, type `pm install`/`pm uninstall`/`pm update` + the plugin name. -- Press `F5` while in the query window to reload all plugin data. +- Install/Uninstall/Update plugins: in the search window, type `pm` `install`/`uninstall`/`update` + the plugin name. - Saved user settings are located: - If using roaming: `%APPDATA%\FlowLauncher` - If using portable, by default: `%localappdata%\FlowLauncher\app-\UserData` - Logs are saved along with your user settings folder. +[More tips](https://flow-launcher.github.io/docs/#/usage-tips) + ## Status Flow is under heavy development, but the code base is stable, so contributions are very welcome. If you would like to help maintain it, please do not hesistate to get in touch. @@ -70,7 +72,7 @@ You will find the main goals of flow placed under Projects board, so feel free t Get in touch if you like to join the Flow-Launcher Team and help build this great tool. -**Question/Suggestion** +## Question/Suggestion Yes please, submit an issue to let us know. @@ -86,4 +88,4 @@ Install .Net Core 5 SDK via Visual Studio installer or manually from [here](http ## Documentation -[Wiki](https://github.com/Flow-Launcher/Flow.Launcher/wiki) +Visit [here](https://flow-launcher.github.io/docs) for more information on usage, development and design documentations diff --git a/SolutionAssemblyInfo.cs b/SolutionAssemblyInfo.cs index 7f13f4130..bcddbd1bd 100644 --- a/SolutionAssemblyInfo.cs +++ b/SolutionAssemblyInfo.cs @@ -16,6 +16,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("1.7.1")] -[assembly: AssemblyFileVersion("1.7.1")] -[assembly: AssemblyInformationalVersion("1.7.1")] \ No newline at end of file +[assembly: AssemblyVersion("1.7.2")] +[assembly: AssemblyFileVersion("1.7.2")] +[assembly: AssemblyInformationalVersion("1.7.2")] \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 6340318d4..299a829ba 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: '1.7.1.{build}' +version: '1.7.2.{build}' init: - ps: |