mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #3410 from Jack251970/binary_storage_api
New API Functions Plugin Cache Storage & Use API Functions for Program Plugin & Remove Reflection with Interfaces
This commit is contained in:
commit
c159b4885e
15 changed files with 378 additions and 129 deletions
|
|
@ -189,5 +189,10 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models
|
|||
{
|
||||
_api.StopLoadingBar();
|
||||
}
|
||||
|
||||
public void SavePluginCaches()
|
||||
{
|
||||
_api.SavePluginCaches();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using Flow.Launcher.Infrastructure.Logger;
|
|||
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 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
|
||||
API.SavePluginSettings();
|
||||
API.SavePluginCaches();
|
||||
}
|
||||
|
||||
public static async ValueTask DisposePluginsAsync()
|
||||
|
|
@ -575,11 +577,11 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
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
|
||||
|
|
|
|||
19
Flow.Launcher.Core/Storage/IRemovable.cs
Normal file
19
Flow.Launcher.Core/Storage/IRemovable.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
namespace Flow.Launcher.Core.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// Remove storage instances from <see cref="Launcher.Plugin.IPublicAPI"/> instance
|
||||
/// </summary>
|
||||
public interface IRemovable
|
||||
{
|
||||
/// <summary>
|
||||
/// Remove all <see cref="Infrastructure.Storage.PluginJsonStorage{T}"/> instances of one plugin
|
||||
/// </summary>
|
||||
/// <param name="assemblyName"></param>
|
||||
public void RemovePluginSettings(string assemblyName);
|
||||
|
||||
/// <summary>
|
||||
/// Remove all <see cref="Infrastructure.Storage.PluginBinaryStorage{T}"/> instances of one plugin
|
||||
/// </summary>
|
||||
/// <param name="cacheDirectory"></param>
|
||||
public void RemovePluginCaches(string cacheDirectory);
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
_hashGenerator = new ImageHashGenerator();
|
||||
|
||||
var usage = await LoadStorageToConcurrentDictionaryAsync();
|
||||
_storage.ClearData();
|
||||
|
||||
ImageCache.Initialize(usage);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -12,44 +15,55 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
/// Normally, it has better performance, but not readable
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It utilize MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
|
||||
/// It utilizes MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
|
||||
/// </remarks>
|
||||
public class BinaryStorage<T>
|
||||
public class BinaryStorage<T> : ISavable
|
||||
{
|
||||
protected T? Data;
|
||||
|
||||
public const string FileSuffix = ".cache";
|
||||
|
||||
// Let the derived class to set the file path
|
||||
public BinaryStorage(string filename, string directoryPath = null)
|
||||
{
|
||||
directoryPath ??= DataLocation.CacheDirectory;
|
||||
FilesFolders.ValidateDirectory(directoryPath);
|
||||
protected string FilePath { get; init; } = null!;
|
||||
|
||||
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
|
||||
protected string DirectoryPath { get; init; } = null!;
|
||||
|
||||
// Let the derived class to set the file path
|
||||
protected BinaryStorage()
|
||||
{
|
||||
}
|
||||
|
||||
public string FilePath { get; }
|
||||
public BinaryStorage(string filename)
|
||||
{
|
||||
DirectoryPath = DataLocation.CacheDirectory;
|
||||
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||
|
||||
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
|
||||
}
|
||||
|
||||
public async ValueTask<T> 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<T> DeserializeAsync(Stream stream, T defaultData)
|
||||
|
|
@ -57,7 +71,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
try
|
||||
{
|
||||
var t = await MemoryPackSerializer.DeserializeAsync<T>(stream);
|
||||
return t;
|
||||
return t ?? defaultData;
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
|
|
@ -66,6 +80,27 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Serialize object using json format.
|
||||
/// </summary>
|
||||
public class JsonStorage<T> where T : new()
|
||||
public class JsonStorage<T> : ISavable where T : new()
|
||||
{
|
||||
protected T? Data;
|
||||
|
||||
|
|
|
|||
49
Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs
Normal file
49
Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs
Normal file
|
|
@ -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<T> : BinaryStorage<T> 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<IPublicAPI>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -352,6 +352,38 @@ namespace Flow.Launcher.Plugin
|
|||
public void StopLoadingBar();
|
||||
|
||||
/// <summary>
|
||||
/// Save all Flow's plugins caches
|
||||
/// </summary>
|
||||
void SavePluginCaches();
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type for deserialization</typeparam>
|
||||
/// <param name="cacheName">Cache file name</param>
|
||||
/// <param name="cacheDirectory">Cache directory from plugin metadata</param>
|
||||
/// <param name="defaultData">Default data to return</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// BinaryStorage utilizes MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
|
||||
/// </remarks>
|
||||
Task<T> LoadCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory, T defaultData) where T : new();
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type for Serialization</typeparam>
|
||||
/// <param name="cacheName">Cache file name</param>
|
||||
/// <param name="cacheDirectory">Cache directory from plugin metadata</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// BinaryStorage utilizes MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
|
||||
/// </remarks>
|
||||
Task SaveCacheBinaryStorageAsync<T>(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.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
namespace Flow.Launcher.Plugin
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For storing plugin settings, prefer <see cref="IPublicAPI.LoadSettingJsonStorage{T}"/>
|
||||
/// or <see cref="IPublicAPI.SaveSettingJsonStorage{T}"/>.
|
||||
/// Once called, your settings will be automatically saved by Flow.
|
||||
/// or <see cref="IPublicAPI.SaveSettingJsonStorage{T}"/>.
|
||||
/// For storing plugin caches, prefer <see cref="IPublicAPI.LoadCacheBinaryStorageAsync{T}"/>
|
||||
/// or <see cref="IPublicAPI.SaveCacheBinaryStorageAsync{T}(string, string)"/>.
|
||||
/// Once called, those settings and caches will be automatically saved by Flow.
|
||||
/// </remarks>
|
||||
public interface ISavable : IFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Save additional plugin data, such as cache.
|
||||
/// Save additional plugin data.
|
||||
/// </summary>
|
||||
void Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ using Flow.Launcher.Core;
|
|||
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 @@ using Squirrel;
|
|||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
public class PublicAPIInstance : IPublicAPI
|
||||
public class PublicAPIInstance : IPublicAPI, IRemovable
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
private readonly Internationalization _translater;
|
||||
|
|
@ -223,20 +224,17 @@ namespace Flow.Launcher
|
|||
var name = value.GetType().GetField("AssemblyName")?.GetValue(value)?.ToString();
|
||||
if (name == assemblyName)
|
||||
{
|
||||
_pluginJsonStorages.Remove(key, out var _);
|
||||
_pluginJsonStorages.TryRemove(key, out var _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save plugin settings.
|
||||
/// </summary>
|
||||
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 @@ namespace Flow.Launcher
|
|||
((PluginJsonStorage<T>)_pluginJsonStorages[type]).Save();
|
||||
}
|
||||
|
||||
public void SaveJsonStorage<T>(T settings) where T : new()
|
||||
{
|
||||
var type = typeof(T);
|
||||
_pluginJsonStorages[type] = new PluginJsonStorage<T>(settings);
|
||||
|
||||
((PluginJsonStorage<T>)_pluginJsonStorages[type]).Save();
|
||||
}
|
||||
|
||||
public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null)
|
||||
{
|
||||
using var explorer = new Process();
|
||||
|
|
@ -370,6 +360,48 @@ namespace Flow.Launcher
|
|||
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, 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<T> LoadCacheBinaryStorageAsync<T>(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<T>(cacheName, cacheDirectory);
|
||||
|
||||
return await ((PluginBinaryStorage<T>)_pluginBinaryStorages[(cacheName, cacheDirectory, type)]).TryLoadAsync(defaultData);
|
||||
}
|
||||
|
||||
public async Task SaveCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory) where T : new()
|
||||
{
|
||||
var type = typeof(T);
|
||||
if (!_pluginBinaryStorages.ContainsKey((cacheName, cacheDirectory, type)))
|
||||
_pluginBinaryStorages[(cacheName, cacheDirectory, type)] = new PluginBinaryStorage<T>(cacheName, cacheDirectory);
|
||||
|
||||
await ((PluginBinaryStorage<T>)_pluginBinaryStorages[(cacheName, cacheDirectory, type)]).SaveAsync();
|
||||
}
|
||||
|
||||
public ValueTask<ImageSource> LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true) =>
|
||||
ImageLoader.LoadAsync(path, loadFullImage, cacheImage);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using System.Threading;
|
|||
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,19 +18,20 @@ using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
|
|||
|
||||
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<Win32> _win32s { get; private set; }
|
||||
internal static List<UWPApp> _uwps { get; private set; }
|
||||
internal static Settings _settings { get; private set; }
|
||||
|
||||
internal static SemaphoreSlim _win32sLock = new(1, 1);
|
||||
internal static SemaphoreSlim _uwpsLock = new(1, 1);
|
||||
|
||||
internal static PluginInitContext Context { get; private set; }
|
||||
|
||||
private static BinaryStorage<Win32[]> _win32Storage;
|
||||
private static BinaryStorage<UWPApp[]> _uwpStorage;
|
||||
|
||||
private static readonly List<Result> emptyResults = new();
|
||||
|
||||
private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 };
|
||||
|
|
@ -77,22 +77,15 @@ namespace Flow.Launcher.Plugin.Program
|
|||
|
||||
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<List<Result>> 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 @@ namespace Flow.Launcher.Plugin.Program
|
|||
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 @@ namespace Flow.Launcher.Plugin.Program
|
|||
|
||||
_settings = context.API.LoadSettingJsonStorage<Settings>();
|
||||
|
||||
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 @@ namespace Flow.Launcher.Plugin.Program
|
|||
}
|
||||
|
||||
// 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[]>("Win32", Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
||||
_win32s = await _win32Storage.TryLoadAsync(Array.Empty<Win32>());
|
||||
_uwpStorage = new BinaryStorage<UWPApp[]>("UWP", Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
||||
_uwps = await _uwpStorage.TryLoadAsync(Array.Empty<UWPApp>());
|
||||
});
|
||||
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}>");
|
||||
await _win32sLock.WaitAsync();
|
||||
_win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCacheDirectory, new List<Win32>());
|
||||
_win32sCount = _win32s.Count;
|
||||
_win32sLock.Release();
|
||||
|
||||
bool cacheEmpty = !_win32s.Any() || !_uwps.Any();
|
||||
await _uwpsLock.WaitAsync();
|
||||
_uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCacheDirectory, new List<UWPApp>());
|
||||
_uwpsCount = _uwps.Count;
|
||||
_uwpsLock.Release();
|
||||
});
|
||||
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}>");
|
||||
|
||||
var cacheEmpty = _win32sCount == 0 || _uwpsCount == 0;
|
||||
|
||||
if (cacheEmpty || _settings.LastIndexTime.AddHours(30) < DateTime.Now)
|
||||
{
|
||||
|
|
@ -273,36 +278,69 @@ namespace Flow.Launcher.Plugin.Program
|
|||
}
|
||||
}
|
||||
|
||||
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<List<Win32>>(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<List<UWPApp>>(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 @@ namespace Flow.Launcher.Plugin.Program
|
|||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
return new ProgramSetting(Context, _settings, _win32s, _uwps);
|
||||
return new ProgramSetting(Context, _settings);
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
|
|
@ -342,7 +380,7 @@ namespace Flow.Launcher.Plugin.Program
|
|||
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 @@ namespace Flow.Launcher.Plugin.Program
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
{
|
||||
await Task.Delay(3000).ConfigureAwait(false);
|
||||
PackageChangeChannel.Reader.TryRead(out _);
|
||||
await Task.Run(Main.IndexUwpPrograms);
|
||||
await Task.Run(Main.IndexUwpProgramsAsync);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -797,7 +797,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
{
|
||||
}
|
||||
|
||||
await Task.Run(Main.IndexWin32Programs);
|
||||
await Task.Run(Main.IndexWin32ProgramsAsync);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
|
|||
.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<ProgramSource> selectedProgramSourcesToDisable, bool status)
|
||||
internal static async Task SetProgramSourcesStatusAsync(List<ProgramSource> selectedProgramSourcesToDisable, bool status)
|
||||
{
|
||||
foreach(var program in ProgramSetting.ProgramSettingDisplayList)
|
||||
{
|
||||
|
|
@ -39,14 +43,17 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
|
|||
}
|
||||
}
|
||||
|
||||
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 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
|
|||
program.Enabled = status;
|
||||
}
|
||||
}
|
||||
Main._uwpsLock.Release();
|
||||
}
|
||||
|
||||
internal static void StoreDisabledInSettings()
|
||||
|
|
@ -72,12 +80,22 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
|
|||
Main._settings.DisabledProgramSources.RemoveAll(t1 => t1.Enabled);
|
||||
}
|
||||
|
||||
internal static bool IsReindexRequired(this List<ProgramSource> selectedItems)
|
||||
internal static async Task<bool> IsReindexRequiredAsync(this List<ProgramSource> 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
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
/// </summary>
|
||||
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 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
|
||||
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 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
EditProgramSource(selectedProgramSource);
|
||||
}
|
||||
|
||||
private void EditProgramSource(ProgramSource selectedProgramSource)
|
||||
private async void EditProgramSource(ProgramSource selectedProgramSource)
|
||||
{
|
||||
if (selectedProgramSource == null)
|
||||
{
|
||||
|
|
@ -202,13 +202,13 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
{
|
||||
if (selectedProgramSource.Enabled)
|
||||
{
|
||||
ProgramSettingDisplay.SetProgramSourcesStatus(new List<ProgramSource> { selectedProgramSource },
|
||||
await ProgramSettingDisplay.SetProgramSourcesStatusAsync(new List<ProgramSource> { selectedProgramSource },
|
||||
true); // sync status in win32, uwp and disabled
|
||||
ProgramSettingDisplay.RemoveDisabledFromSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramSettingDisplay.SetProgramSourcesStatus(new List<ProgramSource> { selectedProgramSource },
|
||||
await ProgramSettingDisplay.SetProgramSourcesStatusAsync(new List<ProgramSource> { selectedProgramSource },
|
||||
false);
|
||||
ProgramSettingDisplay.StoreDisabledInSettings();
|
||||
}
|
||||
|
|
@ -277,14 +277,14 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
}
|
||||
}
|
||||
|
||||
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<ProgramSource>()
|
||||
|
|
@ -311,18 +311,18 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
}
|
||||
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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue