Skip to content

Commit c8d16a8

Browse files
authored
Merge pull request #3456 from Flow-Launcher/plugin_exception
Improve FL Exiting & Exception Handler
2 parents 578079c + 5bafe23 commit c8d16a8

File tree

9 files changed

+77
-43
lines changed

9 files changed

+77
-43
lines changed

Flow.Launcher.Core/Plugin/PluginManager.cs

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static class PluginManager
3737

3838
private static PluginsSettings Settings;
3939
private static List<PluginMetadata> _metadatas;
40-
private static List<string> _modifiedPlugins = new();
40+
private static readonly List<string> _modifiedPlugins = new();
4141

4242
/// <summary>
4343
/// Directories that will hold Flow Launcher plugin directory
@@ -61,10 +61,17 @@ private static void DeletePythonBinding()
6161
/// </summary>
6262
public static void Save()
6363
{
64-
foreach (var plugin in AllPlugins)
64+
foreach (var pluginPair in AllPlugins)
6565
{
66-
var savable = plugin.Plugin as ISavable;
67-
savable?.Save();
66+
var savable = pluginPair.Plugin as ISavable;
67+
try
68+
{
69+
savable?.Save();
70+
}
71+
catch (Exception e)
72+
{
73+
API.LogException(ClassName, $"Failed to save plugin {pluginPair.Metadata.Name}", e);
74+
}
6875
}
6976

7077
API.SavePluginSettings();
@@ -81,14 +88,21 @@ public static async ValueTask DisposePluginsAsync()
8188

8289
private static async Task DisposePluginAsync(PluginPair pluginPair)
8390
{
84-
switch (pluginPair.Plugin)
91+
try
92+
{
93+
switch (pluginPair.Plugin)
94+
{
95+
case IDisposable disposable:
96+
disposable.Dispose();
97+
break;
98+
case IAsyncDisposable asyncDisposable:
99+
await asyncDisposable.DisposeAsync();
100+
break;
101+
}
102+
}
103+
catch (Exception e)
85104
{
86-
case IDisposable disposable:
87-
disposable.Dispose();
88-
break;
89-
case IAsyncDisposable asyncDisposable:
90-
await asyncDisposable.DisposeAsync();
91-
break;
105+
API.LogException(ClassName, $"Failed to dispose plugin {pluginPair.Metadata.Name}", e);
92106
}
93107
}
94108

@@ -292,7 +306,7 @@ public static async Task<List<Result>> QueryForPluginAsync(PluginPair pair, Quer
292306
{
293307
Title = $"{metadata.Name}: Failed to respond!",
294308
SubTitle = "Select this result for more info",
295-
IcoPath = Flow.Launcher.Infrastructure.Constant.ErrorIcon,
309+
IcoPath = Constant.ErrorIcon,
296310
PluginDirectory = metadata.PluginDirectory,
297311
ActionKeywordAssigned = query.ActionKeyword,
298312
PluginID = metadata.ID,
@@ -369,8 +383,8 @@ public static bool ActionKeywordRegistered(string actionKeyword)
369383
{
370384
// this method is only checking for action keywords (defined as not '*') registration
371385
// hence the actionKeyword != Query.GlobalPluginWildcardSign logic
372-
return actionKeyword != Query.GlobalPluginWildcardSign
373-
&& NonGlobalPlugins.ContainsKey(actionKeyword);
386+
return actionKeyword != Query.GlobalPluginWildcardSign
387+
&& NonGlobalPlugins.ContainsKey(actionKeyword);
374388
}
375389

376390
/// <summary>

Flow.Launcher.Core/Resource/Internationalization.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ public class Internationalization
2222
private const string DefaultFile = "en.xaml";
2323
private const string Extension = ".xaml";
2424
private readonly Settings _settings;
25-
private readonly List<string> _languageDirectories = new List<string>();
26-
private readonly List<ResourceDictionary> _oldResources = new List<ResourceDictionary>();
25+
private readonly List<string> _languageDirectories = new();
26+
private readonly List<ResourceDictionary> _oldResources = new();
2727
private readonly string SystemLanguageCode;
2828

2929
public Internationalization(Settings settings)
@@ -144,7 +144,7 @@ public void ChangeLanguage(string languageCode)
144144
_settings.Language = isSystem ? Constant.SystemLanguageCode : language.LanguageCode;
145145
}
146146

147-
private Language GetLanguageByLanguageCode(string languageCode)
147+
private static Language GetLanguageByLanguageCode(string languageCode)
148148
{
149149
var lowercase = languageCode.ToLower();
150150
var language = AvailableLanguages.GetAvailableLanguages().FirstOrDefault(o => o.LanguageCode.ToLower() == lowercase);
@@ -239,7 +239,7 @@ public List<Language> LoadAvailableLanguages()
239239
return list;
240240
}
241241

242-
public string GetTranslation(string key)
242+
public static string GetTranslation(string key)
243243
{
244244
var translation = Application.Current.TryFindResource(key);
245245
if (translation is string)
@@ -257,8 +257,7 @@ private void UpdatePluginMetadataTranslations()
257257
{
258258
foreach (var p in PluginManager.GetPluginsForInterface<IPluginI18n>())
259259
{
260-
var pluginI18N = p.Plugin as IPluginI18n;
261-
if (pluginI18N == null) return;
260+
if (p.Plugin is not IPluginI18n pluginI18N) return;
262261
try
263262
{
264263
p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle();
@@ -272,19 +271,19 @@ private void UpdatePluginMetadataTranslations()
272271
}
273272
}
274273

275-
public string LanguageFile(string folder, string language)
274+
private static string LanguageFile(string folder, string language)
276275
{
277276
if (Directory.Exists(folder))
278277
{
279-
string path = Path.Combine(folder, language);
278+
var path = Path.Combine(folder, language);
280279
if (File.Exists(path))
281280
{
282281
return path;
283282
}
284283
else
285284
{
286285
Log.Error($"|Internationalization.LanguageFile|Language path can't be found <{path}>");
287-
string english = Path.Combine(folder, DefaultFile);
286+
var english = Path.Combine(folder, DefaultFile);
288287
if (File.Exists(english))
289288
{
290289
return english;

Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ public BinaryStorage(string filename)
4545
[Obsolete("This constructor is obsolete. Use BinaryStorage(string filename) instead.")]
4646
public BinaryStorage(string filename, string directoryPath = null!)
4747
{
48-
directoryPath ??= DataLocation.CacheDirectory;
49-
FilesFolders.ValidateDirectory(directoryPath);
48+
DirectoryPath = directoryPath ?? DataLocation.CacheDirectory;
49+
FilesFolders.ValidateDirectory(DirectoryPath);
5050

51-
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
51+
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
5252
}
5353

5454
public async ValueTask<T> TryLoadAsync(T defaultData)

Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ namespace Flow.Launcher.Infrastructure.Storage
1717

1818
public FlowLauncherJsonStorage()
1919
{
20-
var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
21-
FilesFolders.ValidateDirectory(directoryPath);
20+
DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
21+
FilesFolders.ValidateDirectory(DirectoryPath);
2222

2323
var filename = typeof(T).Name;
24-
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
24+
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
2525
}
2626

2727
public new void Save()

Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,12 @@ public static string GetPreviousExistingDirectory(Func<string, bool> locationExi
264264
var index = path.LastIndexOf('\\');
265265
if (index > 0 && index < (path.Length - 1))
266266
{
267-
string previousDirectoryPath = path.Substring(0, index + 1);
268-
return locationExists(previousDirectoryPath) ? previousDirectoryPath : "";
267+
string previousDirectoryPath = path[..(index + 1)];
268+
return locationExists(previousDirectoryPath) ? previousDirectoryPath : string.Empty;
269269
}
270270
else
271271
{
272-
return "";
272+
return string.Empty;
273273
}
274274
}
275275

@@ -285,7 +285,7 @@ public static string ReturnPreviousDirectoryIfIncompleteString(string path)
285285
// not full path, get previous level directory string
286286
var indexOfSeparator = path.LastIndexOf('\\');
287287

288-
return path.Substring(0, indexOfSeparator + 1);
288+
return path[..(indexOfSeparator + 1)];
289289
}
290290

291291
return path;

Flow.Launcher/App.xaml.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>
153153

154154
RegisterAppDomainExceptions();
155155
RegisterDispatcherUnhandledException();
156+
RegisterTaskSchedulerUnhandledException();
156157

157158
var imageLoadertask = ImageLoader.InitializeAsync();
158159

@@ -284,6 +285,15 @@ private static void RegisterAppDomainExceptions()
284285
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
285286
}
286287

288+
/// <summary>
289+
/// let exception throw as normal is better for Debug
290+
/// </summary>
291+
[Conditional("RELEASE")]
292+
private static void RegisterTaskSchedulerUnhandledException()
293+
{
294+
TaskScheduler.UnobservedTaskException += ErrorReporting.TaskSchedulerUnobservedTaskException;
295+
}
296+
287297
#endregion
288298

289299
#region IDisposable

Flow.Launcher/Helper/ErrorReporting.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using System;
2+
using System.Threading.Tasks;
3+
using System.Windows;
24
using System.Windows.Threading;
3-
using NLog;
45
using Flow.Launcher.Infrastructure;
56
using Flow.Launcher.Infrastructure.Exception;
7+
using NLog;
68

79
namespace Flow.Launcher.Helper;
810

@@ -30,6 +32,13 @@ public static void DispatcherUnhandledException(object sender, DispatcherUnhandl
3032
e.Handled = true;
3133
}
3234

35+
public static void TaskSchedulerUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
36+
{
37+
//handle unobserved task exceptions
38+
Application.Current.Dispatcher.Invoke(() => Report(e.Exception));
39+
//prevent application exit, so the user can copy the prompted error info
40+
}
41+
3342
public static string RuntimeInfo()
3443
{
3544
var info =

Flow.Launcher/MainWindow.xaml.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,15 +291,15 @@ private async void OnClosing(object sender, CancelEventArgs e)
291291
{
292292
if (!CanClose)
293293
{
294+
CanClose = true;
294295
_notifyIcon.Visible = false;
295296
App.API.SaveAppAllSettings();
296297
e.Cancel = true;
297298
await ImageLoader.WaitSaveAsync();
298299
await PluginManager.DisposePluginsAsync();
299300
Notification.Uninstall();
300-
// After plugins are all disposed, we can close the main window
301-
CanClose = true;
302-
// Use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event
301+
// After plugins are all disposed, we shutdown application to close app
302+
// We use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event
303303
Application.Current.Shutdown();
304304
}
305305
}

Flow.Launcher/PublicAPIInstance.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,23 @@ namespace Flow.Launcher
3838
public class PublicAPIInstance : IPublicAPI, IRemovable
3939
{
4040
private readonly Settings _settings;
41-
private readonly Internationalization _translater;
4241
private readonly MainViewModel _mainVM;
4342

43+
// Must use getter to access Application.Current.Resources.MergedDictionaries so earlier
4444
private Theme _theme;
4545
private Theme Theme => _theme ??= Ioc.Default.GetRequiredService<Theme>();
4646

47+
// Must use getter to avoid circular dependency
48+
private Updater _updater;
49+
private Updater Updater => _updater ??= Ioc.Default.GetRequiredService<Updater>();
50+
4751
private readonly object _saveSettingsLock = new();
4852

4953
#region Constructor
5054

51-
public PublicAPIInstance(Settings settings, Internationalization translater, MainViewModel mainVM)
55+
public PublicAPIInstance(Settings settings, MainViewModel mainVM)
5256
{
5357
_settings = settings;
54-
_translater = translater;
5558
_mainVM = mainVM;
5659
GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback;
5760
WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
@@ -100,8 +103,7 @@ public event VisibilityChangedEventHandler VisibilityChanged
100103
remove => _mainVM.VisibilityChanged -= value;
101104
}
102105

103-
// Must use Ioc.Default.GetRequiredService<Updater>() to avoid circular dependency
104-
public void CheckForNewUpdate() => _ = Ioc.Default.GetRequiredService<Updater>().UpdateAppAsync(false);
106+
public void CheckForNewUpdate() => _ = Updater.UpdateAppAsync(false);
105107

106108
public void SaveAppAllSettings()
107109
{
@@ -178,7 +180,7 @@ public void CopyToClipboard(string stringToCopy, bool directCopy = false, bool s
178180

179181
public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed;
180182

181-
public string GetTranslation(string key) => _translater.GetTranslation(key);
183+
public string GetTranslation(string key) => Internationalization.GetTranslation(key);
182184

183185
public List<PluginPair> GetAllPlugins() => PluginManager.AllPlugins.ToList();
184186

0 commit comments

Comments
 (0)