diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs
index 102d0089f9a..4d988b837b6 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs
@@ -189,5 +189,10 @@ public void StopLoadingBar()
{
_api.StopLoadingBar();
}
+
+ public void SavePluginCaches()
+ {
+ _api.SavePluginCaches();
+ }
}
}
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index aa6c54a9434..0ee268f5c56 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -13,6 +13,7 @@
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
+using IRemovable = Flow.Launcher.Core.Storage.IRemovable;
using ISavable = Flow.Launcher.Plugin.ISavable;
namespace Flow.Launcher.Core.Plugin
@@ -65,6 +66,7 @@ public static void Save()
}
API.SavePluginSettings();
+ API.SavePluginCaches();
}
public static async ValueTask DisposePluginsAsync()
@@ -575,11 +577,11 @@ internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool remo
if (removePluginSettings)
{
- // For dotnet plugins, we need to remove their PluginJsonStorage instance
- if (AllowedLanguage.IsDotNet(plugin.Language))
+ // For dotnet plugins, we need to remove their PluginJsonStorage and PluginBinaryStorage instances
+ if (AllowedLanguage.IsDotNet(plugin.Language) && API is IRemovable removable)
{
- var method = API.GetType().GetMethod("RemovePluginSettings");
- method?.Invoke(API, new object[] { plugin.AssemblyName });
+ removable.RemovePluginSettings(plugin.AssemblyName);
+ removable.RemovePluginCaches(plugin.PluginCacheDirectoryPath);
}
try
diff --git a/Flow.Launcher.Core/Storage/IRemovable.cs b/Flow.Launcher.Core/Storage/IRemovable.cs
new file mode 100644
index 00000000000..bcf1cdd5e48
--- /dev/null
+++ b/Flow.Launcher.Core/Storage/IRemovable.cs
@@ -0,0 +1,19 @@
+namespace Flow.Launcher.Core.Storage;
+
+///
+/// Remove storage instances from instance
+///
+public interface IRemovable
+{
+ ///
+ /// Remove all instances of one plugin
+ ///
+ ///
+ public void RemovePluginSettings(string assemblyName);
+
+ ///
+ /// Remove all instances of one plugin
+ ///
+ ///
+ public void RemovePluginCaches(string cacheDirectory);
+}
diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
index 01abf8995fc..41a33104b66 100644
--- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
+++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
@@ -34,6 +34,7 @@ public static async Task InitializeAsync()
_hashGenerator = new ImageHashGenerator();
var usage = await LoadStorageToConcurrentDictionaryAsync();
+ _storage.ClearData();
ImageCache.Initialize(usage);
diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
index a8d5f5d6218..43bb8dadecb 100644
--- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
@@ -2,9 +2,12 @@
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using MemoryPack;
+#nullable enable
+
namespace Flow.Launcher.Infrastructure.Storage
{
///
@@ -12,44 +15,55 @@ namespace Flow.Launcher.Infrastructure.Storage
/// Normally, it has better performance, but not readable
///
///
- /// It utilize MemoryPack, which means the object must be MemoryPackSerializable
+ /// It utilizes MemoryPack, which means the object must be MemoryPackSerializable
///
- public class BinaryStorage
+ public class BinaryStorage : ISavable
{
+ protected T? Data;
+
public const string FileSuffix = ".cache";
+ protected string FilePath { get; init; } = null!;
+
+ protected string DirectoryPath { get; init; } = null!;
+
// Let the derived class to set the file path
- public BinaryStorage(string filename, string directoryPath = null)
+ protected BinaryStorage()
{
- directoryPath ??= DataLocation.CacheDirectory;
- FilesFolders.ValidateDirectory(directoryPath);
-
- FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
}
- public string FilePath { get; }
+ public BinaryStorage(string filename)
+ {
+ DirectoryPath = DataLocation.CacheDirectory;
+ FilesFolders.ValidateDirectory(DirectoryPath);
+
+ FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
+ }
public async ValueTask TryLoadAsync(T defaultData)
{
+ if (Data != null) return Data;
+
if (File.Exists(FilePath))
{
if (new FileInfo(FilePath).Length == 0)
{
Log.Error($"|BinaryStorage.TryLoad|Zero length cache file <{FilePath}>");
- await SaveAsync(defaultData);
- return defaultData;
+ Data = defaultData;
+ await SaveAsync();
}
await using var stream = new FileStream(FilePath, FileMode.Open);
- var d = await DeserializeAsync(stream, defaultData);
- return d;
+ Data = await DeserializeAsync(stream, defaultData);
}
else
{
Log.Info("|BinaryStorage.TryLoad|Cache file not exist, load default data");
- await SaveAsync(defaultData);
- return defaultData;
+ Data = defaultData;
+ await SaveAsync();
}
+
+ return Data;
}
private static async ValueTask DeserializeAsync(Stream stream, T defaultData)
@@ -57,7 +71,7 @@ private static async ValueTask DeserializeAsync(Stream stream, T defaultData)
try
{
var t = await MemoryPackSerializer.DeserializeAsync(stream);
- return t;
+ return t ?? defaultData;
}
catch (System.Exception)
{
@@ -66,6 +80,27 @@ private static async ValueTask DeserializeAsync(Stream stream, T defaultData)
}
}
+ public void Save()
+ {
+ var serialized = MemoryPackSerializer.Serialize(Data);
+
+ File.WriteAllBytes(FilePath, serialized);
+ }
+
+ public async ValueTask SaveAsync()
+ {
+ await SaveAsync(Data.NonNull());
+ }
+
+ // ImageCache need to convert data into concurrent dictionary for usage,
+ // so we would better to clear the data
+ public void ClearData()
+ {
+ Data = default;
+ }
+
+ // ImageCache storages data in its class,
+ // so we need to pass it to SaveAsync
public async ValueTask SaveAsync(T data)
{
await using var stream = new FileStream(FilePath, FileMode.Create);
diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
index a3488124b92..cdf3ae90962 100644
--- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
@@ -1,18 +1,20 @@
-#nullable enable
-using System;
+using System;
using System.Globalization;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
+using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
+#nullable enable
+
namespace Flow.Launcher.Infrastructure.Storage
{
///
/// Serialize object using json format.
///
- public class JsonStorage where T : new()
+ public class JsonStorage : ISavable where T : new()
{
protected T? Data;
diff --git a/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs
new file mode 100644
index 00000000000..d18060e3df0
--- /dev/null
+++ b/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs
@@ -0,0 +1,49 @@
+using System.IO;
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.DependencyInjection;
+using Flow.Launcher.Plugin;
+using Flow.Launcher.Plugin.SharedCommands;
+
+namespace Flow.Launcher.Infrastructure.Storage
+{
+ public class PluginBinaryStorage : BinaryStorage where T : new()
+ {
+ private static readonly string ClassName = "PluginBinaryStorage";
+
+ // 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 PluginBinaryStorage(string cacheName, string cacheDirectory)
+ {
+ DirectoryPath = cacheDirectory;
+ FilesFolders.ValidateDirectory(DirectoryPath);
+
+ FilePath = Path.Combine(DirectoryPath, $"{cacheName}{FileSuffix}");
+ }
+
+ public new void Save()
+ {
+ try
+ {
+ base.Save();
+ }
+ catch (System.Exception e)
+ {
+ API.LogException(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
+ }
+ }
+
+ public new async Task SaveAsync()
+ {
+ try
+ {
+ await base.SaveAsync();
+ }
+ catch (System.Exception e)
+ {
+ API.LogException(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
+ }
+ }
+ }
+}
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index 7a1869fd687..a3020b60725 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -352,6 +352,38 @@ public interface IPublicAPI
public void StopLoadingBar();
///
+ /// Save all Flow's plugins caches
+ ///
+ void SavePluginCaches();
+
+ ///
+ /// Load BinaryStorage for current plugin's cache. This is the method used to load cache from binary in Flow.
+ /// When the file is not exist, it will create a new instance for the specific type.
+ ///
+ /// Type for deserialization
+ /// Cache file name
+ /// Cache directory from plugin metadata
+ /// Default data to return
+ ///
+ ///
+ /// BinaryStorage utilizes MemoryPack, which means the object must be MemoryPackSerializable
+ ///
+ Task LoadCacheBinaryStorageAsync(string cacheName, string cacheDirectory, T defaultData) where T : new();
+
+ ///
+ /// Save BinaryStorage for current plugin's cache. This is the method used to save cache to binary in Flow.Launcher
+ /// This method will save the original instance loaded with LoadCacheBinaryStorageAsync.
+ /// This API call is for manually Save. Flow will automatically save all cache type that has called LoadCacheBinaryStorageAsync or SaveCacheBinaryStorageAsync previously.
+ ///
+ /// Type for Serialization
+ /// Cache file name
+ /// Cache directory from plugin metadata
+ ///
+ ///
+ /// BinaryStorage utilizes MemoryPack, which means the object must be MemoryPackSerializable
+ ///
+ Task SaveCacheBinaryStorageAsync(string cacheName, string cacheDirectory) where T : new();
+
/// Load image from path. Support local, remote and data:image url.
/// If image path is missing, it will return a missing icon.
///
diff --git a/Flow.Launcher.Plugin/Interfaces/ISavable.cs b/Flow.Launcher.Plugin/Interfaces/ISavable.cs
index 77bd304e4ea..38cbf8e08f5 100644
--- a/Flow.Launcher.Plugin/Interfaces/ISavable.cs
+++ b/Flow.Launcher.Plugin/Interfaces/ISavable.cs
@@ -1,18 +1,21 @@
-namespace Flow.Launcher.Plugin
+namespace Flow.Launcher.Plugin
{
///
- /// Inherit this interface if additional data e.g. cache needs to be saved.
+ /// Inherit this interface if you need to save additional data which is not a setting or cache,
+ /// please implement this interface.
///
///
/// For storing plugin settings, prefer
- /// or .
- /// Once called, your settings will be automatically saved by Flow.
+ /// or .
+ /// For storing plugin caches, prefer
+ /// or .
+ /// Once called, those settings and caches will be automatically saved by Flow.
///
public interface ISavable : IFeatures
{
///
- /// Save additional plugin data, such as cache.
+ /// Save additional plugin data.
///
void Save();
}
-}
\ No newline at end of file
+}
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 4afd0327b21..a523f90bb8f 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -16,6 +16,7 @@
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Core.ExternalPlugins;
+using Flow.Launcher.Core.Storage;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http;
@@ -33,7 +34,7 @@
namespace Flow.Launcher
{
- public class PublicAPIInstance : IPublicAPI
+ public class PublicAPIInstance : IPublicAPI, IRemovable
{
private readonly Settings _settings;
private readonly Internationalization _translater;
@@ -223,20 +224,17 @@ public void RemovePluginSettings(string assemblyName)
var name = value.GetType().GetField("AssemblyName")?.GetValue(value)?.ToString();
if (name == assemblyName)
{
- _pluginJsonStorages.Remove(key, out var _);
+ _pluginJsonStorages.TryRemove(key, out var _);
}
}
}
- ///
- /// Save plugin settings.
- ///
public void SavePluginSettings()
{
foreach (var value in _pluginJsonStorages.Values)
{
- var method = value.GetType().GetMethod("Save");
- method?.Invoke(value, null);
+ var savable = value as ISavable;
+ savable?.Save();
}
}
@@ -258,14 +256,6 @@ public void SavePluginSettings()
((PluginJsonStorage)_pluginJsonStorages[type]).Save();
}
- public void SaveJsonStorage(T settings) where T : new()
- {
- var type = typeof(T);
- _pluginJsonStorages[type] = new PluginJsonStorage(settings);
-
- ((PluginJsonStorage)_pluginJsonStorages[type]).Save();
- }
-
public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null)
{
using var explorer = new Process();
@@ -370,6 +360,48 @@ public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "",
public Task ShowProgressBoxAsync(string caption, Func, Task> reportProgressAsync,
Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress);
+ private readonly ConcurrentDictionary<(string, string, Type), object> _pluginBinaryStorages = new();
+
+ public void RemovePluginCaches(string cacheDirectory)
+ {
+ foreach (var keyValuePair in _pluginBinaryStorages)
+ {
+ var key = keyValuePair.Key;
+ var currentCacheDirectory = key.Item2;
+ if (cacheDirectory == currentCacheDirectory)
+ {
+ _pluginBinaryStorages.TryRemove(key, out var _);
+ }
+ }
+ }
+
+ public void SavePluginCaches()
+ {
+ foreach (var value in _pluginBinaryStorages.Values)
+ {
+ var savable = value as ISavable;
+ savable?.Save();
+ }
+ }
+
+ public async Task LoadCacheBinaryStorageAsync(string cacheName, string cacheDirectory, T defaultData) where T : new()
+ {
+ var type = typeof(T);
+ if (!_pluginBinaryStorages.ContainsKey((cacheName, cacheDirectory, type)))
+ _pluginBinaryStorages[(cacheName, cacheDirectory, type)] = new PluginBinaryStorage(cacheName, cacheDirectory);
+
+ return await ((PluginBinaryStorage)_pluginBinaryStorages[(cacheName, cacheDirectory, type)]).TryLoadAsync(defaultData);
+ }
+
+ public async Task SaveCacheBinaryStorageAsync(string cacheName, string cacheDirectory) where T : new()
+ {
+ var type = typeof(T);
+ if (!_pluginBinaryStorages.ContainsKey((cacheName, cacheDirectory, type)))
+ _pluginBinaryStorages[(cacheName, cacheDirectory, type)] = new PluginBinaryStorage(cacheName, cacheDirectory);
+
+ await ((PluginBinaryStorage)_pluginBinaryStorages[(cacheName, cacheDirectory, type)]).SaveAsync();
+ }
+
public ValueTask LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true) =>
ImageLoader.LoadAsync(path, loadFullImage, cacheImage);
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
index 73b4ae9e65c..a50868b69dd 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
@@ -7,7 +7,6 @@
using System.Threading.Tasks;
using System.Windows.Controls;
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;
@@ -19,18 +18,19 @@
namespace Flow.Launcher.Plugin.Program
{
- public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable,
- IDisposable
+ public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, IAsyncReloadable, IDisposable
{
- internal static Win32[] _win32s { get; set; }
- internal static UWPApp[] _uwps { get; set; }
- internal static Settings _settings { get; set; }
+ private const string Win32CacheName = "Win32";
+ private const string UwpCacheName = "UWP";
+ internal static List _win32s { get; private set; }
+ internal static List _uwps { get; private set; }
+ internal static Settings _settings { get; private set; }
- internal static PluginInitContext Context { get; private set; }
+ internal static SemaphoreSlim _win32sLock = new(1, 1);
+ internal static SemaphoreSlim _uwpsLock = new(1, 1);
- private static BinaryStorage _win32Storage;
- private static BinaryStorage _uwpStorage;
+ internal static PluginInitContext Context { get; private set; }
private static readonly List emptyResults = new();
@@ -77,22 +77,15 @@ public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, I
private static readonly string WindowsAppPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsApps");
- static Main()
- {
- }
-
- public void Save()
- {
- _win32Storage.SaveAsync(_win32s);
- _uwpStorage.SaveAsync(_uwps);
- }
-
public async Task> QueryAsync(Query query, CancellationToken token)
{
var result = await cache.GetOrCreateAsync(query.Search, async entry =>
{
- var resultList = await Task.Run(() =>
+ var resultList = await Task.Run(async () =>
{
+ await _win32sLock.WaitAsync(token);
+ await _uwpsLock.WaitAsync(token);
+
try
{
// Collect all UWP Windows app directories
@@ -119,7 +112,11 @@ public async Task> QueryAsync(Query query, CancellationToken token)
Log.Debug("|Flow.Launcher.Plugin.Program.Main|Query operation cancelled");
return emptyResults;
}
-
+ finally
+ {
+ _uwpsLock.Release();
+ _win32sLock.Release();
+ }
}, token);
resultList = resultList.Any() ? resultList : emptyResults;
@@ -189,9 +186,12 @@ public async Task InitAsync(PluginInitContext context)
_settings = context.API.LoadSettingJsonStorage();
+ var _win32sCount = 0;
+ var _uwpsCount = 0;
await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () =>
{
- FilesFolders.ValidateDirectory(Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
+ var pluginCacheDirectory = Context.CurrentPluginMetadata.PluginCacheDirectoryPath;
+ FilesFolders.ValidateDirectory(pluginCacheDirectory);
static void MoveFile(string sourcePath, string destinationPath)
{
@@ -236,22 +236,27 @@ static void MoveFile(string sourcePath, string destinationPath)
}
// Move old cache files to the new cache directory
- var oldWin32CacheFile = Path.Combine(DataLocation.CacheDirectory, $"Win32.cache");
- var newWin32CacheFile = Path.Combine(Context.CurrentPluginMetadata.PluginCacheDirectoryPath, $"Win32.cache");
+ var oldWin32CacheFile = Path.Combine(DataLocation.CacheDirectory, $"{Win32CacheName}.cache");
+ var newWin32CacheFile = Path.Combine(pluginCacheDirectory, $"{Win32CacheName}.cache");
MoveFile(oldWin32CacheFile, newWin32CacheFile);
- var oldUWPCacheFile = Path.Combine(DataLocation.CacheDirectory, $"UWP.cache");
- var newUWPCacheFile = Path.Combine(Context.CurrentPluginMetadata.PluginCacheDirectoryPath, $"UWP.cache");
+ var oldUWPCacheFile = Path.Combine(DataLocation.CacheDirectory, $"{UwpCacheName}.cache");
+ var newUWPCacheFile = Path.Combine(pluginCacheDirectory, $"{UwpCacheName}.cache");
MoveFile(oldUWPCacheFile, newUWPCacheFile);
- _win32Storage = new BinaryStorage("Win32", Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
- _win32s = await _win32Storage.TryLoadAsync(Array.Empty());
- _uwpStorage = new BinaryStorage("UWP", Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
- _uwps = await _uwpStorage.TryLoadAsync(Array.Empty());
+ await _win32sLock.WaitAsync();
+ _win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCacheDirectory, new List());
+ _win32sCount = _win32s.Count;
+ _win32sLock.Release();
+
+ await _uwpsLock.WaitAsync();
+ _uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCacheDirectory, new List());
+ _uwpsCount = _uwps.Count;
+ _uwpsLock.Release();
});
- Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>");
- Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>");
+ Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32sCount}>");
+ Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwpsCount}>");
- bool cacheEmpty = !_win32s.Any() || !_uwps.Any();
+ var cacheEmpty = _win32sCount == 0 || _uwpsCount == 0;
if (cacheEmpty || _settings.LastIndexTime.AddHours(30) < DateTime.Now)
{
@@ -273,36 +278,69 @@ static void WatchProgramUpdate()
}
}
- public static void IndexWin32Programs()
+ public static async Task IndexWin32ProgramsAsync()
{
- var win32S = Win32.All(_settings);
- _win32s = win32S;
- ResetCache();
- _win32Storage.SaveAsync(_win32s);
- _settings.LastIndexTime = DateTime.Now;
+ await _win32sLock.WaitAsync();
+ try
+ {
+ var win32S = Win32.All(_settings);
+ _win32s.Clear();
+ foreach (var win32 in win32S)
+ {
+ _win32s.Add(win32);
+ }
+ ResetCache();
+ await Context.API.SaveCacheBinaryStorageAsync>(Win32CacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
+ _settings.LastIndexTime = DateTime.Now;
+ }
+ catch (Exception e)
+ {
+ Log.Exception("|Flow.Launcher.Plugin.Program.Main|Failed to index Win32 programs", e);
+ }
+ finally
+ {
+ _win32sLock.Release();
+ }
}
- public static void IndexUwpPrograms()
+ public static async Task IndexUwpProgramsAsync()
{
- var applications = UWPPackage.All(_settings);
- _uwps = applications;
- ResetCache();
- _uwpStorage.SaveAsync(_uwps);
- _settings.LastIndexTime = DateTime.Now;
+ await _uwpsLock.WaitAsync();
+ try
+ {
+ var uwps = UWPPackage.All(_settings);
+ _uwps.Clear();
+ foreach (var uwp in uwps)
+ {
+ _uwps.Add(uwp);
+ }
+ ResetCache();
+ await Context.API.SaveCacheBinaryStorageAsync>(UwpCacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
+ _settings.LastIndexTime = DateTime.Now;
+ }
+ catch (Exception e)
+ {
+ Log.Exception("|Flow.Launcher.Plugin.Program.Main|Failed to index Uwp programs", e);
+ }
+ finally
+ {
+ _uwpsLock.Release();
+ }
}
public static async Task IndexProgramsAsync()
{
- var a = Task.Run(() =>
+ var win32Task = Task.Run(async () =>
{
- Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs);
+ await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32ProgramsAsync);
});
- var b = Task.Run(() =>
+ var uwpTask = Task.Run(async () =>
{
- Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|UWPProgram index cost", IndexUwpPrograms);
+ await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|UWPProgram index cost", IndexUwpProgramsAsync);
});
- await Task.WhenAll(a, b).ConfigureAwait(false);
+
+ await Task.WhenAll(win32Task, uwpTask).ConfigureAwait(false);
}
internal static void ResetCache()
@@ -314,7 +352,7 @@ internal static void ResetCache()
public Control CreateSettingPanel()
{
- return new ProgramSetting(Context, _settings, _win32s, _uwps);
+ return new ProgramSetting(Context, _settings);
}
public string GetTranslatedPluginTitle()
@@ -342,7 +380,7 @@ public List LoadContextMenus(Result selectedResult)
Title = Context.API.GetTranslation("flowlauncher_plugin_program_disable_program"),
Action = c =>
{
- DisableProgram(program);
+ _ = DisableProgramAsync(program);
Context.API.ShowMsg(
Context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_success"),
Context.API.GetTranslation(
@@ -358,30 +396,43 @@ public List LoadContextMenus(Result selectedResult)
return menuOptions;
}
- private static void DisableProgram(IProgram programToDelete)
+ private static async Task DisableProgramAsync(IProgram programToDelete)
{
if (_settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
return;
+ await _uwpsLock.WaitAsync();
if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
{
var program = _uwps.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
program.Enabled = false;
_settings.DisabledProgramSources.Add(new ProgramSource(program));
- _ = Task.Run(() =>
- {
- IndexUwpPrograms();
- });
+ _uwpsLock.Release();
+
+ // Reindex UWP programs
+ _ = Task.Run(IndexUwpProgramsAsync);
+ return;
}
- else if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
+ else
+ {
+ _uwpsLock.Release();
+ }
+
+ await _win32sLock.WaitAsync();
+ if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
{
var program = _win32s.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
program.Enabled = false;
_settings.DisabledProgramSources.Add(new ProgramSource(program));
- _ = Task.Run(() =>
- {
- IndexWin32Programs();
- });
+ _win32sLock.Release();
+
+ // Reindex Win32 programs
+ _ = Task.Run(IndexWin32ProgramsAsync);
+ return;
+ }
+ else
+ {
+ _win32sLock.Release();
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
index 654897cc57f..bf100ed7ee3 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
@@ -317,7 +317,7 @@ public static async Task WatchPackageChange()
{
await Task.Delay(3000).ConfigureAwait(false);
PackageChangeChannel.Reader.TryRead(out _);
- await Task.Run(Main.IndexUwpPrograms);
+ await Task.Run(Main.IndexUwpProgramsAsync);
}
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
index a64a708efd3..06be2a628cf 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
@@ -797,7 +797,7 @@ public static async Task MonitorDirectoryChangeAsync()
{
}
- await Task.Run(Main.IndexWin32Programs);
+ await Task.Run(Main.IndexWin32ProgramsAsync);
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs b/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs
index e4d7c323a5f..b89a2a6ba9e 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
using Flow.Launcher.Plugin.Program.Views.Models;
namespace Flow.Launcher.Plugin.Program.Views.Commands
@@ -15,21 +16,24 @@ internal static List LoadProgramSources()
.ToList();
}
- internal static void DisplayAllPrograms()
+ internal static async Task DisplayAllProgramsAsync()
{
+ await Main._win32sLock.WaitAsync();
var win32 = Main._win32s
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.Select(x => new ProgramSource(x));
+ ProgramSetting.ProgramSettingDisplayList.AddRange(win32);
+ Main._win32sLock.Release();
+ await Main._uwpsLock.WaitAsync();
var uwp = Main._uwps
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.Select(x => new ProgramSource(x));
-
- ProgramSetting.ProgramSettingDisplayList.AddRange(win32);
ProgramSetting.ProgramSettingDisplayList.AddRange(uwp);
+ Main._uwpsLock.Release();
}
- internal static void SetProgramSourcesStatus(List selectedProgramSourcesToDisable, bool status)
+ internal static async Task SetProgramSourcesStatusAsync(List selectedProgramSourcesToDisable, bool status)
{
foreach(var program in ProgramSetting.ProgramSettingDisplayList)
{
@@ -39,14 +43,17 @@ internal static void SetProgramSourcesStatus(List selectedProgram
}
}
- foreach(var program in Main._win32s)
+ await Main._win32sLock.WaitAsync();
+ foreach (var program in Main._win32s)
{
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
{
program.Enabled = status;
}
}
+ Main._win32sLock.Release();
+ await Main._uwpsLock.WaitAsync();
foreach (var program in Main._uwps)
{
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
@@ -54,6 +61,7 @@ internal static void SetProgramSourcesStatus(List selectedProgram
program.Enabled = status;
}
}
+ Main._uwpsLock.Release();
}
internal static void StoreDisabledInSettings()
@@ -72,12 +80,22 @@ internal static void RemoveDisabledFromSettings()
Main._settings.DisabledProgramSources.RemoveAll(t1 => t1.Enabled);
}
- internal static bool IsReindexRequired(this List selectedItems)
+ internal static async Task IsReindexRequiredAsync(this List selectedItems)
{
// Not in cache
- if (selectedItems.Any(t1 => t1.Enabled && !Main._uwps.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
+ await Main._win32sLock.WaitAsync();
+ await Main._uwpsLock.WaitAsync();
+ try
+ {
+ if (selectedItems.Any(t1 => t1.Enabled && !Main._uwps.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
&& selectedItems.Any(t1 => t1.Enabled && !Main._win32s.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)))
- return true;
+ return true;
+ }
+ finally
+ {
+ Main._win32sLock.Release();
+ Main._uwpsLock.Release();
+ }
// ProgramSources holds list of user added directories,
// so when we enable/disable we need to reindex to show/not show the programs
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs
index 91864cb680c..c42bd4f305d 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs
@@ -18,8 +18,8 @@ namespace Flow.Launcher.Plugin.Program.Views
///
public partial class ProgramSetting : UserControl
{
- private PluginInitContext context;
- private Settings _settings;
+ private readonly PluginInitContext context;
+ private readonly Settings _settings;
private GridViewColumnHeader _lastHeaderClicked;
private ListSortDirection _lastDirection;
@@ -109,7 +109,7 @@ public bool EnableUWP
public bool ShowUWPCheckbox => UWPPackage.SupportUWP();
- public ProgramSetting(PluginInitContext context, Settings settings, Win32[] win32s, UWPApp[] uwps)
+ public ProgramSetting(PluginInitContext context, Settings settings)
{
this.context = context;
_settings = settings;
@@ -183,7 +183,7 @@ private void btnEditProgramSource_OnClick(object sender, RoutedEventArgs e)
EditProgramSource(selectedProgramSource);
}
- private void EditProgramSource(ProgramSource selectedProgramSource)
+ private async void EditProgramSource(ProgramSource selectedProgramSource)
{
if (selectedProgramSource == null)
{
@@ -202,13 +202,13 @@ private void EditProgramSource(ProgramSource selectedProgramSource)
{
if (selectedProgramSource.Enabled)
{
- ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource },
+ await ProgramSettingDisplay.SetProgramSourcesStatusAsync(new List { selectedProgramSource },
true); // sync status in win32, uwp and disabled
ProgramSettingDisplay.RemoveDisabledFromSettings();
}
else
{
- ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource },
+ await ProgramSettingDisplay.SetProgramSourcesStatusAsync(new List { selectedProgramSource },
false);
ProgramSettingDisplay.StoreDisabledInSettings();
}
@@ -277,14 +277,14 @@ private void programSourceView_Drop(object sender, DragEventArgs e)
}
}
- private void btnLoadAllProgramSource_OnClick(object sender, RoutedEventArgs e)
+ private async void btnLoadAllProgramSource_OnClick(object sender, RoutedEventArgs e)
{
- ProgramSettingDisplay.DisplayAllPrograms();
+ await ProgramSettingDisplay.DisplayAllProgramsAsync();
ViewRefresh();
}
- private void btnProgramSourceStatus_OnClick(object sender, RoutedEventArgs e)
+ private async void btnProgramSourceStatus_OnClick(object sender, RoutedEventArgs e)
{
var selectedItems = programSourceView
.SelectedItems.Cast()
@@ -311,18 +311,18 @@ private void btnProgramSourceStatus_OnClick(object sender, RoutedEventArgs e)
}
else if (HasMoreOrEqualEnabledItems(selectedItems))
{
- ProgramSettingDisplay.SetProgramSourcesStatus(selectedItems, false);
+ await ProgramSettingDisplay.SetProgramSourcesStatusAsync(selectedItems, false);
ProgramSettingDisplay.StoreDisabledInSettings();
}
else
{
- ProgramSettingDisplay.SetProgramSourcesStatus(selectedItems, true);
+ await ProgramSettingDisplay.SetProgramSourcesStatusAsync(selectedItems, true);
ProgramSettingDisplay.RemoveDisabledFromSettings();
}
- if (selectedItems.IsReindexRequired())
+ if (await selectedItems.IsReindexRequiredAsync())
ReIndexing();
programSourceView.SelectedItems.Clear();