mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #1657 from Flow-Launcher/fix_win_index_search
Add exception handling when Windows Search service not available
This commit is contained in:
commit
314cd0aa3d
5 changed files with 138 additions and 98 deletions
|
|
@ -116,7 +116,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
{
|
||||
// Given
|
||||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
var baseQuery = queryConstructor.BaseQueryHelper;
|
||||
var baseQuery = queryConstructor.CreateBaseQuery();
|
||||
|
||||
// system running this test could have different locale than the hard-coded 1033 LCID en-US.
|
||||
var queryKeywordLocale = baseQuery.QueryKeywordLocale;
|
||||
|
|
|
|||
|
|
@ -62,46 +62,57 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
return new List<Result>();
|
||||
}
|
||||
|
||||
IAsyncEnumerable<SearchResult> searchResults = null;
|
||||
IAsyncEnumerable<SearchResult> searchResults;
|
||||
|
||||
bool isPathSearch = query.Search.IsLocationPathString();
|
||||
bool isPathSearch = query.Search.IsLocationPathString() || IsEnvironmentVariableSearch(query.Search);
|
||||
|
||||
string engineName;
|
||||
|
||||
switch (isPathSearch)
|
||||
{
|
||||
case true
|
||||
when ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword)
|
||||
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
|
||||
|
||||
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
|
||||
|
||||
results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false));
|
||||
|
||||
|
||||
return results.ToList();
|
||||
|
||||
case false
|
||||
case false
|
||||
when ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword):
|
||||
|
||||
|
||||
// Intentionally require enabling of Everything's content search due to its slowness
|
||||
if (Settings.ContentIndexProvider is EverythingSearchManager && !Settings.EnableEverythingContentSearch)
|
||||
return EverythingContentSearchResult(query);
|
||||
|
||||
|
||||
searchResults = Settings.ContentIndexProvider.ContentSearchAsync("", query.Search, token);
|
||||
|
||||
engineName = Enum.GetName(Settings.ContentSearchEngine);
|
||||
break;
|
||||
|
||||
|
||||
case false
|
||||
when ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword)
|
||||
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
|
||||
|
||||
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
|
||||
|
||||
searchResults = Settings.IndexProvider.SearchAsync(query.Search, token);
|
||||
|
||||
engineName = Enum.GetName(Settings.IndexSearchEngine);
|
||||
break;
|
||||
default:
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
if (searchResults == null)
|
||||
return results.ToList();
|
||||
|
||||
await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false))
|
||||
results.Add(ResultManager.CreateResult(query, search));
|
||||
try
|
||||
{
|
||||
await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false))
|
||||
results.Add(ResultManager.CreateResult(query, search));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is OperationCanceledException)
|
||||
return results.ToList();
|
||||
|
||||
throw new SearchException(engineName, e.Message, e);
|
||||
}
|
||||
|
||||
results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any(
|
||||
excludedPath => r.SubTitle.StartsWith(excludedPath.Path, StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
|
|
@ -175,46 +186,47 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
var retrievedDirectoryPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath);
|
||||
|
||||
results.Add(retrievedDirectoryPath.EndsWith(":\\")
|
||||
results.Add(retrievedDirectoryPath.EndsWith(":\\")
|
||||
? ResultManager.CreateDriveSpaceDisplayResult(retrievedDirectoryPath, query.ActionKeyword, useIndexSearch)
|
||||
: ResultManager.CreateOpenCurrentFolderResult(retrievedDirectoryPath, query.ActionKeyword, useIndexSearch));
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return new List<Result>();
|
||||
|
||||
IEnumerable<SearchResult> directoryResult;
|
||||
IAsyncEnumerable<SearchResult> directoryResult;
|
||||
|
||||
var recursiveIndicatorIndex = query.Search.IndexOf('>');
|
||||
|
||||
if (recursiveIndicatorIndex > 0 && Settings.PathEnumerationEngine != Settings.PathEnumerationEngineOption.DirectEnumeration)
|
||||
{
|
||||
directoryResult =
|
||||
await Settings.PathEnumerator.EnumerateAsync(
|
||||
query.Search[..recursiveIndicatorIndex],
|
||||
query.Search[(recursiveIndicatorIndex + 1)..],
|
||||
true,
|
||||
token)
|
||||
.ToListAsync(cancellationToken: token)
|
||||
.ConfigureAwait(false);
|
||||
Settings.PathEnumerator.EnumerateAsync(
|
||||
query.Search[..recursiveIndicatorIndex],
|
||||
query.Search[(recursiveIndicatorIndex + 1)..],
|
||||
true,
|
||||
token);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
directoryResult = DirectoryInfoSearch.TopLevelDirectorySearch(query, query.Search, token);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new SearchException("DirectoryInfoSearch", e.Message, e);
|
||||
}
|
||||
directoryResult = DirectoryInfoSearch.TopLevelDirectorySearch(query, query.Search, token).ToAsyncEnumerable();
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return new List<Result>();
|
||||
|
||||
try
|
||||
{
|
||||
await foreach (var directory in directoryResult.WithCancellation(token).ConfigureAwait(false))
|
||||
{
|
||||
results.Add(ResultManager.CreateResult(query, directory));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new SearchException(Enum.GetName(Settings.PathEnumerationEngine), e.Message, e);
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
results.UnionWith(directoryResult.Select(searchResult => ResultManager.CreateResult(query, searchResult)));
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
|
|
@ -227,8 +239,15 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
var pathToDirectory = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath);
|
||||
|
||||
return !Settings.IndexSearchExcludedSubdirectoryPaths.Any(
|
||||
x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))
|
||||
x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))
|
||||
&& WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory);
|
||||
}
|
||||
|
||||
internal static bool IsEnvironmentVariableSearch(string search)
|
||||
{
|
||||
return search.StartsWith("%")
|
||||
&& search != "%%"
|
||||
&& !search.Contains('\\');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using Microsoft.Search.Interop;
|
||||
|
||||
|
|
@ -6,25 +6,21 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
{
|
||||
public class QueryConstructor
|
||||
{
|
||||
private Settings Settings { get; }
|
||||
private Settings settings { get; }
|
||||
|
||||
private const string SystemIndex = "SystemIndex";
|
||||
|
||||
public CSearchQueryHelper BaseQueryHelper { get; }
|
||||
|
||||
public QueryConstructor(Settings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
BaseQueryHelper = CreateBaseQuery();
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
|
||||
private CSearchQueryHelper CreateBaseQuery()
|
||||
public CSearchQueryHelper CreateBaseQuery()
|
||||
{
|
||||
var baseQuery = CreateQueryHelper();
|
||||
|
||||
// Set the number of results we want. Don't set this property if all results are needed.
|
||||
baseQuery.QueryMaxResults = Settings.MaxResult;
|
||||
baseQuery.QueryMaxResults = settings.MaxResult;
|
||||
|
||||
// Set list of columns we want to display, getting the path presently
|
||||
baseQuery.QuerySelectColumns = "System.FileName, System.ItemUrl, System.ItemType";
|
||||
|
|
@ -38,9 +34,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
return baseQuery;
|
||||
}
|
||||
|
||||
internal static CSearchQueryHelper CreateQueryHelper()
|
||||
internal CSearchQueryHelper CreateQueryHelper()
|
||||
{
|
||||
// This uses the Microsoft.Search.Interop assembly
|
||||
// Throws COMException if Windows Search service is not running/disabled, this needs to be caught
|
||||
var manager = new CSearchManager();
|
||||
|
||||
// SystemIndex catalog is the default catalog in Windows
|
||||
|
|
@ -67,7 +64,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
? RecursiveDirectoryConstraint(path)
|
||||
: TopLevelDirectoryConstraint(path);
|
||||
|
||||
var query = $"SELECT TOP {Settings.MaxResult} {BaseQueryHelper.QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {FileName}";
|
||||
var query = $"SELECT TOP {settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {FileName}";
|
||||
|
||||
return query;
|
||||
}
|
||||
|
|
@ -81,7 +78,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
userSearchString = "*";
|
||||
|
||||
// Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause
|
||||
return $"{BaseQueryHelper.GenerateSQLFromUserQuery(userSearchString.ToString())} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
|
||||
return $"{CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString.ToString())} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
|
||||
}
|
||||
|
||||
///<summary>
|
||||
|
|
@ -101,7 +98,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
public string FileContent(ReadOnlySpan<char> userSearchString)
|
||||
{
|
||||
string query =
|
||||
$"SELECT TOP {Settings.MaxResult} {BaseQueryHelper.QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
|
||||
$"SELECT TOP {settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
|
||||
|
||||
return query;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Microsoft.Search.Interop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
@ -86,32 +86,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
{
|
||||
throw new SearchException("Windows Index", e.Message, e);
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// Occurs because the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin
|
||||
|
||||
if (!SearchManager.Settings.WarnWindowsSearchServiceOff)
|
||||
return AsyncEnumerable.Empty<SearchResult>();
|
||||
|
||||
var api = SearchManager.Context.API;
|
||||
|
||||
throw new EngineNotAvailableException(
|
||||
"Windows Index",
|
||||
api.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
|
||||
api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
|
||||
c =>
|
||||
{
|
||||
SearchManager.Settings.WarnWindowsSearchServiceOff = false;
|
||||
|
||||
// Clears the warning message so user is not mistaken that it has not worked
|
||||
api.ChangeQuery(string.Empty);
|
||||
|
||||
return ValueTask.FromResult(false);
|
||||
})
|
||||
{
|
||||
ErrorIcon = Constants.WindowsIndexErrorImagePath
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool PathIsIndexed(string path)
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Plugin.Explorer.Exceptions;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.IProvider;
|
||||
using Microsoft.Search.Interop;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
||||
{
|
||||
public class WindowsIndexSearchManager : IIndexProvider, IContentIndexProvider, IPathIndexProvider
|
||||
{
|
||||
private Settings Settings { get; }
|
||||
private QueryConstructor QueryConstructor { get; }
|
||||
|
||||
private CSearchQueryHelper QueryHelper { get; }
|
||||
private QueryConstructor QueryConstructor { get; }
|
||||
|
||||
public WindowsIndexSearchManager(Settings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
QueryConstructor = new QueryConstructor(Settings);
|
||||
QueryHelper = QueryConstructor.CreateQueryHelper();
|
||||
}
|
||||
|
||||
private IAsyncEnumerable<SearchResult> WindowsIndexFileContentSearchAsync(
|
||||
|
|
@ -28,20 +28,38 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
if (querySearchString.IsEmpty)
|
||||
return AsyncEnumerable.Empty<SearchResult>();
|
||||
|
||||
return WindowsIndex.WindowsIndexSearchAsync(
|
||||
QueryHelper.ConnectionString,
|
||||
QueryConstructor.FileContent(querySearchString),
|
||||
token);
|
||||
try
|
||||
{
|
||||
return WindowsIndex.WindowsIndexSearchAsync(
|
||||
QueryConstructor.CreateQueryHelper().ConnectionString,
|
||||
QueryConstructor.FileContent(querySearchString),
|
||||
token);
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// Occurs when the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin
|
||||
// Thrown by QueryConstructor.CreateQueryHelper()
|
||||
return HandledEngineNotAvailableExceptionAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private IAsyncEnumerable<SearchResult> WindowsIndexFilesAndFoldersSearchAsync(
|
||||
ReadOnlySpan<char> querySearchString,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return WindowsIndex.WindowsIndexSearchAsync(
|
||||
QueryHelper.ConnectionString,
|
||||
QueryConstructor.FilesAndFolders(querySearchString),
|
||||
token);
|
||||
try
|
||||
{
|
||||
return WindowsIndex.WindowsIndexSearchAsync(
|
||||
QueryConstructor.CreateQueryHelper().ConnectionString,
|
||||
QueryConstructor.FilesAndFolders(querySearchString),
|
||||
token);
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// Occurs when the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin
|
||||
// Thrown by QueryConstructor.CreateQueryHelper()
|
||||
return HandledEngineNotAvailableExceptionAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private IAsyncEnumerable<SearchResult> WindowsIndexTopLevelFolderSearchAsync(
|
||||
|
|
@ -50,12 +68,19 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
bool recursive,
|
||||
CancellationToken token)
|
||||
{
|
||||
var queryConstructor = new QueryConstructor(Settings);
|
||||
|
||||
return WindowsIndex.WindowsIndexSearchAsync(
|
||||
QueryConstructor.CreateQueryHelper().ConnectionString,
|
||||
queryConstructor.Directory(path, search, recursive),
|
||||
token);
|
||||
try
|
||||
{
|
||||
return WindowsIndex.WindowsIndexSearchAsync(
|
||||
QueryConstructor.CreateQueryHelper().ConnectionString,
|
||||
QueryConstructor.Directory(path, search, recursive),
|
||||
token);
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// Occurs when the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin
|
||||
// Thrown by QueryConstructor.CreateQueryHelper()
|
||||
return HandledEngineNotAvailableExceptionAsync();
|
||||
}
|
||||
}
|
||||
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token)
|
||||
{
|
||||
|
|
@ -69,5 +94,30 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
{
|
||||
return WindowsIndexTopLevelFolderSearchAsync(search, path, recursive, token);
|
||||
}
|
||||
|
||||
private IAsyncEnumerable<SearchResult> HandledEngineNotAvailableExceptionAsync()
|
||||
{
|
||||
if (!SearchManager.Settings.WarnWindowsSearchServiceOff)
|
||||
return AsyncEnumerable.Empty<SearchResult>();
|
||||
|
||||
var api = SearchManager.Context.API;
|
||||
|
||||
throw new EngineNotAvailableException(
|
||||
"Windows Index",
|
||||
api.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
|
||||
api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
|
||||
c =>
|
||||
{
|
||||
SearchManager.Settings.WarnWindowsSearchServiceOff = false;
|
||||
|
||||
// Clears the warning message so user is not mistaken that it has not worked
|
||||
api.ChangeQuery(string.Empty);
|
||||
|
||||
return ValueTask.FromResult(false);
|
||||
})
|
||||
{
|
||||
ErrorIcon = Constants.WindowsIndexErrorImagePath
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue