Code Refactor with IAsyncEnumerable instead of Task<IEnumerable>

This commit is contained in:
Hongtao Zhang 2022-08-16 18:45:36 -04:00
parent 86210d0cfb
commit a3622d472e
No known key found for this signature in database
GPG key ID: 75F655B91C7AC9BB
12 changed files with 150 additions and 248 deletions

View file

@ -68,6 +68,7 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2021.2.0" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
</ItemGroup>
</Project>

View file

@ -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\\", "*")]

View file

@ -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 += "*";

View file

@ -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);
}
/// <summary>
/// Resets this instance.
/// </summary>
private static void Reset()
{
lock (syncObject)
{
EverythingApiDllImport.Everything_Reset();
}
}
/// <summary>
/// Checks whether the sort option is Fast Sort.
/// </summary>
@ -99,24 +90,24 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
/// <summary>
/// Searches the specified key word and reset the everything API afterwards
/// </summary>
/// <param name="keyword">The key word.</param>
/// <param name="option">Search Criteria</param>
/// <param name="token">when cancelled the current search will stop and exit (and would not reset)</param>
/// <param name="parentPath">Search Within a parent folder</param>
/// <param name="recursive">Search Within sub folder of the parent folder</param>
/// <param name="sortOption">Sort By</param>
/// <param name="offset">The offset.</param>
/// <param name="maxCount">The max count.</param>
/// <returns></returns>
public static IEnumerable<SearchResult> SearchAsync(EverythingSearchOption option,
CancellationToken token)
/// <returns>An IAsyncEnumerable that will enumerate all results searched by the specific query and option</returns>
public static async IAsyncEnumerable<SearchResult> 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<SearchResult>();
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();
}
}
}

View file

@ -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<IEnumerable<SearchResult>> SearchAsync(string search, CancellationToken token)
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token)
{
return ValueTask.FromResult(EverythingApi.SearchAsync(
return EverythingApi.SearchAsync(
new EverythingSearchOption(search, Settings.SortOption),
token));
token);
}
public ValueTask<IEnumerable<SearchResult>> ContentSearchAsync(string plainSearch,
public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch,
string contentSearch, CancellationToken token)
{
if (!Settings.EnableEverythingContentSearch)
{
return new ValueTask<IEnumerable<SearchResult>>(new List<SearchResult>());
return AsyncEnumerable.Empty<SearchResult>();
}
return new ValueTask<IEnumerable<SearchResult>>(EverythingApi.SearchAsync(
return EverythingApi.SearchAsync(
new EverythingSearchOption(
plainSearch,
Settings.SortOption,
true,
contentSearch),
token));
token);
}
public ValueTask<IEnumerable<SearchResult>> EnumerateAsync(string path, string search, bool recursive, CancellationToken token)
public IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, CancellationToken token)
{
return new ValueTask<IEnumerable<SearchResult>>(
EverythingApi.SearchAsync(
return EverythingApi.SearchAsync(
new EverythingSearchOption(search,
Settings.SortOption,
parentPath: path,
isRecursive: recursive),
token));
ParentPath: path,
IsRecursive: recursive),
token);
}
}
}

View file

@ -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);
}

View file

@ -6,6 +6,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
public interface IContentIndexProvider
{
public ValueTask<IEnumerable<SearchResult>> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token);
public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token = default);
}
}

View file

@ -6,6 +6,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
public interface IIndexProvider
{
public ValueTask<IEnumerable<SearchResult>> SearchAsync(string search, CancellationToken token);
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token);
}
}

View file

@ -7,6 +7,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
public interface IPathEnumerable
{
public ValueTask<IEnumerable<SearchResult>> EnumerateAsync(string path, string search, bool recursive, CancellationToken token);
public IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, CancellationToken token);
}
}

View file

@ -41,8 +41,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search
internal async Task<List<Result>> SearchAsync(Query query, CancellationToken token)
{
var querySearch = query.Search;
var results = new HashSet<Result>(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<SearchResult> searchResults;
IAsyncEnumerable<SearchResult> 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<List<Result>> PathSearchAsync(Query query, CancellationToken token = default)
private async Task<List<Result>> 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<SearchResult>();
directoryResult = Enumerable.Empty<SearchResult>();
}
}
@ -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<List<Result>> TopLevelDirectorySearchBehaviourAsync(
Func<Query, string, CancellationToken, Task<List<Result>>> windowsIndexSearch,
Func<Query, string, CancellationToken, List<Result>> 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)
{

View file

@ -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<List<SearchResult>> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, CancellationToken token)
private static async IAsyncEnumerable<SearchResult> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, [EnumeratorCancellation] CancellationToken token)
{
var results = new List<SearchResult>();
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<List<SearchResult>> WindowsIndexSearchAsync(
string searchString,
internal static IAsyncEnumerable<SearchResult> WindowsIndexSearchAsync(string searchString,
Func<CSearchQueryHelper> createQueryHelper,
Func<string, string> constructQuery,
IEnumerable<AccessLink> 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<SearchResult>();
var constructedQuery = constructQuery(searchString);
return
await ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, token);
return ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, token);
}
private static void RemoveResultsInExclusionList(List<SearchResult> results, IReadOnlyList<AccessLink> exclusionList, CancellationToken token)

View file

@ -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<List<SearchResult>> WindowsIndexFileContentSearchAsync(string querySearchString,
private IAsyncEnumerable<SearchResult> WindowsIndexFileContentSearchAsync(string querySearchString,
CancellationToken token)
{
if (string.IsNullOrEmpty(querySearchString))
return new List<SearchResult>();
return AsyncEnumerable.Empty<SearchResult>();
return await WindowsIndex.WindowsIndexSearchAsync(
return WindowsIndex.WindowsIndexSearchAsync(
querySearchString,
QueryConstructor.CreateQueryHelper,
QueryConstructor.QueryForFileContentSearch,
Settings.IndexSearchExcludedSubdirectoryPaths,
token).ConfigureAwait(false);
token);
}
private async Task<List<SearchResult>> WindowsIndexFilesAndFoldersSearchAsync(string querySearchString,
private IAsyncEnumerable<SearchResult> 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<List<SearchResult>> WindowsIndexTopLevelFolderSearchAsync(string path,string search,
private IAsyncEnumerable<SearchResult> 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<IEnumerable<SearchResult>> SearchAsync(string search, CancellationToken token)
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token)
{
return await WindowsIndexFilesAndFoldersSearchAsync(search, token);
return WindowsIndexFilesAndFoldersSearchAsync(search, token);
}
public async ValueTask<IEnumerable<SearchResult>> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token)
public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token)
{
return await WindowsIndexFileContentSearchAsync(contentSearch, token);
return WindowsIndexFileContentSearchAsync(contentSearch, token);
}
public async ValueTask<IEnumerable<SearchResult>> EnumerateAsync(string path, string search, bool recursive, CancellationToken token)
public IAsyncEnumerable<SearchResult> 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);
}
}
}