Skip to content

Commit c159b48

Browse files
authored
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
2 parents 822b65e + 482e373 commit c159b48

File tree

15 files changed

+376
-127
lines changed

15 files changed

+376
-127
lines changed

Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs

+5
Original file line numberDiff line numberDiff line change
@@ -189,5 +189,10 @@ public void StopLoadingBar()
189189
{
190190
_api.StopLoadingBar();
191191
}
192+
193+
public void SavePluginCaches()
194+
{
195+
_api.SavePluginCaches();
196+
}
192197
}
193198
}

Flow.Launcher.Core/Plugin/PluginManager.cs

+6-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Flow.Launcher.Infrastructure.UserSettings;
1414
using Flow.Launcher.Plugin;
1515
using Flow.Launcher.Plugin.SharedCommands;
16+
using IRemovable = Flow.Launcher.Core.Storage.IRemovable;
1617
using ISavable = Flow.Launcher.Plugin.ISavable;
1718

1819
namespace Flow.Launcher.Core.Plugin
@@ -65,6 +66,7 @@ public static void Save()
6566
}
6667

6768
API.SavePluginSettings();
69+
API.SavePluginCaches();
6870
}
6971

7072
public static async ValueTask DisposePluginsAsync()
@@ -575,11 +577,11 @@ internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool remo
575577

576578
if (removePluginSettings)
577579
{
578-
// For dotnet plugins, we need to remove their PluginJsonStorage instance
579-
if (AllowedLanguage.IsDotNet(plugin.Language))
580+
// For dotnet plugins, we need to remove their PluginJsonStorage and PluginBinaryStorage instances
581+
if (AllowedLanguage.IsDotNet(plugin.Language) && API is IRemovable removable)
580582
{
581-
var method = API.GetType().GetMethod("RemovePluginSettings");
582-
method?.Invoke(API, new object[] { plugin.AssemblyName });
583+
removable.RemovePluginSettings(plugin.AssemblyName);
584+
removable.RemovePluginCaches(plugin.PluginCacheDirectoryPath);
583585
}
584586

585587
try
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Flow.Launcher.Core.Storage;
2+
3+
/// <summary>
4+
/// Remove storage instances from <see cref="Launcher.Plugin.IPublicAPI"/> instance
5+
/// </summary>
6+
public interface IRemovable
7+
{
8+
/// <summary>
9+
/// Remove all <see cref="Infrastructure.Storage.PluginJsonStorage{T}"/> instances of one plugin
10+
/// </summary>
11+
/// <param name="assemblyName"></param>
12+
public void RemovePluginSettings(string assemblyName);
13+
14+
/// <summary>
15+
/// Remove all <see cref="Infrastructure.Storage.PluginBinaryStorage{T}"/> instances of one plugin
16+
/// </summary>
17+
/// <param name="cacheDirectory"></param>
18+
public void RemovePluginCaches(string cacheDirectory);
19+
}

Flow.Launcher.Infrastructure/Image/ImageLoader.cs

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public static async Task InitializeAsync()
3434
_hashGenerator = new ImageHashGenerator();
3535

3636
var usage = await LoadStorageToConcurrentDictionaryAsync();
37+
_storage.ClearData();
3738

3839
ImageCache.Initialize(usage);
3940

Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs

+50-15
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,76 @@
22
using System.Threading.Tasks;
33
using Flow.Launcher.Infrastructure.Logger;
44
using Flow.Launcher.Infrastructure.UserSettings;
5+
using Flow.Launcher.Plugin;
56
using Flow.Launcher.Plugin.SharedCommands;
67
using MemoryPack;
78

9+
#nullable enable
10+
811
namespace Flow.Launcher.Infrastructure.Storage
912
{
1013
/// <summary>
1114
/// Stroage object using binary data
1215
/// Normally, it has better performance, but not readable
1316
/// </summary>
1417
/// <remarks>
15-
/// It utilize MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
18+
/// It utilizes MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
1619
/// </remarks>
17-
public class BinaryStorage<T>
20+
public class BinaryStorage<T> : ISavable
1821
{
22+
protected T? Data;
23+
1924
public const string FileSuffix = ".cache";
2025

26+
protected string FilePath { get; init; } = null!;
27+
28+
protected string DirectoryPath { get; init; } = null!;
29+
2130
// Let the derived class to set the file path
22-
public BinaryStorage(string filename, string directoryPath = null)
31+
protected BinaryStorage()
2332
{
24-
directoryPath ??= DataLocation.CacheDirectory;
25-
FilesFolders.ValidateDirectory(directoryPath);
26-
27-
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
2833
}
2934

30-
public string FilePath { get; }
35+
public BinaryStorage(string filename)
36+
{
37+
DirectoryPath = DataLocation.CacheDirectory;
38+
FilesFolders.ValidateDirectory(DirectoryPath);
39+
40+
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
41+
}
3142

3243
public async ValueTask<T> TryLoadAsync(T defaultData)
3344
{
45+
if (Data != null) return Data;
46+
3447
if (File.Exists(FilePath))
3548
{
3649
if (new FileInfo(FilePath).Length == 0)
3750
{
3851
Log.Error($"|BinaryStorage.TryLoad|Zero length cache file <{FilePath}>");
39-
await SaveAsync(defaultData);
40-
return defaultData;
52+
Data = defaultData;
53+
await SaveAsync();
4154
}
4255

4356
await using var stream = new FileStream(FilePath, FileMode.Open);
44-
var d = await DeserializeAsync(stream, defaultData);
45-
return d;
57+
Data = await DeserializeAsync(stream, defaultData);
4658
}
4759
else
4860
{
4961
Log.Info("|BinaryStorage.TryLoad|Cache file not exist, load default data");
50-
await SaveAsync(defaultData);
51-
return defaultData;
62+
Data = defaultData;
63+
await SaveAsync();
5264
}
65+
66+
return Data;
5367
}
5468

5569
private static async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
5670
{
5771
try
5872
{
5973
var t = await MemoryPackSerializer.DeserializeAsync<T>(stream);
60-
return t;
74+
return t ?? defaultData;
6175
}
6276
catch (System.Exception)
6377
{
@@ -66,6 +80,27 @@ private static async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
6680
}
6781
}
6882

83+
public void Save()
84+
{
85+
var serialized = MemoryPackSerializer.Serialize(Data);
86+
87+
File.WriteAllBytes(FilePath, serialized);
88+
}
89+
90+
public async ValueTask SaveAsync()
91+
{
92+
await SaveAsync(Data.NonNull());
93+
}
94+
95+
// ImageCache need to convert data into concurrent dictionary for usage,
96+
// so we would better to clear the data
97+
public void ClearData()
98+
{
99+
Data = default;
100+
}
101+
102+
// ImageCache storages data in its class,
103+
// so we need to pass it to SaveAsync
69104
public async ValueTask SaveAsync(T data)
70105
{
71106
await using var stream = new FileStream(FilePath, FileMode.Create);

Flow.Launcher.Infrastructure/Storage/JsonStorage.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
#nullable enable
2-
using System;
1+
using System;
32
using System.Globalization;
43
using System.IO;
54
using System.Text.Json;
65
using System.Threading.Tasks;
76
using Flow.Launcher.Infrastructure.Logger;
7+
using Flow.Launcher.Plugin;
88
using Flow.Launcher.Plugin.SharedCommands;
99

10+
#nullable enable
11+
1012
namespace Flow.Launcher.Infrastructure.Storage
1113
{
1214
/// <summary>
1315
/// Serialize object using json format.
1416
/// </summary>
15-
public class JsonStorage<T> where T : new()
17+
public class JsonStorage<T> : ISavable where T : new()
1618
{
1719
protected T? Data;
1820

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System.IO;
2+
using System.Threading.Tasks;
3+
using CommunityToolkit.Mvvm.DependencyInjection;
4+
using Flow.Launcher.Plugin;
5+
using Flow.Launcher.Plugin.SharedCommands;
6+
7+
namespace Flow.Launcher.Infrastructure.Storage
8+
{
9+
public class PluginBinaryStorage<T> : BinaryStorage<T> where T : new()
10+
{
11+
private static readonly string ClassName = "PluginBinaryStorage";
12+
13+
// We should not initialize API in static constructor because it will create another API instance
14+
private static IPublicAPI api = null;
15+
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
16+
17+
public PluginBinaryStorage(string cacheName, string cacheDirectory)
18+
{
19+
DirectoryPath = cacheDirectory;
20+
FilesFolders.ValidateDirectory(DirectoryPath);
21+
22+
FilePath = Path.Combine(DirectoryPath, $"{cacheName}{FileSuffix}");
23+
}
24+
25+
public new void Save()
26+
{
27+
try
28+
{
29+
base.Save();
30+
}
31+
catch (System.Exception e)
32+
{
33+
API.LogException(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
34+
}
35+
}
36+
37+
public new async Task SaveAsync()
38+
{
39+
try
40+
{
41+
await base.SaveAsync();
42+
}
43+
catch (System.Exception e)
44+
{
45+
API.LogException(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
46+
}
47+
}
48+
}
49+
}

Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs

+32
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,38 @@ public interface IPublicAPI
352352
public void StopLoadingBar();
353353

354354
/// <summary>
355+
/// Save all Flow's plugins caches
356+
/// </summary>
357+
void SavePluginCaches();
358+
359+
/// <summary>
360+
/// Load BinaryStorage for current plugin's cache. This is the method used to load cache from binary in Flow.
361+
/// When the file is not exist, it will create a new instance for the specific type.
362+
/// </summary>
363+
/// <typeparam name="T">Type for deserialization</typeparam>
364+
/// <param name="cacheName">Cache file name</param>
365+
/// <param name="cacheDirectory">Cache directory from plugin metadata</param>
366+
/// <param name="defaultData">Default data to return</param>
367+
/// <returns></returns>
368+
/// <remarks>
369+
/// BinaryStorage utilizes MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
370+
/// </remarks>
371+
Task<T> LoadCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory, T defaultData) where T : new();
372+
373+
/// <summary>
374+
/// Save BinaryStorage for current plugin's cache. This is the method used to save cache to binary in Flow.Launcher
375+
/// This method will save the original instance loaded with LoadCacheBinaryStorageAsync.
376+
/// This API call is for manually Save. Flow will automatically save all cache type that has called LoadCacheBinaryStorageAsync or SaveCacheBinaryStorageAsync previously.
377+
/// </summary>
378+
/// <typeparam name="T">Type for Serialization</typeparam>
379+
/// <param name="cacheName">Cache file name</param>
380+
/// <param name="cacheDirectory">Cache directory from plugin metadata</param>
381+
/// <returns></returns>
382+
/// <remarks>
383+
/// BinaryStorage utilizes MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
384+
/// </remarks>
385+
Task SaveCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory) where T : new();
386+
355387
/// Load image from path. Support local, remote and data:image url.
356388
/// If image path is missing, it will return a missing icon.
357389
/// </summary>
+9-6
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
namespace Flow.Launcher.Plugin
1+
namespace Flow.Launcher.Plugin
22
{
33
/// <summary>
4-
/// Inherit this interface if additional data e.g. cache needs to be saved.
4+
/// Inherit this interface if you need to save additional data which is not a setting or cache,
5+
/// please implement this interface.
56
/// </summary>
67
/// <remarks>
78
/// For storing plugin settings, prefer <see cref="IPublicAPI.LoadSettingJsonStorage{T}"/>
8-
/// or <see cref="IPublicAPI.SaveSettingJsonStorage{T}"/>.
9-
/// Once called, your settings will be automatically saved by Flow.
9+
/// or <see cref="IPublicAPI.SaveSettingJsonStorage{T}"/>.
10+
/// For storing plugin caches, prefer <see cref="IPublicAPI.LoadCacheBinaryStorageAsync{T}"/>
11+
/// or <see cref="IPublicAPI.SaveCacheBinaryStorageAsync{T}(string, string)"/>.
12+
/// Once called, those settings and caches will be automatically saved by Flow.
1013
/// </remarks>
1114
public interface ISavable : IFeatures
1215
{
1316
/// <summary>
14-
/// Save additional plugin data, such as cache.
17+
/// Save additional plugin data.
1518
/// </summary>
1619
void Save();
1720
}
18-
}
21+
}

0 commit comments

Comments
 (0)