mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Removed logic that converted absolute paths to relative paths within the ProgramDirectory. Now, plugin settings file paths are always stored as absolute paths. Deleted the ConvertToRelativePathIfPossible method and updated usages accordingly.
254 lines
10 KiB
C#
254 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Forms;
|
|
using Flow.Launcher.Infrastructure.UserSettings;
|
|
using Flow.Launcher.Plugin;
|
|
using Flow.Launcher.Plugin.SharedCommands;
|
|
|
|
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|
{
|
|
public abstract class AbstractPluginEnvironment
|
|
{
|
|
private static readonly string ClassName = nameof(AbstractPluginEnvironment);
|
|
|
|
protected readonly IPublicAPI API = PublicApi.Instance;
|
|
|
|
internal abstract string Language { get; }
|
|
|
|
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<PluginMetadata> PluginMetadataList;
|
|
|
|
internal PluginsSettings PluginSettings;
|
|
|
|
internal AbstractPluginEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings)
|
|
{
|
|
PluginMetadataList = pluginMetadataList;
|
|
PluginSettings = pluginSettings;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves the configured plugin settings file path to an absolute path.
|
|
/// Supports both absolute paths and relative paths (relative to ProgramDirectory).
|
|
/// </summary>
|
|
private string ResolvedPluginsSettingsFilePath => DataLocation.ResolveAbsolutePath(PluginsSettingsFilePath);
|
|
|
|
internal IEnumerable<PluginPair> Setup()
|
|
{
|
|
// If no plugin is using the language, return empty list
|
|
if (!PluginMetadataList.Any(o => o.Language.Equals(Language, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
return new List<PluginPair>();
|
|
}
|
|
|
|
var resolvedPath = ResolvedPluginsSettingsFilePath;
|
|
if (!string.IsNullOrEmpty(resolvedPath) && FilesFolders.FileExists(resolvedPath))
|
|
{
|
|
// Ensure latest only if user is using Flow's environment setup.
|
|
if (resolvedPath.StartsWith(EnvPath, StringComparison.OrdinalIgnoreCase))
|
|
EnsureLatestInstalled(ExecutablePath, resolvedPath, EnvPath);
|
|
|
|
return SetPathForPluginPairs(resolvedPath, Language);
|
|
}
|
|
|
|
var noRuntimeMessage = Localize.runtimePluginInstalledChooseRuntimePrompt(Language, EnvName, Environment.NewLine);
|
|
if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
|
{
|
|
var msg = Localize.runtimePluginChooseRuntimeExecutable(EnvName);
|
|
|
|
var selectedFile = GetFileFromDialog(msg, FileDialogFilter);
|
|
|
|
if (!string.IsNullOrEmpty(selectedFile))
|
|
{
|
|
PluginsSettingsFilePath = selectedFile;
|
|
}
|
|
// Nothing selected because user pressed cancel from the file dialog window
|
|
else
|
|
{
|
|
var forceDownloadMessage = Localize.runtimeExecutableInvalidChooseDownload(Language, EnvName, Environment.NewLine);
|
|
|
|
// Let users select valid path or choose to download
|
|
while (string.IsNullOrEmpty(selectedFile))
|
|
{
|
|
if (API.ShowMsgBox(forceDownloadMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
|
{
|
|
// Continue select file
|
|
selectedFile = GetFileFromDialog(msg, FileDialogFilter);
|
|
}
|
|
else
|
|
{
|
|
// User selected no, break the loop
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(selectedFile))
|
|
{
|
|
PluginsSettingsFilePath = selectedFile;
|
|
}
|
|
else
|
|
{
|
|
InstallEnvironment();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InstallEnvironment();
|
|
}
|
|
|
|
resolvedPath = ResolvedPluginsSettingsFilePath;
|
|
if (FilesFolders.FileExists(resolvedPath))
|
|
{
|
|
return SetPathForPluginPairs(resolvedPath, Language);
|
|
}
|
|
else
|
|
{
|
|
API.ShowMsgBox(Localize.runtimePluginUnableToSetExecutablePath(Language));
|
|
API.LogError(ClassName,
|
|
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
|
|
$"{Language}Environment");
|
|
|
|
return new List<PluginPair>();
|
|
}
|
|
}
|
|
|
|
internal abstract void InstallEnvironment();
|
|
|
|
private void EnsureLatestInstalled(string expectedPath, string currentPath, string installedDirPath)
|
|
{
|
|
if (expectedPath == currentPath) return;
|
|
|
|
FilesFolders.RemoveFolderIfExists(installedDirPath, (s) => API.ShowMsgBox(s));
|
|
|
|
InstallEnvironment();
|
|
}
|
|
|
|
internal abstract PluginPair CreatePluginPair(string filePath, PluginMetadata metadata);
|
|
|
|
private IEnumerable<PluginPair> SetPathForPluginPairs(string filePath, string languageToSet)
|
|
{
|
|
var pluginPairs = new List<PluginPair>();
|
|
|
|
foreach (var metadata in PluginMetadataList)
|
|
{
|
|
if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
metadata.AssemblyName = string.Empty;
|
|
pluginPairs.Add(CreatePluginPair(filePath, metadata));
|
|
}
|
|
}
|
|
|
|
return pluginPairs;
|
|
}
|
|
|
|
private 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();
|
|
return result == DialogResult.OK ? dlg.FileName : string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// After app updated while in portable mode or switched between portable/roaming mode,
|
|
/// need to update each plugin's executable path so user will not be prompted again to reinstall the environments.
|
|
/// </summary>
|
|
/// <param name="settings"></param>
|
|
public static void PreStartPluginExecutablePathUpdate(Settings settings)
|
|
{
|
|
if (DataLocation.PortableDataLocationInUse())
|
|
{
|
|
// When user is using portable but has moved flow to a different location
|
|
if (IsUsingPortablePath(settings.PluginSettings.PythonExecutablePath, DataLocation.PythonEnvironmentName)
|
|
&& !settings.PluginSettings.PythonExecutablePath.StartsWith(DataLocation.PortableDataPath))
|
|
{
|
|
settings.PluginSettings.PythonExecutablePath
|
|
= GetUpdatedEnvironmentPath(settings.PluginSettings.PythonExecutablePath);
|
|
}
|
|
|
|
if (IsUsingPortablePath(settings.PluginSettings.NodeExecutablePath, DataLocation.NodeEnvironmentName)
|
|
&& !settings.PluginSettings.NodeExecutablePath.StartsWith(DataLocation.PortableDataPath))
|
|
{
|
|
settings.PluginSettings.NodeExecutablePath
|
|
= GetUpdatedEnvironmentPath(settings.PluginSettings.NodeExecutablePath);
|
|
}
|
|
|
|
// When user has switched from roaming to portable
|
|
if (IsUsingRoamingPath(settings.PluginSettings.PythonExecutablePath))
|
|
{
|
|
settings.PluginSettings.PythonExecutablePath
|
|
= settings.PluginSettings.PythonExecutablePath.Replace(DataLocation.RoamingDataPath, DataLocation.PortableDataPath);
|
|
}
|
|
|
|
if (IsUsingRoamingPath(settings.PluginSettings.NodeExecutablePath))
|
|
{
|
|
settings.PluginSettings.NodeExecutablePath
|
|
= settings.PluginSettings.NodeExecutablePath.Replace(DataLocation.RoamingDataPath, DataLocation.PortableDataPath);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (IsUsingPortablePath(settings.PluginSettings.PythonExecutablePath, DataLocation.PythonEnvironmentName))
|
|
{
|
|
settings.PluginSettings.PythonExecutablePath
|
|
= GetUpdatedEnvironmentPath(settings.PluginSettings.PythonExecutablePath);
|
|
}
|
|
|
|
if (IsUsingPortablePath(settings.PluginSettings.NodeExecutablePath, DataLocation.NodeEnvironmentName))
|
|
{
|
|
settings.PluginSettings.NodeExecutablePath
|
|
= GetUpdatedEnvironmentPath(settings.PluginSettings.NodeExecutablePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool IsUsingPortablePath(string filePath, string pluginEnvironmentName)
|
|
{
|
|
if (string.IsNullOrEmpty(filePath)) return false;
|
|
|
|
// DataLocation.PortableDataPath returns the current portable path, this determines if an out
|
|
// of date path is also a portable path.
|
|
var portableAppEnvLocation = Path.Combine("UserData", DataLocation.PluginEnvironments, pluginEnvironmentName);
|
|
|
|
return filePath.Contains(portableAppEnvLocation);
|
|
}
|
|
|
|
private static bool IsUsingRoamingPath(string filePath)
|
|
{
|
|
if (string.IsNullOrEmpty(filePath)) return false;
|
|
|
|
return filePath.StartsWith(DataLocation.RoamingDataPath);
|
|
}
|
|
|
|
private static string GetUpdatedEnvironmentPath(string filePath)
|
|
{
|
|
var index = filePath.IndexOf(DataLocation.PluginEnvironments);
|
|
|
|
// get the substring after "Environments" because we can not determine it dynamically
|
|
var executablePathSubstring = filePath[(index + DataLocation.PluginEnvironments.Length)..];
|
|
return $"{DataLocation.PluginEnvironmentsPath}{executablePathSubstring}";
|
|
}
|
|
}
|
|
}
|