mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #331 from taooceros/JSONRPCModelOpt
JSONRPC Async Model
This commit is contained in:
commit
6c42458ef1
4 changed files with 176 additions and 118 deletions
|
|
@ -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<Stream> 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}\"";
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
[JsonPropertyName("result")]
|
||||
public new List<JsonRPCResult> 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
/// </summary>
|
||||
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
|
|||
/// </summary>
|
||||
public abstract string SupportedLanguage { get; set; }
|
||||
|
||||
protected abstract string ExecuteQuery(Query query);
|
||||
protected abstract Task<Stream> ExecuteQueryAsync(Query query, CancellationToken token);
|
||||
protected abstract string ExecuteCallback(JsonRPCRequestModel rpcRequest);
|
||||
protected abstract string ExecuteContextMenu(Result selectedResult);
|
||||
|
||||
public List<Result> 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<Result> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
string output = ExecuteContextMenu(selectedResult);
|
||||
|
|
@ -59,50 +45,71 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task<List<Result>> DeserializedResultAsync(Stream output)
|
||||
{
|
||||
if (output == Stream.Null) return null;
|
||||
|
||||
JsonRPCQueryResponseModel queryResponseModel = await
|
||||
JsonSerializer.DeserializeAsync<JsonRPCQueryResponseModel>(output);
|
||||
|
||||
return ParseResults(queryResponseModel);
|
||||
}
|
||||
|
||||
private List<Result> DeserializedResult(string output)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(output))
|
||||
if (string.IsNullOrEmpty(output)) return null;
|
||||
|
||||
JsonRPCQueryResponseModel queryResponseModel =
|
||||
JsonSerializer.Deserialize<JsonRPCQueryResponseModel>(output);
|
||||
return ParseResults(queryResponseModel);
|
||||
}
|
||||
|
||||
private List<Result> ParseResults(JsonRPCQueryResponseModel queryResponseModel)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
if (queryResponseModel.Result == null) return null;
|
||||
|
||||
if(!string.IsNullOrEmpty(queryResponseModel.DebugMessage))
|
||||
{
|
||||
List<Result> results = new List<Result>();
|
||||
context.API.ShowMsg(queryResponseModel.DebugMessage);
|
||||
}
|
||||
|
||||
JsonRPCQueryResponseModel queryResponseModel = JsonSerializer.Deserialize<JsonRPCQueryResponseModel>(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<JsonRPCRequestModel>(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<JsonRPCRequestModel>(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
|
|||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="arguments"></param>
|
||||
/// <param name="token">Cancellation Token</param>
|
||||
/// <returns></returns>
|
||||
protected string Execute(string fileName, string arguments)
|
||||
protected Task<Stream> 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<Stream> 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<List<Result>> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Stream> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue