add exception handling when Windows Search service not available

This commit is contained in:
Jeremy Wu 2022-12-15 22:03:54 +11:00
parent 61aaa0129d
commit 00c229d82c
4 changed files with 80 additions and 59 deletions

View file

@ -116,7 +116,7 @@ namespace Flow.Launcher.Test.Plugins
{ {
// Given // Given
var queryConstructor = new QueryConstructor(new Settings()); 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. // system running this test could have different locale than the hard-coded 1033 LCID en-US.
var queryKeywordLocale = baseQuery.QueryKeywordLocale; var queryKeywordLocale = baseQuery.QueryKeywordLocale;

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Buffers; using System.Buffers;
using Microsoft.Search.Interop; using Microsoft.Search.Interop;
@ -6,25 +6,21 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
{ {
public class QueryConstructor public class QueryConstructor
{ {
private Settings Settings { get; } private Settings settings { get; }
private const string SystemIndex = "SystemIndex"; private const string SystemIndex = "SystemIndex";
public CSearchQueryHelper BaseQueryHelper { get; }
public QueryConstructor(Settings settings) public QueryConstructor(Settings settings)
{ {
Settings = settings; this.settings = settings;
BaseQueryHelper = CreateBaseQuery();
} }
public CSearchQueryHelper CreateBaseQuery()
private CSearchQueryHelper CreateBaseQuery()
{ {
var baseQuery = CreateQueryHelper(); var baseQuery = CreateQueryHelper();
// Set the number of results we want. Don't set this property if all results are needed. // 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 // Set list of columns we want to display, getting the path presently
baseQuery.QuerySelectColumns = "System.FileName, System.ItemUrl, System.ItemType"; baseQuery.QuerySelectColumns = "System.FileName, System.ItemUrl, System.ItemType";
@ -38,9 +34,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
return baseQuery; return baseQuery;
} }
internal static CSearchQueryHelper CreateQueryHelper() internal CSearchQueryHelper CreateQueryHelper()
{ {
// This uses the Microsoft.Search.Interop assembly // 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(); var manager = new CSearchManager();
// SystemIndex catalog is the default catalog in Windows // SystemIndex catalog is the default catalog in Windows
@ -67,7 +64,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
? RecursiveDirectoryConstraint(path) ? RecursiveDirectoryConstraint(path)
: TopLevelDirectoryConstraint(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; return query;
} }
@ -81,7 +78,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
userSearchString = "*"; userSearchString = "*";
// Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause // 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> ///<summary>
@ -101,7 +98,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
public string FileContent(ReadOnlySpan<char> userSearchString) public string FileContent(ReadOnlySpan<char> userSearchString)
{ {
string query = 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; return query;
} }

View file

@ -1,4 +1,4 @@
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Microsoft.Search.Interop; using Microsoft.Search.Interop;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -86,32 +86,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
{ {
throw new SearchException("Windows Index", e.Message, e); 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) internal static bool PathIsIndexed(string path)

View file

@ -1,24 +1,24 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Flow.Launcher.Plugin.Explorer.Exceptions;
using Flow.Launcher.Plugin.Explorer.Search.IProvider; using Flow.Launcher.Plugin.Explorer.Search.IProvider;
using Microsoft.Search.Interop;
namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
{ {
public class WindowsIndexSearchManager : IIndexProvider, IContentIndexProvider, IPathIndexProvider public class WindowsIndexSearchManager : IIndexProvider, IContentIndexProvider, IPathIndexProvider
{ {
private Settings Settings { get; } private Settings Settings { get; }
private QueryConstructor QueryConstructor { get; }
private CSearchQueryHelper QueryHelper { get; } private QueryConstructor QueryConstructor { get; }
public WindowsIndexSearchManager(Settings settings) public WindowsIndexSearchManager(Settings settings)
{ {
Settings = settings; Settings = settings;
QueryConstructor = new QueryConstructor(Settings); QueryConstructor = new QueryConstructor(Settings);
QueryHelper = QueryConstructor.CreateQueryHelper();
} }
private IAsyncEnumerable<SearchResult> WindowsIndexFileContentSearchAsync( private IAsyncEnumerable<SearchResult> WindowsIndexFileContentSearchAsync(
@ -28,20 +28,38 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
if (querySearchString.IsEmpty) if (querySearchString.IsEmpty)
return AsyncEnumerable.Empty<SearchResult>(); return AsyncEnumerable.Empty<SearchResult>();
return WindowsIndex.WindowsIndexSearchAsync( try
QueryHelper.ConnectionString, {
QueryConstructor.FileContent(querySearchString), return WindowsIndex.WindowsIndexSearchAsync(
token); 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( private IAsyncEnumerable<SearchResult> WindowsIndexFilesAndFoldersSearchAsync(
ReadOnlySpan<char> querySearchString, ReadOnlySpan<char> querySearchString,
CancellationToken token = default) CancellationToken token = default)
{ {
return WindowsIndex.WindowsIndexSearchAsync( try
QueryHelper.ConnectionString, {
QueryConstructor.FilesAndFolders(querySearchString), return WindowsIndex.WindowsIndexSearchAsync(
token); 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( private IAsyncEnumerable<SearchResult> WindowsIndexTopLevelFolderSearchAsync(
@ -50,12 +68,19 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
bool recursive, bool recursive,
CancellationToken token) CancellationToken token)
{ {
var queryConstructor = new QueryConstructor(Settings); try
{
return WindowsIndex.WindowsIndexSearchAsync( return WindowsIndex.WindowsIndexSearchAsync(
QueryConstructor.CreateQueryHelper().ConnectionString, QueryConstructor.CreateQueryHelper().ConnectionString,
queryConstructor.Directory(path, search, recursive), QueryConstructor.Directory(path, search, recursive),
token); 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) 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); 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
};
}
} }
} }