Skip to content

New API Functions Plugin Cache Storage & Use API Functions for Program Plugin & Remove Reflection with Interfaces #3410

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 25 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2432753
Add plugin json storage
Jack251970 Apr 1, 2025
ca221d7
Add binary storage api functions
Jack251970 Apr 1, 2025
0496d6c
Use api functions for Program plugin
Jack251970 Apr 1, 2025
68e1fc2
Use try remove for safety
Jack251970 Apr 1, 2025
8cd81b7
Merge branch 'dev' into binary_storage_api
Jack251970 Apr 4, 2025
56536d0
Add log for plugin binary storage
Jack251970 Apr 4, 2025
e6d3d0f
Improve code quality
Jack251970 Apr 5, 2025
6e5c7ad
Use ISavable interface instead of reflection
Jack251970 Apr 5, 2025
3185bda
Use IRemovable interface instead of reflection
Jack251970 Apr 5, 2025
c55a453
Merge branch 'dev' into binary_storage_api
Jack251970 Apr 5, 2025
9f84a2d
Merge branch 'dev' into binary_storage_api
Jack251970 Apr 8, 2025
0619041
Merge branch 'dev' into binary_storage_api
Jack251970 Apr 8, 2025
7da2884
Add locks for win32s & uwps
Jack251970 Apr 8, 2025
87aca2f
Merge branch 'binary_storage_api' of https://github.com/Jack251970/Fl…
Jack251970 Apr 8, 2025
734c5bb
Fix lock release issue
Jack251970 Apr 8, 2025
c11ee2f
Improve code quality & comments & Fix lock issue
Jack251970 Apr 8, 2025
54e7652
Fix see cref issue
Jack251970 Apr 8, 2025
6826802
Improve performance
Jack251970 Apr 8, 2025
4c4a6c0
Add log handler for indexing
Jack251970 Apr 8, 2025
aaadf16
Fix typos
Jack251970 Apr 8, 2025
4749ca2
Improve code comments
Jack251970 Apr 8, 2025
2ff09cf
Improve code quality
Jack251970 Apr 8, 2025
653b833
Fix typos
Jack251970 Apr 8, 2025
d7ca36e
Change variable name
Jack251970 Apr 8, 2025
482e373
Remove useless releases
Jack251970 Apr 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,10 @@ public void StopLoadingBar()
{
_api.StopLoadingBar();
}

public void SavePluginCaches()
{
_api.SavePluginCaches();
}
}
}
10 changes: 6 additions & 4 deletions Flow.Launcher.Core/Plugin/PluginManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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
Expand Down Expand Up @@ -65,6 +66,7 @@ public static void Save()
}

API.SavePluginSettings();
API.SavePluginCaches();
}

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

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
Expand Down
19 changes: 19 additions & 0 deletions Flow.Launcher.Core/Storage/IRemovable.cs
Original file line number Diff line number Diff line change
@@ -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);
}
1 change: 1 addition & 0 deletions Flow.Launcher.Infrastructure/Image/ImageLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public static async Task InitializeAsync()
_hashGenerator = new ImageHashGenerator();

var usage = await LoadStorageToConcurrentDictionaryAsync();
_storage.ClearData();

ImageCache.Initialize(usage);

Expand Down
65 changes: 50 additions & 15 deletions Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,76 @@
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>
/// Stroage object using binary data
/// 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";

protected string FilePath { get; init; } = null!;

protected string DirectoryPath { get; init; } = null!;

// Let the derived class to set the file path
public BinaryStorage(string filename, string directoryPath = null)
protected BinaryStorage()
{
directoryPath ??= DataLocation.CacheDirectory;
FilesFolders.ValidateDirectory(directoryPath);

FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
}

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)
{
try
{
var t = await MemoryPackSerializer.DeserializeAsync<T>(stream);
return t;
return t ?? defaultData;
}
catch (System.Exception)
{
Expand All @@ -66,6 +80,27 @@ private static async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
}
}

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);
Expand Down
8 changes: 5 additions & 3 deletions Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
49 changes: 49 additions & 0 deletions Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
32 changes: 32 additions & 0 deletions Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,38 @@ public interface IPublicAPI
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>
Expand Down
15 changes: 9 additions & 6 deletions Flow.Launcher.Plugin/Interfaces/ISavable.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Loading
Loading