mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Implement JSONRPC V2 Draft
This commit is contained in:
parent
c74eafb9d5
commit
67d1b896b1
11 changed files with 276 additions and 128 deletions
|
|
@ -6,7 +6,7 @@ using Flow.Launcher.Plugin;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.Plugin
|
namespace Flow.Launcher.Core.Plugin
|
||||||
{
|
{
|
||||||
internal class ExecutablePlugin : JsonRPCPlugin
|
internal class ExecutablePlugin : JsonRpcPlugin
|
||||||
{
|
{
|
||||||
private readonly ProcessStartInfo _startInfo;
|
private readonly ProcessStartInfo _startInfo;
|
||||||
public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
|
public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
|
||||||
|
|
|
||||||
|
|
@ -21,67 +21,36 @@ using System.Text.Json;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.Plugin
|
namespace Flow.Launcher.Core.Plugin
|
||||||
{
|
{
|
||||||
public class JsonRPCErrorModel
|
public record JsonRPCRequestMessage(PluginMetadata PluginMetadata, IAsyncEnumerable<JsonRPCRequestModel> Requests);
|
||||||
{
|
public record JsonRPCBase(int Id, JsonRPCErrorModel Error = default);
|
||||||
public int Code { get; set; }
|
public record JsonRPCErrorModel(int Code, string Message, string Data);
|
||||||
|
|
||||||
public string Message { get; set; }
|
public record JsonRPCResponseModel(int Id, JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error);
|
||||||
|
public record JsonRPCQueryResponseModel(int Id,
|
||||||
|
[property: JsonPropertyName("result")] List<JsonRPCResult> Result,
|
||||||
|
Dictionary<string, object> SettingsChange = null,
|
||||||
|
string DebugMessage = "",
|
||||||
|
JsonRPCErrorModel Error = default) : JsonRPCResponseModel(Id, Error);
|
||||||
|
|
||||||
public string Data { get; set; }
|
public record JsonRPCRequestModel(int Id,
|
||||||
}
|
string Method,
|
||||||
|
object[] Parameters,
|
||||||
|
Dictionary<string, object> Settings = default,
|
||||||
|
JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error);
|
||||||
|
|
||||||
|
|
||||||
public class JsonRPCResponseModel
|
|
||||||
{
|
|
||||||
public string Result { get; set; }
|
|
||||||
|
|
||||||
public JsonRPCErrorModel Error { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class JsonRPCQueryResponseModel : JsonRPCResponseModel
|
|
||||||
{
|
|
||||||
[JsonPropertyName("result")]
|
|
||||||
public new List<JsonRPCResult> Result { get; set; }
|
|
||||||
|
|
||||||
public Dictionary<string, object> SettingsChange { get; set; }
|
|
||||||
|
|
||||||
public string DebugMessage { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class JsonRPCRequestModel
|
|
||||||
{
|
|
||||||
public string Method { get; set; }
|
|
||||||
|
|
||||||
public object[] Parameters { get; set; }
|
|
||||||
|
|
||||||
public Dictionary<string, object> Settings { get; set; }
|
|
||||||
|
|
||||||
private static readonly JsonSerializerOptions options = new()
|
|
||||||
{
|
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
||||||
};
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return JsonSerializer.Serialize(this, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Json RPC Request that Flow Launcher sent to client
|
|
||||||
/// </summary>
|
|
||||||
public class JsonRPCServerRequestModel : JsonRPCRequestModel
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Json RPC Request(in query response) that client sent to Flow Launcher
|
/// Json RPC Request(in query response) that client sent to Flow Launcher
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JsonRPCClientRequestModel : JsonRPCRequestModel
|
public record JsonRPCClientRequestModel(
|
||||||
{
|
int Id,
|
||||||
public bool DontHideAfterAction { get; set; }
|
string Method,
|
||||||
}
|
object[] Parameters,
|
||||||
|
Dictionary<string, object> Settings,
|
||||||
|
bool DontHideAfterAction = false,
|
||||||
|
JsonRPCErrorModel Error = default) : JsonRPCRequestModel(Id, Method, Parameters, Settings, Error);
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represent the json-rpc result item that client send to Flow Launcher
|
/// Represent the json-rpc result item that client send to Flow Launcher
|
||||||
/// Typically, we will send back this request model to client after user select the result item
|
/// Typically, we will send back this request model to client after user select the result item
|
||||||
|
|
|
||||||
21
Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs
Normal file
21
Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Flow.Launcher.Core.Plugin
|
||||||
|
{
|
||||||
|
// TODO: After Upgrading to .Net 7, adding Source Generating Context for IAsyncEnumerable JsonRPCMessage
|
||||||
|
|
||||||
|
[JsonSerializable(typeof(JsonRPCQueryResponseModel))]
|
||||||
|
public partial class JsonRPCQueryResponseModelContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonSerializable(typeof(JsonRPCRequestModel))]
|
||||||
|
public partial class JsonRPCRequestModelContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonSerializable(typeof(JsonRPCClientRequestModel))]
|
||||||
|
public partial class JsonRPCClientRequestModelContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,10 +29,10 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
/// Represent the plugin that using JsonPRC
|
/// Represent the plugin that using JsonPRC
|
||||||
/// every JsonRPC plugin should has its own plugin instance
|
/// every JsonRPC plugin should has its own plugin instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
|
internal abstract class JsonRpcPlugin : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
|
||||||
{
|
{
|
||||||
protected PluginInitContext context;
|
protected PluginInitContext Context;
|
||||||
public const string JsonRPC = "JsonRPC";
|
public const string JsonRpc = "JsonRPC";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The language this JsonRPCPlugin support
|
/// The language this JsonRPCPlugin support
|
||||||
|
|
@ -43,19 +43,16 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
private static readonly RecyclableMemoryStreamManager BufferManager = new();
|
private static readonly RecyclableMemoryStreamManager BufferManager = new();
|
||||||
|
|
||||||
private string SettingConfigurationPath => Path.Combine(context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
|
private int RequestId { get; set; }
|
||||||
private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name, "Settings.json");
|
|
||||||
|
private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
|
||||||
|
private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name, "Settings.json");
|
||||||
|
|
||||||
public List<Result> LoadContextMenus(Result selectedResult)
|
public List<Result> LoadContextMenus(Result selectedResult)
|
||||||
{
|
{
|
||||||
var request = new JsonRPCRequestModel
|
var request = new JsonRPCRequestModel(RequestId++,
|
||||||
{
|
"context_menu",
|
||||||
Method = "context_menu",
|
new[] { selectedResult.ContextData });
|
||||||
Parameters = new[]
|
|
||||||
{
|
|
||||||
selectedResult.ContextData
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var output = Request(request);
|
var output = Request(request);
|
||||||
return DeserializedResult(output);
|
return DeserializedResult(output);
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +110,7 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(queryResponseModel.DebugMessage))
|
if (!string.IsNullOrEmpty(queryResponseModel.DebugMessage))
|
||||||
{
|
{
|
||||||
context.API.ShowMsg(queryResponseModel.DebugMessage);
|
Context.API.ShowMsg(queryResponseModel.DebugMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var result in queryResponseModel.Result)
|
foreach (var result in queryResponseModel.Result)
|
||||||
|
|
@ -281,7 +278,7 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
{
|
{
|
||||||
case (0, 0):
|
case (0, 0):
|
||||||
const string errorMessage = "Empty JSON-RPC Response.";
|
const string errorMessage = "Empty JSON-RPC Response.";
|
||||||
Log.Warn($"|{nameof(JsonRPCPlugin)}.{nameof(ExecuteAsync)}|{errorMessage}");
|
Log.Warn($"|{nameof(JsonRpcPlugin)}.{nameof(ExecuteAsync)}|{errorMessage}");
|
||||||
break;
|
break;
|
||||||
case (_, not 0):
|
case (_, not 0):
|
||||||
throw new InvalidDataException(Encoding.UTF8.GetString(errorBuffer.ToArray())); // The process has exited with an error message
|
throw new InvalidDataException(Encoding.UTF8.GetString(errorBuffer.ToArray())); // The process has exited with an error message
|
||||||
|
|
@ -295,20 +292,15 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
||||||
{
|
{
|
||||||
var request = new JsonRPCRequestModel
|
var request = new JsonRPCRequestModel(RequestId++,
|
||||||
{
|
"query",
|
||||||
Method = "query",
|
new object[]{ query.Search },
|
||||||
Parameters = new object[]
|
Settings);
|
||||||
{
|
|
||||||
query.Search
|
|
||||||
},
|
|
||||||
Settings = Settings
|
|
||||||
};
|
|
||||||
var output = await RequestAsync(request, token);
|
var output = await RequestAsync(request, token);
|
||||||
return await DeserializedResultAsync(output);
|
return await DeserializedResultAsync(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InitSettingAsync()
|
private async Task InitSettingAsync()
|
||||||
{
|
{
|
||||||
if (!File.Exists(SettingConfigurationPath))
|
if (!File.Exists(SettingConfigurationPath))
|
||||||
return;
|
return;
|
||||||
|
|
@ -337,7 +329,7 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
public virtual async Task InitAsync(PluginInitContext context)
|
public virtual async Task InitAsync(PluginInitContext context)
|
||||||
{
|
{
|
||||||
this.context = context;
|
this.Context = context;
|
||||||
await InitSettingAsync();
|
await InitSettingAsync();
|
||||||
}
|
}
|
||||||
private static readonly Thickness settingControlMargin = new(10, 4, 10, 4);
|
private static readonly Thickness settingControlMargin = new(10, 4, 10, 4);
|
||||||
|
|
@ -483,7 +475,7 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
{
|
{
|
||||||
if (Settings != null)
|
if (Settings != null)
|
||||||
{
|
{
|
||||||
Helper.ValidateDirectory(Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name));
|
Helper.ValidateDirectory(Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name));
|
||||||
File.WriteAllText(SettingPath, JsonSerializer.Serialize(Settings, settingSerializeOption));
|
File.WriteAllText(SettingPath, JsonSerializer.Serialize(Settings, settingSerializeOption));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
100
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
Normal file
100
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
|
|
||||||
|
namespace Flow.Launcher.Core.Plugin
|
||||||
|
{
|
||||||
|
public abstract class JsonRpcPluginV2 : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
|
||||||
|
{
|
||||||
|
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<JsonRPCRequestModel> InputMessageChannel { get; set; }
|
||||||
|
|
||||||
|
private (Task SendTask, Task ReceiveTask) MessageTask { get; set; }
|
||||||
|
private CancellationTokenSource MessageCancellationTokenSource { get; set; }
|
||||||
|
|
||||||
|
protected int RequestId;
|
||||||
|
|
||||||
|
private ConcurrentDictionary<int, TaskCompletionSource<JsonRPCQueryResponseModel>> RequestTaskDictionary { get; } = new();
|
||||||
|
|
||||||
|
// TODO: Switch to Async Task
|
||||||
|
private async void ReceiveMessageAsync(CancellationToken token)
|
||||||
|
{
|
||||||
|
var response =
|
||||||
|
JsonSerializer.DeserializeAsyncEnumerable<JsonRPCQueryResponseModel>(OutputStream, cancellationToken: token);
|
||||||
|
|
||||||
|
ArgumentNullException.ThrowIfNull(response);
|
||||||
|
|
||||||
|
await foreach (var message in response.WithCancellation(token))
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Switch to Async Task
|
||||||
|
private async void SendMessageAsync(PluginMetadata metadata, CancellationToken token)
|
||||||
|
{
|
||||||
|
var fullMessage = new JsonRPCRequestMessage(metadata, InputMessageChannel.Reader.ReadAllAsync(token));
|
||||||
|
await JsonSerializer.SerializeAsync(InputStream, fullMessage, cancellationToken: token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
||||||
|
{
|
||||||
|
int currentRequestId = Interlocked.Add(ref RequestId, 1);
|
||||||
|
var message = new JsonRPCRequestModel(currentRequestId, "query", new object[]
|
||||||
|
{
|
||||||
|
query
|
||||||
|
});
|
||||||
|
await InputMessageChannel.Writer.WriteAsync(message, token);
|
||||||
|
await Task.Delay(50);
|
||||||
|
await InputStream.FlushAsync();
|
||||||
|
var task = new TaskCompletionSource<JsonRPCQueryResponseModel>();
|
||||||
|
RequestTaskDictionary[currentRequestId] = task;
|
||||||
|
var result = await task.Task;
|
||||||
|
//TODO: Parse Result
|
||||||
|
return new List<Result>();
|
||||||
|
}
|
||||||
|
public virtual Task InitAsync(PluginInitContext context)
|
||||||
|
{
|
||||||
|
InputMessageChannel = Channel.CreateUnbounded<JsonRPCRequestModel>();
|
||||||
|
MessageCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
SendMessageAsync(context.CurrentPluginMetadata, MessageCancellationTokenSource.Token);
|
||||||
|
ReceiveMessageAsync(MessageCancellationTokenSource.Token);
|
||||||
|
// MessageTask =
|
||||||
|
// (SendMessageAsync(context.CurrentPluginMetadata, MessageCancellationTokenSource.Token),
|
||||||
|
// ReceiveMessageAsync(MessageCancellationTokenSource.Token));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
public List<Result> LoadContextMenus(Result selectedResult)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
public Control CreateSettingPanel()
|
||||||
|
{
|
||||||
|
// TODO: Implement CreateSettingPanel
|
||||||
|
return new Control();
|
||||||
|
}
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
// TODO: Save settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -83,7 +83,10 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins.Add(new PluginPair {Plugin = plugin, Metadata = metadata});
|
plugins.Add(new PluginPair
|
||||||
|
{
|
||||||
|
Plugin = plugin, Metadata = metadata
|
||||||
|
});
|
||||||
});
|
});
|
||||||
metadata.InitTime += milliseconds;
|
metadata.InitTime += milliseconds;
|
||||||
}
|
}
|
||||||
|
|
@ -110,14 +113,14 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
public static IEnumerable<PluginPair> PythonPlugins(List<PluginMetadata> source, PluginsSettings settings)
|
public static IEnumerable<PluginPair> PythonPlugins(List<PluginMetadata> source, PluginsSettings settings)
|
||||||
{
|
{
|
||||||
if (!source.Any(o => o.Language.ToUpper() == AllowedLanguage.Python))
|
if (!source.Any(o => o.Language.ToUpper() is AllowedLanguage.Python or AllowedLanguage.PythonV2))
|
||||||
return new List<PluginPair>();
|
return new List<PluginPair>();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(settings.PythonDirectory) && FilesFolders.LocationExists(settings.PythonDirectory))
|
if (!string.IsNullOrEmpty(settings.PythonDirectory) && FilesFolders.LocationExists(settings.PythonDirectory))
|
||||||
return SetPythonPathForPluginPairs(source, Path.Combine(settings.PythonDirectory, PythonExecutable));
|
return SetPythonPathForPluginPairs(source, Path.Combine(settings.PythonDirectory, PythonExecutable));
|
||||||
|
|
||||||
var pythonPath = string.Empty;
|
var pythonPath = string.Empty;
|
||||||
|
|
||||||
if (MessageBox.Show("Flow detected you have installed Python plugins, which " +
|
if (MessageBox.Show("Flow detected you have installed Python plugins, which " +
|
||||||
"will need Python to run. Would you like to download Python? " +
|
"will need Python to run. Would you like to download Python? " +
|
||||||
Environment.NewLine + Environment.NewLine +
|
Environment.NewLine + Environment.NewLine +
|
||||||
|
|
@ -185,17 +188,22 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
return SetPythonPathForPluginPairs(source, pythonPath);
|
return SetPythonPathForPluginPairs(source, pythonPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<PluginPair> SetPythonPathForPluginPairs(List<PluginMetadata> source, string pythonPath)
|
private static IEnumerable<PluginPair> SetPythonPathForPluginPairs(IEnumerable<PluginMetadata> source, string pythonPath)
|
||||||
=> source
|
=> source
|
||||||
.Where(o => o.Language.ToUpper() == AllowedLanguage.Python)
|
.Where(o => o.Language.ToUpper() is AllowedLanguage.Python or AllowedLanguage.PythonV2)
|
||||||
.Select(metadata => new PluginPair
|
.Select(metadata => new PluginPair
|
||||||
{
|
{
|
||||||
Plugin = new PythonPlugin(pythonPath),
|
Plugin = metadata.Language.ToUpper() switch
|
||||||
|
{
|
||||||
|
AllowedLanguage.Python => new PythonPlugin(pythonPath),
|
||||||
|
AllowedLanguage.PythonV2 => new PythonPluginV2(pythonPath),
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
},
|
||||||
Metadata = metadata
|
Metadata = metadata
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
public static IEnumerable<PluginPair> ExecutablePlugins(IEnumerable<PluginMetadata> source)
|
public static IEnumerable<PluginPair> ExecutablePlugins(IEnumerable<PluginMetadata> source)
|
||||||
{
|
{
|
||||||
return source
|
return source
|
||||||
.Where(o => o.Language.ToUpper() == AllowedLanguage.Executable)
|
.Where(o => o.Language.ToUpper() == AllowedLanguage.Executable)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ using Flow.Launcher.Plugin;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.Plugin
|
namespace Flow.Launcher.Core.Plugin
|
||||||
{
|
{
|
||||||
internal class PythonPlugin : JsonRPCPlugin
|
internal class PythonPlugin : JsonRpcPlugin
|
||||||
{
|
{
|
||||||
private readonly ProcessStartInfo _startInfo;
|
private readonly ProcessStartInfo _startInfo;
|
||||||
public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
|
public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
};
|
};
|
||||||
|
|
||||||
// temp fix for issue #667
|
// temp fix for issue #667
|
||||||
var path = Path.Combine(Constant.ProgramDirectory, JsonRPC);
|
var path = Path.Combine(Constant.ProgramDirectory, JsonRpc);
|
||||||
_startInfo.EnvironmentVariables["PYTHONPATH"] = path;
|
_startInfo.EnvironmentVariables["PYTHONPATH"] = path;
|
||||||
|
|
||||||
_startInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
|
_startInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
|
||||||
|
|
@ -47,7 +47,7 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default)
|
protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
_startInfo.ArgumentList[2] = rpcRequest.ToString();
|
_startInfo.ArgumentList[2] = rpcRequest.ToString();
|
||||||
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
|
_startInfo.WorkingDirectory = Context.CurrentPluginMetadata.PluginDirectory;
|
||||||
// TODO: Async Action
|
// TODO: Async Action
|
||||||
return Execute(_startInfo);
|
return Execute(_startInfo);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
63
Flow.Launcher.Core/Plugin/PythonPluginV2.cs
Normal file
63
Flow.Launcher.Core/Plugin/PythonPluginV2.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Flow.Launcher.Infrastructure;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
|
|
||||||
|
namespace Flow.Launcher.Core.Plugin
|
||||||
|
{
|
||||||
|
public class PythonPluginV2 : JsonRpcPluginV2
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
|
||||||
|
public PythonPluginV2(string filename)
|
||||||
|
{
|
||||||
|
_startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = filename,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
RedirectStandardInput = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// temp fix for issue #667
|
||||||
|
var path = Path.Combine(Constant.ProgramDirectory, JsonRpc);
|
||||||
|
_startInfo.EnvironmentVariables["PYTHONPATH"] = path;
|
||||||
|
|
||||||
|
_startInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
|
||||||
|
_startInfo.EnvironmentVariables["FLOW_PROGRAM_DIRECTORY"] = Constant.ProgramDirectory;
|
||||||
|
_startInfo.EnvironmentVariables["FLOW_APPLICATION_DIRECTORY"] = Constant.ApplicationDirectory;
|
||||||
|
|
||||||
|
|
||||||
|
//Add -B flag to tell python don't write .py[co] files. Because .pyc contains location infos which will prevent python portable
|
||||||
|
_startInfo.ArgumentList.Add("-B");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
await base.InitAsync(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
namespace Flow.Launcher.Plugin
|
using System;
|
||||||
|
|
||||||
|
namespace Flow.Launcher.Plugin
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allowed plugin languages
|
/// Allowed plugin languages
|
||||||
|
|
@ -8,34 +10,27 @@
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Python
|
/// Python
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Python
|
public const string Python = "PYTHON";
|
||||||
{
|
|
||||||
get { return "PYTHON"; }
|
/// <summary>
|
||||||
}
|
/// Python V2
|
||||||
|
/// </summary>
|
||||||
|
public const string PythonV2 = "PYTHON_V2";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// C#
|
/// C#
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string CSharp
|
public const string CSharp = "CSHARP";
|
||||||
{
|
|
||||||
get { return "CSHARP"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// F#
|
/// F#
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string FSharp
|
public const string FSharp = "FSHARP";
|
||||||
{
|
|
||||||
get { return "FSHARP"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Standard .exe
|
/// Standard .exe
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Executable
|
public const string Executable = "EXECUTABLE";
|
||||||
{
|
|
||||||
get { return "EXECUTABLE"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines if this language is a .NET language
|
/// Determines if this language is a .NET language
|
||||||
|
|
@ -56,8 +51,9 @@
|
||||||
public static bool IsAllowed(string language)
|
public static bool IsAllowed(string language)
|
||||||
{
|
{
|
||||||
return IsDotNet(language)
|
return IsDotNet(language)
|
||||||
|| language.ToUpper() == Python.ToUpper()
|
|| String.Equals(language, Python, StringComparison.CurrentCultureIgnoreCase)
|
||||||
|| language.ToUpper() == Executable.ToUpper();
|
|| String.Equals(language, PythonV2, StringComparison.CurrentCultureIgnoreCase)
|
||||||
|
|| String.Equals(language, Executable, StringComparison.CurrentCultureIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Flow.Launcher.Plugin
|
namespace Flow.Launcher.Plugin
|
||||||
{
|
{
|
||||||
|
|
@ -71,26 +72,31 @@ namespace Flow.Launcher.Plugin
|
||||||
|
|
||||||
public string ActionKeyword { get; init; }
|
public string ActionKeyword { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Return first search split by space if it has
|
/// Return first search split by space if it has
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string FirstSearch => SplitSearch(0);
|
public string FirstSearch => SplitSearch(0);
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
private string _secondToEndSearch;
|
private string _secondToEndSearch;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// strings from second search (including) to last search
|
/// strings from second search (including) to last search
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
public string SecondToEndSearch => SearchTerms.Length > 1 ? (_secondToEndSearch ??= string.Join(' ', SearchTerms[1..])) : "";
|
public string SecondToEndSearch => SearchTerms.Length > 1 ? (_secondToEndSearch ??= string.Join(' ', SearchTerms[1..])) : "";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Return second search split by space if it has
|
/// Return second search split by space if it has
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
public string SecondSearch => SplitSearch(1);
|
public string SecondSearch => SplitSearch(1);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Return third search split by space if it has
|
/// Return third search split by space if it has
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
public string ThirdSearch => SplitSearch(2);
|
public string ThirdSearch => SplitSearch(2);
|
||||||
|
|
||||||
private string SplitSearch(int index)
|
private string SplitSearch(int index)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
// ReSharper disable once InconsistentNaming
|
// ReSharper disable once InconsistentNaming
|
||||||
internal class JsonRPCPluginTest : JsonRPCPlugin
|
internal class JsonRPCPluginTest : JsonRpcPlugin
|
||||||
{
|
{
|
||||||
public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
|
public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
|
||||||
|
|
||||||
|
|
@ -56,21 +56,14 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
|
|
||||||
public static List<JsonRPCQueryResponseModel> ResponseModelsSource = new()
|
public static List<JsonRPCQueryResponseModel> ResponseModelsSource = new()
|
||||||
{
|
{
|
||||||
new()
|
new JsonRPCQueryResponseModel(0, new List<JsonRPCResult>()),
|
||||||
|
new JsonRPCQueryResponseModel(0, new List<JsonRPCResult>
|
||||||
{
|
{
|
||||||
Result = new()
|
new JsonRPCResult
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Result = new()
|
|
||||||
{
|
{
|
||||||
new JsonRPCResult
|
Title = "Test1", SubTitle = "Test2"
|
||||||
{
|
|
||||||
Title = "Test1",
|
|
||||||
SubTitle = "Test2"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
[TestCaseSource(typeof(JsonRPCPluginTest), nameof(ResponseModelsSource))]
|
[TestCaseSource(typeof(JsonRPCPluginTest), nameof(ResponseModelsSource))]
|
||||||
|
|
@ -97,4 +90,4 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue