Skip to content

Commit 67cc1e2

Browse files
authored
Merge pull request #3417 from Flow-Launcher/environment_exit
Fix environment exit & Wait image cache save before restarting & Add settings saving lock & Do not crash when caught exception saving
2 parents 3771d64 + 28ab71f commit 67cc1e2

File tree

11 files changed

+168
-50
lines changed

11 files changed

+168
-50
lines changed

Flow.Launcher.Core/Configuration/Portable.cs

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class Portable : IPortable
2222
/// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish
2323
/// </summary>
2424
/// <returns></returns>
25-
private UpdateManager NewUpdateManager()
25+
private static UpdateManager NewUpdateManager()
2626
{
2727
var applicationFolderName = Constant.ApplicationDirectory
2828
.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None)
@@ -81,20 +81,16 @@ public void EnablePortableMode()
8181

8282
public void RemoveShortcuts()
8383
{
84-
using (var portabilityUpdater = NewUpdateManager())
85-
{
86-
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu);
87-
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop);
88-
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup);
89-
}
84+
using var portabilityUpdater = NewUpdateManager();
85+
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu);
86+
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop);
87+
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup);
9088
}
9189

9290
public void RemoveUninstallerEntry()
9391
{
94-
using (var portabilityUpdater = NewUpdateManager())
95-
{
96-
portabilityUpdater.RemoveUninstallerRegistryEntry();
97-
}
92+
using var portabilityUpdater = NewUpdateManager();
93+
portabilityUpdater.RemoveUninstallerRegistryEntry();
9894
}
9995

10096
public void MoveUserDataFolder(string fromLocation, string toLocation)
@@ -110,12 +106,10 @@ public void VerifyUserDataAfterMove(string fromLocation, string toLocation)
110106

111107
public void CreateShortcuts()
112108
{
113-
using (var portabilityUpdater = NewUpdateManager())
114-
{
115-
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu, false);
116-
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop, false);
117-
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup, false);
118-
}
109+
using var portabilityUpdater = NewUpdateManager();
110+
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu, false);
111+
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop, false);
112+
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup, false);
119113
}
120114

121115
public void CreateUninstallerEntry()
@@ -129,18 +123,14 @@ public void CreateUninstallerEntry()
129123
subKey2.SetValue("DisplayIcon", Path.Combine(Constant.ApplicationDirectory, "app.ico"), RegistryValueKind.String);
130124
}
131125

132-
using (var portabilityUpdater = NewUpdateManager())
133-
{
134-
_ = portabilityUpdater.CreateUninstallerRegistryEntry();
135-
}
126+
using var portabilityUpdater = NewUpdateManager();
127+
_ = portabilityUpdater.CreateUninstallerRegistryEntry();
136128
}
137129

138-
internal void IndicateDeletion(string filePathTodelete)
130+
private static void IndicateDeletion(string filePathTodelete)
139131
{
140132
var deleteFilePath = Path.Combine(filePathTodelete, DataLocation.DeletionIndicatorFile);
141-
using (var _ = File.CreateText(deleteFilePath))
142-
{
143-
}
133+
using var _ = File.CreateText(deleteFilePath);
144134
}
145135

146136
///<summary>

Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public class JsonRPCPluginSettings
2323
protected ConcurrentDictionary<string, object?> Settings { get; set; } = null!;
2424
public required IPublicAPI API { get; init; }
2525

26+
private static readonly string ClassName = nameof(JsonRPCPluginSettings);
27+
2628
private JsonStorage<ConcurrentDictionary<string, object?>> _storage = null!;
2729

2830
private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin");
@@ -122,12 +124,26 @@ public void UpdateSettings(IReadOnlyDictionary<string, object> settings)
122124

123125
public async Task SaveAsync()
124126
{
125-
await _storage.SaveAsync();
127+
try
128+
{
129+
await _storage.SaveAsync();
130+
}
131+
catch (System.Exception e)
132+
{
133+
API.LogException(ClassName, $"Failed to save plugin settings to path: {SettingPath}", e);
134+
}
126135
}
127136

128137
public void Save()
129138
{
130-
_storage.Save();
139+
try
140+
{
141+
_storage.Save();
142+
}
143+
catch (System.Exception e)
144+
{
145+
API.LogException(ClassName, $"Failed to save plugin settings to path: {SettingPath}", e);
146+
}
131147
}
132148

133149
public bool NeedCreateSettingPanel()

Flow.Launcher.Core/Updater.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@
44
using System.Net.Http;
55
using System.Net.Sockets;
66
using System.Linq;
7+
using System.Text.Json;
8+
using System.Text.Json.Serialization;
9+
using System.Threading;
710
using System.Threading.Tasks;
811
using System.Windows;
9-
using JetBrains.Annotations;
10-
using Squirrel;
12+
using CommunityToolkit.Mvvm.DependencyInjection;
1113
using Flow.Launcher.Core.Resource;
1214
using Flow.Launcher.Plugin.SharedCommands;
1315
using Flow.Launcher.Infrastructure;
1416
using Flow.Launcher.Infrastructure.Http;
1517
using Flow.Launcher.Infrastructure.Logger;
1618
using Flow.Launcher.Infrastructure.UserSettings;
1719
using Flow.Launcher.Plugin;
18-
using System.Text.Json.Serialization;
19-
using System.Threading;
20-
using System.Text.Json;
20+
using JetBrains.Annotations;
21+
using Squirrel;
2122

2223
namespace Flow.Launcher.Core
2324
{
@@ -71,7 +72,7 @@ public async Task UpdateAppAsync(bool silentUpdate = true)
7172

7273
if (DataLocation.PortableDataLocationInUse())
7374
{
74-
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
75+
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion}\\{DataLocation.PortableFolderName}";
7576
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s));
7677
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)))
7778
_api.ShowMsgBox(string.Format(_api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
@@ -123,7 +124,7 @@ private class GithubRelease
123124
}
124125

125126
// https://github.com/Squirrel/Squirrel.Windows/blob/master/src/Squirrel/UpdateManager.Factory.cs
126-
private async Task<UpdateManager> GitHubUpdateManagerAsync(string repository)
127+
private static async Task<UpdateManager> GitHubUpdateManagerAsync(string repository)
127128
{
128129
var uri = new Uri(repository);
129130
var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases";
@@ -145,9 +146,9 @@ private async Task<UpdateManager> GitHubUpdateManagerAsync(string repository)
145146
return manager;
146147
}
147148

148-
public string NewVersionTips(string version)
149+
private static string NewVersionTips(string version)
149150
{
150-
var translator = InternationalizationManager.Instance;
151+
var translator = Ioc.Default.GetRequiredService<Internationalization>();
151152
var tips = string.Format(translator.GetTranslation("newVersionTips"), version);
152153

153154
return tips;

Flow.Launcher.Infrastructure/Image/ImageLoader.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ await Stopwatch.NormalAsync("|ImageLoader.Initialize|Preload images cost", async
6161
});
6262
}
6363

64-
public static async Task Save()
64+
public static async Task SaveAsync()
6565
{
6666
await storageLock.WaitAsync();
6767

@@ -71,12 +71,22 @@ await _storage.SaveAsync(ImageCache.EnumerateEntries()
7171
.Select(x => x.Key)
7272
.ToList());
7373
}
74+
catch (System.Exception e)
75+
{
76+
Log.Exception($"|ImageLoader.SaveAsync|Failed to save image cache to file", e);
77+
}
7478
finally
7579
{
7680
storageLock.Release();
7781
}
7882
}
7983

84+
public static async Task WaitSaveAsync()
85+
{
86+
await storageLock.WaitAsync();
87+
storageLock.Release();
88+
}
89+
8090
private static async Task<List<(string, bool)>> LoadStorageToConcurrentDictionaryAsync()
8191
{
8292
await storageLock.WaitAsync();

Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
using System.IO;
2+
using System.Threading.Tasks;
3+
using CommunityToolkit.Mvvm.DependencyInjection;
24
using Flow.Launcher.Infrastructure.UserSettings;
5+
using Flow.Launcher.Plugin;
36
using Flow.Launcher.Plugin.SharedCommands;
47

58
namespace Flow.Launcher.Infrastructure.Storage
69
{
710
public class FlowLauncherJsonStorage<T> : JsonStorage<T> where T : new()
811
{
12+
private static readonly string ClassName = "FlowLauncherJsonStorage";
13+
14+
// We should not initialize API in static constructor because it will create another API instance
15+
private static IPublicAPI api = null;
16+
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
17+
918
public FlowLauncherJsonStorage()
1019
{
1120
var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
@@ -14,5 +23,29 @@ public FlowLauncherJsonStorage()
1423
var filename = typeof(T).Name;
1524
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
1625
}
26+
27+
public new void Save()
28+
{
29+
try
30+
{
31+
base.Save();
32+
}
33+
catch (System.Exception e)
34+
{
35+
API.LogException(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
36+
}
37+
}
38+
39+
public new async Task SaveAsync()
40+
{
41+
try
42+
{
43+
await base.SaveAsync();
44+
}
45+
catch (System.Exception e)
46+
{
47+
API.LogException(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
48+
}
49+
}
1750
}
1851
}

Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System.IO;
2+
using System.Threading.Tasks;
3+
using CommunityToolkit.Mvvm.DependencyInjection;
24
using Flow.Launcher.Infrastructure.UserSettings;
5+
using Flow.Launcher.Plugin;
36
using Flow.Launcher.Plugin.SharedCommands;
47

58
namespace Flow.Launcher.Infrastructure.Storage
@@ -9,6 +12,12 @@ namespace Flow.Launcher.Infrastructure.Storage
912
// Use assembly name to check which plugin is using this storage
1013
public readonly string AssemblyName;
1114

15+
private static readonly string ClassName = "PluginJsonStorage";
16+
17+
// We should not initialize API in static constructor because it will create another API instance
18+
private static IPublicAPI api = null;
19+
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
20+
1221
public PluginJsonStorage()
1322
{
1423
// C# related, add python related below
@@ -24,5 +33,29 @@ public PluginJsonStorage(T data) : this()
2433
{
2534
Data = data;
2635
}
36+
37+
public new void Save()
38+
{
39+
try
40+
{
41+
base.Save();
42+
}
43+
catch (System.Exception e)
44+
{
45+
API.LogException(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
46+
}
47+
}
48+
49+
public new async Task SaveAsync()
50+
{
51+
try
52+
{
53+
await base.SaveAsync();
54+
}
55+
catch (System.Exception e)
56+
{
57+
API.LogException(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
58+
}
59+
}
2760
}
2861
}

Flow.Launcher.Infrastructure/Win32Helper.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,5 +488,16 @@ or PInvoke.LOCALE_TRANSIENT_KEYBOARD3
488488
}
489489

490490
#endregion
491+
492+
#region Notification
493+
494+
public static bool IsNotificationSupported()
495+
{
496+
// Notifications only supported on Windows 10 19041+
497+
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
498+
Environment.OSVersion.Version.Build >= 19041;
499+
}
500+
501+
#endregion
491502
}
492503
}

Flow.Launcher/App.xaml.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,14 @@ protected virtual void Dispose(bool disposing)
304304
return;
305305
}
306306

307+
// If we call Environment.Exit(0), the application dispose will be called before _mainWindow.Close()
308+
// Accessing _mainWindow?.Dispatcher will cause the application stuck
309+
// So here we need to check it and just return so that we will not acees _mainWindow?.Dispatcher
310+
if (!_mainWindow.CanClose)
311+
{
312+
return;
313+
}
314+
307315
_disposed = true;
308316
}
309317

Flow.Launcher/MainWindow.xaml.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ namespace Flow.Launcher
3232
{
3333
public partial class MainWindow : IDisposable
3434
{
35+
#region Public Property
36+
37+
// Window Event: Close Event
38+
public bool CanClose { get; set; } = false;
39+
40+
#endregion
41+
3542
#region Private Fields
3643

3744
// Dependency Injection
@@ -45,8 +52,6 @@ public partial class MainWindow : IDisposable
4552
private readonly ContextMenu _contextMenu = new();
4653
private readonly MainViewModel _viewModel;
4754

48-
// Window Event: Close Event
49-
private bool _canClose = false;
5055
// Window Event: Key Event
5156
private bool _isArrowKeyPressed = false;
5257

@@ -279,15 +284,15 @@ private async void OnLoaded(object sender, RoutedEventArgs _)
279284

280285
private async void OnClosing(object sender, CancelEventArgs e)
281286
{
282-
if (!_canClose)
287+
if (!CanClose)
283288
{
284289
_notifyIcon.Visible = false;
285290
App.API.SaveAppAllSettings();
286291
e.Cancel = true;
287292
await PluginManager.DisposePluginsAsync();
288293
Notification.Uninstall();
289294
// After plugins are all disposed, we can close the main window
290-
_canClose = true;
295+
CanClose = true;
291296
// Use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event
292297
Application.Current.Shutdown();
293298
}

Flow.Launcher/Notification.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ namespace Flow.Launcher
99
{
1010
internal static class Notification
1111
{
12-
internal static bool legacy = Environment.OSVersion.Version.Build < 19041;
13-
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
12+
internal static bool legacy = !Win32Helper.IsNotificationSupported();
13+
1414
internal static void Uninstall()
1515
{
1616
if (!legacy)
@@ -25,7 +25,6 @@ public static void Show(string title, string subTitle, string iconPath = null)
2525
});
2626
}
2727

28-
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
2928
private static void ShowInternal(string title, string subTitle, string iconPath = null)
3029
{
3130
// Handle notification for win7/8/early win10

0 commit comments

Comments
 (0)