2021-03-15 20:12:36 +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;
|
2020-05-19 10:10:46 +00:00
|
|
|
|
|
|
|
|
|
|
namespace Flow.Launcher.Plugin.Explorer.Search
|
|
|
|
|
|
{
|
|
|
|
|
|
public class SearchManager
|
|
|
|
|
|
{
|
2021-07-25 07:48:57 +00:00
|
|
|
|
internal static PluginInitContext Context;
|
2020-05-19 12:36:56 +00:00
|
|
|
|
|
2021-07-25 09:58:21 +00:00
|
|
|
|
internal static 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
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-17 22:38:43 +00:00
|
|
|
|
private class PathEqualityComparator : IEqualityComparer<Result>
|
|
|
|
|
|
{
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2022-11-24 22:46:20 +00:00
|
|
|
|
return x.Title == y.Title && x.SubTitle == y.SubTitle;
|
2021-02-17 22:38:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public int GetHashCode(Result obj)
|
|
|
|
|
|
{
|
2022-11-24 22:46:20 +00:00
|
|
|
|
return HashCode.Combine(obj.Title.GetHashCode(), obj.SubTitle?.GetHashCode() ?? 0);
|
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-09-10 15:45:41 +00:00
|
|
|
|
IAsyncEnumerable<SearchResult> searchResults = null;
|
|
|
|
|
|
|
|
|
|
|
|
bool isPathSearch = query.Search.IsLocationPathString();
|
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)
|
|
|
|
|
|
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
|
|
|
|
|
|
|
2022-09-10 15:45:41 +00:00
|
|
|
|
results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false));
|
2022-11-29 09:44:28 +00:00
|
|
|
|
|
2022-09-10 15:45:41 +00:00
|
|
|
|
return results.ToList();
|
2022-11-29 09:44:28 +00:00
|
|
|
|
|
|
|
|
|
|
case false
|
|
|
|
|
|
when ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword):
|
|
|
|
|
|
|
|
|
|
|
|
// 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-11-29 09:44:28 +00:00
|
|
|
|
|
2022-09-10 15:45:41 +00:00
|
|
|
|
searchResults = Settings.ContentIndexProvider.ContentSearchAsync("", query.Search, token);
|
2022-11-29 09:44:28 +00:00
|
|
|
|
|
2022-09-10 15:45:41 +00:00
|
|
|
|
break;
|
2022-11-29 09:44:28 +00:00
|
|
|
|
|
2022-11-29 20:42:59 +00:00
|
|
|
|
case false
|
|
|
|
|
|
when ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword)
|
|
|
|
|
|
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
|
|
|
|
|
|
|
2022-09-10 15:45:41 +00:00
|
|
|
|
searchResults = Settings.IndexProvider.SearchAsync(query.Search, token);
|
2022-11-29 09:44:28 +00:00
|
|
|
|
|
2022-09-10 15:45:41 +00:00
|
|
|
|
break;
|
2021-05-22 09:23:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-16 22:45:36 +00:00
|
|
|
|
if (searchResults == null)
|
|
|
|
|
|
return results.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false))
|
|
|
|
|
|
results.Add(ResultManager.CreateResult(query, search));
|
2021-05-22 09:23:25 +00:00
|
|
|
|
|
2022-11-24 23:06:22 +00:00
|
|
|
|
results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any(
|
|
|
|
|
|
excludedPath => r.SubTitle.StartsWith(excludedPath.Path, StringComparison.OrdinalIgnoreCase)));
|
|
|
|
|
|
|
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")
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static List<Result> EverythingContentSearchResult(Query query)
|
|
|
|
|
|
{
|
|
|
|
|
|
return new List<Result>()
|
|
|
|
|
|
{
|
|
|
|
|
|
new()
|
|
|
|
|
|
{
|
|
|
|
|
|
Title = "Do you want to enable content search for Everything?",
|
2022-12-01 22:04:13 +00:00
|
|
|
|
SubTitle = "It can be very slow without index (which is only supported in Everything v1.5+)",
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
2020-06-20 04:26:13 +00:00
|
|
|
|
var isEnvironmentVariable = EnvironmentVariables.IsEnvironmentVariableSearch(querySearch);
|
2020-05-31 21:53:34 +00:00
|
|
|
|
|
2020-06-20 04:26:13 +00:00
|
|
|
|
if (isEnvironmentVariable)
|
2021-07-25 07:48:57 +00:00
|
|
|
|
return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, Context);
|
2020-05-24 09:09:44 +00:00
|
|
|
|
|
2020-05-26 11:49:59 +00:00
|
|
|
|
// Query is a location path with a full environment variable, eg. %appdata%\somefolder\
|
2021-01-02 14:01:15 +00:00
|
|
|
|
var isEnvironmentVariablePath = querySearch[1..].Contains("%\\");
|
2020-06-20 04:26:13 +00:00
|
|
|
|
|
|
|
|
|
|
var locationPath = querySearch;
|
2022-05-15 20:15:56 +00:00
|
|
|
|
|
2020-06-20 04:26:13 +00:00
|
|
|
|
if (isEnvironmentVariablePath)
|
2020-05-31 21:53:34 +00:00
|
|
|
|
locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath);
|
2020-05-25 23:03:27 +00:00
|
|
|
|
|
2021-01-24 10:08:48 +00:00
|
|
|
|
// Check that actual location exists, otherwise directory search will throw directory not found exception
|
2022-03-28 20:28:18 +00:00
|
|
|
|
if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).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
|
2022-08-16 22:45:36 +00:00
|
|
|
|
&& UseWindowsIndexForDirectorySearch(locationPath);
|
2022-11-04 06:06:43 +00:00
|
|
|
|
|
2022-09-24 19:31:32 +00:00
|
|
|
|
var retrievedDirectoryPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath);
|
2021-01-02 14:01:15 +00:00
|
|
|
|
|
2022-11-29 03:21:05 +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
|
|
|
|
|
2022-03-28 20:28:18 +00:00
|
|
|
|
IEnumerable<SearchResult> directoryResult;
|
|
|
|
|
|
|
2022-05-15 20:15:56 +00:00
|
|
|
|
var recursiveIndicatorIndex = query.Search.IndexOf('>');
|
|
|
|
|
|
|
2022-11-29 03:21:05 +00:00
|
|
|
|
if (recursiveIndicatorIndex > 0 && Settings.PathEnumerationEngine != Settings.PathEnumerationEngineOption.DirectEnumeration)
|
2022-03-28 20:28:18 +00:00
|
|
|
|
{
|
|
|
|
|
|
directoryResult =
|
2022-08-16 22:45:36 +00:00
|
|
|
|
await Settings.PathEnumerator.EnumerateAsync(
|
|
|
|
|
|
query.Search[..recursiveIndicatorIndex],
|
2022-05-15 20:15:56 +00:00
|
|
|
|
query.Search[(recursiveIndicatorIndex + 1)..],
|
|
|
|
|
|
true,
|
2022-09-10 15:45:41 +00:00
|
|
|
|
token)
|
|
|
|
|
|
.ToListAsync(cancellationToken: token)
|
2022-03-28 20:28:18 +00:00
|
|
|
|
.ConfigureAwait(false);
|
2022-05-15 20:15:56 +00:00
|
|
|
|
|
2022-03-28 20:28:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2022-07-04 00:50:02 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
directoryResult = DirectoryInfoSearch.TopLevelDirectorySearch(query, query.Search, token);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
2022-09-22 00:18:20 +00:00
|
|
|
|
throw new SearchException("DirectoryInfoSearch", e.Message, e);
|
2022-07-04 00:50:02 +00:00
|
|
|
|
}
|
2022-03-28 20:28:18 +00:00
|
|
|
|
}
|
2022-05-15 20:15:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
2021-01-20 05:47:25 +00:00
|
|
|
|
|
2021-01-21 11:39:18 +00:00
|
|
|
|
token.ThrowIfCancellationRequested();
|
2021-01-20 05:47:25 +00:00
|
|
|
|
|
2022-03-28 20:28:18 +00:00
|
|
|
|
results.UnionWith(directoryResult.Select(searchResult => ResultManager.CreateResult(query, searchResult)));
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-16 22:45:36 +00:00
|
|
|
|
public static 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(
|
|
|
|
|
|
x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
&& WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory);
|
2020-06-02 10:12:14 +00:00
|
|
|
|
}
|
2020-05-19 10:10:46 +00:00
|
|
|
|
}
|
2022-08-08 04:31:38 +00:00
|
|
|
|
}
|