diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Exceptions/EngineNotAvailableException.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Exceptions/EngineNotAvailableException.cs index 04b200545..340877bd1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Exceptions/EngineNotAvailableException.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Exceptions/EngineNotAvailableException.cs @@ -1,5 +1,10 @@ -using System; +#nullable enable + +using System; +using System.Threading.Tasks; +using System.Windows; using Flow.Launcher.Plugin.Explorer.Search.IProvider; +using JetBrains.Annotations; namespace Flow.Launcher.Plugin.Explorer.Exceptions; @@ -7,14 +12,24 @@ public class EngineNotAvailableException : Exception { public string EngineName { get; } public string Resolution { get; } + public Func>? Action { get; } + + public string? ErrorIcon { get; init; } + public EngineNotAvailableException(string engineName, string resolution, - string message) : base(message) + string message, + Func> action = null) : base(message) { EngineName = engineName; Resolution = resolution; + Action = action ?? (_ => + { + Clipboard.SetDataObject(this.ToString()); + return ValueTask.FromResult(true); + }); } - + public EngineNotAvailableException(string engineName, string resolution, string message, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/error.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/error.png new file mode 100644 index 000000000..3b17d925b Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Explorer/Images/error.png differ diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/everything_error.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/everything_error.png new file mode 100644 index 000000000..ad3eab179 Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Explorer/Images/everything_error.png differ diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/index_error.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/index_error.png new file mode 100644 index 000000000..518d1d75d Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Explorer/Images/index_error.png differ diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/index_error2.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/index_error2.png new file mode 100644 index 000000000..13a3d34fe Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Explorer/Images/index_error2.png differ diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/robot_error.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/robot_error.png new file mode 100644 index 000000000..6b4f83b42 Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Explorer/Images/robot_error.png differ diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 7201347c4..e5b3ab625 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -81,25 +81,28 @@ namespace Flow.Launcher.Plugin.Explorer try { return await searchManager.SearchAsync(query, token); - } - catch (Exception e) when (e is SearchException or SearchException) + catch (Exception e) when (e is SearchException or EngineNotAvailableException) { return new List { new() { Title = e.Message, - SubTitle = e is EngineNotAvailableException engineException - ? engineException.Resolution + SubTitle = e is EngineNotAvailableException { Resolution: { } resolution } + ? resolution : "Enter to copy the message to clipboard", Score = 501, - IcoPath = Constants.ExplorerIconImagePath, - Action = _ => - { - Clipboard.SetDataObject(e.ToString()); - return true; - } + IcoPath = e is EngineNotAvailableException { ErrorIcon: { } iconPath } + ? iconPath + : Constants.GeneralSearchErrorImagePath, + AsyncAction = e is EngineNotAvailableException {Action: { } action} + ? action + : _ => + { + Clipboard.SetDataObject(e.ToString()); + return new ValueTask(true); + } } }; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs index 897033590..8d6a6a738 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs @@ -18,6 +18,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal const string QuickAccessImagePath = "Images\\quickaccess.png"; internal const string RemoveQuickAccessImagePath = "Images\\removequickaccess.png"; internal const string ShowContextMenuImagePath = "Images\\contextmenu.png"; + internal const string EverythingErrorImagePath = "Images\\everything_error.png"; + internal const string IndexSearchWarningImagePath = "Images\\index_error.png"; + internal const string WindowsIndexErrorImagePath = "Images\\index_error2.png"; + internal const string GeneralSearchErrorImagePath = "Images\\robot_error.png"; internal const string ToolTipOpenDirectory = "Ctrl + Enter to open the directory"; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs index 7b47d6bea..cd3183e7b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs @@ -8,6 +8,8 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Windows.Forms.Design; +using Flow.Launcher.Plugin.Explorer.Exceptions; namespace Flow.Launcher.Plugin.Explorer.Search.Everything { @@ -87,6 +89,15 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything return fastSortOptionEnabled; } + public static async ValueTask IsEverythingRunningAsync(CancellationToken token = default) + { + await _semaphore.WaitAsync(token); + EverythingApiDllImport.Everything_GetMajorVersion(); + var result = EverythingApiDllImport.Everything_GetLastError() != StateCode.IPCError; + _semaphore.Release(); + return result; + } + /// /// Searches the specified key word and reset the everything API afterwards /// @@ -101,21 +112,21 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything if (option.MaxCount < 0) throw new ArgumentOutOfRangeException(nameof(option.MaxCount), option.MaxCount, "MaxCount must be greater than or equal to 0"); - + await _semaphore.WaitAsync(token); - + if (token.IsCancellationRequested) yield break; - + try { - + if (option.Keyword.StartsWith("@")) { EverythingApiDllImport.Everything_SetRegex(true); option.Keyword = option.Keyword[1..]; } - + var builder = new StringBuilder(); builder.Append(option.Keyword); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs index 534afcb78..5b80819fa 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs @@ -77,7 +77,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything [DllImport(DLL)] internal static extern int Everything_GetNumFileResults(); - + + [DllImport(DLL)] + internal static extern int Everything_GetMajorVersion(); + [DllImport(DLL)] internal static extern int Everything_GetNumFolderResults(); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingDownloadHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingDownloadHelper.cs index 75bbd2b79..c5ecc250d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingDownloadHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingDownloadHelper.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace Flow.Launcher.Plugin.Explorer.Search.Everything; -public class EverythingDownloadHelper +public static class EverythingDownloadHelper { public static async Task PromptDownloadIfNotInstallAsync(string installedLocation, IPublicAPI api) { @@ -21,15 +21,17 @@ public class EverythingDownloadHelper installedLocation = GetInstalledPath(); - if (string.IsNullOrEmpty(installedLocation) && - System.Windows.Forms.MessageBox.Show( - string.Format(api.GetTranslation("flowlauncher_plugin_everything_installing_select"), Environment.NewLine), - api.GetTranslation("flowlauncher_plugin_everything_installing_title"), - System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes) + if (string.IsNullOrEmpty(installedLocation)) { // Solves single thread apartment (STA) mode requirement error when using OpenFileDialog var t = new Thread(() => { + if (System.Windows.Forms.MessageBox.Show( + string.Format(api.GetTranslation("flowlauncher_plugin_everything_installing_select"), Environment.NewLine), + api.GetTranslation("flowlauncher_plugin_everything_installing_title"), + System.Windows.Forms.MessageBoxButtons.YesNo) != System.Windows.Forms.DialogResult.Yes) + return; + var dlg = new System.Windows.Forms.OpenFileDialog { InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) @@ -51,7 +53,7 @@ public class EverythingDownloadHelper { return installedLocation; } - + api.ShowMsg(api.GetTranslation("flowlauncher_plugin_everything_installing_title"), api.GetTranslation("flowlauncher_plugin_everything_installing_subtitle"), "", useMainWindowAsOwner: false); @@ -96,4 +98,4 @@ public class EverythingDownloadHelper return File.Exists(scoopInstalledPath) ? scoopInstalledPath : string.Empty; } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs index a1892e496..fd977edbe 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs @@ -1,9 +1,14 @@ using System; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Flow.Launcher.Plugin.Explorer.Exceptions; +using Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions; using Flow.Launcher.Plugin.Explorer.Search.IProvider; namespace Flow.Launcher.Plugin.Explorer.Search.Everything @@ -17,37 +22,85 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything Settings = settings; } - - public IAsyncEnumerable SearchAsync(string search, CancellationToken token) + private async ValueTask ThrowIfEverythingNotAvailableAsync(CancellationToken token = default) { - return EverythingApi.SearchAsync( - new EverythingSearchOption(search, Settings.SortOption), - token); + if (!await EverythingApi.IsEverythingRunningAsync(token)) + throw new EngineNotAvailableException( + Enum.GetName(Settings.IndexSearchEngineOption.Everything), + "Click to Open or Download Everything", + "Everything is not running.", + async _ => + { + var installedPath = await EverythingDownloadHelper.PromptDownloadIfNotInstallAsync(Settings.EverythingInstalledPath, Main.Context.API); + if (installedPath == null) + { + Main.Context.API.ShowMsgError("Unable to find Everything.exe"); + return false; + } + Settings.EverythingInstalledPath = installedPath; + Process.Start(installedPath, "-startup"); + return true; + }) + { + ErrorIcon = Constants.EverythingErrorImagePath + }; } - public IAsyncEnumerable ContentSearchAsync(string plainSearch, - string contentSearch, CancellationToken token) + + public async IAsyncEnumerable SearchAsync(string search, [EnumeratorCancellation] CancellationToken token) { + await ThrowIfEverythingNotAvailableAsync(token); + if (token.IsCancellationRequested) + yield break; + var option = new EverythingSearchOption(search, Settings.SortOption); + await foreach (var result in EverythingApi.SearchAsync(option, token)) + yield return result; + } + public async IAsyncEnumerable ContentSearchAsync(string plainSearch, + string contentSearch, [EnumeratorCancellation] CancellationToken token) + { + await ThrowIfEverythingNotAvailableAsync(token); if (!Settings.EnableEverythingContentSearch) { - return AsyncEnumerable.Empty(); + throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything), + "Click to Enable Everything Content Search (only applicable to Everything 1.5+ with indexed content)", + "Everything Content Search is not enabled.", + _ => + { + Settings.EnableEverythingContentSearch = true; + return ValueTask.FromResult(true); + }) + { + ErrorIcon = Constants.EverythingErrorImagePath + }; } + if (token.IsCancellationRequested) + yield break; - return EverythingApi.SearchAsync( - new EverythingSearchOption( - plainSearch, - Settings.SortOption, - true, - contentSearch), - token); + var option = new EverythingSearchOption(plainSearch, + Settings.SortOption, + true, + contentSearch); + + await foreach (var result in EverythingApi.SearchAsync(option, token)) + { + yield return result; + } } - public IAsyncEnumerable EnumerateAsync(string path, string search, bool recursive, CancellationToken token) + public async IAsyncEnumerable EnumerateAsync(string path, string search, bool recursive, [EnumeratorCancellation] CancellationToken token) { - return EverythingApi.SearchAsync( - new EverythingSearchOption(search, - Settings.SortOption, - ParentPath: path, - IsRecursive: recursive), - token); + await ThrowIfEverythingNotAvailableAsync(token); + if (token.IsCancellationRequested) + yield break; + + var option = new EverythingSearchOption(search, + Settings.SortOption, + ParentPath: path, + IsRecursive: recursive); + + await foreach (var result in EverythingApi.SearchAsync(option, token)) + { + yield return result; + } } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs index 9028c6d16..f7a4a019b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs @@ -1,5 +1,4 @@ using Flow.Launcher.Infrastructure.Logger; -using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; using Microsoft.Search.Interop; using System; using System.Collections.Generic; @@ -10,7 +9,6 @@ using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using System.Windows; using Flow.Launcher.Plugin.Explorer.Exceptions; namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex @@ -96,21 +94,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex throw new EngineNotAvailableException("Windows Index", api.GetTranslation("plugin_explorer_windowsSearchServiceFix"), api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"), - e); + e) + { + ErrorIcon = Constants.WindowsIndexErrorImagePath + }; } } - // TODO: Move to General Search Manager - private static void RemoveResultsInExclusionList(List results, IReadOnlyList exclusionList, CancellationToken token) - { - var indexExclusionListCount = exclusionList.Count; - - if (indexExclusionListCount == 0) - return; - results.RemoveAll(searchResult => - exclusionList.Any(exclude => searchResult.FullPath.StartsWith(exclude.Path, StringComparison.OrdinalIgnoreCase)) - ); - } internal static bool PathIsIndexed(string path) { @@ -127,63 +117,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex } } - // TODO: Use a custom exception to handle this - private static List ResultForWindexSearchOff(string rawQuery) - { - var api = SearchManager.Context.API; - return new List - { - new Result - { - Title = api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"), - SubTitle = api.GetTranslation("plugin_explorer_windowsSearchServiceFix"), - Action = c => - { - SearchManager.Settings.WarnWindowsSearchServiceOff = false; - - var pluginsManagerPlugin = api.GetAllPlugins().FirstOrDefault(x => x.Metadata.ID == "9f8f9b14-2518-4907-b211-35ab6290dee7"); - - var actionKeywordCount = pluginsManagerPlugin.Metadata.ActionKeywords.Count; - - if (actionKeywordCount > 1) - LogException("PluginsManager's action keyword has increased to more than 1, this does not allow for determining the " + - "right action keyword. Explorer's code for managing Windows Search service not running exception needs to be updated", - new InvalidOperationException()); - - if (MessageBox.Show(string.Format(api.GetTranslation("plugin_explorer_alternative"), Environment.NewLine), - api.GetTranslation("plugin_explorer_alternative_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes - && actionKeywordCount == 1) - { - api.ChangeQuery($"{pluginsManagerPlugin.Metadata.ActionKeywords[0]} install everything"); - } - else - { - // Clears the warning message because same query string will not alter the displayed result list - api.ChangeQuery(string.Empty); - - api.ChangeQuery(rawQuery); - } - - var mainWindow = Application.Current.MainWindow!; - mainWindow.Show(); - mainWindow.Focus(); - - return false; - }, - IcoPath = Constants.ExplorerIconImagePath - } - }; - } - - private static void LogException(string message, Exception e) - { -#if DEBUG // Please investigate and handle error from index search - throw e; -#else - Log.Exception($"|Flow.Launcher.Plugin.Explorer.IndexSearch|{message}", e); -#endif - } } }