diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 420da266d87..9ec95215556 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -39,8 +39,8 @@ public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestric } [SupportedOSPlatform("windows7.0")] - [TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY System.FileName")] - [TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY System.FileName")] + [TestCase("C:\\", $"SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY {QueryConstructor.OrderIdentifier}")] + [TestCase("C:\\SomeFolder\\", $"SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY {QueryConstructor.OrderIdentifier}")] public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString) { // Given @@ -59,7 +59,7 @@ public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_Then [TestCase("C:\\SomeFolder", "flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType" + " FROM SystemIndex WHERE directory='file:C:\\SomeFolder'" + " AND (System.FileName LIKE 'flow.launcher.sln%' OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"'))" + - " ORDER BY System.FileName")] + $" ORDER BY {QueryConstructor.OrderIdentifier}")] public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString( string folderPath, string userSearchString, string expectedString) { @@ -87,8 +87,8 @@ public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereR [SupportedOSPlatform("windows7.0")] [TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" " + "FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " + - "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY System.FileName")] - [TestCase("", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" FROM \"SystemIndex\" WHERE WorkId IS NOT NULL AND scope='file:' ORDER BY System.FileName")] + $"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY {QueryConstructor.OrderIdentifier}")] + [TestCase("", $"SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" FROM \"SystemIndex\" WHERE WorkId IS NOT NULL AND scope='file:' ORDER BY {QueryConstructor.OrderIdentifier}")] public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString( string userSearchString, string expectedString) { @@ -107,7 +107,6 @@ public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShould ClassicAssert.AreEqual(expectedString, resultString); } - [SupportedOSPlatform("windows7.0")] [TestCase(@"some words", @"FREETEXT('some words')")] public void GivenWindowsIndexSearch_WhenQueryWhereRestrictionsIsForFileContentSearch_ThenShouldReturnFreeTextString( @@ -127,7 +126,7 @@ public void GivenWindowsIndexSearch_WhenQueryWhereRestrictionsIsForFileContentSe [SupportedOSPlatform("windows7.0")] [TestCase("some words", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " + - "FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY System.FileName")] + $"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY {QueryConstructor.OrderIdentifier}")] public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString( string userSearchString, string expectedString) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index 9e3707fbeab..1d160983a00 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -6,16 +6,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { public class QueryConstructor { - private static Regex _specialCharacterMatcher = new(@"[\@\@\#\#\&\&*_;,\%\|\!\(\)\{\}\[\]\^\~\?\\""\/\:\=\-]+", RegexOptions.Compiled); - private static Regex _multiWhiteSpacesMatcher = new(@"\s+", RegexOptions.Compiled); + private static readonly Regex _specialCharacterMatcher = new(@"[\@\@\#\#\&\&*_;,\%\|\!\(\)\{\}\[\]\^\~\?\\""\/\:\=\-]+", RegexOptions.Compiled); + private static readonly Regex _multiWhiteSpacesMatcher = new(@"\s+", RegexOptions.Compiled); - private Settings settings { get; } + private Settings Settings { get; } private const string SystemIndex = "SystemIndex"; public QueryConstructor(Settings settings) { - this.settings = settings; + Settings = settings; } public CSearchQueryHelper CreateBaseQuery() @@ -23,7 +23,7 @@ public CSearchQueryHelper CreateBaseQuery() var baseQuery = CreateQueryHelper(); // Set the number of results we want. Don't set this property if all results are needed. - baseQuery.QueryMaxResults = settings.MaxResult; + baseQuery.QueryMaxResults = Settings.MaxResult; // Set list of columns we want to display, getting the path presently baseQuery.QuerySelectColumns = "System.FileName, System.ItemUrl, System.ItemType"; @@ -37,7 +37,7 @@ public CSearchQueryHelper CreateBaseQuery() return baseQuery; } - internal CSearchQueryHelper CreateQueryHelper() + internal static CSearchQueryHelper CreateQueryHelper() { // This uses the Microsoft.Search.Interop assembly // Throws COMException if Windows Search service is not running/disabled, this needs to be caught @@ -54,20 +54,19 @@ internal CSearchQueryHelper CreateQueryHelper() public static string TopLevelDirectoryConstraint(ReadOnlySpan path) => $"directory='file:{path}'"; public static string RecursiveDirectoryConstraint(ReadOnlySpan path) => $"scope='file:{path}'"; - /// /// Search will be performed on all folders and files on the first level of a specified directory. /// public string Directory(ReadOnlySpan path, ReadOnlySpan searchString = default, bool recursive = false) { - var queryConstraint = searchString.IsWhiteSpace() ? "" : $"AND ({FileName} LIKE '{searchString}%' OR CONTAINS({FileName},'\"{searchString}*\"'))"; + var queryConstraint = searchString.IsWhiteSpace() ? "" : $"AND (System.FileName LIKE '{searchString}%' OR CONTAINS(System.FileName,'\"{searchString}*\"'))"; var scopeConstraint = recursive ? RecursiveDirectoryConstraint(path) : TopLevelDirectoryConstraint(path); - var query = $"SELECT TOP {settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {FileName}"; + var query = $"SELECT TOP {Settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {OrderIdentifier}"; return query; } @@ -84,7 +83,7 @@ public string FilesAndFolders(ReadOnlySpan userSearchString) var replacedSearchString = ReplaceSpecialCharacterWithTwoSideWhiteSpace(userSearchString); // Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause - return $"{CreateBaseQuery().GenerateSQLFromUserQuery(replacedSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}"; + return $"{CreateBaseQuery().GenerateSQLFromUserQuery(replacedSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {OrderIdentifier}"; } /// @@ -121,10 +120,12 @@ private static string ReplaceSpecialCharacterWithTwoSideWhiteSpace(ReadOnlySpan< public const string RestrictionsForAllFilesAndFoldersSearch = "scope='file:'"; /// - /// Order identifier: file name + /// Order identifier: System.Search.Rank DESC /// - public const string FileName = "System.FileName"; - + /// + /// + /// + public const string OrderIdentifier = "System.Search.Rank DESC"; /// /// Search will be performed on all indexed file contents for the specified search keywords. @@ -132,7 +133,7 @@ private static string ReplaceSpecialCharacterWithTwoSideWhiteSpace(ReadOnlySpan< public string FileContent(ReadOnlySpan userSearchString) { string query = - $"SELECT TOP {settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}"; + $"SELECT TOP {Settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {OrderIdentifier}"; return query; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs index c3a7d9e9123..3d69a1ee672 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs @@ -82,14 +82,17 @@ private IAsyncEnumerable WindowsIndexTopLevelFolderSearchAsync( return HandledEngineNotAvailableExceptionAsync(); } } + public IAsyncEnumerable SearchAsync(string search, CancellationToken token) { return WindowsIndexFilesAndFoldersSearchAsync(search, token: token); } + public IAsyncEnumerable ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token) { return WindowsIndexFileContentSearchAsync(contentSearch, token); } + public IAsyncEnumerable EnumerateAsync(string path, string search, bool recursive, CancellationToken token) { return WindowsIndexTopLevelFolderSearchAsync(search, path, recursive, token); @@ -100,19 +103,17 @@ private IAsyncEnumerable HandledEngineNotAvailableExceptionAsync() if (!Settings.WarnWindowsSearchServiceOff) return AsyncEnumerable.Empty(); - var api = Main.Context.API; - throw new EngineNotAvailableException( "Windows Index", - api.GetTranslation("plugin_explorer_windowsSearchServiceFix"), - api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"), + Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceFix"), + Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"), Constants.WindowsIndexErrorImagePath, c => { Settings.WarnWindowsSearchServiceOff = false; // Clears the warning message so user is not mistaken that it has not worked - api.ChangeQuery(string.Empty); + Main.Context.API.ChangeQuery(string.Empty); return ValueTask.FromResult(false); });