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.Windows.Forms; namespace Flow.Launcher.Core.ExternalPlugins { public 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.PythonFilePath) && FilesFolders.FileExists(pluginSettings.PythonFilePath)) return SetPathForPluginPairs($"{pluginSettings.PythonFilePath}\\{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 msg = $"Please select the {environment} executable"; var selectedFile = string.Empty; switch (languageType) { case AllowedLanguage.Python: selectedFile = GetFileFromDialog(msg, "Python|pythonw.exe"); if (!string.IsNullOrEmpty(selectedFile)) { Constant.PythonPath = selectedFile; pluginSettings.PythonFilePath = Constant.PythonPath; } break; case AllowedLanguage.TypeScript: case AllowedLanguage.JavaScript: selectedFile = GetFileFromDialog(msg); if (!string.IsNullOrEmpty(selectedFile)) { Constant.NodePath = selectedFile; pluginSettings.NodeFilePath = Constant.NodePath; } break; default: break; } // Nothing selected because user pressed cancel from the file dialog window if (string.IsNullOrEmpty(selectedFile)) InstallEnvironment(languageType); } else { InstallEnvironment(languageType); } switch (languageType) { case AllowedLanguage.Python when FilesFolders.FileExists(pluginSettings.PythonFilePath) && !string.IsNullOrEmpty(Constant.PythonPath): return SetPathForPluginPairs(pluginSettings.PythonFilePath, languageType); case AllowedLanguage.TypeScript when FilesFolders.FileExists(pluginSettings.NodeFilePath) && !string.IsNullOrEmpty(Constant.NodePath): case AllowedLanguage.JavaScript when FilesFolders.FileExists(pluginSettings.NodeFilePath) && !string.IsNullOrEmpty(Constant.NodePath): 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, setting's PythonFilePath variable is still an empty string.", "PluginEnvironment"); return new List(); } } private void InstallEnvironment(string languageType) { switch (languageType) {//TODO: UPDATE TO USE CENTRALISED PLUGINENVIRONMENT FOLDER 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(); Constant.PythonPath = Path.Combine(pythonDirPath, PythonExecutable); pluginSettings.PythonFilePath = Constant.PythonPath; 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; } public static string GetFileFromDialog(string title, string filter="") { var dlg = new OpenFileDialog { InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), Multiselect = false, CheckFileExists = true, CheckPathExists = true, Title = title, Filter = filter }; var result = dlg.ShowDialog(); if (result == DialogResult.OK) { return dlg.FileName; } else { return string.Empty; } } } }