diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs
index 069154364..2b570d2c0 100644
--- a/Flow.Launcher.Core/Configuration/Portable.cs
+++ b/Flow.Launcher.Core/Configuration/Portable.cs
@@ -22,7 +22,7 @@ namespace Flow.Launcher.Core.Configuration
/// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish
///
///
- private UpdateManager NewUpdateManager()
+ private static UpdateManager NewUpdateManager()
{
var applicationFolderName = Constant.ApplicationDirectory
.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None)
@@ -81,20 +81,16 @@ namespace Flow.Launcher.Core.Configuration
public void RemoveShortcuts()
{
- using (var portabilityUpdater = NewUpdateManager())
- {
- portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu);
- portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop);
- portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup);
- }
+ using var portabilityUpdater = NewUpdateManager();
+ portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu);
+ portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop);
+ portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup);
}
public void RemoveUninstallerEntry()
{
- using (var portabilityUpdater = NewUpdateManager())
- {
- portabilityUpdater.RemoveUninstallerRegistryEntry();
- }
+ using var portabilityUpdater = NewUpdateManager();
+ portabilityUpdater.RemoveUninstallerRegistryEntry();
}
public void MoveUserDataFolder(string fromLocation, string toLocation)
@@ -110,12 +106,10 @@ namespace Flow.Launcher.Core.Configuration
public void CreateShortcuts()
{
- using (var portabilityUpdater = NewUpdateManager())
- {
- portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu, false);
- portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop, false);
- portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup, false);
- }
+ using var portabilityUpdater = NewUpdateManager();
+ portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu, false);
+ portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop, false);
+ portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup, false);
}
public void CreateUninstallerEntry()
@@ -129,18 +123,14 @@ namespace Flow.Launcher.Core.Configuration
subKey2.SetValue("DisplayIcon", Path.Combine(Constant.ApplicationDirectory, "app.ico"), RegistryValueKind.String);
}
- using (var portabilityUpdater = NewUpdateManager())
- {
- _ = portabilityUpdater.CreateUninstallerRegistryEntry();
- }
+ using var portabilityUpdater = NewUpdateManager();
+ _ = portabilityUpdater.CreateUninstallerRegistryEntry();
}
- internal void IndicateDeletion(string filePathTodelete)
+ private static void IndicateDeletion(string filePathTodelete)
{
var deleteFilePath = Path.Combine(filePathTodelete, DataLocation.DeletionIndicatorFile);
- using (var _ = File.CreateText(deleteFilePath))
- {
- }
+ using var _ = File.CreateText(deleteFilePath);
}
///
diff --git a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs
index 68be746f2..e9713564e 100644
--- a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs
@@ -1,5 +1,6 @@
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Logger;
+using Flow.Launcher.Plugin;
using System;
using System.Collections.Generic;
using System.Net;
diff --git a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginStore.cs b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginStore.cs
index affd7c312..1f23c2f66 100644
--- a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginStore.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginStore.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.ExternalPlugins
{
diff --git a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs
index ac8abcdcc..44d3ef0ff 100644
--- a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs
@@ -1,8 +1,9 @@
-using Flow.Launcher.Infrastructure.Logger;
-using System;
+using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.DependencyInjection;
+using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.ExternalPlugins
{
@@ -17,11 +18,11 @@ namespace Flow.Launcher.Core.ExternalPlugins
private static readonly SemaphoreSlim manifestUpdateLock = new(1);
private static DateTime lastFetchedAt = DateTime.MinValue;
- private static TimeSpan fetchTimeout = TimeSpan.FromMinutes(2);
+ private static readonly TimeSpan fetchTimeout = TimeSpan.FromMinutes(2);
public static List UserPlugins { get; private set; }
- public static async Task UpdateManifestAsync(CancellationToken token = default, bool usePrimaryUrlOnly = false)
+ public static async Task UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default)
{
try
{
@@ -43,7 +44,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
}
catch (Exception e)
{
- Log.Exception($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Http request failed", e);
+ Ioc.Default.GetRequiredService().LogException(nameof(PluginsManifest), "Http request failed", e);
}
finally
{
diff --git a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs
deleted file mode 100644
index 79d6d7605..000000000
--- a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-
-namespace Flow.Launcher.Core.ExternalPlugins
-{
- public record UserPlugin
- {
- public string ID { get; set; }
- public string Name { get; set; }
- public string Description { get; set; }
- public string Author { get; set; }
- public string Version { get; set; }
- public string Language { get; set; }
- public string Website { get; set; }
- public string UrlDownload { get; set; }
- public string UrlSourceCode { get; set; }
- public string LocalInstallPath { get; set; }
- public string IcoPath { get; set; }
- public DateTime? LatestReleaseDate { get; set; }
- public DateTime? DateAdded { get; set; }
-
- public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath);
- }
-}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index 944b2fd10..e0a217251 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -23,6 +23,8 @@ namespace Flow.Launcher.Core.Plugin
protected ConcurrentDictionary Settings { get; set; } = null!;
public required IPublicAPI API { get; init; }
+ private static readonly string ClassName = nameof(JsonRPCPluginSettings);
+
private JsonStorage> _storage = null!;
private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin");
@@ -122,12 +124,26 @@ namespace Flow.Launcher.Core.Plugin
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()
{
- _storage.Save();
+ try
+ {
+ _storage.Save();
+ }
+ catch (System.Exception e)
+ {
+ API.LogException(ClassName, $"Failed to save plugin settings to path: {SettingPath}", e);
+ }
}
public bool NeedCreateSettingPanel()
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 17517832b..aa6c54a94 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -454,16 +454,11 @@ namespace Flow.Launcher.Core.Plugin
#region Public functions
- public static bool PluginModified(string uuid)
+ public static bool PluginModified(string id)
{
- return _modifiedPlugins.Contains(uuid);
+ return _modifiedPlugins.Contains(id);
}
-
- ///
- /// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url,
- /// unless it's a local path installation
- ///
public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{
InstallPlugin(newVersion, zipFilePath, checkModified:false);
@@ -471,17 +466,11 @@ namespace Flow.Launcher.Core.Plugin
_modifiedPlugins.Add(existingVersion.ID);
}
- ///
- /// Install a plugin. By default will remove the zip file if installation is from url, unless it's a local path installation
- ///
public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
{
InstallPlugin(plugin, zipFilePath, checkModified: true);
}
- ///
- /// Uninstall a plugin.
- ///
public static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false)
{
await UninstallPluginAsync(plugin, removePluginFromSettings, removePluginSettings, true);
@@ -525,20 +514,20 @@ namespace Flow.Launcher.Core.Plugin
var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
var defaultPluginIDs = new List
- {
- "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
- "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
- "572be03c74c642baae319fc283e561a8", // Explorer
- "6A122269676E40EB86EB543B945932B9", // PluginIndicator
- "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
- "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
- "791FC278BA414111B8D1886DFE447410", // Program
- "D409510CD0D2481F853690A07E6DC426", // Shell
- "CEA08895D2544B019B2E9C5009600DF4", // Sys
- "0308FD86DE0A4DEE8D62B9B535370992", // URL
- "565B73353DBF4806919830B9202EE3BF", // WebSearch
- "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
- };
+ {
+ "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
+ "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
+ "572be03c74c642baae319fc283e561a8", // Explorer
+ "6A122269676E40EB86EB543B945932B9", // PluginIndicator
+ "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
+ "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
+ "791FC278BA414111B8D1886DFE447410", // Program
+ "D409510CD0D2481F853690A07E6DC426", // Shell
+ "CEA08895D2544B019B2E9C5009600DF4", // Sys
+ "0308FD86DE0A4DEE8D62B9B535370992", // URL
+ "565B73353DBF4806919830B9202EE3BF", // WebSearch
+ "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
+ };
// Treat default plugin differently, it needs to be removable along with each flow release
var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID)
diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs
index 9a77ece32..20b884b3d 100644
--- a/Flow.Launcher.Core/Updater.cs
+++ b/Flow.Launcher.Core/Updater.cs
@@ -4,10 +4,12 @@ using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading;
using System.Threading.Tasks;
using System.Windows;
-using JetBrains.Annotations;
-using Squirrel;
+using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Infrastructure;
@@ -15,8 +17,8 @@ using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
-using System.Text.Json.Serialization;
-using System.Threading;
+using JetBrains.Annotations;
+using Squirrel;
namespace Flow.Launcher.Core
{
@@ -51,7 +53,7 @@ namespace Flow.Launcher.Core
var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
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)
{
@@ -70,7 +72,7 @@ namespace Flow.Launcher.Core
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));
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)))
_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
- private async Task GitHubUpdateManagerAsync(string repository)
+ private static async Task GitHubUpdateManagerAsync(string repository)
{
var uri = new Uri(repository);
var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases";
@@ -144,12 +146,22 @@ namespace Flow.Launcher.Core
return manager;
}
- public string NewVersionTips(string version)
+ private static string NewVersionTips(string version)
{
- var translator = InternationalizationManager.Instance;
+ var translator = Ioc.Default.GetRequiredService();
var tips = string.Format(translator.GetTranslation("newVersionTips"), version);
return tips;
}
+
+ private static string Formatted(T t)
+ {
+ var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions
+ {
+ WriteIndented = true
+ });
+
+ return formatted;
+ }
}
}
diff --git a/Flow.Launcher.Infrastructure/Helper.cs b/Flow.Launcher.Infrastructure/Helper.cs
index 864d796c7..b02d84ca7 100644
--- a/Flow.Launcher.Infrastructure/Helper.cs
+++ b/Flow.Launcher.Infrastructure/Helper.cs
@@ -1,19 +1,11 @@
#nullable enable
using System;
-using System.IO;
-using System.Text.Json;
-using System.Text.Json.Serialization;
namespace Flow.Launcher.Infrastructure
{
public static class Helper
{
- static Helper()
- {
- jsonFormattedSerializerOptions.Converters.Add(new JsonStringEnumConverter());
- }
-
///
/// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy
///
@@ -36,55 +28,5 @@ namespace Flow.Launcher.Infrastructure
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(this T t)
- {
- var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions
- {
- WriteIndented = true
- });
-
- return formatted;
- }
}
}
diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
index 6f7b1cd90..c8d3ffbc4 100644
--- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
+++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
@@ -5,12 +5,10 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
-using static Flow.Launcher.Infrastructure.Http.Http;
namespace Flow.Launcher.Infrastructure.Image
{
@@ -28,7 +26,6 @@ namespace Flow.Launcher.Infrastructure.Image
public const int SmallIconSize = 64;
public const int FullIconSize = 256;
-
private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" };
public static async Task InitializeAsync()
@@ -61,7 +58,7 @@ namespace Flow.Launcher.Infrastructure.Image
});
}
- public static async Task Save()
+ public static async Task SaveAsync()
{
await storageLock.WaitAsync();
@@ -71,12 +68,22 @@ namespace Flow.Launcher.Infrastructure.Image
.Select(x => x.Key)
.ToList());
}
+ catch (System.Exception e)
+ {
+ Log.Exception($"|ImageLoader.SaveAsync|Failed to save image cache to file", e);
+ }
finally
{
storageLock.Release();
}
}
+ public static async Task WaitSaveAsync()
+ {
+ await storageLock.WaitAsync();
+ storageLock.Release();
+ }
+
private static async Task> LoadStorageToConcurrentDictionaryAsync()
{
await storageLock.WaitAsync();
@@ -173,7 +180,7 @@ namespace Flow.Launcher.Infrastructure.Image
private static async Task LoadRemoteImageAsync(bool loadFullImage, Uri uriResult)
{
// Download image from url
- await using var resp = await GetStreamAsync(uriResult);
+ await using var resp = await Http.Http.GetStreamAsync(uriResult);
await using var buffer = new MemoryStream();
await resp.CopyToAsync(buffer);
buffer.Seek(0, SeekOrigin.Begin);
diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
index 5b73faae6..a8d5f5d62 100644
--- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin.SharedCommands;
using MemoryPack;
namespace Flow.Launcher.Infrastructure.Storage
@@ -21,7 +22,7 @@ namespace Flow.Launcher.Infrastructure.Storage
public BinaryStorage(string filename, string directoryPath = null)
{
directoryPath ??= DataLocation.CacheDirectory;
- Helper.ValidateDirectory(directoryPath);
+ FilesFolders.ValidateDirectory(directoryPath);
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
}
diff --git a/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs
index 865041fb3..8b4062b6b 100644
--- a/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs
@@ -1,17 +1,51 @@
using System.IO;
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin;
+using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Infrastructure.Storage
{
public class FlowLauncherJsonStorage : JsonStorage 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();
+
public FlowLauncherJsonStorage()
{
var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
- Helper.ValidateDirectory(directoryPath);
+ FilesFolders.ValidateDirectory(directoryPath);
var filename = typeof(T).Name;
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);
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
index 40106acd8..a3488124b 100644
--- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
@@ -5,6 +5,7 @@ using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
+using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Infrastructure.Storage
{
@@ -37,7 +38,7 @@ namespace Flow.Launcher.Infrastructure.Storage
FilePath = filePath;
DirectoryPath = Path.GetDirectoryName(filePath) ?? throw new ArgumentException("Invalid file path");
- Helper.ValidateDirectory(DirectoryPath);
+ FilesFolders.ValidateDirectory(DirectoryPath);
}
public async Task LoadAsync()
diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs
index b377c81aa..e8cbd70fb 100644
--- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs
@@ -1,5 +1,9 @@
using System.IO;
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin;
+using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Infrastructure.Storage
{
@@ -8,13 +12,19 @@ namespace Flow.Launcher.Infrastructure.Storage
// Use assembly name to check which plugin is using this storage
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();
+
public PluginJsonStorage()
{
// C# related, add python related below
var dataType = typeof(T);
AssemblyName = dataType.Assembly.GetName().Name;
DirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, AssemblyName);
- Helper.ValidateDirectory(DirectoryPath);
+ FilesFolders.ValidateDirectory(DirectoryPath);
FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}");
}
@@ -23,5 +33,29 @@ namespace Flow.Launcher.Infrastructure.Storage
{
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);
+ }
+ }
}
}
diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs
index 7a3a0c36e..f9c548de8 100644
--- a/Flow.Launcher.Infrastructure/Win32Helper.cs
+++ b/Flow.Launcher.Infrastructure/Win32Helper.cs
@@ -124,6 +124,16 @@ namespace Flow.Launcher.Infrastructure
return PInvoke.SetForegroundWindow(new(handle));
}
+ public static bool IsForegroundWindow(Window window)
+ {
+ return IsForegroundWindow(GetWindowHandle(window));
+ }
+
+ internal static bool IsForegroundWindow(HWND handle)
+ {
+ return handle.Equals(PInvoke.GetForegroundWindow());
+ }
+
#endregion
#region Task Switching
@@ -354,10 +364,20 @@ namespace Flow.Launcher.Infrastructure
// No installed English layout found
if (enHKL == HKL.Null) return;
- // Get the current foreground window
- var hwnd = PInvoke.GetForegroundWindow();
+ // When application is exiting, the Application.Current will be null
+ if (Application.Current == null) return;
+
+ // Get the FL main window
+ var hwnd = GetWindowHandle(Application.Current.MainWindow, true);
if (hwnd == HWND.Null) return;
+ // Check if the FL main window is the current foreground window
+ if (!IsForegroundWindow(hwnd))
+ {
+ var result = PInvoke.SetForegroundWindow(hwnd);
+ if (!result) throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
// Get the current foreground window thread ID
var threadId = PInvoke.GetWindowThreadProcessId(hwnd);
if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error());
@@ -367,12 +387,10 @@ namespace Flow.Launcher.Infrastructure
// the IME mode instead of switching to another layout.
var currentLayout = PInvoke.GetKeyboardLayout(threadId);
var currentLangId = (uint)currentLayout.Value & KeyboardLayoutLoWord;
- foreach (var langTag in ImeLanguageTags)
+ foreach (var imeLangTag in ImeLanguageTags)
{
- if (GetLanguageTag(currentLangId).StartsWith(langTag, StringComparison.OrdinalIgnoreCase))
- {
- return;
- }
+ var langTag = GetLanguageTag(currentLangId);
+ if (langTag.StartsWith(imeLangTag, StringComparison.OrdinalIgnoreCase)) return;
}
// Backup current keyboard layout
@@ -488,5 +506,16 @@ namespace Flow.Launcher.Infrastructure
}
#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
}
}
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index f178ebb90..eeb3f5de3 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -344,5 +344,62 @@ namespace Flow.Launcher.Plugin
/// Stop the loading bar in main window
///
public void StopLoadingBar();
+
+ ///
+ /// Update the plugin manifest
+ ///
+ ///
+ /// FL has multiple urls to download the plugin manifest. Set this to true to only use the primary url.
+ ///
+ ///
+ /// True if the manifest is updated successfully, false otherwise
+ public Task UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default);
+
+ ///
+ /// Get the plugin manifest
+ ///
+ ///
+ public IReadOnlyList GetPluginManifest();
+
+ ///
+ /// Check if the plugin has been modified.
+ /// If this plugin is updated, installed or uninstalled and users do not restart the app,
+ /// it will be marked as modified
+ ///
+ /// Plugin id
+ ///
+ public bool PluginModified(string id);
+
+ ///
+ /// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url,
+ /// unless it's a local path installation
+ ///
+ /// The metadata of the old plugin to update
+ /// The new plugin to update
+ ///
+ /// Path to the zip file containing the plugin. It will be unzipped to the temporary directory, removed and installed.
+ ///
+ ///
+ public Task UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath);
+
+ ///
+ /// Install a plugin. By default will remove the zip file if installation is from url,
+ /// unless it's a local path installation
+ ///
+ /// The plugin to install
+ ///
+ /// Path to the zip file containing the plugin. It will be unzipped to the temporary directory, removed and installed.
+ ///
+ public void InstallPlugin(UserPlugin plugin, string zipFilePath);
+
+ ///
+ /// Uninstall a plugin
+ ///
+ /// The metadata of the plugin to uninstall
+ ///
+ /// Plugin has their own settings. If this is set to true, the plugin settings will be removed.
+ ///
+ ///
+ public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false);
}
}
diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
index 5f003e351..1de5841a5 100644
--- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
+++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
@@ -318,5 +318,51 @@ namespace Flow.Launcher.Plugin.SharedCommands
{
return path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
}
+
+ ///
+ /// Validates a directory, creating it if it doesn't exist
+ ///
+ ///
+ public static void ValidateDirectory(string path)
+ {
+ if (!Directory.Exists(path))
+ {
+ Directory.CreateDirectory(path);
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ ///
+ ///
+ 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);
+ }
+ }
+ }
+ }
}
}
diff --git a/Flow.Launcher.Plugin/UserPlugin.cs b/Flow.Launcher.Plugin/UserPlugin.cs
new file mode 100644
index 000000000..74a16b83d
--- /dev/null
+++ b/Flow.Launcher.Plugin/UserPlugin.cs
@@ -0,0 +1,80 @@
+using System;
+
+namespace Flow.Launcher.Plugin
+{
+ ///
+ /// User Plugin Model for Flow Launcher
+ ///
+ public record UserPlugin
+ {
+ ///
+ /// Unique identifier of the plugin
+ ///
+ public string ID { get; set; }
+
+ ///
+ /// Name of the plugin
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Description of the plugin
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// Author of the plugin
+ ///
+ public string Author { get; set; }
+
+ ///
+ /// Version of the plugin
+ ///
+ public string Version { get; set; }
+
+ ///
+ /// Allow language of the plugin
+ ///
+ public string Language { get; set; }
+
+ ///
+ /// Website of the plugin
+ ///
+ public string Website { get; set; }
+
+ ///
+ /// URL to download the plugin
+ ///
+ public string UrlDownload { get; set; }
+
+ ///
+ /// URL to the source code of the plugin
+ ///
+ public string UrlSourceCode { get; set; }
+
+ ///
+ /// Local path where the plugin is installed
+ ///
+ public string LocalInstallPath { get; set; }
+
+ ///
+ /// Icon path of the plugin
+ ///
+ public string IcoPath { get; set; }
+
+ ///
+ /// The date when the plugin was last updated
+ ///
+ public DateTime? LatestReleaseDate { get; set; }
+
+ ///
+ /// The date when the plugin was added to the local system
+ ///
+ public DateTime? DateAdded { get; set; }
+
+ ///
+ /// Indicates whether the plugin is installed from a local path
+ ///
+ public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath);
+ }
+}
diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs
index 9aee56bff..81938612c 100644
--- a/Flow.Launcher/App.xaml.cs
+++ b/Flow.Launcher/App.xaml.cs
@@ -179,8 +179,6 @@ namespace Flow.Launcher
Current.MainWindow = _mainWindow;
Current.MainWindow.Title = Constant.FlowLauncher;
- HotKeyMapper.Initialize();
-
// main windows needs initialized before theme change because of blur settings
Ioc.Default.GetRequiredService().ChangeTheme();
@@ -304,6 +302,14 @@ namespace Flow.Launcher
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;
}
diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs
index 011d46d6b..30afe67a1 100644
--- a/Flow.Launcher/MainWindow.xaml.cs
+++ b/Flow.Launcher/MainWindow.xaml.cs
@@ -16,8 +16,10 @@ using System.Windows.Threading;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
+using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Hotkey;
+using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.ViewModel;
@@ -32,6 +34,13 @@ namespace Flow.Launcher
{
public partial class MainWindow : IDisposable
{
+ #region Public Property
+
+ // Window Event: Close Event
+ public bool CanClose { get; set; } = false;
+
+ #endregion
+
#region Private Fields
// Dependency Injection
@@ -45,8 +54,6 @@ namespace Flow.Launcher
private readonly ContextMenu _contextMenu = new();
private readonly MainViewModel _viewModel;
- // Window Event: Close Event
- private bool _canClose = false;
// Window Event: Key Event
private bool _isArrowKeyPressed = false;
@@ -168,6 +175,9 @@ namespace Flow.Launcher
// Without this part, when shown for the first time, switching the context menu does not move the cursor to the end.
_viewModel.QueryTextCursorMovedToEnd = false;
+ // Initialize hotkey mapper after window is loaded
+ HotKeyMapper.Initialize();
+
// View model property changed event
_viewModel.PropertyChanged += (o, e) =>
{
@@ -279,15 +289,16 @@ namespace Flow.Launcher
private async void OnClosing(object sender, CancelEventArgs e)
{
- if (!_canClose)
+ if (!CanClose)
{
_notifyIcon.Visible = false;
App.API.SaveAppAllSettings();
e.Cancel = true;
+ await ImageLoader.WaitSaveAsync();
await PluginManager.DisposePluginsAsync();
Notification.Uninstall();
// 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
Application.Current.Shutdown();
}
diff --git a/Flow.Launcher/Notification.cs b/Flow.Launcher/Notification.cs
index cb1cbb729..30b3a0673 100644
--- a/Flow.Launcher/Notification.cs
+++ b/Flow.Launcher/Notification.cs
@@ -9,8 +9,8 @@ namespace Flow.Launcher
{
internal static class Notification
{
- internal static bool legacy = Environment.OSVersion.Version.Build < 19041;
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "")]
+ internal static bool legacy = !Win32Helper.IsNotificationSupported();
+
internal static void Uninstall()
{
if (!legacy)
@@ -25,7 +25,6 @@ namespace Flow.Launcher
});
}
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "")]
private static void ShowInternal(string title, string subTitle, string iconPath = null)
{
// Handle notification for win7/8/early win10
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index e19ad2fdc..c40e40ebb 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -28,6 +28,7 @@ using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.ViewModel;
using JetBrains.Annotations;
using Flow.Launcher.Core.Resource;
+using Flow.Launcher.Core.ExternalPlugins;
namespace Flow.Launcher
{
@@ -37,6 +38,8 @@ namespace Flow.Launcher
private readonly Internationalization _translater;
private readonly MainViewModel _mainVM;
+ private readonly object _saveSettingsLock = new();
+
#region Constructor
public PublicAPIInstance(Settings settings, Internationalization translater, MainViewModel mainVM)
@@ -57,21 +60,28 @@ namespace Flow.Launcher
_mainVM.ChangeQueryText(query, requery);
}
- public void RestartApp()
+#pragma warning disable VSTHRD100 // Avoid async void methods
+
+ public async void RestartApp()
{
_mainVM.Hide();
- // we must manually save
+ // We must manually save
// UpdateManager.RestartApp() will call Environment.Exit(0)
// which will cause ungraceful exit
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,
// 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.
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
+#pragma warning restore VSTHRD100 // Avoid async void methods
+
public void ShowMainWindow() => _mainVM.Show();
public void HideMainWindow() => _mainVM.Hide();
@@ -85,10 +95,13 @@ namespace Flow.Launcher
public void SaveAppAllSettings()
{
- PluginManager.Save();
- _mainVM.Save();
- _settings.Save();
- _ = ImageLoader.Save();
+ lock (_saveSettingsLock)
+ {
+ _settings.Save();
+ PluginManager.Save();
+ _mainVM.Save();
+ }
+ _ = ImageLoader.SaveAsync();
}
public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync();
@@ -342,6 +355,22 @@ namespace Flow.Launcher
public Task ShowProgressBoxAsync(string caption, Func, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress);
+ public Task UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default) =>
+ PluginsManifest.UpdateManifestAsync(usePrimaryUrlOnly, token);
+
+ public IReadOnlyList GetPluginManifest() => PluginsManifest.UserPlugins;
+
+ public bool PluginModified(string id) => PluginManager.PluginModified(id);
+
+ public Task UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath) =>
+ PluginManager.UpdatePluginAsync(pluginMetadata, plugin, zipFilePath);
+
+ public void InstallPlugin(UserPlugin plugin, string zipFilePath) =>
+ PluginManager.InstallPlugin(plugin, zipFilePath);
+
+ public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false) =>
+ PluginManager.UninstallPluginAsync(pluginMetadata, removePluginSettings);
+
#endregion
#region Private Methods
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
index 15579a61d..84d8a2ff9 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
@@ -2,7 +2,6 @@
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
-using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
@@ -14,7 +13,7 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
public string FilterText { get; set; } = string.Empty;
public IList ExternalPlugins =>
- PluginsManifest.UserPlugins?.Select(p => new PluginStoreItemViewModel(p))
+ App.API.GetPluginManifest()?.Select(p => new PluginStoreItemViewModel(p))
.OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease)
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated)
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.None)
@@ -24,7 +23,7 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
[RelayCommand]
private async Task RefreshExternalPluginsAsync()
{
- if (await PluginsManifest.UpdateManifestAsync())
+ if (await App.API.UpdatePluginManifestAsync())
{
OnPropertyChanged(nameof(ExternalPlugins));
}
diff --git a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
index 38b5bec65..d1cf74501 100644
--- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
@@ -1,10 +1,8 @@
using System;
using System.Linq;
using CommunityToolkit.Mvvm.Input;
-using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin;
-using SemanticVersioning;
using Version = SemanticVersioning.Version;
namespace Flow.Launcher.ViewModel
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
index 482e821dc..265657ef4 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
@@ -1,5 +1,4 @@
-using Flow.Launcher.Core.ExternalPlugins;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Flow.Launcher.Plugin.PluginsManager
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
index c33c42889..8ff41a7ad 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
@@ -18,8 +18,6 @@
-
-
@@ -37,4 +35,8 @@
PreserveNewest
+
+
+
+
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs
index 156135f81..742d85fc1 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs
@@ -1,18 +1,16 @@
-using Flow.Launcher.Core.ExternalPlugins;
-using Flow.Launcher.Plugin.PluginsManager.ViewModels;
-using Flow.Launcher.Plugin.PluginsManager.Views;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
-using Flow.Launcher.Infrastructure;
using System.Threading.Tasks;
using System.Threading;
+using Flow.Launcher.Plugin.PluginsManager.ViewModels;
+using Flow.Launcher.Plugin.PluginsManager.Views;
namespace Flow.Launcher.Plugin.PluginsManager
{
public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n
{
- internal PluginInitContext Context { get; set; }
+ internal static PluginInitContext Context { get; set; }
internal Settings Settings;
@@ -35,7 +33,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
contextMenu = new ContextMenu(Context);
pluginManager = new PluginsManager(Context, Settings);
- await PluginsManifest.UpdateManifestAsync();
+ await Context.API.UpdatePluginManifestAsync();
}
public List LoadContextMenus(Result selectedResult)
@@ -56,7 +54,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Settings.UpdateCommand => await pluginManager.RequestUpdateAsync(query.SecondToEndSearch, token, query.IsReQuery),
_ => pluginManager.GetDefaultHotKeys().Where(hotkey =>
{
- hotkey.Score = StringMatcher.FuzzySearch(query.Search, hotkey.Title).Score;
+ hotkey.Score = Context.API.FuzzySearch(query.Search, hotkey.Title).Score;
return hotkey.Score > 0;
}).ToList()
};
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 79d6aedd5..a16778ff4 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -1,10 +1,4 @@
-using Flow.Launcher.Core.ExternalPlugins;
-using Flow.Launcher.Core.Plugin;
-using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Infrastructure.Http;
-using Flow.Launcher.Infrastructure.Logger;
-using Flow.Launcher.Plugin.SharedCommands;
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -12,12 +6,15 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
+using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Plugin.PluginsManager
{
internal class PluginsManager
{
- private const string zip = "zip";
+ private const string ZipSuffix = "zip";
+
+ private static readonly string ClassName = nameof(PluginsManager);
private PluginInitContext Context { get; set; }
@@ -51,7 +48,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
return new List()
{
- new Result()
+ new()
{
Title = Settings.InstallCommand,
IcoPath = icoPath,
@@ -63,7 +60,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
return false;
}
},
- new Result()
+ new()
{
Title = Settings.UninstallCommand,
IcoPath = icoPath,
@@ -75,7 +72,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
return false;
}
},
- new Result()
+ new()
{
Title = Settings.UpdateCommand,
IcoPath = icoPath,
@@ -169,7 +166,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.ShowMsgError(
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name),
Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
- Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
+ Context.API.LogException(ClassName, "An error occurred while downloading plugin", e);
return;
}
@@ -179,7 +176,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
plugin.Name));
- Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
+ Context.API.LogException(ClassName, "An error occurred while downloading plugin", e);
return;
}
@@ -237,7 +234,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
internal async ValueTask> RequestUpdateAsync(string search, CancellationToken token,
bool usePrimaryUrlOnly = false)
{
- await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly);
+ await Context.API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
var pluginFromLocalPath = null as UserPlugin;
var updateFromLocalPath = false;
@@ -250,7 +247,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
var updateSource = !updateFromLocalPath
- ? PluginsManifest.UserPlugins
+ ? Context.API.GetPluginManifest()
: new List { pluginFromLocalPath };
var resultsForUpdate = (
@@ -260,7 +257,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version,
StringComparison.InvariantCulture) <
0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest)
- && !PluginManager.PluginModified(existingPlugin.Metadata.ID)
+ && !Context.API.PluginModified(existingPlugin.Metadata.ID)
select
new
{
@@ -276,7 +273,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (!resultsForUpdate.Any())
return new List
{
- new Result
+ new()
{
Title = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_subtitle"),
@@ -341,7 +338,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
else
{
- await PluginManager.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin,
+ await Context.API.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin,
downloadToFilePath);
if (Settings.AutoRestartAfterChanging)
@@ -366,7 +363,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
}).ContinueWith(t =>
{
- Log.Exception("PluginsManager", $"Update failed for {x.Name}",
+ Context.API.LogException(ClassName, $"Update failed for {x.Name}",
t.Exception.InnerException);
Context.API.ShowMsg(
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
@@ -433,12 +430,12 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (cts.IsCancellationRequested)
return;
else
- await PluginManager.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin,
+ await Context.API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin,
downloadToFilePath);
}
catch (Exception ex)
{
- Log.Exception("PluginsManager", $"Update failed for {plugin.Name}", ex.InnerException);
+ Context.API.LogException(ClassName, $"Update failed for {plugin.Name}", ex.InnerException);
Context.API.ShowMsg(
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(
@@ -486,7 +483,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
return results
.Where(x =>
{
- var matchResult = StringMatcher.FuzzySearch(searchName, x.Title);
+ var matchResult = Context.API.FuzzySearch(searchName, x.Title);
if (matchResult.IsSearchPrecisionScoreMet())
x.Score = matchResult.Score;
@@ -498,7 +495,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
internal List InstallFromWeb(string url)
{
var filename = url.Split("/").Last();
- var name = filename.Split(string.Format(".{0}", zip)).First();
+ var name = filename.Split(string.Format(".{0}", ZipSuffix)).First();
var plugin = new UserPlugin
{
@@ -552,7 +549,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
return new List
{
- new Result
+ new()
{
Title = $"{plugin.Name} by {plugin.Author}",
SubTitle = plugin.Description,
@@ -602,19 +599,18 @@ namespace Flow.Launcher.Plugin.PluginsManager
internal async ValueTask> RequestInstallOrUpdateAsync(string search, CancellationToken token,
bool usePrimaryUrlOnly = false)
{
- await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly);
+ await Context.API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
if (Uri.IsWellFormedUriString(search, UriKind.Absolute)
- && search.Split('.').Last() == zip)
+ && search.Split('.').Last() == ZipSuffix)
return InstallFromWeb(search);
if (FilesFolders.IsZipFilePath(search, checkFileExists: true))
return InstallFromLocalPath(search);
var results =
- PluginsManifest
- .UserPlugins
- .Where(x => !PluginExists(x.ID) && !PluginManager.PluginModified(x.ID))
+ Context.API.GetPluginManifest()
+ .Where(x => !PluginExists(x.ID) && !Context.API.PluginModified(x.ID))
.Select(x =>
new Result
{
@@ -647,7 +643,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
try
{
- PluginManager.InstallPlugin(plugin, downloadedFilePath);
+ Context.API.InstallPlugin(plugin, downloadedFilePath);
if (!plugin.IsFromLocalInstallPath)
File.Delete(downloadedFilePath);
@@ -656,21 +652,21 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"));
- Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
+ Context.API.LogException(ClassName, e.Message, e);
}
catch (InvalidOperationException e)
{
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"),
plugin.Name));
- Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
+ Context.API.LogException(ClassName, e.Message, e);
}
catch (ArgumentException e)
{
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"),
plugin.Name));
- Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
+ Context.API.LogException(ClassName, e.Message, e);
}
}
@@ -740,11 +736,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_subtitle"),
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_title"),
button: MessageBoxButton.YesNo) == MessageBoxResult.No;
- await PluginManager.UninstallPluginAsync(plugin, removePluginFromSettings: true, removePluginSettings: removePluginSettings);
+ await Context.API.UninstallPluginAsync(plugin, removePluginSettings);
}
catch (ArgumentException e)
{
- Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
+ Context.API.LogException(ClassName, e.Message, e);
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_error_title"),
Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"));
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs
index 743f5b25b..4bb78f6ff 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs
@@ -1,5 +1,4 @@
-using Flow.Launcher.Core.ExternalPlugins;
-using ICSharpCode.SharpZipLib.Zip;
+using ICSharpCode.SharpZipLib.Zip;
using System.IO;
using System.IO.Compression;
using System.Linq;
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
index 3be23214c..73b4ae9e6 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
@@ -6,13 +6,13 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
-using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.Program.Programs;
using Flow.Launcher.Plugin.Program.Views;
using Flow.Launcher.Plugin.Program.Views.Models;
+using Flow.Launcher.Plugin.SharedCommands;
using Microsoft.Extensions.Caching.Memory;
using Path = System.IO.Path;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
@@ -191,7 +191,7 @@ namespace Flow.Launcher.Plugin.Program
await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () =>
{
- Helper.ValidateDirectory(Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
+ FilesFolders.ValidateDirectory(Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
static void MoveFile(string sourcePath, string destinationPath)
{
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs
index 7ad9715bb..76aeb3250 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs
@@ -5,7 +5,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
-using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Plugin.WebSearch
@@ -180,7 +179,7 @@ namespace Flow.Launcher.Plugin.WebSearch
// Default images directory is in the WebSearch's application folder
DefaultImagesDirectory = Path.Combine(pluginDirectory, Images);
- Helper.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory);
+ FilesFolders.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory);
// Custom images directory is in the WebSearch's data location folder
CustomImagesDirectory = Path.Combine(_context.CurrentPluginMetadata.PluginSettingsDirectoryPath, "CustomIcons");