From fa0cd35e186fe253a3a79e29a4ef44169f6897aa Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Mon, 28 Mar 2022 15:28:18 -0500 Subject: [PATCH] Support Path Enumeration (Part 1) --- .../Search/Everything/EverythingAPI.cs | 18 ++++++-- .../Everything/EverythingSearchManager.cs | 13 ++++-- .../Search/IContentIndexProvider.cs | 11 +++++ .../Search/IIndexProvider.cs | 11 +++++ .../Search/IPathEnumerable.cs | 7 ++- .../Search/SearchManager.cs | 34 ++++++++------- .../Search/WindowsIndex/IIndexProvider.cs | 13 ------ .../Search/WindowsIndex/WindowsIndex.cs | 37 +++------------- ...anager.cs => WindowsIndexSearchManager.cs} | 27 ++++++------ .../Flow.Launcher.Plugin.Explorer/Settings.cs | 43 ++++++++++++++++--- 10 files changed, 129 insertions(+), 85 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/IIndexProvider.cs delete mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs rename Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/{WindowsIndexManager.cs => WindowsIndexSearchManager.cs} (64%) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs index 838d05dd3..501ce5b22 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs @@ -101,15 +101,20 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything /// /// The key word. /// when cancelled the current search will stop and exit (and would not reset) + /// Search Within a parent folder + /// Search Within sub folder of the parent folder /// Sort By /// The offset. /// The max count. /// - public static IEnumerable SearchAsync(string keyword, CancellationToken token, SortOption sortOption = SortOption.NAME_ASCENDING, int offset = 0, int maxCount = 100) + public static IEnumerable SearchAsync(string keyword, + CancellationToken token, + SortOption sortOption = SortOption.NAME_ASCENDING, + string parentPath = "", + bool recursive = false, + int offset = 0, + int maxCount = 100) { - if (string.IsNullOrEmpty(keyword)) - throw new ArgumentNullException(nameof(keyword)); - if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); @@ -124,6 +129,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything keyword = keyword[1..]; } + if (!string.IsNullOrEmpty(parentPath)) + { + keyword += $" {(recursive ? "" : "parent:")}\"{parentPath}\""; + } + EverythingApiDllImport.Everything_SetSearchW(keyword); EverythingApiDllImport.Everything_SetOffset(offset); EverythingApiDllImport.Everything_SetMax(maxCount); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs index e1486f9fb..14069f678 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace Flow.Launcher.Plugin.Explorer.Search.Everything { - public class EverythingSearchManager : IIndexProvider + public class EverythingSearchManager : IIndexProvider, IContentIndexProvider, IPathEnumerable { private Settings Settings { get; } @@ -15,13 +15,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything } - public ValueTask> SearchAsync(Query query, CancellationToken token) + public ValueTask> SearchAsync(string search, CancellationToken token) { - return ValueTask.FromResult(EverythingApi.SearchAsync(query.Search, token, Settings.SortOption)); + return ValueTask.FromResult(EverythingApi.SearchAsync(search, token, Settings.SortOption)); } - public ValueTask> ContentSearchAsync(Query query, CancellationToken token) + public ValueTask> ContentSearchAsync(string search, CancellationToken token) { return new ValueTask>(new List()); } + public ValueTask> EnumerateAsync(string path, string search, bool recursive, CancellationToken token) + { + return new ValueTask>( + EverythingApi.SearchAsync("", token, Settings.SortOption, path, recursive)); + } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs new file mode 100644 index 000000000..b2142357e --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Explorer.Search +{ + public interface IContentIndexProvider + { + public ValueTask> ContentSearchAsync(string search, CancellationToken token); + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IIndexProvider.cs new file mode 100644 index 000000000..be40b6a24 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IIndexProvider.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Explorer.Search +{ + public interface IIndexProvider + { + public ValueTask> SearchAsync(string search, CancellationToken token); + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs index d7f0a8ec8..eb82422f4 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs @@ -1,9 +1,12 @@ -using System.Collections.Generic; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace Flow.Launcher.Plugin.Explorer.Search { public interface IPathEnumerable { - public IEnumerable Enumerate(string path, bool recursive); + public ValueTask> EnumerateAsync(string path, string search, bool recursive, CancellationToken token); } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 080621f42..93707dcf2 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -61,11 +61,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (IsFileContentSearch(query.ActionKeyword)) { - searchResults = await Settings.IndexProvider.ContentSearchAsync(query, token); + searchResults = await Settings.ContentIndexProvider.ContentSearchAsync(query.Search, token); } else { - searchResults = await Settings.IndexProvider.SearchAsync(query, token); + searchResults = await Settings.IndexProvider.SearchAsync(query.Search, token); } if (ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword) || @@ -120,12 +120,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search var isEnvironmentVariablePath = querySearch[1..].Contains("%\\"); var locationPath = querySearch; - + if (isEnvironmentVariablePath) locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath); // Check that actual location exists, otherwise directory search will throw directory not found exception - if (!FilesFolders.LocationExists(FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath))) + if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).LocationExists()) return results.ToList(); var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath); @@ -134,16 +134,24 @@ namespace Flow.Launcher.Plugin.Explorer.Search token.ThrowIfCancellationRequested(); - // var directoryResult = await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync, - // DirectoryInfoClassSearch, - // useIndexSearch, - // query, - // locationPath, - // token).ConfigureAwait(false); + IEnumerable directoryResult; + + if (query.Search.Contains('>')) + { + directoryResult = + await Settings.PathEnumerator.EnumerateAsync(locationPath, "", false, token) + .ConfigureAwait(false); + } + else + { + directoryResult = DirectoryInfoSearch.TopLevelDirectorySearch(query, query.Search, token); + } + + token.ThrowIfCancellationRequested(); - // results.UnionWith(directoryResult); + results.UnionWith(directoryResult.Select(searchResult => ResultManager.CreateResult(query, searchResult))); return results.ToList(); } @@ -153,10 +161,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search return actionKeyword == Settings.FileContentSearchActionKeyword; } - private List DirectoryInfoClassSearch(Query query, string querySearch, CancellationToken token) - { - return DirectoryInfoSearch.TopLevelDirectorySearch(query, querySearch, token); - } public async Task> TopLevelDirectorySearchBehaviourAsync( Func>> windowsIndexSearch, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs deleted file mode 100644 index e42d78f9a..000000000 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex -{ - public interface IIndexProvider - { - public ValueTask> SearchAsync(Query query, CancellationToken token); - public ValueTask> ContentSearchAsync(Query query, CancellationToken token); - } -} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs index 28e9c0e71..7eb9f06f3 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs @@ -19,7 +19,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex // Reserved keywords in oleDB private const string ReservedStringPattern = @"^[`\@\#\^,\&\/\\\$\%_;\[\]]+$"; - private static async Task> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token) + private static async Task> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, CancellationToken token) { var results = new List(); @@ -82,7 +82,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex Func createQueryHelper, Func constructQuery, List exclusionList, - Query query, CancellationToken token) { var regexMatch = Regex.Match(searchString, ReservedStringPattern); @@ -93,40 +92,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex var constructedQuery = constructQuery(searchString); return - await ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, query, token); + await ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, token); } - private static List RemoveResultsInExclusionList(List results, List exclusionList, CancellationToken token) + private static void RemoveResultsInExclusionList(List results, IReadOnlyList exclusionList, CancellationToken token) { var indexExclusionListCount = exclusionList.Count; if (indexExclusionListCount == 0) - return results; - - var filteredResults = new List(); - - for (var index = 0; index < results.Count; index++) - { - token.ThrowIfCancellationRequested(); - - var excludeResult = false; - - for (var i = 0; i < indexExclusionListCount; i++) - { - token.ThrowIfCancellationRequested(); - - if (results[index].SubTitle.StartsWith(exclusionList[i].Path, StringComparison.OrdinalIgnoreCase)) - { - excludeResult = true; - break; - } - } - - if (!excludeResult) - filteredResults.Add(results[index]); - } - - return filteredResults; + return; + results.RemoveAll(searchResult => + exclusionList.Any(exclude => searchResult.FullPath.StartsWith(exclude.Path, StringComparison.OrdinalIgnoreCase)) + ); } internal static bool PathIsIndexed(string path) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs similarity index 64% rename from Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexManager.cs rename to Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs index 72f5e9cb1..1e64c54f6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs @@ -4,18 +4,18 @@ using System.Threading.Tasks; namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { - public class WindowsIndexManager : IIndexProvider + public class WindowsIndexSearchManager : IIndexProvider, IContentIndexProvider, IPathEnumerable { private Settings Settings { get; } private QueryConstructor QueryConstructor { get; } - public WindowsIndexManager(Settings settings) + public WindowsIndexSearchManager(Settings settings) { Settings = settings; QueryConstructor = new QueryConstructor(Settings); } - private async Task> WindowsIndexFileContentSearchAsync(Query query, string querySearchString, + private async Task> WindowsIndexFileContentSearchAsync(string querySearchString, CancellationToken token) { if (string.IsNullOrEmpty(querySearchString)) @@ -26,13 +26,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex QueryConstructor.CreateQueryHelper, QueryConstructor.QueryForFileContentSearch, Settings.IndexSearchExcludedSubdirectoryPaths, - query, token).ConfigureAwait(false); } - private async Task> WindowsIndexFilesAndFoldersSearchAsync(Query query, string querySearchString, + private async Task> WindowsIndexFilesAndFoldersSearchAsync(string querySearchString, CancellationToken token) { return await WindowsIndex.WindowsIndexSearchAsync( @@ -40,12 +39,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex QueryConstructor.CreateQueryHelper, QueryConstructor.QueryForAllFilesAndFolders, Settings.IndexSearchExcludedSubdirectoryPaths, - query, token).ConfigureAwait(false); } - private async Task> WindowsIndexTopLevelFolderSearchAsync(Query query, string path, + private async Task> WindowsIndexTopLevelFolderSearchAsync(string path,string search, CancellationToken token) { var queryConstructor = new QueryConstructor(Settings); @@ -55,16 +53,21 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex queryConstructor.CreateQueryHelper, queryConstructor.QueryForTopLevelDirectorySearch, Settings.IndexSearchExcludedSubdirectoryPaths, - query, token).ConfigureAwait(false); } - public ValueTask> SearchAsync(Query query, CancellationToken token) + public async ValueTask> SearchAsync(string search, CancellationToken token) { - return default; + return await WindowsIndexFilesAndFoldersSearchAsync(search, token); } - public ValueTask> ContentSearchAsync(Query query, CancellationToken token) + public async ValueTask> ContentSearchAsync(string search, CancellationToken token) { - return default; + return await WindowsIndexFileContentSearchAsync(search, token); + } + public async ValueTask> EnumerateAsync(string path, string search, bool recursive, CancellationToken token) + { + if(recursive) + return await WindowsIndexFilesAndFoldersSearchAsync(search, token).ConfigureAwait(false); + return await WindowsIndexTopLevelFolderSearchAsync(path, search, token); } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index f3ce8abc5..d9d2a788f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -6,6 +6,7 @@ using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; namespace Flow.Launcher.Plugin.Explorer { @@ -44,18 +45,44 @@ namespace Flow.Launcher.Plugin.Explorer public bool WarnWindowsSearchServiceOff { get; set; } = true; - private List _indexProviders; + private IReadOnlyList _indexProviders; + private IReadOnlyList _fileContentIndexProviders; + private IReadOnlyList _pathEnumerables; public Settings() { + var everythingManager = new EverythingSearchManager(this); + var windowsIndexManager = new WindowsIndexSearchManager(this); + _indexProviders = new List() { - new EverythingSearchManager(this), - new WindowsIndexManager(this) + everythingManager, + windowsIndexManager + }; + + _pathEnumerables = new List() + { + everythingManager, + windowsIndexManager + }; + + _fileContentIndexProviders = new List + { + windowsIndexManager, everythingManager }; } + public IndexSearchEngineOption IndexSearchEngine { get; set; } + [JsonIgnore] public IIndexProvider IndexProvider => _indexProviders[(int)IndexSearchEngine]; + + public PathTraversalEngineOption PathEnumerationEngine { get; set; } + [JsonIgnore] + public IPathEnumerable PathEnumerator => _pathEnumerables[(int)PathEnumerationEngine]; + public ContentIndexSearchEngineOption ContentSearchEngine { get; set; } + [JsonIgnore] + public IContentIndexProvider ContentIndexProvider => _fileContentIndexProviders[(int)ContentSearchEngine]; + public enum PathTraversalEngineOption { Everything, @@ -68,11 +95,17 @@ namespace Flow.Launcher.Plugin.Explorer Everything, WindowsIndex } - #region Everything Settings - + + public enum ContentIndexSearchEngineOption + { + Everything, + WindowsIndex + } public bool LaunchHidden { get; set; } = false; + #region Everything Settings + public string EverythingInstalledPath { get; set; } public SortOption[] SortOptions { get; set; } = Enum.GetValues();