Skip to content

Commit 314cd0a

Browse files
authored
Merge pull request #1657 from Flow-Launcher/fix_win_index_search
Add exception handling when Windows Search service not available
2 parents 09e72e3 + 42725f2 commit 314cd0a

File tree

5 files changed

+138
-98
lines changed

5 files changed

+138
-98
lines changed

Flow.Launcher.Test/Plugins/ExplorerTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShould
116116
{
117117
// Given
118118
var queryConstructor = new QueryConstructor(new Settings());
119-
var baseQuery = queryConstructor.BaseQueryHelper;
119+
var baseQuery = queryConstructor.CreateBaseQuery();
120120

121121
// system running this test could have different locale than the hard-coded 1033 LCID en-US.
122122
var queryKeywordLocale = baseQuery.QueryKeywordLocale;

Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs

Lines changed: 58 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -62,46 +62,57 @@ internal async Task<List<Result>> SearchAsync(Query query, CancellationToken tok
6262
return new List<Result>();
6363
}
6464

65-
IAsyncEnumerable<SearchResult> searchResults = null;
65+
IAsyncEnumerable<SearchResult> searchResults;
6666

67-
bool isPathSearch = query.Search.IsLocationPathString();
67+
bool isPathSearch = query.Search.IsLocationPathString() || IsEnvironmentVariableSearch(query.Search);
68+
69+
string engineName;
6870

6971
switch (isPathSearch)
7072
{
7173
case true
7274
when ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword)
73-
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
74-
75+
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
76+
7577
results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false));
76-
78+
7779
return results.ToList();
7880

79-
case false
81+
case false
8082
when ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword):
81-
83+
8284
// Intentionally require enabling of Everything's content search due to its slowness
8385
if (Settings.ContentIndexProvider is EverythingSearchManager && !Settings.EnableEverythingContentSearch)
8486
return EverythingContentSearchResult(query);
85-
87+
8688
searchResults = Settings.ContentIndexProvider.ContentSearchAsync("", query.Search, token);
87-
89+
engineName = Enum.GetName(Settings.ContentSearchEngine);
8890
break;
89-
91+
9092
case false
9193
when ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword)
92-
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
93-
94+
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
95+
9496
searchResults = Settings.IndexProvider.SearchAsync(query.Search, token);
95-
97+
engineName = Enum.GetName(Settings.IndexSearchEngine);
9698
break;
99+
default:
100+
return results.ToList();
97101
}
98102

99-
if (searchResults == null)
100-
return results.ToList();
101-
102-
await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false))
103-
results.Add(ResultManager.CreateResult(query, search));
103+
try
104+
{
105+
await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false))
106+
results.Add(ResultManager.CreateResult(query, search));
107+
}
108+
catch (Exception e)
109+
{
110+
if (e is OperationCanceledException)
111+
return results.ToList();
104112

113+
throw new SearchException(engineName, e.Message, e);
114+
}
115+
105116
results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any(
106117
excludedPath => r.SubTitle.StartsWith(excludedPath.Path, StringComparison.OrdinalIgnoreCase)));
107118

@@ -175,46 +186,47 @@ private async Task<List<Result>> PathSearchAsync(Query query, CancellationToken
175186

176187
var retrievedDirectoryPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath);
177188

178-
results.Add(retrievedDirectoryPath.EndsWith(":\\")
189+
results.Add(retrievedDirectoryPath.EndsWith(":\\")
179190
? ResultManager.CreateDriveSpaceDisplayResult(retrievedDirectoryPath, query.ActionKeyword, useIndexSearch)
180191
: ResultManager.CreateOpenCurrentFolderResult(retrievedDirectoryPath, query.ActionKeyword, useIndexSearch));
181192

182193
if (token.IsCancellationRequested)
183194
return new List<Result>();
184195

185-
IEnumerable<SearchResult> directoryResult;
196+
IAsyncEnumerable<SearchResult> directoryResult;
186197

187198
var recursiveIndicatorIndex = query.Search.IndexOf('>');
188199

189200
if (recursiveIndicatorIndex > 0 && Settings.PathEnumerationEngine != Settings.PathEnumerationEngineOption.DirectEnumeration)
190201
{
191202
directoryResult =
192-
await Settings.PathEnumerator.EnumerateAsync(
193-
query.Search[..recursiveIndicatorIndex],
194-
query.Search[(recursiveIndicatorIndex + 1)..],
195-
true,
196-
token)
197-
.ToListAsync(cancellationToken: token)
198-
.ConfigureAwait(false);
203+
Settings.PathEnumerator.EnumerateAsync(
204+
query.Search[..recursiveIndicatorIndex],
205+
query.Search[(recursiveIndicatorIndex + 1)..],
206+
true,
207+
token);
199208

200209
}
201210
else
202211
{
203-
try
204-
{
205-
directoryResult = DirectoryInfoSearch.TopLevelDirectorySearch(query, query.Search, token);
206-
}
207-
catch (Exception e)
208-
{
209-
throw new SearchException("DirectoryInfoSearch", e.Message, e);
210-
}
212+
directoryResult = DirectoryInfoSearch.TopLevelDirectorySearch(query, query.Search, token).ToAsyncEnumerable();
211213
}
212214

215+
if (token.IsCancellationRequested)
216+
return new List<Result>();
213217

218+
try
219+
{
220+
await foreach (var directory in directoryResult.WithCancellation(token).ConfigureAwait(false))
221+
{
222+
results.Add(ResultManager.CreateResult(query, directory));
223+
}
224+
}
225+
catch (Exception e)
226+
{
227+
throw new SearchException(Enum.GetName(Settings.PathEnumerationEngine), e.Message, e);
228+
}
214229

215-
token.ThrowIfCancellationRequested();
216-
217-
results.UnionWith(directoryResult.Select(searchResult => ResultManager.CreateResult(query, searchResult)));
218230

219231
return results.ToList();
220232
}
@@ -227,8 +239,15 @@ private bool UseWindowsIndexForDirectorySearch(string locationPath)
227239
var pathToDirectory = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath);
228240

229241
return !Settings.IndexSearchExcludedSubdirectoryPaths.Any(
230-
x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))
242+
x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))
231243
&& WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory);
232244
}
245+
246+
internal static bool IsEnvironmentVariableSearch(string search)
247+
{
248+
return search.StartsWith("%")
249+
&& search != "%%"
250+
&& !search.Contains('\\');
251+
}
233252
}
234253
}

Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,26 @@
1-
using System;
1+
using System;
22
using System.Buffers;
33
using Microsoft.Search.Interop;
44

55
namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
66
{
77
public class QueryConstructor
88
{
9-
private Settings Settings { get; }
9+
private Settings settings { get; }
1010

1111
private const string SystemIndex = "SystemIndex";
1212

13-
public CSearchQueryHelper BaseQueryHelper { get; }
14-
1513
public QueryConstructor(Settings settings)
1614
{
17-
Settings = settings;
18-
BaseQueryHelper = CreateBaseQuery();
15+
this.settings = settings;
1916
}
2017

21-
22-
private CSearchQueryHelper CreateBaseQuery()
18+
public CSearchQueryHelper CreateBaseQuery()
2319
{
2420
var baseQuery = CreateQueryHelper();
2521

2622
// Set the number of results we want. Don't set this property if all results are needed.
27-
baseQuery.QueryMaxResults = Settings.MaxResult;
23+
baseQuery.QueryMaxResults = settings.MaxResult;
2824

2925
// Set list of columns we want to display, getting the path presently
3026
baseQuery.QuerySelectColumns = "System.FileName, System.ItemUrl, System.ItemType";
@@ -38,9 +34,10 @@ private CSearchQueryHelper CreateBaseQuery()
3834
return baseQuery;
3935
}
4036

41-
internal static CSearchQueryHelper CreateQueryHelper()
37+
internal CSearchQueryHelper CreateQueryHelper()
4238
{
4339
// This uses the Microsoft.Search.Interop assembly
40+
// Throws COMException if Windows Search service is not running/disabled, this needs to be caught
4441
var manager = new CSearchManager();
4542

4643
// SystemIndex catalog is the default catalog in Windows
@@ -67,7 +64,7 @@ public string Directory(ReadOnlySpan<char> path, ReadOnlySpan<char> searchString
6764
? RecursiveDirectoryConstraint(path)
6865
: TopLevelDirectoryConstraint(path);
6966

70-
var query = $"SELECT TOP {Settings.MaxResult} {BaseQueryHelper.QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {FileName}";
67+
var query = $"SELECT TOP {settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {FileName}";
7168

7269
return query;
7370
}
@@ -81,7 +78,7 @@ public string FilesAndFolders(ReadOnlySpan<char> userSearchString)
8178
userSearchString = "*";
8279

8380
// Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause
84-
return $"{BaseQueryHelper.GenerateSQLFromUserQuery(userSearchString.ToString())} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
81+
return $"{CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString.ToString())} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
8582
}
8683

8784
///<summary>
@@ -101,7 +98,7 @@ public string FilesAndFolders(ReadOnlySpan<char> userSearchString)
10198
public string FileContent(ReadOnlySpan<char> userSearchString)
10299
{
103100
string query =
104-
$"SELECT TOP {Settings.MaxResult} {BaseQueryHelper.QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
101+
$"SELECT TOP {settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
105102

106103
return query;
107104
}

Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Flow.Launcher.Infrastructure.Logger;
1+
using Flow.Launcher.Infrastructure.Logger;
22
using Microsoft.Search.Interop;
33
using System;
44
using System.Collections.Generic;
@@ -86,32 +86,6 @@ internal static IAsyncEnumerable<SearchResult> WindowsIndexSearchAsync(
8686
{
8787
throw new SearchException("Windows Index", e.Message, e);
8888
}
89-
catch (COMException)
90-
{
91-
// Occurs because the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin
92-
93-
if (!SearchManager.Settings.WarnWindowsSearchServiceOff)
94-
return AsyncEnumerable.Empty<SearchResult>();
95-
96-
var api = SearchManager.Context.API;
97-
98-
throw new EngineNotAvailableException(
99-
"Windows Index",
100-
api.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
101-
api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
102-
c =>
103-
{
104-
SearchManager.Settings.WarnWindowsSearchServiceOff = false;
105-
106-
// Clears the warning message so user is not mistaken that it has not worked
107-
api.ChangeQuery(string.Empty);
108-
109-
return ValueTask.FromResult(false);
110-
})
111-
{
112-
ErrorIcon = Constants.WindowsIndexErrorImagePath
113-
};
114-
}
11589
}
11690

11791
internal static bool PathIsIndexed(string path)

0 commit comments

Comments
 (0)