From 4e48c3eb10017323574c407d0dcdd46786fdece4 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 22 Nov 2022 09:20:27 +1100 Subject: [PATCH] refactor --- .../Environments/AbstractPluginEnvironment.cs | 148 ++++++++++ .../Environments/JavaScriptEnvironment.cs | 14 + .../Environments/PythonEnvironment.cs | 38 +++ .../Environments/TypeScriptEnvironment.cs | 34 +++ .../ExternalPlugins/PluginEnvironment.cs | 264 ------------------ Flow.Launcher.Core/Plugin/PluginsLoader.cs | 14 +- .../UserSettings/PluginSettings.cs | 16 +- Flow.Launcher/SettingWindow.xaml | 8 +- Flow.Launcher/SettingWindow.xaml.cs | 17 +- .../ViewModel/SettingWindowViewModel.cs | 23 ++ 10 files changed, 282 insertions(+), 294 deletions(-) create mode 100644 Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs create mode 100644 Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptEnvironment.cs create mode 100644 Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs create mode 100644 Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs delete mode 100644 Flow.Launcher.Core/ExternalPlugins/PluginEnvironment.cs diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs new file mode 100644 index 000000000..9aa1c9f8c --- /dev/null +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs @@ -0,0 +1,148 @@ +using Flow.Launcher.Core.Plugin; +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.Linq; +using System.Windows.Forms; + +namespace Flow.Launcher.Core.ExternalPlugins.Environments +{ + internal abstract class AbstractPluginEnvironment + { + internal abstract string Language { get; } + + internal const string Environments = "Environments"; + + internal abstract string EnvName { get; } + + internal abstract string EnvPath { get; } + + internal abstract string InstallPath { get; } + + internal abstract string ExecutablePath { get; } + + internal virtual string FileDialogFilter => string.Empty; + + internal abstract string PluginsSettingsFilePath { get; set; } + + internal List PluginMetadataList; + + internal PluginsSettings PluginSettings; + + internal AbstractPluginEnvironment(List pluginMetadataList, PluginsSettings pluginSettings) + { + PluginMetadataList = pluginMetadataList; + PluginSettings = pluginSettings; + } + //TODO: CHECK IF NEED TO RESET PATH AFTER FLOW UPDATE + internal IEnumerable Setup() + { + if (!PluginMetadataList.Any(o => o.Language.Equals(Language, StringComparison.OrdinalIgnoreCase))) + return new List(); + + if (!string.IsNullOrEmpty(PluginsSettingsFilePath) && FilesFolders.FileExists(PluginsSettingsFilePath)) + { + EnsureLatestInstalled(ExecutablePath, PluginsSettingsFilePath, EnvPath); + + return SetPathForPluginPairs(PluginsSettingsFilePath, Language); + } + + if (MessageBox.Show($"Flow detected you have installed {Language} plugins, which " + + $"will require {EnvName} to run. Would you like to download {EnvName}? " + + Environment.NewLine + Environment.NewLine + + "Click no if it's already installed, " + + $"and you will be prompted to select the folder that contains the {EnvName} executable", + string.Empty, MessageBoxButtons.YesNo) == DialogResult.No) + { + var msg = $"Please select the {EnvName} executable"; + var selectedFile = string.Empty; + + selectedFile = GetFileFromDialog(msg, FileDialogFilter); + + if (!string.IsNullOrEmpty(selectedFile)) + PluginsSettingsFilePath = selectedFile; + + // Nothing selected because user pressed cancel from the file dialog window + if (string.IsNullOrEmpty(selectedFile)) + InstallEnvironment(); + } + else + { + InstallEnvironment(); + } + + if (FilesFolders.FileExists(PluginsSettingsFilePath)) + { + return SetPathForPluginPairs(PluginsSettingsFilePath, Language); + } + else + { + MessageBox.Show( + $"Unable to set {Language} executable path, please try from Flow's settings (scroll down to the bottom)."); + Log.Error("PluginsLoader", + $"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.", + $"{Language}Environment"); + + return new List(); + } + } + + internal abstract void InstallEnvironment(); + + private void EnsureLatestInstalled(string expectedPath, string currentPath, string installedDirPath) + { + if (expectedPath == currentPath) + return; + + FilesFolders.RemoveFolderIfExists(installedDirPath); + + InstallEnvironment(); + + } + + private IEnumerable SetPathForPluginPairs(string filePath, string languageToSet) + { + var pluginPairs = new List(); + + foreach (var metadata in PluginMetadataList) + { + if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase)) + { + pluginPairs.Add(new PluginPair + { + Plugin = new PythonPlugin(filePath), + Metadata = metadata + }); + } + } + + return pluginPairs; + } + + private 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; + } + } + } +} diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptEnvironment.cs new file mode 100644 index 000000000..b67059b1b --- /dev/null +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptEnvironment.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Core.ExternalPlugins.Environments +{ + + internal class JavaScriptEnvironment : TypeScriptEnvironment + { + internal override string Language => AllowedLanguage.JavaScript; + + internal JavaScriptEnvironment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { } + } +} diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs new file mode 100644 index 000000000..3d670be95 --- /dev/null +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs @@ -0,0 +1,38 @@ +using Droplex; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedCommands; +using System.Collections.Generic; +using System.IO; + +namespace Flow.Launcher.Core.ExternalPlugins.Environments +{ + internal class PythonEnvironment : AbstractPluginEnvironment + { + internal override string Language => AllowedLanguage.Python; + + internal override string EnvName => "Python"; + + internal override string EnvPath => Path.Combine(DataLocation.DataDirectory(), Environments, EnvName); + + internal override string InstallPath => Path.Combine(EnvPath, "PythonEmbeddable-v3.8.9"); + + internal override string ExecutablePath => Path.Combine(InstallPath, "pythonw.exe"); + + internal override string FileDialogFilter => "Python|pythonw.exe"; + + internal override string PluginsSettingsFilePath { get => PluginSettings.PythonExecutablePath; set => PluginSettings.PythonExecutablePath = value; } + + internal PythonEnvironment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { } + + internal override void InstallEnvironment() + { + FilesFolders.RemoveFolderIfExists(InstallPath); + + // Python 3.8.9 is used for Windows 7 compatibility + DroplexPackage.Drop(App.python_3_8_9_embeddable, InstallPath).Wait(); + + PluginsSettingsFilePath = ExecutablePath; + } + } +} diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs new file mode 100644 index 000000000..4534c9106 --- /dev/null +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Droplex; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin.SharedCommands; +using Flow.Launcher.Plugin; +using System.IO; + +namespace Flow.Launcher.Core.ExternalPlugins.Environments +{ + internal class TypeScriptEnvironment : AbstractPluginEnvironment + { + internal override string Language => AllowedLanguage.TypeScript; + + internal override string EnvName => "Node.js"; + + internal override string EnvPath => Path.Combine(DataLocation.DataDirectory(), Environments, EnvName); + + internal override string InstallPath => Path.Combine(EnvPath, "Node-v16.18.0"); + internal override string ExecutablePath => Path.Combine(InstallPath, "node-v16.18.0-win-x64\\node.exe"); + + internal override string PluginsSettingsFilePath { get => PluginSettings.NodeExecutablePath; set => PluginSettings.NodeExecutablePath = value; } + + internal TypeScriptEnvironment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { } + + internal override void InstallEnvironment() + { + FilesFolders.RemoveFolderIfExists(InstallPath); + + DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait(); + + PluginsSettingsFilePath = ExecutablePath; + } + } +} diff --git a/Flow.Launcher.Core/ExternalPlugins/PluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/PluginEnvironment.cs deleted file mode 100644 index a70ab5528..000000000 --- a/Flow.Launcher.Core/ExternalPlugins/PluginEnvironment.cs +++ /dev/null @@ -1,264 +0,0 @@ -using Droplex; -using Flow.Launcher.Core.Plugin; -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 environments = "Environments"; - - private const string python = "Python"; - - private static string pythonEnvDirPath = Path.Combine(DataLocation.DataDirectory(), environments, python); - - private static string pythonDirPath = Path.Combine(pythonEnvDirPath, "PythonEmbeddable-v3.8.9"); - - private string pythonFilePath = Path.Combine(pythonDirPath, "pythonw.exe"); - - private const string nodejs = "Node.js"; - - private static string nodeEnvDirPath = Path.Combine(DataLocation.DataDirectory(), environments, nodejs); - - private static string nodeDirPath = Path.Combine(nodeEnvDirPath, "Node-v16.18.0"); - - private string nodeFilePath = Path.Combine(nodeDirPath, $"node-v16.18.0-win-x64\\node.exe"); - - 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 - internal IEnumerable PythonSetup() - { - return Setup(AllowedLanguage.Python, python); - } - - internal IEnumerable TypeScriptSetup() - { - return Setup(AllowedLanguage.TypeScript, nodejs); - } - - internal IEnumerable JavaScriptSetup() - { - return Setup(AllowedLanguage.JavaScript, nodejs); - } - - 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)) - { - EnsureLatestInstalled( - pythonFilePath, - pluginSettings.PythonFilePath, - pythonEnvDirPath, - languageType); - - return SetPathForPluginPairs(pluginSettings.PythonFilePath, languageType); - } - - break; - - case AllowedLanguage.TypeScript: - case AllowedLanguage.JavaScript: - if (!string.IsNullOrEmpty(pluginSettings.NodeFilePath) && FilesFolders.FileExists(pluginSettings.NodeFilePath)) - { - EnsureLatestInstalled( - nodeFilePath, - pluginSettings.NodeFilePath, - nodeEnvDirPath, - languageType); - - 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)) - pluginSettings.PythonFilePath = selectedFile; - break; - - case AllowedLanguage.TypeScript: - case AllowedLanguage.JavaScript: - selectedFile = GetFileFromDialog(msg); - - if (!string.IsNullOrEmpty(selectedFile)) - pluginSettings.NodeFilePath = selectedFile; - 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): - return SetPathForPluginPairs(pluginSettings.PythonFilePath, languageType); - - case AllowedLanguage.TypeScript when FilesFolders.FileExists(pluginSettings.NodeFilePath): - case AllowedLanguage.JavaScript when FilesFolders.FileExists(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, setting's PythonFilePath variable is still an empty string.", - "PluginEnvironment"); - - return new List(); - } - } - - private void InstallEnvironment(string languageType) - { - switch (languageType) - { - case AllowedLanguage.Python: - FilesFolders.RemoveFolderIfExists(pythonDirPath); - - // Python 3.8.9 is used for Windows 7 compatibility - DroplexPackage.Drop(App.python_3_8_9_embeddable, pythonDirPath).Wait(); - - pluginSettings.PythonFilePath = pythonFilePath; - break; - - case AllowedLanguage.TypeScript: - case AllowedLanguage.JavaScript: - FilesFolders.RemoveFolderIfExists(nodeDirPath); - - DroplexPackage.Drop(App.nodejs_16_18_0, nodeDirPath).Wait(); - - pluginSettings.NodeFilePath = nodeFilePath; - break; - - default: - break; - } - } - - private void EnsureLatestInstalled(string expectedPath, string currentPath, string installedDirPath, string languagType) - { - if (expectedPath == currentPath) - return; - - FilesFolders.RemoveFolderIfExists(installedDirPath); - - InstallEnvironment(languagType); - - } - - 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; - } - } - - } -} diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 40bef79f9..e6329aba1 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Windows.Forms; -using Flow.Launcher.Core.ExternalPlugins; +using Flow.Launcher.Core.ExternalPlugins.Environments; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; @@ -14,16 +14,16 @@ namespace Flow.Launcher.Core.Plugin { public static class PluginsLoader { - public const string PythonExecutable = "pythonw.exe"; - public static List Plugins(List metadatas, PluginsSettings settings) { var dotnetPlugins = DotNetPlugins(metadatas); - var pluginEnv = new PluginEnvironment(metadatas, settings); - var pythonPlugins = pluginEnv.PythonSetup(); - var tsPlugins = pluginEnv.TypeScriptSetup(); - var jsPlugins = pluginEnv.JavaScriptSetup(); + var pythonEnv = new PythonEnvironment(metadatas, settings); + var tsEnv = new TypeScriptEnvironment(metadatas, settings); + var jsEnv = new JavaScriptEnvironment(metadatas, settings); + var pythonPlugins = pythonEnv.Setup(); + var tsPlugins = tsEnv.Setup(); + var jsPlugins = jsEnv.Setup(); var executablePlugins = ExecutablePlugins(metadatas); diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs index 079d9ba10..a6ee735c3 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -5,23 +5,23 @@ namespace Flow.Launcher.Infrastructure.UserSettings { public class PluginsSettings : BaseModel { - private string pythonFilePath; - public string PythonFilePath { - get { return pythonFilePath; } + private string pythonExecutablePath; + public string PythonExecutablePath { + get { return pythonExecutablePath; } set { - pythonFilePath = value; + pythonExecutablePath = value; Constant.PythonPath = value; } } - private string nodeFilePath; - public string NodeFilePath + private string nodeExecutablePath; + public string NodeExecutablePath { - get { return nodeFilePath; } + get { return nodeExecutablePath; } set { - nodeFilePath = value; + nodeExecutablePath = value; Constant.NodePath = value; } } diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index d1d13d5fd..52195acb6 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -944,11 +944,11 @@ + Text="{Binding Settings.PluginSettings.PythonExecutablePath, TargetNullValue='None'}" />