From 4ea1ee0c043335cce3b5fd1b82058b2ca25dcdad Mon Sep 17 00:00:00 2001 From: Jeremy Date: Sun, 23 Oct 2022 20:05:12 +1100 Subject: [PATCH] add Node.js environment setup --- .../ExternalPlugins/PluginEnvironment.cs | 216 ++++++++++++++++++ Flow.Launcher.Core/Flow.Launcher.Core.csproj | 2 +- Flow.Launcher.Core/Plugin/NodePlugin.cs | 33 +++ Flow.Launcher.Core/Plugin/PluginsLoader.cs | 16 +- Flow.Launcher.Infrastructure/Constant.cs | 3 +- .../UserSettings/PluginSettings.cs | 7 +- Flow.Launcher.Plugin/AllowedLanguage.cs | 44 ++-- 7 files changed, 294 insertions(+), 27 deletions(-) create mode 100644 Flow.Launcher.Core/ExternalPlugins/PluginEnvironment.cs create mode 100644 Flow.Launcher.Core/Plugin/NodePlugin.cs diff --git a/Flow.Launcher.Core/ExternalPlugins/PluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/PluginEnvironment.cs new file mode 100644 index 000000000..da24ad87f --- /dev/null +++ b/Flow.Launcher.Core/ExternalPlugins/PluginEnvironment.cs @@ -0,0 +1,216 @@ +using Droplex; +using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Infrastructure; +using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedCommands; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Flow.Launcher.Core.ExternalPlugins +{ + internal class PluginEnvironment + { + private const string PythonExecutable = "pythonw.exe"; + + private const string NodeExecutable = "node.exe"; + + private const string PythonEnv = "Python"; + + private const string NodeEnv = "Node.js"; + + private List pluginMetadataList; + + private PluginsSettings pluginSettings; + + internal PluginEnvironment(List pluginMetadataList, PluginsSettings pluginSettings) + { + this.pluginMetadataList = pluginMetadataList; + this.pluginSettings = pluginSettings; + } + //TODO: CHECK IF NEED TO RESET PATH AFTER FLOW UPDATE + // LOG NODE PATH + internal IEnumerable PythonSetup() + { + return Setup(AllowedLanguage.Python, PythonEnv); + } + + internal IEnumerable TypeScriptSetup() + { + return Setup(AllowedLanguage.TypeScript, NodeEnv); + } + + internal IEnumerable JavaScriptSetup() + { + return Setup(AllowedLanguage.JavaScript, NodeEnv); + } + + private IEnumerable Setup(string languageType, string environment) + { + if (!pluginMetadataList.Any(o => o.Language.Equals(languageType, StringComparison.OrdinalIgnoreCase))) + return new List(); + + var envFilePath = string.Empty; + + switch (languageType) + { + case AllowedLanguage.Python: + if (!string.IsNullOrEmpty(pluginSettings.PythonDirectory) && FilesFolders.LocationExists(pluginSettings.PythonDirectory)) + return SetPathForPluginPairs($"{pluginSettings.PythonDirectory}\\{PythonExecutable}", languageType); + break; + + case AllowedLanguage.TypeScript: + case AllowedLanguage.JavaScript: + if (!string.IsNullOrEmpty(pluginSettings.NodeFilePath) && FilesFolders.FileExists(pluginSettings.NodeFilePath)) + return SetPathForPluginPairs(pluginSettings.NodeFilePath, languageType); + break; + + default: + break; + } + + if (MessageBox.Show($"Flow detected you have installed {languageType} plugins, which " + + $"will require {environment} to run. Would you like to download {environment}? " + + Environment.NewLine + Environment.NewLine + + "Click no if it's already installed, " + + $"and you will be prompted to select the folder that contains the {environment} executable", + string.Empty, MessageBoxButtons.YesNo) == DialogResult.No) + { + var dlg = new OpenFileDialog + { + InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), + Multiselect = false, + CheckFileExists = true, + CheckPathExists = true, + Title = $"Please select the {environment} executable" + }; + + var result = dlg.ShowDialog(); + if (result == DialogResult.OK) + { + switch (languageType) + { + case AllowedLanguage.Python: + Constant.PythonPath = dlg.FileName; + pluginSettings.PythonDirectory = Constant.PythonPath; + break; + case AllowedLanguage.TypeScript: + case AllowedLanguage.JavaScript: + Constant.NodePath = dlg.FileName; + pluginSettings.NodeFilePath = Constant.NodePath; + break; + default: + break; + } + } + else + { + InstallEnvironment(languageType); + } + } + else + { + InstallEnvironment(languageType); + } + + switch (languageType) + { + case AllowedLanguage.Python when FilesFolders.FileExists(Constant.PythonPath) && !string.IsNullOrEmpty(pluginSettings.PythonDirectory): + return SetPathForPluginPairs(Constant.PythonPath, languageType); + + case AllowedLanguage.TypeScript when FilesFolders.FileExists(Constant.NodePath) && !string.IsNullOrEmpty(pluginSettings.NodeFilePath): + case AllowedLanguage.JavaScript when FilesFolders.FileExists(Constant.NodePath) && !string.IsNullOrEmpty(pluginSettings.NodeFilePath): + return SetPathForPluginPairs(pluginSettings.NodeFilePath, languageType); + + default: + MessageBox.Show( + "Unable to set Python executable path, please try from Flow's settings (scroll down to the bottom)."); + Log.Error("PluginsLoader", + $"Not able to successfully set Python path, the PythonDirectory variable is still an empty string.", + "PythonPlugins"); + + return new List(); + } + } + + private void InstallEnvironment(string languageType) + { + switch (languageType) + { + case AllowedLanguage.Python: + var pythonDirPath = Path.Combine(DataLocation.DataDirectory(), "PythonEmbeddable"); + FilesFolders.RemoveFolderIfExists(pythonDirPath); + + // Python 3.8.9 is used for Windows 7 compatibility + DroplexPackage.Drop(App.python_3_8_9_embeddable, pythonDirPath).Wait(); + + pluginSettings.PythonDirectory = pythonDirPath; + Constant.PythonPath = Path.Combine(pythonDirPath, PythonExecutable); + + break; + + case AllowedLanguage.TypeScript: + case AllowedLanguage.JavaScript: + var nodeDirPath = Path.Combine(DataLocation.DataDirectory(), "Node"); + FilesFolders.RemoveFolderIfExists(nodeDirPath); + + DroplexPackage.Drop(App.nodejs_16_18_0, nodeDirPath).Wait(); + + Constant.NodePath = Path.Combine(nodeDirPath, $"node-v16.18.0-win-x64\\{NodeExecutable}"); + pluginSettings.NodeFilePath = Constant.NodePath; + + break; + + default: + break; + } + } + + private IEnumerable SetPathForPluginPairs(string filePath, string languageToSet) + { + var pluginPairs = new List(); + + foreach (var metadata in pluginMetadataList) { + + switch (languageToSet) + { + case AllowedLanguage.Python when metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase): + pluginPairs.Add(new PluginPair + { + Plugin = new PythonPlugin(filePath), + Metadata = metadata + }); + break; + + case AllowedLanguage.TypeScript when metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase): + pluginPairs.Add(new PluginPair + { + Plugin = new NodePlugin(filePath), + Metadata = metadata + }); + break; + + case AllowedLanguage.JavaScript when metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase): + pluginPairs.Add(new PluginPair + { + Plugin = new NodePlugin(filePath), + Metadata = metadata + }); + break; + + default: + break; + } + } + + return pluginPairs; + } + + } +} diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index 7d18c467b..d20fa94dc 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -53,7 +53,7 @@ - + diff --git a/Flow.Launcher.Core/Plugin/NodePlugin.cs b/Flow.Launcher.Core/Plugin/NodePlugin.cs new file mode 100644 index 000000000..f019aceac --- /dev/null +++ b/Flow.Launcher.Core/Plugin/NodePlugin.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Core.Plugin +{ + /// + /// Execution of JavaScript & TypeScript plugins + /// + internal class NodePlugin : JsonRPCPlugin + { + public override string SupportedLanguage { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public NodePlugin(string filename) + { + + } + + protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default) + { + throw new NotImplementedException(); + } + + protected override Task RequestAsync(JsonRPCRequestModel rpcRequest, CancellationToken token = default) + { + throw new NotImplementedException(); + } + } +} diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 752174263..3246a0210 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Threading.Tasks; using System.Windows.Forms; using Droplex; +using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; @@ -22,9 +23,20 @@ namespace Flow.Launcher.Core.Plugin public static List Plugins(List metadatas, PluginsSettings settings) { var dotnetPlugins = DotNetPlugins(metadatas); - var pythonPlugins = PythonPlugins(metadatas, settings); + + var pluginEnv = new PluginEnvironment(metadatas, settings); + var pythonPlugins = pluginEnv.PythonSetup(); + var tsPlugins = pluginEnv.TypeScriptSetup(); + var jsthonPlugins = pluginEnv.JavaScriptSetup(); + var executablePlugins = ExecutablePlugins(metadatas); - var plugins = dotnetPlugins.Concat(pythonPlugins).Concat(executablePlugins).ToList(); + + var plugins = dotnetPlugins + .Concat(pythonPlugins) + .Concat(tsPlugins) + .Concat(jsthonPlugins) + .Concat(executablePlugins) + .ToList(); return plugins; } diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index 57b39e46e..f06bf58e6 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.IO; using System.Reflection; @@ -30,6 +30,7 @@ namespace Flow.Launcher.Infrastructure public static readonly string MissingImgIcon = Path.Combine(ImagesDirectory, "app_missing_img.png"); public static string PythonPath; + public static string NodePath; public static readonly string QueryTextBoxIconImagePath = $"{ProgramDirectory}\\Images\\mainsearch.svg"; diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs index c06e1587d..68409f3f9 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -1,11 +1,14 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.UserSettings { public class PluginsSettings : BaseModel { - public string PythonDirectory { get; set; } + public string PythonDirectory { get; set; } // IS FILE PATH BUT CHANGE NAME TO FILE PATH?? + + public string NodeFilePath { get; set; } + public Dictionary Plugins { get; set; } = new Dictionary(); public void UpdatePluginSettings(List metadatas) diff --git a/Flow.Launcher.Plugin/AllowedLanguage.cs b/Flow.Launcher.Plugin/AllowedLanguage.cs index 94c645d27..d5eea4fa5 100644 --- a/Flow.Launcher.Plugin/AllowedLanguage.cs +++ b/Flow.Launcher.Plugin/AllowedLanguage.cs @@ -1,4 +1,6 @@ -namespace Flow.Launcher.Plugin +using System; + +namespace Flow.Launcher.Plugin { /// /// Allowed plugin languages @@ -8,34 +10,32 @@ /// /// Python /// - public static string Python - { - get { return "PYTHON"; } - } + public const string Python = "Python"; /// /// C# /// - public static string CSharp - { - get { return "CSHARP"; } - } + public const string CSharp = "CSharp"; /// /// F# /// - public static string FSharp - { - get { return "FSHARP"; } - } + public const string FSharp = "FSharp"; /// /// Standard .exe /// - public static string Executable - { - get { return "EXECUTABLE"; } - } + public const string Executable = "Executable"; + + /// + /// TypeScript + /// + public const string TypeScript = "TypeScript"; + + /// + /// JavaScript + /// + public const string JavaScript = "JavaScript"; /// /// Determines if this language is a .NET language @@ -44,8 +44,8 @@ /// public static bool IsDotNet(string language) { - return language.ToUpper() == CSharp - || language.ToUpper() == FSharp; + return language.Equals(CSharp, StringComparison.OrdinalIgnoreCase) + || language.Equals(FSharp, StringComparison.OrdinalIgnoreCase); } /// @@ -56,8 +56,10 @@ public static bool IsAllowed(string language) { return IsDotNet(language) - || language.ToUpper() == Python.ToUpper() - || language.ToUpper() == Executable.ToUpper(); + || language.Equals(Python, StringComparison.OrdinalIgnoreCase) + || language.Equals(Executable, StringComparison.OrdinalIgnoreCase) + || language.Equals(TypeScript, StringComparison.OrdinalIgnoreCase) + || language.Equals(JavaScript, StringComparison.OrdinalIgnoreCase); } } }