Skip to content
Merged
42 changes: 28 additions & 14 deletions Flow.Launcher.Core/Plugin/PluginManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static class PluginManager

private static PluginsSettings Settings;
private static List<PluginMetadata> _metadatas;
private static List<string> _modifiedPlugins = new();
private static readonly List<string> _modifiedPlugins = new();

/// <summary>
/// Directories that will hold Flow Launcher plugin directory
Expand All @@ -61,10 +61,17 @@ private static void DeletePythonBinding()
/// </summary>
public static void Save()
{
foreach (var plugin in AllPlugins)
foreach (var pluginPair in AllPlugins)
{
var savable = plugin.Plugin as ISavable;
savable?.Save();
var savable = pluginPair.Plugin as ISavable;
try
{
savable?.Save();
}
catch (Exception e)
{
API.LogException(ClassName, $"Failed to save plugin {pluginPair.Metadata.Name}", e);
}
}

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

private static async Task DisposePluginAsync(PluginPair pluginPair)
{
switch (pluginPair.Plugin)
try
{
switch (pluginPair.Plugin)
{
case IDisposable disposable:
disposable.Dispose();
break;
case IAsyncDisposable asyncDisposable:
await asyncDisposable.DisposeAsync();
break;
}
}
catch (Exception e)
{
case IDisposable disposable:
disposable.Dispose();
break;
case IAsyncDisposable asyncDisposable:
await asyncDisposable.DisposeAsync();
break;
API.LogException(ClassName, $"Failed to dispose plugin {pluginPair.Metadata.Name}", e);
}
}

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

/// <summary>
Expand Down
17 changes: 8 additions & 9 deletions Flow.Launcher.Core/Resource/Internationalization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public class Internationalization
private const string DefaultFile = "en.xaml";
private const string Extension = ".xaml";
private readonly Settings _settings;
private readonly List<string> _languageDirectories = new List<string>();
private readonly List<ResourceDictionary> _oldResources = new List<ResourceDictionary>();
private readonly List<string> _languageDirectories = new();
private readonly List<ResourceDictionary> _oldResources = new();
private readonly string SystemLanguageCode;

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

private Language GetLanguageByLanguageCode(string languageCode)
private static Language GetLanguageByLanguageCode(string languageCode)
{
var lowercase = languageCode.ToLower();
var language = AvailableLanguages.GetAvailableLanguages().FirstOrDefault(o => o.LanguageCode.ToLower() == lowercase);
Expand Down Expand Up @@ -239,7 +239,7 @@ public List<Language> LoadAvailableLanguages()
return list;
}

public string GetTranslation(string key)
public static string GetTranslation(string key)
{
var translation = Application.Current.TryFindResource(key);
if (translation is string)
Expand All @@ -257,8 +257,7 @@ private void UpdatePluginMetadataTranslations()
{
foreach (var p in PluginManager.GetPluginsForInterface<IPluginI18n>())
{
var pluginI18N = p.Plugin as IPluginI18n;
if (pluginI18N == null) return;
if (p.Plugin is not IPluginI18n pluginI18N) return;
try
{
p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle();
Expand All @@ -272,19 +271,19 @@ private void UpdatePluginMetadataTranslations()
}
}

public string LanguageFile(string folder, string language)
private static string LanguageFile(string folder, string language)
{
if (Directory.Exists(folder))
{
string path = Path.Combine(folder, language);
var path = Path.Combine(folder, language);
if (File.Exists(path))
{
return path;
}
else
{
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))
{
return english;
Expand Down
6 changes: 3 additions & 3 deletions Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ public BinaryStorage(string filename)
[Obsolete("This constructor is obsolete. Use BinaryStorage(string filename) instead.")]
public BinaryStorage(string filename, string directoryPath = null!)
{
directoryPath ??= DataLocation.CacheDirectory;
FilesFolders.ValidateDirectory(directoryPath);
DirectoryPath = directoryPath ?? DataLocation.CacheDirectory;
FilesFolders.ValidateDirectory(DirectoryPath);

FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
}

public async ValueTask<T> TryLoadAsync(T defaultData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ namespace Flow.Launcher.Infrastructure.Storage

public FlowLauncherJsonStorage()
{
var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
FilesFolders.ValidateDirectory(directoryPath);
DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
FilesFolders.ValidateDirectory(DirectoryPath);

var filename = typeof(T).Name;
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
}

public new void Save()
Expand Down
8 changes: 4 additions & 4 deletions Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,12 @@ public static string GetPreviousExistingDirectory(Func<string, bool> locationExi
var index = path.LastIndexOf('\\');
if (index > 0 && index < (path.Length - 1))
{
string previousDirectoryPath = path.Substring(0, index + 1);
return locationExists(previousDirectoryPath) ? previousDirectoryPath : "";
string previousDirectoryPath = path[..(index + 1)];
return locationExists(previousDirectoryPath) ? previousDirectoryPath : string.Empty;
}
else
{
return "";
return string.Empty;
}
}

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

return path.Substring(0, indexOfSeparator + 1);
return path[..(indexOfSeparator + 1)];
}

return path;
Expand Down
10 changes: 10 additions & 0 deletions Flow.Launcher/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>

RegisterAppDomainExceptions();
RegisterDispatcherUnhandledException();
RegisterTaskSchedulerUnhandledException();

var imageLoadertask = ImageLoader.InitializeAsync();

Expand Down Expand Up @@ -284,6 +285,15 @@ private static void RegisterAppDomainExceptions()
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

#region IDisposable
Expand Down
12 changes: 11 additions & 1 deletion Flow.Launcher/Helper/ErrorReporting.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using NLog;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Exception;
using NLog;

namespace Flow.Launcher.Helper;

Expand Down Expand Up @@ -30,6 +32,14 @@ public static void DispatcherUnhandledException(object sender, DispatcherUnhandl
e.Handled = true;
}

public static void TaskSchedulerUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
//handle unobserved task exceptions
Application.Current.Dispatcher.Invoke(() => Report(e.Exception));
//prevent application exist, so the user can copy prompted error info
e.SetObserved();
}

public static string RuntimeInfo()
{
var info =
Expand Down
6 changes: 3 additions & 3 deletions Flow.Launcher/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,15 +291,15 @@ private async void OnClosing(object sender, CancelEventArgs e)
{
if (!CanClose)
{
CanClose = true;
_notifyIcon.Visible = false;
App.API.SaveAppAllSettings();
e.Cancel = true;
await ImageLoader.WaitSaveAsync();
await PluginManager.DisposePluginsAsync();
Notification.Uninstall();
// After plugins are all disposed, we can close the main window
CanClose = true;
// Use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event
// After plugins are all disposed, we shutdown application to close app
// We use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event
Application.Current.Shutdown();
}
}
Expand Down
14 changes: 8 additions & 6 deletions Flow.Launcher/PublicAPIInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,23 @@ namespace Flow.Launcher
public class PublicAPIInstance : IPublicAPI, IRemovable
{
private readonly Settings _settings;
private readonly Internationalization _translater;
private readonly MainViewModel _mainVM;

// Must use getter to access Application.Current.Resources.MergedDictionaries so earlier
private Theme _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();

#region Constructor

public PublicAPIInstance(Settings settings, Internationalization translater, MainViewModel mainVM)
public PublicAPIInstance(Settings settings, MainViewModel mainVM)
{
_settings = settings;
_translater = translater;
_mainVM = mainVM;
GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback;
WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
Expand Down Expand Up @@ -100,8 +103,7 @@ public event VisibilityChangedEventHandler VisibilityChanged
remove => _mainVM.VisibilityChanged -= value;
}

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

public void SaveAppAllSettings()
{
Expand Down Expand Up @@ -178,7 +180,7 @@ public void CopyToClipboard(string stringToCopy, bool directCopy = false, bool s

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();

Expand Down
Loading