Skip to content

Commit 07bafea

Browse files
authored
Merge branch 'dev' into ui_wpf_modern
2 parents 67be26a + 7a7760c commit 07bafea

File tree

26 files changed

+361
-246
lines changed

26 files changed

+361
-246
lines changed

Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public async Task<List<UserPlugin>> FetchAsync(CancellationToken token)
7575
}
7676
catch (OperationCanceledException) when (token.IsCancellationRequested)
7777
{
78-
API.LogInfo(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller.");
78+
API.LogDebug(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller.");
7979
return null;
8080
}
8181
catch (TaskCanceledException)

Flow.Launcher.Core/Resource/Internationalization.cs

Lines changed: 37 additions & 12 deletions
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;
@@ -14,7 +14,7 @@
1414

1515
namespace Flow.Launcher.Core.Resource
1616
{
17-
public class Internationalization
17+
public class Internationalization : IDisposable
1818
{
1919
private static readonly string ClassName = nameof(Internationalization);
2020

@@ -30,6 +30,7 @@ public class Internationalization
3030
private readonly List<string> _languageDirectories = [];
3131
private readonly List<ResourceDictionary> _oldResources = [];
3232
private static string SystemLanguageCode;
33+
private readonly SemaphoreSlim _langChangeLock = new(1, 1);
3334

3435
public Internationalization(Settings settings)
3536
{
@@ -185,20 +186,33 @@ private static Language GetLanguageByLanguageCode(string languageCode)
185186

186187
private async Task ChangeLanguageAsync(Language language, bool updateMetadata = true)
187188
{
188-
// Remove old language files and load language
189-
RemoveOldLanguageFiles();
190-
if (language != AvailableLanguages.English)
189+
await _langChangeLock.WaitAsync();
190+
191+
try
191192
{
192-
LoadLanguage(language);
193-
}
193+
// Remove old language files and load language
194+
RemoveOldLanguageFiles();
195+
if (language != AvailableLanguages.English)
196+
{
197+
LoadLanguage(language);
198+
}
194199

195-
// Change culture info
196-
ChangeCultureInfo(language.LanguageCode);
200+
// Change culture info
201+
ChangeCultureInfo(language.LanguageCode);
197202

198-
if (updateMetadata)
203+
if (updateMetadata)
204+
{
205+
// Raise event for plugins after culture is set
206+
await Task.Run(UpdatePluginMetadataTranslations);
207+
}
208+
}
209+
catch (Exception e)
199210
{
200-
// Raise event for plugins after culture is set
201-
await Task.Run(UpdatePluginMetadataTranslations);
211+
API.LogException(ClassName, $"Failed to change language to <{language.LanguageCode}>", e);
212+
}
213+
finally
214+
{
215+
_langChangeLock.Release();
202216
}
203217
}
204218

@@ -257,6 +271,7 @@ private void RemoveOldLanguageFiles()
257271
{
258272
dicts.Remove(r);
259273
}
274+
_oldResources.Clear();
260275
}
261276

262277
private void LoadLanguage(Language language)
@@ -368,5 +383,15 @@ public static void UpdatePluginMetadataTranslations()
368383
}
369384

370385
#endregion
386+
387+
#region IDisposable
388+
389+
public void Dispose()
390+
{
391+
RemoveOldLanguageFiles();
392+
_langChangeLock.Dispose();
393+
}
394+
395+
#endregion
371396
}
372397
}

Flow.Launcher.Infrastructure/Image/ImageLoader.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static class ImageLoader
2222
private static Lock storageLock { get; } = new();
2323
private static BinaryStorage<List<(string, bool)>> _storage;
2424
private static readonly ConcurrentDictionary<string, string> GuidToKey = new();
25-
private static IImageHashGenerator _hashGenerator;
25+
private static ImageHashGenerator _hashGenerator;
2626
private static readonly bool EnableImageHash = true;
2727
public static ImageSource Image => ImageCache[Constant.ImageIcon, false];
2828
public static ImageSource MissingImage => ImageCache[Constant.MissingImgIcon, false];
@@ -31,7 +31,7 @@ public static class ImageLoader
3131
public const int FullIconSize = 256;
3232
public const int FullImageSize = 320;
3333

34-
private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" };
34+
private static readonly string[] ImageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico"];
3535
private static readonly string SvgExtension = ".svg";
3636

3737
public static async Task InitializeAsync()
@@ -327,7 +327,7 @@ public static async ValueTask<ImageSource> LoadAsync(string path, bool loadFullI
327327
return img;
328328
}
329329

330-
private static ImageSource LoadFullImage(string path)
330+
private static BitmapImage LoadFullImage(string path)
331331
{
332332
BitmapImage image = new BitmapImage();
333333
image.BeginInit();
@@ -364,7 +364,7 @@ private static ImageSource LoadFullImage(string path)
364364
return image;
365365
}
366366

367-
private static ImageSource LoadSvgImage(string path, bool loadFullImage = false)
367+
private static RenderTargetBitmap LoadSvgImage(string path, bool loadFullImage = false)
368368
{
369369
// Set up drawing settings
370370
var desiredHeight = loadFullImage ? FullImageSize : SmallIconSize;

Flow.Launcher/App.xaml.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public partial class App : IDisposable, ISingleInstanceApp
4646
private static Settings _settings;
4747
private static MainWindow _mainWindow;
4848
private readonly MainViewModel _mainVM;
49+
private readonly Internationalization _internationalization;
4950

5051
// To prevent two disposals running at the same time.
5152
private static readonly object _disposingLock = new();
@@ -111,6 +112,7 @@ public App()
111112
API = Ioc.Default.GetRequiredService<IPublicAPI>();
112113
_settings.Initialize();
113114
_mainVM = Ioc.Default.GetRequiredService<MainViewModel>();
115+
_internationalization = Ioc.Default.GetRequiredService<Internationalization>();
114116
}
115117
catch (Exception e)
116118
{
@@ -197,7 +199,7 @@ await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>
197199
Win32Helper.EnableWin32DarkMode(_settings.ColorScheme);
198200

199201
// Initialize language before portable clean up since it needs translations
200-
await Ioc.Default.GetRequiredService<Internationalization>().InitializeLanguageAsync();
202+
await _internationalization.InitializeLanguageAsync();
201203

202204
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
203205

@@ -425,6 +427,7 @@ protected virtual void Dispose(bool disposing)
425427
_mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose);
426428
_mainVM?.Dispose();
427429
DialogJump.Dispose();
430+
_internationalization.Dispose();
428431
}
429432

430433
API.LogInfo(ClassName, "End Flow Launcher dispose ----------------------------------------------------");

Flow.Launcher/Helper/WallpaperPathRetrieval.cs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5+
using System.Threading;
56
using System.Windows;
67
using System.Windows.Media;
78
using System.Windows.Media.Imaging;
@@ -16,7 +17,7 @@ public static class WallpaperPathRetrieval
1617

1718
private const int MaxCacheSize = 3;
1819
private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = new();
19-
private static readonly object CacheLock = new();
20+
private static readonly Lock CacheLock = new();
2021

2122
public static Brush GetWallpaperBrush()
2223
{
@@ -31,7 +32,7 @@ public static Brush GetWallpaperBrush()
3132
var wallpaperPath = Win32Helper.GetWallpaperPath();
3233
if (string.IsNullOrEmpty(wallpaperPath) || !File.Exists(wallpaperPath))
3334
{
34-
App.API.LogInfo(ClassName, $"Wallpaper path is invalid: {wallpaperPath}");
35+
App.API.LogError(ClassName, $"Wallpaper path is invalid: {wallpaperPath}");
3536
var wallpaperColor = GetWallpaperColor();
3637
return new SolidColorBrush(wallpaperColor);
3738
}
@@ -47,17 +48,22 @@ public static Brush GetWallpaperBrush()
4748
return cachedWallpaper;
4849
}
4950
}
50-
51-
using var fileStream = File.OpenRead(wallpaperPath);
52-
var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
53-
var frame = decoder.Frames[0];
54-
var originalWidth = frame.PixelWidth;
55-
var originalHeight = frame.PixelHeight;
51+
52+
int originalWidth, originalHeight;
53+
// Use `using ()` instead of `using var` sentence here to ensure the wallpaper file is not locked
54+
using (var fileStream = File.OpenRead(wallpaperPath))
55+
{
56+
var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
57+
var frame = decoder.Frames[0];
58+
originalWidth = frame.PixelWidth;
59+
originalHeight = frame.PixelHeight;
60+
}
5661

5762
if (originalWidth == 0 || originalHeight == 0)
5863
{
59-
App.API.LogInfo(ClassName, $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
60-
return new SolidColorBrush(Colors.Transparent);
64+
App.API.LogError(ClassName, $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
65+
var wallpaperColor = GetWallpaperColor();
66+
return new SolidColorBrush(wallpaperColor);
6167
}
6268

6369
// Calculate the scaling factor to fit the image within 800x600 while preserving aspect ratio
@@ -70,7 +76,9 @@ public static Brush GetWallpaperBrush()
7076
// Set DecodePixelWidth and DecodePixelHeight to resize the image while preserving aspect ratio
7177
var bitmap = new BitmapImage();
7278
bitmap.BeginInit();
79+
bitmap.CacheOption = BitmapCacheOption.OnLoad; // Use OnLoad to ensure the wallpaper file is not locked
7380
bitmap.UriSource = new Uri(wallpaperPath);
81+
bitmap.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
7482
bitmap.DecodePixelWidth = decodedPixelWidth;
7583
bitmap.DecodePixelHeight = decodedPixelHeight;
7684
bitmap.EndInit();
@@ -104,13 +112,13 @@ public static Brush GetWallpaperBrush()
104112

105113
private static Color GetWallpaperColor()
106114
{
107-
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", false);
115+
using var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", false);
108116
var result = key?.GetValue("Background", null);
109117
if (result is string strResult)
110118
{
111119
try
112120
{
113-
var parts = strResult.Trim().Split(new[] { ' ' }, 3).Select(byte.Parse).ToList();
121+
var parts = strResult.Trim().Split([' '], 3).Select(byte.Parse).ToList();
114122
return Color.FromRgb(parts[0], parts[1], parts[2]);
115123
}
116124
catch (Exception ex)

Flow.Launcher/Languages/en.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@
219219
<system:String x:Key="failedToUninstallPluginTitle">Fail to uninstall {0}</system:String>
220220
<system:String x:Key="fileNotFoundMessage">Unable to find plugin.json from the extracted zip file, or this path {0} does not exist</system:String>
221221
<system:String x:Key="pluginExistAlreadyMessage">A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin</system:String>
222+
<system:String x:Key="errorCreatingSettingPanel">Error creating setting panel for plugin {0}:{1}{2}</system:String>
222223

223224
<!-- Setting Plugin Store -->
224225
<system:String x:Key="pluginStore">Plugin Store</system:String>

Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -231,35 +231,41 @@ private bool ClearCacheFolder()
231231
}
232232
});
233233

234-
// Firstly, delete plugin cache directories
235-
pluginCacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
236-
.ToList()
237-
.ForEach(dir =>
238-
{
239-
try
240-
{
241-
// Plugin may create directories in its cache directory
242-
dir.Delete(recursive: true);
243-
}
244-
catch (Exception e)
245-
{
246-
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
247-
success = false;
248-
}
249-
});
250-
251-
// Then, delete plugin directory
252-
var dir = GetPluginCacheDir();
253-
try
254-
{
255-
dir.Delete(recursive: false);
256-
}
257-
catch (Exception e)
234+
// Check if plugin cache directory exists before attempting to delete
235+
// Or it will throw DirectoryNotFoundException in `pluginCacheDirectory.EnumerateDirectories`
236+
if (pluginCacheDirectory.Exists)
258237
{
259-
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
260-
success = false;
238+
// Firstly, delete plugin cache directories
239+
pluginCacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
240+
.ToList()
241+
.ForEach(dir =>
242+
{
243+
try
244+
{
245+
// Plugin may create directories in its cache directory
246+
dir.Delete(recursive: true);
247+
}
248+
catch (Exception e)
249+
{
250+
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
251+
success = false;
252+
}
253+
});
254+
255+
// Then, delete plugin directory
256+
var dir = pluginCacheDirectory;
257+
try
258+
{
259+
dir.Delete(recursive: false);
260+
}
261+
catch (Exception e)
262+
{
263+
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
264+
success = false;
265+
}
261266
}
262267

268+
// Raise regardless to cover scenario where size needs to be recalculated if the folder is manually removed on disk.
263269
OnPropertyChanged(nameof(CacheFolderSize));
264270

265271
return success;

0 commit comments

Comments
 (0)