From a3622d472e42cbc568b4e270f036698bdd6d9c60 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Tue, 16 Aug 2022 18:45:36 -0400 Subject: [PATCH] Code Refactor with IAsyncEnumerable instead of Task --- .../Flow.Launcher.Plugin.csproj | 1 + Flow.Launcher.Test/Plugins/ExplorerTest.cs | 73 +++++----------- .../DirectoryInfo/DirectoryInfoSearch.cs | 4 +- .../Search/Everything/EverythingAPI.cs | 63 ++++++-------- .../Everything/EverythingSearchManager.cs | 26 +++--- .../Everything/EverythingSearchOption.cs | 38 ++------- .../Search/IContentIndexProvider.cs | 2 +- .../Search/IIndexProvider.cs | 2 +- .../Search/IPathEnumerable.cs | 2 +- .../Search/SearchManager.cs | 59 +++++-------- .../Search/WindowsIndex/WindowsIndex.cs | 85 +++++++------------ .../WindowsIndex/WindowsIndexSearchManager.cs | 43 +++++----- 12 files changed, 150 insertions(+), 248 deletions(-) diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 41072993c..55f2f3a7e 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -68,6 +68,7 @@ + diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 9d7fccad9..7e379e3f8 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -7,6 +7,7 @@ using Flow.Launcher.Plugin.SharedCommands; using NUnit.Framework; using System; using System.Collections.Generic; +using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; @@ -44,6 +45,7 @@ namespace Flow.Launcher.Test.Plugins private bool PreviousLocationNotExistReturnsFalse(string dummyString) => false; + [SupportedOSPlatform("windows7.0")] [TestCase("C:\\SomeFolder\\", "directory='file:C:\\SomeFolder\\'")] public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestrictionsShouldUseDirectoryString(string path, string expectedString) { @@ -60,6 +62,7 @@ namespace Flow.Launcher.Test.Plugins $"Actual: {result}{Environment.NewLine}"); } + [SupportedOSPlatform("windows7.0")] [TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY System.FileName")] [TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY System.FileName")] public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString) @@ -76,10 +79,11 @@ namespace Flow.Launcher.Test.Plugins $"Actual string was: {queryString}{Environment.NewLine}"); } + [SupportedOSPlatform("windows7.0")] [TestCase("C:\\SomeFolder\\flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " + - "FROM SystemIndex WHERE (System.FileName LIKE 'flow.launcher.sln%' " + - "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033))" + - " AND directory='file:C:\\SomeFolder' ORDER BY System.FileName")] + "FROM SystemIndex WHERE (System.FileName LIKE 'flow.launcher.sln%' " + + "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033))" + + " AND directory='file:C:\\SomeFolder' ORDER BY System.FileName")] public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString( string userSearchString, string expectedString) { @@ -94,10 +98,11 @@ namespace Flow.Launcher.Test.Plugins $"Expected string: {expectedString}{Environment.NewLine} " + $"Actual string was: {queryString}{Environment.NewLine}"); } - + + [SupportedOSPlatform("windows7.0")] [TestCase("C:\\SomeFolder\\SomeApp", "(System.FileName LIKE 'SomeApp%' " + - "OR CONTAINS(System.FileName,'\"SomeApp*\"',1033))" + - " AND directory='file:C:\\SomeFolder'")] + "OR CONTAINS(System.FileName,'\"SomeApp*\"',1033))" + + " AND directory='file:C:\\SomeFolder'")] public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryWhereRestrictionsShouldUseDirectoryString( string userSearchString, string expectedString) { @@ -113,6 +118,7 @@ namespace Flow.Launcher.Test.Plugins $"Actual string was: {queryString}{Environment.NewLine}"); } + [SupportedOSPlatform("windows7.0")] [TestCase("scope='file:'")] public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString) { @@ -125,9 +131,10 @@ namespace Flow.Launcher.Test.Plugins $"Actual string was: {resultString}{Environment.NewLine}"); } + [SupportedOSPlatform("windows7.0")] [TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" " + - "FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " + - "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY System.FileName")] + "FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " + + "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY System.FileName")] public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString( string userSearchString, string expectedString) { @@ -149,49 +156,8 @@ namespace Flow.Launcher.Test.Plugins $"Expected query string: {expectedString}{Environment.NewLine} " + $"Actual string was: {resultString}{Environment.NewLine}"); } - - [TestCase] - public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldContinueDirectoryInfoClassSearch() - { - // Given - var searchManager = new SearchManager(new Settings(), new PluginInitContext()); - - // When - var results = await searchManager.TopLevelDirectorySearchBehaviourAsync( - MethodWindowsIndexSearchReturnsZeroResultsAsync, - MethodDirectoryInfoClassSearchReturnsTwoResults, - false, - new Query(), - "string not used", - default); - - // Then - Assert.IsTrue(results.Count == 2, - $"Expected to have 2 results from DirectoryInfoClassSearch {Environment.NewLine} " + - $"Actual number of results is {results.Count} {Environment.NewLine}"); - } - - [TestCase] - public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearch() - { - // Given - var searchManager = new SearchManager(new Settings(), new PluginInitContext()); - - // When - var results = await searchManager.TopLevelDirectorySearchBehaviourAsync( - MethodWindowsIndexSearchReturnsZeroResultsAsync, - MethodDirectoryInfoClassSearchReturnsTwoResults, - true, - new Query(), - "string not used", - default); - - // Then - Assert.IsTrue(results.Count == 0, - $"Expected to have 0 results because location is indexed {Environment.NewLine} " + - $"Actual number of results is {results.Count} {Environment.NewLine}"); - } - + + [SupportedOSPlatform("windows7.0")] [TestCase(@"some words", @"FREETEXT('some words')")] public void GivenWindowsIndexSearch_WhenQueryWhereRestrictionsIsForFileContentSearch_ThenShouldReturnFreeTextString( string querySearchString, string expectedString) @@ -208,6 +174,7 @@ namespace Flow.Launcher.Test.Plugins $"Actual string was: {resultString}{Environment.NewLine}"); } + [SupportedOSPlatform("windows7.0")] [TestCase("some words", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " + "FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY System.FileName")] public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString( @@ -233,7 +200,7 @@ namespace Flow.Launcher.Test.Plugins var searchManager = new SearchManager(new Settings(), new PluginInitContext()); // When - var result = searchManager.IsFileContentSearch(query.ActionKeyword); + var result = SearchManager.IsFileContentSearch(query.ActionKeyword); // Then Assert.IsTrue(result, @@ -301,6 +268,7 @@ namespace Flow.Launcher.Test.Plugins $"Actual path string is {returnedPath} {Environment.NewLine}"); } + [SupportedOSPlatform("windows7.0")] [TestCase("c:\\SomeFolder\\>", "scope='file:c:\\SomeFolder'")] [TestCase("c:\\SomeFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' " + "OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND " @@ -319,6 +287,7 @@ namespace Flow.Launcher.Test.Plugins $"Actual string was: {resultString}{Environment.NewLine}"); } + [SupportedOSPlatform("windows7.0")] [TestCase("c:\\somefolder\\>somefile", "*somefile*")] [TestCase("c:\\somefolder\\somefile", "somefile*")] [TestCase("c:\\somefolder\\", "*")] diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 82eecd8fd..9c9734967 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -33,10 +33,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { var indexOfSeparator = search.LastIndexOf(Constants.DirectorySeperator); - incompleteName = search.Substring(indexOfSeparator + 1).ToLower(); + incompleteName = search[(indexOfSeparator + 1)..].ToLower(); if (incompleteName.StartsWith(Constants.AllFilesFolderSearchWildcard)) - incompleteName = "*" + incompleteName.Substring(1); + incompleteName = string.Concat("*", incompleteName.AsSpan(1)); } incompleteName += "*"; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs index 92538d554..b1710a30a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs @@ -2,6 +2,8 @@ using Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions; using System; using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading; @@ -15,7 +17,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything private const int BufferSize = 4096; - private static readonly object syncObject = new object(); + private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); // cached buffer to remove redundant allocations. private static readonly StringBuilder buffer = new StringBuilder(BufferSize); @@ -71,17 +73,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything 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. /// @@ -99,24 +90,24 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything /// /// Searches the specified key word and reset the everything API afterwards /// - /// The key word. + /// Search Criteria /// 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(EverythingSearchOption option, - CancellationToken token) + /// An IAsyncEnumerable that will enumerate all results searched by the specific query and option + public static async IAsyncEnumerable SearchAsync(EverythingSearchOption option, + [EnumeratorCancellation] CancellationToken token) { if (option.Offset < 0) throw new ArgumentOutOfRangeException(nameof(option.Offset), option.Offset, "Offset must be greater than or equal to 0"); if (option.MaxCount < 0) throw new ArgumentOutOfRangeException(nameof(option.MaxCount), option.MaxCount, "MaxCount must be greater than or equal to 0"); - - lock (syncObject) + + await _semaphore.WaitAsync(token); + + if (token.IsCancellationRequested) + yield break; + + try { if (option.Keyword.StartsWith("@")) { @@ -140,24 +131,21 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything EverythingApiDllImport.Everything_SetSort(option.SortOption); - if (token.IsCancellationRequested) - { - return null; - } + if (token.IsCancellationRequested) yield break; + if (!EverythingApiDllImport.Everything_QueryW(true)) { CheckAndThrowExceptionOnError(); - return null; + yield break; } - var results = new List(); for (var idx = 0; idx < EverythingApiDllImport.Everything_GetNumResults(); ++idx) { if (token.IsCancellationRequested) { - return null; + yield break; } EverythingApiDllImport.Everything_GetResultFullPathNameW(idx, buffer, BufferSize); @@ -170,12 +158,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything ResultType.Volume }; - results.Add(result); + yield return result; } - - Reset(); - - return results; + } + finally + { + EverythingApiDllImport.Everything_Reset(); + _semaphore.Release(); } } @@ -197,6 +186,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything throw new MemoryErrorException(); case StateCode.RegisterClassExError: throw new RegisterClassExException(); + case StateCode.OK: + break; + default: + throw new ArgumentOutOfRangeException(); } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs index 3bd4a3c7c..f22542ee2 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs @@ -1,5 +1,6 @@ using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -15,37 +16,36 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything } - public ValueTask> SearchAsync(string search, CancellationToken token) + public IAsyncEnumerable SearchAsync(string search, CancellationToken token) { - return ValueTask.FromResult(EverythingApi.SearchAsync( + return EverythingApi.SearchAsync( new EverythingSearchOption(search, Settings.SortOption), - token)); + token); } - public ValueTask> ContentSearchAsync(string plainSearch, + public IAsyncEnumerable ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token) { if (!Settings.EnableEverythingContentSearch) { - return new ValueTask>(new List()); + return AsyncEnumerable.Empty(); } - return new ValueTask>(EverythingApi.SearchAsync( + return EverythingApi.SearchAsync( new EverythingSearchOption( plainSearch, Settings.SortOption, true, contentSearch), - token)); + token); } - public ValueTask> EnumerateAsync(string path, string search, bool recursive, CancellationToken token) + public IAsyncEnumerable EnumerateAsync(string path, string search, bool recursive, CancellationToken token) { - return new ValueTask>( - EverythingApi.SearchAsync( + return EverythingApi.SearchAsync( new EverythingSearchOption(search, Settings.SortOption, - parentPath: path, - isRecursive: recursive), - token)); + 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 index ef41cf1f0..9d52d35fb 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs @@ -2,34 +2,12 @@ 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; } - } + public record struct EverythingSearchOption(string Keyword, + SortOption SortOption, + bool IsContentSearch = false, + string ContentSearchKeyword = "", + string ParentPath = "", + bool IsRecursive = true, + int Offset = 0, + int MaxCount = 100); } \ 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 bd7e14a66..7e2506f9a 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 plainSearch, string contentSearch, CancellationToken token); + 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/IIndexProvider.cs index be40b6a24..dfb07f9c4 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IIndexProvider.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IIndexProvider.cs @@ -6,6 +6,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search { public interface IIndexProvider { - public ValueTask> SearchAsync(string search, CancellationToken token); + public IAsyncEnumerable 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 eb82422f4..57425c02b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs @@ -7,6 +7,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search { public interface IPathEnumerable { - public ValueTask> EnumerateAsync(string path, string search, bool recursive, CancellationToken token); + 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/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 255d52632..32bd1fd7e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -41,8 +41,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal async Task> SearchAsync(Query query, CancellationToken token) { - var querySearch = query.Search; - var results = new HashSet(PathEqualityComparator.Instance); // This allows the user to type the below action keywords and see/search the list of quick folder links @@ -58,35 +56,37 @@ namespace Flow.Launcher.Plugin.Explorer.Search results.UnionWith(quickAccessLinks); } - IEnumerable searchResults; + 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 = await Settings.ContentIndexProvider.ContentSearchAsync("", query.Search, token); + searchResults = Settings.ContentIndexProvider.ContentSearchAsync("", query.Search, token); } else { - searchResults = await Settings.IndexProvider.SearchAsync(query.Search, token); + searchResults = Settings.IndexProvider.SearchAsync(query.Search, token); } + if (ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword) || ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)) { results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false)); } - if (((ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword) || - ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)) && - querySearch.Length > 0) || - ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword) && - !querySearch.IsLocationPathString()) + if (searchResults == null) { - if (searchResults != null) - results.UnionWith(searchResults.Select(search => ResultManager.CreateResult(query, search))); + return results.ToList(); + } + + await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false)) + { + results.Add(ResultManager.CreateResult(query, search)); } return results.ToList(); @@ -131,7 +131,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search }; } - public async Task> PathSearchAsync(Query query, CancellationToken token = default) + private async Task> PathSearchAsync(Query query, CancellationToken token = default) { var querySearch = query.Search; @@ -154,7 +154,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).LocationExists()) return results.ToList(); - var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath); + var useIndexSearch = Settings.IndexSearchEngine is Settings.IndexSearchEngineOption.WindowsIndex + && UseWindowsIndexForDirectorySearch(locationPath); results.Add(ResultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch)); @@ -167,10 +168,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (recursiveIndicatorIndex > 0 && Settings.PathEnumerationEngine != Settings.PathEnumerationEngineOption.Direct) { directoryResult = - await Settings.PathEnumerator.EnumerateAsync(query.Search[..recursiveIndicatorIndex], + await Settings.PathEnumerator.EnumerateAsync( + query.Search[..recursiveIndicatorIndex], query.Search[(recursiveIndicatorIndex + 1)..], true, - token) + token).ToListAsync(cancellationToken: token) .ConfigureAwait(false); } @@ -188,10 +190,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search Title = string.Format(SearchManager.Context.API.GetTranslation( "plugin_explorer_directoryinfosearch_error"), e.Message), - Score = 501, + Score = 501, IcoPath = Constants.ExplorerIconImagePath }); - directoryResult = new List(); + directoryResult = Enumerable.Empty(); } } @@ -204,25 +206,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search return results.ToList(); } - public bool IsFileContentSearch(string actionKeyword) - { - return actionKeyword == Settings.FileContentSearchActionKeyword; - } - - - public async Task> TopLevelDirectorySearchBehaviourAsync( - Func>> windowsIndexSearch, - Func> directoryInfoClassSearch, - bool useIndexSearch, - Query query, - string querySearchString, - CancellationToken token) - { - if (!useIndexSearch) - return directoryInfoClassSearch(query, querySearchString, token); - - return await windowsIndexSearch(query, querySearchString, token); - } + public static bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword; + private bool UseWindowsIndexForDirectorySearch(string locationPath) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs index 64717b41c..6a6d8cdb8 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Data.OleDb; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading; @@ -19,66 +20,47 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex // Reserved keywords in oleDB private const string ReservedStringPattern = @"^[`\@\#\^,\&\/\\\$\%_;\[\]]+$"; - private static async Task> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, CancellationToken token) + private static async IAsyncEnumerable ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, [EnumeratorCancellation] CancellationToken token) { - var results = new List(); + await using var conn = new OleDbConnection(connectionString); + await conn.OpenAsync(token); + token.ThrowIfCancellationRequested(); - try + await using var command = new OleDbCommand(indexQueryString, conn); + // Results return as an OleDbDataReader. + await using var dataReaderResults = await command.ExecuteReaderAsync(token) as OleDbDataReader; + token.ThrowIfCancellationRequested(); + + if (dataReaderResults is not { HasRows: true }) + { + yield break; + } + while (await dataReaderResults.ReadAsync(token)) { - await using var conn = new OleDbConnection(connectionString); - await conn.OpenAsync(token); token.ThrowIfCancellationRequested(); - - await using var command = new OleDbCommand(indexQueryString, conn); - // Results return as an OleDbDataReader. - await using var dataReaderResults = await command.ExecuteReaderAsync(token) as OleDbDataReader; - token.ThrowIfCancellationRequested(); - - if (dataReaderResults is { HasRows: true }) + if (dataReaderResults.GetValue(0) == DBNull.Value || dataReaderResults.GetValue(1) == DBNull.Value) { - while (await dataReaderResults.ReadAsync(token)) - { - token.ThrowIfCancellationRequested(); - if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value) - { - // # is URI syntax for the fragment component, need to be encoded so LocalPath returns complete path - var encodedFragmentPath = dataReaderResults - .GetString(1) - .Replace("#", "%23", StringComparison.OrdinalIgnoreCase); - - var path = new Uri(encodedFragmentPath).LocalPath; - - results.Add(new SearchResult() - { - FullPath = path, - Type = dataReaderResults.GetString(2) == "Directory" ? ResultType.Folder : ResultType.File, - WindowsIndexed = true - }); - } - } + continue; } - } - catch (OperationCanceledException) - { - // return empty result when cancelled - return results; - } - catch (InvalidOperationException e) - { - // Internal error from ExecuteReader(): Connection closed. - LogException("Internal error from ExecuteReader()", e); - } - catch (Exception e) - { - LogException("General error from performing index search", e); + // # is URI syntax for the fragment component, need to be encoded so LocalPath returns complete path + var encodedFragmentPath = dataReaderResults + .GetString(1) + .Replace("#", "%23", StringComparison.OrdinalIgnoreCase); + + var path = new Uri(encodedFragmentPath).LocalPath; + + yield return new SearchResult() + { + FullPath = path, + Type = dataReaderResults.GetString(2) == "Directory" ? ResultType.Folder : ResultType.File, + WindowsIndexed = true + }; } // Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. - return results; } - internal static async ValueTask> WindowsIndexSearchAsync( - string searchString, + internal static IAsyncEnumerable WindowsIndexSearchAsync(string searchString, Func createQueryHelper, Func constructQuery, IEnumerable exclusionList, @@ -87,12 +69,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex var regexMatch = Regex.Match(searchString, ReservedStringPattern); if (regexMatch.Success) - return new(); + return AsyncEnumerable.Empty(); var constructedQuery = constructQuery(searchString); - return - await ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, token); + return ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, token); } private static void RemoveResultsInExclusionList(List results, IReadOnlyList exclusionList, CancellationToken token) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs index 05e75c646..2b170afc4 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -13,61 +14,55 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex Settings = settings; QueryConstructor = new QueryConstructor(Settings); } - - - private async Task> WindowsIndexFileContentSearchAsync(string querySearchString, + + private IAsyncEnumerable WindowsIndexFileContentSearchAsync(string querySearchString, CancellationToken token) { if (string.IsNullOrEmpty(querySearchString)) - return new List(); + return AsyncEnumerable.Empty(); - return await WindowsIndex.WindowsIndexSearchAsync( + return WindowsIndex.WindowsIndexSearchAsync( querySearchString, QueryConstructor.CreateQueryHelper, QueryConstructor.QueryForFileContentSearch, Settings.IndexSearchExcludedSubdirectoryPaths, - token).ConfigureAwait(false); + token); } - - - private async Task> WindowsIndexFilesAndFoldersSearchAsync(string querySearchString, + private IAsyncEnumerable WindowsIndexFilesAndFoldersSearchAsync(string querySearchString, CancellationToken token) { - return await WindowsIndex.WindowsIndexSearchAsync( + return WindowsIndex.WindowsIndexSearchAsync( querySearchString, QueryConstructor.CreateQueryHelper, QueryConstructor.QueryForAllFilesAndFolders, Settings.IndexSearchExcludedSubdirectoryPaths, - token).ConfigureAwait(false); + token); } - - - private async Task> WindowsIndexTopLevelFolderSearchAsync(string path,string search, + + private IAsyncEnumerable WindowsIndexTopLevelFolderSearchAsync(string path,string search, CancellationToken token) { var queryConstructor = new QueryConstructor(Settings); - return await WindowsIndex.WindowsIndexSearchAsync( + return WindowsIndex.WindowsIndexSearchAsync( path, queryConstructor.CreateQueryHelper, queryConstructor.QueryForTopLevelDirectorySearch, Settings.IndexSearchExcludedSubdirectoryPaths, - token).ConfigureAwait(false); + token); } - public async ValueTask> SearchAsync(string search, CancellationToken token) + public IAsyncEnumerable SearchAsync(string search, CancellationToken token) { - return await WindowsIndexFilesAndFoldersSearchAsync(search, token); + return WindowsIndexFilesAndFoldersSearchAsync(search, token); } - public async ValueTask> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token) + public IAsyncEnumerable ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token) { - return await WindowsIndexFileContentSearchAsync(contentSearch, token); + return WindowsIndexFileContentSearchAsync(contentSearch, token); } - public async ValueTask> EnumerateAsync(string path, string search, bool recursive, CancellationToken token) + public IAsyncEnumerable EnumerateAsync(string path, string search, bool recursive, CancellationToken token) { - if(recursive) - return await WindowsIndexFilesAndFoldersSearchAsync(search, token).ConfigureAwait(false); - return await WindowsIndexTopLevelFolderSearchAsync(path, search, token); + return recursive ? WindowsIndexFilesAndFoldersSearchAsync(search, token) : WindowsIndexTopLevelFolderSearchAsync(path, search, token); } } } \ No newline at end of file