Custom Exception & Some Refactor

- Try use ReadOnlySpan<char> instead of String for applicable API
- Use Customized Exception to return error result
This commit is contained in:
Hongtao Zhang 2022-09-21 19:18:20 -05:00
parent d973470465
commit 8b1c125bdf
No known key found for this signature in database
GPG key ID: 75F655B91C7AC9BB
14 changed files with 179 additions and 79 deletions

View file

@ -0,0 +1,31 @@
using System;
using Flow.Launcher.Plugin.Explorer.Search.IProvider;
namespace Flow.Launcher.Plugin.Explorer.Exceptions;
public class EngineNotAvailableException : Exception
{
public string EngineName { get; }
public string Resolution { get; }
public EngineNotAvailableException(string engineName,
string resolution,
string message) : base(message)
{
EngineName = engineName;
Resolution = resolution;
}
public EngineNotAvailableException(string engineName,
string resolution,
string message,
Exception innerException) : base(message, innerException)
{
EngineName = engineName;
Resolution = resolution;
}
public override string ToString()
{
return $"Engine {EngineName} is not available.\n Try to {Resolution}\n {base.ToString()}";
}
}

View file

@ -0,0 +1,23 @@
using System;
namespace Flow.Launcher.Plugin.Explorer.Exceptions
{
public class SearchException : Exception
{
public string EngineName { get; }
public SearchException(string engineName, string message) : base(message)
{
EngineName = engineName;
}
public SearchException(string engineName, string message, Exception innerException) : base(message, innerException)
{
EngineName = engineName;
}
public override string ToString()
{
return $"{EngineName} Search Exception:\n {base.ToString()}";
}
}
}

View file

@ -11,7 +11,9 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Flow.Launcher.Plugin.Explorer.Exceptions;
namespace Flow.Launcher.Plugin.Explorer
{
@ -76,7 +78,31 @@ namespace Flow.Launcher.Plugin.Explorer
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
{
return await searchManager.SearchAsync(query, token);
try
{
return await searchManager.SearchAsync(query, token);
}
catch (Exception e) when (e is SearchException or SearchException)
{
return new List<Result>
{
new()
{
Title = e.Message,
SubTitle = e is EngineNotAvailableException engineException
? engineException.Resolution
: "Enter to copy the message to clipboard",
Score = 501,
IcoPath = Constants.ExplorerIconImagePath,
Action = _ =>
{
Clipboard.SetDataObject(e.ToString());
return true;
}
}
};
}
}
public string GetTranslatedPluginTitle()
@ -89,4 +115,4 @@ namespace Flow.Launcher.Plugin.Explorer
return Context.API.GetTranslation("plugin_explorer_plugin_description");
}
}
}
}

View file

@ -57,26 +57,21 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, enumerationOption))
{
if (fileSystemInfo is System.IO.DirectoryInfo)
results.Add(new SearchResult
{
results.Add(new SearchResult()
FullPath = fileSystemInfo.FullName,
Type = fileSystemInfo switch
{
FullPath = fileSystemInfo.FullName,
Type = ResultType.Folder,
WindowsIndexed = false
});
}
else
{
results.Add(new SearchResult()
{
FullPath = fileSystemInfo.FullName,
Type = ResultType.File,
WindowsIndexed = false
});
}
System.IO.DirectoryInfo {Parent: null} => ResultType.Volume,
System.IO.DirectoryInfo => ResultType.Folder,
FileInfo => ResultType.File,
_ => throw new ArgumentOutOfRangeException(nameof(fileSystemInfo))
},
WindowsIndexed = false
});
token.ThrowIfCancellationRequested();
if (token.IsCancellationRequested)
return results;
}
}
catch (Exception e)

View file

@ -109,23 +109,27 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
try
{
if (option.Keyword.StartsWith("@"))
{
EverythingApiDllImport.Everything_SetRegex(true);
option.Keyword = option.Keyword[1..];
}
var builder = new StringBuilder();
builder.Append(option.Keyword);
if (!string.IsNullOrEmpty(option.ParentPath))
if (!option.ParentPath.IsWhiteSpace())
{
option.Keyword += $" {(option.IsRecursive ? "" : "parent:")}\"{option.ParentPath}\"";
builder.Append($" {(option.IsRecursive ? "" : "parent:")}\"{option.ParentPath}\"");
}
if (option.IsContentSearch)
{
option.Keyword += $" content:\"{option.ContentSearchKeyword}\"";
builder.Append($" content:\"{option.ContentSearchKeyword}\"");
}
EverythingApiDllImport.Everything_SetSearchW(option.Keyword);
EverythingApiDllImport.Everything_SetSearchW(builder.ToString());
EverythingApiDllImport.Everything_SetOffset(option.Offset);
EverythingApiDllImport.Everything_SetMax(option.MaxCount);
@ -133,8 +137,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
if (token.IsCancellationRequested) yield break;
if (!EverythingApiDllImport.Everything_QueryW(true))
{
CheckAndThrowExceptionOnError();

View file

@ -1,4 +1,5 @@
using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex;
using System;
using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@ -17,14 +18,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
}
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token)
public IAsyncEnumerable<SearchResult> SearchAsync(ReadOnlySpan<char> search, CancellationToken token)
{
return EverythingApi.SearchAsync(
new EverythingSearchOption(search, Settings.SortOption),
token);
}
public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch,
string contentSearch, CancellationToken token)
public IAsyncEnumerable<SearchResult> ContentSearchAsync(ReadOnlySpan<char> plainSearch,
ReadOnlySpan<char> contentSearch, CancellationToken token)
{
if (!Settings.EnableEverythingContentSearch)
{
@ -39,7 +40,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
contentSearch),
token);
}
public IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, CancellationToken token)
public IAsyncEnumerable<SearchResult> EnumerateAsync(ReadOnlySpan<char> path, ReadOnlySpan<char> search, bool recursive, CancellationToken token)
{
return EverythingApi.SearchAsync(
new EverythingSearchOption(search,

View file

@ -1,13 +1,14 @@
using Flow.Launcher.Plugin.Everything.Everything;
using System;
using Flow.Launcher.Plugin.Everything.Everything;
namespace Flow.Launcher.Plugin.Explorer.Search.Everything
{
public record struct EverythingSearchOption(string Keyword,
public record struct EverythingSearchOption(ReadOnlySpan<char> Keyword,
SortOption SortOption,
bool IsContentSearch = false,
string ContentSearchKeyword = "",
string ParentPath = "",
ReadOnlySpan<char> ContentSearchKeyword = default,
ReadOnlySpan<char> ParentPath = default,
bool IsRecursive = true,
int Offset = 0,
int MaxCount = 100);
}
}

View file

@ -1,10 +1,11 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Flow.Launcher.Plugin.Explorer.Search.IProvider
{
public interface IContentIndexProvider
{
public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token = default);
public IAsyncEnumerable<SearchResult> ContentSearchAsync(ReadOnlySpan<char> plainSearch, ReadOnlySpan<char> contentSearch, CancellationToken token = default);
}
}

View file

@ -1,10 +1,11 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Flow.Launcher.Plugin.Explorer.Search.IProvider
{
public interface IIndexProvider
{
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token);
public IAsyncEnumerable<SearchResult> SearchAsync(ReadOnlySpan<char> search, CancellationToken token);
}
}

View file

@ -1,10 +1,11 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Flow.Launcher.Plugin.Explorer.Search.IProvider
{
public interface IPathIndexProvider
{
public IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, CancellationToken token);
public IAsyncEnumerable<SearchResult> EnumerateAsync(ReadOnlySpan<char> path, ReadOnlySpan<char> search, bool recursive, CancellationToken token);
}
}

View file

@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Flow.Launcher.Plugin.Explorer.Exceptions;
namespace Flow.Launcher.Plugin.Explorer.Search
{
@ -66,9 +67,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
switch (isPathSearch)
{
case true
when (ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword)
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)):
case true
when (ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword)
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)):
results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false));
return results.ToList();
break;
@ -168,7 +169,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
results.Add(ResultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch));
token.ThrowIfCancellationRequested();
if (token.IsCancellationRequested)
return new List<Result>();
IEnumerable<SearchResult> directoryResult;
@ -194,16 +196,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
}
catch (Exception e)
{
results.Add(
new Result
{
Title = string.Format(SearchManager.Context.API.GetTranslation(
"plugin_explorer_directoryinfosearch_error"),
e.Message),
Score = 501,
IcoPath = Constants.ExplorerIconImagePath
});
directoryResult = Enumerable.Empty<SearchResult>();
throw new SearchException("DirectoryInfoSearch", e.Message, e);
}
}

View file

@ -1,3 +1,5 @@
using System;
using System.Buffers;
using Microsoft.Search.Interop;
namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
@ -50,25 +52,29 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
return queryHelper;
}
private static string TopLevelDirectoryConstraint(string path) => $"directory='file:{path}'";
private static string RecursiveDirectoryConstraint(string path) => $"scope='file:{path}'";
private static string TopLevelDirectoryConstraint(ReadOnlySpan<char> path) => $"directory='file:{path}'";
private static string RecursiveDirectoryConstraint(ReadOnlySpan<char> path) => $"scope='file:{path}'";
///<summary>
/// Set the required WHERE clause restriction to search on the first level of a specified directory.
///</summary>
[Obsolete("This method is not used and will be removed in a future version.")]
public string QueryWhereRestrictionsForTopLevelDirectorySearch(string path)
{
return QueryWhereRestrictionsFromLocationPath(path, "directory='file:");
}
///<summary>
/// Set the required WHERE clause restriction to search all files and subfolders of a specified directory.
///</summary>
[Obsolete("This method is not used and will be removed in a future version.")]
public string QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(string path)
{
return QueryWhereRestrictionsFromLocationPath(path, "directory='scope:");
}
// TODO: Remove the method
private string QueryWhereRestrictionsFromLocationPath(string path, string searchDepth)
{
if (path.EndsWith(Constants.DirectorySeperator))
@ -92,15 +98,15 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
///<summary>
/// Search will be performed on all folders and files on the first level of a specified directory.
///</summary>
public string Directory(string path, string searchString = "", bool recursive = false)
public string Directory(ReadOnlySpan<char> path, ReadOnlySpan<char> searchString = default, bool recursive = false)
{
var queryConstraint = searchString is "" ? "" : $"AND ({FileName} LIKE '{searchString}%' OR CONTAINS({FileName},'\"{searchString}*\"'))";
var queryConstraint = searchString.IsWhiteSpace() ? "" : $"AND ({FileName} LIKE '{searchString}%' OR CONTAINS({FileName},'\"{searchString}*\"'))";
var scopeConstraint = recursive
? RecursiveDirectoryConstraint(path)
: TopLevelDirectoryConstraint(path);
string query = $"SELECT TOP {Settings.MaxResult} {BaseQueryHelper.QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {FileName}";
var query = $"SELECT TOP {Settings.MaxResult} {BaseQueryHelper.QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {FileName}";
return query;
}
@ -108,13 +114,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
///<summary>
/// Search will be performed on all folders and files based on user's search keywords.
///</summary>
public string FilesAndFolders(string userSearchString)
public string FilesAndFolders(ReadOnlySpan<char> userSearchString)
{
if (string.IsNullOrEmpty(userSearchString))
if (userSearchString.IsWhiteSpace())
userSearchString = "*";
// Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause
return $"{BaseQueryHelper.GenerateSQLFromUserQuery(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
return $"{BaseQueryHelper.GenerateSQLFromUserQuery(userSearchString.ToString())} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
}
///<summary>
@ -131,7 +137,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
///<summary>
/// Search will be performed on all indexed file contents for the specified search keywords.
///</summary>
public string FileContent(string userSearchString)
public string FileContent(ReadOnlySpan<char> userSearchString)
{
string query =
$"SELECT TOP {Settings.MaxResult} {BaseQueryHelper.QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
@ -142,6 +148,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
///<summary>
/// Set the required WHERE clause restriction to search within file content.
///</summary>
public static string RestrictionsForFileContentSearch(string searchQuery) => $"FREETEXT('{searchQuery}')";
public static string RestrictionsForFileContentSearch(ReadOnlySpan<char> searchQuery) => $"FREETEXT('{searchQuery}')";
}
}

View file

@ -11,6 +11,7 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Flow.Launcher.Plugin.Explorer.Exceptions;
namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
{
@ -77,9 +78,26 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
string search,
CancellationToken token)
{
return _reservedPatternMatcher.IsMatch(search)
? AsyncEnumerable.Empty<SearchResult>()
: ExecuteWindowsIndexSearchAsync(search, connectionString, token);
try
{
return _reservedPatternMatcher.IsMatch(search)
? AsyncEnumerable.Empty<SearchResult>()
: ExecuteWindowsIndexSearchAsync(search, connectionString, token);
}
catch (InvalidOperationException e)
{
throw new SearchException("Windows Index", e.Message, e);
}
catch (COMException e)
{
var api = SearchManager.Context.API;
throw new EngineNotAvailableException("Windows Index",
api.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
e);
}
}
// TODO: Move to General Search Manager
@ -138,7 +156,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
MessageBoxButton.YesNo) == MessageBoxResult.Yes
&& actionKeywordCount == 1)
{
api.ChangeQuery(string.Format("{0} install everything", pluginsManagerPlugin.Metadata.ActionKeywords[0]));
api.ChangeQuery($"{pluginsManagerPlugin.Metadata.ActionKeywords[0]} install everything");
}
else
{
@ -148,7 +166,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
api.ChangeQuery(rawQuery);
}
var mainWindow = Application.Current.MainWindow;
var mainWindow = Application.Current.MainWindow!;
mainWindow.Show();
mainWindow.Focus();

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -20,10 +21,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
QueryHelper = QueryConstructor.CreateQueryHelper();
}
private IAsyncEnumerable<SearchResult> WindowsIndexFileContentSearchAsync(string querySearchString,
private IAsyncEnumerable<SearchResult> WindowsIndexFileContentSearchAsync(ReadOnlySpan<char> querySearchString,
CancellationToken token)
{
if (string.IsNullOrEmpty(querySearchString))
if (querySearchString.IsEmpty)
return AsyncEnumerable.Empty<SearchResult>();
return WindowsIndex.WindowsIndexSearchAsync(
@ -32,7 +33,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
token);
}
private IAsyncEnumerable<SearchResult> WindowsIndexFilesAndFoldersSearchAsync(string querySearchString,
private IAsyncEnumerable<SearchResult> WindowsIndexFilesAndFoldersSearchAsync(ReadOnlySpan<char> querySearchString,
CancellationToken token = default)
{
return WindowsIndex.WindowsIndexSearchAsync(
@ -41,8 +42,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
token);
}
private IAsyncEnumerable<SearchResult> WindowsIndexTopLevelFolderSearchAsync(string search,
string path,
private IAsyncEnumerable<SearchResult> WindowsIndexTopLevelFolderSearchAsync(ReadOnlySpan<char> search,
ReadOnlySpan<char> path,
bool recursive,
CancellationToken token)
{
@ -53,15 +54,15 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
queryConstructor.Directory(path, search, recursive),
token);
}
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token)
public IAsyncEnumerable<SearchResult> SearchAsync(ReadOnlySpan<char> search, CancellationToken token)
{
return WindowsIndexFilesAndFoldersSearchAsync(search, token: token);
}
public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token)
public IAsyncEnumerable<SearchResult> ContentSearchAsync(ReadOnlySpan<char> plainSearch, ReadOnlySpan<char> contentSearch, CancellationToken token)
{
return WindowsIndexFileContentSearchAsync(contentSearch, token);
}
public IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, CancellationToken token)
public IAsyncEnumerable<SearchResult> EnumerateAsync(ReadOnlySpan<char> path, ReadOnlySpan<char> search, bool recursive, CancellationToken token)
{
return WindowsIndexTopLevelFolderSearchAsync(search, path, recursive, token);
}