diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index d7df11f8303..9e932a5085b 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -55,6 +55,7 @@ + diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 431458881ec..22f547a5a75 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -11,7 +11,9 @@ using System.Windows.Forms; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin; +using ICSharpCode.SharpZipLib.Zip; using JetBrains.Annotations; +using Microsoft.IO; namespace Flow.Launcher.Core.Plugin { @@ -33,9 +35,11 @@ internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu protected abstract string ExecuteCallback(JsonRPCRequestModel rpcRequest); protected abstract string ExecuteContextMenu(Result selectedResult); + private static readonly RecyclableMemoryStreamManager BufferManager = new(); + public List LoadContextMenus(Result selectedResult) { - string output = ExecuteContextMenu(selectedResult); + var output = ExecuteContextMenu(selectedResult); try { return DeserializedResult(output); @@ -61,12 +65,23 @@ private async Task> DeserializedResultAsync(Stream output) { if (output == Stream.Null) return null; - var queryResponseModel = await - JsonSerializer.DeserializeAsync(output, options); + try + { + var queryResponseModel = + await JsonSerializer.DeserializeAsync(output, options); - await output.DisposeAsync(); - - return ParseResults(queryResponseModel); + return ParseResults(queryResponseModel); + } + catch (JsonException e) + { + Log.Exception(GetType().FullName, "Unexpected Json Input", e); + } + finally + { + await output.DisposeAsync(); + } + + return null; } private List DeserializedResult(string output) @@ -81,7 +96,6 @@ private List DeserializedResult(string output) private List ParseResults(JsonRPCQueryResponseModel queryResponseModel) { - var results = new List(); if (queryResponseModel.Result == null) return null; if (!string.IsNullOrEmpty(queryResponseModel.DebugMessage)) @@ -89,7 +103,7 @@ private List ParseResults(JsonRPCQueryResponseModel queryResponseModel) context.API.ShowMsg(queryResponseModel.DebugMessage); } - foreach (JsonRPCResult result in queryResponseModel.Result) + foreach (var result in queryResponseModel.Result) { result.Action = c => { @@ -114,7 +128,8 @@ private List ParseResults(JsonRPCQueryResponseModel queryResponseModel) return !result.JsonRPCAction.DontHideAfterAction; } - var jsonRpcRequestModel = JsonSerializer.Deserialize(actionResponse, options); + var jsonRpcRequestModel = + JsonSerializer.Deserialize(actionResponse, options); if (jsonRpcRequestModel?.Method?.StartsWith("Flow.Launcher.") ?? false) { @@ -125,9 +140,12 @@ private List ParseResults(JsonRPCQueryResponseModel queryResponseModel) return !result.JsonRPCAction.DontHideAfterAction; }; - results.Add(result); } + var results = new List(); + + results.AddRange(queryResponseModel.Result); + return results; } @@ -217,16 +235,42 @@ protected string Execute(ProcessStartInfo startInfo) protected async Task ExecuteAsync(ProcessStartInfo startInfo, CancellationToken token = default) { + Process process = null; + bool disposed = false; try { - using var process = Process.Start(startInfo); + process = Process.Start(startInfo); if (process == null) { Log.Error("|JsonRPCPlugin.ExecuteAsync|Can't start new process"); return Stream.Null; } - var result = process.StandardOutput.BaseStream; + await using var source = process.StandardOutput.BaseStream; + + var buffer = BufferManager.GetStream(); + + token.Register(() => + { + // ReSharper disable once AccessToModifiedClosure + // Manually Check whether disposed + if (!disposed && !process.HasExited) + process.Kill(); + }); + + try + { + // token expire won't instantly trigger the exception, + // manually kill process at before + await source.CopyToAsync(buffer, token); + } + catch (OperationCanceledException) + { + await buffer.DisposeAsync(); + return Stream.Null; + } + + buffer.Seek(0, SeekOrigin.Begin); token.ThrowIfCancellationRequested(); @@ -245,7 +289,7 @@ protected async Task ExecuteAsync(ProcessStartInfo startInfo, Cancellati return Stream.Null; } - return result; + return buffer; } catch (Exception e) { @@ -254,15 +298,24 @@ protected async Task ExecuteAsync(ProcessStartInfo startInfo, Cancellati e); return Stream.Null; } + finally + { + process?.Dispose(); + disposed = true; + } } public async Task> QueryAsync(Query query, CancellationToken token) { - var output = await ExecuteQueryAsync(query, token); try { + var output = await ExecuteQueryAsync(query, token); return await DeserializedResultAsync(output); } + catch (OperationCanceledException) + { + return null; + } catch (Exception e) { Log.Exception($"|JsonRPCPlugin.Query|Exception when query <{query}>", e);