Flow.Launcher/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs

332 lines
11 KiB
C#
Raw Permalink Normal View History

using Flow.Launcher.Core.Resource;
using System;
2016-01-06 21:34:42 +00:00
using System.Collections.Generic;
2014-07-06 14:57:11 +00:00
using System.Diagnostics;
2021-02-14 05:56:27 +00:00
using System.IO;
using System.Linq;
2014-07-10 15:57:08 +00:00
using System.Reflection;
using System.Text.Json;
2014-07-10 15:57:08 +00:00
using System.Threading;
using System.Threading.Tasks;
2014-12-26 14:51:04 +00:00
using System.Windows.Forms;
2020-04-21 09:12:17 +00:00
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin;
2021-07-27 06:11:48 +00:00
using ICSharpCode.SharpZipLib.Zip;
2021-02-08 06:37:37 +00:00
using JetBrains.Annotations;
using Microsoft.IO;
2014-07-06 14:57:11 +00:00
2020-04-21 09:12:17 +00:00
namespace Flow.Launcher.Core.Plugin
2014-07-06 14:57:11 +00:00
{
2014-12-26 11:36:43 +00:00
/// <summary>
/// Represent the plugin that using JsonPRC
/// every JsonRPC plugin should has its own plugin instance
2014-12-26 11:36:43 +00:00
/// </summary>
2021-02-08 06:37:37 +00:00
internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu
2014-07-06 14:57:11 +00:00
{
protected PluginInitContext context;
public const string JsonRPC = "JsonRPC";
2014-07-06 14:57:11 +00:00
2014-12-26 11:36:43 +00:00
/// <summary>
/// The language this JsonRPCPlugin support
/// </summary>
public abstract string SupportedLanguage { get; set; }
2014-07-06 14:57:11 +00:00
2021-02-14 05:56:27 +00:00
protected abstract Task<Stream> ExecuteQueryAsync(Query query, CancellationToken token);
2014-12-26 11:36:43 +00:00
protected abstract string ExecuteCallback(JsonRPCRequestModel rpcRequest);
2017-04-11 13:34:04 +00:00
protected abstract string ExecuteContextMenu(Result selectedResult);
2014-07-06 14:57:11 +00:00
private static readonly RecyclableMemoryStreamManager BufferManager = new();
2017-04-11 13:34:04 +00:00
public List<Result> LoadContextMenus(Result selectedResult)
{
var output = ExecuteContextMenu(selectedResult);
2017-04-11 13:34:04 +00:00
try
{
return DeserializedResult(output);
}
catch (Exception e)
{
Log.Exception($"|JsonRPCPlugin.LoadContextMenus|Exception on result <{selectedResult}>", e);
return null;
}
}
2021-07-05 03:03:07 +00:00
private static readonly JsonSerializerOptions options = new()
{
2021-07-05 03:03:07 +00:00
PropertyNameCaseInsensitive = true,
IgnoreNullValues = true,
Converters =
{
new JsonObjectConverter()
}
};
2014-07-07 15:05:06 +00:00
2021-02-14 05:59:59 +00:00
private async Task<List<Result>> DeserializedResultAsync(Stream output)
{
if (output == Stream.Null) return null;
try
{
var queryResponseModel =
await JsonSerializer.DeserializeAsync<JsonRPCQueryResponseModel>(output, options);
2021-02-14 05:59:59 +00:00
return ParseResults(queryResponseModel);
}
catch (JsonException e)
{
Log.Exception(GetType().FullName, "Unexpected Json Input", e);
}
finally
{
await output.DisposeAsync();
}
return null;
2021-02-14 05:59:59 +00:00
}
private List<Result> DeserializedResult(string output)
{
if (string.IsNullOrEmpty(output)) return null;
var queryResponseModel =
2021-07-05 03:03:07 +00:00
JsonSerializer.Deserialize<JsonRPCQueryResponseModel>(output, options);
2021-02-14 05:59:59 +00:00
return ParseResults(queryResponseModel);
}
2021-02-14 05:56:27 +00:00
private List<Result> ParseResults(JsonRPCQueryResponseModel queryResponseModel)
{
if (queryResponseModel.Result == null) return null;
if (!string.IsNullOrEmpty(queryResponseModel.DebugMessage))
2021-02-14 05:59:59 +00:00
{
context.API.ShowMsg(queryResponseModel.DebugMessage);
}
foreach (var result in queryResponseModel.Result)
2021-02-14 05:56:27 +00:00
{
result.Action = c =>
2017-04-11 13:32:51 +00:00
{
2021-02-14 05:56:27 +00:00
if (result.JsonRPCAction == null) return false;
if (string.IsNullOrEmpty(result.JsonRPCAction.Method))
2021-02-14 05:56:27 +00:00
{
return !result.JsonRPCAction.DontHideAfterAction;
}
if (result.JsonRPCAction.Method.StartsWith("Flow.Launcher."))
{
ExecuteFlowLauncherAPI(result.JsonRPCAction.Method["Flow.Launcher.".Length..],
result.JsonRPCAction.Parameters);
}
else
{
var actionResponse = ExecuteCallback(result.JsonRPCAction);
if (string.IsNullOrEmpty(actionResponse))
2017-04-11 13:32:51 +00:00
{
return !result.JsonRPCAction.DontHideAfterAction;
2021-02-14 05:56:27 +00:00
}
var jsonRpcRequestModel =
JsonSerializer.Deserialize<JsonRPCRequestModel>(actionResponse, options);
if (jsonRpcRequestModel?.Method?.StartsWith("Flow.Launcher.") ?? false)
2021-02-14 05:56:27 +00:00
{
ExecuteFlowLauncherAPI(jsonRpcRequestModel.Method["Flow.Launcher.".Length..],
jsonRpcRequestModel.Parameters);
2017-04-11 13:32:51 +00:00
}
2021-02-14 05:56:27 +00:00
}
2021-02-08 06:37:37 +00:00
2021-02-14 05:56:27 +00:00
return !result.JsonRPCAction.DontHideAfterAction;
};
2014-07-06 14:57:11 +00:00
}
2021-02-08 06:37:37 +00:00
var results = new List<Result>();
results.AddRange(queryResponseModel.Result);
2021-02-14 05:56:27 +00:00
return results;
}
2020-04-21 12:16:10 +00:00
private void ExecuteFlowLauncherAPI(string method, object[] parameters)
2014-07-10 15:57:08 +00:00
{
var parametersTypeArray = parameters.Select(param => param.GetType()).ToArray();
MethodInfo methodInfo = PluginManager.API.GetType().GetMethod(method, parametersTypeArray);
2015-02-03 10:32:16 +00:00
if (methodInfo != null)
2014-07-10 15:57:08 +00:00
{
try
{
2014-12-26 14:51:04 +00:00
methodInfo.Invoke(PluginManager.API, parameters);
2014-07-10 15:57:08 +00:00
}
2016-01-06 21:34:42 +00:00
catch (Exception)
2014-07-10 15:57:08 +00:00
{
#if (DEBUG)
2021-02-08 06:37:37 +00:00
throw;
2014-07-10 15:57:08 +00:00
#endif
}
}
}
2014-07-07 15:05:06 +00:00
/// <summary>
/// Execute external program and return the output
/// </summary>
/// <param name="fileName"></param>
/// <param name="arguments"></param>
2021-02-08 06:37:37 +00:00
/// <param name="token">Cancellation Token</param>
2014-07-07 15:05:06 +00:00
/// <returns></returns>
2021-02-14 05:56:27 +00:00
protected Task<Stream> ExecuteAsync(string fileName, string arguments, CancellationToken token = default)
2014-07-18 12:00:55 +00:00
{
2021-02-14 05:56:27 +00:00
ProcessStartInfo start = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
2021-02-14 04:02:59 +00:00
return ExecuteAsync(start, token);
2014-07-18 12:00:55 +00:00
}
protected string Execute(ProcessStartInfo startInfo)
{
try
{
using var process = Process.Start(startInfo);
if (process == null) return string.Empty;
2021-02-14 05:56:27 +00:00
using var standardOutput = process.StandardOutput;
var result = standardOutput.ReadToEnd();
if (string.IsNullOrEmpty(result))
{
using var standardError = process.StandardError;
var error = standardError.ReadToEnd();
if (!string.IsNullOrEmpty(error))
{
Log.Error($"|JsonRPCPlugin.Execute|{error}");
return string.Empty;
}
Log.Error("|JsonRPCPlugin.Execute|Empty standard output and standard error.");
return string.Empty;
}
if (result.StartsWith("DEBUG:"))
{
MessageBox.Show(new Form
{
TopMost = true
}, result.Substring(6));
return string.Empty;
}
return result;
}
catch (Exception e)
{
Log.Exception(
$"|JsonRPCPlugin.Execute|Exception for filename <{startInfo.FileName}> with argument <{startInfo.Arguments}>",
e);
return string.Empty;
}
}
2021-02-14 05:56:27 +00:00
protected async Task<Stream> ExecuteAsync(ProcessStartInfo startInfo, CancellationToken token = default)
2014-07-06 14:57:11 +00:00
{
2021-07-27 06:11:48 +00:00
Process process = null;
bool disposed = false;
2014-07-06 14:57:11 +00:00
try
{
2021-07-27 06:11:48 +00:00
process = Process.Start(startInfo);
if (process == null)
2014-07-06 14:57:11 +00:00
{
Log.Error("|JsonRPCPlugin.ExecuteAsync|Can't start new process");
2021-02-14 05:56:27 +00:00
return Stream.Null;
}
await using var source = process.StandardOutput.BaseStream;
var buffer = BufferManager.GetStream();
2021-07-27 06:11:48 +00:00
token.Register(() =>
{
2021-07-27 06:11:48 +00:00
// ReSharper disable once AccessToModifiedClosure
// Manually Check whether disposed
if (!disposed && !process.HasExited)
process.Kill();
});
try
{
2021-07-27 06:11:48 +00:00
// token expire won't instantly trigger the exception,
2021-07-27 09:01:27 +00:00
// 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();
2021-07-27 06:11:48 +00:00
2021-02-14 05:56:27 +00:00
if (!process.StandardError.EndOfStream)
{
2021-02-08 06:37:37 +00:00
using var standardError = process.StandardError;
var error = await standardError.ReadToEndAsync();
2021-02-08 06:37:37 +00:00
if (!string.IsNullOrEmpty(error))
2014-07-06 14:57:11 +00:00
{
Log.Error($"|JsonRPCPlugin.ExecuteAsync|{error}");
2021-02-14 05:56:27 +00:00
return Stream.Null;
2017-01-30 00:26:11 +00:00
}
2021-02-08 06:37:37 +00:00
Log.Error("|JsonRPCPlugin.ExecuteAsync|Empty standard output and standard error.");
2021-02-14 05:56:27 +00:00
return Stream.Null;
}
return buffer;
2014-07-06 14:57:11 +00:00
}
catch (Exception e)
2014-07-06 14:57:11 +00:00
{
2021-02-08 06:37:37 +00:00
Log.Exception(
$"|JsonRPCPlugin.ExecuteAsync|Exception for filename <{startInfo.FileName}> with argument <{startInfo.Arguments}>",
2021-02-08 06:37:37 +00:00
e);
2021-02-14 05:56:27 +00:00
return Stream.Null;
2014-07-06 14:57:11 +00:00
}
2021-07-27 06:11:48 +00:00
finally
{
process?.Dispose();
disposed = true;
}
2014-07-06 14:57:11 +00:00
}
2021-02-08 06:37:37 +00:00
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
{
try
{
var output = await ExecuteQueryAsync(query, token);
2021-02-14 05:56:27 +00:00
return await DeserializedResultAsync(output);
2021-02-08 06:37:37 +00:00
}
catch (OperationCanceledException)
{
return null;
}
2021-02-08 06:37:37 +00:00
catch (Exception e)
{
Log.Exception($"|JsonRPCPlugin.Query|Exception when query <{query}>", e);
return null;
}
}
public virtual Task InitAsync(PluginInitContext context)
2014-07-06 14:57:11 +00:00
{
2021-02-08 06:37:37 +00:00
this.context = context;
return Task.CompletedTask;
2014-07-06 14:57:11 +00:00
}
}
}