mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #3898 from Flow-Launcher/storage_api_method
Load images into ImageCache & Use non-async method in ImageCache
This commit is contained in:
commit
72b4ff31ed
4 changed files with 95 additions and 69 deletions
|
|
@ -19,14 +19,14 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
private static readonly string ClassName = nameof(ImageLoader);
|
||||
|
||||
private static readonly ImageCache ImageCache = new();
|
||||
private static SemaphoreSlim storageLock { get; } = new SemaphoreSlim(1, 1);
|
||||
private static Lock storageLock { get; } = new();
|
||||
private static BinaryStorage<List<(string, bool)>> _storage;
|
||||
private static readonly ConcurrentDictionary<string, string> GuidToKey = new();
|
||||
private static IImageHashGenerator _hashGenerator;
|
||||
private static readonly bool EnableImageHash = true;
|
||||
public static ImageSource Image { get; } = new BitmapImage(new Uri(Constant.ImageIcon));
|
||||
public static ImageSource MissingImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon));
|
||||
public static ImageSource LoadingImage { get; } = new BitmapImage(new Uri(Constant.LoadingImgIcon));
|
||||
public static ImageSource Image => ImageCache[Constant.ImageIcon, false];
|
||||
public static ImageSource MissingImage => ImageCache[Constant.MissingImgIcon, false];
|
||||
public static ImageSource LoadingImage => ImageCache[Constant.LoadingImgIcon, false];
|
||||
public const int SmallIconSize = 64;
|
||||
public const int FullIconSize = 256;
|
||||
public const int FullImageSize = 320;
|
||||
|
|
@ -36,20 +36,25 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
|
||||
public static async Task InitializeAsync()
|
||||
{
|
||||
_storage = new BinaryStorage<List<(string, bool)>>("Image");
|
||||
_hashGenerator = new ImageHashGenerator();
|
||||
|
||||
var usage = await LoadStorageToConcurrentDictionaryAsync();
|
||||
_storage.ClearData();
|
||||
|
||||
ImageCache.Initialize(usage);
|
||||
|
||||
foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon })
|
||||
var usage = await Task.Run(() =>
|
||||
{
|
||||
ImageSource img = new BitmapImage(new Uri(icon));
|
||||
img.Freeze();
|
||||
ImageCache[icon, false] = img;
|
||||
}
|
||||
_storage = new BinaryStorage<List<(string, bool)>>("Image");
|
||||
_hashGenerator = new ImageHashGenerator();
|
||||
|
||||
var usage = LoadStorageToConcurrentDictionary();
|
||||
_storage.ClearData();
|
||||
|
||||
ImageCache.Initialize(usage);
|
||||
|
||||
foreach (var icon in new[] { Constant.DefaultIcon, Constant.ImageIcon, Constant.MissingImgIcon, Constant.LoadingImgIcon })
|
||||
{
|
||||
ImageSource img = new BitmapImage(new Uri(icon));
|
||||
img.Freeze();
|
||||
ImageCache[icon, false] = img;
|
||||
}
|
||||
|
||||
return usage;
|
||||
});
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
|
|
@ -64,42 +69,26 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
});
|
||||
}
|
||||
|
||||
public static async Task SaveAsync()
|
||||
public static void Save()
|
||||
{
|
||||
await storageLock.WaitAsync();
|
||||
|
||||
try
|
||||
lock (storageLock)
|
||||
{
|
||||
await _storage.SaveAsync(ImageCache.EnumerateEntries()
|
||||
.Select(x => x.Key)
|
||||
.ToList());
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Log.Exception(ClassName, "Failed to save image cache to file", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
storageLock.Release();
|
||||
try
|
||||
{
|
||||
_storage.Save([.. ImageCache.EnumerateEntries().Select(x => x.Key)]);
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Log.Exception(ClassName, "Failed to save image cache to file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task WaitSaveAsync()
|
||||
private static List<(string, bool)> LoadStorageToConcurrentDictionary()
|
||||
{
|
||||
await storageLock.WaitAsync();
|
||||
storageLock.Release();
|
||||
}
|
||||
|
||||
private static async Task<List<(string, bool)>> LoadStorageToConcurrentDictionaryAsync()
|
||||
{
|
||||
await storageLock.WaitAsync();
|
||||
try
|
||||
lock (storageLock)
|
||||
{
|
||||
return await _storage.TryLoadAsync(new List<(string, bool)>());
|
||||
}
|
||||
finally
|
||||
{
|
||||
storageLock.Release();
|
||||
return _storage.TryLoad([]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +163,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
Log.Exception(ClassName, $"Failed to get thumbnail for {path} on first try", e);
|
||||
Log.Exception(ClassName, $"Failed to get thumbnail for {path} on second try", e2);
|
||||
|
||||
ImageSource image = ImageCache[Constant.MissingImgIcon, false];
|
||||
ImageSource image = MissingImage;
|
||||
ImageCache[path, false] = image;
|
||||
imageResult = new ImageResult(image, ImageType.Error);
|
||||
}
|
||||
|
|
@ -273,7 +262,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
}
|
||||
else
|
||||
{
|
||||
image = ImageCache[Constant.MissingImgIcon, false];
|
||||
image = MissingImage;
|
||||
path = Constant.MissingImgIcon;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ using MemoryPack;
|
|||
namespace Flow.Launcher.Infrastructure.Storage
|
||||
{
|
||||
/// <summary>
|
||||
/// Stroage object using binary data
|
||||
/// Storage object using binary data
|
||||
/// Normally, it has better performance, but not readable
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
|
|
@ -53,6 +53,45 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
|
||||
}
|
||||
|
||||
public T TryLoad(T defaultData)
|
||||
{
|
||||
if (Data != null) return Data;
|
||||
|
||||
if (File.Exists(FilePath))
|
||||
{
|
||||
if (new FileInfo(FilePath).Length == 0)
|
||||
{
|
||||
Log.Error(ClassName, $"Zero length cache file <{FilePath}>");
|
||||
Data = defaultData;
|
||||
Save();
|
||||
}
|
||||
|
||||
var bytes = File.ReadAllBytes(FilePath);
|
||||
Data = Deserialize(bytes, defaultData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Info(ClassName, "Cache file not exist, load default data");
|
||||
Data = defaultData;
|
||||
Save();
|
||||
}
|
||||
return Data;
|
||||
}
|
||||
|
||||
private T Deserialize(ReadOnlySpan<byte> bytes, T defaultData)
|
||||
{
|
||||
try
|
||||
{
|
||||
var t = MemoryPackSerializer.Deserialize<T>(bytes);
|
||||
return t ?? defaultData;
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Log.Exception(ClassName, $"Deserialize error for file <{FilePath}>", e);
|
||||
return defaultData;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<T> TryLoadAsync(T defaultData)
|
||||
{
|
||||
if (Data != null) return Data;
|
||||
|
|
@ -79,26 +118,31 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
return Data;
|
||||
}
|
||||
|
||||
private static async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
|
||||
private async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
|
||||
{
|
||||
try
|
||||
{
|
||||
var t = await MemoryPackSerializer.DeserializeAsync<T>(stream);
|
||||
return t ?? defaultData;
|
||||
}
|
||||
catch (System.Exception)
|
||||
catch (System.Exception e)
|
||||
{
|
||||
// Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e);
|
||||
Log.Exception(ClassName, $"Deserialize error for file <{FilePath}>", e);
|
||||
return defaultData;
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
Save(Data.NonNull());
|
||||
}
|
||||
|
||||
public void Save(T data)
|
||||
{
|
||||
// User may delete the directory, so we need to check it
|
||||
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||
|
||||
var serialized = MemoryPackSerializer.Serialize(Data);
|
||||
var serialized = MemoryPackSerializer.Serialize(data);
|
||||
File.WriteAllBytes(FilePath, serialized);
|
||||
}
|
||||
|
||||
|
|
@ -107,15 +151,6 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
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)
|
||||
{
|
||||
// User may delete the directory, so we need to check it
|
||||
|
|
@ -124,5 +159,12 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
await using var stream = new FileStream(FilePath, FileMode.Create);
|
||||
await MemoryPackSerializer.SerializeAsync(stream, data);
|
||||
}
|
||||
|
||||
// ImageCache need to convert data into concurrent dictionary for usage,
|
||||
// so we would better to clear the data
|
||||
public void ClearData()
|
||||
{
|
||||
Data = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ using Flow.Launcher.Core.Plugin;
|
|||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
using Flow.Launcher.Infrastructure.DialogJump;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
|
@ -358,7 +357,6 @@ namespace Flow.Launcher
|
|||
_notifyIcon.Visible = false;
|
||||
App.API.SaveAppAllSettings();
|
||||
e.Cancel = true;
|
||||
await ImageLoader.WaitSaveAsync();
|
||||
await PluginManager.DisposePluginsAsync();
|
||||
Notification.Uninstall();
|
||||
// After plugins are all disposed, we shutdown application to close app
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ namespace Flow.Launcher
|
|||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
|
||||
public async void RestartApp()
|
||||
public void RestartApp()
|
||||
{
|
||||
_mainVM.Hide();
|
||||
|
||||
|
|
@ -84,9 +84,6 @@ namespace Flow.Launcher
|
|||
// 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.
|
||||
|
|
@ -116,8 +113,8 @@ namespace Flow.Launcher
|
|||
_settings.Save();
|
||||
PluginManager.Save();
|
||||
_mainVM.Save();
|
||||
ImageLoader.Save();
|
||||
}
|
||||
_ = ImageLoader.SaveAsync();
|
||||
}
|
||||
|
||||
public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync();
|
||||
|
|
|
|||
Loading…
Reference in a new issue