Skip to content

Commit a9c2240

Browse files
authored
Merge branch 'dev' into administrator_mode
2 parents c9ca92e + 9b04989 commit a9c2240

File tree

226 files changed

+7455
-1166
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

226 files changed

+7455
-1166
lines changed

.github/workflows/default_plugins.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
steps:
1313
- uses: actions/checkout@v5
1414
- name: Setup .NET
15-
uses: actions/setup-dotnet@v4
15+
uses: actions/setup-dotnet@v5
1616
with:
1717
dotnet-version: 9.0.x
1818

.github/workflows/dotnet.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
"**/SolutionAssemblyInfo.cs"
3030
version: ${{ env.FlowVersion }}.${{ env.BUILD_NUMBER }}
3131
- name: Setup .NET
32-
uses: actions/setup-dotnet@v4
32+
uses: actions/setup-dotnet@v5
3333
with:
3434
dotnet-version: 9.0.x
3535
# cache: true

.github/workflows/release_pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
steps:
1414
- uses: actions/checkout@v5
1515

16-
- uses: actions/setup-python@v5
16+
- uses: actions/setup-python@v6
1717
with:
1818
python-version: "3.x"
1919

.github/workflows/stale.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
issues: write
1919
pull-requests: write
2020
steps:
21-
- uses: actions/stale@v9
21+
- uses: actions/stale@v10
2222
with:
2323
stale-issue-message: 'This issue is stale because it has been open ${{ env.days-before-stale }} days with no activity. Remove stale label or comment or this will be closed in ${{ env.days-before-stale }} days.\n\nAlternatively this issue can be kept open by adding one of the following labels:\n${{ env.exempt-issue-labels }}'
2424
days-before-stale: ${{ env.days-before-stale }}

Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Threading.Tasks;
55
using CommunityToolkit.Mvvm.DependencyInjection;
66
using Flow.Launcher.Plugin;
7+
using Flow.Launcher.Infrastructure;
78

89
namespace Flow.Launcher.Core.ExternalPlugins
910
{
@@ -12,10 +13,10 @@ public static class PluginsManifest
1213
private static readonly string ClassName = nameof(PluginsManifest);
1314

1415
private static readonly CommunityPluginStore mainPluginStore =
15-
new("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/plugin_api_v2/plugins.json",
16-
"https://fastly.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json",
17-
"https://gcore.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json",
18-
"https://cdn.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json");
16+
new("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/main/plugins.json",
17+
"https://fastly.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@main/plugins.json",
18+
"https://gcore.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@main/plugins.json",
19+
"https://cdn.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@main/plugins.json");
1920

2021
private static readonly SemaphoreSlim manifestUpdateLock = new(1);
2122

@@ -39,13 +40,23 @@ public static async Task<bool> UpdateManifestAsync(bool usePrimaryUrlOnly = fals
3940
var results = await mainPluginStore.FetchAsync(token, usePrimaryUrlOnly).ConfigureAwait(false);
4041

4142
// If the results are empty, we shouldn't update the manifest because the results are invalid.
42-
if (results.Count != 0)
43-
{
44-
UserPlugins = results;
45-
lastFetchedAt = DateTime.Now;
43+
if (results.Count == 0)
44+
return false;
4645

47-
return true;
46+
var updatedPluginResults = new List<UserPlugin>();
47+
var appVersion = SemanticVersioning.Version.Parse(Constant.Version);
48+
49+
for (int i = 0; i < results.Count; i++)
50+
{
51+
if (IsMinimumAppVersionSatisfied(results[i], appVersion))
52+
updatedPluginResults.Add(results[i]);
4853
}
54+
55+
UserPlugins = updatedPluginResults;
56+
57+
lastFetchedAt = DateTime.Now;
58+
59+
return true;
4960
}
5061
}
5162
catch (Exception e)
@@ -59,5 +70,28 @@ public static async Task<bool> UpdateManifestAsync(bool usePrimaryUrlOnly = fals
5970

6071
return false;
6172
}
73+
74+
private static bool IsMinimumAppVersionSatisfied(UserPlugin plugin, SemanticVersioning.Version appVersion)
75+
{
76+
if (string.IsNullOrEmpty(plugin.MinimumAppVersion))
77+
return true;
78+
79+
try
80+
{
81+
if (appVersion >= SemanticVersioning.Version.Parse(plugin.MinimumAppVersion))
82+
return true;
83+
}
84+
catch (Exception e)
85+
{
86+
API.LogException(ClassName, $"Failed to parse the minimum app version {plugin.MinimumAppVersion} for plugin {plugin.Name}. "
87+
+ "Plugin excluded from manifest", e);
88+
return false;
89+
}
90+
91+
API.LogInfo(ClassName, $"Plugin {plugin.Name} requires minimum Flow Launcher version {plugin.MinimumAppVersion}, "
92+
+ $"but current version is {Constant.Version}. Plugin excluded from manifest.");
93+
94+
return false;
95+
}
6296
}
6397
}

Flow.Launcher.Core/Plugin/PluginInstaller.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.IO;
44
using System.IO.Compression;

Flow.Launcher.Core/Resource/Internationalization.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Globalization;
44
using System.IO;

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.Infrastructure/UserSettings/Settings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Collections.Generic;
1+
using System.Collections.Generic;
22
using System.Collections.ObjectModel;
33
using System.Text.Json.Serialization;
44
using System.Windows;

0 commit comments

Comments
 (0)