mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into binary_storage_api
This commit is contained in:
commit
8cd81b74f9
27 changed files with 490 additions and 223 deletions
|
|
@ -22,7 +22,7 @@ namespace Flow.Launcher.Core.Configuration
|
||||||
/// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish
|
/// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private UpdateManager NewUpdateManager()
|
private static UpdateManager NewUpdateManager()
|
||||||
{
|
{
|
||||||
var applicationFolderName = Constant.ApplicationDirectory
|
var applicationFolderName = Constant.ApplicationDirectory
|
||||||
.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None)
|
.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None)
|
||||||
|
|
@ -81,20 +81,16 @@ namespace Flow.Launcher.Core.Configuration
|
||||||
|
|
||||||
public void RemoveShortcuts()
|
public void RemoveShortcuts()
|
||||||
{
|
{
|
||||||
using (var portabilityUpdater = NewUpdateManager())
|
using var portabilityUpdater = NewUpdateManager();
|
||||||
{
|
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu);
|
||||||
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu);
|
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop);
|
||||||
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop);
|
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup);
|
||||||
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveUninstallerEntry()
|
public void RemoveUninstallerEntry()
|
||||||
{
|
{
|
||||||
using (var portabilityUpdater = NewUpdateManager())
|
using var portabilityUpdater = NewUpdateManager();
|
||||||
{
|
portabilityUpdater.RemoveUninstallerRegistryEntry();
|
||||||
portabilityUpdater.RemoveUninstallerRegistryEntry();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MoveUserDataFolder(string fromLocation, string toLocation)
|
public void MoveUserDataFolder(string fromLocation, string toLocation)
|
||||||
|
|
@ -110,12 +106,10 @@ namespace Flow.Launcher.Core.Configuration
|
||||||
|
|
||||||
public void CreateShortcuts()
|
public void CreateShortcuts()
|
||||||
{
|
{
|
||||||
using (var portabilityUpdater = NewUpdateManager())
|
using var portabilityUpdater = NewUpdateManager();
|
||||||
{
|
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu, false);
|
||||||
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu, false);
|
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop, false);
|
||||||
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop, false);
|
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup, false);
|
||||||
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateUninstallerEntry()
|
public void CreateUninstallerEntry()
|
||||||
|
|
@ -129,18 +123,14 @@ namespace Flow.Launcher.Core.Configuration
|
||||||
subKey2.SetValue("DisplayIcon", Path.Combine(Constant.ApplicationDirectory, "app.ico"), RegistryValueKind.String);
|
subKey2.SetValue("DisplayIcon", Path.Combine(Constant.ApplicationDirectory, "app.ico"), RegistryValueKind.String);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var portabilityUpdater = NewUpdateManager())
|
using var portabilityUpdater = NewUpdateManager();
|
||||||
{
|
_ = portabilityUpdater.CreateUninstallerRegistryEntry();
|
||||||
_ = portabilityUpdater.CreateUninstallerRegistryEntry();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void IndicateDeletion(string filePathTodelete)
|
private static void IndicateDeletion(string filePathTodelete)
|
||||||
{
|
{
|
||||||
var deleteFilePath = Path.Combine(filePathTodelete, DataLocation.DeletionIndicatorFile);
|
var deleteFilePath = Path.Combine(filePathTodelete, DataLocation.DeletionIndicatorFile);
|
||||||
using (var _ = File.CreateText(deleteFilePath))
|
using var _ = File.CreateText(deleteFilePath);
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///<summary>
|
///<summary>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using System;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
|
||||||
using Flow.Launcher.Plugin;
|
|
||||||
using Flow.Launcher.Plugin.SharedCommands;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using Flow.Launcher.Core.Resource;
|
|
||||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
{
|
{
|
||||||
|
|
@ -43,8 +42,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
|
|
||||||
internal IEnumerable<PluginPair> Setup()
|
internal IEnumerable<PluginPair> Setup()
|
||||||
{
|
{
|
||||||
|
// If no plugin is using the language, return empty list
|
||||||
if (!PluginMetadataList.Any(o => o.Language.Equals(Language, StringComparison.OrdinalIgnoreCase)))
|
if (!PluginMetadataList.Any(o => o.Language.Equals(Language, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
return new List<PluginPair>();
|
return new List<PluginPair>();
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(PluginsSettingsFilePath) && FilesFolders.FileExists(PluginsSettingsFilePath))
|
if (!string.IsNullOrEmpty(PluginsSettingsFilePath) && FilesFolders.FileExists(PluginsSettingsFilePath))
|
||||||
{
|
{
|
||||||
|
|
@ -56,24 +58,55 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
}
|
}
|
||||||
|
|
||||||
var noRuntimeMessage = string.Format(
|
var noRuntimeMessage = string.Format(
|
||||||
InternationalizationManager.Instance.GetTranslation("runtimePluginInstalledChooseRuntimePrompt"),
|
API.GetTranslation("runtimePluginInstalledChooseRuntimePrompt"),
|
||||||
Language,
|
Language,
|
||||||
EnvName,
|
EnvName,
|
||||||
Environment.NewLine
|
Environment.NewLine
|
||||||
);
|
);
|
||||||
if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||||
{
|
{
|
||||||
var msg = string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName);
|
var msg = string.Format(API.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName);
|
||||||
string selectedFile;
|
|
||||||
|
|
||||||
selectedFile = GetFileFromDialog(msg, FileDialogFilter);
|
var selectedFile = GetFileFromDialog(msg, FileDialogFilter);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(selectedFile))
|
if (!string.IsNullOrEmpty(selectedFile))
|
||||||
|
{
|
||||||
PluginsSettingsFilePath = selectedFile;
|
PluginsSettingsFilePath = selectedFile;
|
||||||
|
}
|
||||||
// Nothing selected because user pressed cancel from the file dialog window
|
// Nothing selected because user pressed cancel from the file dialog window
|
||||||
if (string.IsNullOrEmpty(selectedFile))
|
else
|
||||||
InstallEnvironment();
|
{
|
||||||
|
var forceDownloadMessage = string.Format(
|
||||||
|
API.GetTranslation("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
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -86,7 +119,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
API.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
|
API.ShowMsgBox(string.Format(API.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
|
||||||
Log.Error("PluginsLoader",
|
Log.Error("PluginsLoader",
|
||||||
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
|
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
|
||||||
$"{Language}Environment");
|
$"{Language}Environment");
|
||||||
|
|
@ -99,13 +132,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
|
|
||||||
private void EnsureLatestInstalled(string expectedPath, string currentPath, string installedDirPath)
|
private void EnsureLatestInstalled(string expectedPath, string currentPath, string installedDirPath)
|
||||||
{
|
{
|
||||||
if (expectedPath == currentPath)
|
if (expectedPath == currentPath) return;
|
||||||
return;
|
|
||||||
|
|
||||||
FilesFolders.RemoveFolderIfExists(installedDirPath, (s) => API.ShowMsgBox(s));
|
FilesFolders.RemoveFolderIfExists(installedDirPath, (s) => API.ShowMsgBox(s));
|
||||||
|
|
||||||
InstallEnvironment();
|
InstallEnvironment();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal abstract PluginPair CreatePluginPair(string filePath, PluginMetadata metadata);
|
internal abstract PluginPair CreatePluginPair(string filePath, PluginMetadata metadata);
|
||||||
|
|
@ -126,7 +157,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
return pluginPairs;
|
return pluginPairs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetFileFromDialog(string title, string filter = "")
|
private static string GetFileFromDialog(string title, string filter = "")
|
||||||
{
|
{
|
||||||
var dlg = new OpenFileDialog
|
var dlg = new OpenFileDialog
|
||||||
{
|
{
|
||||||
|
|
@ -140,7 +171,6 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
|
|
||||||
var result = dlg.ShowDialog();
|
var result = dlg.ShowDialog();
|
||||||
return result == DialogResult.OK ? dlg.FileName : string.Empty;
|
return result == DialogResult.OK ? dlg.FileName : string.Empty;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -183,31 +213,33 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (IsUsingPortablePath(settings.PluginSettings.PythonExecutablePath, DataLocation.PythonEnvironmentName))
|
if (IsUsingPortablePath(settings.PluginSettings.PythonExecutablePath, DataLocation.PythonEnvironmentName))
|
||||||
|
{
|
||||||
settings.PluginSettings.PythonExecutablePath
|
settings.PluginSettings.PythonExecutablePath
|
||||||
= GetUpdatedEnvironmentPath(settings.PluginSettings.PythonExecutablePath);
|
= GetUpdatedEnvironmentPath(settings.PluginSettings.PythonExecutablePath);
|
||||||
|
}
|
||||||
|
|
||||||
if (IsUsingPortablePath(settings.PluginSettings.NodeExecutablePath, DataLocation.NodeEnvironmentName))
|
if (IsUsingPortablePath(settings.PluginSettings.NodeExecutablePath, DataLocation.NodeEnvironmentName))
|
||||||
|
{
|
||||||
settings.PluginSettings.NodeExecutablePath
|
settings.PluginSettings.NodeExecutablePath
|
||||||
= GetUpdatedEnvironmentPath(settings.PluginSettings.NodeExecutablePath);
|
= GetUpdatedEnvironmentPath(settings.PluginSettings.NodeExecutablePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsUsingPortablePath(string filePath, string pluginEnvironmentName)
|
private static bool IsUsingPortablePath(string filePath, string pluginEnvironmentName)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(filePath))
|
if (string.IsNullOrEmpty(filePath)) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
// DataLocation.PortableDataPath returns the current portable path, this determines if an out
|
// DataLocation.PortableDataPath returns the current portable path, this determines if an out
|
||||||
// of date path is also a portable path.
|
// of date path is also a portable path.
|
||||||
var portableAppEnvLocation = $"UserData\\{DataLocation.PluginEnvironments}\\{pluginEnvironmentName}";
|
var portableAppEnvLocation = Path.Combine("UserData", DataLocation.PluginEnvironments, pluginEnvironmentName);
|
||||||
|
|
||||||
return filePath.Contains(portableAppEnvLocation);
|
return filePath.Contains(portableAppEnvLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsUsingRoamingPath(string filePath)
|
private static bool IsUsingRoamingPath(string filePath)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(filePath))
|
if (string.IsNullOrEmpty(filePath)) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
return filePath.StartsWith(DataLocation.RoamingDataPath);
|
return filePath.StartsWith(DataLocation.RoamingDataPath);
|
||||||
}
|
}
|
||||||
|
|
@ -217,8 +249,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
var index = filePath.IndexOf(DataLocation.PluginEnvironments);
|
var index = filePath.IndexOf(DataLocation.PluginEnvironments);
|
||||||
|
|
||||||
// get the substring after "Environments" because we can not determine it dynamically
|
// get the substring after "Environments" because we can not determine it dynamically
|
||||||
var ExecutablePathSubstring = filePath.Substring(index + DataLocation.PluginEnvironments.Count());
|
var executablePathSubstring = filePath[(index + DataLocation.PluginEnvironments.Length)..];
|
||||||
return $"{DataLocation.PluginEnvironmentsPath}{ExecutablePathSubstring}";
|
return $"{DataLocation.PluginEnvironmentsPath}{executablePathSubstring}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using Droplex;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Droplex;
|
||||||
using Flow.Launcher.Core.Plugin;
|
using Flow.Launcher.Core.Plugin;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using Flow.Launcher.Plugin.SharedCommands;
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
{
|
{
|
||||||
|
|
@ -22,7 +22,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
|
|
||||||
internal override string FileDialogFilter => "Python|pythonw.exe";
|
internal override string FileDialogFilter => "Python|pythonw.exe";
|
||||||
|
|
||||||
internal override string PluginsSettingsFilePath { get => PluginSettings.PythonExecutablePath; set => PluginSettings.PythonExecutablePath = value; }
|
internal override string PluginsSettingsFilePath
|
||||||
|
{
|
||||||
|
get => PluginSettings.PythonExecutablePath;
|
||||||
|
set => PluginSettings.PythonExecutablePath = value;
|
||||||
|
}
|
||||||
|
|
||||||
internal PythonEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
|
internal PythonEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Droplex;
|
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
|
||||||
using Flow.Launcher.Plugin.SharedCommands;
|
|
||||||
using Flow.Launcher.Plugin;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Droplex;
|
||||||
using Flow.Launcher.Core.Plugin;
|
using Flow.Launcher.Core.Plugin;
|
||||||
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
{
|
{
|
||||||
|
|
@ -19,7 +19,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
internal override string InstallPath => Path.Combine(EnvPath, "Node-v16.18.0");
|
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 ExecutablePath => Path.Combine(InstallPath, "node-v16.18.0-win-x64\\node.exe");
|
||||||
|
|
||||||
internal override string PluginsSettingsFilePath { get => PluginSettings.NodeExecutablePath; set => PluginSettings.NodeExecutablePath = value; }
|
internal override string PluginsSettingsFilePath
|
||||||
|
{
|
||||||
|
get => PluginSettings.NodeExecutablePath;
|
||||||
|
set => PluginSettings.NodeExecutablePath = value;
|
||||||
|
}
|
||||||
|
|
||||||
internal TypeScriptEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
|
internal TypeScriptEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Droplex;
|
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
|
||||||
using Flow.Launcher.Plugin.SharedCommands;
|
|
||||||
using Flow.Launcher.Plugin;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Droplex;
|
||||||
using Flow.Launcher.Core.Plugin;
|
using Flow.Launcher.Core.Plugin;
|
||||||
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
{
|
{
|
||||||
|
|
@ -19,7 +19,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
internal override string InstallPath => Path.Combine(EnvPath, "Node-v16.18.0");
|
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 ExecutablePath => Path.Combine(InstallPath, "node-v16.18.0-win-x64\\node.exe");
|
||||||
|
|
||||||
internal override string PluginsSettingsFilePath { get => PluginSettings.NodeExecutablePath; set => PluginSettings.NodeExecutablePath = value; }
|
internal override string PluginsSettingsFilePath
|
||||||
|
{
|
||||||
|
get => PluginSettings.NodeExecutablePath;
|
||||||
|
set => PluginSettings.NodeExecutablePath = value;
|
||||||
|
}
|
||||||
|
|
||||||
internal TypeScriptV2Environment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
|
internal TypeScriptV2Environment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
protected ConcurrentDictionary<string, object?> Settings { get; set; } = null!;
|
protected ConcurrentDictionary<string, object?> Settings { get; set; } = null!;
|
||||||
public required IPublicAPI API { get; init; }
|
public required IPublicAPI API { get; init; }
|
||||||
|
|
||||||
|
private static readonly string ClassName = nameof(JsonRPCPluginSettings);
|
||||||
|
|
||||||
private JsonStorage<ConcurrentDictionary<string, object?>> _storage = null!;
|
private JsonStorage<ConcurrentDictionary<string, object?>> _storage = null!;
|
||||||
|
|
||||||
private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin");
|
private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin");
|
||||||
|
|
@ -122,12 +124,26 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
public async Task SaveAsync()
|
public async Task SaveAsync()
|
||||||
{
|
{
|
||||||
await _storage.SaveAsync();
|
try
|
||||||
|
{
|
||||||
|
await _storage.SaveAsync();
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
API.LogException(ClassName, $"Failed to save plugin settings to path: {SettingPath}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
{
|
{
|
||||||
_storage.Save();
|
try
|
||||||
|
{
|
||||||
|
_storage.Save();
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
API.LogException(ClassName, $"Failed to save plugin settings to path: {SettingPath}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool NeedCreateSettingPanel()
|
public bool NeedCreateSettingPanel()
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
using Flow.Launcher.Core.ExternalPlugins;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
using Flow.Launcher.Core.ExternalPlugins;
|
||||||
using Flow.Launcher.Infrastructure;
|
using Flow.Launcher.Infrastructure;
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using ISavable = Flow.Launcher.Plugin.ISavable;
|
|
||||||
using Flow.Launcher.Plugin.SharedCommands;
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
using System.Text.Json;
|
using ISavable = Flow.Launcher.Plugin.ISavable;
|
||||||
using Flow.Launcher.Core.Resource;
|
|
||||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.Plugin
|
namespace Flow.Launcher.Core.Plugin
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@ using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using JetBrains.Annotations;
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
using Squirrel;
|
|
||||||
using Flow.Launcher.Core.Resource;
|
using Flow.Launcher.Core.Resource;
|
||||||
using Flow.Launcher.Plugin.SharedCommands;
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
using Flow.Launcher.Infrastructure;
|
using Flow.Launcher.Infrastructure;
|
||||||
|
|
@ -15,8 +17,8 @@ using Flow.Launcher.Infrastructure.Http;
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using System.Text.Json.Serialization;
|
using JetBrains.Annotations;
|
||||||
using System.Threading;
|
using Squirrel;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core
|
namespace Flow.Launcher.Core
|
||||||
{
|
{
|
||||||
|
|
@ -51,7 +53,7 @@ namespace Flow.Launcher.Core
|
||||||
var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
|
var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
|
||||||
var currentVersion = Version.Parse(Constant.Version);
|
var currentVersion = Version.Parse(Constant.Version);
|
||||||
|
|
||||||
Log.Info($"|Updater.UpdateApp|Future Release <{newUpdateInfo.FutureReleaseEntry.Formatted()}>");
|
Log.Info($"|Updater.UpdateApp|Future Release <{Formatted(newUpdateInfo.FutureReleaseEntry)}>");
|
||||||
|
|
||||||
if (newReleaseVersion <= currentVersion)
|
if (newReleaseVersion <= currentVersion)
|
||||||
{
|
{
|
||||||
|
|
@ -70,7 +72,7 @@ namespace Flow.Launcher.Core
|
||||||
|
|
||||||
if (DataLocation.PortableDataLocationInUse())
|
if (DataLocation.PortableDataLocationInUse())
|
||||||
{
|
{
|
||||||
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
|
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion}\\{DataLocation.PortableFolderName}";
|
||||||
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s));
|
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s));
|
||||||
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)))
|
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)))
|
||||||
_api.ShowMsgBox(string.Format(_api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
|
_api.ShowMsgBox(string.Format(_api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
|
||||||
|
|
@ -122,7 +124,7 @@ namespace Flow.Launcher.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/Squirrel/Squirrel.Windows/blob/master/src/Squirrel/UpdateManager.Factory.cs
|
// https://github.com/Squirrel/Squirrel.Windows/blob/master/src/Squirrel/UpdateManager.Factory.cs
|
||||||
private async Task<UpdateManager> GitHubUpdateManagerAsync(string repository)
|
private static async Task<UpdateManager> GitHubUpdateManagerAsync(string repository)
|
||||||
{
|
{
|
||||||
var uri = new Uri(repository);
|
var uri = new Uri(repository);
|
||||||
var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases";
|
var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases";
|
||||||
|
|
@ -144,12 +146,22 @@ namespace Flow.Launcher.Core
|
||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string NewVersionTips(string version)
|
private static string NewVersionTips(string version)
|
||||||
{
|
{
|
||||||
var translator = InternationalizationManager.Instance;
|
var translator = Ioc.Default.GetRequiredService<Internationalization>();
|
||||||
var tips = string.Format(translator.GetTranslation("newVersionTips"), version);
|
var tips = string.Format(translator.GetTranslation("newVersionTips"), version);
|
||||||
|
|
||||||
return tips;
|
return tips;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string Formatted<T>(T t)
|
||||||
|
{
|
||||||
|
var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = true
|
||||||
|
});
|
||||||
|
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,11 @@
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Flow.Launcher.Infrastructure
|
namespace Flow.Launcher.Infrastructure
|
||||||
{
|
{
|
||||||
public static class Helper
|
public static class Helper
|
||||||
{
|
{
|
||||||
static Helper()
|
|
||||||
{
|
|
||||||
jsonFormattedSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy
|
/// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -36,55 +28,5 @@ namespace Flow.Launcher.Infrastructure
|
||||||
throw new NullReferenceException();
|
throw new NullReferenceException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ValidateDataDirectory(string bundledDataDirectory, string dataDirectory)
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(dataDirectory))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(dataDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var bundledDataPath in Directory.GetFiles(bundledDataDirectory))
|
|
||||||
{
|
|
||||||
var data = Path.GetFileName(bundledDataPath);
|
|
||||||
var dataPath = Path.Combine(dataDirectory, data.NonNull());
|
|
||||||
if (!File.Exists(dataPath))
|
|
||||||
{
|
|
||||||
File.Copy(bundledDataPath, dataPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var time1 = new FileInfo(bundledDataPath).LastWriteTimeUtc;
|
|
||||||
var time2 = new FileInfo(dataPath).LastWriteTimeUtc;
|
|
||||||
if (time1 != time2)
|
|
||||||
{
|
|
||||||
File.Copy(bundledDataPath, dataPath, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ValidateDirectory(string path)
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(path))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly JsonSerializerOptions jsonFormattedSerializerOptions = new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
WriteIndented = true
|
|
||||||
};
|
|
||||||
|
|
||||||
public static string Formatted<T>(this T t)
|
|
||||||
{
|
|
||||||
var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
WriteIndented = true
|
|
||||||
});
|
|
||||||
|
|
||||||
return formatted;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Save()
|
public static async Task SaveAsync()
|
||||||
{
|
{
|
||||||
await storageLock.WaitAsync();
|
await storageLock.WaitAsync();
|
||||||
|
|
||||||
|
|
@ -72,12 +72,22 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
.Select(x => x.Key)
|
.Select(x => x.Key)
|
||||||
.ToList());
|
.ToList());
|
||||||
}
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
Log.Exception($"|ImageLoader.SaveAsync|Failed to save image cache to file", e);
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
storageLock.Release();
|
storageLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task WaitSaveAsync()
|
||||||
|
{
|
||||||
|
await storageLock.WaitAsync();
|
||||||
|
storageLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task<List<(string, bool)>> LoadStorageToConcurrentDictionaryAsync()
|
private static async Task<List<(string, bool)>> LoadStorageToConcurrentDictionaryAsync()
|
||||||
{
|
{
|
||||||
await storageLock.WaitAsync();
|
await storageLock.WaitAsync();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
using MemoryPack;
|
using MemoryPack;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
@ -33,7 +34,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
||||||
public BinaryStorage(string filename)
|
public BinaryStorage(string filename)
|
||||||
{
|
{
|
||||||
DirectoryPath = DataLocation.CacheDirectory;
|
DirectoryPath = DataLocation.CacheDirectory;
|
||||||
Helper.ValidateDirectory(DirectoryPath);
|
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||||
|
|
||||||
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
|
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,51 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
|
|
||||||
namespace Flow.Launcher.Infrastructure.Storage
|
namespace Flow.Launcher.Infrastructure.Storage
|
||||||
{
|
{
|
||||||
public class FlowLauncherJsonStorage<T> : JsonStorage<T> where T : new()
|
public class FlowLauncherJsonStorage<T> : JsonStorage<T> where T : new()
|
||||||
{
|
{
|
||||||
|
private static readonly string ClassName = "FlowLauncherJsonStorage";
|
||||||
|
|
||||||
|
// We should not initialize API in static constructor because it will create another API instance
|
||||||
|
private static IPublicAPI api = null;
|
||||||
|
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||||
|
|
||||||
public FlowLauncherJsonStorage()
|
public FlowLauncherJsonStorage()
|
||||||
{
|
{
|
||||||
var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
|
var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
|
||||||
Helper.ValidateDirectory(directoryPath);
|
FilesFolders.ValidateDirectory(directoryPath);
|
||||||
|
|
||||||
var filename = typeof(T).Name;
|
var filename = typeof(T).Name;
|
||||||
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
|
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public new void Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
base.Save();
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
API.LogException(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public new async Task SaveAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await base.SaveAsync();
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
API.LogException(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ using System.IO;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
|
|
||||||
namespace Flow.Launcher.Infrastructure.Storage
|
namespace Flow.Launcher.Infrastructure.Storage
|
||||||
{
|
{
|
||||||
|
|
@ -37,7 +38,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
||||||
FilePath = filePath;
|
FilePath = filePath;
|
||||||
DirectoryPath = Path.GetDirectoryName(filePath) ?? throw new ArgumentException("Invalid file path");
|
DirectoryPath = Path.GetDirectoryName(filePath) ?? throw new ArgumentException("Invalid file path");
|
||||||
|
|
||||||
Helper.ValidateDirectory(DirectoryPath);
|
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<T> LoadAsync()
|
public async Task<T> LoadAsync()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
|
|
||||||
namespace Flow.Launcher.Infrastructure.Storage
|
namespace Flow.Launcher.Infrastructure.Storage
|
||||||
{
|
{
|
||||||
|
|
@ -8,14 +12,49 @@ namespace Flow.Launcher.Infrastructure.Storage
|
||||||
// Use assembly name to check which plugin is using this storage
|
// Use assembly name to check which plugin is using this storage
|
||||||
public readonly string AssemblyName;
|
public readonly string AssemblyName;
|
||||||
|
|
||||||
|
private static readonly string ClassName = "PluginJsonStorage";
|
||||||
|
|
||||||
|
// We should not initialize API in static constructor because it will create another API instance
|
||||||
|
private static IPublicAPI api = null;
|
||||||
|
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||||
|
|
||||||
public PluginJsonStorage()
|
public PluginJsonStorage()
|
||||||
{
|
{
|
||||||
var dataType = typeof(T);
|
var dataType = typeof(T);
|
||||||
AssemblyName = dataType.Assembly.GetName().Name;
|
AssemblyName = dataType.Assembly.GetName().Name;
|
||||||
DirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, AssemblyName);
|
DirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, AssemblyName);
|
||||||
Helper.ValidateDirectory(DirectoryPath);
|
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||||
|
|
||||||
FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}");
|
FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PluginJsonStorage(T data) : this()
|
||||||
|
{
|
||||||
|
Data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
base.Save();
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
API.LogException(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public new async Task SaveAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await base.SaveAsync();
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
API.LogException(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -488,5 +488,16 @@ namespace Flow.Launcher.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Notification
|
||||||
|
|
||||||
|
public static bool IsNotificationSupported()
|
||||||
|
{
|
||||||
|
// Notifications only supported on Windows 10 19041+
|
||||||
|
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
|
||||||
|
Environment.OSVersion.Version.Build >= 19041;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -318,5 +318,51 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
||||||
{
|
{
|
||||||
return path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
return path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates a directory, creating it if it doesn't exist
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
public static void ValidateDirectory(string path)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(path))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates a data directory, synchronizing it by ensuring all files from a bundled source directory exist in it.
|
||||||
|
/// If files are missing or outdated, they are copied from the bundled directory to the data directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bundledDataDirectory"></param>
|
||||||
|
/// <param name="dataDirectory"></param>
|
||||||
|
public static void ValidateDataDirectory(string bundledDataDirectory, string dataDirectory)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(dataDirectory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dataDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var bundledDataPath in Directory.GetFiles(bundledDataDirectory))
|
||||||
|
{
|
||||||
|
var data = Path.GetFileName(bundledDataPath);
|
||||||
|
if (data == null) continue;
|
||||||
|
var dataPath = Path.Combine(dataDirectory, data);
|
||||||
|
if (!File.Exists(dataPath))
|
||||||
|
{
|
||||||
|
File.Copy(bundledDataPath, dataPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var time1 = new FileInfo(bundledDataPath).LastWriteTimeUtc;
|
||||||
|
var time2 = new FileInfo(dataPath).LastWriteTimeUtc;
|
||||||
|
if (time1 != time2)
|
||||||
|
{
|
||||||
|
File.Copy(bundledDataPath, dataPath, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,11 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
|
await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
|
||||||
{
|
{
|
||||||
|
// Because new message box api uses MessageBoxEx window,
|
||||||
|
// if it is created and closed before main window is created, it will cause the application to exit.
|
||||||
|
// So set to OnExplicitShutdown to prevent the application from shutting down before main window is created
|
||||||
|
Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
||||||
|
|
||||||
Log.SetLogLevel(_settings.LogLevel);
|
Log.SetLogLevel(_settings.LogLevel);
|
||||||
|
|
||||||
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
|
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
|
||||||
|
|
@ -299,6 +304,14 @@ namespace Flow.Launcher
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we call Environment.Exit(0), the application dispose will be called before _mainWindow.Close()
|
||||||
|
// Accessing _mainWindow?.Dispatcher will cause the application stuck
|
||||||
|
// So here we need to check it and just return so that we will not acees _mainWindow?.Dispatcher
|
||||||
|
if (!_mainWindow.CanClose)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,11 @@
|
||||||
Click no if it's already installed, and you will be prompted to select the folder that contains the {1} executable
|
Click no if it's already installed, and you will be prompted to select the folder that contains the {1} executable
|
||||||
</system:String>
|
</system:String>
|
||||||
<system:String x:Key="runtimePluginChooseRuntimeExecutable">Please select the {0} executable</system:String>
|
<system:String x:Key="runtimePluginChooseRuntimeExecutable">Please select the {0} executable</system:String>
|
||||||
|
<system:String x:Key="runtimeExecutableInvalidChooseDownload">
|
||||||
|
Your selected {0} executable is invalid.
|
||||||
|
{2}{2}
|
||||||
|
Click yes if you would like select the {0} executable agian. Click no if you would like to download {1}
|
||||||
|
</system:String>
|
||||||
<system:String x:Key="runtimePluginUnableToSetExecutablePath">Unable to set {0} executable path, please try from Flow's settings (scroll down to the bottom).</system:String>
|
<system:String x:Key="runtimePluginUnableToSetExecutablePath">Unable to set {0} executable path, please try from Flow's settings (scroll down to the bottom).</system:String>
|
||||||
<system:String x:Key="failedToInitializePluginsTitle">Fail to Init Plugins</system:String>
|
<system:String x:Key="failedToInitializePluginsTitle">Fail to Init Plugins</system:String>
|
||||||
<system:String x:Key="failedToInitializePluginsMessage">Plugins: {0} - fail to load and would be disabled, please contact plugin creator for help</system:String>
|
<system:String x:Key="failedToInitializePluginsMessage">Plugins: {0} - fail to load and would be disabled, please contact plugin creator for help</system:String>
|
||||||
|
|
@ -320,6 +325,9 @@
|
||||||
<system:String x:Key="logfolder">Log Folder</system:String>
|
<system:String x:Key="logfolder">Log Folder</system:String>
|
||||||
<system:String x:Key="clearlogfolder">Clear Logs</system:String>
|
<system:String x:Key="clearlogfolder">Clear Logs</system:String>
|
||||||
<system:String x:Key="clearlogfolderMessage">Are you sure you want to delete all logs?</system:String>
|
<system:String x:Key="clearlogfolderMessage">Are you sure you want to delete all logs?</system:String>
|
||||||
|
<system:String x:Key="clearcachefolder">Clear Caches</system:String>
|
||||||
|
<system:String x:Key="clearcachefolderMessage">Are you sure you want to delete all caches?</system:String>
|
||||||
|
<system:String x:Key="clearfolderfailMessage">Failed to clear part of folders and files. Please see log file for more information</system:String>
|
||||||
<system:String x:Key="welcomewindow">Wizard</system:String>
|
<system:String x:Key="welcomewindow">Wizard</system:String>
|
||||||
<system:String x:Key="userdatapath">User Data Location</system:String>
|
<system:String x:Key="userdatapath">User Data Location</system:String>
|
||||||
<system:String x:Key="userdatapathToolTip">User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not.</system:String>
|
<system:String x:Key="userdatapathToolTip">User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not.</system:String>
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,13 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
public partial class MainWindow : IDisposable
|
public partial class MainWindow : IDisposable
|
||||||
{
|
{
|
||||||
|
#region Public Property
|
||||||
|
|
||||||
|
// Window Event: Close Event
|
||||||
|
public bool CanClose { get; set; } = false;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Private Fields
|
#region Private Fields
|
||||||
|
|
||||||
// Dependency Injection
|
// Dependency Injection
|
||||||
|
|
@ -45,8 +52,6 @@ namespace Flow.Launcher
|
||||||
private readonly ContextMenu _contextMenu = new();
|
private readonly ContextMenu _contextMenu = new();
|
||||||
private readonly MainViewModel _viewModel;
|
private readonly MainViewModel _viewModel;
|
||||||
|
|
||||||
// Window Event: Close Event
|
|
||||||
private bool _canClose = false;
|
|
||||||
// Window Event: Key Event
|
// Window Event: Key Event
|
||||||
private bool _isArrowKeyPressed = false;
|
private bool _isArrowKeyPressed = false;
|
||||||
|
|
||||||
|
|
@ -279,7 +284,7 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
private async void OnClosing(object sender, CancelEventArgs e)
|
private async void OnClosing(object sender, CancelEventArgs e)
|
||||||
{
|
{
|
||||||
if (!_canClose)
|
if (!CanClose)
|
||||||
{
|
{
|
||||||
_notifyIcon.Visible = false;
|
_notifyIcon.Visible = false;
|
||||||
App.API.SaveAppAllSettings();
|
App.API.SaveAppAllSettings();
|
||||||
|
|
@ -287,7 +292,7 @@ namespace Flow.Launcher
|
||||||
await PluginManager.DisposePluginsAsync();
|
await PluginManager.DisposePluginsAsync();
|
||||||
Notification.Uninstall();
|
Notification.Uninstall();
|
||||||
// After plugins are all disposed, we can close the main window
|
// After plugins are all disposed, we can close the main window
|
||||||
_canClose = true;
|
CanClose = true;
|
||||||
// Use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event
|
// Use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event
|
||||||
Application.Current.Shutdown();
|
Application.Current.Shutdown();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
internal static class Notification
|
internal static class Notification
|
||||||
{
|
{
|
||||||
internal static bool legacy = Environment.OSVersion.Version.Build < 19041;
|
internal static bool legacy = !Win32Helper.IsNotificationSupported();
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
|
|
||||||
internal static void Uninstall()
|
internal static void Uninstall()
|
||||||
{
|
{
|
||||||
if (!legacy)
|
if (!legacy)
|
||||||
|
|
@ -25,7 +25,6 @@ namespace Flow.Launcher
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
|
|
||||||
private static void ShowInternal(string title, string subTitle, string iconPath = null)
|
private static void ShowInternal(string title, string subTitle, string iconPath = null)
|
||||||
{
|
{
|
||||||
// Handle notification for win7/8/early win10
|
// Handle notification for win7/8/early win10
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ namespace Flow.Launcher
|
||||||
private readonly Internationalization _translater;
|
private readonly Internationalization _translater;
|
||||||
private readonly MainViewModel _mainVM;
|
private readonly MainViewModel _mainVM;
|
||||||
|
|
||||||
|
private readonly object _saveSettingsLock = new();
|
||||||
|
|
||||||
#region Constructor
|
#region Constructor
|
||||||
|
|
||||||
public PublicAPIInstance(Settings settings, Internationalization translater, MainViewModel mainVM)
|
public PublicAPIInstance(Settings settings, Internationalization translater, MainViewModel mainVM)
|
||||||
|
|
@ -57,21 +59,28 @@ namespace Flow.Launcher
|
||||||
_mainVM.ChangeQueryText(query, requery);
|
_mainVM.ChangeQueryText(query, requery);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RestartApp()
|
#pragma warning disable VSTHRD100 // Avoid async void methods
|
||||||
|
|
||||||
|
public async void RestartApp()
|
||||||
{
|
{
|
||||||
_mainVM.Hide();
|
_mainVM.Hide();
|
||||||
|
|
||||||
// we must manually save
|
// We must manually save
|
||||||
// UpdateManager.RestartApp() will call Environment.Exit(0)
|
// UpdateManager.RestartApp() will call Environment.Exit(0)
|
||||||
// which will cause ungraceful exit
|
// which will cause ungraceful exit
|
||||||
SaveAppAllSettings();
|
SaveAppAllSettings();
|
||||||
|
|
||||||
|
// Wait for all image caches to be saved before restarting
|
||||||
|
await ImageLoader.WaitSaveAsync();
|
||||||
|
|
||||||
// Restart requires Squirrel's Update.exe to be present in the parent folder,
|
// Restart requires Squirrel's Update.exe to be present in the parent folder,
|
||||||
// it is only published from the project's release pipeline. When debugging without it,
|
// it is only published from the project's release pipeline. When debugging without it,
|
||||||
// the project may not restart or just terminates. This is expected.
|
// the project may not restart or just terminates. This is expected.
|
||||||
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning restore VSTHRD100 // Avoid async void methods
|
||||||
|
|
||||||
public void ShowMainWindow() => _mainVM.Show();
|
public void ShowMainWindow() => _mainVM.Show();
|
||||||
|
|
||||||
public void HideMainWindow() => _mainVM.Hide();
|
public void HideMainWindow() => _mainVM.Hide();
|
||||||
|
|
@ -85,10 +94,13 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
public void SaveAppAllSettings()
|
public void SaveAppAllSettings()
|
||||||
{
|
{
|
||||||
PluginManager.Save();
|
lock (_saveSettingsLock)
|
||||||
_mainVM.Save();
|
{
|
||||||
_settings.Save();
|
_settings.Save();
|
||||||
_ = ImageLoader.Save();
|
PluginManager.Save();
|
||||||
|
_mainVM.Save();
|
||||||
|
}
|
||||||
|
_ = ImageLoader.SaveAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync();
|
public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync();
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Flow.Launcher.Core;
|
using Flow.Launcher.Core;
|
||||||
using Flow.Launcher.Core.Resource;
|
|
||||||
using Flow.Launcher.Infrastructure;
|
using Flow.Launcher.Infrastructure;
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
|
|
@ -16,6 +15,8 @@ namespace Flow.Launcher.SettingPages.ViewModels;
|
||||||
|
|
||||||
public partial class SettingsPaneAboutViewModel : BaseModel
|
public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
{
|
{
|
||||||
|
private static readonly string ClassName = nameof(SettingsPaneAboutViewModel);
|
||||||
|
|
||||||
private readonly Settings _settings;
|
private readonly Settings _settings;
|
||||||
private readonly Updater _updater;
|
private readonly Updater _updater;
|
||||||
|
|
||||||
|
|
@ -24,7 +25,16 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var size = GetLogFiles().Sum(file => file.Length);
|
var size = GetLogFiles().Sum(file => file.Length);
|
||||||
return $"{InternationalizationManager.Instance.GetTranslation("clearlogfolder")} ({BytesToReadableString(size)})";
|
return $"{App.API.GetTranslation("clearlogfolder")} ({BytesToReadableString(size)})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CacheFolderSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var size = GetCacheFiles().Sum(file => file.Length);
|
||||||
|
return $"{App.API.GetTranslation("clearcachefolder")} ({BytesToReadableString(size)})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,7 +52,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
};
|
};
|
||||||
|
|
||||||
public string ActivatedTimes => string.Format(
|
public string ActivatedTimes => string.Format(
|
||||||
InternationalizationManager.Instance.GetTranslation("about_activate_times"),
|
App.API.GetTranslation("about_activate_times"),
|
||||||
_settings.ActivateTimes
|
_settings.ActivateTimes
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -88,14 +98,35 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
private void AskClearLogFolderConfirmation()
|
private void AskClearLogFolderConfirmation()
|
||||||
{
|
{
|
||||||
var confirmResult = App.API.ShowMsgBox(
|
var confirmResult = App.API.ShowMsgBox(
|
||||||
InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"),
|
App.API.GetTranslation("clearlogfolderMessage"),
|
||||||
InternationalizationManager.Instance.GetTranslation("clearlogfolder"),
|
App.API.GetTranslation("clearlogfolder"),
|
||||||
MessageBoxButton.YesNo
|
MessageBoxButton.YesNo
|
||||||
);
|
);
|
||||||
|
|
||||||
if (confirmResult == MessageBoxResult.Yes)
|
if (confirmResult == MessageBoxResult.Yes)
|
||||||
{
|
{
|
||||||
ClearLogFolder();
|
if (!ClearLogFolder())
|
||||||
|
{
|
||||||
|
App.API.ShowMsgBox(App.API.GetTranslation("clearfolderfailMessage"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void AskClearCacheFolderConfirmation()
|
||||||
|
{
|
||||||
|
var confirmResult = App.API.ShowMsgBox(
|
||||||
|
App.API.GetTranslation("clearcachefolderMessage"),
|
||||||
|
App.API.GetTranslation("clearcachefolder"),
|
||||||
|
MessageBoxButton.YesNo
|
||||||
|
);
|
||||||
|
|
||||||
|
if (confirmResult == MessageBoxResult.Yes)
|
||||||
|
{
|
||||||
|
if (!ClearCacheFolder())
|
||||||
|
{
|
||||||
|
App.API.ShowMsgBox(App.API.GetTranslation("clearfolderfailMessage"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +144,6 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
App.API.OpenDirectory(parentFolderPath);
|
App.API.OpenDirectory(parentFolderPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void OpenLogsFolder()
|
private void OpenLogsFolder()
|
||||||
{
|
{
|
||||||
|
|
@ -121,21 +151,47 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private Task UpdateApp() => _updater.UpdateAppAsync(false);
|
private Task UpdateAppAsync() => _updater.UpdateAppAsync(false);
|
||||||
|
|
||||||
private void ClearLogFolder()
|
private bool ClearLogFolder()
|
||||||
{
|
{
|
||||||
|
var success = true;
|
||||||
var logDirectory = GetLogDir();
|
var logDirectory = GetLogDir();
|
||||||
var logFiles = GetLogFiles();
|
var logFiles = GetLogFiles();
|
||||||
|
|
||||||
logFiles.ForEach(f => f.Delete());
|
logFiles.ForEach(f =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
f.Delete();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
App.API.LogException(ClassName, $"Failed to delete log file: {f.Name}", e);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
logDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
|
logDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
|
||||||
|
// Do not clean log files of current version
|
||||||
.Where(dir => !Constant.Version.Equals(dir.Name))
|
.Where(dir => !Constant.Version.Equals(dir.Name))
|
||||||
.ToList()
|
.ToList()
|
||||||
.ForEach(dir => dir.Delete());
|
.ForEach(dir =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
dir.Delete(true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
App.API.LogException(ClassName, $"Failed to delete log directory: {dir.Name}", e);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
OnPropertyChanged(nameof(LogFolderSize));
|
OnPropertyChanged(nameof(LogFolderSize));
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DirectoryInfo GetLogDir(string version = "")
|
private static DirectoryInfo GetLogDir(string version = "")
|
||||||
|
|
@ -148,6 +204,55 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
return GetLogDir(version).EnumerateFiles("*", SearchOption.AllDirectories).ToList();
|
return GetLogDir(version).EnumerateFiles("*", SearchOption.AllDirectories).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool ClearCacheFolder()
|
||||||
|
{
|
||||||
|
var success = true;
|
||||||
|
var cacheDirectory = GetCacheDir();
|
||||||
|
var cacheFiles = GetCacheFiles();
|
||||||
|
|
||||||
|
cacheFiles.ForEach(f =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
f.Delete();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
App.API.LogException(ClassName, $"Failed to delete cache file: {f.Name}", e);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
|
||||||
|
.ToList()
|
||||||
|
.ForEach(dir =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
dir.Delete(true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(CacheFolderSize));
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DirectoryInfo GetCacheDir()
|
||||||
|
{
|
||||||
|
return new DirectoryInfo(DataLocation.CacheDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<FileInfo> GetCacheFiles()
|
||||||
|
{
|
||||||
|
return GetCacheDir().EnumerateFiles("*", SearchOption.AllDirectories).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private static string BytesToReadableString(long bytes)
|
private static string BytesToReadableString(long bytes)
|
||||||
{
|
{
|
||||||
const int scale = 1024;
|
const int scale = 1024;
|
||||||
|
|
@ -156,8 +261,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
|
|
||||||
foreach (string order in orders)
|
foreach (string order in orders)
|
||||||
{
|
{
|
||||||
if (bytes > max)
|
if (bytes > max) return $"{decimal.Divide(bytes, max):##.##} {order}";
|
||||||
return $"{decimal.Divide(bytes, max):##.##} {order}";
|
|
||||||
|
|
||||||
max /= scale;
|
max /= scale;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,10 @@
|
||||||
Margin="0 12 0 0"
|
Margin="0 12 0 0"
|
||||||
Icon="">
|
Icon="">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
Margin="0 0 12 0"
|
||||||
|
Command="{Binding AskClearCacheFolderConfirmationCommand}"
|
||||||
|
Content="{Binding CacheFolderSize, Mode=OneWay}" />
|
||||||
<Button
|
<Button
|
||||||
Margin="0 0 12 0"
|
Margin="0 0 12 0"
|
||||||
Command="{Binding AskClearLogFolderConfirmationCommand}"
|
Command="{Binding AskClearLogFolderConfirmationCommand}"
|
||||||
|
|
|
||||||
|
|
@ -28,16 +28,16 @@
|
||||||
Style="{StaticResource PageTitle}"
|
Style="{StaticResource PageTitle}"
|
||||||
Text="{DynamicResource general}"
|
Text="{DynamicResource general}"
|
||||||
TextAlignment="left" />
|
TextAlignment="left" />
|
||||||
|
<cc:ExCard
|
||||||
<cc:CardGroup Margin="0 10 0 0">
|
Title="{DynamicResource startFlowLauncherOnSystemStartup}"
|
||||||
|
Margin="0 8 0 0"
|
||||||
<cc:Card Title="{DynamicResource startFlowLauncherOnSystemStartup}" Icon="">
|
Icon="">
|
||||||
|
<cc:ExCard.SideContent>
|
||||||
<ui:ToggleSwitch
|
<ui:ToggleSwitch
|
||||||
IsOn="{Binding StartFlowLauncherOnSystemStartup}"
|
IsOn="{Binding StartFlowLauncherOnSystemStartup}"
|
||||||
OffContent="{DynamicResource disable}"
|
OffContent="{DynamicResource disable}"
|
||||||
OnContent="{DynamicResource enable}" />
|
OnContent="{DynamicResource enable}" />
|
||||||
</cc:Card>
|
</cc:ExCard.SideContent>
|
||||||
|
|
||||||
<cc:Card
|
<cc:Card
|
||||||
Title="{DynamicResource useLogonTaskForStartup}"
|
Title="{DynamicResource useLogonTaskForStartup}"
|
||||||
Sub="{DynamicResource useLogonTaskForStartupTooltip}"
|
Sub="{DynamicResource useLogonTaskForStartupTooltip}"
|
||||||
|
|
@ -47,17 +47,16 @@
|
||||||
OffContent="{DynamicResource disable}"
|
OffContent="{DynamicResource disable}"
|
||||||
OnContent="{DynamicResource enable}" />
|
OnContent="{DynamicResource enable}" />
|
||||||
</cc:Card>
|
</cc:Card>
|
||||||
|
</cc:ExCard>
|
||||||
<cc:Card
|
<cc:Card
|
||||||
Title="{DynamicResource hideOnStartup}"
|
Title="{DynamicResource hideOnStartup}"
|
||||||
Sub="{DynamicResource hideOnStartupToolTip}"
|
Icon=""
|
||||||
Type="InsideFit">
|
Sub="{DynamicResource hideOnStartupToolTip}">
|
||||||
<ui:ToggleSwitch
|
<ui:ToggleSwitch
|
||||||
IsOn="{Binding Settings.HideOnStartup}"
|
IsOn="{Binding Settings.HideOnStartup}"
|
||||||
OffContent="{DynamicResource disable}"
|
OffContent="{DynamicResource disable}"
|
||||||
OnContent="{DynamicResource enable}" />
|
OnContent="{DynamicResource enable}" />
|
||||||
</cc:Card>
|
</cc:Card>
|
||||||
</cc:CardGroup>
|
|
||||||
|
|
||||||
<cc:Card Title="{DynamicResource hideFlowLauncherWhenLoseFocus}" Margin="0 14 0 0">
|
<cc:Card Title="{DynamicResource hideFlowLauncherWhenLoseFocus}" Margin="0 14 0 0">
|
||||||
<ui:ToggleSwitch
|
<ui:ToggleSwitch
|
||||||
|
|
@ -178,30 +177,6 @@
|
||||||
OnContent="{DynamicResource enable}" />
|
OnContent="{DynamicResource enable}" />
|
||||||
</cc:Card>
|
</cc:Card>
|
||||||
|
|
||||||
<cc:CardGroup Margin="0 14 0 0">
|
|
||||||
<cc:Card
|
|
||||||
Title="{DynamicResource searchDelay}"
|
|
||||||
Icon=""
|
|
||||||
Sub="{DynamicResource searchDelayToolTip}">
|
|
||||||
<ui:ToggleSwitch
|
|
||||||
IsOn="{Binding Settings.SearchQueryResultsWithDelay}"
|
|
||||||
OffContent="{DynamicResource disable}"
|
|
||||||
OnContent="{DynamicResource enable}" />
|
|
||||||
</cc:Card>
|
|
||||||
|
|
||||||
<cc:Card
|
|
||||||
Title="{DynamicResource searchDelayTime}"
|
|
||||||
Icon=""
|
|
||||||
Sub="{DynamicResource searchDelayTimeToolTip}">
|
|
||||||
<ComboBox
|
|
||||||
MaxWidth="200"
|
|
||||||
DisplayMemberPath="Display"
|
|
||||||
ItemsSource="{Binding SearchDelayTimes}"
|
|
||||||
SelectedItem="{Binding SearchDelayTime}"
|
|
||||||
SelectedValuePath="Value" />
|
|
||||||
</cc:Card>
|
|
||||||
</cc:CardGroup>
|
|
||||||
|
|
||||||
<cc:CardGroup Margin="0 14 0 0">
|
<cc:CardGroup Margin="0 14 0 0">
|
||||||
<cc:Card Title="{DynamicResource querySearchPrecision}" Sub="{DynamicResource querySearchPrecisionToolTip}">
|
<cc:Card Title="{DynamicResource querySearchPrecision}" Sub="{DynamicResource querySearchPrecisionToolTip}">
|
||||||
<ComboBox
|
<ComboBox
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using Flow.Launcher.Infrastructure;
|
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using Flow.Launcher.Plugin.Program.Programs;
|
using Flow.Launcher.Plugin.Program.Programs;
|
||||||
using Flow.Launcher.Plugin.Program.Views;
|
using Flow.Launcher.Plugin.Program.Views;
|
||||||
using Flow.Launcher.Plugin.Program.Views.Models;
|
using Flow.Launcher.Plugin.Program.Views.Models;
|
||||||
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
|
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
|
||||||
|
|
@ -184,7 +184,7 @@ namespace Flow.Launcher.Plugin.Program
|
||||||
{
|
{
|
||||||
var pluginCachePath = Context.CurrentPluginMetadata.PluginCacheDirectoryPath;
|
var pluginCachePath = Context.CurrentPluginMetadata.PluginCacheDirectoryPath;
|
||||||
|
|
||||||
Helper.ValidateDirectory(pluginCachePath);
|
FilesFolders.ValidateDirectory(pluginCachePath);
|
||||||
|
|
||||||
static void MoveFile(string sourcePath, string destinationPath)
|
static void MoveFile(string sourcePath, string destinationPath)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,13 @@ namespace Flow.Launcher.Plugin.Sys
|
||||||
{
|
{
|
||||||
public const string Keyword = "fltheme";
|
public const string Keyword = "fltheme";
|
||||||
|
|
||||||
private readonly Theme _theme;
|
|
||||||
private readonly PluginInitContext _context;
|
private readonly PluginInitContext _context;
|
||||||
|
|
||||||
|
// Do not initialize it in the constructor, because it will cause null reference in
|
||||||
|
// var dicts = Application.Current.Resources.MergedDictionaries; line of Theme
|
||||||
|
private Theme theme = null;
|
||||||
|
private Theme Theme => theme ??= Ioc.Default.GetRequiredService<Theme>();
|
||||||
|
|
||||||
#region Theme Selection
|
#region Theme Selection
|
||||||
|
|
||||||
// Theme select codes simplified from SettingsPaneThemeViewModel.cs
|
// Theme select codes simplified from SettingsPaneThemeViewModel.cs
|
||||||
|
|
@ -19,24 +23,23 @@ namespace Flow.Launcher.Plugin.Sys
|
||||||
private Theme.ThemeData _selectedTheme;
|
private Theme.ThemeData _selectedTheme;
|
||||||
public Theme.ThemeData SelectedTheme
|
public Theme.ThemeData SelectedTheme
|
||||||
{
|
{
|
||||||
get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == _theme.GetCurrentTheme());
|
get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == Theme.GetCurrentTheme());
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_selectedTheme = value;
|
_selectedTheme = value;
|
||||||
_theme.ChangeTheme(value.FileNameWithoutExtension);
|
Theme.ChangeTheme(value.FileNameWithoutExtension);
|
||||||
|
|
||||||
_ = _theme.RefreshFrameAsync();
|
_ = Theme.RefreshFrameAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Theme.ThemeData> Themes => _theme.LoadAvailableThemes();
|
private List<Theme.ThemeData> Themes => Theme.LoadAvailableThemes();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public ThemeSelector(PluginInitContext context)
|
public ThemeSelector(PluginInitContext context)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_theme = Ioc.Default.GetRequiredService<Theme>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Result> Query(Query query)
|
public List<Result> Query(Query query)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using Flow.Launcher.Infrastructure;
|
|
||||||
using Flow.Launcher.Plugin.SharedCommands;
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
|
|
||||||
namespace Flow.Launcher.Plugin.WebSearch
|
namespace Flow.Launcher.Plugin.WebSearch
|
||||||
|
|
@ -180,7 +179,7 @@ namespace Flow.Launcher.Plugin.WebSearch
|
||||||
|
|
||||||
// Default images directory is in the WebSearch's application folder
|
// Default images directory is in the WebSearch's application folder
|
||||||
DefaultImagesDirectory = Path.Combine(pluginDirectory, Images);
|
DefaultImagesDirectory = Path.Combine(pluginDirectory, Images);
|
||||||
Helper.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory);
|
FilesFolders.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory);
|
||||||
|
|
||||||
// Custom images directory is in the WebSearch's data location folder
|
// Custom images directory is in the WebSearch's data location folder
|
||||||
CustomImagesDirectory = Path.Combine(_context.CurrentPluginMetadata.PluginSettingsDirectoryPath, "CustomIcons");
|
CustomImagesDirectory = Path.Combine(_context.CurrentPluginMetadata.PluginSettingsDirectoryPath, "CustomIcons");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue