using System; using System.Diagnostics; using System.IO; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin; namespace Flow.Launcher.Core.Plugin { internal class PythonPlugin : JsonRPCPlugin { private readonly ProcessStartInfo _startInfo; public PythonPlugin(string filename) { _startInfo = new ProcessStartInfo { FileName = filename, UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, }; var path = Path.Combine(Constant.ProgramDirectory, JsonRPC); _startInfo.EnvironmentVariables["PYTHONPATH"] = path; // Prevent Python from writing .py[co] files. // Because .pyc contains location infos which will prevent python portable. _startInfo.EnvironmentVariables["PYTHONDONTWRITEBYTECODE"] = "1"; _startInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version; _startInfo.EnvironmentVariables["FLOW_PROGRAM_DIRECTORY"] = Constant.ProgramDirectory; _startInfo.EnvironmentVariables["FLOW_APPLICATION_DIRECTORY"] = Constant.ApplicationDirectory; } protected override Task RequestAsync(JsonRPCRequestModel request, CancellationToken token = default) { _startInfo.ArgumentList[2] = JsonSerializer.Serialize(request, RequestSerializeOption); return ExecuteAsync(_startInfo, token); } protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default) { // since this is not static, request strings will build up in ArgumentList if index is not specified _startInfo.ArgumentList[2] = JsonSerializer.Serialize(rpcRequest, RequestSerializeOption); _startInfo.WorkingDirectory = Context.CurrentPluginMetadata.PluginDirectory; // TODO: Async Action return Execute(_startInfo); } public override async Task InitAsync(PluginInitContext context) { // Run .py files via `-c ` if (context.CurrentPluginMetadata.ExecuteFilePath.EndsWith(".py", StringComparison.OrdinalIgnoreCase)) { var rootDirectory = context.CurrentPluginMetadata.PluginDirectory; var libDirectory = Path.Combine(rootDirectory, "lib"); var libPyWin32Directory = Path.Combine(libDirectory, "win32"); var libPyWin32LibDirectory = Path.Combine(libPyWin32Directory, "lib"); var pluginDirectory = Path.Combine(rootDirectory, "plugin"); // This makes it easier for plugin authors to import their own modules. // They won't have to add `.`, `./lib`, or `./plugin` to their sys.path manually. // Instead of running the .py file directly, we pass the code we want to run as a CLI argument. // This code sets sys.path for the plugin author and then runs the .py file via runpy. _startInfo.ArgumentList.Add("-c"); _startInfo.ArgumentList.Add( $""" import sys sys.path.append(r'{rootDirectory}') sys.path.append(r'{libDirectory}') sys.path.append(r'{libPyWin32LibDirectory}') sys.path.append(r'{libPyWin32Directory}') sys.path.append(r'{pluginDirectory}') import runpy runpy.run_path(r'{context.CurrentPluginMetadata.ExecuteFilePath}', None, '__main__') """ ); // Plugins always expect the JSON data to be in the third argument // (we're always setting it as _startInfo.ArgumentList[2] = ...). _startInfo.ArgumentList.Add(""); } // Run .pyz files as is else { // No need for -B flag because we're using PYTHONDONTWRITEBYTECODE env variable now, // but the plugins still expect data to be sent as the third argument, so we're keeping // the flag here, even though it's not necessary anymore. _startInfo.ArgumentList.Add("-B"); _startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath); // Plugins always expect the JSON data to be in the third argument // (we're always setting it as _startInfo.ArgumentList[2] = ...). _startInfo.ArgumentList.Add(""); } await base.InitAsync(context); _startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory; } } }