diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/Everything64.dll b/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/Everything64.dll new file mode 100644 index 000000000..6d093b793 Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/Everything64.dll differ diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/Everything86.dll b/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/Everything86.dll new file mode 100644 index 000000000..de73b87d1 Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/Everything86.dll differ diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj index b4ab89a36..ae48ac3ee 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -35,6 +35,9 @@ Designer PreserveNewest + + PreserveNewest + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 60208759e..d8879a4f6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -1,9 +1,12 @@ using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin.Explorer.Search; +using Flow.Launcher.Plugin.Explorer.Search.Everything; using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; using Flow.Launcher.Plugin.Explorer.ViewModels; using Flow.Launcher.Plugin.Explorer.Views; +using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -47,7 +50,8 @@ namespace Flow.Launcher.Plugin.Explorer contextMenu = new ContextMenu(Context, Settings, viewModel); searchManager = new SearchManager(Settings, Context); ResultManager.Init(Context, Settings); - + EverythingApiDllImport.Load(Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "EverythingSDK", + Environment.Is64BitProcess ? "Everything64.dll" : "Everything86.dll")); return Task.CompletedTask; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs new file mode 100644 index 000000000..089a0f8ff --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs @@ -0,0 +1,193 @@ +using Flow.Launcher.Plugin.Everything.Everything; +using Flow.Launcher.Plugin.Everything.Everything.Exceptions; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything +{ + + public static class EverythingApi + { + + private const int BufferSize = 4096; + + private static readonly object syncObject = new object(); + // cached buffer to remove redundant allocations. + private static readonly StringBuilder buffer = new StringBuilder(BufferSize); + + public enum StateCode + { + OK, + MemoryError, + IPCError, + RegisterClassExError, + CreateWindowError, + CreateThreadError, + InvalidIndexError, + InvalidCallError + } + + /// + /// Gets or sets a value indicating whether [match path]. + /// + /// true if [match path]; otherwise, false. + public static bool MatchPath + { + get => EverythingApiDllImport.Everything_GetMatchPath(); + set => EverythingApiDllImport.Everything_SetMatchPath(value); + } + + /// + /// Gets or sets a value indicating whether [match case]. + /// + /// true if [match case]; otherwise, false. + public static bool MatchCase + { + get => EverythingApiDllImport.Everything_GetMatchCase(); + set => EverythingApiDllImport.Everything_SetMatchCase(value); + } + + /// + /// Gets or sets a value indicating whether [match whole word]. + /// + /// true if [match whole word]; otherwise, false. + public static bool MatchWholeWord + { + get => EverythingApiDllImport.Everything_GetMatchWholeWord(); + set => EverythingApiDllImport.Everything_SetMatchWholeWord(value); + } + + /// + /// Gets or sets a value indicating whether [enable regex]. + /// + /// true if [enable regex]; otherwise, false. + public static bool EnableRegex + { + get => EverythingApiDllImport.Everything_GetRegex(); + set => EverythingApiDllImport.Everything_SetRegex(value); + } + + /// + /// Resets this instance. + /// + private static void Reset() + { + lock (syncObject) + { + EverythingApiDllImport.Everything_Reset(); + } + } + + /// + /// Checks whether the sort option is Fast Sort. + /// + public static bool IsFastSortOption(SortOption sortOption) + { + var fastSortOptionEnabled = EverythingApiDllImport.Everything_IsFastSort(sortOption); + + // If the Everything service is not running, then this call will incorrectly report + // the state as false. This checks for errors thrown by the api and up to the caller to handle. + CheckAndThrowExceptionOnError(); + + return fastSortOptionEnabled; + } + + /// + /// Searches the specified key word and reset the everything API afterwards + /// + /// The key word. + /// when cancelled the current search will stop and exit (and would not reset) + /// 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) + { + if (string.IsNullOrEmpty(keyword)) + throw new ArgumentNullException(nameof(keyword)); + + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset)); + + if (maxCount < 0) + throw new ArgumentOutOfRangeException(nameof(maxCount)); + + lock (syncObject) + { + if (keyword.StartsWith("@")) + { + EverythingApiDllImport.Everything_SetRegex(true); + keyword = keyword[1..]; + } + + EverythingApiDllImport.Everything_SetSearchW(keyword); + EverythingApiDllImport.Everything_SetOffset(offset); + EverythingApiDllImport.Everything_SetMax(maxCount); + + EverythingApiDllImport.Everything_SetSort(sortOption); + + if (token.IsCancellationRequested) + { + return null; + } + + + if (!EverythingApiDllImport.Everything_QueryW(true)) + { + CheckAndThrowExceptionOnError(); + return null; + } + + var results = new List(); + for (var idx = 0; idx < EverythingApiDllImport.Everything_GetNumResults(); ++idx) + { + if (token.IsCancellationRequested) + { + return null; + } + + EverythingApiDllImport.Everything_GetResultFullPathNameW(idx, buffer, BufferSize); + + var result = new SearchResult + { + FullPath = buffer.ToString(), + Type = EverythingApiDllImport.Everything_IsFolderResult(idx) ? ResultType.Folder : + EverythingApiDllImport.Everything_IsFileResult(idx) ? ResultType.File : + ResultType.Volume + }; + + results.Add(result); + } + + Reset(); + + return results; + } + } + + private static void CheckAndThrowExceptionOnError() + { + switch (EverythingApiDllImport.Everything_GetLastError()) + { + case StateCode.CreateThreadError: + throw new CreateThreadException(); + case StateCode.CreateWindowError: + throw new CreateWindowException(); + case StateCode.InvalidCallError: + throw new InvalidCallException(); + case StateCode.InvalidIndexError: + throw new InvalidIndexException(); + case StateCode.IPCError: + throw new IPCErrorException(); + case StateCode.MemoryError: + throw new MemoryErrorException(); + case StateCode.RegisterClassExError: + throw new RegisterClassExException(); + } + } + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs new file mode 100644 index 000000000..b100a6026 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs @@ -0,0 +1,153 @@ +using Flow.Launcher.Plugin.Everything.Everything; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything +{ + public static class EverythingApiDllImport + { + public static void Load(string path) + { + LoadLibrary(path); + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern int LoadLibrary(string name); + + private const string DLL = "Everything.dll"; + + [DllImport(DLL, CharSet = CharSet.Unicode)] + internal static extern int Everything_SetSearchW(string lpSearchString); + + [DllImport(DLL)] + internal static extern void Everything_SetMatchPath(bool bEnable); + + [DllImport(DLL)] + internal static extern void Everything_SetMatchCase(bool bEnable); + + [DllImport(DLL)] + internal static extern void Everything_SetMatchWholeWord(bool bEnable); + + [DllImport(DLL)] + internal static extern void Everything_SetRegex(bool bEnable); + + [DllImport(DLL)] + internal static extern void Everything_SetMax(int dwMax); + + [DllImport(DLL)] + internal static extern void Everything_SetOffset(int dwOffset); + + [DllImport(DLL)] + internal static extern bool Everything_GetMatchPath(); + + [DllImport(DLL)] + internal static extern bool Everything_GetMatchCase(); + + [DllImport(DLL)] + internal static extern bool Everything_GetMatchWholeWord(); + + [DllImport(DLL)] + internal static extern bool Everything_GetRegex(); + + [DllImport(DLL)] + internal static extern uint Everything_GetMax(); + + [DllImport(DLL)] + internal static extern uint Everything_GetOffset(); + + [DllImport(DLL, CharSet = CharSet.Unicode)] + internal static extern string Everything_GetSearchW(); + + [DllImport(DLL)] + internal static extern EverythingApi.StateCode Everything_GetLastError(); + + [DllImport(DLL, CharSet = CharSet.Unicode)] + internal static extern bool Everything_QueryW(bool bWait); + + [DllImport(DLL)] + internal static extern void Everything_SortResultsByPath(); + + [DllImport(DLL)] + internal static extern int Everything_GetNumFileResults(); + + [DllImport(DLL)] + internal static extern int Everything_GetNumFolderResults(); + + [DllImport(DLL)] + internal static extern int Everything_GetNumResults(); + + [DllImport(DLL)] + internal static extern int Everything_GetTotFileResults(); + + [DllImport(DLL)] + internal static extern int Everything_GetTotFolderResults(); + + [DllImport(DLL)] + internal static extern int Everything_GetTotResults(); + + [DllImport(DLL)] + internal static extern bool Everything_IsVolumeResult(int nIndex); + + [DllImport(DLL)] + internal static extern bool Everything_IsFolderResult(int nIndex); + + [DllImport(DLL)] + internal static extern bool Everything_IsFileResult(int nIndex); + + [DllImport(DLL, CharSet = CharSet.Unicode)] + internal static extern void Everything_GetResultFullPathNameW(int nIndex, StringBuilder lpString, int nMaxCount); + + [DllImport(DLL)] + internal static extern void Everything_Reset(); + + // Everything 1.4 + + [DllImport(DLL)] + public static extern void Everything_SetSort(SortOption dwSortType); + [DllImport(DLL)] + public static extern bool Everything_IsFastSort(SortOption dwSortType); + [DllImport(DLL)] + public static extern SortOption Everything_GetSort(); + [DllImport(DLL)] + public static extern uint Everything_GetResultListSort(); + [DllImport(DLL)] + public static extern void Everything_SetRequestFlags(uint dwRequestFlags); + [DllImport(DLL)] + public static extern uint Everything_GetRequestFlags(); + [DllImport(DLL)] + public static extern uint Everything_GetResultListRequestFlags(); + [DllImport("Everything64.dll", CharSet = CharSet.Unicode)] + public static extern IntPtr Everything_GetResultExtension(uint nIndex); + [DllImport(DLL)] + public static extern bool Everything_GetResultSize(uint nIndex, out long lpFileSize); + [DllImport(DLL)] + public static extern bool Everything_GetResultDateCreated(uint nIndex, out long lpFileTime); + [DllImport(DLL)] + public static extern bool Everything_GetResultDateModified(uint nIndex, out long lpFileTime); + [DllImport(DLL)] + public static extern bool Everything_GetResultDateAccessed(uint nIndex, out long lpFileTime); + [DllImport(DLL)] + public static extern uint Everything_GetResultAttributes(uint nIndex); + [DllImport(DLL, CharSet = CharSet.Unicode)] + public static extern IntPtr Everything_GetResultFileListFileName(uint nIndex); + [DllImport(DLL)] + public static extern uint Everything_GetResultRunCount(uint nIndex); + [DllImport(DLL)] + public static extern bool Everything_GetResultDateRun(uint nIndex, out long lpFileTime); + [DllImport(DLL)] + public static extern bool Everything_GetResultDateRecentlyChanged(uint nIndex, out long lpFileTime); + [DllImport(DLL, CharSet = CharSet.Unicode)] + public static extern IntPtr Everything_GetResultHighlightedFileName(uint nIndex); + [DllImport(DLL, CharSet = CharSet.Unicode)] + public static extern IntPtr Everything_GetResultHighlightedPath(uint nIndex); + [DllImport(DLL, CharSet = CharSet.Unicode)] + public static extern IntPtr Everything_GetResultHighlightedFullPathAndFileName(uint nIndex); + [DllImport(DLL)] + public static extern uint Everything_GetRunCountFromFileName(string lpFileName); + [DllImport(DLL)] + public static extern bool Everything_SetRunCountFromFileName(string lpFileName, uint dwRunCount); + [DllImport(DLL)] + public static extern uint Everything_IncRunCountFromFileName(string lpFileName); + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs new file mode 100644 index 000000000..e1486f9fb --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs @@ -0,0 +1,27 @@ +using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything +{ + public class EverythingSearchManager : IIndexProvider + { + private Settings Settings { get; } + + public EverythingSearchManager(Settings settings) + { + Settings = settings; + } + + + public ValueTask> SearchAsync(Query query, CancellationToken token) + { + return ValueTask.FromResult(EverythingApi.SearchAsync(query.Search, token, Settings.SortOption)); + } + public ValueTask> ContentSearchAsync(Query query, CancellationToken token) + { + return new ValueTask>(new List()); + } + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/SortOption.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/SortOption.cs new file mode 100644 index 000000000..434afd1b4 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/SortOption.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Everything.Everything +{ + public enum SortOption : uint + { + NAME_ASCENDING = 1u, + NAME_DESCENDING = 2u, + PATH_ASCENDING = 3u, + PATH_DESCENDING = 4u, + SIZE_ASCENDING = 5u, + SIZE_DESCENDING = 6u, + EXTENSION_ASCENDING = 7u, + EXTENSION_DESCENDING = 8u, + TYPE_NAME_ASCENDING = 9u, + TYPE_NAME_DESCENDING = 10u, + DATE_CREATED_ASCENDING = 11u, + DATE_CREATED_DESCENDING = 12u, + DATE_MODIFIED_ASCENDING = 13u, + DATE_MODIFIED_DESCENDING = 14u, + ATTRIBUTES_ASCENDING = 15u, + ATTRIBUTES_DESCENDING = 16u, + FILE_LIST_FILENAME_ASCENDING = 17u, + FILE_LIST_FILENAME_DESCENDING = 18u, + RUN_COUNT_ASCENDING = 19u, + RUN_COUNT_DESCENDING = 20u, + DATE_RECENTLY_CHANGED_ASCENDING = 21u, + DATE_RECENTLY_CHANGED_DESCENDING = 22u, + DATE_ACCESSED_ASCENDING = 23u, + DATE_ACCESSED_DESCENDING = 24u, + DATE_RUN_ASCENDING = 25u, + DATE_RUN_DESCENDING = 26u + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs new file mode 100644 index 000000000..d7f0a8ec8 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IPathEnumerable.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Flow.Launcher.Plugin.Explorer.Search +{ + public interface IPathEnumerable + { + public IEnumerable Enumerate(string path, bool recursive); + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 63310bebd..35ba0bafb 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -35,6 +35,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search return $"{keyword}{formatted_path}"; } + public static Result CreateResult(Query query, SearchResult result) + { + return result.Type switch + { + ResultType.Folder or ResultType.Volume => CreateFolderResult(Path.GetFileName(result.FullPath), + result.FullPath, result.FullPath, query, 0, result.ShowIndexState, result.WindowsIndexed), + ResultType.File => CreateFileResult( + result.FullPath, query, 0, result.ShowIndexState, result.WindowsIndexed), + _ => throw new ArgumentOutOfRangeException() + }; + } + internal static Result CreateFolderResult(string title, string subtitle, string path, Query query, int score = 0, bool showIndexState = false, bool windowsIndexed = false) { return new Result @@ -61,7 +73,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search } Context.API.ChangeQuery(GetPathWithActionKeyword(path, ResultType.Folder)); - + return false; }, Score = score, @@ -192,16 +204,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search } } - internal class SearchResult - { - public string FullPath { get; set; } - public ResultType Type { get; set; } - - public bool WindowsIndexed { get; set; } - - public bool ShowIndexState { get; set; } - } - public enum ResultType { Volume, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 8ce627a44..080621f42 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -57,8 +57,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search results.UnionWith(quickaccessLinks); } + IEnumerable searchResults; + if (IsFileContentSearch(query.ActionKeyword)) - return await WindowsIndexFileContentSearchAsync(query, querySearch, token).ConfigureAwait(false); + { + searchResults = await Settings.IndexProvider.ContentSearchAsync(query, token); + } + else + { + searchResults = await Settings.IndexProvider.SearchAsync(query, token); + } if (ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword) || ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)) @@ -71,8 +79,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search querySearch.Length > 0 && !querySearch.IsLocationPathString()) { - results.UnionWith(await WindowsIndexFilesAndFoldersSearchAsync(query, querySearch, token) - .ConfigureAwait(false)); + if (searchResults != null) + results.UnionWith(searchResults.Select(search => ResultManager.CreateResult(query, search))); } return results.ToList(); @@ -93,7 +101,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search Settings.ActionKeyword.IndexSearchActionKeyword => Settings.IndexSearchKeywordEnabled && keyword == Settings.IndexSearchActionKeyword, Settings.ActionKeyword.QuickAccessActionKeyword => Settings.QuickAccessKeywordEnabled && - keyword == Settings.QuickAccessActionKeyword + keyword == Settings.QuickAccessActionKeyword }; } @@ -126,37 +134,20 @@ namespace Flow.Launcher.Plugin.Explorer.Search token.ThrowIfCancellationRequested(); - var directoryResult = await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync, - DirectoryInfoClassSearch, - useIndexSearch, - query, - locationPath, - token).ConfigureAwait(false); + // var directoryResult = await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync, + // DirectoryInfoClassSearch, + // useIndexSearch, + // query, + // locationPath, + // token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); - results.UnionWith(directoryResult); + // results.UnionWith(directoryResult); return results.ToList(); } - private async Task> WindowsIndexFileContentSearchAsync(Query query, string querySearchString, - CancellationToken token) - { - var queryConstructor = new QueryConstructor(Settings); - - if (string.IsNullOrEmpty(querySearchString)) - return new List(); - - return await IndexSearch.WindowsIndexSearchAsync( - querySearchString, - queryConstructor.CreateQueryHelper, - queryConstructor.QueryForFileContentSearch, - Settings.IndexSearchExcludedSubdirectoryPaths, - query, - token).ConfigureAwait(false); - } - public bool IsFileContentSearch(string actionKeyword) { return actionKeyword == Settings.FileContentSearchActionKeyword; @@ -181,34 +172,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search return await windowsIndexSearch(query, querySearchString, token); } - private async Task> WindowsIndexFilesAndFoldersSearchAsync(Query query, string querySearchString, - CancellationToken token) - { - var queryConstructor = new QueryConstructor(Settings); - - return await IndexSearch.WindowsIndexSearchAsync( - querySearchString, - queryConstructor.CreateQueryHelper, - queryConstructor.QueryForAllFilesAndFolders, - Settings.IndexSearchExcludedSubdirectoryPaths, - query, - token).ConfigureAwait(false); - } - - private async Task> WindowsIndexTopLevelFolderSearchAsync(Query query, string path, - CancellationToken token) - { - var queryConstructor = new QueryConstructor(Settings); - - return await IndexSearch.WindowsIndexSearchAsync( - path, - queryConstructor.CreateQueryHelper, - queryConstructor.QueryForTopLevelDirectorySearch, - Settings.IndexSearchExcludedSubdirectoryPaths, - query, - token).ConfigureAwait(false); - } - private bool UseWindowsIndexForDirectorySearch(string locationPath) { var pathToDirectory = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath); @@ -221,7 +184,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search .StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))) return false; - return IndexSearch.PathIsIndexed(pathToDirectory); + return WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory); } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs new file mode 100644 index 000000000..815c6b7b0 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs @@ -0,0 +1,14 @@ +using System; + +namespace Flow.Launcher.Plugin.Explorer.Search +{ + public struct SearchResult + { + public string FullPath { get; init; } + public ResultType Type { get; init; } + + public bool WindowsIndexed { get; init; } + + public bool ShowIndexState { get; init; } + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs new file mode 100644 index 000000000..e42d78f9a --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IIndexProvider.cs @@ -0,0 +1,13 @@ +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/IndexSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs similarity index 71% rename from Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs rename to Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs index 2e842c843..28e9c0e71 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs @@ -13,16 +13,15 @@ using System.Windows; namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { - internal static class IndexSearch + internal static class WindowsIndex { // Reserved keywords in oleDB - private const string reservedStringPattern = @"^[`\@\#\^,\&\/\\\$\%_;\[\]]+$"; + private const string ReservedStringPattern = @"^[`\@\#\^,\&\/\\\$\%_;\[\]]+$"; - internal static async Task> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token) + private static async Task> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token) { - var results = new List(); - var fileResults = new List(); + var results = new List(); try { @@ -35,7 +34,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex await using var dataReaderResults = await command.ExecuteReaderAsync(token) as OleDbDataReader; token.ThrowIfCancellationRequested(); - if (dataReaderResults.HasRows) + if (dataReaderResults is { HasRows: true }) { while (await dataReaderResults.ReadAsync(token)) { @@ -49,18 +48,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex var path = new Uri(encodedFragmentPath).LocalPath; - if (dataReaderResults.GetString(2) == "Directory") + results.Add(new SearchResult() { - results.Add(ResultManager.CreateFolderResult( - dataReaderResults.GetString(0), - path, - path, - query, 0, true, true)); - } - else - { - fileResults.Add(ResultManager.CreateFileResult(path, query, 0, true, true)); - } + FullPath = path, + Type = dataReaderResults.GetString(2) == "Directory" ? ResultType.Folder : ResultType.File, + WindowsIndexed = true + }); } } } @@ -80,13 +73,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex LogException("General error from performing index search", e); } - results.AddRange(fileResults); - - // Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. - return results; + // Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. + return results; } - internal async static Task> WindowsIndexSearchAsync( + internal static async ValueTask> WindowsIndexSearchAsync( string searchString, Func createQueryHelper, Func constructQuery, @@ -94,28 +85,15 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex Query query, CancellationToken token) { - var regexMatch = Regex.Match(searchString, reservedStringPattern); + var regexMatch = Regex.Match(searchString, ReservedStringPattern); if (regexMatch.Success) - return new List(); - - try - { - var constructedQuery = constructQuery(searchString); + return new(); - return RemoveResultsInExclusionList( - await ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, query, token).ConfigureAwait(false), - exclusionList, - token); - } - catch (COMException) - { - // Occurs because the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin - if (!SearchManager.Settings.WarnWindowsSearchServiceOff) - return new List(); + var constructedQuery = constructQuery(searchString); - return ResultForWindexSearchOff(query.RawQuery); - } + return + await ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, query, token); } private static List RemoveResultsInExclusionList(List results, List exclusionList, CancellationToken token) @@ -159,7 +137,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex var indexManager = csm.GetCatalog("SystemIndex").GetCrawlScopeManager(); return indexManager.IncludedInCrawlScope(path) > 0; } - catch(COMException) + catch (COMException) { // Occurs because the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin return false; @@ -180,18 +158,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { SearchManager.Settings.WarnWindowsSearchServiceOff = false; - var pluginsManagerPlugin= api.GetAllPlugins().FirstOrDefault(x => x.Metadata.ID == "9f8f9b14-2518-4907-b211-35ab6290dee7"); + var pluginsManagerPlugin = api.GetAllPlugins().FirstOrDefault(x => x.Metadata.ID == "9f8f9b14-2518-4907-b211-35ab6290dee7"); var actionKeywordCount = pluginsManagerPlugin.Metadata.ActionKeywords.Count; if (actionKeywordCount > 1) LogException("PluginsManager's action keyword has increased to more than 1, this does not allow for determining the " + - "right action keyword. Explorer's code for managing Windows Search service not running exception needs to be updated", + "right action keyword. Explorer's code for managing Windows Search service not running exception needs to be updated", new InvalidOperationException()); if (MessageBox.Show(string.Format(api.GetTranslation("plugin_explorer_alternative"), Environment.NewLine), - api.GetTranslation("plugin_explorer_alternative_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes + api.GetTranslation("plugin_explorer_alternative_title"), + MessageBoxButton.YesNo) == MessageBoxResult.Yes && actionKeywordCount == 1) { api.ChangeQuery(string.Format("{0} install everything", pluginsManagerPlugin.Metadata.ActionKeywords[0])); @@ -221,7 +199,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex throw e; #else Log.Exception($"|Flow.Launcher.Plugin.Explorer.IndexSearch|{message}", e); -#endif +#endif } } -} +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexManager.cs new file mode 100644 index 000000000..72f5e9cb1 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexManager.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex +{ + public class WindowsIndexManager : IIndexProvider + { + private Settings Settings { get; } + private QueryConstructor QueryConstructor { get; } + public WindowsIndexManager(Settings settings) + { + Settings = settings; + QueryConstructor = new QueryConstructor(Settings); + } + + + private async Task> WindowsIndexFileContentSearchAsync(Query query, string querySearchString, + CancellationToken token) + { + if (string.IsNullOrEmpty(querySearchString)) + return new List(); + + return await WindowsIndex.WindowsIndexSearchAsync( + querySearchString, + QueryConstructor.CreateQueryHelper, + QueryConstructor.QueryForFileContentSearch, + Settings.IndexSearchExcludedSubdirectoryPaths, + query, + token).ConfigureAwait(false); + } + + + + private async Task> WindowsIndexFilesAndFoldersSearchAsync(Query query, string querySearchString, + CancellationToken token) + { + return await WindowsIndex.WindowsIndexSearchAsync( + querySearchString, + QueryConstructor.CreateQueryHelper, + QueryConstructor.QueryForAllFilesAndFolders, + Settings.IndexSearchExcludedSubdirectoryPaths, + query, + token).ConfigureAwait(false); + } + + + private async Task> WindowsIndexTopLevelFolderSearchAsync(Query query, string path, + CancellationToken token) + { + var queryConstructor = new QueryConstructor(Settings); + + return await WindowsIndex.WindowsIndexSearchAsync( + path, + queryConstructor.CreateQueryHelper, + queryConstructor.QueryForTopLevelDirectorySearch, + Settings.IndexSearchExcludedSubdirectoryPaths, + query, + token).ConfigureAwait(false); + } + public ValueTask> SearchAsync(Query query, CancellationToken token) + { + return default; + } + public ValueTask> ContentSearchAsync(Query query, CancellationToken token) + { + return default; + } + } +} \ 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 351091dfe..f3ce8abc5 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -1,7 +1,11 @@ +using Flow.Launcher.Plugin.Everything.Everything; using Flow.Launcher.Plugin.Explorer.Search; +using Flow.Launcher.Plugin.Explorer.Search.Everything; using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; +using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using System; using System.Collections.Generic; +using System.Linq; namespace Flow.Launcher.Plugin.Explorer { @@ -40,6 +44,43 @@ namespace Flow.Launcher.Plugin.Explorer public bool WarnWindowsSearchServiceOff { get; set; } = true; + private List _indexProviders; + public Settings() + { + _indexProviders = new List() + { + new EverythingSearchManager(this), + new WindowsIndexManager(this) + }; + } + public IndexSearchEngineOption IndexSearchEngine { get; set; } + public IIndexProvider IndexProvider => _indexProviders[(int)IndexSearchEngine]; + + public enum PathTraversalEngineOption + { + Everything, + WindowsIndex, + Direct + } + + public enum IndexSearchEngineOption + { + Everything, + WindowsIndex + } + #region Everything Settings + + + public bool LaunchHidden { get; set; } = false; + + public string EverythingInstalledPath { get; set; } + + public SortOption[] SortOptions { get; set; } = Enum.GetValues(); + + public SortOption SortOption { get; set; } = SortOption.NAME_ASCENDING; + + #endregion + internal enum ActionKeyword { SearchActionKeyword, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index 77ec5457b..e1793ad47 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -9,7 +9,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels { public class SettingsViewModel { - internal Settings Settings { get; set; } + public Settings Settings { get; set; } internal PluginInitContext Context { get; set; } @@ -29,7 +29,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels internal void RemoveAccessLinkFromExcludedIndexPaths(AccessLink selectedRow) => Settings.IndexSearchExcludedSubdirectoryPaths.Remove(selectedRow); - internal void OpenWindowsIndexingOptions() + internal static void OpenWindowsIndexingOptions() { var psi = new ProcessStartInfo { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml index a7fa58643..a441c10b5 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml @@ -6,14 +6,13 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModels="clr-namespace:Flow.Launcher.Plugin.Explorer.ViewModels" xmlns:views="clr-namespace:Flow.Launcher.Plugin.Explorer.Views" + xmlns:qa="clr-namespace:Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks" d:DesignHeight="450" d:DesignWidth="800" - mc:Ignorable="d"> + mc:Ignorable="d" + d:DataContext="{d:DesignInstance viewModels:SettingsViewModel}"> - - - - + @@ -97,8 +96,14 @@ AllowDrop="True" DragEnter="lbxAccessLinks_DragEnter" Drop="lbxAccessLinks_Drop" - ItemTemplate="{StaticResource ListViewTemplateExcludedPaths}" /> + ItemTemplate="{StaticResource ListViewActionKeywords}" /> + + @@ -144,4 +149,4 @@ - + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs index 63c4a8dca..8f60d0178 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs @@ -25,6 +25,8 @@ namespace Flow.Launcher.Plugin.Explorer.Views public ExplorerSettings(SettingsViewModel viewModel) { + DataContext = viewModel; + InitializeComponent(); this.viewModel = viewModel; @@ -310,7 +312,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views private void btnOpenIndexingOptions_Click(object sender, RoutedEventArgs e) { - viewModel.OpenWindowsIndexingOptions(); + SettingsViewModel.OpenWindowsIndexingOptions(); } public void SetButtonVisibilityToHidden()