Skip to content

Commit 062c6b5

Browse files
authored
Merge pull request #2418 from Flow-Launcher/binary_formatter
use memorypack instead of binaryformatter
2 parents ecd237d + d57f718 commit 062c6b5

File tree

9 files changed

+899
-873
lines changed

9 files changed

+899
-873
lines changed

Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
<PrivateAssets>all</PrivateAssets>
5454
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
5555
</PackageReference>
56+
<PackageReference Include="MemoryPack" Version="1.10.0" />
5657
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.7.30" />
5758
<PackageReference Include="NLog" Version="4.7.10" />
5859
<PackageReference Include="NLog.Schema" Version="4.7.10" />

Flow.Launcher.Infrastructure/Image/ImageLoader.cs

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Collections.Generic;
44
using System.IO;
55
using System.Linq;
6+
using System.Threading;
67
using System.Threading.Tasks;
78
using System.Windows.Media;
89
using System.Windows.Media.Imaging;
@@ -15,6 +16,7 @@ namespace Flow.Launcher.Infrastructure.Image
1516
public static class ImageLoader
1617
{
1718
private static readonly ImageCache ImageCache = new();
19+
private static SemaphoreSlim storageLock { get; } = new SemaphoreSlim(1, 1);
1820
private static BinaryStorage<Dictionary<(string, bool), int>> _storage;
1921
private static readonly ConcurrentDictionary<string, string> GuidToKey = new();
2022
private static IImageHashGenerator _hashGenerator;
@@ -25,24 +27,18 @@ public static class ImageLoader
2527
public const int FullIconSize = 256;
2628

2729

28-
private static readonly string[] ImageExtensions =
29-
{
30-
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico"
31-
};
30+
private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" };
3231

33-
public static void Initialize()
32+
public static async Task InitializeAsync()
3433
{
3534
_storage = new BinaryStorage<Dictionary<(string, bool), int>>("Image");
3635
_hashGenerator = new ImageHashGenerator();
3736

38-
var usage = LoadStorageToConcurrentDictionary();
37+
var usage = await LoadStorageToConcurrentDictionaryAsync();
3938

4039
ImageCache.Initialize(usage.ToDictionary(x => x.Key, x => x.Value));
4140

42-
foreach (var icon in new[]
43-
{
44-
Constant.DefaultIcon, Constant.MissingImgIcon
45-
})
41+
foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon })
4642
{
4743
ImageSource img = new BitmapImage(new Uri(icon));
4844
img.Freeze();
@@ -58,29 +54,41 @@ await Stopwatch.NormalAsync("|ImageLoader.Initialize|Preload images cost", async
5854
await LoadAsync(path, isFullImage);
5955
}
6056
});
61-
Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
57+
Log.Info(
58+
$"|ImageLoader.Initialize|Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
6259
});
6360
}
6461

65-
public static void Save()
62+
public static async Task Save()
6663
{
67-
lock (_storage)
64+
await storageLock.WaitAsync();
65+
66+
try
6867
{
69-
_storage.Save(ImageCache.Data
68+
_storage.SaveAsync(ImageCache.Data
7069
.ToDictionary(
7170
x => x.Key,
7271
x => x.Value.usage));
7372
}
73+
finally
74+
{
75+
storageLock.Release();
76+
}
7477
}
7578

76-
private static ConcurrentDictionary<(string, bool), int> LoadStorageToConcurrentDictionary()
79+
private static async Task<ConcurrentDictionary<(string, bool), int>> LoadStorageToConcurrentDictionaryAsync()
7780
{
78-
lock (_storage)
81+
await storageLock.WaitAsync();
82+
try
7983
{
80-
var loaded = _storage.TryLoad(new Dictionary<(string, bool), int>());
84+
var loaded = await _storage.TryLoadAsync(new Dictionary<(string, bool), int>());
8185

8286
return new ConcurrentDictionary<(string, bool), int>(loaded);
8387
}
88+
finally
89+
{
90+
storageLock.Release();
91+
}
8492
}
8593

8694
private class ImageResult
@@ -129,6 +137,7 @@ private static async ValueTask<ImageResult> LoadInternalAsync(string path, bool
129137
ImageCache[path, loadFullImage] = image;
130138
return new ImageResult(image, ImageType.ImageFile);
131139
}
140+
132141
if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
133142
{
134143
var imageSource = new BitmapImage(new Uri(path));
@@ -158,6 +167,7 @@ private static async ValueTask<ImageResult> LoadInternalAsync(string path, bool
158167

159168
return imageResult;
160169
}
170+
161171
private static async Task<BitmapImage> LoadRemoteImageAsync(bool loadFullImage, Uri uriResult)
162172
{
163173
// Download image from url
@@ -173,6 +183,7 @@ private static async Task<BitmapImage> LoadRemoteImageAsync(bool loadFullImage,
173183
image.DecodePixelHeight = SmallIconSize;
174184
image.DecodePixelWidth = SmallIconSize;
175185
}
186+
176187
image.StreamSource = buffer;
177188
image.EndInit();
178189
image.StreamSource = null;
@@ -188,8 +199,8 @@ private static ImageResult GetThumbnailResult(ref string path, bool loadFullImag
188199
if (Directory.Exists(path))
189200
{
190201
/* Directories can also have thumbnails instead of shell icons.
191-
* Generating thumbnails for a bunch of folder results while scrolling
192-
* could have a big impact on performance and Flow.Launcher responsibility.
202+
* Generating thumbnails for a bunch of folder results while scrolling
203+
* could have a big impact on performance and Flow.Launcher responsibility.
193204
* - Solution: just load the icon
194205
*/
195206
type = ImageType.Folder;
@@ -208,9 +219,9 @@ private static ImageResult GetThumbnailResult(ref string path, bool loadFullImag
208219
}
209220
else
210221
{
211-
/* Although the documentation for GetImage on MSDN indicates that
222+
/* Although the documentation for GetImage on MSDN indicates that
212223
* if a thumbnail is available it will return one, this has proved to not
213-
* be the case in many situations while testing.
224+
* be the case in many situations while testing.
214225
* - Solution: explicitly pass the ThumbnailOnly flag
215226
*/
216227
image = GetThumbnail(path, ThumbnailOptions.ThumbnailOnly);
@@ -236,7 +247,8 @@ private static ImageResult GetThumbnailResult(ref string path, bool loadFullImag
236247
return new ImageResult(image, type);
237248
}
238249

239-
private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = ThumbnailOptions.ThumbnailOnly, int size = SmallIconSize)
250+
private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = ThumbnailOptions.ThumbnailOnly,
251+
int size = SmallIconSize)
240252
{
241253
return WindowsThumbnailProvider.GetThumbnail(
242254
path,
@@ -261,17 +273,19 @@ public static async ValueTask<ImageSource> LoadAsync(string path, bool loadFullI
261273

262274
var img = imageResult.ImageSource;
263275
if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache)
264-
{ // we need to get image hash
276+
{
277+
// we need to get image hash
265278
string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null;
266279
if (hash != null)
267280
{
268-
269281
if (GuidToKey.TryGetValue(hash, out string key))
270-
{ // image already exists
282+
{
283+
// image already exists
271284
img = ImageCache[key, loadFullImage] ?? img;
272285
}
273286
else
274-
{ // new guid
287+
{
288+
// new guid
275289

276290
GuidToKey[hash] = path;
277291
}
@@ -289,7 +303,7 @@ private static BitmapImage LoadFullImage(string path)
289303
BitmapImage image = new BitmapImage();
290304
image.BeginInit();
291305
image.CacheOption = BitmapCacheOption.OnLoad;
292-
image.UriSource = new Uri(path);
306+
image.UriSource = new Uri(path);
293307
image.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
294308
image.EndInit();
295309

@@ -314,8 +328,10 @@ private static BitmapImage LoadFullImage(string path)
314328
resizedHeight.EndInit();
315329
return resizedHeight;
316330
}
331+
317332
return resizedWidth;
318333
}
334+
319335
return image;
320336
}
321337
}

Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs

Lines changed: 23 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,115 +4,78 @@
44
using System.Runtime.Serialization;
55
using System.Runtime.Serialization.Formatters;
66
using System.Runtime.Serialization.Formatters.Binary;
7+
using System.Threading.Tasks;
78
using Flow.Launcher.Infrastructure.Logger;
89
using Flow.Launcher.Infrastructure.UserSettings;
10+
using MemoryPack;
911

1012
namespace Flow.Launcher.Infrastructure.Storage
1113
{
12-
#pragma warning disable SYSLIB0011 // BinaryFormatter is obsolete.
1314
/// <summary>
1415
/// Stroage object using binary data
1516
/// Normally, it has better performance, but not readable
1617
/// </summary>
18+
/// <remarks>
19+
/// It utilize MemoryPack, which means the object must be MemoryPackSerializable
20+
/// https://github.com/Cysharp/MemoryPack
21+
/// </remarks>
1722
public class BinaryStorage<T>
1823
{
24+
const string DirectoryName = "Cache";
25+
26+
const string FileSuffix = ".cache";
27+
1928
public BinaryStorage(string filename)
2029
{
21-
const string directoryName = "Cache";
22-
var directoryPath = Path.Combine(DataLocation.DataDirectory(), directoryName);
30+
var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
2331
Helper.ValidateDirectory(directoryPath);
2432

25-
const string fileSuffix = ".cache";
26-
FilePath = Path.Combine(directoryPath, $"{filename}{fileSuffix}");
33+
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
2734
}
2835

2936
public string FilePath { get; }
3037

31-
public T TryLoad(T defaultData)
38+
public async ValueTask<T> TryLoadAsync(T defaultData)
3239
{
3340
if (File.Exists(FilePath))
3441
{
3542
if (new FileInfo(FilePath).Length == 0)
3643
{
3744
Log.Error($"|BinaryStorage.TryLoad|Zero length cache file <{FilePath}>");
38-
Save(defaultData);
45+
await SaveAsync(defaultData);
3946
return defaultData;
4047
}
4148

42-
using (var stream = new FileStream(FilePath, FileMode.Open))
43-
{
44-
var d = Deserialize(stream, defaultData);
45-
return d;
46-
}
49+
await using var stream = new FileStream(FilePath, FileMode.Open);
50+
var d = await DeserializeAsync(stream, defaultData);
51+
return d;
4752
}
4853
else
4954
{
5055
Log.Info("|BinaryStorage.TryLoad|Cache file not exist, load default data");
51-
Save(defaultData);
56+
await SaveAsync(defaultData);
5257
return defaultData;
5358
}
5459
}
5560

56-
private T Deserialize(FileStream stream, T defaultData)
61+
private async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
5762
{
58-
//http://stackoverflow.com/questions/2120055/binaryformatter-deserialize-gives-serializationexception
59-
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
60-
BinaryFormatter binaryFormatter = new BinaryFormatter
61-
{
62-
AssemblyFormat = FormatterAssemblyStyle.Simple
63-
};
64-
6563
try
6664
{
67-
var t = ((T)binaryFormatter.Deserialize(stream)).NonNull();
65+
var t = await MemoryPackSerializer.DeserializeAsync<T>(stream);
6866
return t;
6967
}
7068
catch (System.Exception e)
7169
{
7270
Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e);
7371
return defaultData;
7472
}
75-
finally
76-
{
77-
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
78-
}
7973
}
8074

81-
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
75+
public async ValueTask SaveAsync(T data)
8276
{
83-
Assembly ayResult = null;
84-
string sShortAssemblyName = args.Name.Split(',')[0];
85-
Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies();
86-
foreach (Assembly ayAssembly in ayAssemblies)
87-
{
88-
if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0])
89-
{
90-
ayResult = ayAssembly;
91-
break;
92-
}
93-
}
94-
return ayResult;
95-
}
96-
97-
public void Save(T data)
98-
{
99-
using (var stream = new FileStream(FilePath, FileMode.Create))
100-
{
101-
BinaryFormatter binaryFormatter = new BinaryFormatter
102-
{
103-
AssemblyFormat = FormatterAssemblyStyle.Simple
104-
};
105-
106-
try
107-
{
108-
binaryFormatter.Serialize(stream, data);
109-
}
110-
catch (SerializationException e)
111-
{
112-
Log.Exception($"|BinaryStorage.Save|serialize error for file <{FilePath}>", e);
113-
}
114-
}
77+
await using var stream = new FileStream(FilePath, FileMode.Create);
78+
await MemoryPackSerializer.SerializeAsync(stream, data);
11579
}
11680
}
117-
#pragma warning restore SYSLIB0011
11881
}

Flow.Launcher/App.xaml.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,13 @@ await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
5252
{
5353
_portable.PreStartCleanUpAfterPortabilityUpdate();
5454

55-
Log.Info("|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------");
55+
Log.Info(
56+
"|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------");
5657
Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}");
5758
RegisterAppDomainExceptions();
5859
RegisterDispatcherUnhandledException();
5960

60-
ImageLoader.Initialize();
61+
var imageLoadertask = ImageLoader.InitializeAsync();
6162

6263
_settingsVM = new SettingWindowViewModel(_updater, _portable);
6364
_settings = _settingsVM.Settings;
@@ -78,6 +79,8 @@ await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
7879
Http.Proxy = _settings.Proxy;
7980

8081
await PluginManager.InitializePluginsAsync(API);
82+
await imageLoadertask;
83+
8184
var window = new MainWindow(_settings, _mainVM);
8285

8386
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
@@ -103,7 +106,8 @@ await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
103106
AutoUpdates();
104107

105108
API.SaveAppAllSettings();
106-
Log.Info("|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- ");
109+
Log.Info(
110+
"|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- ");
107111
});
108112
}
109113

@@ -122,7 +126,8 @@ private void AutoStartup()
122126
// but if it fails (permissions, etc) then don't keep retrying
123127
// this also gives the user a visual indication in the Settings widget
124128
_settings.StartFlowLauncherOnSystemStartup = false;
125-
Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"), e.Message);
129+
Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"),
130+
e.Message);
126131
}
127132
}
128133
}

0 commit comments

Comments
 (0)