mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Code Refactor with IAsyncEnumerable instead of Task<IEnumerable>
This commit is contained in:
parent
86210d0cfb
commit
a3622d472e
12 changed files with 150 additions and 248 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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\\", "*")]
|
||||
|
|
|
|||
|
|
@ -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 += "*";
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue