Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions Flow.Launcher.Test/Plugins/ExplorerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand All @@ -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(
Expand All @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@ 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()
{
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";
Expand All @@ -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
Expand All @@ -54,20 +54,19 @@ internal CSearchQueryHelper CreateQueryHelper()

public static string TopLevelDirectoryConstraint(ReadOnlySpan<char> path) => $"directory='file:{path}'";
public static string RecursiveDirectoryConstraint(ReadOnlySpan<char> path) => $"scope='file:{path}'";


///<summary>
/// Search will be performed on all folders and files on the first level of a specified directory.
///</summary>
public string Directory(ReadOnlySpan<char> path, ReadOnlySpan<char> 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;
}
Expand All @@ -84,7 +83,7 @@ public string FilesAndFolders(ReadOnlySpan<char> 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}";
}

/// <summary>
Expand Down Expand Up @@ -121,18 +120,20 @@ private static string ReplaceSpecialCharacterWithTwoSideWhiteSpace(ReadOnlySpan<
public const string RestrictionsForAllFilesAndFoldersSearch = "scope='file:'";

/// <summary>
/// Order identifier: file name
/// Order identifier: System.Search.Rank DESC
/// </summary>
public const string FileName = "System.FileName";

/// <remarks>
/// <see href="https://docs.microsoft.com/en-us/windows/win32/properties/props-system-search-rank"/>
/// </remarks>
public const string OrderIdentifier = "System.Search.Rank DESC";

///<summary>
/// Search will be performed on all indexed file contents for the specified search keywords.
///</summary>
public string FileContent(ReadOnlySpan<char> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,17 @@ private IAsyncEnumerable<SearchResult> WindowsIndexTopLevelFolderSearchAsync(
return HandledEngineNotAvailableExceptionAsync();
}
}

public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token)
{
return WindowsIndexFilesAndFoldersSearchAsync(search, token: token);
}

public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token)
{
return WindowsIndexFileContentSearchAsync(contentSearch, token);
}

public IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, CancellationToken token)
{
return WindowsIndexTopLevelFolderSearchAsync(search, path, recursive, token);
Expand All @@ -100,19 +103,17 @@ private IAsyncEnumerable<SearchResult> HandledEngineNotAvailableExceptionAsync()
if (!Settings.WarnWindowsSearchServiceOff)
return AsyncEnumerable.Empty<SearchResult>();

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);
});
Expand Down
Loading