Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -185,5 +185,10 @@ public void StopLoadingBar()
{
_api.StopLoadingBar();
}

public void SavePluginCaches()
{
_api.SavePluginCaches();
}
}
}
5 changes: 4 additions & 1 deletion Flow.Launcher.Core/Plugin/PluginManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public static void Save()
}

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

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

if (removePluginSettings)
{
// For dotnet plugins, we need to remove their PluginJsonStorage instance
// For dotnet plugins, we need to remove their PluginJsonStorage and PluginBinaryStorage instances
if (AllowedLanguage.IsDotNet(plugin.Language))
{
var method = API.GetType().GetMethod("RemovePluginSettings");
method?.Invoke(API, new object[] { plugin.AssemblyName });
var method1 = API.GetType().GetMethod("RemovePluginCache");
method1?.Invoke(API, new object[] { plugin.PluginCacheDirectoryPath });
}

try
Expand Down
1 change: 1 addition & 0 deletions Flow.Launcher.Infrastructure/Image/ImageLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public static async Task InitializeAsync()
_hashGenerator = new ImageHashGenerator();

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

ImageCache.Initialize(usage);

Expand Down
60 changes: 48 additions & 12 deletions Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using Flow.Launcher.Infrastructure.UserSettings;
using MemoryPack;

#nullable enable

namespace Flow.Launcher.Infrastructure.Storage
{
/// <summary>
Expand All @@ -15,48 +17,61 @@ namespace Flow.Launcher.Infrastructure.Storage
/// </remarks>
public class BinaryStorage<T>
{
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;
Helper.ValidateDirectory(directoryPath);

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

public string FilePath { get; }
public BinaryStorage(string filename)
{
DirectoryPath = DataLocation.CacheDirectory;
Helper.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 = d;
}
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 @@ -65,6 +80,27 @@ private static async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
}
}

public async ValueTask SaveAsync()
{
await using var stream = new FileStream(FilePath, FileMode.Create);
await MemoryPackSerializer.SerializeAsync(stream, Data);
}

// For SavePluginSettings function
public void Save()
{
var serialized = MemoryPackSerializer.Serialize(Data);

File.WriteAllBytes(FilePath, serialized);
}

// ImageCache need to be converted into concurrent dictionary, so it does not need to cache loading results into Data
public void ClearData()
{
Data = default;
}

// ImageCache storages data in its class, so it needs to pass it to SaveAsync
public async ValueTask SaveAsync(T data)
{
await using var stream = new FileStream(FilePath, FileMode.Create);
Expand Down
15 changes: 15 additions & 0 deletions Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.IO;

namespace Flow.Launcher.Infrastructure.Storage
{
public class PluginBinaryStorage<T> : BinaryStorage<T> where T : new()
{
public PluginBinaryStorage(string cacheName, string cacheDirectory)
{
DirectoryPath = cacheDirectory;
Helper.ValidateDirectory(DirectoryPath);

FilePath = Path.Combine(DirectoryPath, $"{cacheName}{FileSuffix}");
}
}
}
6 changes: 0 additions & 6 deletions Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,12 @@ namespace Flow.Launcher.Infrastructure.Storage

public PluginJsonStorage()
{
// C# related, add python related below
var dataType = typeof(T);
AssemblyName = dataType.Assembly.GetName().Name;
DirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, AssemblyName);
Helper.ValidateDirectory(DirectoryPath);

FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}");
}

public PluginJsonStorage(T data) : this()
{
Data = data;
}
}
}
33 changes: 33 additions & 0 deletions Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -344,5 +344,38 @@ public interface IPublicAPI
/// Stop the loading bar in main window
/// </summary>
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 utilize 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 utilize 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();
}
}
7 changes: 4 additions & 3 deletions Flow.Launcher.Plugin/Interfaces/ISavable.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
namespace Flow.Launcher.Plugin
namespace Flow.Launcher.Plugin
{
/// <summary>
/// Inherit this interface if additional data e.g. cache needs to be saved.
/// </summary>
/// <remarks>
/// For storing plugin settings, prefer <see cref="IPublicAPI.LoadSettingJsonStorage{T}"/>
/// or <see cref="IPublicAPI.SaveSettingJsonStorage{T}"/>.
/// or <see cref="IPublicAPI.SaveSettingJsonStorage{T}"/>.
/// or <see cref="IPublicAPI.SaveCacheBinaryStorageAsync{T}(string, string)"/>.
/// Once called, your settings will be automatically saved by Flow.
/// </remarks>
public interface ISavable : IFeatures
Expand All @@ -15,4 +16,4 @@ public interface ISavable : IFeatures
/// </summary>
void Save();
}
}
}
53 changes: 45 additions & 8 deletions Flow.Launcher/PublicAPIInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,6 @@ public void SavePluginSettings()
((PluginJsonStorage<T>)_pluginJsonStorages[type]).Save();
}

public void SaveJsonStorage<T>(T settings) where T : new()
{
var type = typeof(T);
_pluginJsonStorages[type] = new PluginJsonStorage<T>(settings);

((PluginJsonStorage<T>)_pluginJsonStorages[type]).Save();
}

public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null)
{
using var explorer = new Process();
Expand Down Expand Up @@ -342,6 +334,51 @@ public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", M

public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress);

private readonly ConcurrentDictionary<(string, string, Type), object> _pluginBinaryStorages = new();

public void RemovePluginCache(string cacheDirectory)
{
foreach (var keyValuePair in _pluginBinaryStorages)
{
var key = keyValuePair.Key;
var currentCacheDirectory = key.Item2;
if (cacheDirectory == currentCacheDirectory)
{
_pluginBinaryStorages.Remove(key, out var _);
}
}
}

/// <summary>
/// Save plugin caches.
/// </summary>
public void SavePluginCaches()
{
foreach (var value in _pluginBinaryStorages.Values)
{
var method = value.GetType().GetMethod("Save");
method?.Invoke(value, null);
}
}

public async Task<T> LoadCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory, T defaultData) where T : new()
{
var type = typeof(T);
if (!_pluginBinaryStorages.ContainsKey((cacheName, cacheDirectory, type)))
_pluginBinaryStorages[(cacheName, cacheDirectory, type)] = new PluginBinaryStorage<T>(cacheName, cacheDirectory);

return await ((PluginBinaryStorage<T>)_pluginBinaryStorages[(cacheName, cacheDirectory, type)]).TryLoadAsync(defaultData);
}

public async Task SaveCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory) where T : new()
{
var type = typeof(T);
if (!_pluginBinaryStorages.ContainsKey((cacheName, cacheDirectory, type)))
_pluginBinaryStorages[(cacheName, cacheDirectory, type)] = new PluginBinaryStorage<T>(cacheName, cacheDirectory);

await ((PluginBinaryStorage<T>)_pluginBinaryStorages[(cacheName, cacheDirectory, type)]).SaveAsync();
}

#endregion

#region Private Methods
Expand Down
Loading
Loading