From 6fdff2971c99f5c4b17bf339653406eadd66b827 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Fri, 25 Mar 2022 16:19:00 -0500 Subject: [PATCH 01/63] Merge Explorer & Everything Plugins (Step 1) 1. Shared Interface for Windows Index and Everything Index 2. Settings Merge (Part 1) 3. Include Everything dll --- .../EverythingSDK/Everything64.dll | Bin 0 -> 93000 bytes .../EverythingSDK/Everything86.dll | Bin 0 -> 85320 bytes .../Flow.Launcher.Plugin.Explorer.csproj | 3 + Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 6 +- .../Search/Everything/EverythingAPI.cs | 193 ++++++++++++++++++ .../Everything/EverythingApiDllImport.cs | 153 ++++++++++++++ .../Everything/EverythingSearchManager.cs | 27 +++ .../Search/Everything/SortOption.cs | 39 ++++ .../Search/IPathEnumerable.cs | 9 + .../Search/ResultManager.cs | 24 ++- .../Search/SearchManager.cs | 77 ++----- .../Search/SearchResult.cs | 14 ++ .../Search/WindowsIndex/IIndexProvider.cs | 13 ++ .../{IndexSearch.cs => WindowsIndex.cs} | 72 +++---- .../WindowsIndex/WindowsIndexManager.cs | 70 +++++++ .../Flow.Launcher.Plugin.Explorer/Settings.cs | 41 ++++ .../ViewModels/SettingsViewModel.cs | 4 +- .../Views/ExplorerSettings.xaml | 19 +- .../Views/ExplorerSettings.xaml.cs | 4 +- 19 files changed, 642 insertions(+), 126 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/Everything64.dll create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/Everything86.dll create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/SortOption.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs rename Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/{IndexSearch.cs => WindowsIndex.cs} (71%) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexManager.cs diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/Everything64.dll b/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/Everything64.dll new file mode 100644 index 0000000000000000000000000000000000000000..6d093b79316c04a8b3ff7db50a1a3e37d6c81425 GIT binary patch literal 93000 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 literal 0 HcmV?d00001 diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/Everything86.dll b/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/Everything86.dll new file mode 100644 index 0000000000000000000000000000000000000000..de73b87d109a74e490b57434abc5f413906152aa GIT binary patch literal 85320 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 literal 0 HcmV?d00001 diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj index b4ab89a361d..ae48ac3ee3d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -35,6 +35,9 @@ Designer PreserveNewest + + PreserveNewest + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 60208759ed0..d8879a4f675 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -1,9 +1,12 @@ using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin.Explorer.Search; +using Flow.Launcher.Plugin.Explorer.Search.Everything; using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; using Flow.Launcher.Plugin.Explorer.ViewModels; using Flow.Launcher.Plugin.Explorer.Views; +using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -47,7 +50,8 @@ public Task InitAsync(PluginInitContext context) contextMenu = new ContextMenu(Context, Settings, viewModel); searchManager = new SearchManager(Settings, Context); ResultManager.Init(Context, Settings); - + EverythingApiDllImport.Load(Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "EverythingSDK", + Environment.Is64BitProcess ? "Everything64.dll" : "Everything86.dll")); return Task.CompletedTask; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs new file mode 100644 index 00000000000..089a0f8ffab --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs @@ -0,0 +1,193 @@ +using Flow.Launcher.Plugin.Everything.Everything; +using Flow.Launcher.Plugin.Everything.Everything.Exceptions; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything +{ + + public static class EverythingApi + { + + private const int BufferSize = 4096; + + private static readonly object syncObject = new object(); + // cached buffer to remove redundant allocations. + private static readonly StringBuilder buffer = new StringBuilder(BufferSize); + + public enum StateCode + { + OK, + MemoryError, + IPCError, + RegisterClassExError, + CreateWindowError, + CreateThreadError, + InvalidIndexError, + InvalidCallError + } + + /// + /// Gets or sets a value indicating whether [match path]. + /// + /// true if [match path]; otherwise, false. + public static bool MatchPath + { + get => EverythingApiDllImport.Everything_GetMatchPath(); + set => EverythingApiDllImport.Everything_SetMatchPath(value); + } + + /// + /// Gets or sets a value indicating whether [match case]. + /// + /// true if [match case]; otherwise, false. + public static bool MatchCase + { + get => EverythingApiDllImport.Everything_GetMatchCase(); + set => EverythingApiDllImport.Everything_SetMatchCase(value); + } + + /// + /// Gets or sets a value indicating whether [match whole word]. + /// + /// true if [match whole word]; otherwise, false. + public static bool MatchWholeWord + { + get => EverythingApiDllImport.Everything_GetMatchWholeWord(); + set => EverythingApiDllImport.Everything_SetMatchWholeWord(value); + } + + /// + /// Gets or sets a value indicating whether [enable regex]. + /// + /// true if [enable regex]; otherwise, false. + public static bool EnableRegex + { + get => EverythingApiDllImport.Everything_GetRegex(); + set => EverythingApiDllImport.Everything_SetRegex(value); + } + + /// + /// Resets this instance. + /// + private static void Reset() + { + lock (syncObject) + { + EverythingApiDllImport.Everything_Reset(); + } + } + + /// + /// Checks whether the sort option is Fast Sort. + /// + public static bool IsFastSortOption(SortOption sortOption) + { + var fastSortOptionEnabled = EverythingApiDllImport.Everything_IsFastSort(sortOption); + + // If the Everything service is not running, then this call will incorrectly report + // the state as false. This checks for errors thrown by the api and up to the caller to handle. + CheckAndThrowExceptionOnError(); + + return fastSortOptionEnabled; + } + + /// + /// Searches the specified key word and reset the everything API afterwards + /// + /// The key word. + /// when cancelled the current search will stop and exit (and would not reset) + /// Sort By + /// The offset. + /// The max count. + /// + public static IEnumerable SearchAsync(string keyword, CancellationToken token, SortOption sortOption = SortOption.NAME_ASCENDING, int offset = 0, int maxCount = 100) + { + if (string.IsNullOrEmpty(keyword)) + throw new ArgumentNullException(nameof(keyword)); + + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset)); + + if (maxCount < 0) + throw new ArgumentOutOfRangeException(nameof(maxCount)); + + lock (syncObject) + { + if (keyword.StartsWith("@")) + { + EverythingApiDllImport.Everything_SetRegex(true); + keyword = keyword[1..]; + } + + EverythingApiDllImport.Everything_SetSearchW(keyword); + EverythingApiDllImport.Everything_SetOffset(offset); + EverythingApiDllImport.Everything_SetMax(maxCount); + + EverythingApiDllImport.Everything_SetSort(sortOption); + + if (token.IsCancellationRequested) + { + return null; + } + + + if (!EverythingApiDllImport.Everything_QueryW(true)) + { + CheckAndThrowExceptionOnError(); + return null; + } + + var results = new List(); + for (var idx = 0; idx < EverythingApiDllImport.Everything_GetNumResults(); ++idx) + { + if (token.IsCancellationRequested) + { + return null; + } + + EverythingApiDllImport.Everything_GetResultFullPathNameW(idx, buffer, BufferSize); + + var result = new SearchResult + { + FullPath = buffer.ToString(), + Type = EverythingApiDllImport.Everything_IsFolderResult(idx) ? ResultType.Folder : + EverythingApiDllImport.Everything_IsFileResult(idx) ? ResultType.File : + ResultType.Volume + }; + + results.Add(result); + } + + Reset(); + + return results; + } + } + + private static void CheckAndThrowExceptionOnError() + { + switch (EverythingApiDllImport.Everything_GetLastError()) + { + case StateCode.CreateThreadError: + throw new CreateThreadException(); + case StateCode.CreateWindowError: + throw new CreateWindowException(); + case StateCode.InvalidCallError: + throw new InvalidCallException(); + case StateCode.InvalidIndexError: + throw new InvalidIndexException(); + case StateCode.IPCError: + throw new IPCErrorException(); + case StateCode.MemoryError: + throw new MemoryErrorException(); + case StateCode.RegisterClassExError: + throw new RegisterClassExException(); + } + } + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs new file mode 100644 index 00000000000..b100a602653 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs @@ -0,0 +1,153 @@ +using Flow.Launcher.Plugin.Everything.Everything; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything +{ + public static class EverythingApiDllImport + { + public static void Load(string path) + { + LoadLibrary(path); + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern int LoadLibrary(string name); + + private const string DLL = "Everything.dll"; + + [DllImport(DLL, CharSet = CharSet.Unicode)] + internal static extern int Everything_SetSearchW(string lpSearchString); + + [DllImport(DLL)] + internal static extern void Everything_SetMatchPath(bool bEnable); + + [DllImport(DLL)] + internal static extern void Everything_SetMatchCase(bool bEnable); + + [DllImport(DLL)] + internal static extern void Everything_SetMatchWholeWord(bool bEnable); + + [DllImport(DLL)] + internal static extern void Everything_SetRegex(bool bEnable); + + [DllImport(DLL)] + internal static extern void Everything_SetMax(int dwMax); + + [DllImport(DLL)] + internal static extern void Everything_SetOffset(int dwOffset); + + [DllImport(DLL)] + internal static extern bool Everything_GetMatchPath(); + + [DllImport(DLL)] + internal static extern bool Everything_GetMatchCase(); + + [DllImport(DLL)] + internal static extern bool Everything_GetMatchWholeWord(); + + [DllImport(DLL)] + internal static extern bool Everything_GetRegex(); + + [DllImport(DLL)] + internal static extern uint Everything_GetMax(); + + [DllImport(DLL)] + internal static extern uint Everything_GetOffset(); + + [DllImport(DLL, CharSet = CharSet.Unicode)] + internal static extern string Everything_GetSearchW(); + + [DllImport(DLL)] + internal static extern EverythingApi.StateCode Everything_GetLastError(); + + [DllImport(DLL, CharSet = CharSet.Unicode)] + internal static extern bool Everything_QueryW(bool bWait); + + [DllImport(DLL)] + internal static extern void Everything_SortResultsByPath(); + + [DllImport(DLL)] + internal static extern int Everything_GetNumFileResults(); + + [DllImport(DLL)] + internal static extern int Everything_GetNumFolderResults(); + + [DllImport(DLL)] + internal static extern int Everything_GetNumResults(); + + [DllImport(DLL)] + internal static extern int Everything_GetTotFileResults(); + + [DllImport(DLL)] + internal static extern int Everything_GetTotFolderResults(); + + [DllImport(DLL)] + internal static extern int Everything_GetTotResults(); + + [DllImport(DLL)] + internal static extern bool Everything_IsVolumeResult(int nIndex); + + [DllImport(DLL)] + internal static extern bool Everything_IsFolderResult(int nIndex); + + [DllImport(DLL)] + internal static extern bool Everything_IsFileResult(int nIndex); + + [DllImport(DLL, CharSet = CharSet.Unicode)] + internal static extern void Everything_GetResultFullPathNameW(int nIndex, StringBuilder lpString, int nMaxCount); + + [DllImport(DLL)] + internal static extern void Everything_Reset(); + + // Everything 1.4 + + [DllImport(DLL)] + public static extern void Everything_SetSort(SortOption dwSortType); + [DllImport(DLL)] + public static extern bool Everything_IsFastSort(SortOption dwSortType); + [DllImport(DLL)] + public static extern SortOption Everything_GetSort(); + [DllImport(DLL)] + public static extern uint Everything_GetResultListSort(); + [DllImport(DLL)] + public static extern void Everything_SetRequestFlags(uint dwRequestFlags); + [DllImport(DLL)] + public static extern uint Everything_GetRequestFlags(); + [DllImport(DLL)] + public static extern uint Everything_GetResultListRequestFlags(); + [DllImport("Everything64.dll", CharSet = CharSet.Unicode)] + public static extern IntPtr Everything_GetResultExtension(uint nIndex); + [DllImport(DLL)] + public static extern bool Everything_GetResultSize(uint nIndex, out long lpFileSize); + [DllImport(DLL)] + public static extern bool Everything_GetResultDateCreated(uint nIndex, out long lpFileTime); + [DllImport(DLL)] + public static extern bool Everything_GetResultDateModified(uint nIndex, out long lpFileTime); + [DllImport(DLL)] + public static extern bool Everything_GetResultDateAccessed(uint nIndex, out long lpFileTime); + [DllImport(DLL)] + public static extern uint Everything_GetResultAttributes(uint nIndex); + [DllImport(DLL, CharSet = CharSet.Unicode)] + public static extern IntPtr Everything_GetResultFileListFileName(uint nIndex); + [DllImport(DLL)] + public static extern uint Everything_GetResultRunCount(uint nIndex); + [DllImport(DLL)] + public static extern bool Everything_GetResultDateRun(uint nIndex, out long lpFileTime); + [DllImport(DLL)] + public static extern bool Everything_GetResultDateRecentlyChanged(uint nIndex, out long lpFileTime); + [DllImport(DLL, CharSet = CharSet.Unicode)] + public static extern IntPtr Everything_GetResultHighlightedFileName(uint nIndex); + [DllImport(DLL, CharSet = CharSet.Unicode)] + public static extern IntPtr Everything_GetResultHighlightedPath(uint nIndex); + [DllImport(DLL, CharSet = CharSet.Unicode)] + public static extern IntPtr Everything_GetResultHighlightedFullPathAndFileName(uint nIndex); + [DllImport(DLL)] + public static extern uint Everything_GetRunCountFromFileName(string lpFileName); + [DllImport(DLL)] + public static extern bool Everything_SetRunCountFromFileName(string lpFileName, uint dwRunCount); + [DllImport(DLL)] + public static extern uint Everything_IncRunCountFromFileName(string lpFileName); + } +} \ 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 new file mode 100644 index 00000000000..e1486f9fb21 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs @@ -0,0 +1,27 @@ +using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything +{ + public class EverythingSearchManager : IIndexProvider + { + private Settings Settings { get; } + + public EverythingSearchManager(Settings settings) + { + Settings = settings; + } + + + public ValueTask> SearchAsync(Query query, CancellationToken token) + { + return ValueTask.FromResult(EverythingApi.SearchAsync(query.Search, token, Settings.SortOption)); + } + public ValueTask> ContentSearchAsync(Query query, CancellationToken token) + { + return new ValueTask>(new List()); + } + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/SortOption.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/SortOption.cs new file mode 100644 index 00000000000..434afd1b465 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/SortOption.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Everything.Everything +{ + public enum SortOption : uint + { + NAME_ASCENDING = 1u, + NAME_DESCENDING = 2u, + PATH_ASCENDING = 3u, + PATH_DESCENDING = 4u, + SIZE_ASCENDING = 5u, + SIZE_DESCENDING = 6u, + EXTENSION_ASCENDING = 7u, + EXTENSION_DESCENDING = 8u, + TYPE_NAME_ASCENDING = 9u, + TYPE_NAME_DESCENDING = 10u, + DATE_CREATED_ASCENDING = 11u, + DATE_CREATED_DESCENDING = 12u, + DATE_MODIFIED_ASCENDING = 13u, + DATE_MODIFIED_DESCENDING = 14u, + ATTRIBUTES_ASCENDING = 15u, + ATTRIBUTES_DESCENDING = 16u, + FILE_LIST_FILENAME_ASCENDING = 17u, + FILE_LIST_FILENAME_DESCENDING = 18u, + RUN_COUNT_ASCENDING = 19u, + RUN_COUNT_DESCENDING = 20u, + DATE_RECENTLY_CHANGED_ASCENDING = 21u, + DATE_RECENTLY_CHANGED_DESCENDING = 22u, + DATE_ACCESSED_ASCENDING = 23u, + DATE_ACCESSED_DESCENDING = 24u, + DATE_RUN_ASCENDING = 25u, + DATE_RUN_DESCENDING = 26u + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs new file mode 100644 index 00000000000..d7f0a8ec8f4 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Flow.Launcher.Plugin.Explorer.Search +{ + public interface IPathEnumerable + { + public IEnumerable Enumerate(string path, bool recursive); + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 63310bebd3a..35ba0bafbb3 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -35,6 +35,18 @@ private static string GetPathWithActionKeyword(string path, ResultType type) return $"{keyword}{formatted_path}"; } + public static Result CreateResult(Query query, SearchResult result) + { + return result.Type switch + { + ResultType.Folder or ResultType.Volume => CreateFolderResult(Path.GetFileName(result.FullPath), + result.FullPath, result.FullPath, query, 0, result.ShowIndexState, result.WindowsIndexed), + ResultType.File => CreateFileResult( + result.FullPath, query, 0, result.ShowIndexState, result.WindowsIndexed), + _ => throw new ArgumentOutOfRangeException() + }; + } + internal static Result CreateFolderResult(string title, string subtitle, string path, Query query, int score = 0, bool showIndexState = false, bool windowsIndexed = false) { return new Result @@ -61,7 +73,7 @@ internal static Result CreateFolderResult(string title, string subtitle, string } Context.API.ChangeQuery(GetPathWithActionKeyword(path, ResultType.Folder)); - + return false; }, Score = score, @@ -192,16 +204,6 @@ internal static Result CreateFileResult(string filePath, Query query, int score } } - internal class SearchResult - { - public string FullPath { get; set; } - public ResultType Type { get; set; } - - public bool WindowsIndexed { get; set; } - - public bool ShowIndexState { get; set; } - } - public enum ResultType { Volume, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 8ce627a44f0..080621f420a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -57,8 +57,16 @@ internal async Task> SearchAsync(Query query, CancellationToken tok results.UnionWith(quickaccessLinks); } + IEnumerable searchResults; + if (IsFileContentSearch(query.ActionKeyword)) - return await WindowsIndexFileContentSearchAsync(query, querySearch, token).ConfigureAwait(false); + { + searchResults = await Settings.IndexProvider.ContentSearchAsync(query, token); + } + else + { + searchResults = await Settings.IndexProvider.SearchAsync(query, token); + } if (ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword) || ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)) @@ -71,8 +79,8 @@ internal async Task> SearchAsync(Query query, CancellationToken tok querySearch.Length > 0 && !querySearch.IsLocationPathString()) { - results.UnionWith(await WindowsIndexFilesAndFoldersSearchAsync(query, querySearch, token) - .ConfigureAwait(false)); + if (searchResults != null) + results.UnionWith(searchResults.Select(search => ResultManager.CreateResult(query, search))); } return results.ToList(); @@ -93,7 +101,7 @@ private bool ActionKeywordMatch(Query query, Settings.ActionKeyword allowedActio Settings.ActionKeyword.IndexSearchActionKeyword => Settings.IndexSearchKeywordEnabled && keyword == Settings.IndexSearchActionKeyword, Settings.ActionKeyword.QuickAccessActionKeyword => Settings.QuickAccessKeywordEnabled && - keyword == Settings.QuickAccessActionKeyword + keyword == Settings.QuickAccessActionKeyword }; } @@ -126,37 +134,20 @@ public async Task> PathSearchAsync(Query query, CancellationToken t token.ThrowIfCancellationRequested(); - var directoryResult = await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync, - DirectoryInfoClassSearch, - useIndexSearch, - query, - locationPath, - token).ConfigureAwait(false); + // var directoryResult = await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync, + // DirectoryInfoClassSearch, + // useIndexSearch, + // query, + // locationPath, + // token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); - results.UnionWith(directoryResult); + // results.UnionWith(directoryResult); return results.ToList(); } - private async Task> WindowsIndexFileContentSearchAsync(Query query, string querySearchString, - CancellationToken token) - { - var queryConstructor = new QueryConstructor(Settings); - - if (string.IsNullOrEmpty(querySearchString)) - return new List(); - - return await IndexSearch.WindowsIndexSearchAsync( - querySearchString, - queryConstructor.CreateQueryHelper, - queryConstructor.QueryForFileContentSearch, - Settings.IndexSearchExcludedSubdirectoryPaths, - query, - token).ConfigureAwait(false); - } - public bool IsFileContentSearch(string actionKeyword) { return actionKeyword == Settings.FileContentSearchActionKeyword; @@ -181,34 +172,6 @@ public async Task> TopLevelDirectorySearchBehaviourAsync( return await windowsIndexSearch(query, querySearchString, token); } - private async Task> WindowsIndexFilesAndFoldersSearchAsync(Query query, string querySearchString, - CancellationToken token) - { - var queryConstructor = new QueryConstructor(Settings); - - return await IndexSearch.WindowsIndexSearchAsync( - querySearchString, - queryConstructor.CreateQueryHelper, - queryConstructor.QueryForAllFilesAndFolders, - Settings.IndexSearchExcludedSubdirectoryPaths, - query, - token).ConfigureAwait(false); - } - - private async Task> WindowsIndexTopLevelFolderSearchAsync(Query query, string path, - CancellationToken token) - { - var queryConstructor = new QueryConstructor(Settings); - - return await IndexSearch.WindowsIndexSearchAsync( - path, - queryConstructor.CreateQueryHelper, - queryConstructor.QueryForTopLevelDirectorySearch, - Settings.IndexSearchExcludedSubdirectoryPaths, - query, - token).ConfigureAwait(false); - } - private bool UseWindowsIndexForDirectorySearch(string locationPath) { var pathToDirectory = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath); @@ -221,7 +184,7 @@ private bool UseWindowsIndexForDirectorySearch(string locationPath) .StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))) return false; - return IndexSearch.PathIsIndexed(pathToDirectory); + return WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory); } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs new file mode 100644 index 00000000000..815c6b7b0a1 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs @@ -0,0 +1,14 @@ +using System; + +namespace Flow.Launcher.Plugin.Explorer.Search +{ + public struct SearchResult + { + public string FullPath { get; init; } + public ResultType Type { get; init; } + + public bool WindowsIndexed { get; init; } + + public bool ShowIndexState { get; init; } + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs new file mode 100644 index 00000000000..e42d78f9a18 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs @@ -0,0 +1,13 @@ +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex +{ + public interface IIndexProvider + { + public ValueTask> SearchAsync(Query query, CancellationToken token); + public ValueTask> ContentSearchAsync(Query query, CancellationToken token); + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs similarity index 71% rename from Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs rename to Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs index 2e842c84354..28e9c0e71f3 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs @@ -13,16 +13,15 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { - internal static class IndexSearch + internal static class WindowsIndex { // Reserved keywords in oleDB - private const string reservedStringPattern = @"^[`\@\#\^,\&\/\\\$\%_;\[\]]+$"; + private const string ReservedStringPattern = @"^[`\@\#\^,\&\/\\\$\%_;\[\]]+$"; - internal static async Task> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token) + private static async Task> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token) { - var results = new List(); - var fileResults = new List(); + var results = new List(); try { @@ -35,7 +34,7 @@ internal static async Task> ExecuteWindowsIndexSearchAsync(string i await using var dataReaderResults = await command.ExecuteReaderAsync(token) as OleDbDataReader; token.ThrowIfCancellationRequested(); - if (dataReaderResults.HasRows) + if (dataReaderResults is { HasRows: true }) { while (await dataReaderResults.ReadAsync(token)) { @@ -49,18 +48,12 @@ internal static async Task> ExecuteWindowsIndexSearchAsync(string i var path = new Uri(encodedFragmentPath).LocalPath; - if (dataReaderResults.GetString(2) == "Directory") + results.Add(new SearchResult() { - results.Add(ResultManager.CreateFolderResult( - dataReaderResults.GetString(0), - path, - path, - query, 0, true, true)); - } - else - { - fileResults.Add(ResultManager.CreateFileResult(path, query, 0, true, true)); - } + FullPath = path, + Type = dataReaderResults.GetString(2) == "Directory" ? ResultType.Folder : ResultType.File, + WindowsIndexed = true + }); } } } @@ -80,13 +73,11 @@ internal static async Task> ExecuteWindowsIndexSearchAsync(string i LogException("General error from performing index search", e); } - results.AddRange(fileResults); - - // Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. - return results; + // Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. + return results; } - internal async static Task> WindowsIndexSearchAsync( + internal static async ValueTask> WindowsIndexSearchAsync( string searchString, Func createQueryHelper, Func constructQuery, @@ -94,28 +85,15 @@ internal async static Task> WindowsIndexSearchAsync( Query query, CancellationToken token) { - var regexMatch = Regex.Match(searchString, reservedStringPattern); + var regexMatch = Regex.Match(searchString, ReservedStringPattern); if (regexMatch.Success) - return new List(); - - try - { - var constructedQuery = constructQuery(searchString); + return new(); - return RemoveResultsInExclusionList( - await ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, query, token).ConfigureAwait(false), - exclusionList, - token); - } - catch (COMException) - { - // Occurs because the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin - if (!SearchManager.Settings.WarnWindowsSearchServiceOff) - return new List(); + var constructedQuery = constructQuery(searchString); - return ResultForWindexSearchOff(query.RawQuery); - } + return + await ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, query, token); } private static List RemoveResultsInExclusionList(List results, List exclusionList, CancellationToken token) @@ -159,7 +137,7 @@ internal static bool PathIsIndexed(string path) var indexManager = csm.GetCatalog("SystemIndex").GetCrawlScopeManager(); return indexManager.IncludedInCrawlScope(path) > 0; } - catch(COMException) + catch (COMException) { // Occurs because the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin return false; @@ -180,18 +158,18 @@ private static List ResultForWindexSearchOff(string rawQuery) { SearchManager.Settings.WarnWindowsSearchServiceOff = false; - var pluginsManagerPlugin= api.GetAllPlugins().FirstOrDefault(x => x.Metadata.ID == "9f8f9b14-2518-4907-b211-35ab6290dee7"); + 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", + "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 + api.GetTranslation("plugin_explorer_alternative_title"), + MessageBoxButton.YesNo) == MessageBoxResult.Yes && actionKeywordCount == 1) { api.ChangeQuery(string.Format("{0} install everything", pluginsManagerPlugin.Metadata.ActionKeywords[0])); @@ -221,7 +199,7 @@ private static void LogException(string message, Exception e) throw e; #else Log.Exception($"|Flow.Launcher.Plugin.Explorer.IndexSearch|{message}", e); -#endif +#endif } } -} +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexManager.cs new file mode 100644 index 00000000000..72f5e9cb153 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexManager.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex +{ + public class WindowsIndexManager : IIndexProvider + { + private Settings Settings { get; } + private QueryConstructor QueryConstructor { get; } + public WindowsIndexManager(Settings settings) + { + Settings = settings; + QueryConstructor = new QueryConstructor(Settings); + } + + + private async Task> WindowsIndexFileContentSearchAsync(Query query, string querySearchString, + CancellationToken token) + { + if (string.IsNullOrEmpty(querySearchString)) + return new List(); + + return await WindowsIndex.WindowsIndexSearchAsync( + querySearchString, + QueryConstructor.CreateQueryHelper, + QueryConstructor.QueryForFileContentSearch, + Settings.IndexSearchExcludedSubdirectoryPaths, + query, + token).ConfigureAwait(false); + } + + + + private async Task> WindowsIndexFilesAndFoldersSearchAsync(Query query, string querySearchString, + CancellationToken token) + { + return await WindowsIndex.WindowsIndexSearchAsync( + querySearchString, + QueryConstructor.CreateQueryHelper, + QueryConstructor.QueryForAllFilesAndFolders, + Settings.IndexSearchExcludedSubdirectoryPaths, + query, + token).ConfigureAwait(false); + } + + + private async Task> WindowsIndexTopLevelFolderSearchAsync(Query query, string path, + CancellationToken token) + { + var queryConstructor = new QueryConstructor(Settings); + + return await WindowsIndex.WindowsIndexSearchAsync( + path, + queryConstructor.CreateQueryHelper, + queryConstructor.QueryForTopLevelDirectorySearch, + Settings.IndexSearchExcludedSubdirectoryPaths, + query, + token).ConfigureAwait(false); + } + public ValueTask> SearchAsync(Query query, CancellationToken token) + { + return default; + } + public ValueTask> ContentSearchAsync(Query query, CancellationToken token) + { + return default; + } + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 351091dfe43..f3ce8abc59a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -1,7 +1,11 @@ +using Flow.Launcher.Plugin.Everything.Everything; using Flow.Launcher.Plugin.Explorer.Search; +using Flow.Launcher.Plugin.Explorer.Search.Everything; using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; +using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using System; using System.Collections.Generic; +using System.Linq; namespace Flow.Launcher.Plugin.Explorer { @@ -40,6 +44,43 @@ public class Settings public bool WarnWindowsSearchServiceOff { get; set; } = true; + private List _indexProviders; + public Settings() + { + _indexProviders = new List() + { + new EverythingSearchManager(this), + new WindowsIndexManager(this) + }; + } + public IndexSearchEngineOption IndexSearchEngine { get; set; } + public IIndexProvider IndexProvider => _indexProviders[(int)IndexSearchEngine]; + + public enum PathTraversalEngineOption + { + Everything, + WindowsIndex, + Direct + } + + public enum IndexSearchEngineOption + { + Everything, + WindowsIndex + } + #region Everything Settings + + + public bool LaunchHidden { get; set; } = false; + + public string EverythingInstalledPath { get; set; } + + public SortOption[] SortOptions { get; set; } = Enum.GetValues(); + + public SortOption SortOption { get; set; } = SortOption.NAME_ASCENDING; + + #endregion + internal enum ActionKeyword { SearchActionKeyword, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index 77ec5457b3b..e1793ad473b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -9,7 +9,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels { public class SettingsViewModel { - internal Settings Settings { get; set; } + public Settings Settings { get; set; } internal PluginInitContext Context { get; set; } @@ -29,7 +29,7 @@ public void Save() internal void RemoveAccessLinkFromExcludedIndexPaths(AccessLink selectedRow) => Settings.IndexSearchExcludedSubdirectoryPaths.Remove(selectedRow); - internal void OpenWindowsIndexingOptions() + internal static void OpenWindowsIndexingOptions() { var psi = new ProcessStartInfo { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml index a7fa586433d..a441c10b553 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml @@ -6,14 +6,13 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModels="clr-namespace:Flow.Launcher.Plugin.Explorer.ViewModels" xmlns:views="clr-namespace:Flow.Launcher.Plugin.Explorer.Views" + xmlns:qa="clr-namespace:Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks" d:DesignHeight="450" d:DesignWidth="800" - mc:Ignorable="d"> + mc:Ignorable="d" + d:DataContext="{d:DesignInstance viewModels:SettingsViewModel}"> - - - - + @@ -97,8 +96,14 @@ AllowDrop="True" DragEnter="lbxAccessLinks_DragEnter" Drop="lbxAccessLinks_Drop" - ItemTemplate="{StaticResource ListViewTemplateExcludedPaths}" /> + ItemTemplate="{StaticResource ListViewActionKeywords}" /> + + @@ -144,4 +149,4 @@ - + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs index 63c4a8dcaab..8f60d01780a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs @@ -25,6 +25,8 @@ public partial class ExplorerSettings public ExplorerSettings(SettingsViewModel viewModel) { + DataContext = viewModel; + InitializeComponent(); this.viewModel = viewModel; @@ -310,7 +312,7 @@ private void lbxAccessLinks_DragEnter(object sender, DragEventArgs e) private void btnOpenIndexingOptions_Click(object sender, RoutedEventArgs e) { - viewModel.OpenWindowsIndexingOptions(); + SettingsViewModel.OpenWindowsIndexingOptions(); } public void SetButtonVisibilityToHidden() From b671f562effc409ab730a7b1bb0983b520979b57 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Fri, 25 Mar 2022 16:33:01 -0500 Subject: [PATCH 02/63] Add missing File --- .../Search/Everything/EverythingAPI.cs | 2 +- .../Everything/Exceptions/CreateThreadException.cs | 11 +++++++++++ .../Everything/Exceptions/CreateWindowException.cs | 11 +++++++++++ .../Search/Everything/Exceptions/IPCErrorException.cs | 11 +++++++++++ .../Everything/Exceptions/InvalidCallException.cs | 11 +++++++++++ .../Everything/Exceptions/InvalidIndexException.cs | 11 +++++++++++ .../Everything/Exceptions/MemoryErrorException.cs | 11 +++++++++++ .../Everything/Exceptions/RegisterClassExException.cs | 11 +++++++++++ 8 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/CreateThreadException.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/CreateWindowException.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/IPCErrorException.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/InvalidCallException.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/InvalidIndexException.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/MemoryErrorException.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/RegisterClassExException.cs diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs index 089a0f8ffab..838d05dd3a5 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs @@ -1,5 +1,5 @@ using Flow.Launcher.Plugin.Everything.Everything; -using Flow.Launcher.Plugin.Everything.Everything.Exceptions; +using Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions; using System; using System.Collections.Generic; using System.Runtime.InteropServices; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/CreateThreadException.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/CreateThreadException.cs new file mode 100644 index 00000000000..32163057bc1 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/CreateThreadException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions +{ + /// + /// + /// + public class CreateThreadException : ApplicationException + { + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/CreateWindowException.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/CreateWindowException.cs new file mode 100644 index 00000000000..9704226d78b --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/CreateWindowException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions +{ + /// + /// + /// + public class CreateWindowException : ApplicationException + { + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/IPCErrorException.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/IPCErrorException.cs new file mode 100644 index 00000000000..41629d2e429 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/IPCErrorException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions +{ + /// + /// + /// + public class IPCErrorException : ApplicationException + { + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/InvalidCallException.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/InvalidCallException.cs new file mode 100644 index 00000000000..f84dc1ab8c9 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/InvalidCallException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions +{ + /// + /// + /// + public class InvalidCallException : ApplicationException + { + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/InvalidIndexException.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/InvalidIndexException.cs new file mode 100644 index 00000000000..cbf75e5a383 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/InvalidIndexException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions +{ + /// + /// + /// + public class InvalidIndexException : ApplicationException + { + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/MemoryErrorException.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/MemoryErrorException.cs new file mode 100644 index 00000000000..c632cd53070 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/MemoryErrorException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions +{ + /// + /// + /// + public class MemoryErrorException : ApplicationException + { + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/RegisterClassExException.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/RegisterClassExException.cs new file mode 100644 index 00000000000..2ebdbb6899b --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/RegisterClassExException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions +{ + /// + /// + /// + public class RegisterClassExException : ApplicationException + { + } +} \ No newline at end of file From fa0cd35e186fe253a3a79e29a4ef44169f6897aa Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Mon, 28 Mar 2022 15:28:18 -0500 Subject: [PATCH 03/63] Support Path Enumeration (Part 1) --- .../Search/Everything/EverythingAPI.cs | 18 ++++++-- .../Everything/EverythingSearchManager.cs | 13 ++++-- .../Search/IContentIndexProvider.cs | 11 +++++ .../Search/IIndexProvider.cs | 11 +++++ .../Search/IPathEnumerable.cs | 7 ++- .../Search/SearchManager.cs | 34 ++++++++------- .../Search/WindowsIndex/IIndexProvider.cs | 13 ------ .../Search/WindowsIndex/WindowsIndex.cs | 37 +++------------- ...anager.cs => WindowsIndexSearchManager.cs} | 27 ++++++------ .../Flow.Launcher.Plugin.Explorer/Settings.cs | 43 ++++++++++++++++--- 10 files changed, 129 insertions(+), 85 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/IIndexProvider.cs delete mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs rename Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/{WindowsIndexManager.cs => WindowsIndexSearchManager.cs} (64%) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs index 838d05dd3a5..501ce5b2249 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs @@ -101,15 +101,20 @@ public static bool IsFastSortOption(SortOption sortOption) /// /// The key word. /// when cancelled the current search will stop and exit (and would not reset) + /// Search Within a parent folder + /// Search Within sub folder of the parent folder /// Sort By /// The offset. /// The max count. /// - public static IEnumerable SearchAsync(string keyword, CancellationToken token, SortOption sortOption = SortOption.NAME_ASCENDING, int offset = 0, int maxCount = 100) + public static IEnumerable SearchAsync(string keyword, + CancellationToken token, + SortOption sortOption = SortOption.NAME_ASCENDING, + string parentPath = "", + bool recursive = false, + int offset = 0, + int maxCount = 100) { - if (string.IsNullOrEmpty(keyword)) - throw new ArgumentNullException(nameof(keyword)); - if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); @@ -124,6 +129,11 @@ public static IEnumerable SearchAsync(string keyword, Cancellation keyword = keyword[1..]; } + if (!string.IsNullOrEmpty(parentPath)) + { + keyword += $" {(recursive ? "" : "parent:")}\"{parentPath}\""; + } + EverythingApiDllImport.Everything_SetSearchW(keyword); EverythingApiDllImport.Everything_SetOffset(offset); EverythingApiDllImport.Everything_SetMax(maxCount); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs index e1486f9fb21..14069f678f8 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs @@ -5,7 +5,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything { - public class EverythingSearchManager : IIndexProvider + public class EverythingSearchManager : IIndexProvider, IContentIndexProvider, IPathEnumerable { private Settings Settings { get; } @@ -15,13 +15,18 @@ public EverythingSearchManager(Settings settings) } - public ValueTask> SearchAsync(Query query, CancellationToken token) + public ValueTask> SearchAsync(string search, CancellationToken token) { - return ValueTask.FromResult(EverythingApi.SearchAsync(query.Search, token, Settings.SortOption)); + return ValueTask.FromResult(EverythingApi.SearchAsync(search, token, Settings.SortOption)); } - public ValueTask> ContentSearchAsync(Query query, CancellationToken token) + public ValueTask> ContentSearchAsync(string search, CancellationToken token) { return new ValueTask>(new List()); } + public ValueTask> EnumerateAsync(string path, string search, bool recursive, CancellationToken token) + { + return new ValueTask>( + EverythingApi.SearchAsync("", token, Settings.SortOption, path, recursive)); + } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs new file mode 100644 index 00000000000..b2142357ebf --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Explorer.Search +{ + public interface IContentIndexProvider + { + public ValueTask> ContentSearchAsync(string search, CancellationToken token); + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IIndexProvider.cs new file mode 100644 index 00000000000..be40b6a24b9 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IIndexProvider.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Explorer.Search +{ + public interface IIndexProvider + { + public ValueTask> SearchAsync(string search, CancellationToken token); + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs index d7f0a8ec8f4..eb82422f477 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs @@ -1,9 +1,12 @@ -using System.Collections.Generic; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace Flow.Launcher.Plugin.Explorer.Search { public interface IPathEnumerable { - public IEnumerable Enumerate(string path, bool recursive); + public ValueTask> EnumerateAsync(string path, string search, bool recursive, CancellationToken token); } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 080621f420a..93707dcf26c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -61,11 +61,11 @@ internal async Task> SearchAsync(Query query, CancellationToken tok if (IsFileContentSearch(query.ActionKeyword)) { - searchResults = await Settings.IndexProvider.ContentSearchAsync(query, token); + searchResults = await Settings.ContentIndexProvider.ContentSearchAsync(query.Search, token); } else { - searchResults = await Settings.IndexProvider.SearchAsync(query, token); + searchResults = await Settings.IndexProvider.SearchAsync(query.Search, token); } if (ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword) || @@ -120,12 +120,12 @@ public async Task> PathSearchAsync(Query query, CancellationToken t var isEnvironmentVariablePath = querySearch[1..].Contains("%\\"); var locationPath = querySearch; - + if (isEnvironmentVariablePath) locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath); // Check that actual location exists, otherwise directory search will throw directory not found exception - if (!FilesFolders.LocationExists(FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath))) + if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).LocationExists()) return results.ToList(); var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath); @@ -134,16 +134,24 @@ public async Task> PathSearchAsync(Query query, CancellationToken t token.ThrowIfCancellationRequested(); - // var directoryResult = await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync, - // DirectoryInfoClassSearch, - // useIndexSearch, - // query, - // locationPath, - // token).ConfigureAwait(false); + IEnumerable directoryResult; + + if (query.Search.Contains('>')) + { + directoryResult = + await Settings.PathEnumerator.EnumerateAsync(locationPath, "", false, token) + .ConfigureAwait(false); + } + else + { + directoryResult = DirectoryInfoSearch.TopLevelDirectorySearch(query, query.Search, token); + } + + token.ThrowIfCancellationRequested(); - // results.UnionWith(directoryResult); + results.UnionWith(directoryResult.Select(searchResult => ResultManager.CreateResult(query, searchResult))); return results.ToList(); } @@ -153,10 +161,6 @@ public bool IsFileContentSearch(string actionKeyword) return actionKeyword == Settings.FileContentSearchActionKeyword; } - private List DirectoryInfoClassSearch(Query query, string querySearch, CancellationToken token) - { - return DirectoryInfoSearch.TopLevelDirectorySearch(query, querySearch, token); - } public async Task> TopLevelDirectorySearchBehaviourAsync( Func>> windowsIndexSearch, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs deleted file mode 100644 index e42d78f9a18..00000000000 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex -{ - public interface IIndexProvider - { - public ValueTask> SearchAsync(Query query, CancellationToken token); - public ValueTask> ContentSearchAsync(Query query, CancellationToken token); - } -} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs index 28e9c0e71f3..7eb9f06f3bd 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs @@ -19,7 +19,7 @@ internal static class WindowsIndex // Reserved keywords in oleDB private const string ReservedStringPattern = @"^[`\@\#\^,\&\/\\\$\%_;\[\]]+$"; - private static async Task> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token) + private static async Task> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, CancellationToken token) { var results = new List(); @@ -82,7 +82,6 @@ internal static async ValueTask> WindowsIndexSearchAsync( Func createQueryHelper, Func constructQuery, List exclusionList, - Query query, CancellationToken token) { var regexMatch = Regex.Match(searchString, ReservedStringPattern); @@ -93,40 +92,18 @@ internal static async ValueTask> WindowsIndexSearchAsync( var constructedQuery = constructQuery(searchString); return - await ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, query, token); + await ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, token); } - private static List RemoveResultsInExclusionList(List results, List exclusionList, CancellationToken token) + private static void RemoveResultsInExclusionList(List results, IReadOnlyList exclusionList, CancellationToken token) { var indexExclusionListCount = exclusionList.Count; if (indexExclusionListCount == 0) - return results; - - var filteredResults = new List(); - - for (var index = 0; index < results.Count; index++) - { - token.ThrowIfCancellationRequested(); - - var excludeResult = false; - - for (var i = 0; i < indexExclusionListCount; i++) - { - token.ThrowIfCancellationRequested(); - - if (results[index].SubTitle.StartsWith(exclusionList[i].Path, StringComparison.OrdinalIgnoreCase)) - { - excludeResult = true; - break; - } - } - - if (!excludeResult) - filteredResults.Add(results[index]); - } - - return filteredResults; + return; + results.RemoveAll(searchResult => + exclusionList.Any(exclude => searchResult.FullPath.StartsWith(exclude.Path, StringComparison.OrdinalIgnoreCase)) + ); } internal static bool PathIsIndexed(string path) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs similarity index 64% rename from Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexManager.cs rename to Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs index 72f5e9cb153..1e64c54f651 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs @@ -4,18 +4,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { - public class WindowsIndexManager : IIndexProvider + public class WindowsIndexSearchManager : IIndexProvider, IContentIndexProvider, IPathEnumerable { private Settings Settings { get; } private QueryConstructor QueryConstructor { get; } - public WindowsIndexManager(Settings settings) + public WindowsIndexSearchManager(Settings settings) { Settings = settings; QueryConstructor = new QueryConstructor(Settings); } - private async Task> WindowsIndexFileContentSearchAsync(Query query, string querySearchString, + private async Task> WindowsIndexFileContentSearchAsync(string querySearchString, CancellationToken token) { if (string.IsNullOrEmpty(querySearchString)) @@ -26,13 +26,12 @@ private async Task> WindowsIndexFileContentSearchAsync(Query QueryConstructor.CreateQueryHelper, QueryConstructor.QueryForFileContentSearch, Settings.IndexSearchExcludedSubdirectoryPaths, - query, token).ConfigureAwait(false); } - private async Task> WindowsIndexFilesAndFoldersSearchAsync(Query query, string querySearchString, + private async Task> WindowsIndexFilesAndFoldersSearchAsync(string querySearchString, CancellationToken token) { return await WindowsIndex.WindowsIndexSearchAsync( @@ -40,12 +39,11 @@ private async Task> WindowsIndexFilesAndFoldersSearchAsync(Qu QueryConstructor.CreateQueryHelper, QueryConstructor.QueryForAllFilesAndFolders, Settings.IndexSearchExcludedSubdirectoryPaths, - query, token).ConfigureAwait(false); } - private async Task> WindowsIndexTopLevelFolderSearchAsync(Query query, string path, + private async Task> WindowsIndexTopLevelFolderSearchAsync(string path,string search, CancellationToken token) { var queryConstructor = new QueryConstructor(Settings); @@ -55,16 +53,21 @@ private async Task> WindowsIndexTopLevelFolderSearchAsync(Que queryConstructor.CreateQueryHelper, queryConstructor.QueryForTopLevelDirectorySearch, Settings.IndexSearchExcludedSubdirectoryPaths, - query, token).ConfigureAwait(false); } - public ValueTask> SearchAsync(Query query, CancellationToken token) + public async ValueTask> SearchAsync(string search, CancellationToken token) { - return default; + return await WindowsIndexFilesAndFoldersSearchAsync(search, token); } - public ValueTask> ContentSearchAsync(Query query, CancellationToken token) + public async ValueTask> ContentSearchAsync(string search, CancellationToken token) { - return default; + return await WindowsIndexFileContentSearchAsync(search, token); + } + public async ValueTask> EnumerateAsync(string path, string search, bool recursive, CancellationToken token) + { + if(recursive) + return await WindowsIndexFilesAndFoldersSearchAsync(search, token).ConfigureAwait(false); + return await WindowsIndexTopLevelFolderSearchAsync(path, search, token); } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index f3ce8abc59a..d9d2a788f44 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; namespace Flow.Launcher.Plugin.Explorer { @@ -44,18 +45,44 @@ public class Settings public bool WarnWindowsSearchServiceOff { get; set; } = true; - private List _indexProviders; + private IReadOnlyList _indexProviders; + private IReadOnlyList _fileContentIndexProviders; + private IReadOnlyList _pathEnumerables; public Settings() { + var everythingManager = new EverythingSearchManager(this); + var windowsIndexManager = new WindowsIndexSearchManager(this); + _indexProviders = new List() { - new EverythingSearchManager(this), - new WindowsIndexManager(this) + everythingManager, + windowsIndexManager + }; + + _pathEnumerables = new List() + { + everythingManager, + windowsIndexManager + }; + + _fileContentIndexProviders = new List + { + windowsIndexManager, everythingManager }; } + public IndexSearchEngineOption IndexSearchEngine { get; set; } + [JsonIgnore] public IIndexProvider IndexProvider => _indexProviders[(int)IndexSearchEngine]; + + public PathTraversalEngineOption PathEnumerationEngine { get; set; } + [JsonIgnore] + public IPathEnumerable PathEnumerator => _pathEnumerables[(int)PathEnumerationEngine]; + public ContentIndexSearchEngineOption ContentSearchEngine { get; set; } + [JsonIgnore] + public IContentIndexProvider ContentIndexProvider => _fileContentIndexProviders[(int)ContentSearchEngine]; + public enum PathTraversalEngineOption { Everything, @@ -68,11 +95,17 @@ public enum IndexSearchEngineOption Everything, WindowsIndex } - #region Everything Settings - + + public enum ContentIndexSearchEngineOption + { + Everything, + WindowsIndex + } public bool LaunchHidden { get; set; } = false; + #region Everything Settings + public string EverythingInstalledPath { get; set; } public SortOption[] SortOptions { get; set; } = Enum.GetValues(); From 9b471de3cf818f4026a20b4e5e7551e24e96f0da Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sun, 15 May 2022 15:15:56 -0500 Subject: [PATCH 04/63] Implement Path Enumeration and ContentSearch --- Flow.Launcher/MainWindow.xaml.cs | 2 +- .../DirectoryInfo/DirectoryInfoSearch.cs | 33 ++++++------ .../Search/Everything/EverythingAPI.cs | 38 ++++++------- .../Everything/EverythingSearchManager.cs | 29 ++++++++-- .../Everything/EverythingSearchOption.cs | 35 ++++++++++++ .../Search/IContentIndexProvider.cs | 2 +- .../Search/SearchManager.cs | 54 +++++++++++++++---- .../WindowsIndex/WindowsIndexSearchManager.cs | 4 +- .../Flow.Launcher.Plugin.Explorer/Settings.cs | 5 +- 9 files changed, 147 insertions(+), 55 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 366407182e0..37f2052888b 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -520,7 +520,7 @@ private void OnKeyDown(object sender, KeyEventArgs e) && QueryTextBox.CaretIndex == QueryTextBox.Text.Length) { var queryWithoutActionKeyword = - QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins).Search; + QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins)?.Search; if (FilesFolders.IsLocationPathString(queryWithoutActionKeyword)) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 14c90d57f42..27498002c20 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -10,7 +10,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { public static class DirectoryInfoSearch { - internal static List TopLevelDirectorySearch(Query query, string search, CancellationToken token) + internal static IEnumerable TopLevelDirectorySearch(Query query, string search, CancellationToken token) { var criteria = ConstructSearchCriteria(search); @@ -44,31 +44,36 @@ public static string ConstructSearchCriteria(string search) return incompleteName; } - private static List DirectorySearch(EnumerationOptions enumerationOption, Query query, string search, + private static IEnumerable DirectorySearch(EnumerationOptions enumerationOption, Query query, string search, string searchCriteria, CancellationToken token) { - var results = new List(); + var results = new List(); var path = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(search); - var folderList = new List(); - var fileList = new List(); - try { var directoryInfo = new System.IO.DirectoryInfo(path); - foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, enumerationOption) - ) + foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, enumerationOption)) { if (fileSystemInfo is System.IO.DirectoryInfo) { - folderList.Add(ResultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, - fileSystemInfo.FullName, query, 0, true, false)); + results.Add(new SearchResult() + { + FullPath = fileSystemInfo.FullName, + Type = ResultType.Folder, + WindowsIndexed = false + }); } else { - fileList.Add(ResultManager.CreateFileResult(fileSystemInfo.FullName, query, 0, true, false)); + results.Add(new SearchResult() + { + FullPath = fileSystemInfo.FullName, + Type = ResultType.File, + WindowsIndexed = false + }); } token.ThrowIfCancellationRequested(); @@ -77,13 +82,11 @@ private static List DirectorySearch(EnumerationOptions enumerationOption catch (Exception e) { Log.Exception("Flow.Plugin.Explorer.", nameof(DirectoryInfoSearch), e); - results.Add(new Result {Title = e.Message, Score = 501}); - - return results; + throw; } // Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. - return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList(); + return results.OrderBy(r=>r.Type).ThenBy(r=>r.FullPath); } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs index 501ce5b2249..92538d554ed 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs @@ -107,38 +107,38 @@ public static bool IsFastSortOption(SortOption sortOption) /// The offset. /// The max count. /// - public static IEnumerable SearchAsync(string keyword, - CancellationToken token, - SortOption sortOption = SortOption.NAME_ASCENDING, - string parentPath = "", - bool recursive = false, - int offset = 0, - int maxCount = 100) + public static IEnumerable SearchAsync(EverythingSearchOption option, + CancellationToken token) { - if (offset < 0) - throw new ArgumentOutOfRangeException(nameof(offset)); + if (option.Offset < 0) + throw new ArgumentOutOfRangeException(nameof(option.Offset), option.Offset, "Offset must be greater than or equal to 0"); - if (maxCount < 0) - throw new ArgumentOutOfRangeException(nameof(maxCount)); + if (option.MaxCount < 0) + throw new ArgumentOutOfRangeException(nameof(option.MaxCount), option.MaxCount, "MaxCount must be greater than or equal to 0"); lock (syncObject) { - if (keyword.StartsWith("@")) + if (option.Keyword.StartsWith("@")) { EverythingApiDllImport.Everything_SetRegex(true); - keyword = keyword[1..]; + option.Keyword = option.Keyword[1..]; } - if (!string.IsNullOrEmpty(parentPath)) + if (!string.IsNullOrEmpty(option.ParentPath)) { - keyword += $" {(recursive ? "" : "parent:")}\"{parentPath}\""; + option.Keyword += $" {(option.IsRecursive ? "" : "parent:")}\"{option.ParentPath}\""; } - EverythingApiDllImport.Everything_SetSearchW(keyword); - EverythingApiDllImport.Everything_SetOffset(offset); - EverythingApiDllImport.Everything_SetMax(maxCount); + if (option.IsContentSearch) + { + option.Keyword += $" content:\"{option.ContentSearchKeyword}\""; + } + + EverythingApiDllImport.Everything_SetSearchW(option.Keyword); + EverythingApiDllImport.Everything_SetOffset(option.Offset); + EverythingApiDllImport.Everything_SetMax(option.MaxCount); - EverythingApiDllImport.Everything_SetSort(sortOption); + EverythingApiDllImport.Everything_SetSort(option.SortOption); if (token.IsCancellationRequested) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs index 14069f678f8..3bd4a3c7c4b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs @@ -8,7 +8,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything public class EverythingSearchManager : IIndexProvider, IContentIndexProvider, IPathEnumerable { private Settings Settings { get; } - + public EverythingSearchManager(Settings settings) { Settings = settings; @@ -17,16 +17,35 @@ public EverythingSearchManager(Settings settings) public ValueTask> SearchAsync(string search, CancellationToken token) { - return ValueTask.FromResult(EverythingApi.SearchAsync(search, token, Settings.SortOption)); + return ValueTask.FromResult(EverythingApi.SearchAsync( + new EverythingSearchOption(search, Settings.SortOption), + token)); } - public ValueTask> ContentSearchAsync(string search, CancellationToken token) + public ValueTask> ContentSearchAsync(string plainSearch, + string contentSearch, CancellationToken token) { - return new ValueTask>(new List()); + if (!Settings.EnableEverythingContentSearch) + { + return new ValueTask>(new List()); + } + + return new ValueTask>(EverythingApi.SearchAsync( + new EverythingSearchOption( + plainSearch, + Settings.SortOption, + true, + contentSearch), + token)); } public ValueTask> EnumerateAsync(string path, string search, bool recursive, CancellationToken token) { return new ValueTask>( - EverythingApi.SearchAsync("", token, Settings.SortOption, path, recursive)); + EverythingApi.SearchAsync( + new EverythingSearchOption(search, + Settings.SortOption, + parentPath: path, + isRecursive: recursive), + token)); } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs new file mode 100644 index 00000000000..ef41cf1f0fd --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs @@ -0,0 +1,35 @@ +using Flow.Launcher.Plugin.Everything.Everything; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything +{ + public struct EverythingSearchOption + { + public EverythingSearchOption(string keyword, + SortOption sortOption, + bool isContentSearch = false, + string contentSearchKeyword = "", + string parentPath = "", + bool isRecursive = true, + int offset = 0, + int maxCount = 100) + { + Keyword = keyword; + SortOption = sortOption; + ContentSearchKeyword = contentSearchKeyword; + IsContentSearch = isContentSearch; + ParentPath = parentPath; + IsRecursive = isRecursive; + Offset = offset; + MaxCount = maxCount; + } + public string Keyword { get; set; } + public SortOption SortOption { get; set; } + public string ParentPath { get; set; } + public bool IsRecursive { get; set; } + + public bool IsContentSearch { get; set; } + public string ContentSearchKeyword { get; set; } + public int Offset { get;set; } + public int MaxCount { get; set; } + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs index b2142357ebf..bd7e14a669e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs @@ -6,6 +6,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search { public interface IContentIndexProvider { - public ValueTask> ContentSearchAsync(string search, CancellationToken token); + public ValueTask> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token); } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 93707dcf26c..99a5c35ba87 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,4 +1,5 @@ using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; +using Flow.Launcher.Plugin.Explorer.Search.Everything; using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.SharedCommands; @@ -59,9 +60,13 @@ internal async Task> SearchAsync(Query query, CancellationToken tok IEnumerable searchResults; - if (IsFileContentSearch(query.ActionKeyword)) + if (ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword)) { - searchResults = await Settings.ContentIndexProvider.ContentSearchAsync(query.Search, token); + if (Settings.ContentIndexProvider is EverythingSearchManager && !Settings.EnableEverythingContentSearch) + { + return EverythingContentSearchResult(query); + } + searchResults = await Settings.ContentIndexProvider.ContentSearchAsync("", query.Search, token); } else { @@ -74,9 +79,10 @@ internal async Task> SearchAsync(Query query, CancellationToken tok results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false)); } - if ((ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword) || - ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)) && - querySearch.Length > 0 && + if (((ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword) || + ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)) && + querySearch.Length > 0) || + ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword) && !querySearch.IsLocationPathString()) { if (searchResults != null) @@ -101,7 +107,27 @@ private bool ActionKeywordMatch(Query query, Settings.ActionKeyword allowedActio Settings.ActionKeyword.IndexSearchActionKeyword => Settings.IndexSearchKeywordEnabled && keyword == Settings.IndexSearchActionKeyword, Settings.ActionKeyword.QuickAccessActionKeyword => Settings.QuickAccessKeywordEnabled && - keyword == Settings.QuickAccessActionKeyword + keyword == Settings.QuickAccessActionKeyword, + _ => throw new ArgumentOutOfRangeException(nameof(allowedActionKeyword), allowedActionKeyword, "actionKeyword out of range") + }; + } + + private static List EverythingContentSearchResult(Query query) + { + return new List() + { + new() + { + Title = "Do you want to enable content search for Everything?", + SubTitle = "It can be super slow without index (which is only supported in Everything 1.5+)", + IcoPath = "Images/search.png", + Action = c => + { + Settings.EnableEverythingContentSearch = true; + Context.API.ChangeQuery(query.RawQuery, true); + return false; + } + } }; } @@ -120,7 +146,7 @@ public async Task> PathSearchAsync(Query query, CancellationToken t var isEnvironmentVariablePath = querySearch[1..].Contains("%\\"); var locationPath = querySearch; - + if (isEnvironmentVariablePath) locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath); @@ -136,18 +162,24 @@ public async Task> PathSearchAsync(Query query, CancellationToken t IEnumerable directoryResult; - if (query.Search.Contains('>')) + var recursiveIndicatorIndex = query.Search.IndexOf('>'); + + if (recursiveIndicatorIndex > 0 && Settings.PathEnumerationEngine != Settings.PathTraversalEngineOption.Direct) { directoryResult = - await Settings.PathEnumerator.EnumerateAsync(locationPath, "", false, token) + await Settings.PathEnumerator.EnumerateAsync(query.Search[..recursiveIndicatorIndex], + query.Search[(recursiveIndicatorIndex + 1)..], + true, + token) .ConfigureAwait(false); + } else { directoryResult = DirectoryInfoSearch.TopLevelDirectorySearch(query, query.Search, token); } - - + + token.ThrowIfCancellationRequested(); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs index 1e64c54f651..05e75c64656 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs @@ -59,9 +59,9 @@ public async ValueTask> SearchAsync(string search, Can { return await WindowsIndexFilesAndFoldersSearchAsync(search, token); } - public async ValueTask> ContentSearchAsync(string search, CancellationToken token) + public async ValueTask> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token) { - return await WindowsIndexFileContentSearchAsync(search, token); + return await WindowsIndexFileContentSearchAsync(contentSearch, token); } public async ValueTask> EnumerateAsync(string path, string search, bool recursive, CancellationToken token) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index d9d2a788f44..3c91eeabde1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -67,7 +67,8 @@ public Settings() _fileContentIndexProviders = new List { - windowsIndexManager, everythingManager + windowsIndexManager, + everythingManager, }; } @@ -111,6 +112,8 @@ public enum ContentIndexSearchEngineOption public SortOption[] SortOptions { get; set; } = Enum.GetValues(); public SortOption SortOption { get; set; } = SortOption.NAME_ASCENDING; + + public bool EnableEverythingContentSearch { get; set; } = false; #endregion From d5e1b7cb2232ae93c28ffc1e0bd0764912b6aa4c Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Thu, 30 Jun 2022 23:56:15 -0500 Subject: [PATCH 05/63] Rewrite Explorer View Logic based on MVVM Pattern to avoid complicated View Logic --- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 7 + Flow.Launcher/PublicAPIInstance.cs | 2 + .../Flow.Launcher.Plugin.Explorer.csproj | 1 - .../Languages/en.xaml | 1 + Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 2 +- .../Search/QuickAccessLinks/QuickAccess.cs | 4 +- .../Search/WindowsIndex/WindowsIndex.cs | 2 +- .../Flow.Launcher.Plugin.Explorer/Settings.cs | 7 +- .../ViewModels/ActionKeywordModel.cs | 57 +++++ .../ViewModels/RelayCommand.cs | 27 +++ .../ViewModels/SettingsViewModel.cs | 185 +++++++++++++- .../Views/ActionKeywordSetting.xaml | 2 +- .../Views/ActionKeywordSetting.xaml.cs | 55 ++--- .../Views/ExplorerSettings.xaml | 169 +++++++------ .../Views/ExplorerSettings.xaml.cs | 225 +----------------- 15 files changed, 401 insertions(+), 345 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/ActionKeywordModel.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/RelayCommand.cs diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 69057820ef8..8ec0c6eae00 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -181,6 +181,13 @@ public interface IPublicAPI /// The actionkeyword that is supposed to be removed void RemoveActionKeyword(string pluginId, string oldActionKeyword); + /// + /// Check whether specific ActionKeyword is assigned to any of the plugin + /// + /// The actionkeyword for checking + /// True if the actionkeyword is already assigned, False otherwise + bool ActionKeywordAssigned(string actionKeyword); + /// /// Log debug message /// Message will only be logged in Debug mode diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 81f7a238965..7cb704cccaa 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -142,6 +142,8 @@ public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, public void AddActionKeyword(string pluginId, string newActionKeyword) => PluginManager.AddActionKeyword(pluginId, newActionKeyword); + public bool ActionKeywordAssigned(string actionKeyword) => PluginManager.ActionKeywordRegistered(actionKeyword); + public void RemoveActionKeyword(string pluginId, string oldActionKeyword) => PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj index ae48ac3ee3d..4d2a4e50af0 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -50,5 +50,4 @@ - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index e23bd77bd4c..78c822614dc 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -20,6 +20,7 @@ Delete Edit Add + General Setting Customise Action Keywords Quick Access Links Index Search Excluded Paths diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index d8879a4f675..5c651266a16 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -44,7 +44,7 @@ public Task InitAsync(PluginInitContext context) if (Settings.QuickFolderAccessLinks.Any()) { Settings.QuickAccessLinks = Settings.QuickFolderAccessLinks; - Settings.QuickFolderAccessLinks = new List(); + Settings.QuickFolderAccessLinks = new(); } contextMenu = new ContextMenu(Context, Settings, viewModel); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs index 55975c2a59d..b39e410d6aa 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs @@ -8,7 +8,7 @@ internal static class QuickAccess { private const int quickAccessResultScore = 100; - internal static List AccessLinkListMatched(Query query, List accessLinks) + internal static List AccessLinkListMatched(Query query, IEnumerable accessLinks) { if (string.IsNullOrEmpty(query.Search)) return new List(); @@ -29,7 +29,7 @@ internal static List AccessLinkListMatched(Query query, List }).ToList(); } - internal static List AccessLinkListAll(Query query, List accessLinks) + internal static List AccessLinkListAll(Query query, IEnumerable accessLinks) => accessLinks .OrderBy(x => x.Type) .ThenBy(x => x.Name) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs index 7eb9f06f3bd..64717b41ca1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs @@ -81,7 +81,7 @@ internal static async ValueTask> WindowsIndexSearchAsync( string searchString, Func createQueryHelper, Func constructQuery, - List exclusionList, + IEnumerable exclusionList, CancellationToken token) { var regexMatch = Regex.Match(searchString, ReservedStringPattern); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 3c91eeabde1..af334fdd03a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -5,6 +5,7 @@ using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Text.Json.Serialization; @@ -14,14 +15,14 @@ public class Settings { public int MaxResult { get; set; } = 100; - public List QuickAccessLinks { get; set; } = new List(); + public ObservableCollection QuickAccessLinks { get; set; } = new (); // as at v1.7.0 this is to maintain backwards compatibility, need to be removed afterwards. - public List QuickFolderAccessLinks { get; set; } = new List(); + public ObservableCollection QuickFolderAccessLinks { get; set; } = new (); public bool UseWindowsIndexForDirectorySearch { get; set; } = true; - public List IndexSearchExcludedSubdirectoryPaths { get; set; } = new List(); + public ObservableCollection IndexSearchExcludedSubdirectoryPaths { get; set; } = new ObservableCollection(); public string SearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/ActionKeywordModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/ActionKeywordModel.cs new file mode 100644 index 00000000000..2f614ead80d --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/ActionKeywordModel.cs @@ -0,0 +1,57 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Flow.Launcher.Plugin.Explorer.Views +{ + public class ActionKeywordModel : INotifyPropertyChanged + { + private static Settings _settings; + + public event PropertyChangedEventHandler PropertyChanged; + + public static void Init(Settings settings) + { + _settings = settings; + } + + internal ActionKeywordModel(Settings.ActionKeyword actionKeyword, string description) + { + KeywordProperty = actionKeyword; + Description = description; + } + + public string Description { get; private init; } + + internal Settings.ActionKeyword KeywordProperty { get; } + + private void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + private string? keyword; + + public string Keyword + { + get => keyword ??= _settings.GetActionKeyword(KeywordProperty); + set + { + keyword = value; + _settings.SetActionKeyword(KeywordProperty, value); + OnPropertyChanged(); + } + } + private bool? enabled; + + public bool Enabled + { + get => enabled ??= _settings.GetActionKeywordEnabled(KeywordProperty); + set + { + enabled = value; + _settings.SetActionKeywordEnabled(KeywordProperty, value); + OnPropertyChanged(); + } + } + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/RelayCommand.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/RelayCommand.cs new file mode 100644 index 00000000000..ff704b67979 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/RelayCommand.cs @@ -0,0 +1,27 @@ +using System; +using System.Windows.Input; + +namespace Flow.Launcher.Plugin.Explorer.ViewModels +{ + internal class RelayCommand : ICommand + { + private Action _action; + + public RelayCommand(Action action) + { + _action = action; + } + + public virtual bool CanExecute(object parameter) + { + return true; + } + + public event EventHandler CanExecuteChanged; + + public virtual void Execute(object parameter) + { + _action?.Invoke(parameter); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index e1793ad473b..182acc80acb 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -2,8 +2,13 @@ using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; +using Flow.Launcher.Plugin.Explorer.Views; +using System.Collections.ObjectModel; using System.Diagnostics; +using System.IO; using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Input; namespace Flow.Launcher.Plugin.Explorer.ViewModels { @@ -25,6 +30,175 @@ public void Save() Context.API.SaveSettingJsonStorage(); } + + public AccessLink SelectedQuickAccessLink { get; set; } + public AccessLink SelectedIndexSearchExcludedPath { get; set; } + public ActionKeywordModel SelectedActionKeyword { get; set; } + + + + public ICommand RemoveLinkCommand => new RelayCommand(RemoveLink); + public ICommand EditLinkCommand => new RelayCommand(EditLink); + public ICommand AddLinkCommand => new RelayCommand(AddLink); + + public ICommand EditActionKeywordCommand => new RelayCommand(EditActionKeyword); + + private void EditActionKeyword(object obj) + { + if (SelectedActionKeyword is not ActionKeywordModel actionKeyword) + { + ShowUnselectedMessage(); + return; + } + + var actionKeywordWindow = new ActionKeywordSetting(actionKeyword, Context.API); + + if (actionKeywordWindow.ShowDialog() ?? false) + { + if (actionKeyword.Enabled && !actionKeywordWindow.KeywordEnabled) + { + Context.API.RemoveActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword); + } + else if (!actionKeyword.Enabled && actionKeywordWindow.KeywordEnabled) + { + Context.API.AddActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword); + } + else if (actionKeyword.Enabled && actionKeywordWindow.KeywordEnabled) + { + // same keyword will have dialog result false + Context.API.RemoveActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword); + Context.API.AddActionKeyword(Context.CurrentPluginMetadata.ID, actionKeywordWindow.ActionKeyword); + } + + (actionKeyword.Keyword, actionKeyword.Enabled) = (actionKeywordWindow.ActionKeyword, actionKeywordWindow.KeywordEnabled); + } + + } + + private AccessLink? PromptUserSelectPath(ResultType type, string initialDirectory = null) + { + AccessLink newAccessLink = null; + + if (type is ResultType.Folder) + { + var folderBrowserDialog = new FolderBrowserDialog(); + + if (initialDirectory is not null) + folderBrowserDialog.InitialDirectory = initialDirectory; + + if (folderBrowserDialog.ShowDialog() != DialogResult.OK) + return newAccessLink; + + newAccessLink = new AccessLink { Path = folderBrowserDialog.SelectedPath }; + } + else if (type is ResultType.File) + { + var openFileDialog = new OpenFileDialog(); + if (initialDirectory is not null) + openFileDialog.InitialDirectory = initialDirectory; + + if (openFileDialog.ShowDialog() != DialogResult.OK) + return newAccessLink; + + newAccessLink = new AccessLink { Path = openFileDialog.FileName }; + } + return newAccessLink; + } + + private void EditLink(object obj) + { + if (obj is not string container) return; + + AccessLink selectedLink; + ObservableCollection collection; + + switch (container) + { + case "QuickAccessLink": + if (SelectedQuickAccessLink == null) + { + ShowUnselectedMessage(); + return; + } + selectedLink = SelectedQuickAccessLink; + collection = Settings.QuickAccessLinks; + break; + case "IndexSearchExcludedPaths": + if (SelectedIndexSearchExcludedPath == null) + { + ShowUnselectedMessage(); + return; + } + selectedLink = SelectedIndexSearchExcludedPath; + collection = Settings.IndexSearchExcludedSubdirectoryPaths; + break; + default: + return; + } + + var link = PromptUserSelectPath(selectedLink.Type, + selectedLink.Type == ResultType.Folder + ? selectedLink.Path + : Path.GetDirectoryName(selectedLink.Path)); + + if (link is null) + return; + + collection.Remove(selectedLink); + collection.Add(link); + } + + private void ShowUnselectedMessage() + { + string warning = Context.API.GetTranslation("plugin_explorer_make_selection_warning"); + MessageBox.Show(warning); + } + + private void AddLink(object obj) + { + if (obj is not string container) return; + + var folderBrowserDialog = new FolderBrowserDialog(); + + if (folderBrowserDialog.ShowDialog() != DialogResult.OK) + return; + + var newAccessLink = new AccessLink { Path = folderBrowserDialog.SelectedPath }; + + + switch (container) + { + case "QuickAccessLink": + if (SelectedQuickAccessLink == null) return; + Settings.QuickAccessLinks.Add(newAccessLink); + break; + case "IndexSearchExcludedPaths": + if (SelectedIndexSearchExcludedPath == null) return; + Settings.IndexSearchExcludedSubdirectoryPaths.Add(newAccessLink); + break; + } + } + + private void RemoveLink(object obj) + { + if (obj is not string container) return; + + switch (container) + { + case "QuickAccessLink": + if (SelectedQuickAccessLink == null) return; + Settings.QuickAccessLinks.Remove(SelectedQuickAccessLink); + break; + case "IndexSearchExcludedPaths": + if (SelectedIndexSearchExcludedPath == null) return; + Settings.IndexSearchExcludedSubdirectoryPaths.Remove(SelectedIndexSearchExcludedPath); + break; + } + Save(); + } + + + internal void RemoveLinkFromQuickAccess(AccessLink selectedRow) => Settings.QuickAccessLinks.Remove(selectedRow); internal void RemoveAccessLinkFromExcludedIndexPaths(AccessLink selectedRow) => Settings.IndexSearchExcludedSubdirectoryPaths.Remove(selectedRow); @@ -41,16 +215,11 @@ internal static void OpenWindowsIndexingOptions() Process.Start(psi); } - internal void UpdateActionKeyword(Settings.ActionKeyword modifiedActionKeyword, string newActionKeyword, string oldActionKeyword) - { + internal void UpdateActionKeyword(Settings.ActionKeyword modifiedActionKeyword, string newActionKeyword, string oldActionKeyword) => PluginManager.ReplaceActionKeyword(Context.CurrentPluginMetadata.ID, oldActionKeyword, newActionKeyword); - } - internal bool IsActionKeywordAlreadyAssigned(string newActionKeyword) - { - return PluginManager.ActionKeywordRegistered(newActionKeyword); - } + internal bool IsActionKeywordAlreadyAssigned(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword); internal bool IsNewActionKeywordGlobal(string newActionKeyword) => newActionKeyword == Query.GlobalPluginWildcardSign; } -} +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml index a469184832f..e36d9b92ed4 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml @@ -96,7 +96,7 @@ Name="ChkActionKeywordEnabled" Width="auto" VerticalAlignment="Center" - IsChecked="{Binding Enabled}" + IsChecked="{Binding KeywordEnabled}" ToolTip="{DynamicResource plugin_explorer_actionkeyword_enabled_tooltip}" /> diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs index 27e4a0b9a8d..918adb527e1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs @@ -13,9 +13,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views /// public partial class ActionKeywordSetting : Window { - private SettingsViewModel settingsViewModel; - - public ActionKeywordView CurrentActionKeyword { get; set; } + public ActionKeywordModel CurrentActionKeyword { get; set; } public string ActionKeyword { @@ -23,24 +21,22 @@ public string ActionKeyword set { // Set Enable to be true if user change ActionKeyword - Enabled = true; + KeywordEnabled = true; actionKeyword = value; } } - public bool Enabled { get; set; } + public bool KeywordEnabled { get; set; } private string actionKeyword; + private readonly IPublicAPI api; - public ActionKeywordSetting(SettingsViewModel settingsViewModel, - ActionKeywordView selectedActionKeyword) + public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword, IPublicAPI api) { - this.settingsViewModel = settingsViewModel; - CurrentActionKeyword = selectedActionKeyword; - + this.api = api; ActionKeyword = selectedActionKeyword.Keyword; - Enabled = selectedActionKeyword.Enabled; + KeywordEnabled = selectedActionKeyword.Enabled; InitializeComponent(); @@ -52,56 +48,43 @@ private void OnDoneButtonClick(object sender, RoutedEventArgs e) if (string.IsNullOrEmpty(ActionKeyword)) ActionKeyword = Query.GlobalPluginWildcardSign; - if (CurrentActionKeyword.Keyword == ActionKeyword && CurrentActionKeyword.Enabled == Enabled) + if (CurrentActionKeyword.Keyword == ActionKeyword && CurrentActionKeyword.Enabled == KeywordEnabled) { + DialogResult = false; Close(); return; } + if (ActionKeyword == "") + { + ActionKeyword = "*"; + } if (ActionKeyword == Query.GlobalPluginWildcardSign) switch (CurrentActionKeyword.KeywordProperty) { case Settings.ActionKeyword.FileContentSearchActionKeyword: - MessageBox.Show(settingsViewModel.Context.API.GetTranslation("plugin_explorer_globalActionKeywordInvalid")); + MessageBox.Show(api.GetTranslation("plugin_explorer_globalActionKeywordInvalid")); return; case Settings.ActionKeyword.QuickAccessActionKeyword: - MessageBox.Show(settingsViewModel.Context.API.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid")); + MessageBox.Show(api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid")); return; } - var oldActionKeyword = CurrentActionKeyword.Keyword; - - if (!Enabled || !settingsViewModel.IsActionKeywordAlreadyAssigned(ActionKeyword)) + if (!KeywordEnabled || !api.ActionKeywordAssigned(ActionKeyword)) { - // Update View Data - CurrentActionKeyword.Keyword = Enabled == true ? ActionKeyword : Query.GlobalPluginWildcardSign; - CurrentActionKeyword.Enabled = Enabled; - - switch (Enabled) - { - // reset to global so it does not take up an action keyword when disabled - // not for null Enable plugin - case false when oldActionKeyword != Query.GlobalPluginWildcardSign: - settingsViewModel.UpdateActionKeyword(CurrentActionKeyword.KeywordProperty, - Query.GlobalPluginWildcardSign, oldActionKeyword); - break; - default: - settingsViewModel.UpdateActionKeyword(CurrentActionKeyword.KeywordProperty, - CurrentActionKeyword.Keyword, oldActionKeyword); - break; - } - + DialogResult = true; Close(); return; } // The keyword is not valid, so show message - MessageBox.Show(settingsViewModel.Context.API.GetTranslation("newActionKeywordsHasBeenAssigned")); + MessageBox.Show(api.GetTranslation("newActionKeywordsHasBeenAssigned")); } private void BtnCancel_OnClick(object sender, RoutedEventArgs e) { + DialogResult = false; Close(); } private void TxtCurrentActionKeyword_OnKeyDown(object sender, KeyEventArgs e) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml index a441c10b553..1e14ba09daf 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml @@ -4,13 +4,13 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:qa="clr-namespace:Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks" xmlns:viewModels="clr-namespace:Flow.Launcher.Plugin.Explorer.ViewModels" xmlns:views="clr-namespace:Flow.Launcher.Plugin.Explorer.Views" - xmlns:qa="clr-namespace:Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks" + d:DataContext="{d:DesignInstance viewModels:SettingsViewModel}" d:DesignHeight="450" d:DesignWidth="800" - mc:Ignorable="d" - d:DataContext="{d:DesignInstance viewModels:SettingsViewModel}"> + mc:Ignorable="d"> @@ -37,7 +37,7 @@ + Text="{Binding Keyword}"> - @@ -61,10 +69,10 @@ - + - @@ -74,56 +82,47 @@ - - - - - -