From 000c45bfa8992be99997a7c3d25d11f04ee7b853 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sat, 10 Sep 2022 10:45:41 -0500 Subject: [PATCH] Refactor Explorer Code to fit the new architecture 1. Rename some method in QueryConstructor.cs by removing prefix and suffix 2. Split the logic of checking '>' to outer SearchManager.cs 3. Use IAsyncEnumerable for potential future improvement. --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 14 ++-- .../Flow.Launcher.Plugin.Explorer.csproj | 4 + .../DirectoryInfo/DirectoryInfoSearch.cs | 2 +- .../Search/Everything/EverythingAPI.cs | 2 +- .../Everything/EverythingSearchManager.cs | 5 +- .../Search/IPathEnumerable.cs | 12 --- .../{ => IProvider}/IContentIndexProvider.cs | 5 +- .../Search/{ => IProvider}/IIndexProvider.cs | 5 +- .../Search/IProvider/IPathIndexProvider.cs | 10 +++ .../Search/ResultManager.cs | 2 +- .../Search/SearchManager.cs | 48 +++++++----- .../Search/SearchResult.cs | 5 +- .../Search/WindowsIndex/QueryConstructor.cs | 73 ++++++++++--------- .../Search/WindowsIndex/WindowsIndex.cs | 50 +++++++------ .../WindowsIndex/WindowsIndexSearchManager.cs | 39 +++++----- .../Flow.Launcher.Plugin.Explorer/Settings.cs | 7 +- 16 files changed, 155 insertions(+), 128 deletions(-) delete mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs rename Plugins/Flow.Launcher.Plugin.Explorer/Search/{ => IProvider}/IContentIndexProvider.cs (77%) rename Plugins/Flow.Launcher.Plugin.Explorer/Search/{ => IProvider}/IIndexProvider.cs (73%) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IPathIndexProvider.cs diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 3b25e8382..5286f85c7 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -73,7 +73,7 @@ namespace Flow.Launcher.Test.Plugins var queryConstructor = new QueryConstructor(new Settings()); //When - var queryString = queryConstructor.QueryForTopLevelDirectorySearch(folderPath); + var queryString = queryConstructor.Directory(folderPath); // Then Assert.IsTrue(queryString == expectedString, @@ -93,7 +93,7 @@ namespace Flow.Launcher.Test.Plugins var queryConstructor = new QueryConstructor(new Settings()); //When - var queryString = queryConstructor.QueryForTopLevelDirectorySearch(userSearchString); + var queryString = queryConstructor.Directory(userSearchString); // Then Assert.IsTrue(queryString == expectedString, @@ -125,7 +125,7 @@ namespace Flow.Launcher.Test.Plugins public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString) { //When - var resultString = QueryConstructor.QueryWhereRestrictionsForAllFilesAndFoldersSearch; + var resultString = QueryConstructor.RestrictionsForAllFilesAndFoldersSearch; // Then Assert.IsTrue(resultString == expectedString, @@ -142,7 +142,7 @@ namespace Flow.Launcher.Test.Plugins { // Given var queryConstructor = new QueryConstructor(new Settings()); - var baseQuery = queryConstructor.CreateBaseQuery(); + var baseQuery = queryConstructor.BaseQueryHelper; // system running this test could have different locale than the hard-coded 1033 LCID en-US. var queryKeywordLocale = baseQuery.QueryKeywordLocale; @@ -151,7 +151,7 @@ namespace Flow.Launcher.Test.Plugins //When - var resultString = queryConstructor.QueryForAllFilesAndFolders(userSearchString); + var resultString = queryConstructor.FilesAndFolders(userSearchString); // Then Assert.IsTrue(resultString == expectedString, @@ -169,7 +169,7 @@ namespace Flow.Launcher.Test.Plugins var queryConstructor = new QueryConstructor(new Settings()); //When - var resultString = queryConstructor.QueryWhereRestrictionsForFileContentSearch(querySearchString); + var resultString = QueryConstructor.RestrictionsForFileContentSearch(querySearchString); // Then Assert.IsTrue(resultString == expectedString, @@ -187,7 +187,7 @@ namespace Flow.Launcher.Test.Plugins var queryConstructor = new QueryConstructor(new Settings()); //When - var resultString = queryConstructor.QueryForFileContentSearch(userSearchString); + var resultString = queryConstructor.FileContent(userSearchString); // Then Assert.IsTrue(resultString == expectedString, 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 f6d144404..2f2e7b9f6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -51,4 +51,8 @@ + + + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 9c9734967..e0f169207 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -90,4 +90,4 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo 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 b1710a30a..553185c03 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs @@ -193,4 +193,4 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything } } } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs index f22542ee2..a017d43c9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Flow.Launcher.Plugin.Explorer.Search.IProvider; namespace Flow.Launcher.Plugin.Explorer.Search.Everything { - public class EverythingSearchManager : IIndexProvider, IContentIndexProvider, IPathEnumerable + public class EverythingSearchManager : IIndexProvider, IContentIndexProvider, IPathIndexProvider { private Settings Settings { get; } @@ -48,4 +49,4 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything 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 deleted file mode 100644 index 57425c02b..000000000 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Flow.Launcher.Plugin.Explorer.Search -{ - public interface IPathEnumerable - { - public IAsyncEnumerable EnumerateAsync(string path, string search, bool recursive, CancellationToken token); - } -} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IContentIndexProvider.cs similarity index 77% rename from Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs rename to Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IContentIndexProvider.cs index 7e2506f9a..7b8960b37 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IContentIndexProvider.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; using System.Threading; -using System.Threading.Tasks; -namespace Flow.Launcher.Plugin.Explorer.Search +namespace Flow.Launcher.Plugin.Explorer.Search.IProvider { public interface IContentIndexProvider { public IAsyncEnumerable ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token = default); } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IIndexProvider.cs similarity index 73% rename from Plugins/Flow.Launcher.Plugin.Explorer/Search/IIndexProvider.cs rename to Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IIndexProvider.cs index dfb07f9c4..9909b18d8 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IIndexProvider.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IIndexProvider.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; using System.Threading; -using System.Threading.Tasks; -namespace Flow.Launcher.Plugin.Explorer.Search +namespace Flow.Launcher.Plugin.Explorer.Search.IProvider { public interface IIndexProvider { public IAsyncEnumerable SearchAsync(string search, CancellationToken token); } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IPathIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IPathIndexProvider.cs new file mode 100644 index 000000000..56d735687 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IPathIndexProvider.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading; + +namespace Flow.Launcher.Plugin.Explorer.Search.IProvider +{ + public interface IPathIndexProvider + { + public IAsyncEnumerable EnumerateAsync(string path, string search, bool recursive, CancellationToken token); + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 35ba0bafb..03cc4ae89 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -210,4 +210,4 @@ namespace Flow.Launcher.Plugin.Explorer.Search Folder, File } -} \ 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 edea70025..c6432e599 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -55,30 +55,39 @@ namespace Flow.Launcher.Plugin.Explorer.Search results.UnionWith(quickAccessLinks); } - - IAsyncEnumerable searchResults; - - if (ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword)) - { - // A backdoor to prevent everything to do content search - if (Settings.ContentIndexProvider is EverythingSearchManager && !Settings.EnableEverythingContentSearch) - { - return EverythingContentSearchResult(query); - } - searchResults = Settings.ContentIndexProvider.ContentSearchAsync("", query.Search, token); - } else { - searchResults = Settings.IndexProvider.SearchAsync(query.Search, token); + return new List(); } + IAsyncEnumerable searchResults = null; - if (ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword) || - ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)) + bool isPathSearch = query.Search.IsLocationPathString(); + + switch (isPathSearch) { - results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false)); + case true + when (ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword) + || ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)): + results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false)); + return results.ToList(); + break; + case false when ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword): + { + // A backdoor to prevent everything to do content search + if (Settings.ContentIndexProvider is EverythingSearchManager && !Settings.EnableEverythingContentSearch) + { + return EverythingContentSearchResult(query); + } + searchResults = Settings.ContentIndexProvider.ContentSearchAsync("", query.Search, token); + break; + } + case false: + searchResults = Settings.IndexProvider.SearchAsync(query.Search, token); + break; } + if (searchResults == null) { return results.ToList(); @@ -154,7 +163,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).LocationExists()) return results.ToList(); - var useIndexSearch = Settings.IndexSearchEngine is Settings.IndexSearchEngineOption.WindowsIndex + var useIndexSearch = Settings.IndexSearchEngine is Settings.IndexSearchEngineOption.WindowsIndex && UseWindowsIndexForDirectorySearch(locationPath); results.Add(ResultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch)); @@ -172,7 +181,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search query.Search[..recursiveIndicatorIndex], query.Search[(recursiveIndicatorIndex + 1)..], true, - token).ToListAsync(cancellationToken: token) + token) + .ToListAsync(cancellationToken: token) .ConfigureAwait(false); } @@ -207,7 +217,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search } public static bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword; - + private bool UseWindowsIndexForDirectorySearch(string locationPath) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs index 815c6b7b0..eee88b957 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs @@ -2,13 +2,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search { - public struct SearchResult + public record struct SearchResult { public string FullPath { get; init; } public ResultType Type { get; init; } + public int Score { 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/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index 96fb01d7e..3c04c624f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -4,21 +4,25 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { public class QueryConstructor { - private readonly Settings settings; + private Settings Settings { get; } private const string SystemIndex = "SystemIndex"; + public CSearchQueryHelper BaseQueryHelper { get; } + public QueryConstructor(Settings settings) { - this.settings = settings; + Settings = settings; + BaseQueryHelper = CreateBaseQuery(); } - public CSearchQueryHelper CreateBaseQuery() + + private CSearchQueryHelper CreateBaseQuery() { var baseQuery = CreateQueryHelper(); // Set the number of results we want. Don't set this property if all results are needed. - baseQuery.QueryMaxResults = settings.MaxResult; + baseQuery.QueryMaxResults = Settings.MaxResult; // Set list of columns we want to display, getting the path presently baseQuery.QuerySelectColumns = "System.FileName, System.ItemUrl, System.ItemType"; @@ -32,7 +36,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex return baseQuery; } - internal CSearchQueryHelper CreateQueryHelper() + internal static CSearchQueryHelper CreateQueryHelper() { // This uses the Microsoft.Search.Interop assembly var manager = new CSearchManager(); @@ -42,18 +46,19 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex // Get the ISearchQueryHelper which will help us to translate AQS --> SQL necessary to query the indexer var queryHelper = catalogManager.GetQueryHelper(); - + return queryHelper; } + private static string TopLevelDirectoryConstraint(string path) => $"directory='file:{path}'"; + private static string RecursiveDirectoryConstraint(string path) => $"scope='file:{path}'"; + /// /// Set the required WHERE clause restriction to search on the first level of a specified directory. /// public string QueryWhereRestrictionsForTopLevelDirectorySearch(string path) { - var searchDepth = $"directory='file:"; - - return QueryWhereRestrictionsFromLocationPath(path, searchDepth); + return QueryWhereRestrictionsFromLocationPath(path, "directory='file:"); } /// @@ -61,9 +66,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex /// public string QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(string path) { - var searchDepth = $"scope='file:"; - - return QueryWhereRestrictionsFromLocationPath(path, searchDepth); + return QueryWhereRestrictionsFromLocationPath(path, "directory='scope:"); } private string QueryWhereRestrictionsFromLocationPath(string path, string searchDepth) @@ -73,70 +76,72 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex var indexOfSeparator = path.LastIndexOf(Constants.DirectorySeperator); - var itemName = path.Substring(indexOfSeparator + 1); + var itemName = path[(indexOfSeparator + 1)..]; if (itemName.StartsWith(Constants.AllFilesFolderSearchWildcard)) - itemName = itemName.Substring(1); + itemName = itemName[1..]; var previousLevelDirectory = path.Substring(0, indexOfSeparator); if (string.IsNullOrEmpty(itemName)) return $"{searchDepth}{previousLevelDirectory}'"; - return $"(System.FileName LIKE '{itemName}%' OR CONTAINS(System.FileName,'\"{itemName}*\"',1033)) AND {searchDepth}{previousLevelDirectory}'"; + return $"(System.FileName LIKE '{itemName}%' OR CONTAINS({FileName},'\"{itemName}*\"',1033)) AND {searchDepth}{previousLevelDirectory}'"; } /// /// Search will be performed on all folders and files on the first level of a specified directory. /// - public string QueryForTopLevelDirectorySearch(string path) + public string Directory(string path, string searchString = "", bool recursive = false) { - string query = "SELECT TOP " + settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE "; + var queryConstraint = searchString is "" ? "" : $"AND ({FileName} LIKE '{searchString}%' OR CONTAINS({FileName},'\"{searchString}*\"'))"; - if (path.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > path.LastIndexOf(Constants.DirectorySeperator)) - return query + QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path) + QueryOrderByFileNameRestriction; + var scopeConstraint = recursive + ? RecursiveDirectoryConstraint(path) + : TopLevelDirectoryConstraint(path); - return query + QueryWhereRestrictionsForTopLevelDirectorySearch(path) + QueryOrderByFileNameRestriction; + string query = $"SELECT TOP {Settings.MaxResult} {BaseQueryHelper.QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {FileName}"; + + return query; } /// /// Search will be performed on all folders and files based on user's search keywords. /// - public string QueryForAllFilesAndFolders(string userSearchString) + public string FilesAndFolders(string userSearchString) { if (string.IsNullOrEmpty(userSearchString)) userSearchString = "*"; - + // Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause - return CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch - + QueryOrderByFileNameRestriction; + return $"{BaseQueryHelper.GenerateSQLFromUserQuery(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}"; } /// /// Set the required WHERE clause restriction to search for all files and folders. /// - public const string QueryWhereRestrictionsForAllFilesAndFoldersSearch = "scope='file:'"; + public const string RestrictionsForAllFilesAndFoldersSearch = "scope='file:'"; - public const string QueryOrderByFileNameRestriction = " ORDER BY System.FileName"; + /// + /// Order identifier: file name + /// + public const string FileName = "System.FileName"; /// /// Search will be performed on all indexed file contents for the specified search keywords. /// - public string QueryForFileContentSearch(string userSearchString) + public string FileContent(string userSearchString) { - string query = "SELECT TOP " + settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE "; + string query = + $"SELECT TOP {Settings.MaxResult} {BaseQueryHelper.QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}"; - return query + QueryWhereRestrictionsForFileContentSearch(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch - + QueryOrderByFileNameRestriction; + return query; } /// /// Set the required WHERE clause restriction to search within file content. /// - public string QueryWhereRestrictionsForFileContentSearch(string searchQuery) - { - return $"FREETEXT('{searchQuery}')"; - } + public static string RestrictionsForFileContentSearch(string searchQuery) => $"FREETEXT('{searchQuery}')"; } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs index 6a6d8cdb8..60e5ac741 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs @@ -18,7 +18,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { // Reserved keywords in oleDB - private const string ReservedStringPattern = @"^[`\@\#\^,\&\/\\\$\%_;\[\]]+$"; + private static Regex _reservedPatternMatcher = new(@"^[`\@\#\^,\&\/\\\$\%_;\[\]]+$", RegexOptions.Compiled); private static async IAsyncEnumerable ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, [EnumeratorCancellation] CancellationToken token) { @@ -28,31 +28,42 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex await using var command = new OleDbCommand(indexQueryString, conn); // Results return as an OleDbDataReader. - await using var dataReaderResults = await command.ExecuteReaderAsync(token) as OleDbDataReader; + OleDbDataReader dataReaderAttempt; + try + { + dataReaderAttempt = await command.ExecuteReaderAsync(token) as OleDbDataReader; + } + catch (OleDbException e) + { + Log.Exception($"|WindowsIndex.ExecuteWindowsIndexSearchAsync|Failed to execute windows index search query: {indexQueryString}", e); + yield break; + } + await using var dataReader = dataReaderAttempt; token.ThrowIfCancellationRequested(); - if (dataReaderResults is not { HasRows: true }) + if (dataReader is not { HasRows: true }) { yield break; } - while (await dataReaderResults.ReadAsync(token)) + + while (await dataReader.ReadAsync(token)) { token.ThrowIfCancellationRequested(); - if (dataReaderResults.GetValue(0) == DBNull.Value || dataReaderResults.GetValue(1) == DBNull.Value) + if (dataReader.GetValue(0) == DBNull.Value || dataReader.GetValue(1) == DBNull.Value) { continue; } // # is URI syntax for the fragment component, need to be encoded so LocalPath returns complete path - var encodedFragmentPath = dataReaderResults + var encodedFragmentPath = dataReader .GetString(1) .Replace("#", "%23", StringComparison.OrdinalIgnoreCase); var path = new Uri(encodedFragmentPath).LocalPath; - yield return new SearchResult() + yield return new SearchResult { FullPath = path, - Type = dataReaderResults.GetString(2) == "Directory" ? ResultType.Folder : ResultType.File, + Type = dataReader.GetString(2) == "Directory" ? ResultType.Folder : ResultType.File, WindowsIndexed = true }; } @@ -60,22 +71,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex // Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. } - internal static IAsyncEnumerable WindowsIndexSearchAsync(string searchString, - Func createQueryHelper, - Func constructQuery, - IEnumerable exclusionList, + + + internal static IAsyncEnumerable WindowsIndexSearchAsync(string connectionString, + string search, CancellationToken token) { - var regexMatch = Regex.Match(searchString, ReservedStringPattern); - - if (regexMatch.Success) - return AsyncEnumerable.Empty(); - - var constructedQuery = constructQuery(searchString); - - return ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, token); + return _reservedPatternMatcher.IsMatch(search) + ? AsyncEnumerable.Empty() + : ExecuteWindowsIndexSearchAsync(search, connectionString, token); } + // TODO: Move to General Search Manager private static void RemoveResultsInExclusionList(List results, IReadOnlyList exclusionList, CancellationToken token) { var indexExclusionListCount = exclusionList.Count; @@ -102,6 +109,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex } } + // TODO: Use a custom exception to handle this private static List ResultForWindexSearchOff(string rawQuery) { var api = SearchManager.Context.API; @@ -160,4 +168,4 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex #endif } } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs index 2b170afc4..6203df60e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs @@ -2,19 +2,24 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Flow.Launcher.Plugin.Explorer.Search.IProvider; +using Microsoft.Search.Interop; namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { - public class WindowsIndexSearchManager : IIndexProvider, IContentIndexProvider, IPathEnumerable + public class WindowsIndexSearchManager : IIndexProvider, IContentIndexProvider, IPathIndexProvider { private Settings Settings { get; } private QueryConstructor QueryConstructor { get; } + + private CSearchQueryHelper QueryHelper { get; } public WindowsIndexSearchManager(Settings settings) { Settings = settings; QueryConstructor = new QueryConstructor(Settings); + QueryHelper = QueryConstructor.CreateQueryHelper(); } - + private IAsyncEnumerable WindowsIndexFileContentSearchAsync(string querySearchString, CancellationToken token) { @@ -22,39 +27,35 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex return AsyncEnumerable.Empty(); return WindowsIndex.WindowsIndexSearchAsync( - querySearchString, - QueryConstructor.CreateQueryHelper, - QueryConstructor.QueryForFileContentSearch, - Settings.IndexSearchExcludedSubdirectoryPaths, + QueryHelper.ConnectionString, + QueryConstructor.FileContent(querySearchString), token); } private IAsyncEnumerable WindowsIndexFilesAndFoldersSearchAsync(string querySearchString, - CancellationToken token) + CancellationToken token = default) { return WindowsIndex.WindowsIndexSearchAsync( - querySearchString, - QueryConstructor.CreateQueryHelper, - QueryConstructor.QueryForAllFilesAndFolders, - Settings.IndexSearchExcludedSubdirectoryPaths, + QueryHelper.ConnectionString, + QueryConstructor.FilesAndFolders(querySearchString), token); } - private IAsyncEnumerable WindowsIndexTopLevelFolderSearchAsync(string path,string search, + private IAsyncEnumerable WindowsIndexTopLevelFolderSearchAsync(string search, + string path, + bool recursive, CancellationToken token) { var queryConstructor = new QueryConstructor(Settings); return WindowsIndex.WindowsIndexSearchAsync( - path, - queryConstructor.CreateQueryHelper, - queryConstructor.QueryForTopLevelDirectorySearch, - Settings.IndexSearchExcludedSubdirectoryPaths, + QueryConstructor.CreateQueryHelper().ConnectionString, + queryConstructor.Directory(path, search, recursive), token); } public IAsyncEnumerable SearchAsync(string search, CancellationToken token) { - return WindowsIndexFilesAndFoldersSearchAsync(search, token); + return WindowsIndexFilesAndFoldersSearchAsync(search, token: token); } public IAsyncEnumerable ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token) { @@ -62,7 +63,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex } public IAsyncEnumerable EnumerateAsync(string path, string search, bool recursive, CancellationToken token) { - return recursive ? WindowsIndexFilesAndFoldersSearchAsync(search, token) : WindowsIndexTopLevelFolderSearchAsync(path, search, token); + return WindowsIndexTopLevelFolderSearchAsync(search, path, recursive, 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 0b2c8eea2..eb67af3a6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -9,6 +9,7 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Text.Json.Serialization; +using Flow.Launcher.Plugin.Explorer.Search.IProvider; namespace Flow.Launcher.Plugin.Explorer { @@ -65,7 +66,7 @@ namespace Flow.Launcher.Plugin.Explorer #region SearchEngine - public PathEnumerationEngineOption PathEnumerationEngine { get; set; } + public PathEnumerationEngineOption PathEnumerationEngine { get; set; } = PathEnumerationEngineOption.WindowsIndex; private EverythingSearchManager EverythingManagerInstance => _everythingManagerInstance ??= new EverythingSearchManager(this); private WindowsIndexSearchManager WindowsIndexSearchManager => _windowsIndexSearchManager ??= new WindowsIndexSearchManager(this); @@ -81,7 +82,7 @@ namespace Flow.Launcher.Plugin.Explorer }; [JsonIgnore] - public IPathEnumerable PathEnumerator => PathEnumerationEngine switch + public IPathIndexProvider PathEnumerator => PathEnumerationEngine switch { PathEnumerationEngineOption.Everything => EverythingManagerInstance, PathEnumerationEngineOption.WindowsIndex => WindowsIndexSearchManager, @@ -192,4 +193,4 @@ namespace Flow.Launcher.Plugin.Explorer _ => throw new ArgumentOutOfRangeException(nameof(actionKeyword), actionKeyword, "ActionKeyword enabled status not defined") }; } -} \ No newline at end of file +}