diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs index 180893774..4d75e1b8f 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Flow.Launcher.Core.Plugin; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; @@ -8,6 +9,15 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments { internal override string Language => AllowedLanguage.PythonV2; + internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata) + { + return new PluginPair + { + Plugin = new PythonPluginV2(filePath), + Metadata = metadata + }; + } + internal PythonV2Environment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { } } } diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index 4077320bc..0188f7b46 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -57,11 +57,16 @@ + + + + + \ No newline at end of file diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs index 477ee620d..f4ea43894 100644 --- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs +++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs @@ -28,7 +28,7 @@ namespace Flow.Launcher.Core.Plugin public record JsonRPCResponseModel(int Id, JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error); public record JsonRPCQueryResponseModel(int Id, [property: JsonPropertyName("result")] List Result, - IReadOnlyDictionary SettingsChange = null, + IReadOnlyDictionary SettingsChanges = null, string DebugMessage = "", JsonRPCErrorModel Error = default) : JsonRPCResponseModel(Id, Error); diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 3a7fcb216..cfe134549 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -251,10 +251,18 @@ namespace Flow.Launcher.Core.Plugin return sourceBuffer; } - - protected override async Task> QueryRequestAsync(JsonRPCRequestModel request, CancellationToken token) + public override async Task> QueryAsync(Query query, CancellationToken token) { + var request = new JsonRPCRequestModel(RequestId++, + "query", + new object[] + { + query.Search + }, + Settings.Inner); + var output = await RequestAsync(request, token); + return await DeserializedResultAsync(output); } } diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs index e3386a52f..85b474157 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs @@ -69,7 +69,6 @@ namespace Flow.Launcher.Core.Plugin }; protected abstract Task ExecuteResultAsync(JsonRPCResult result); - protected abstract Task> QueryRequestAsync(JsonRPCRequestModel request, CancellationToken token); protected PortableSettings Settings { get; set; } @@ -96,7 +95,7 @@ namespace Flow.Launcher.Core.Plugin results.AddRange(queryResponseModel.Result); - Settings.UpdateSettings(queryResponseModel.SettingsChange); + Settings.UpdateSettings(queryResponseModel.SettingsChanges); return results; } @@ -123,19 +122,7 @@ namespace Flow.Launcher.Core.Plugin } } - public async Task> QueryAsync(Query query, CancellationToken token) - { - var request = new JsonRPCRequestModel(RequestId++, - "query", - new object[] - { - query.Search - }, - Settings.Inner); - - return await QueryRequestAsync(request, token); - - } + public abstract Task> QueryAsync(Query query, CancellationToken token); private async Task InitSettingAsync() diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs index bc5d00ed4..e1e79359d 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs @@ -7,77 +7,70 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using System.Windows.Controls; +using Flow.Launcher.Core.Plugin.JsonRPCV2Models; using Flow.Launcher.Plugin; +using StreamJsonRpc; + namespace Flow.Launcher.Core.Plugin { internal abstract class JsonRpcPluginV2 : JsonRPCPluginBase { public abstract string SupportedLanguage { get; set; } - + public const string JsonRpc = "JsonRPC"; - protected abstract Stream InputStream { get; set; } - protected abstract Stream OutputStream { get; set; } - protected abstract StreamReader ErrorStream { get; set; } - protected Channel InputMessageChannel { get; set; } + protected abstract JsonRpc Rpc { get; set; } - private (Task SendTask, Task ReceiveTask) MessageTask { get; set; } - private CancellationTokenSource MessageCancellationTokenSource { get; set; } + protected StreamReader ErrorStream { get; set; } - protected int RequestId; - private ConcurrentDictionary> RequestTaskDictionary { get; } = new(); - - // TODO: Switch to Async Task - private async void ReceiveMessageAsync(CancellationToken token) + protected override async Task ExecuteResultAsync(JsonRPCResult result) { - var response = - JsonSerializer.DeserializeAsyncEnumerable(OutputStream, cancellationToken: token); - - ArgumentNullException.ThrowIfNull(response); - - await foreach (var message in response.WithCancellation(token)) + try { - if (!RequestTaskDictionary.TryGetValue(message.Id, out var task)) - { - // Either Task is already handled or it is a invalid resopnse. - continue; - } - RequestTaskDictionary.Remove(message.Id, out _); - task.TrySetResult(message); + var res = await Rpc.InvokeAsync(result.JsonRPCAction.Method, argument: result.JsonRPCAction.Parameters); + + return res.Hide; + } + catch + { + return false; } } - // TODO: Switch to Async Task - private async void SendMessageAsync(PluginMetadata metadata, CancellationToken token) + public override async Task> QueryAsync(Query query, CancellationToken token) { - var fullMessage = new JsonRPCRequestMessage(metadata, InputMessageChannel.Reader.ReadAllAsync(token)); - await JsonSerializer.SerializeAsync(InputStream, fullMessage, cancellationToken: token); + try + { + var res = await Rpc.InvokeAsync("query", query); + + var results = ParseResults(res); + + return results; + } + catch + { + return new List(); + } } - protected override async Task> QueryRequestAsync(JsonRPCRequestModel query, CancellationToken token) - { - int currentRequestId = Interlocked.Add(ref RequestId, 1); - await InputMessageChannel.Writer.WriteAsync(query, token); - await Task.Delay(50, token); - await InputStream.FlushAsync(token); - var task = new TaskCompletionSource(); - RequestTaskDictionary[currentRequestId] = task; - var result = await task.Task; - //TODO: Parse Result - return new List(); - } + public override async Task InitAsync(PluginInitContext context) { await base.InitAsync(context); - InputMessageChannel = Channel.CreateUnbounded(); - MessageCancellationTokenSource = new CancellationTokenSource(); - SendMessageAsync(context.CurrentPluginMetadata, MessageCancellationTokenSource.Token); - ReceiveMessageAsync(MessageCancellationTokenSource.Token); - // MessageTask = - // (SendMessageAsync(context.CurrentPluginMetadata, MessageCancellationTokenSource.Token), - // ReceiveMessageAsync(MessageCancellationTokenSource.Token)); + + _ = ReadErrorAsync(); + + async Task ReadErrorAsync() + { + var error = await ErrorStream.ReadToEndAsync(); + + if (!string.IsNullOrEmpty(error)) + { + throw new Exception(error); + } + } } } } diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs new file mode 100644 index 000000000..6a130f70f --- /dev/null +++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs @@ -0,0 +1,4 @@ +namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models +{ + public record JsonRPCExecuteResponse(bool Hide = true); +} diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCQueryRequest.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCQueryRequest.cs new file mode 100644 index 000000000..003724a23 --- /dev/null +++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCQueryRequest.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models +{ + public record JsonRPCQueryRequest( + List Results + ); +} diff --git a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs index 1e7a74a58..c3b47a79c 100644 --- a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs +++ b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs @@ -2,10 +2,14 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Windows.Input; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin; +using Microsoft.VisualStudio.Threading; +using StreamJsonRpc; namespace Flow.Launcher.Core.Plugin { @@ -13,11 +17,11 @@ namespace Flow.Launcher.Core.Plugin { private readonly ProcessStartInfo _startInfo; private Process _process; + public override string SupportedLanguage { get; set; } = AllowedLanguage.Python; - protected override Stream InputStream { get; set; } - protected override Stream OutputStream { get; set; } - protected override StreamReader ErrorStream { get; set; } + protected override JsonRpc Rpc { get; set; } + public PythonPluginV2(string filename) { @@ -49,23 +53,26 @@ namespace Flow.Launcher.Core.Plugin { throw new NotImplementedException(); } - protected override Task ExecuteResultAsync(JsonRPCResult result) - { - throw new NotImplementedException(); - } + public override async Task InitAsync(PluginInitContext context) { _startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath); _startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory; _process = Process.Start(_startInfo); - + ArgumentNullException.ThrowIfNull(_process); - - InputStream = _process.StandardInput.BaseStream; - OutputStream = _process.StandardOutput.BaseStream; - ErrorStream = _process.StandardError; - + + var formatter = new JsonMessageFormatter(); + var handler = new NewLineDelimitedMessageHandler(_process.StandardInput.BaseStream, + _process.StandardOutput.BaseStream, + formatter); + + Rpc = new JsonRpc(handler, context.API); + Rpc.StartListening(); + + _ = _process.StandardError.ReadToEndAsync().ContinueWith(e => throw new Exception(e.Result)); + await base.InitAsync(context); } } diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 1143f7f72..b0e1391e3 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -90,7 +90,7 @@ - + all diff --git a/Flow.Launcher/Notification.cs b/Flow.Launcher/Notification.cs index 57c1e88f2..bc130c834 100644 --- a/Flow.Launcher/Notification.cs +++ b/Flow.Launcher/Notification.cs @@ -31,12 +31,12 @@ namespace Flow.Launcher var Icon = !File.Exists(iconPath) ? Path.Combine(Constant.ProgramDirectory, "Images\\app.png") : iconPath; - - new ToastContentBuilder() - .AddText(title, hintMaxLines: 1) - .AddText(subTitle) - .AddAppLogoOverride(new Uri(Icon)) - .Show(); + + // new ToastContentBuilder() + // .AddText(title, hintMaxLines: 1) + // .AddText(subTitle) + // .AddAppLogoOverride(new Uri(Icon)) + // .Show(); } private static void LegacyShow(string title, string subTitle, string iconPath)