mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #3456 from Flow-Launcher/plugin_exception
Improve FL Exiting & Exception Handler
This commit is contained in:
commit
c8d16a8de4
9 changed files with 77 additions and 43 deletions
|
|
@ -37,7 +37,7 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
private static PluginsSettings Settings;
|
private static PluginsSettings Settings;
|
||||||
private static List<PluginMetadata> _metadatas;
|
private static List<PluginMetadata> _metadatas;
|
||||||
private static List<string> _modifiedPlugins = new();
|
private static readonly List<string> _modifiedPlugins = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Directories that will hold Flow Launcher plugin directory
|
/// Directories that will hold Flow Launcher plugin directory
|
||||||
|
|
@ -61,10 +61,17 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void Save()
|
public static void Save()
|
||||||
{
|
{
|
||||||
foreach (var plugin in AllPlugins)
|
foreach (var pluginPair in AllPlugins)
|
||||||
{
|
{
|
||||||
var savable = plugin.Plugin as ISavable;
|
var savable = pluginPair.Plugin as ISavable;
|
||||||
savable?.Save();
|
try
|
||||||
|
{
|
||||||
|
savable?.Save();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
API.LogException(ClassName, $"Failed to save plugin {pluginPair.Metadata.Name}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
API.SavePluginSettings();
|
API.SavePluginSettings();
|
||||||
|
|
@ -81,14 +88,21 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
private static async Task DisposePluginAsync(PluginPair pluginPair)
|
private static async Task DisposePluginAsync(PluginPair pluginPair)
|
||||||
{
|
{
|
||||||
switch (pluginPair.Plugin)
|
try
|
||||||
{
|
{
|
||||||
case IDisposable disposable:
|
switch (pluginPair.Plugin)
|
||||||
disposable.Dispose();
|
{
|
||||||
break;
|
case IDisposable disposable:
|
||||||
case IAsyncDisposable asyncDisposable:
|
disposable.Dispose();
|
||||||
await asyncDisposable.DisposeAsync();
|
break;
|
||||||
break;
|
case IAsyncDisposable asyncDisposable:
|
||||||
|
await asyncDisposable.DisposeAsync();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
API.LogException(ClassName, $"Failed to dispose plugin {pluginPair.Metadata.Name}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,7 +306,7 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
{
|
{
|
||||||
Title = $"{metadata.Name}: Failed to respond!",
|
Title = $"{metadata.Name}: Failed to respond!",
|
||||||
SubTitle = "Select this result for more info",
|
SubTitle = "Select this result for more info",
|
||||||
IcoPath = Flow.Launcher.Infrastructure.Constant.ErrorIcon,
|
IcoPath = Constant.ErrorIcon,
|
||||||
PluginDirectory = metadata.PluginDirectory,
|
PluginDirectory = metadata.PluginDirectory,
|
||||||
ActionKeywordAssigned = query.ActionKeyword,
|
ActionKeywordAssigned = query.ActionKeyword,
|
||||||
PluginID = metadata.ID,
|
PluginID = metadata.ID,
|
||||||
|
|
@ -369,8 +383,8 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
{
|
{
|
||||||
// this method is only checking for action keywords (defined as not '*') registration
|
// this method is only checking for action keywords (defined as not '*') registration
|
||||||
// hence the actionKeyword != Query.GlobalPluginWildcardSign logic
|
// hence the actionKeyword != Query.GlobalPluginWildcardSign logic
|
||||||
return actionKeyword != Query.GlobalPluginWildcardSign
|
return actionKeyword != Query.GlobalPluginWildcardSign
|
||||||
&& NonGlobalPlugins.ContainsKey(actionKeyword);
|
&& NonGlobalPlugins.ContainsKey(actionKeyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ namespace Flow.Launcher.Core.Resource
|
||||||
private const string DefaultFile = "en.xaml";
|
private const string DefaultFile = "en.xaml";
|
||||||
private const string Extension = ".xaml";
|
private const string Extension = ".xaml";
|
||||||
private readonly Settings _settings;
|
private readonly Settings _settings;
|
||||||
private readonly List<string> _languageDirectories = new List<string>();
|
private readonly List<string> _languageDirectories = new();
|
||||||
private readonly List<ResourceDictionary> _oldResources = new List<ResourceDictionary>();
|
private readonly List<ResourceDictionary> _oldResources = new();
|
||||||
private readonly string SystemLanguageCode;
|
private readonly string SystemLanguageCode;
|
||||||
|
|
||||||
public Internationalization(Settings settings)
|
public Internationalization(Settings settings)
|
||||||
|
|
@ -144,7 +144,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
_settings.Language = isSystem ? Constant.SystemLanguageCode : language.LanguageCode;
|
_settings.Language = isSystem ? Constant.SystemLanguageCode : language.LanguageCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Language GetLanguageByLanguageCode(string languageCode)
|
private static Language GetLanguageByLanguageCode(string languageCode)
|
||||||
{
|
{
|
||||||
var lowercase = languageCode.ToLower();
|
var lowercase = languageCode.ToLower();
|
||||||
var language = AvailableLanguages.GetAvailableLanguages().FirstOrDefault(o => o.LanguageCode.ToLower() == lowercase);
|
var language = AvailableLanguages.GetAvailableLanguages().FirstOrDefault(o => o.LanguageCode.ToLower() == lowercase);
|
||||||
|
|
@ -239,7 +239,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetTranslation(string key)
|
public static string GetTranslation(string key)
|
||||||
{
|
{
|
||||||
var translation = Application.Current.TryFindResource(key);
|
var translation = Application.Current.TryFindResource(key);
|
||||||
if (translation is string)
|
if (translation is string)
|
||||||
|
|
@ -257,8 +257,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
{
|
{
|
||||||
foreach (var p in PluginManager.GetPluginsForInterface<IPluginI18n>())
|
foreach (var p in PluginManager.GetPluginsForInterface<IPluginI18n>())
|
||||||
{
|
{
|
||||||
var pluginI18N = p.Plugin as IPluginI18n;
|
if (p.Plugin is not IPluginI18n pluginI18N) return;
|
||||||
if (pluginI18N == null) return;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle();
|
p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle();
|
||||||
|
|
@ -272,11 +271,11 @@ namespace Flow.Launcher.Core.Resource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string LanguageFile(string folder, string language)
|
private static string LanguageFile(string folder, string language)
|
||||||
{
|
{
|
||||||
if (Directory.Exists(folder))
|
if (Directory.Exists(folder))
|
||||||
{
|
{
|
||||||
string path = Path.Combine(folder, language);
|
var path = Path.Combine(folder, language);
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
return path;
|
return path;
|
||||||
|
|
@ -284,7 +283,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log.Error($"|Internationalization.LanguageFile|Language path can't be found <{path}>");
|
Log.Error($"|Internationalization.LanguageFile|Language path can't be found <{path}>");
|
||||||
string english = Path.Combine(folder, DefaultFile);
|
var english = Path.Combine(folder, DefaultFile);
|
||||||
if (File.Exists(english))
|
if (File.Exists(english))
|
||||||
{
|
{
|
||||||
return english;
|
return english;
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,10 @@ namespace Flow.Launcher.Infrastructure.Storage
|
||||||
[Obsolete("This constructor is obsolete. Use BinaryStorage(string filename) instead.")]
|
[Obsolete("This constructor is obsolete. Use BinaryStorage(string filename) instead.")]
|
||||||
public BinaryStorage(string filename, string directoryPath = null!)
|
public BinaryStorage(string filename, string directoryPath = null!)
|
||||||
{
|
{
|
||||||
directoryPath ??= DataLocation.CacheDirectory;
|
DirectoryPath = directoryPath ?? DataLocation.CacheDirectory;
|
||||||
FilesFolders.ValidateDirectory(directoryPath);
|
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||||
|
|
||||||
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
|
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<T> TryLoadAsync(T defaultData)
|
public async ValueTask<T> TryLoadAsync(T defaultData)
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@ namespace Flow.Launcher.Infrastructure.Storage
|
||||||
|
|
||||||
public FlowLauncherJsonStorage()
|
public FlowLauncherJsonStorage()
|
||||||
{
|
{
|
||||||
var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
|
DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
|
||||||
FilesFolders.ValidateDirectory(directoryPath);
|
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||||
|
|
||||||
var filename = typeof(T).Name;
|
var filename = typeof(T).Name;
|
||||||
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
|
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public new void Save()
|
public new void Save()
|
||||||
|
|
|
||||||
|
|
@ -264,12 +264,12 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
||||||
var index = path.LastIndexOf('\\');
|
var index = path.LastIndexOf('\\');
|
||||||
if (index > 0 && index < (path.Length - 1))
|
if (index > 0 && index < (path.Length - 1))
|
||||||
{
|
{
|
||||||
string previousDirectoryPath = path.Substring(0, index + 1);
|
string previousDirectoryPath = path[..(index + 1)];
|
||||||
return locationExists(previousDirectoryPath) ? previousDirectoryPath : "";
|
return locationExists(previousDirectoryPath) ? previousDirectoryPath : string.Empty;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return "";
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,7 +285,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
||||||
// not full path, get previous level directory string
|
// not full path, get previous level directory string
|
||||||
var indexOfSeparator = path.LastIndexOf('\\');
|
var indexOfSeparator = path.LastIndexOf('\\');
|
||||||
|
|
||||||
return path.Substring(0, indexOfSeparator + 1);
|
return path[..(indexOfSeparator + 1)];
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,7 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
RegisterAppDomainExceptions();
|
RegisterAppDomainExceptions();
|
||||||
RegisterDispatcherUnhandledException();
|
RegisterDispatcherUnhandledException();
|
||||||
|
RegisterTaskSchedulerUnhandledException();
|
||||||
|
|
||||||
var imageLoadertask = ImageLoader.InitializeAsync();
|
var imageLoadertask = ImageLoader.InitializeAsync();
|
||||||
|
|
||||||
|
|
@ -284,6 +285,15 @@ namespace Flow.Launcher
|
||||||
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
|
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// let exception throw as normal is better for Debug
|
||||||
|
/// </summary>
|
||||||
|
[Conditional("RELEASE")]
|
||||||
|
private static void RegisterTaskSchedulerUnhandledException()
|
||||||
|
{
|
||||||
|
TaskScheduler.UnobservedTaskException += ErrorReporting.TaskSchedulerUnobservedTaskException;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region IDisposable
|
#region IDisposable
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using NLog;
|
|
||||||
using Flow.Launcher.Infrastructure;
|
using Flow.Launcher.Infrastructure;
|
||||||
using Flow.Launcher.Infrastructure.Exception;
|
using Flow.Launcher.Infrastructure.Exception;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
namespace Flow.Launcher.Helper;
|
namespace Flow.Launcher.Helper;
|
||||||
|
|
||||||
|
|
@ -30,6 +32,13 @@ public static class ErrorReporting
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void TaskSchedulerUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
|
||||||
|
{
|
||||||
|
//handle unobserved task exceptions
|
||||||
|
Application.Current.Dispatcher.Invoke(() => Report(e.Exception));
|
||||||
|
//prevent application exit, so the user can copy the prompted error info
|
||||||
|
}
|
||||||
|
|
||||||
public static string RuntimeInfo()
|
public static string RuntimeInfo()
|
||||||
{
|
{
|
||||||
var info =
|
var info =
|
||||||
|
|
|
||||||
|
|
@ -291,15 +291,15 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
if (!CanClose)
|
if (!CanClose)
|
||||||
{
|
{
|
||||||
|
CanClose = true;
|
||||||
_notifyIcon.Visible = false;
|
_notifyIcon.Visible = false;
|
||||||
App.API.SaveAppAllSettings();
|
App.API.SaveAppAllSettings();
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
await ImageLoader.WaitSaveAsync();
|
await ImageLoader.WaitSaveAsync();
|
||||||
await PluginManager.DisposePluginsAsync();
|
await PluginManager.DisposePluginsAsync();
|
||||||
Notification.Uninstall();
|
Notification.Uninstall();
|
||||||
// After plugins are all disposed, we can close the main window
|
// After plugins are all disposed, we shutdown application to close app
|
||||||
CanClose = true;
|
// We use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event
|
||||||
// Use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event
|
|
||||||
Application.Current.Shutdown();
|
Application.Current.Shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,20 +38,23 @@ namespace Flow.Launcher
|
||||||
public class PublicAPIInstance : IPublicAPI, IRemovable
|
public class PublicAPIInstance : IPublicAPI, IRemovable
|
||||||
{
|
{
|
||||||
private readonly Settings _settings;
|
private readonly Settings _settings;
|
||||||
private readonly Internationalization _translater;
|
|
||||||
private readonly MainViewModel _mainVM;
|
private readonly MainViewModel _mainVM;
|
||||||
|
|
||||||
|
// Must use getter to access Application.Current.Resources.MergedDictionaries so earlier
|
||||||
private Theme _theme;
|
private Theme _theme;
|
||||||
private Theme Theme => _theme ??= Ioc.Default.GetRequiredService<Theme>();
|
private Theme Theme => _theme ??= Ioc.Default.GetRequiredService<Theme>();
|
||||||
|
|
||||||
|
// Must use getter to avoid circular dependency
|
||||||
|
private Updater _updater;
|
||||||
|
private Updater Updater => _updater ??= Ioc.Default.GetRequiredService<Updater>();
|
||||||
|
|
||||||
private readonly object _saveSettingsLock = new();
|
private readonly object _saveSettingsLock = new();
|
||||||
|
|
||||||
#region Constructor
|
#region Constructor
|
||||||
|
|
||||||
public PublicAPIInstance(Settings settings, Internationalization translater, MainViewModel mainVM)
|
public PublicAPIInstance(Settings settings, MainViewModel mainVM)
|
||||||
{
|
{
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
_translater = translater;
|
|
||||||
_mainVM = mainVM;
|
_mainVM = mainVM;
|
||||||
GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback;
|
GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback;
|
||||||
WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
|
WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
|
||||||
|
|
@ -100,8 +103,7 @@ namespace Flow.Launcher
|
||||||
remove => _mainVM.VisibilityChanged -= value;
|
remove => _mainVM.VisibilityChanged -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must use Ioc.Default.GetRequiredService<Updater>() to avoid circular dependency
|
public void CheckForNewUpdate() => _ = Updater.UpdateAppAsync(false);
|
||||||
public void CheckForNewUpdate() => _ = Ioc.Default.GetRequiredService<Updater>().UpdateAppAsync(false);
|
|
||||||
|
|
||||||
public void SaveAppAllSettings()
|
public void SaveAppAllSettings()
|
||||||
{
|
{
|
||||||
|
|
@ -178,7 +180,7 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed;
|
public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed;
|
||||||
|
|
||||||
public string GetTranslation(string key) => _translater.GetTranslation(key);
|
public string GetTranslation(string key) => Internationalization.GetTranslation(key);
|
||||||
|
|
||||||
public List<PluginPair> GetAllPlugins() => PluginManager.AllPlugins.ToList();
|
public List<PluginPair> GetAllPlugins() => PluginManager.AllPlugins.ToList();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue