2024-07-13 12:17:56 +00:00
|
|
|
|
using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo;
|
2022-05-15 20:15:56 +00:00
|
|
|
|
using Flow.Launcher.Plugin.Explorer.Search.Everything;
|
2021-01-26 09:48:06 +00:00
|
|
|
|
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
2020-05-24 10:14:36 +00:00
|
|
|
|
using Flow.Launcher.Plugin.SharedCommands;
|
2020-05-19 12:36:56 +00:00
|
|
|
|
using System;
|
2020-05-19 10:10:46 +00:00
|
|
|
|
using System.Collections.Generic;
|
2020-06-02 10:12:14 +00:00
|
|
|
|
using System.Linq;
|
2021-01-02 14:01:15 +00:00
|
|
|
|
using System.Threading;
|
|
|
|
|
|
using System.Threading.Tasks;
|
2022-09-22 00:18:20 +00:00
|
|
|
|
using Flow.Launcher.Plugin.Explorer.Exceptions;
|
2024-07-16 22:46:01 +00:00
|
|
|
|
using Path = System.IO.Path;
|
2020-05-19 10:10:46 +00:00
|
|
|
|
|
|
|
|
|
|
namespace Flow.Launcher.Plugin.Explorer.Search
|
|
|
|
|
|
{
|
|
|
|
|
|
public class SearchManager
|
|
|
|
|
|
{
|
2023-01-19 06:47:42 +00:00
|
|
|
|
internal PluginInitContext Context;
|
2020-05-19 12:36:56 +00:00
|
|
|
|
|
2023-01-19 06:47:42 +00:00
|
|
|
|
internal Settings Settings;
|
2020-05-24 22:14:21 +00:00
|
|
|
|
|
2020-05-19 12:36:56 +00:00
|
|
|
|
public SearchManager(Settings settings, PluginInitContext context)
|
|
|
|
|
|
{
|
2021-07-25 07:48:57 +00:00
|
|
|
|
Context = context;
|
2021-07-25 09:58:21 +00:00
|
|
|
|
Settings = settings;
|
2020-05-23 06:40:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-19 15:16:12 +00:00
|
|
|
|
/// <summary>
|
2023-01-20 04:13:02 +00:00
|
|
|
|
/// Note: A path that ends with "\" and one that doesn't will not be regarded as equal.
|
2023-01-19 15:16:12 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class PathEqualityComparator : IEqualityComparer<Result>
|
2021-02-17 22:38:43 +00:00
|
|
|
|
{
|
|
|
|
|
|
private static PathEqualityComparator instance;
|
|
|
|
|
|
public static PathEqualityComparator Instance => instance ??= new PathEqualityComparator();
|
2021-06-02 01:10:24 +00:00
|
|
|
|
|
2021-02-17 22:38:43 +00:00
|
|
|
|
public bool Equals(Result x, Result y)
|
|
|
|
|
|
{
|
2023-01-19 15:16:12 +00:00
|
|
|
|
return x.Title.Equals(y.Title, StringComparison.OrdinalIgnoreCase)
|
2023-01-19 08:06:01 +00:00
|
|
|
|
&& string.Equals(x.SubTitle, y.SubTitle, StringComparison.OrdinalIgnoreCase);
|
2021-02-17 22:38:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public int GetHashCode(Result obj)
|
|
|
|
|
|
{
|
2023-01-19 08:06:01 +00:00
|
|
|
|
return HashCode.Combine(obj.Title.ToLowerInvariant(), obj.SubTitle?.ToLowerInvariant() ?? "");
|
2021-02-17 22:38:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-02 14:01:15 +00:00
|
|
|
|
internal async Task<List<Result>> SearchAsync(Query query, CancellationToken token)
|
2020-05-23 06:40:31 +00:00
|
|
|
|
{
|
2021-07-30 10:44:29 +00:00
|
|
|
|
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
|
|
|
|
|
|
if (ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)
|
|
|
|
|
|
|| ActionKeywordMatch(query, Settings.ActionKeyword.QuickAccessActionKeyword)
|
2022-09-24 19:31:32 +00:00
|
|
|
|
|| ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword)
|
2022-11-29 09:33:22 +00:00
|
|
|
|
|| ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword)
|
|
|
|
|
|
|| ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword))
|
2021-07-30 10:44:29 +00:00
|
|
|
|
{
|
2022-11-29 21:21:17 +00:00
|
|
|
|
if (string.IsNullOrEmpty(query.Search) && ActionKeywordMatch(query, Settings.ActionKeyword.QuickAccessActionKeyword))
|
2021-07-30 10:44:29 +00:00
|
|
|
|
return QuickAccess.AccessLinkListAll(query, Settings.QuickAccessLinks);
|
|
|
|
|
|
|
2022-07-04 00:50:02 +00:00
|
|
|
|
var quickAccessLinks = QuickAccess.AccessLinkListMatched(query, Settings.QuickAccessLinks);
|
2021-07-30 10:44:29 +00:00
|
|
|
|
|
2022-07-04 00:50:02 +00:00
|
|
|
|
results.UnionWith(quickAccessLinks);
|
2021-07-30 10:44:29 +00:00
|
|
|
|
}
|
2022-03-25 21:19:00 +00:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2022-09-10 15:45:41 +00:00
|
|
|
|
return new List<Result>();
|
2022-03-25 21:19:00 +00:00
|
|
|
|
}
|
2020-05-24 22:14:21 +00:00
|
|
|
|
|
2022-12-17 16:43:17 +00:00
|
|
|
|
IAsyncEnumerable<SearchResult> searchResults;
|
2022-09-10 15:45:41 +00:00
|
|
|
|
|
2024-07-13 12:15:50 +00:00
|
|
|
|
bool isPathSearch = query.Search.IsLocationPathString()
|
|
|
|
|
|
|| EnvironmentVariables.IsEnvironmentVariableSearch(query.Search)
|
|
|
|
|
|
|| EnvironmentVariables.HasEnvironmentVar(query.Search);
|
2022-12-17 16:43:17 +00:00
|
|
|
|
|
2022-12-17 16:50:23 +00:00
|
|
|
|
string engineName;
|
2022-08-16 22:45:36 +00:00
|
|
|
|
|
2022-09-10 15:45:41 +00:00
|
|
|
|
switch (isPathSearch)
|
2021-05-22 09:23:25 +00:00
|
|
|
|
{
|
2022-09-22 00:18:20 +00:00
|
|
|
|
case true
|
2022-11-29 09:44:28 +00:00
|
|
|
|
when ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword)
|
2022-12-17 16:43:17 +00:00
|
|
|
|
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
|
|
|
|
|
|
|
2022-09-10 15:45:41 +00:00
|
|
|
|
results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false));
|
2022-12-17 16:43:17 +00:00
|
|
|
|
|
2022-09-10 15:45:41 +00:00
|
|
|
|
return results.ToList();
|
2022-11-29 09:44:28 +00:00
|
|
|
|
|
2022-12-17 16:43:17 +00:00
|
|
|
|
case false
|
2022-11-29 09:44:28 +00:00
|
|
|
|
when ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword):
|
2022-12-17 16:43:17 +00:00
|
|
|
|
|
2022-11-29 09:44:28 +00:00
|
|
|
|
// Intentionally require enabling of Everything's content search due to its slowness
|
2022-09-10 15:45:41 +00:00
|
|
|
|
if (Settings.ContentIndexProvider is EverythingSearchManager && !Settings.EnableEverythingContentSearch)
|
|
|
|
|
|
return EverythingContentSearchResult(query);
|
2022-12-17 16:43:17 +00:00
|
|
|
|
|
2022-09-10 15:45:41 +00:00
|
|
|
|
searchResults = Settings.ContentIndexProvider.ContentSearchAsync("", query.Search, token);
|
2022-12-17 16:50:23 +00:00
|
|
|
|
engineName = Enum.GetName(Settings.ContentSearchEngine);
|
2022-09-10 15:45:41 +00:00
|
|
|
|
break;
|
2022-12-17 16:43:17 +00:00
|
|
|
|
|
2022-11-29 20:42:59 +00:00
|
|
|
|
case false
|
|
|
|
|
|
when ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword)
|
2022-12-17 16:43:17 +00:00
|
|
|
|
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
|
|
|
|
|
|
|
2022-09-10 15:45:41 +00:00
|
|
|
|
searchResults = Settings.IndexProvider.SearchAsync(query.Search, token);
|
2022-12-17 16:50:23 +00:00
|
|
|
|
engineName = Enum.GetName(Settings.IndexSearchEngine);
|
2022-09-10 15:45:41 +00:00
|
|
|
|
break;
|
2022-12-17 16:43:17 +00:00
|
|
|
|
default:
|
|
|
|
|
|
return results.ToList();
|
2021-05-22 09:23:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-17 16:43:17 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false))
|
2024-07-16 22:46:01 +00:00
|
|
|
|
if (search.Type == ResultType.File && IsExcludedFile(search)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
results.Add(ResultManager.CreateResult(query, search));
|
|
|
|
|
|
}
|
2022-12-17 16:43:17 +00:00
|
|
|
|
}
|
2023-01-18 16:37:05 +00:00
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
{
|
2023-01-21 08:57:41 +00:00
|
|
|
|
return new List<Result>();
|
2023-01-18 16:37:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
catch (EngineNotAvailableException)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
2022-12-17 16:43:17 +00:00
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
2022-12-17 16:50:23 +00:00
|
|
|
|
throw new SearchException(engineName, e.Message, e);
|
2022-12-17 16:43:17 +00:00
|
|
|
|
}
|
2023-01-19 08:06:01 +00:00
|
|
|
|
|
2022-11-24 23:06:22 +00:00
|
|
|
|
results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any(
|
2023-09-11 11:16:01 +00:00
|
|
|
|
excludedPath => FilesFolders.PathContains(excludedPath.Path, r.SubTitle, allowEqual: true)));
|
2022-11-24 23:06:22 +00:00
|
|
|
|
|
2021-07-30 10:44:29 +00:00
|
|
|
|
return results.ToList();
|
2021-05-22 09:23:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-30 09:28:18 +00:00
|
|
|
|
private bool ActionKeywordMatch(Query query, Settings.ActionKeyword allowedActionKeyword)
|
2021-05-22 09:23:25 +00:00
|
|
|
|
{
|
2021-06-05 08:44:16 +00:00
|
|
|
|
var keyword = query.ActionKeyword.Length == 0 ? Query.GlobalPluginWildcardSign : query.ActionKeyword;
|
2021-05-29 10:08:34 +00:00
|
|
|
|
|
2021-06-02 01:10:24 +00:00
|
|
|
|
return allowedActionKeyword switch
|
|
|
|
|
|
{
|
2021-07-25 09:58:21 +00:00
|
|
|
|
Settings.ActionKeyword.SearchActionKeyword => Settings.SearchActionKeywordEnabled &&
|
|
|
|
|
|
keyword == Settings.SearchActionKeyword,
|
|
|
|
|
|
Settings.ActionKeyword.PathSearchActionKeyword => Settings.PathSearchKeywordEnabled &&
|
|
|
|
|
|
keyword == Settings.PathSearchActionKeyword,
|
2021-09-01 11:10:52 +00:00
|
|
|
|
Settings.ActionKeyword.FileContentSearchActionKeyword => Settings.FileContentSearchKeywordEnabled &&
|
|
|
|
|
|
keyword == Settings.FileContentSearchActionKeyword,
|
2021-09-01 12:05:19 +00:00
|
|
|
|
Settings.ActionKeyword.IndexSearchActionKeyword => Settings.IndexSearchKeywordEnabled &&
|
2021-09-15 11:15:33 +00:00
|
|
|
|
keyword == Settings.IndexSearchActionKeyword,
|
2021-07-30 10:44:29 +00:00
|
|
|
|
Settings.ActionKeyword.QuickAccessActionKeyword => Settings.QuickAccessKeywordEnabled &&
|
2022-05-15 20:15:56 +00:00
|
|
|
|
keyword == Settings.QuickAccessActionKeyword,
|
|
|
|
|
|
_ => throw new ArgumentOutOfRangeException(nameof(allowedActionKeyword), allowedActionKeyword, "actionKeyword out of range")
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-19 06:47:42 +00:00
|
|
|
|
private List<Result> EverythingContentSearchResult(Query query)
|
2022-05-15 20:15:56 +00:00
|
|
|
|
{
|
|
|
|
|
|
return new List<Result>()
|
|
|
|
|
|
{
|
|
|
|
|
|
new()
|
|
|
|
|
|
{
|
2022-12-21 05:30:49 +00:00
|
|
|
|
Title = Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"),
|
|
|
|
|
|
SubTitle = Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"),
|
2022-12-01 22:04:13 +00:00
|
|
|
|
IcoPath = "Images/index_error.png",
|
2022-05-15 20:15:56 +00:00
|
|
|
|
Action = c =>
|
|
|
|
|
|
{
|
|
|
|
|
|
Settings.EnableEverythingContentSearch = true;
|
|
|
|
|
|
Context.API.ChangeQuery(query.RawQuery, true);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-06-02 01:10:24 +00:00
|
|
|
|
};
|
2021-05-22 09:23:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-16 22:45:36 +00:00
|
|
|
|
private async Task<List<Result>> PathSearchAsync(Query query, CancellationToken token = default)
|
2021-05-22 09:23:25 +00:00
|
|
|
|
{
|
|
|
|
|
|
var querySearch = query.Search;
|
|
|
|
|
|
|
|
|
|
|
|
var results = new HashSet<Result>(PathEqualityComparator.Instance);
|
|
|
|
|
|
|
2023-01-20 07:03:40 +00:00
|
|
|
|
if (EnvironmentVariables.IsEnvironmentVariableSearch(querySearch))
|
2021-07-25 07:48:57 +00:00
|
|
|
|
return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, Context);
|
2020-05-24 09:09:44 +00:00
|
|
|
|
|
2023-01-20 07:03:40 +00:00
|
|
|
|
// Query is a location path with a full environment variable, eg. %appdata%\somefolder\, c:\users\%USERNAME%\downloads
|
|
|
|
|
|
var needToExpand = EnvironmentVariables.HasEnvironmentVar(querySearch);
|
2024-07-13 12:17:56 +00:00
|
|
|
|
var path = needToExpand ? Environment.ExpandEnvironmentVariables(querySearch) : querySearch;
|
2020-05-25 23:03:27 +00:00
|
|
|
|
|
2025-02-07 08:42:01 +00:00
|
|
|
|
// if user uses the unix directory separator, we need to convert it to windows directory separator
|
|
|
|
|
|
path = path.Replace(Constants.UnixDirectorySeparator, Constants.DirectorySeparator);
|
|
|
|
|
|
|
2021-01-24 10:08:48 +00:00
|
|
|
|
// Check that actual location exists, otherwise directory search will throw directory not found exception
|
2024-07-13 12:17:56 +00:00
|
|
|
|
if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path).LocationExists())
|
2021-02-17 22:38:43 +00:00
|
|
|
|
return results.ToList();
|
2020-05-25 23:03:27 +00:00
|
|
|
|
|
2022-09-10 15:45:41 +00:00
|
|
|
|
var useIndexSearch = Settings.IndexSearchEngine is Settings.IndexSearchEngineOption.WindowsIndex
|
2024-07-13 12:17:56 +00:00
|
|
|
|
&& UseWindowsIndexForDirectorySearch(path);
|
2022-11-04 06:06:43 +00:00
|
|
|
|
|
2024-07-13 12:17:56 +00:00
|
|
|
|
var retrievedDirectoryPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path);
|
2021-01-02 14:01:15 +00:00
|
|
|
|
|
2022-12-17 16:43:17 +00:00
|
|
|
|
results.Add(retrievedDirectoryPath.EndsWith(":\\")
|
2022-12-12 03:07:04 +00:00
|
|
|
|
? ResultManager.CreateDriveSpaceDisplayResult(retrievedDirectoryPath, query.ActionKeyword, useIndexSearch)
|
|
|
|
|
|
: ResultManager.CreateOpenCurrentFolderResult(retrievedDirectoryPath, query.ActionKeyword, useIndexSearch));
|
2020-05-23 06:40:31 +00:00
|
|
|
|
|
2022-09-22 00:18:20 +00:00
|
|
|
|
if (token.IsCancellationRequested)
|
|
|
|
|
|
return new List<Result>();
|
2021-01-02 14:01:15 +00:00
|
|
|
|
|
2023-04-06 05:48:31 +00:00
|
|
|
|
IAsyncEnumerable<SearchResult> directoryResult;
|
2022-03-28 20:28:18 +00:00
|
|
|
|
|
2024-07-13 12:17:56 +00:00
|
|
|
|
var recursiveIndicatorIndex = path.IndexOf('>');
|
2022-05-15 20:15:56 +00:00
|
|
|
|
|
2022-11-29 03:21:05 +00:00
|
|
|
|
if (recursiveIndicatorIndex > 0 && Settings.PathEnumerationEngine != Settings.PathEnumerationEngineOption.DirectEnumeration)
|
2022-03-28 20:28:18 +00:00
|
|
|
|
{
|
2023-04-06 05:48:31 +00:00
|
|
|
|
directoryResult =
|
2022-12-17 16:43:17 +00:00
|
|
|
|
Settings.PathEnumerator.EnumerateAsync(
|
2024-07-13 12:17:56 +00:00
|
|
|
|
path[..recursiveIndicatorIndex].Trim(),
|
|
|
|
|
|
path[(recursiveIndicatorIndex + 1)..],
|
2022-12-17 16:43:17 +00:00
|
|
|
|
true,
|
|
|
|
|
|
token);
|
2023-04-06 05:48:31 +00:00
|
|
|
|
|
2022-03-28 20:28:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2024-07-13 12:17:56 +00:00
|
|
|
|
directoryResult = DirectoryInfoSearch.TopLevelDirectorySearch(query, path, token).ToAsyncEnumerable();
|
2022-03-28 20:28:18 +00:00
|
|
|
|
}
|
2022-05-15 20:15:56 +00:00
|
|
|
|
|
2022-12-17 16:43:17 +00:00
|
|
|
|
if (token.IsCancellationRequested)
|
|
|
|
|
|
return new List<Result>();
|
2022-05-15 20:15:56 +00:00
|
|
|
|
|
2022-12-17 16:43:17 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2023-04-06 05:48:31 +00:00
|
|
|
|
await foreach (var directory in directoryResult.WithCancellation(token).ConfigureAwait(false))
|
2022-12-17 16:43:17 +00:00
|
|
|
|
{
|
|
|
|
|
|
results.Add(ResultManager.CreateResult(query, directory));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new SearchException(Enum.GetName(Settings.PathEnumerationEngine), e.Message, e);
|
|
|
|
|
|
}
|
2021-01-20 05:47:25 +00:00
|
|
|
|
|
2021-01-21 11:39:18 +00:00
|
|
|
|
|
2021-02-17 22:38:43 +00:00
|
|
|
|
return results.ToList();
|
2020-05-23 06:40:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-19 06:47:42 +00:00
|
|
|
|
public bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword;
|
2022-09-10 15:45:41 +00:00
|
|
|
|
|
2020-05-19 12:38:42 +00:00
|
|
|
|
|
2020-06-02 10:12:14 +00:00
|
|
|
|
private bool UseWindowsIndexForDirectorySearch(string locationPath)
|
|
|
|
|
|
{
|
2020-08-26 04:10:39 +00:00
|
|
|
|
var pathToDirectory = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath);
|
|
|
|
|
|
|
2022-11-29 03:21:05 +00:00
|
|
|
|
return !Settings.IndexSearchExcludedSubdirectoryPaths.Any(
|
2022-12-17 16:43:17 +00:00
|
|
|
|
x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))
|
2022-11-29 03:21:05 +00:00
|
|
|
|
&& WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory);
|
2020-06-02 10:12:14 +00:00
|
|
|
|
}
|
2024-07-16 22:46:01 +00:00
|
|
|
|
|
|
|
|
|
|
private bool IsExcludedFile(SearchResult result)
|
|
|
|
|
|
{
|
|
|
|
|
|
string[] excludedFileTypes = Settings.ExcludedFileTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
|
string fileExtension = Path.GetExtension(result.FullPath).TrimStart('.');
|
|
|
|
|
|
|
2024-07-17 06:02:09 +00:00
|
|
|
|
return excludedFileTypes.Contains(fileExtension, StringComparer.OrdinalIgnoreCase);
|
2024-07-16 22:46:01 +00:00
|
|
|
|
}
|
2020-05-19 10:10:46 +00:00
|
|
|
|
}
|
2022-08-08 04:31:38 +00:00
|
|
|
|
}
|