Skip to content

Commit 72b4ff3

Browse files
authored
Merge pull request #3898 from Flow-Launcher/storage_api_method
Load images into ImageCache & Use non-async method in ImageCache
2 parents e730235 + 3727bff commit 72b4ff3

File tree

4 files changed

+93
-67
lines changed

4 files changed

+93
-67
lines changed

Flow.Launcher.Infrastructure/Image/ImageLoader.cs

Lines changed: 35 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ public static class ImageLoader
1919
private static readonly string ClassName = nameof(ImageLoader);
2020

2121
private static readonly ImageCache ImageCache = new();
22-
private static SemaphoreSlim storageLock { get; } = new SemaphoreSlim(1, 1);
22+
private static Lock storageLock { get; } = new();
2323
private static BinaryStorage<List<(string, bool)>> _storage;
2424
private static readonly ConcurrentDictionary<string, string> GuidToKey = new();
2525
private static IImageHashGenerator _hashGenerator;
2626
private static readonly bool EnableImageHash = true;
27-
public static ImageSource Image { get; } = new BitmapImage(new Uri(Constant.ImageIcon));
28-
public static ImageSource MissingImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon));
29-
public static ImageSource LoadingImage { get; } = new BitmapImage(new Uri(Constant.LoadingImgIcon));
27+
public static ImageSource Image => ImageCache[Constant.ImageIcon, false];
28+
public static ImageSource MissingImage => ImageCache[Constant.MissingImgIcon, false];
29+
public static ImageSource LoadingImage => ImageCache[Constant.LoadingImgIcon, false];
3030
public const int SmallIconSize = 64;
3131
public const int FullIconSize = 256;
3232
public const int FullImageSize = 320;
@@ -36,20 +36,25 @@ public static class ImageLoader
3636

3737
public static async Task InitializeAsync()
3838
{
39-
_storage = new BinaryStorage<List<(string, bool)>>("Image");
40-
_hashGenerator = new ImageHashGenerator();
39+
var usage = await Task.Run(() =>
40+
{
41+
_storage = new BinaryStorage<List<(string, bool)>>("Image");
42+
_hashGenerator = new ImageHashGenerator();
4143

42-
var usage = await LoadStorageToConcurrentDictionaryAsync();
43-
_storage.ClearData();
44+
var usage = LoadStorageToConcurrentDictionary();
45+
_storage.ClearData();
4446

45-
ImageCache.Initialize(usage);
47+
ImageCache.Initialize(usage);
4648

47-
foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon })
48-
{
49-
ImageSource img = new BitmapImage(new Uri(icon));
50-
img.Freeze();
51-
ImageCache[icon, false] = img;
52-
}
49+
foreach (var icon in new[] { Constant.DefaultIcon, Constant.ImageIcon, Constant.MissingImgIcon, Constant.LoadingImgIcon })
50+
{
51+
ImageSource img = new BitmapImage(new Uri(icon));
52+
img.Freeze();
53+
ImageCache[icon, false] = img;
54+
}
55+
56+
return usage;
57+
});
5358

5459
_ = Task.Run(async () =>
5560
{
@@ -64,42 +69,26 @@ await Stopwatch.InfoAsync(ClassName, "Preload images cost", async () =>
6469
});
6570
}
6671

67-
public static async Task SaveAsync()
72+
public static void Save()
6873
{
69-
await storageLock.WaitAsync();
70-
71-
try
72-
{
73-
await _storage.SaveAsync(ImageCache.EnumerateEntries()
74-
.Select(x => x.Key)
75-
.ToList());
76-
}
77-
catch (System.Exception e)
74+
lock (storageLock)
7875
{
79-
Log.Exception(ClassName, "Failed to save image cache to file", e);
80-
}
81-
finally
82-
{
83-
storageLock.Release();
76+
try
77+
{
78+
_storage.Save([.. ImageCache.EnumerateEntries().Select(x => x.Key)]);
79+
}
80+
catch (System.Exception e)
81+
{
82+
Log.Exception(ClassName, "Failed to save image cache to file", e);
83+
}
8484
}
8585
}
8686

87-
public static async Task WaitSaveAsync()
87+
private static List<(string, bool)> LoadStorageToConcurrentDictionary()
8888
{
89-
await storageLock.WaitAsync();
90-
storageLock.Release();
91-
}
92-
93-
private static async Task<List<(string, bool)>> LoadStorageToConcurrentDictionaryAsync()
94-
{
95-
await storageLock.WaitAsync();
96-
try
97-
{
98-
return await _storage.TryLoadAsync(new List<(string, bool)>());
99-
}
100-
finally
89+
lock (storageLock)
10190
{
102-
storageLock.Release();
91+
return _storage.TryLoad([]);
10392
}
10493
}
10594

@@ -174,7 +163,7 @@ private static async ValueTask<ImageResult> LoadInternalAsync(string path, bool
174163
Log.Exception(ClassName, $"Failed to get thumbnail for {path} on first try", e);
175164
Log.Exception(ClassName, $"Failed to get thumbnail for {path} on second try", e2);
176165

177-
ImageSource image = ImageCache[Constant.MissingImgIcon, false];
166+
ImageSource image = MissingImage;
178167
ImageCache[path, false] = image;
179168
imageResult = new ImageResult(image, ImageType.Error);
180169
}
@@ -273,7 +262,7 @@ private static ImageResult GetThumbnailResult(ref string path, bool loadFullImag
273262
}
274263
else
275264
{
276-
image = ImageCache[Constant.MissingImgIcon, false];
265+
image = MissingImage;
277266
path = Constant.MissingImgIcon;
278267
}
279268

Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
namespace Flow.Launcher.Infrastructure.Storage
1313
{
1414
/// <summary>
15-
/// Stroage object using binary data
15+
/// Storage object using binary data
1616
/// Normally, it has better performance, but not readable
1717
/// </summary>
1818
/// <remarks>
@@ -53,6 +53,45 @@ public BinaryStorage(string filename, string directoryPath = null!)
5353
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
5454
}
5555

56+
public T TryLoad(T defaultData)
57+
{
58+
if (Data != null) return Data;
59+
60+
if (File.Exists(FilePath))
61+
{
62+
if (new FileInfo(FilePath).Length == 0)
63+
{
64+
Log.Error(ClassName, $"Zero length cache file <{FilePath}>");
65+
Data = defaultData;
66+
Save();
67+
}
68+
69+
var bytes = File.ReadAllBytes(FilePath);
70+
Data = Deserialize(bytes, defaultData);
71+
}
72+
else
73+
{
74+
Log.Info(ClassName, "Cache file not exist, load default data");
75+
Data = defaultData;
76+
Save();
77+
}
78+
return Data;
79+
}
80+
81+
private T Deserialize(ReadOnlySpan<byte> bytes, T defaultData)
82+
{
83+
try
84+
{
85+
var t = MemoryPackSerializer.Deserialize<T>(bytes);
86+
return t ?? defaultData;
87+
}
88+
catch (System.Exception e)
89+
{
90+
Log.Exception(ClassName, $"Deserialize error for file <{FilePath}>", e);
91+
return defaultData;
92+
}
93+
}
94+
5695
public async ValueTask<T> TryLoadAsync(T defaultData)
5796
{
5897
if (Data != null) return Data;
@@ -79,26 +118,31 @@ public async ValueTask<T> TryLoadAsync(T defaultData)
79118
return Data;
80119
}
81120

82-
private static async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
121+
private async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
83122
{
84123
try
85124
{
86125
var t = await MemoryPackSerializer.DeserializeAsync<T>(stream);
87126
return t ?? defaultData;
88127
}
89-
catch (System.Exception)
128+
catch (System.Exception e)
90129
{
91-
// Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e);
130+
Log.Exception(ClassName, $"Deserialize error for file <{FilePath}>", e);
92131
return defaultData;
93132
}
94133
}
95134

96135
public void Save()
136+
{
137+
Save(Data.NonNull());
138+
}
139+
140+
public void Save(T data)
97141
{
98142
// User may delete the directory, so we need to check it
99143
FilesFolders.ValidateDirectory(DirectoryPath);
100144

101-
var serialized = MemoryPackSerializer.Serialize(Data);
145+
var serialized = MemoryPackSerializer.Serialize(data);
102146
File.WriteAllBytes(FilePath, serialized);
103147
}
104148

@@ -107,15 +151,6 @@ public async ValueTask SaveAsync()
107151
await SaveAsync(Data.NonNull());
108152
}
109153

110-
// ImageCache need to convert data into concurrent dictionary for usage,
111-
// so we would better to clear the data
112-
public void ClearData()
113-
{
114-
Data = default;
115-
}
116-
117-
// ImageCache storages data in its class,
118-
// so we need to pass it to SaveAsync
119154
public async ValueTask SaveAsync(T data)
120155
{
121156
// User may delete the directory, so we need to check it
@@ -124,5 +159,12 @@ public async ValueTask SaveAsync(T data)
124159
await using var stream = new FileStream(FilePath, FileMode.Create);
125160
await MemoryPackSerializer.SerializeAsync(stream, data);
126161
}
162+
163+
// ImageCache need to convert data into concurrent dictionary for usage,
164+
// so we would better to clear the data
165+
public void ClearData()
166+
{
167+
Data = default;
168+
}
127169
}
128170
}

Flow.Launcher/MainWindow.xaml.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
using Flow.Launcher.Core.Resource;
1919
using Flow.Launcher.Infrastructure;
2020
using Flow.Launcher.Infrastructure.Hotkey;
21-
using Flow.Launcher.Infrastructure.Image;
2221
using Flow.Launcher.Infrastructure.DialogJump;
2322
using Flow.Launcher.Infrastructure.UserSettings;
2423
using Flow.Launcher.Plugin;
@@ -358,7 +357,6 @@ private async void OnClosing(object sender, CancelEventArgs e)
358357
_notifyIcon.Visible = false;
359358
App.API.SaveAppAllSettings();
360359
e.Cancel = true;
361-
await ImageLoader.WaitSaveAsync();
362360
await PluginManager.DisposePluginsAsync();
363361
Notification.Uninstall();
364362
// After plugins are all disposed, we shutdown application to close app

Flow.Launcher/PublicAPIInstance.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public void ChangeQuery(string query, bool requery = false)
7575
}
7676

7777
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
78-
public async void RestartApp()
78+
public void RestartApp()
7979
{
8080
_mainVM.Hide();
8181

@@ -84,9 +84,6 @@ public async void RestartApp()
8484
// which will cause ungraceful exit
8585
SaveAppAllSettings();
8686

87-
// Wait for all image caches to be saved before restarting
88-
await ImageLoader.WaitSaveAsync();
89-
9087
// Restart requires Squirrel's Update.exe to be present in the parent folder,
9188
// it is only published from the project's release pipeline. When debugging without it,
9289
// the project may not restart or just terminates. This is expected.
@@ -116,8 +113,8 @@ public void SaveAppAllSettings()
116113
_settings.Save();
117114
PluginManager.Save();
118115
_mainVM.Save();
116+
ImageLoader.Save();
119117
}
120-
_ = ImageLoader.SaveAsync();
121118
}
122119

123120
public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync();

0 commit comments

Comments
 (0)