From 8848ed1a00b70b9c72eeabec19eb212eb7c96cc4 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 11 May 2020 23:15:15 +1000 Subject: [PATCH 01/91] Add skeleton tests and classes for Explorer --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 16 +++++ Flow.Launcher.sln | 18 +++++- .../Flow.Launcher.Plugin.Explorer.csproj | 24 ++++++++ .../Images/explorer.png | Bin 0 -> 44763 bytes Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 57 ++++++++++++++++++ .../Flow.Launcher.Plugin.Explorer/Settings.cs | 10 +++ .../ViewModels/SettingsViewModel.cs | 25 ++++++++ .../Views/ExplorerSettings.xaml | 12 ++++ .../Views/ExplorerSettings.xaml.cs | 27 +++++++++ .../Flow.Launcher.Plugin.Explorer/plugin.json | 12 ++++ 10 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 Flow.Launcher.Test/Plugins/ExplorerTest.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Images/explorer.png create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Main.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/plugin.json diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs new file mode 100644 index 000000000..2bc95d086 --- /dev/null +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; + +namespace Flow.Launcher.Test.Plugins +{ + [TestFixture] + public class ExplorerTest + { + public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestrictionsShouldUseDirectoryString(string expectedString){} + + public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString() { } + + public void GivenWindowsIndexSearch_WhenReturnedNilAndIsNotIndexed_ThenSearchMethodShouldContinueDirectoryInfoClassSearch() { } + + public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString() { } + } +} diff --git a/Flow.Launcher.sln b/Flow.Launcher.sln index 2827cf585..c242932ea 100644 --- a/Flow.Launcher.sln +++ b/Flow.Launcher.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29806.167 @@ -18,6 +18,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher", "Flow.Launc {1EE20B48-82FB-48A2-8086-675D6DDAB4F0} = {1EE20B48-82FB-48A2-8086-675D6DDAB4F0} {0B9DE348-9361-4940-ADB6-F5953BFFCCEC} = {0B9DE348-9361-4940-ADB6-F5953BFFCCEC} {FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4} + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {F9C4C081-4CC3-4146-95F1-E102B4E10A5F} {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E} {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} {787B8AA6-CA93-4C84-96FE-DF31110AD1C4} = {787B8AA6-CA93-4C84-96FE-DF31110AD1C4} @@ -76,6 +77,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Calcul EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "HelloWorldFSharp", "Plugins\HelloWorldFSharp\HelloWorldFSharp.fsproj", "{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Explorer", "Plugins\Flow.Launcher.Plugin.Explorer\Flow.Launcher.Plugin.Explorer.csproj", "{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -327,6 +330,18 @@ Global {30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x64.Build.0 = Release|Any CPU {30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x86.ActiveCfg = Release|Any CPU {30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x86.Build.0 = Release|Any CPU + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|x64.ActiveCfg = Debug|Any CPU + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|x64.Build.0 = Debug|Any CPU + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|x86.ActiveCfg = Debug|Any CPU + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|x86.Build.0 = Debug|Any CPU + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|Any CPU.Build.0 = Release|Any CPU + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x64.ActiveCfg = Release|Any CPU + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x64.Build.0 = Release|Any CPU + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x86.ActiveCfg = Release|Any CPU + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -347,6 +362,7 @@ Global {9B130CC5-14FB-41FF-B310-0A95B6894C37} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} + {F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F26ACB50-3F6C-4907-B0C9-1ADACC1D0DED} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj new file mode 100644 index 000000000..5f1ec0544 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -0,0 +1,24 @@ + + + + Library + netcoreapp3.1 + true + + + + + + ..\..\Output\Debug\Plugins\Flow.Launcher.Explorer + + + + ..\..\Output\Release\Plugins\Flow.Launcher.Explorer + + + + + + + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/explorer.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/explorer.png new file mode 100644 index 0000000000000000000000000000000000000000..552b2c5bd1715bce33e57cd2f4f8927d03879912 GIT binary patch literal 44763 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 TjU5kzfbZ!OoyVnWmS6q{5RG1u literal 0 HcmV?d00001 diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs new file mode 100644 index 000000000..206911b6c --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -0,0 +1,57 @@ +using Flow.Launcher.Infrastructure.Storage; +using Flow.Launcher.Plugin.Explorer.ViewModels; +using Flow.Launcher.Plugin.Explorer.Views; +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Controls; + +namespace Flow.Launcher.Plugin.Explorer +{ + class Main : ISettingProvider, IPlugin, ISavable, IPluginI18n, IContextMenu + { + private PluginInitContext Context { get; set; } + + private Settings _settings; + + private SettingsViewModel _viewModel; + + public Control CreateSettingPanel() + { + return new ExplorerSettings(); + } + + public string GetTranslatedPluginDescription() + { + throw new NotImplementedException(); + } + + public string GetTranslatedPluginTitle() + { + throw new NotImplementedException(); + } + + public void Init(PluginInitContext context) + { + Context = context; + + _viewModel = new SettingsViewModel(); + _settings = _viewModel.Settings; + } + + public List LoadContextMenus(Result selectedResult) + { + throw new NotImplementedException(); + } + + public List Query(Query query) + { + return new List(); + } + + public void Save() + { + _viewModel.Save(); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs new file mode 100644 index 000000000..dcdb8f06e --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Flow.Launcher.Plugin.Explorer +{ + public class Settings + { + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs new file mode 100644 index 000000000..298476abd --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -0,0 +1,25 @@ +using Flow.Launcher.Infrastructure.Storage; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Flow.Launcher.Plugin.Explorer.ViewModels +{ + public class SettingsViewModel + { + private readonly PluginJsonStorage _storage; + + public Settings Settings { get; set; } + + public SettingsViewModel() + { + _storage = new PluginJsonStorage(); + Settings = _storage.Load(); + } + + public void Save() + { + _storage.Save(); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml new file mode 100644 index 000000000..93defd985 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs new file mode 100644 index 000000000..f87c29412 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs @@ -0,0 +1,27 @@ +using Flow.Launcher.Plugin.Explorer.ViewModels; +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Flow.Launcher.Plugin.Explorer.Views +{ + ///

+ /// Interaction logic for ExplorerSettings.xaml + /// + public partial class ExplorerSettings : UserControl + { + public ExplorerSettings() + { + InitializeComponent(); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json new file mode 100644 index 000000000..4a6c8785c --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json @@ -0,0 +1,12 @@ +{ + "ID": "572be03c74c642baae319fc283e561a8", + "ActionKeyword": "*", + "Name": "Explorer", + "Description": "Search and manage files, folders and subfolders. Explorer utilises Windows Index Search", + "Author": "Jeremy Wu", + "Version": "1.0.0", + "Language": "csharp", + "Website": "https://github.com/Flow-Launcher/Flow.Launcher", + "ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll", + "IcoPath": "Images\\explorer.png" +} From 6d7aa46747ee730679e07151bfc4ef0c5bd19d01 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 11 May 2020 23:19:41 +1000 Subject: [PATCH 02/91] Add Windows Index directory search query test --- Flow.Launcher.Test/Flow.Launcher.Test.csproj | 1 + Flow.Launcher.Test/Plugins/ExplorerTest.cs | 19 +++++++++++++- .../Search/WindowsIndex/QueryConstructor.cs | 26 +++++++++++++++++++ .../Flow.Launcher.Plugin.Explorer/Settings.cs | 1 + 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj index 2dbf1a9d3..e5ca36289 100644 --- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj +++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj @@ -39,6 +39,7 @@ + diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 2bc95d086..c0fa32f5b 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -1,11 +1,28 @@ +using Flow.Launcher.Plugin.Explorer; +using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using NUnit.Framework; +using System; namespace Flow.Launcher.Test.Plugins { [TestFixture] public class ExplorerTest { - public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestrictionsShouldUseDirectoryString(string expectedString){} + [TestCase("directory='file:{path}'")] + public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestrictionsShouldUseDirectoryString(string expectedString) + { + // Given + var queryConstructor = new QueryConstructor(new Settings()); + + // When + var path = @"C:\Dropbox"; + var result = queryConstructor.QueryWhereRestrictionsForTopLevelDirectorySearch(path); + + // Then + Assert.IsTrue(result == expectedString, + $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " + + $"Actual: {result}{Environment.NewLine}"); + } public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString() { } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs new file mode 100644 index 000000000..9d5ffdb30 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex +{ + public class QueryConstructor + { + private Settings _settings; + + public QueryConstructor(Settings settings) + { + _settings = settings; + } + + /// + /// Search will be performed on all folders and files on the first level of a specified directory. + /// + public string QueryWhereRestrictionsForTopLevelDirectorySearch(string path) + { + // Set query restriction for top level directory search + return $"directory='file:{path}'"; + } + + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index dcdb8f06e..2d664c3db 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -6,5 +6,6 @@ namespace Flow.Launcher.Plugin.Explorer { public class Settings { + public int MaxResult { get; set; } = 100; } } From 634e2630865822d7f4591374bf733bc303201084 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 12 May 2020 05:56:51 +1000 Subject: [PATCH 03/91] Correct test case param --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index c0fa32f5b..055a358bc 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -8,15 +8,15 @@ namespace Flow.Launcher.Test.Plugins [TestFixture] public class ExplorerTest { - [TestCase("directory='file:{path}'")] - public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestrictionsShouldUseDirectoryString(string expectedString) + [TestCase("C:\\Dropbox", "directory='file:C:\\Dropbox'")] + public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestrictionsShouldUseDirectoryString(string path, string expectedString) { // Given var queryConstructor = new QueryConstructor(new Settings()); // When - var path = @"C:\Dropbox"; - var result = queryConstructor.QueryWhereRestrictionsForTopLevelDirectorySearch(path); + var folderPath = path; + var result = queryConstructor.QueryWhereRestrictionsForTopLevelDirectorySearch(folderPath); // Then Assert.IsTrue(result == expectedString, From 26f0ce2778346f19c3e5a5f402c20cba61746241 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 12 May 2020 06:47:45 +1000 Subject: [PATCH 04/91] Add base query constructor --- .../Search/WindowsIndex/QueryConstructor.cs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index 9d5ffdb30..db2d80858 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Search.Interop; +using System; using System.Collections.Generic; using System.Text; @@ -8,11 +9,39 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { private Settings _settings; + private const string SystemIndex = "SystemIndex"; + public QueryConstructor(Settings settings) { _settings = settings; } + public CSearchQueryHelper CreateBaseQuery() + { + // This uses the Microsoft.Search.Interop assembly + CSearchManager manager = new CSearchManager(); + + // SystemIndex catalog is the default catalog in Windows + ISearchCatalogManager catalogManager = manager.GetCatalog(SystemIndex); + + // Get the ISearchQueryHelper which will help us to translate AQS --> SQL necessary to query the indexer + var baseQuery = catalogManager.GetQueryHelper(); + + // Set the number of results we want. Don't set this property if all results are needed. + baseQuery.QueryMaxResults = _settings.MaxResult; + + // Set list of columns we want to display, getting the path presently + baseQuery.QuerySelectColumns = "System.FileName, System.ItemPathDisplay"; + + // Filter based on folder/file name + baseQuery.QueryContentProperties = "System.FileName"; + + // Set sorting order + //baseQuery.QuerySorting = "System.ItemType DESC"; + + return baseQuery; + } + /// /// Search will be performed on all folders and files on the first level of a specified directory. /// From f4ae5badf511c2eb5f015f9f586931a4f99d43e2 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 12 May 2020 06:50:17 +1000 Subject: [PATCH 05/91] Add test for directory search query string and method --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 15 +++++++++++++++ .../Flow.Launcher.Plugin.Explorer.csproj | 4 ++++ .../Search/WindowsIndex/QueryConstructor.cs | 11 ++++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 055a358bc..18426bafb 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -24,6 +24,21 @@ namespace Flow.Launcher.Test.Plugins $"Actual: {result}{Environment.NewLine}"); } + [TestCase("C:\\Dropbox", "SELECT TOP 100 System.FileName, System.ItemPathDisplay FROM SystemIndex WHERE directory='file:C:\\Dropbox'")] + public void GivenWindowsIndexSearch_WhenSearchTypeIsSearchTopFolderLevel_ThenQueryShouldUseExpectedString(string folderPath, string expectedString) + { + // Given + var queryConstructor = new QueryConstructor(new Settings()); + + //When + var queryString = queryConstructor.QueryForTopLevelDirectorySearch(folderPath); + + Assert.IsTrue(queryString == expectedString, + $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " + + $"Actual string was: {queryString}{Environment.NewLine}"); + } + + public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString() { } public void GivenWindowsIndexSearch_WhenReturnedNilAndIsNotIndexed_ThenSearchMethodShouldContinueDirectoryInfoClassSearch() { } 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 5f1ec0544..821c92714 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -16,6 +16,10 @@ ..\..\Output\Release\Plugins\Flow.Launcher.Explorer + + + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index db2d80858..ab3d529ef 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -1,4 +1,4 @@ -using Microsoft.Search.Interop; +using Microsoft.Search.Interop; using System; using System.Collections.Generic; using System.Text; @@ -51,5 +51,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex return $"directory='file:{path}'"; } + public string QueryForTopLevelDirectorySearch(string folderPath) + { + string query = "SELECT TOP " + _settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE "; + + query += QueryWhereRestrictionsForTopLevelDirectorySearch(folderPath); + + return query; + } + } } From e87a3a2e614a3a125663053fcf905b8fdc8890b3 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 18 May 2020 20:13:31 +1000 Subject: [PATCH 06/91] Update method comments --- .../Search/WindowsIndex/QueryConstructor.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index ab3d529ef..41b8040f3 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -1,4 +1,4 @@ -using Microsoft.Search.Interop; +using Microsoft.Search.Interop; using System; using System.Collections.Generic; using System.Text; @@ -43,14 +43,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex } /// - /// Search will be performed on all folders and files on the first level of a specified directory. + /// Set the required WHERE clause restriction to search on the first level of a specified directory. /// public string QueryWhereRestrictionsForTopLevelDirectorySearch(string path) { - // Set query restriction for top level directory search return $"directory='file:{path}'"; } + /// + /// Search will be performed on all folders and files on the first level of a specified directory. + /// public string QueryForTopLevelDirectorySearch(string folderPath) { string query = "SELECT TOP " + _settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE "; From ff03f7d284739be6ea78549ca9f677e7daa735fe Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 18 May 2020 20:14:38 +1000 Subject: [PATCH 07/91] Add tests and methods for all folders and files search --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 32 +++++++++++++++++-- .../Search/WindowsIndex/QueryConstructor.cs | 18 ++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 18426bafb..f4028b27d 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -37,9 +37,37 @@ namespace Flow.Launcher.Test.Plugins $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " + $"Actual string was: {queryString}{Environment.NewLine}"); } - - public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString() { } + [TestCase("scope='file:'")] + public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString) + { + // Given + var queryConstructor = new QueryConstructor(new Settings()); + + //When + var resultString = queryConstructor.QueryWhereRestrictionsForAllFilesAndFoldersSearch(); + + Assert.IsTrue(resultString == expectedString, + $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " + + $"Actual string was: {resultString}{Environment.NewLine}"); + } + + [TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemPathDisplay\" " + + "FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " + + "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:'")] + public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString( + string userSearchString, string expectedString) + { + // Given + var queryConstructor = new QueryConstructor(new Settings()); + + //When + var resultString = queryConstructor.QueryForAllFilesAndFolders(userSearchString); + + Assert.IsTrue(resultString == expectedString, + $"Expected query string: {expectedString}{Environment.NewLine} " + + $"Actual string was: {resultString}{Environment.NewLine}"); + } public void GivenWindowsIndexSearch_WhenReturnedNilAndIsNotIndexed_ThenSearchMethodShouldContinueDirectoryInfoClassSearch() { } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index 41b8040f3..b311b0b1d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -1,4 +1,4 @@ -using Microsoft.Search.Interop; +using Microsoft.Search.Interop; using System; using System.Collections.Generic; using System.Text; @@ -62,5 +62,21 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex return query; } + /// + /// Search will be performed on all folders and files based on user's search keywords. + /// + public string QueryForAllFilesAndFolders(string userSearchString) + { + // Generate SQL from our parameters, converting the userQuery from AQS->WHERE clause + return CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch(); + } + + /// + /// Set the required WHERE clause restriction to search for all files and folders. + /// + public string QueryWhereRestrictionsForAllFilesAndFoldersSearch() + { + return $"scope='file:'"; + } } } From 5484b305142acb4d79815307a0b918eb6c521290 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 18 May 2020 21:31:28 +1000 Subject: [PATCH 08/91] Add OleDb connection method --- .../Flow.Launcher.Plugin.Explorer.csproj | 1 + .../Search/WindowsIndex/QueryConstructor.cs | 68 ++++++++++++++++--- 2 files changed, 58 insertions(+), 11 deletions(-) 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 821c92714..b5a192c90 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -17,6 +17,7 @@ + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index b311b0b1d..59ff420dc 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -1,12 +1,16 @@ using Microsoft.Search.Interop; using System; using System.Collections.Generic; -using System.Text; +using System.Data.OleDb; namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { public class QueryConstructor { + public OleDbConnection conn; + public OleDbCommand command; + public OleDbDataReader dataReaderResults; + private Settings _settings; private const string SystemIndex = "SystemIndex"; @@ -18,14 +22,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex public CSearchQueryHelper CreateBaseQuery() { - // This uses the Microsoft.Search.Interop assembly - CSearchManager manager = new CSearchManager(); - - // SystemIndex catalog is the default catalog in Windows - ISearchCatalogManager catalogManager = manager.GetCatalog(SystemIndex); - - // Get the ISearchQueryHelper which will help us to translate AQS --> SQL necessary to query the indexer - var baseQuery = catalogManager.GetQueryHelper(); + var baseQuery = CreateQueryHelper(); // Set the number of results we want. Don't set this property if all results are needed. baseQuery.QueryMaxResults = _settings.MaxResult; @@ -33,7 +30,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex // Set list of columns we want to display, getting the path presently baseQuery.QuerySelectColumns = "System.FileName, System.ItemPathDisplay"; - // Filter based on folder/file name + // Filter based on file name baseQuery.QueryContentProperties = "System.FileName"; // Set sorting order @@ -42,6 +39,20 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex return baseQuery; } + internal CSearchQueryHelper CreateQueryHelper() + { + // This uses the Microsoft.Search.Interop assembly + var manager = new CSearchManager(); + + // SystemIndex catalog is the default catalog in Windows + var catalogManager = manager.GetCatalog(SystemIndex); + + // Get the ISearchQueryHelper which will help us to translate AQS --> SQL necessary to query the indexer + var queryHelper = catalogManager.GetQueryHelper(); + + return queryHelper; + } + /// /// Set the required WHERE clause restriction to search on the first level of a specified directory. /// @@ -67,7 +78,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex /// public string QueryForAllFilesAndFolders(string userSearchString) { - // Generate SQL from our parameters, converting the userQuery from AQS->WHERE clause + // Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause return CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch(); } @@ -78,5 +89,40 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { return $"scope='file:'"; } + + internal List ExecuteWindowsIndexSearch(string query) + { + var results = new List(); + + using (conn = new OleDbConnection(CreateQueryHelper().ConnectionString)) + { + conn.Open(); + + using (command = new OleDbCommand(query, conn)) + { + // Results return as an OleDbDataReader. + using (dataReaderResults = command.ExecuteReader()) + { + if (dataReaderResults.HasRows) + { + while (dataReaderResults.Read()) + { + if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value) + { + var result = new Result + { + Title = dataReaderResults.GetString(0), + SubTitle = dataReaderResults.GetString(1) + }; + results.Add(result); + } + } + } + } + } + } + + return results; + } } } From 7b69d98e5984d5a4cbabd9d73d5ebfa69e5be481 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 18 May 2020 22:23:20 +1000 Subject: [PATCH 09/91] Add Seacher class and move OleDb connection method --- .../Search/WindowsIndex/QueryConstructor.cs | 42 ------------- .../Search/WindowsIndex/Searcher.cs | 61 +++++++++++++++++++ 2 files changed, 61 insertions(+), 42 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index 59ff420dc..7a71c2519 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -1,16 +1,9 @@ using Microsoft.Search.Interop; -using System; -using System.Collections.Generic; -using System.Data.OleDb; namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { public class QueryConstructor { - public OleDbConnection conn; - public OleDbCommand command; - public OleDbDataReader dataReaderResults; - private Settings _settings; private const string SystemIndex = "SystemIndex"; @@ -89,40 +82,5 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { return $"scope='file:'"; } - - internal List ExecuteWindowsIndexSearch(string query) - { - var results = new List(); - - using (conn = new OleDbConnection(CreateQueryHelper().ConnectionString)) - { - conn.Open(); - - using (command = new OleDbCommand(query, conn)) - { - // Results return as an OleDbDataReader. - using (dataReaderResults = command.ExecuteReader()) - { - if (dataReaderResults.HasRows) - { - while (dataReaderResults.Read()) - { - if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value) - { - var result = new Result - { - Title = dataReaderResults.GetString(0), - SubTitle = dataReaderResults.GetString(1) - }; - results.Add(result); - } - } - } - } - } - } - - return results; - } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs new file mode 100644 index 000000000..3af30085d --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Data.OleDb; + +namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex +{ + internal class Searcher + { + public OleDbConnection conn; + + public OleDbCommand command; + + public OleDbDataReader dataReaderResults; + + private readonly object _lock = new object(); + + internal List ExecuteWindowsIndexSearch(string query, string connectionString) + { + var results = new List(); + + using (conn = new OleDbConnection(connectionString)) + { + conn.Open(); + + using (command = new OleDbCommand(query, conn)) + { + // Results return as an OleDbDataReader. + using (dataReaderResults = command.ExecuteReader()) + { + if (dataReaderResults.HasRows) + { + while (dataReaderResults.Read()) + { + if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value) + { + var result = new Result + { + Title = dataReaderResults.GetString(0), + SubTitle = dataReaderResults.GetString(1) + }; + results.Add(result); + } + } + } + } + } + } + + return results; + } + + internal List WindowsIndexSearch(string query, string connectionString, Func constructQuery) + { + lock (_lock) + { + var constructedQuery = constructQuery(query); + return ExecuteWindowsIndexSearch(constructedQuery, connectionString); + } + } + } +} From d029c8d1a1b74d96f83b0d93ecf75ede59a2c373 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 18 May 2020 23:38:03 +1000 Subject: [PATCH 10/91] Update project properties --- .../Flow.Launcher.Plugin.Explorer.csproj | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 b5a192c90..2b8fe291f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -4,18 +4,25 @@ Library netcoreapp3.1 true + false - ..\..\Output\Debug\Plugins\Flow.Launcher.Explorer + ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Explorer - ..\..\Output\Release\Plugins\Flow.Launcher.Explorer + ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.Explorer + + + PreserveNewest + + + From f7d6c6f4b143e54aa2da7d1d569eb1d7e0d28667 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 18 May 2020 23:39:40 +1000 Subject: [PATCH 11/91] Update plugin descriptions --- Plugins/Flow.Launcher.Plugin.Explorer/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json index 4a6c8785c..35e8cc3ac 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json @@ -2,7 +2,7 @@ "ID": "572be03c74c642baae319fc283e561a8", "ActionKeyword": "*", "Name": "Explorer", - "Description": "Search and manage files, folders and subfolders. Explorer utilises Windows Index Search", + "Description": "Search for and manage files and folders. Explorer utilises Windows Index Search", "Author": "Jeremy Wu", "Version": "1.0.0", "Language": "csharp", From dd319c61b31db58454888e319ea5cee8f188fca0 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 May 2020 00:07:17 +1000 Subject: [PATCH 12/91] Update project file to include icon image --- .../Flow.Launcher.Plugin.Explorer.csproj | 6 ++++++ 1 file changed, 6 insertions(+) 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 2b8fe291f..2de4a4149 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -23,6 +23,12 @@ + + + PreserveNewest + + + From be524cb6eac24d9437701815d4eff7fc49a2d76f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 May 2020 00:11:37 +1000 Subject: [PATCH 13/91] Update Searcher class to handle result creation --- .../Search/WindowsIndex/Searcher.cs | 74 +++++++++++++++---- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs index 3af30085d..d36948079 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs @@ -1,6 +1,9 @@ -using System; +using Flow.Launcher.Infrastructure.Logger; +using System; using System.Collections.Generic; +using System.ComponentModel; using System.Data.OleDb; +using System.Diagnostics; namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { @@ -11,44 +14,85 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex public OleDbCommand command; public OleDbDataReader dataReaderResults; + + private PluginInitContext _context; private readonly object _lock = new object(); + public Searcher(PluginInitContext context) + { + _context = context; + } + internal List ExecuteWindowsIndexSearch(string query, string connectionString) { var results = new List(); - using (conn = new OleDbConnection(connectionString)) + try { - conn.Open(); - - using (command = new OleDbCommand(query, conn)) + using (conn = new OleDbConnection(connectionString)) { - // Results return as an OleDbDataReader. - using (dataReaderResults = command.ExecuteReader()) + conn.Open(); + + using (command = new OleDbCommand(query, conn)) { - if (dataReaderResults.HasRows) + // Results return as an OleDbDataReader. + using (dataReaderResults = command.ExecuteReader()) { - while (dataReaderResults.Read()) + if (dataReaderResults.HasRows) { - if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value) + while (dataReaderResults.Read()) { - var result = new Result + if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value) { - Title = dataReaderResults.GetString(0), - SubTitle = dataReaderResults.GetString(1) - }; - results.Add(result); + results.Add(CreateResult(dataReaderResults)); + } } } } } } } + catch (InvalidOperationException) + { + // Internal error from ExecuteReader(): Connection closed. + } + catch (Exception ex) + { + Log.Info(ex.ToString());//UPDATE THIS LOGGING + } return results; } + private Result CreateResult(OleDbDataReader dataReaderResults) + { + return new Result + { + Title = dataReaderResults.GetString(0), + SubTitle = dataReaderResults.GetString(1), + IcoPath = "Images\\Explorer.png",//<------CHANGE + Action = c => + { + try + { + Process.Start(new ProcessStartInfo + { + FileName = dataReaderResults.GetString(1), + UseShellExecute = true, + //WorkingDirectory = workingDir <----?? + }); + } + catch (Win32Exception) + { + _context.API.ShowMsg("Explorer plugin: ", "Unable to open the selected file", string.Empty); //<========= + } + + return true; + } + }; + } + internal List WindowsIndexSearch(string query, string connectionString, Func constructQuery) { lock (_lock) From c295585272d6dca918f26a1bb103d8fc4bea3dd8 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 May 2020 00:14:41 +1000 Subject: [PATCH 14/91] Update Main to handle basic files and folder search query --- Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 206911b6c..f4f63a3a6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -1,14 +1,16 @@ using Flow.Launcher.Infrastructure.Storage; +using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.Explorer.ViewModels; using Flow.Launcher.Plugin.Explorer.Views; using System; using System.Collections.Generic; using System.Text; +using System.Text.RegularExpressions; using System.Windows.Controls; namespace Flow.Launcher.Plugin.Explorer { - class Main : ISettingProvider, IPlugin, ISavable, IPluginI18n, IContextMenu + class Main : ISettingProvider, IPlugin, ISavable, IContextMenu //, IPluginI18n <=== do later { private PluginInitContext Context { get; set; } @@ -16,21 +18,14 @@ namespace Flow.Launcher.Plugin.Explorer private SettingsViewModel _viewModel; + // Reserved keywords in oleDB + private string ReservedStringPattern = @"^[\/\\\$\%]+$"; + public Control CreateSettingPanel() { return new ExplorerSettings(); } - public string GetTranslatedPluginDescription() - { - throw new NotImplementedException(); - } - - public string GetTranslatedPluginTitle() - { - throw new NotImplementedException(); - } - public void Init(PluginInitContext context) { Context = context; @@ -41,12 +36,28 @@ namespace Flow.Launcher.Plugin.Explorer public List LoadContextMenus(Result selectedResult) { - throw new NotImplementedException(); + return new List(); } public List Query(Query query) { - return new List(); + var results = new List(); + + if (string.IsNullOrEmpty(query.Search)) + return results; + + var regexMatch = Regex.Match(query.Search, ReservedStringPattern); + + if (regexMatch.Success) + return results; + + var queryConstructor = new QueryConstructor(_settings); + + var searcher = new Searcher(Context); + + return searcher.WindowsIndexSearch(query.Search, + queryConstructor.CreateQueryHelper().ConnectionString, + queryConstructor.QueryForAllFilesAndFolders); } public void Save() From 25ae9838e97eee508876365fcf2101e19032a450 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 May 2020 00:15:30 +1000 Subject: [PATCH 15/91] Add packages for Windows Index Search to Plugin as required --- Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 82fbb31d5..4f1a34639 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -59,7 +59,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + \ No newline at end of file From 80380b230a56aa25ed4d622e0f91bdef9926e80a Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 May 2020 13:40:38 +1000 Subject: [PATCH 16/91] Change Search to open using shared command --- .../Search/WindowsIndex/Searcher.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs index d36948079..21067056d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs @@ -1,4 +1,5 @@ using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; using System.ComponentModel; @@ -45,7 +46,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value) { - results.Add(CreateResult(dataReaderResults)); + results.Add(CreateResult(dataReaderResults.GetString(0), dataReaderResults.GetString(1))); } } } @@ -65,23 +66,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex return results; } - private Result CreateResult(OleDbDataReader dataReaderResults) + private Result CreateResult(string filename, string path) { return new Result { - Title = dataReaderResults.GetString(0), - SubTitle = dataReaderResults.GetString(1), + Title = filename, + SubTitle = path, IcoPath = "Images\\Explorer.png",//<------CHANGE Action = c => { try { - Process.Start(new ProcessStartInfo - { - FileName = dataReaderResults.GetString(1), - UseShellExecute = true, - //WorkingDirectory = workingDir <----?? - }); + FilesFolders.OpenPath(path); } catch (Win32Exception) { From ac2d318412a0e0c0154e466ceb987c47794317bb Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 May 2020 13:54:56 +1000 Subject: [PATCH 17/91] Rename Searcher class to IndexSearcher- more descriptive --- Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 2 +- .../Search/WindowsIndex/{Searcher.cs => IndexSearcher.cs} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/{Searcher.cs => IndexSearcher.cs} (97%) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index f4f63a3a6..1740e0e5b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -53,7 +53,7 @@ namespace Flow.Launcher.Plugin.Explorer var queryConstructor = new QueryConstructor(_settings); - var searcher = new Searcher(Context); + var searcher = new IndexSearcher(Context); return searcher.WindowsIndexSearch(query.Search, queryConstructor.CreateQueryHelper().ConnectionString, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs similarity index 97% rename from Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs rename to Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs index 21067056d..35b4a76e2 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/Searcher.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs @@ -8,7 +8,7 @@ using System.Diagnostics; namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { - internal class Searcher + internal class IndexSearcher { public OleDbConnection conn; @@ -20,7 +20,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex private readonly object _lock = new object(); - public Searcher(PluginInitContext context) + public IndexSearcher(PluginInitContext context) { _context = context; } From 107fc75d9782aa3dbaf24b05bf99f130f8f83d02 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 May 2020 18:46:42 +1000 Subject: [PATCH 18/91] Add file type icon images --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 4 ++-- .../Flow.Launcher.Plugin.Explorer.csproj | 16 ++++++++++++++++ .../Images/copy.png | Bin 0 -> 501 bytes .../Images/deletefilefolder.png | Bin 0 -> 19719 bytes .../Images/file.png | Bin 0 -> 290 bytes .../Images/folder.png | Bin 0 -> 468 bytes .../Search/Constants.cs | 16 ++++++++++++++++ .../Search/WindowsIndex/IndexSearcher.cs | 8 +++++--- .../Search/WindowsIndex/QueryConstructor.cs | 2 +- 9 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Images/copy.png create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Images/deletefilefolder.png create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Images/file.png create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Images/folder.png create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index f4028b27d..6f3c41aff 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -24,7 +24,7 @@ namespace Flow.Launcher.Test.Plugins $"Actual: {result}{Environment.NewLine}"); } - [TestCase("C:\\Dropbox", "SELECT TOP 100 System.FileName, System.ItemPathDisplay FROM SystemIndex WHERE directory='file:C:\\Dropbox'")] + [TestCase("C:\\Dropbox", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType FROM SystemIndex WHERE directory='file:C:\\Dropbox'")] public void GivenWindowsIndexSearch_WhenSearchTypeIsSearchTopFolderLevel_ThenQueryShouldUseExpectedString(string folderPath, string expectedString) { // Given @@ -52,7 +52,7 @@ namespace Flow.Launcher.Test.Plugins $"Actual string was: {resultString}{Environment.NewLine}"); } - [TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemPathDisplay\" " + + [TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemPathDisplay\", \"System.ItemType\" " + "FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " + "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:'")] public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString( 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 2de4a4149..0261da36b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -27,6 +27,22 @@ PreserveNewest + + + PreserveNewest + + + + Always + + + + PreserveNewest + + + + PreserveNewest + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/copy.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..8f1fca752f87511c2a310b66f50f5eb60c2534fb GIT binary patch literal 501 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCmSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&3=E7#o-U3d z8I5me7foS%_Sik8XR}IS(n#wnctn%m-p4XMCcbM7wh&i5%%SE z-8`H{e3Op;`f_jS`**E(@_mX<{{3`*_jDJ@Q*qwfn^~(i zGtXgf)oq*?#=!70V`6&Db%q3yiamB8l@6*EtePs$c}?k?;=z4fh29mBZkDQhzB1}@ zw(j)`_;p}M|2E<5p2nMhuNJz=sxK0L?a~-D;hedvo2<6egJ)|V>nJ)osGo{-YGG*D z&9d^>y=%AQxA*^cK9w1u)p`A{`je%e&u26J)6OyE3<;Plu(8KaZ_?h}%FIG49IFI z5`TSR9m|fa1%6R)y%er7Fn`^DOfX4)LyK->1oN~7oNJcd-+v*e;+|M_eEvrM2%&5R plRz+fF@NW>3qM#EIb`f(n0vqFn0I?{Bru8?JYD@<);T3K0RYUm%ozXx literal 0 HcmV?d00001 diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/deletefilefolder.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/deletefilefolder.png new file mode 100644 index 0000000000000000000000000000000000000000..024cc92915c48cba2f706ba344cba0c8d8bcbce5 GIT binary patch literal 19719 zcmYhibzIZm_c(qxxa5jK=k0qInR0)nK{Qk#lWQU)nfDjlMLAh1ya($Xa% zQX(A##`b;g_5M75j~{<*&po;K+|%dWbI*-7Hqv9F=cNY#FkREvHU$6-U-P`<;%w@C$LZ0-PUp)27!+R9)-n&8SZfIJ zyEUA-^zCoX%j@zt!X>yJ(^8lyWt1XLiQQk2n39-t{;1w5bpCVVXo zi)$ZDuNAecS-52F9F|nwt`y8S`gh5{apq+5_+aun48j|LR2&{36nP;So~;D;g8^<4 z{PfYB;B>IxUpf3%w%19WfUCRv-~1Y&0Dwc|X5dOBjsK+0d=}$!^-aFVn!(I~0)Trl z*cba*{iQFBvD#GoD1QxU!QJ9=wti>+?&=N^(0fNae?lHAFI+y%bW6B8IGBOH=d z_91e|zXkmTmQzt)Q1OQ1jKXXoWl{dO-uzlU^H81s52S~S8do*sG z;BrnkzG~+@FKR|{v%=wY6tt9##t~PNjC(sGKNk*-CC~rIh@1L|kYn-4k5?~HGNTBq z|4U{cHWr;Coww#)8i%>sey>T`0RY?qb#`tgR!|Af$^>CceuH@N;)UOR6OFmnPfR`h z(90qIN{BatyO*k(XZpKg*O@3F3O5PMSl4kBh2A=}rU-yo*DZenwVH!68B?{q#sWzjk2?f>B+JW*xE30PCnU7bTD({ zCcm(wFn&0WdVz~!l!g|Dgj9d|=pcsW!Sa|4cf`vxOs4_AWTw%~@6D~a8)y8M$6hLZ z)pM>E3pcKjE7K%1*#AyYkhS)*cB$^L5rXE!^T)IhgYK^t{wbJPS^7T#SwGrEeOdFC z)f=Wxm^!P>s132fI-p#P_)(9N@r8=98#lygCxX?HRiT5?{`iCI}vXJ+evGb62 z@cthLuX!aPj@s)ah}@XiZT~GgliSyH38IhY1wmhi(+sjZ zhFqA)lhV@CqEDX&hMyP~-oXJd&umP!6?BV6#IUHZXQGT<2+TL5xRwCGZ}6% zIH>;oN)AXFecHv|f}MF4C~`qzqCX>!cCF9IrrBpy_BId?w^kiw0BuQBk6&^m-kf7c z8m+zMw@}~Xf3^}&{q{3AY&_;G%7m;H=1k{Euv?l%Dv)<_TEwwU(7qM+CHr#>ivo`J z;wJ|McS>u8C`kM`TKwABT}rWxYcFt5$J=@!g9imzmIpr>xvdH9g^o8U9ct213d=V{ z$(~49dUnb6s`m)(<$pEZFvw+Ln3^M46f`G<=QoPUq5=RN59uO0GX5OT&04Cb4fgi` zEwg#hF?wDXW7_gZ>#T!R^uU10IAuZPJ7C?tu<$-THPw3eq{@Btp4S)Zm-93)rX{H;K=mdUiO_gd%+*)q zVr1AIw(OIU1As0rZ-6=)&TZ$Gstk--+eSbKzn#~+f1_8=yKVU9moi)5xN3ZRn#UCT zS>?T)JF^YnhYKnywh2|~pD7E(&y~WYR_=LhUV>39ZD(HE&-SA63n?fq^`xW#@6Ln6 z=B&P~%RBFQ`D6QPJs#Z}p5*nP-OllxHn->1Clnw`QKF#tO&Qo?Vn<>0hQUv=8{1x5EgLLZRXIFOIsTC+3=v!J^ zq&Z_{S(()tA>Oe`ND&?LVK`cDZ_0rCZ2gfiP(x38SF&l<=IH&RU_*e<#$+?) zczpFc=4j!w7Tk7r$VC|Viy*&M;Fgnj6iP8TI@Pft%W%UJ!R&(0uJb9?V?QsMmA z(+S|w1TYy9SUtp)W8~&i>RZD?`OA;48@9d@XCV44&Td<vbNewbI@Soc3RxkChL&OqFcf@}2wfw<@}ekO`>LVqf4x-d=3f24bwn(X;Xg_)p9i zY;|WRs?xe)Zj2Z^CS)&VM^&xoq>6v1cuGNXGNzhLNO!%Z*hKgrpE}T!r6JdomLxJqSf);F4jUEzD|nVBX9s3LKO)WUjR zP1zUB&eIsEQk**I(((lBP=uIn_P%ind%HO+!Gn#^yi!=F4Wkzt)xElkDs?x-Vr$!u zF1b+qBtc?Aiq{{ne@lkp|9PYB-oQFk;gv?hH4djmXL&>`iW?dlm?=O2Ejpo6EpkR~ zpii%wp18BrHztA)NPpVn$+Y^LFK@MOZ$Rxlvgu3#7TQ--SueKX?&qiawytYC!~==zU05bJbtofT;DMd1T{`G~6X#Pfox zAO#od+ySZk&niToeS^iYoMsbdc^q`vtIN0JiB>f2#W!jgmIs8fFk3=k?MmIBvU|y! z2SvOKo9d4Fgfmb>_}K04 zL7oaZ26O7I`#<8`?o3rv7rN07}v7pQXz%hpcnyV>CK zfqLYwws*0be!jW1W}dl7S()ujQFx$lWSy7eH@Ew=vI-PGd3kxiCMtp(ycmAS8I25K zw5ivgn6{K%CWXrq)u)2Uh=~$z|8RzA!Mr5tGN5f4(3e=`kJM|t?aGNK{#>%w z%WsMmh0oz^ zA;=~r9zAteDMXjrxjT;W_K3ggq$)RaW9Yan>j186wUWj@Ia-?IddcbZ=$;z2RjF-N zTj{7Qb#{33OdewgnPCZ7wIaa~$DzzvlIb;Bg5sNZan8hYPGXr8UFd3M)oQ#a-_UCH zVr?*NaBxtEE3nkQAw3w*jj4Qiw@EmS!v9hHZHRk!fG`Rl_hYW+7(Zg!bQ7B|Q9LF+ z7T{|vML0y~DXl!ts`HEEBq<;L?SC>VIv39K@9#l?3F$Ny4wygXhTRX4=YBu*!0?@; zb}TwSquh=W`7Z)%#gpE$(Ykdb4t~+=RAsY#>~YK}6>XuNyj^Jo`M={7CJTiMdS<YG9D=WRka}4$K@a|ypX5Y#6rj79VT#ICUxu=)Z zH9UiNf2Cmd=Jzx1unt;B2*RW}6X(s0f&&M{-G!|)uX*h zn&p_Fz0fg)dAjPbr}L)WNV{&@i&F6CJUVN#+<#qvp8@OQi~pzD=meiS-t~4*?p2kC zn0=n~jFvP#IbtqGP%|xj%|Rshjug^~?K!-fd9eBY6QAz+2d|A zFAJxjB-h<%M=w@ewnUT1VmE0_+5BQ?SSS=GcONQGh8AB}xn)~!+CugdY5gc}%<8il zD>XuZ(D9nnt&^qvUl!8bm8$gFdh;Sjh2uOo{=}a2w5yJvBb?KLO-xLTA+jx1Rb)4r zKA{Spr?n*GMe)Q76k8(*^R4AlX#tN?XY*NGPeAu+DPwJPp-)lGcfR} zCAE(+MQ^-$<1c@>WIXBb=U1zJ=!d3MB0YZ?h*OTzuiE7e(Vk)D*-;6!q}w8d@t)AP zr-}T~-onk#@3jZaHFsk4PBW@YC#EFdQtOlUZ1 zAO5@adxBE?&nuhYF-ZTiKIxt)9icYWZ73f6{yy?DVrD&@6>`nOSr0Cj>@q!Je#4~w z5$!~Wh&{;Wck;yfwWpNmWyIkyL=Tj^Darv@a)$fh-5(IxTFh2&Ai}y>(dSrC)5{A{ zSsUR$C#c$w6|L8od@k7Riqzzdv+X&X90M=0t%?z#IM;Azg(dv=kfSYL$ngua1&jNn zG8a=tC+*r#>TG5Thw9~5EvWq8q~*PXA)-~EbG#635%S_oMfi5p5b35QTk=@G*qfX5 z`1dgx6g*oqk8C1s%_Qgg5czja01H|XX8b))S|cZY*A>+&G)MC-aj2=Vx7>P=;@Ls8 z63@^)w(ZNP&?2j~FkxYHbPKO8XP?I!9dWc?9(n%J@z#oX94~tjs`?3l!XKl)oO00l@fau2t zuNhw8gP%Tg^sG-@ZI>Jk4v2>}W~+s-*j9(|Yq?U$3;f2DqdLQJTwmNM`j@ZI^=*CU z^DZC~i8?bWJLd~8SEOS4!uLP-Cj6MENZ$PXm3hhuo~#HDslp?WlsLw@4U>h1&V( zJFzd~WoVsV2|IJF-xoYdr)OP{433Z5+Z**+w^#l|ob)}WmkAW=&zjaWYn$Ps#_)&x zMj(W2geA}YX#*YW!COJ~%Nu_de@;si)T+Ivi|m6}KIoKRwa>b50w(oNZ1KKtj(^f; z_b$6DUD|b4t_qmRUBieRJZq4^n)m&WorGb{gcpgvYCF$X{O+>8wZGE83G!Vh6HUFny;_Wz5bZ4faL8&+ z@8K38+uB?$E|-gfckC>DgijgBG2rBKAx5=@f~o5*LkUA3yKK?}yIUW^owHWT2iRUE zENq6@;T!x2j~~OJRA%d$&=rlN6#kGJU6@?`<`r_9J*H&AV(`vVwgvrz9<2aRgQt#d zGPiw%AMGupBUj4%Tq%&J5Y65bTPYLL*7OwDc9e2j?TY>pP9g|RQui{vWmPyH)TjU& zMX<*P8S`J6BU-d``@wI^rlb|2;^cwy90>=pN@3$4)Gj#XP?!@0B2bNjZNS3j@m2f@UQ_?ylPeU7vGCL{ zdbl6+H!TpG)Jf5L!{$!Mn(_@2R{tlb-B-TteZ#6!C7!}M3H1)(G&KlAk>CII7DzkR2kOF{~BtU!D2T5?qhz)tcCQ` zZMih@o-BO-y=0}(z`ye%fiwQ*C32WGE@M~vTOn-M-dUE4qGMz* zsIRfKNS@|$G7I~T>lgYUm)ddQM59?Vcxm6CL*MKdSLjohkAOcG=u2mL$^Vtl3b;bx zX*+UnUE4cQ}_ZqLMS0K3VeZIk^CH=6$@u4 zW@hAp%G!k2hg6$+(6+ccHrz}7SIFt`h^*f!2_953CYWY+B*fNsKS)_h^H6%!xbofQ zk&O_F>^2m==$e&Bd+eq!p~%(Etn#g~_6Eq;N5zN&Tfzr7l)PxuM8ZCgV#onr>8JdZ zScjnru=(fx=lEND0vMtomBBhb?*G$egd$^Q!@sTH?ZYdB`K2W9i7(e~`*ey+vqlGf z#|+QoS*a2=Uom1rTR{iAR>OV44YQ^N@7faz{6%<07N1IU78}vZ{*g|@$^IlmBYLr$ z2iicG_;<n7F zkN(C+VEg$mA58fs)6Is-o#2}s`dN>zunF_P_mlwZk+(qJkUVBa6mX(z~$Mn)67RGTgS=G{QwBs*9KX8T>J`Z^Jg!7?G4RXs=z@vkIr+KLiQpJ5eP z9U@_ygZ{54rN2I~f&^mhPH{R~z!Eaw&+E|PgmExlXik;nuOEe4#Ot)4`!I1mAiN>o zX&9XY(-&hMIcVh{dQxja0vOor^M5C!XNH%YT2ZB&4A3^YAZT~v;IC_;6lRF3;0^5b zmP?)xV8rBmkCiO<(PB%cQjD>ow`E`U22A^pJ`}Kc9C6IRgdt!#O~AR`az5Yyd^$!@ z(=UG3@7Vf4IT$^u+H6cz8u>od*CC$}VQe*25B z^zWK#L8DmlqAQ z$uf!3rdgYk{#ttfR@m+ldZCvV1GBzF?~npR;xiH9%4&qR#5X4*gzc!!(M5d9?SQo4 z!#^opr2~m=m=8uZubX?UH1l{{9#R2rOzxMOnuEBwxPK-VuPJq*>5T&9mDJ+r2kku|do9X2bN6=#@`)=m~B@SU|4Mi~b z;A@c-F2DScE(Z-5kv40C|Fp^z_NNK99diexTfUI}{M1sSPJU3CgC+zhi)z9~2>M{BHkGFOb6ocl|Ql%rR;bZH_VI~BJ%i4KLf z6zYv7X+gi^R2Q$Dd=O5;Fr)D;{s?s8DQj2YgawE12p(fhjB4iaSCB&;-ntw#Wx{Ht%5B}ZIx5w;JYFa$G+|1 zm42iskiKm|g`@8g5o6Ppn%X}A7ca1(srYHx)bnb_qG{p&dN@?@oE)=@hB@d!X`+#k zx+FoV4<*Z|>g8uRBf~+J`tF_mPjI1I&}5*ny>2OXqX9hlhejLN?MdN6?8Ir3t~OGtk^=gOaLc158C{~@(etuws? z*xM(f)DBQwvjwRMB~(%uKm-wnQ>3VH8!jRa3=D)@IOQXKpm~8u_%y_6al3ppZrqC(uj?%;xFVp{ToSzXPN8!)W0$+;8?Ofr_?pdhS>lu?`V!y~L+@=7 z$D{`-6qpGfj7%k`16GJUWhvg%k#Hph2a(Xh{#J6gNBl+XYG+@;o9QnPHLU!6wL22^MxdZo8*y;9^dJ@A^bn%n3 zWH#zi^bZUu=8mT*!*qXH|HqP(w=LcWTX};C#rpFc>mI=PfEKfv=ykvu3r{3Nk&K*4 zbBuhMU+RMepiBXbOChs}J~edBx~H%^WSu(yCEje42263lPCM~B)MP<85+6Y|8X~^O zY^JLEhzdM-16y8R=H?d^OqN2H^Ldx`X&k)uj6Lp{L&=am1>uZQeQCO+%^ovYe*tO3 zKNS!#bxQ|Q9<=Ep=ao}v&p@n)0EstH2|Or=Ew8P8k)?N7>Z#)0(!+BxKsHb{eNySX z`pptLx5M?YfH~v`>g;6EA`ZP<@3&ycdga=OOBp>EYs;NNn{$!QDRb z`5DC{9aAE}$xu|7vS*JpSKl9$aQ_qjsS41N?vi1JH)rMf&+|!NKIROR2MTgT}szN^c3 z!3&|OMp&f93i}hCcbS2IKK9P>U-c+p9^Ue*3LG~`y!|KysoVh_RyZ%bRR~;Wft~Kn zLU`-IPE!Z|w~@3N=<5)|&|=C(fQ?fEhKA*y51xfMudu`n?3F=S-8Cg&n&w*}|B3qd zjjYDQ#f*{WYlp$pb^fcBU2C`DXJaPQ9OR0oJ_P1{#ZNIG_5k(Ko=DEOS0oZB5pZ|WD<^o!@|9>hI_4S7K2V*=# zQUTJ+DrGIcc9+I9HvD)jtI&A+LZBqrtcG>y!j5)RzOjUdBtS~G^PjDA8Z{pL3W4gD zQ`gbb1Q-S-0-77|QiHkjwJ|a-p*$-$KA&^ELWzpVIt&M!nK0{y;5`Qw`hL5ca~Dp? z_{tNM=F9J$Xy-hBnTUpXp1MH;W^_SB!_ne&?533jSs#4~gTOOjvj&zZh3oH!>hoX@bw~z!j!^l)G#$!iM2H$18Y7~4;Q$AnLSk-1 zn@i%)Gm+?bP`PdQ)9q$?21#)$1=je&#pp;2zaydZ<&Te5DUtm= z1Z|h)tQuj5?1}t|<#nsle_7^ez$@CXd#%r(KacU^SR3YrK_Lnei4Yne)6*rW%_X#+ zvYXr&A%MngMCFOpBuqW3glf#YZWF5T{%>St1l~Cw#h5I!?EW)XzFFvf00NwbRV4T> z^>W0)gS5sv=2Xu`qR7Bf2ErW)Tt!Jg8-O;50vDhEf9`I_2cP#i@XPE$;rE;t>D0Sl zn8D)j0E+(nNkNNd%O)>Q6(4+0*W8THfUUpt=zBNA6QqvrsaIGst86O+{!Ju7%l+$0 z2ae>(;Zkuv5(_12rJfr3D&^|Ma^D;-ysAO8F~Onkn!SZH)9a;T^vF}#Kr+L^S%U7@ zmQVg6#W{|Qa{1NQY^JbHaRb~jrNPBc2x{jg%(PK#Ldn9Zp2osSJ85q8ApP+V62;kO z$W zEPrW0pyl-dy()2KY$6vv2R0x+kZnIHo_hF;O0znI-RvGMu6Y3cxs@_#2^vmdg++x6 z=Uy??`lSs0MDSp$drW>QLXUh3_vnS|v=WGXPqj(ph1p>akXHq>=P4@gh4A^3pfTa+ zL6XH>NKi5K9C{C2P=EB_lM}crkNQVFRjjL5Y= zUC!%Shh!OK=|IgS6ae7+fx;1l>I0CnL6u1E6#=s?5Ul{RfASwR1Zq1Wf8fX;0BYwU ziyQjW{66dte%2$PM{V-&{|)7sUpK{dLnJ^pMSck#+XG-8H5o{;AOHauCNBUqqo5CD z{BByo_4X?}By@fi;b}=sF;Xuyoc(LKZQ)0;%vfkPrcQy)wD2G=xx)_CmB)F!tFQe* zPqY_-Re?1z4f2>h7>L;89=n_akGsav5iUf=%9yHf=F2_Zou6O6=1cQ*s^39YE-40T z_Vtpi4Ui5NQ%ZD?Z`pAy^|X=^P}E*#M+i_vJ{t`tm%l%fX>yW!S(&xn) z=+V3upAikXbU|7#fv!{-4gxn6=MjZC?5au&4Sm2~T<)BX?8d0Ta{l%y>OfrxpkU4w zec^7jX5PuAJ?#2UtNf^qVI^Jj1Z_v~f735MDY31IKp@%$R*COg{a@)L@qv8`8sw=J zCQf2&GIzpN&F7!an>-$fNtc#v$|Ka4MePuBLUHNd1T4h#ERN;6q&TT-IZ`|;RUtMe3wD)+xlK^1+vjA1puJP^hYFNb> zdw6-55?~5^D`rPVJZO;`E#bxyonw!XaX;;RV?NSOmJNBI+_j>xN`pw#3Cldq1c?XJ z*%3$hB)53<*cJJPvX&p<*u}CC`N?Qv+?5*WZ-ib9B@6;g&e zlh=3D1J~Ojps)3>#G(yi(U;Ws^9LBc8m>OiAj26lAn+VMZC99}{2Iap2A_g=xa)gY z2~FQ5kfmxt54fOaIShO&;iNBig?6)!E{JUH#=^vf!>7z+d4|aHYm(goI569tZx!44 z9%9X`=)OCfLgnIY(^JUb;c8mP#%Kuc@H)tU&)#rq5}Ie${BZTd;7#Uff2+vJo3F29 zfWx`Zq{@-&hmQ2jp`P&9tZWsIJvmoJxiR#!`!EO2!a&>7o-ly)cV`MQM%1ZjB%Xz= zti2*Z+k9=H$2=-;V3DSxVQIb2T@@1k`sg7AHE(3cxBZ+^(zj>Lc^Q{6+V%*>9n2v4A>H4R9X=j1@(Q_NfQV zWp??^qp0#`tJ@!*+`c_R+PT)apML$+siIb>r6+>O>v1|OAyiv=Su=%r%HLmh&3k(E z>pcBB&`1)*bhk_=eWb^L2Bck;NxX zdVOv!FgJDSD;mp7C>ke*%RTX-%Ky-|y3vu3@C>`XUG?$U_UP8pFA7FiiU+b#6BK|- z^t0ZJ8*=Auqj(WJJU3oQ>*yAqXGVBvT)8hwToxidw1Ij$`9+9@gNEN@&^x~xVw-#4#QZcsWu=E5-It4TcAcmymi}zP&6G+MBDF9~`}~{O zKx50yU@~w@%(fS<&jU%=iI>cqcrKv+RUujU=&)~6f_UZX%>;h~X5)hrU{(I>aGNwf zkS7$3r-gl`u-QZGEZ=i%!?x*ep@grGqP%-|X!eL<}0v{s7TCud285l79w-eGY zW7f(Rlut$5*PXIwct<#AEV@-|tB+;tIbFe|IYcn9)b=^~jZpDU!P>?kB(kgLH_ov2 z`|kIu2i}EL^-V`JVvwFVbd_-81$UDr3M#>v-PnwG8sJa>s~{|+#d7a474M;LZXFcU z6{O%pS}8l8+3qMct*ZbJDZ{+shPG2_RhxZG^Bbyc#@<^8QFH}|PmmD)6=I!%w*Kc` z=CU$cXXGVtvhy1pY~)yBb(*wA^X7Vfoa^Jod!x}LIGeD@eSK8&#G-vlSA_vaQBGsO2LpSMHQ%~OjYdl{nO30__7U1g(^*E02$p66+)Q?*8ERgV)(sFo$+P>dy} zu!z2DoiS%oKB@onmd_hIT_A-!R*r%@K>X<#K;w&wOALby+Ab)1s)Qcpu6)j_648CG z10#3<{pODYZqo)lC#qrVzTY!s&9tk=DcreAN;3lgiO?r{=T6H(H4OEa?Gtf4CpUKB zJ>xoVUbor$*K*dVqPe44^luua`}{bNq8WuNP(jV}-Ndd-j$RA)&7~rfYoCU6SkNtH znZFJ;zUDDNCLRCsl7RBPZ>VypAuFbIv=tHgFe%UP$R0A{ivXg)*o6 zI{W{f!rF77Z|p8iC>CDI)`odur;{?_kis~4?qzTQ}Bl6g(FJ_!uGtrQw^b~Xm&w?(OL5IcpGYP@b6{Bsa)XYrfzAqPFvywylIfD1~ z+}znew8%zoRaeC_3+#~=!I%2ymxWt~qiK`g8%>ZSErnE!&6MO5vCDLsGxl)4;eKI~ zR1IF6Fu3yNx{*O@@X-O8)0XeIv#zxoy{-5N2&btEo?SfFA(Xo7@wMY;vkUXFJ1=@- zVG#P7x}2n*Q-0}zzw(U=3k%QA)Za9U{*u*-N_5vP6S(>*Oi`E`;M&C!s`&zYbC5{! znV_pZ-Q9Qz`q>EUV?)AHS&Hh+g+u4DOH7b5ZYoAQF7kld`-Y?i$&u0DzkfFdW7k+c zvC-J>lbZ$x1w;Lb>0vA4uTa4d+kPUi`_p{FEQYMk>@pwWjteIwJlPOcQFKC;nop4xNvXs0)WuTPvs`|vCvTtwbnE`hsoyLMx|qBjx}_N;=-b6 zn+xebKPnPGXE`%s#>Z5cW>=@dXXn!}9E??O@`c8Mt|yS#W760^*(m<5zVXEX(?2q@ zm~n8k^;Q&O?{(hCCZ|^J#j?1SjZnS9&TAZ}o*AHRrd_M&k#bfXu{?l<(0%Qw8INGj;|5&8+S5m-P^8?Ri1{T)9fHEhAWZf_IZiyf-zAd&z6Sv)lf}CbL!Vde zlnUGO62!P)>}c{`fYl?1lT&L$1~i{_x@H&HX-p>mj^t8#lMK# z6&uhjkEXtR+e(QyAz)AbQH?&fD&MIec?u;GKU-(=^3V{Au;N4__u-P+@FjAjqy}st z3Y+$yTe?8;psrK39P()R<0e4ZAIw_E0f39?fFl#kubeBaax%l2PEfvZgkP2^zx*- z7#V$qNewp$vYcstUSs%4Du8ec@`WyttclLOVgv?Ye&c+d^le(m95?A!lBh4iAe7js zAY|}yL;4X$QupAE`^pBGj5ZP%HkFIC_Wh>u^75*7wV5 zt(3_Acay07Os>w=CI`{cgA!Rul_~P3Fn}NO=UoFz$x-hd2l#IarXt?el0L(J|)>EWe{1eY`iCvtmk*1+s}&GKs%cb~C*H~*uKeN10+PK4UH30kh_hxJf+RrG znw5kVc%4b#!45F(*GwsCA$ZU&6dc-^nhctJAq7C+A+O{ham&QR`}xoEU4|4I&Sv}) zf75j@kSo9=;|M0R?H)LppoaNHT*56$U=62exB5AI&+;YXD5 z$~TUUo+$49IA@7Ao-0ol+TA`*HHFkbMF&3eE2P}*ctJ93hD)fP6z)Eh6}oy>?u9My ziPFOpb^x6`IKVx>1&R_Ml{9W>4*Tu&_-V)K-ww*D5dp0I`X6b{fcaGg4K2wbB!T7I z{E0~Saf&0p?JQ!=Fjwm-Y>YC?N!4>ZY9X(+v_NTV|GXu%c5pNnb zD0ux`tT_Qcm|!nCFnxP}=4%+stiyWjiZ!xII~opN-w@WwG3EPavD~A&^1@9!wm!f` z1co@~$<(5BP_*;XP;&wI=3zzG;O6?`7R_L*{F)eM{+flTS!lY;WoJ;iYo}V_Cjr^u z9~Ps`&S_z=)lL~SFsrH>A`ee=?j4ST3DW~(!JMI$81K_ji1`FM1qryqybgte!jjL= zGyym)JC_eq>{KAH3zW=4DWH1;(6WydX2$i|k(F?e?I*(IZEd0K`a!uB+bDr?uc%M1 zU#pBcQg`j}f2cr0u$gITo(0aPD~3;!5JOOpTQucs0oYizrybUc37114=~H3~AxC!K zfZt?gxOgRswG|NqrH71pKhJ84G9m+b9TZ3*cYzWg6%2VTkCG8UNN)0vci5xF(R?b) z{(}9UH;Jm>B`AtM%a%UrrUf!L#1yk7+1xb``bkB>-)Z8t%KAKU8Ry$`1}KoGW6>` zkjHixZq0B#Pj@yqqHH{q|ff)aF*>s zxQu>|Fh-=K8fQ+atsO+VeBlVYYx-!pe0Rg}HUjHjD5Ot`6oGxZc=ickV8Tr{CIDf< z`UJrjfbr-D@TlE;tqs=gdMRJrr0O^gI`R~g=-x)tZMr`~X}8!(;5M9}{`t&*F=Su= zLzt@vby$FOws-AJbRTueSy(O%I{U@hHNJqCTp>I{X*_q@wf3B@3!i~b;8>}w1GcWp z@hT_@zV{J!0583Z&F}p>odkZNo_6vaUgrY5-k{P3rvBwSa|ye2nxV9^QcfIB+qkEd z!s+>7==gh)>?{!w&K|k7^|1I3yxy=Vh81TY4Z72S#oaRz+AmKLq>1OxpSRrkK$qy#D~K)gm3`Esyj_{KRRudO$H|sBA|YIAh>X6P z^PZEql7`_O&~*qh&wzQ-&$IIAD;m6iRP*d*ewYcf!|=($u_eJ5|C18G5SB7bG9CXs zL5mzaC*ZS{ZxqK*ntR4d7i3$r?n6{5)Ine3>^CHG8K3ctb$Xif{DH(X;R7^~KOHY* zvh`2MWOb{Z1;!jh>i*nUw5h{YxVhNlJLB%|zVn9ebbb9}<6Or;XOS?P_koXfYCF= z2<(uaNVZo^&_WtRLxloswXkSl|FhDCuk?uk+L*@d^zKA=uyPL#p`HHl`K%wAG`Aef< zlJiX(C$|$LZMft`qT}CzT`3GLVjgQu@O6MM-X}4Ww3Kg!e%dM*l_$)2cZK0@ay^F7 zdZe3;g5lZI;Kqe?Z^k~gjE$b2sA#JDBub2J6Jj$K_LG(MWuP#W=&KN_{D2|^?R4?N zYImY}tUtX~z({(>m_?S_&W~_5QFC6of2>OSUiS$SDQVxkG`vFl7q1}ahUChb!$-kB zrcCHR=Ngq^tgwn)9cnJpwcXv08$9Dsql*?Ja!=R6xF*u?Roj!yy6Mu%UmkuKMmTgd zmU{Uuk45Nio^Cwxg#Acfnpn#cPKU+{v(7rQ34a_gFPTTUk94Ha{JV6|TC11wg_C-K z){-D;?z`MczYgVy!`31J@8nmXE*W0l2obNPAyqVaQ2F@fa!!l9C%8gaZsE4oh zacf(@zh{R0@|!;MEj}71b>@1u^+j62KTt!Hf*7E(@GaimPC}+KR9{CS_-~ zw?Wxes)yWG_G`|bUimsfy5AoM@3&&<2_En(cGcX0y^rQd&?O*H@rq)*Zw;zqw06dr zToYrBf;QkHcXJYL^3Z3;-gKVSe84k9Imh@Cj~P zDWg**&n}7F_4Gw8#%KJLn61BQfVrhEVL|kD+f9J-qcm#nA?8Z%{;1cu3c;qH-rit) zLMHE_JQv;OQ#yst%h$i0+c-ZCU9|#|6(3;+1&kL@icy6@m)8sZ!5%9bn+6o)x0*rMUXQ=(O0ZNlb&Uo*LaxQq2pG=m4Tua6O7J0 z>UR>UG6^K4VVsoiH^h&RqEzQY*{}y8wL@5#Xa6y@$D0)ktBYgrRo0cMmD|JAz!ESpG3AXA+v0A$+ZnhVSvoRcd0Fj=y|O# zIWg&xAe5}t=x2Wxg7L0fx_+O>`ReTqk&0WYcNVbbVmN4SDm1?9@LO$7sfOGl!aY8L z2r;(8haGw4WtvI!-$bYviRSAtWsnZRP!V<`8ttg^WbWSfivUAi(iMFeV4vH`A`w_l zD(sXmNDcogd!iTrb)ElDvYhIH&A70BZ1}A#$KCIIZ{B`3r+=X>BF$aWchMyYf8!bA zX&XXMyXG~m$K|qn@6h(jUNdD+1O$@ju75BkU(6MlCvl+_C5f_f8yiD^Qi@w$G+!NW zjDCTUOl04sKwE(lZYup>51w9ksTTA9K<7Ftg5aX-S0x7P!Rw`oI-;;T>|X&o1sg)C z?>jD1dfb0xu^u>kUq4hmYk)~;oAH#u`EAv35_>ZK9=jjirz*+o z(6P-rZUx5DDcmMFdm7H~Px_Mh{3=(4AYGEyy7Kbrh@qvIrQGe)*4}Khs$1nXbkJ4z zKfDb~?+AT^Wwr#ZZ*%FJnrA{<5T;Sioe=lh)|LzgJxzvgvX~d<95#3Cd(I}PQ zALX)G0OsYz;N4}4qdN)8!54kBpM8F^bWhvoyXo0CLkUb7s>Q?TqkQSHDUTBw{!_&x zBbLMc#z%q=JDq+`*sG|n_C=GT`q~g#*JMW&r{8CNz&sM2)){qc%Ijx3_|(q!nH#cqpAGjydvdF;iQJrgU6k>;IWML7K*z z4PdH#93MBprs5M#2O?oRB7O%M- zmHuc;N0)dm73BP5CK*=|n}lQzzx|x~xgm0NLvSM!@uhmt5Gqkg64P5x4(wLG*pROU zp)~p;i#dR_B8*x3ESrpKl3BhCn*;n%0-0!XKZEl_f1SU$(A5a^QJTw8gy3G+XcT%y z0(oR}q>kVsF|-X)2`}~?)V@k)h)34Pq!=RmGMGE_ z-rK+TbAI=n-#O=V?m6ds&Q*LE?4Sddz9u#<|4z%(u(O3e^Zj33PnC666E7D#Utax4 z`6Tu{Z}&?@eG&`-J9m@4TSh@iLGy;F$LC&gmUrgfX$dr(9TavJurt(lxR)NYyKaE5 zTR5h+&OGrUq2Uz?Hx5t9c%G=Zz5nb+eX`(V1pCYn-3_F=-Hzv>GP!a!eHb{vm2*C1 zwB*uFu1NR?J^S;hkKZ=~@&`go#B?f&QcXFy#X(Xxg zjN4TsMu9{vw8#1iw~_2-*K8fV`Ez}Yumm*>uhQUe*fz;~E5Lp2e!t|hUg@asDu<+~ z&Sec2)}xeuk7+KA0ZZTG251coTSk8C!9DpdRJsT>+NnbZ>a)ylzS*zh{X9OOVm-r! zC<-=VrDcW4GO4dQs#XQ0)ZdbtEjc7AlCZ{*)w6ZhAB;f}e$NM&w{#7wWUOUFpa$tQI?P$p*&D|1eb zi3d8%dbwmi{kS^x^ZI5q@(TyU9VpTwvjOE(UNFbba{{97_9~K9Q^cU`7?L>{I@{#k zP9UOqK?_-3^hUw-i+2x}5SwP;eZAba0X)kEtZd`*{VQHVjX&U-1fIELnr>zX$Wn8! zz2rK{LG?+17vEEYA_O|bshcpO5Bli&b+d8`J@A=RfQ>L2eifkraL4hcgic_fbV8dF zVCO_or>ntvb;C=MXb!u7VZ|42=85pUYEk>(Nli{YPTX8dYuZch6CjeD!Si%RT`z|) zl+bI*Y}Iwvf`dyk5`WJHpaZqxV(o#V)EB+X-EcXn@o;S=K@b2pjAHTm)h?1!Qj>Zm zYylnKRnYMnH(NV6c`FO)SVuAe=XtE?uw)+fzEb=vy@I?A7qKzjiw#FM)*u!(W|$dj z^X3qXBk|w0T)EYi?!&HU7v^R^RV-7o3w|8@oXVb5hohT&d^M&^O6^1 zF3=%3TwE)cHF~74H%7pN#;B6)iHVL9`g9+Rl$&sojG|?6!qBQaT{uC;o|a6-ROWo? zq!q_o3sBvF(beA3kxKW52(=+b`}0%?&e?pg@3&C^HRB{tq1_Le|4p}ssX}|JZ?W5K zt-tzu3LS;L1~5+|Jl^&Ath#i>Hbo%E0gB(f9MhHt!L4H;D~kW?WJ?%_p|v|R)3p0acvn*niHA%No@d6dI{2Q~ZkA{?=rFr-lH zR8u~w=n|p%;8NUE{X-vjHPM&2$GJ=LF+txuOAwb3B}>hC-pY|v(?nf~<*;sqXFqKs zglc`A>%6-mJw=512s&lLg9=d=`_PRlG!@~d(7jy)K3MZwW%7e|+^oUX1b9vSHgH8^ zxOK3;uc=Wjfnp0&)x;s`e?Ar0RZi+bpFb}8{2{~B<1OdwyKwI}19eA>uCjty9uU(< znak~13V^$lzmd>B-W5}*P^oO)68)_c?A5`J5O$O<-73~3qmS&=>IW0e96+0xTIRo3 z*rIlgR!1<1$x>Rv31LfsgF2{PCPZR>??*y880Km~dRZ+=NuZ<%D!>D+)iNsxi~kVO zmS=EX&&LSLia{s9-Ma4;9uPLLo8HFyG(X}lVXO?|q?}8*t|AAA)snkPN3k>B3(AW) z4Cy=vZ!e!d->6P4?WPn=%0uwtZ(g_+tRNfi9gxA!24nsq87HKMmHDGpv@4YUtl{gIFNeIqOgFKEf1x zB{CrW=)f?nbD}-y)Z~yUTM84F#QTxI!wd8=!F5c|;GcWya>=C61vZgGHoi>7DLfUr7PPY{S*LK&(m(a@bsTPn%X8)vo3| zR|DV9OgyIbAtn0m#R~c;oo-)AA&g?XcUW^Q;$X=EJM-1Q(#AX9w)MSBQu!Tz0A%yU a8omH#B09jfr)wU0a`eba`zl-C`2Pd0VS!Zu literal 0 HcmV?d00001 diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/file.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/file.png new file mode 100644 index 0000000000000000000000000000000000000000..36156767a6572d31205104d16a8e909c5af378b4 GIT binary patch literal 290 zcmV+-0p0$IP)=FA?pM#)7Nq6>`@rGF3#p%4mjka3Wys(?018&wG;f~}U&3=E8co-U3d z8I5me*m@mtkZ_2f6nK;6kLH<03QBPa#m%|8dmGy~l)trY&e*zi=~{MyEqCH>uzqLg z-ee)R(a_j+)5M=XYzIoGgL`n3r5+$TV?~ zLSY^gH{i|-ls|r!cY{r+O=Pm>F}@8wk^OH?4jUc2 v!FV(!YHrE@-m;$hpQ5isz&^hGvmt+Oh5d!HHwsq37+~;p^>bP0l+XkKMAE;S literal 0 HcmV?d00001 diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs new file mode 100644 index 000000000..2be1dfd7a --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Flow.Launcher.Plugin.Explorer.Search +{ + public static class Constants + { + public const string FolderImagePath = "Images\\folder.png"; + public const string FileImagePath = "Images\\file.png"; + public const string DeleteFileFolderImagePath = "Images\\deletefilefolder.png"; + public const string CopyImagePath = "Images\\copy.png"; + + public const string DefaultFolderSubtitleString = "Ctrl + Enter to open the directory"; + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs index 35b4a76e2..022a70cdf 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs @@ -46,7 +46,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value) { - results.Add(CreateResult(dataReaderResults.GetString(0), dataReaderResults.GetString(1))); + results.Add(CreateResult(dataReaderResults.GetString(0), + dataReaderResults.GetString(1), + dataReaderResults.GetString(2))); } } } @@ -66,13 +68,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex return results; } - private Result CreateResult(string filename, string path) + private Result CreateResult(string filename, string path, string fileType) { return new Result { Title = filename, SubTitle = path, - IcoPath = "Images\\Explorer.png",//<------CHANGE + IcoPath = fileType == "Directory" ? Constants.FolderImagePath : Constants.FileImagePath, Action = c => { try diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index 7a71c2519..d74a11b48 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -21,7 +21,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex baseQuery.QueryMaxResults = _settings.MaxResult; // Set list of columns we want to display, getting the path presently - baseQuery.QuerySelectColumns = "System.FileName, System.ItemPathDisplay"; + baseQuery.QuerySelectColumns = "System.FileName, System.ItemPathDisplay, System.ItemType"; // Filter based on file name baseQuery.QueryContentProperties = "System.FileName"; From 06667b7aa76e4f81ba18d8a62225f7a279b5c49f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 May 2020 20:10:46 +1000 Subject: [PATCH 19/91] Add tests for TopLevelFolderSearch behaviour --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 69 ++++++++++++++++++- .../Search/SearchManager.cs | 23 +++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 6f3c41aff..780feae3f 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -1,13 +1,41 @@ +using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.Explorer; +using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using NUnit.Framework; using System; +using System.Collections.Generic; namespace Flow.Launcher.Test.Plugins { [TestFixture] public class ExplorerTest { + private List MethodWindowsIndexSearchReturnsZeroResults(string dummyString) + { + return new List(); + } + + private List MethodDirectoryInfoClassSearchReturnsTwoResults(string dummyString) + { + return new List + { + new Result + { + Title="Result 1" + }, + + new Result + { + Title="Result 2" + } + }; + } + + private bool MethodIndexExistsReturnsTrue(string dummyString) => true; + + private bool MethodIndexExistsReturnsFalse(string dummyString) => false; + [TestCase("C:\\Dropbox", "directory='file:C:\\Dropbox'")] public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestrictionsShouldUseDirectoryString(string path, string expectedString) { @@ -33,6 +61,7 @@ namespace Flow.Launcher.Test.Plugins //When var queryString = queryConstructor.QueryForTopLevelDirectorySearch(folderPath); + // Then Assert.IsTrue(queryString == expectedString, $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " + $"Actual string was: {queryString}{Environment.NewLine}"); @@ -47,6 +76,7 @@ namespace Flow.Launcher.Test.Plugins //When var resultString = queryConstructor.QueryWhereRestrictionsForAllFilesAndFoldersSearch(); + // Then Assert.IsTrue(resultString == expectedString, $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " + $"Actual string was: {resultString}{Environment.NewLine}"); @@ -64,12 +94,49 @@ namespace Flow.Launcher.Test.Plugins //When var resultString = queryConstructor.QueryForAllFilesAndFolders(userSearchString); + // Then Assert.IsTrue(resultString == expectedString, $"Expected query string: {expectedString}{Environment.NewLine} " + $"Actual string was: {resultString}{Environment.NewLine}"); } - public void GivenWindowsIndexSearch_WhenReturnedNilAndIsNotIndexed_ThenSearchMethodShouldContinueDirectoryInfoClassSearch() { } + [TestCase] + public void GivenWindowsIndexSearch_WhenReturnedZeroResultsAndIsNotIndexed_ThenSearchMethodShouldContinueDirectoryInfoClassSearch() + { + // Given + var searchManager = new SearchManager(); + + // When + var results = searchManager.TopLevelFolderSearch( + MethodWindowsIndexSearchReturnsZeroResults, + MethodDirectoryInfoClassSearchReturnsTwoResults, + MethodIndexExistsReturnsFalse, + "path string not used"); + + // Then + Assert.IsTrue(results.Count == 2, + $"Expected to have 2 results from DirectoryInfoClassSearch {Environment.NewLine} " + + $"Actual number of results is {results.Count} {Environment.NewLine}"); + } + + [TestCase] + public void GivenWindowsIndexSearch_WhenReturnedZeroResultsAndIsIndexed_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearch() + { + // Given + var searchManager = new SearchManager(); + + // When + var results = searchManager.TopLevelFolderSearch( + MethodWindowsIndexSearchReturnsZeroResults, + MethodDirectoryInfoClassSearchReturnsTwoResults, + MethodIndexExistsReturnsTrue, + "path string not used"); + + // Then + Assert.IsTrue(results.Count == 0, + $"Expected to have 0 results because location is indexed {Environment.NewLine} " + + $"Actual number of results is {results.Count} {Environment.NewLine}"); + } public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString() { } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs new file mode 100644 index 000000000..c2a250ae7 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Flow.Launcher.Plugin.Explorer.Search +{ + public class SearchManager + { + public List TopLevelFolderSearch( + Func> windowsIndexSearch, + Func> directoryInfoClassSearch, + Func indexExists, + string path) + { + var results = windowsIndexSearch(path); + + if (results.Count == 0 && !indexExists(path)) + return directoryInfoClassSearch(path); + + return results; + } + } +} From 78b797e43b8f011c932139f634a7d48b890bd454 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 May 2020 22:36:56 +1000 Subject: [PATCH 20/91] Add tests for checking string is location path --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 24 +++++++++- .../Search/SearchManager.cs | 46 ++++++++++++++++++- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 780feae3f..38090572e 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -104,7 +104,7 @@ namespace Flow.Launcher.Test.Plugins public void GivenWindowsIndexSearch_WhenReturnedZeroResultsAndIsNotIndexed_ThenSearchMethodShouldContinueDirectoryInfoClassSearch() { // Given - var searchManager = new SearchManager(); + var searchManager = new SearchManager(new Settings(), new PluginInitContext()); // When var results = searchManager.TopLevelFolderSearch( @@ -123,7 +123,7 @@ namespace Flow.Launcher.Test.Plugins public void GivenWindowsIndexSearch_WhenReturnedZeroResultsAndIsIndexed_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearch() { // Given - var searchManager = new SearchManager(); + var searchManager = new SearchManager(new Settings(), new PluginInitContext()); // When var results = searchManager.TopLevelFolderSearch( @@ -139,5 +139,25 @@ namespace Flow.Launcher.Test.Plugins } public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString() { } + + [TestCase(@"c:\\", false)] + [TestCase(@"i:\", true)] + [TestCase(@"\c:\", false)] + [TestCase(@"cc:\", false)] + [TestCase(@"\\\SomeNetworkLocation\", false)] + [TestCase("RandomString", false)] + public void WhenGivenQuerySearchString_ThenShouldIndicateIfItIsLocationString(string querySearchString, bool expectedResult) + { + // When, Given + var searchManager = new SearchManager(new Settings(), new PluginInitContext()); + + var result = searchManager.IsLocationPathString(querySearchString); + + //Then + Assert.IsTrue(result == expectedResult, + $"Expected query search string check result is: {expectedResult} {Environment.NewLine} " + + $"Actual check result is {result} {Environment.NewLine}"); + + } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index c2a250ae7..1dd834428 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,11 +1,53 @@ -using System; +using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; +using System; using System.Collections.Generic; -using System.Text; namespace Flow.Launcher.Plugin.Explorer.Search { public class SearchManager { + private Settings _settings; + private PluginInitContext _context; + + public SearchManager(Settings settings, PluginInitContext context) + { + _settings = settings; + _context = context; + } + + /// + /// This checks whether a given string is a directory path or network location string. + /// It does not check if location actually exists. + /// + public bool IsLocationPathString(string querySearchString) + { + if (string.IsNullOrEmpty(querySearchString)) + return false; + + // // shared folder location, and not \\\location\ + if (querySearchString.Length >= 3 + && querySearchString.StartsWith(@"\\") + && char.IsLetter(querySearchString[2])) + return true; + + // c:\ + if (querySearchString.Length == 3 + && char.IsLetter(querySearchString[0]) + && querySearchString[1] == ':' + && querySearchString[2] == '\\') + return true; + + // c:\\ + if (querySearchString.Length >= 4 + && char.IsLetter(querySearchString[0]) + && querySearchString[1] == ':' + && querySearchString[2] == '\\' + && char.IsLetter(querySearchString[3])) + return true; + + return false; + } + public List TopLevelFolderSearch( Func> windowsIndexSearch, Func> directoryInfoClassSearch, From 690d60b7011135f40c852ad2aa0d335e66abf7a8 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 May 2020 22:38:42 +1000 Subject: [PATCH 21/91] Add the use of a SearchManager --- Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 12 ++---------- .../Search/SearchManager.cs | 11 +++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 1740e0e5b..80f872cf6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -1,10 +1,8 @@ using Flow.Launcher.Infrastructure.Storage; -using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; +using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.ViewModels; using Flow.Launcher.Plugin.Explorer.Views; -using System; using System.Collections.Generic; -using System.Text; using System.Text.RegularExpressions; using System.Windows.Controls; @@ -51,13 +49,7 @@ namespace Flow.Launcher.Plugin.Explorer if (regexMatch.Success) return results; - var queryConstructor = new QueryConstructor(_settings); - - var searcher = new IndexSearcher(Context); - - return searcher.WindowsIndexSearch(query.Search, - queryConstructor.CreateQueryHelper().ConnectionString, - queryConstructor.QueryForAllFilesAndFolders); + return new SearchManager(_settings, Context).Search(query.Search); } public void Save() diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 1dd834428..80a764d50 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -61,5 +61,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search return results; } + + internal List WindowsIndexFilesAndFoldersSearch(string querySearchString) + { + var queryConstructor = new QueryConstructor(_settings); + + var searcher = new IndexSearcher(_context); + + return searcher.WindowsIndexSearch(querySearchString, + queryConstructor.CreateQueryHelper().ConnectionString, + queryConstructor.QueryForAllFilesAndFolders); + } } } From bf6a9d616a315982166518a229b241efaeb4e7af Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 19 May 2020 22:40:27 +1000 Subject: [PATCH 22/91] Rename parameters in IndexSearcher --- .../Search/WindowsIndex/IndexSearcher.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs index 022a70cdf..837c316b0 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; @@ -25,7 +25,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex _context = context; } - internal List ExecuteWindowsIndexSearch(string query, string connectionString) + internal List ExecuteWindowsIndexSearch(string searchString, string connectionString) { var results = new List(); @@ -35,7 +35,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { conn.Open(); - using (command = new OleDbCommand(query, conn)) + using (command = new OleDbCommand(searchString, conn)) { // Results return as an OleDbDataReader. using (dataReaderResults = command.ExecuteReader()) @@ -46,9 +46,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value) { - results.Add(CreateResult(dataReaderResults.GetString(0), - dataReaderResults.GetString(1), - dataReaderResults.GetString(2))); + results.Add(CreateResult( + dataReaderResults.GetString(0), + dataReaderResults.GetString(1), + dataReaderResults.GetString(2))); } } } @@ -91,11 +92,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex }; } - internal List WindowsIndexSearch(string query, string connectionString, Func constructQuery) + internal List WindowsIndexSearch(string searchString, string connectionString, Func constructQuery) { lock (_lock) { - var constructedQuery = constructQuery(query); + var constructedQuery = constructQuery(searchString); return ExecuteWindowsIndexSearch(constructedQuery, connectionString); } } From 66334947e2a82ec20238fe736a2700679375b932 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 23 May 2020 16:37:29 +1000 Subject: [PATCH 23/91] TopLevelFolderSearch- Change name to reflect behaviour --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 4 ++-- Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 38090572e..4ca665cab 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -107,7 +107,7 @@ namespace Flow.Launcher.Test.Plugins var searchManager = new SearchManager(new Settings(), new PluginInitContext()); // When - var results = searchManager.TopLevelFolderSearch( + var results = searchManager.TopLevelFolderSearchBehaviour( MethodWindowsIndexSearchReturnsZeroResults, MethodDirectoryInfoClassSearchReturnsTwoResults, MethodIndexExistsReturnsFalse, @@ -126,7 +126,7 @@ namespace Flow.Launcher.Test.Plugins var searchManager = new SearchManager(new Settings(), new PluginInitContext()); // When - var results = searchManager.TopLevelFolderSearch( + var results = searchManager.TopLevelFolderSearchBehaviour( MethodWindowsIndexSearchReturnsZeroResults, MethodDirectoryInfoClassSearchReturnsTwoResults, MethodIndexExistsReturnsTrue, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 80a764d50..6d74775cc 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -48,7 +48,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search return false; } - public List TopLevelFolderSearch( + public List TopLevelFolderSearchBehaviour( Func> windowsIndexSearch, Func> directoryInfoClassSearch, Func indexExists, From 903984807834ec78ab0508aeaab6a5448c529b88 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 23 May 2020 16:40:31 +1000 Subject: [PATCH 24/91] Add method in search manager to handle top folder level win index search --- .../Search/SearchManager.cs | 41 +++++++++++++++++-- .../Search/WindowsIndex/IndexSearcher.cs | 11 ++++- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 6d74775cc..6e8a61095 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; +using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using System; using System.Collections.Generic; @@ -9,10 +9,31 @@ namespace Flow.Launcher.Plugin.Explorer.Search private Settings _settings; private PluginInitContext _context; + private IndexSearcher searcher; + public SearchManager(Settings settings, PluginInitContext context) { _settings = settings; _context = context; + searcher = new IndexSearcher(_context); + } + + public List Search(string querySearchString) + { + if (IsLocationPathString(querySearchString)) + { + return TopLevelFolderSearchBehaviour(WindowsIndexTopLevelFolderSearch, + DirectoryInfoClassSearch, + WindowsIndexExists, + querySearchString); + } + + return WindowsIndexFilesAndFoldersSearch(querySearchString); + } + + private List DirectoryInfoClassSearch(string arg) + { + throw new NotImplementedException(); } /// @@ -62,15 +83,27 @@ namespace Flow.Launcher.Plugin.Explorer.Search return results; } - internal List WindowsIndexFilesAndFoldersSearch(string querySearchString) + private List WindowsIndexFilesAndFoldersSearch(string querySearchString) { var queryConstructor = new QueryConstructor(_settings); - var searcher = new IndexSearcher(_context); - return searcher.WindowsIndexSearch(querySearchString, queryConstructor.CreateQueryHelper().ConnectionString, queryConstructor.QueryForAllFilesAndFolders); } + + private List WindowsIndexTopLevelFolderSearch(string path) + { + var queryConstructor = new QueryConstructor(_settings); + + return searcher.WindowsIndexSearch(path, + queryConstructor.CreateQueryHelper().ConnectionString, + queryConstructor.QueryForTopLevelDirectorySearch); + } + + private bool WindowsIndexExists(string path) + { + return searcher.PathIsIndexed(path); + } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs index 837c316b0..56ed8ddc6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs @@ -1,5 +1,6 @@ -using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.SharedCommands; +using Microsoft.Search.Interop; using System; using System.Collections.Generic; using System.ComponentModel; @@ -100,5 +101,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex return ExecuteWindowsIndexSearch(constructedQuery, connectionString); } } + + internal bool PathIsIndexed(string path) + { + var csm = new CSearchManager(); + var indexManager = csm.GetCatalog("SystemIndex").GetCrawlScopeManager(); + + return indexManager.IncludedInCrawlScope(path) == 0; + } } } From 9b416bf75fd8f96a821552fb28b07b7ee656a23a Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 24 May 2020 19:09:44 +1000 Subject: [PATCH 25/91] Add EnvironmentVariablesPath handling --- .../Search/EnvironmentVariables.cs | 90 +++++++++++++++++++ .../Search/SearchManager.cs | 16 +++- 2 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs new file mode 100644 index 000000000..dc48512aa --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Flow.Launcher.Plugin.Explorer.Search +{ + public static class EnvironmentVariables + { + internal static bool IsEnvironmentVariableSearch(string search) + { + return LoadEnvironmentStringPaths().Count > 0 && search.StartsWith("%") && !search.Substring(1).Contains("%"); + } + + internal static Dictionary LoadEnvironmentStringPaths() + { + var envStringPaths = new Dictionary(); + + foreach (DictionaryEntry special in Environment.GetEnvironmentVariables()) + { + if (Directory.Exists(special.Value.ToString())) + { + envStringPaths.Add(special.Key.ToString().ToLower(), special.Value.ToString()); + } + } + + return envStringPaths; + } + + internal static string TranslateEnvironmentVariablePath(string environmentVariablePath) + { + var envStringPaths = LoadEnvironmentStringPaths(); + var splitSearch = environmentVariablePath.Substring(1).Split("%"); + var exactEnvStringPath = splitSearch[0]; + + // if there are more than 2 % characters in the query, don't bother + if (splitSearch.Length == 2 && envStringPaths.ContainsKey(exactEnvStringPath)) + { + var queryPartToReplace = $"%{exactEnvStringPath}%"; + var expandedPath = envStringPaths[exactEnvStringPath]; + // replace the %envstring% part of the query with its expanded equivalent + return environmentVariablePath.Replace(queryPartToReplace, expandedPath); + } + + return environmentVariablePath; + } + + internal static List GetEnvironmentStringPathSuggestions(string querySearch, Query query) + { + var results = new List(); + + var environmentVariables = LoadEnvironmentStringPaths(); + var search = querySearch; + + if (querySearch.EndsWith("%") && search.Length > 1) + { + // query starts and ends with a %, find an exact match from env-string paths + search = querySearch.Substring(1, search.Length - 2); + + if (environmentVariables.ContainsKey(search)) + { + var expandedPath = environmentVariables[search]; + + results.Add(new ResultManager().CreateFolderResult($"%{search}%", expandedPath, expandedPath, query)); + + return results; + } + } + + if (querySearch == "%") + { + search = ""; // Get all paths + } + else + { + search = search.Substring(1); + } + + foreach (var p in environmentVariables) + { + if (p.Key.StartsWith(search)) + { + results.Add(new ResultManager().CreateFolderResult($"%{p.Key}%", p.Value, p.Value, query)); + } + } + return results; + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 6e8a61095..e2e195f4d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; +using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using System; using System.Collections.Generic; @@ -20,8 +20,20 @@ namespace Flow.Launcher.Plugin.Explorer.Search public List Search(string querySearchString) { - if (IsLocationPathString(querySearchString)) + var querySearch = query.Search; + + if (EnvironmentVariables.IsEnvironmentVariableSearch(querySearch)) { + return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query); + } + + // Query is a location path with a full environment variable- starts with a % + // and contains another % somewhere before the end of the path + if (querySearch.Substring(1).Contains("%")) + { + querySearch = EnvironmentVariables.TranslateEnvironmentVariablePath(querySearch); + } + return TopLevelFolderSearchBehaviour(WindowsIndexTopLevelFolderSearch, DirectoryInfoClassSearch, WindowsIndexExists, From 191efe31cbc24a1753e2d9fef5b3ef420ad57bd3 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 24 May 2020 19:11:40 +1000 Subject: [PATCH 26/91] Add ResultManager for handling creation of folder and file result --- .../Search/ResultManager.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs new file mode 100644 index 000000000..3ee7848d8 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -0,0 +1,59 @@ +using Flow.Launcher.Infrastructure; +using Flow.Launcher.Plugin.SharedCommands; +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; + +namespace Flow.Launcher.Plugin.Explorer.Search +{ + public class ResultManager + { + public Result CreateFolderResult(string title, string subtitle, string path, Query query) + { + return new Result + { + Title = title, + IcoPath = path, + SubTitle = subtitle, + TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData, + Action = c => + { + if (c.SpecialKeyState.CtrlPressed) + { + try + { + FilesFolders.OpenPath(path); + return true; + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Could not start " + path); + return false; + } + } + + string changeTo = path.EndsWith("\\") ? path : path + "\\"; + Main.Context.API.ChangeQuery(string.IsNullOrEmpty(query.ActionKeyword) ? + changeTo : + query.ActionKeyword + " " + changeTo); + return false; + }, + ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path } + }; + } + } + + public class SearchResult + { + public string FullPath { get; set; } + public ResultType Type { get; set; } + } + + public enum ResultType + { + Volume, + Folder, + File + } +} From 472f4a5f49308a873749f794f6e121ba29a1c4a8 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 24 May 2020 19:15:13 +1000 Subject: [PATCH 27/91] Move ReservedStringPattern to IndexSearcher class --- Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 10 ++-------- .../Search/WindowsIndex/IndexSearcher.cs | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 80f872cf6..e5c9e8e2f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Infrastructure.Storage; +using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.ViewModels; using Flow.Launcher.Plugin.Explorer.Views; @@ -16,9 +16,6 @@ namespace Flow.Launcher.Plugin.Explorer private SettingsViewModel _viewModel; - // Reserved keywords in oleDB - private string ReservedStringPattern = @"^[\/\\\$\%]+$"; - public Control CreateSettingPanel() { return new ExplorerSettings(); @@ -44,10 +41,7 @@ namespace Flow.Launcher.Plugin.Explorer if (string.IsNullOrEmpty(query.Search)) return results; - var regexMatch = Regex.Match(query.Search, ReservedStringPattern); - - if (regexMatch.Success) - return results; + return new SearchManager(_settings, Context).Search(query); return new SearchManager(_settings, Context).Search(query.Search); } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs index 56ed8ddc6..9f99a6248 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.SharedCommands; using Microsoft.Search.Interop; using System; @@ -6,11 +6,14 @@ using System.Collections.Generic; using System.ComponentModel; using System.Data.OleDb; using System.Diagnostics; +using System.Text.RegularExpressions; namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { internal class IndexSearcher { + private readonly object _lock = new object(); + public OleDbConnection conn; public OleDbCommand command; @@ -18,8 +21,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex public OleDbDataReader dataReaderResults; private PluginInitContext _context; - - private readonly object _lock = new object(); + + // Reserved keywords in oleDB + private string ReservedStringPattern = @"^[\/\\\$\%]+$"; public IndexSearcher(PluginInitContext context) { @@ -95,6 +99,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex internal List WindowsIndexSearch(string searchString, string connectionString, Func constructQuery) { + var regexMatch = Regex.Match(searchString, ReservedStringPattern); + + if (regexMatch.Success) + return new List(); + lock (_lock) { var constructedQuery = constructQuery(searchString); From df0871a924d64fe52aa9d45c03f605177bba3a22 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 24 May 2020 19:16:10 +1000 Subject: [PATCH 28/91] Fix incorrect return state on PathIsIndexed --- .../Search/WindowsIndex/IndexSearcher.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs index 9f99a6248..6081bf119 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.SharedCommands; using Microsoft.Search.Interop; using System; @@ -115,8 +115,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { var csm = new CSearchManager(); var indexManager = csm.GetCatalog("SystemIndex").GetCrawlScopeManager(); - - return indexManager.IncludedInCrawlScope(path) == 0; + return indexManager.IncludedInCrawlScope(path) > 0; } } } From c5c15d2ab398173664d9b1c62c01640e46ead543 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 24 May 2020 19:18:58 +1000 Subject: [PATCH 29/91] Move IsLocationPathString method to FilesFolder class --- .../SharedCommands/FilesFolders.cs | 33 +++++++++++++++++++ .../Search/SearchManager.cs | 30 ----------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 17c2dc511..440ac6756 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -127,5 +127,38 @@ namespace Flow.Launcher.Plugin.SharedCommands #endif } } + + /// + /// This checks whether a given string is a directory path or network location string. + /// It does not check if location actually exists. + /// + public static bool IsLocationPathString(string querySearchString) + { + if (string.IsNullOrEmpty(querySearchString)) + return false; + + // // shared folder location, and not \\\location\ + if (querySearchString.Length >= 3 + && querySearchString.StartsWith(@"\\") + && char.IsLetter(querySearchString[2])) + return true; + + // c:\ + if (querySearchString.Length == 3 + && char.IsLetter(querySearchString[0]) + && querySearchString[1] == ':' + && querySearchString[2] == '\\') + return true; + + // c:\\ + if (querySearchString.Length >= 4 + && char.IsLetter(querySearchString[0]) + && querySearchString[1] == ':' + && querySearchString[2] == '\\' + && char.IsLetter(querySearchString[3])) + return true; + + return false; + } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index e2e195f4d..1f2327e5c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -49,36 +49,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search } /// - /// This checks whether a given string is a directory path or network location string. - /// It does not check if location actually exists. - /// - public bool IsLocationPathString(string querySearchString) - { - if (string.IsNullOrEmpty(querySearchString)) - return false; - - // // shared folder location, and not \\\location\ - if (querySearchString.Length >= 3 - && querySearchString.StartsWith(@"\\") - && char.IsLetter(querySearchString[2])) - return true; - - // c:\ - if (querySearchString.Length == 3 - && char.IsLetter(querySearchString[0]) - && querySearchString[1] == ':' - && querySearchString[2] == '\\') - return true; - - // c:\\ - if (querySearchString.Length >= 4 - && char.IsLetter(querySearchString[0]) - && querySearchString[1] == ':' - && querySearchString[2] == '\\' - && char.IsLetter(querySearchString[3])) - return true; - - return false; } public List TopLevelFolderSearchBehaviour( From d839ecab26f74ab1f8ad54b6c74854364e3e64fc Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 24 May 2020 19:19:54 +1000 Subject: [PATCH 30/91] make Context static so can be called anywhere --- Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index e5c9e8e2f..e3f9e9abc 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -8,9 +8,9 @@ using System.Windows.Controls; namespace Flow.Launcher.Plugin.Explorer { - class Main : ISettingProvider, IPlugin, ISavable, IContextMenu //, IPluginI18n <=== do later + public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu //, IPluginI18n <=== do later { - private PluginInitContext Context { get; set; } + internal static PluginInitContext Context { get; set; } private Settings _settings; From 6badecb8770dd0416a9c41a5f2e0d442c2cc9994 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 24 May 2020 20:14:36 +1000 Subject: [PATCH 31/91] Add TopLevelDirecotry search using DirectoryInfo --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 13 +- Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 4 +- .../DirectoryInfo/DirectoryInfoSearch.cs | 114 ++++++++++++++++++ .../Search/ResultManager.cs | 51 ++++++++ .../Search/SearchManager.cs | 31 +++-- 5 files changed, 191 insertions(+), 22 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 4ca665cab..283e19fdf 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -2,6 +2,7 @@ using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.Explorer; using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; +using Flow.Launcher.Plugin.SharedCommands; using NUnit.Framework; using System; using System.Collections.Generic; @@ -16,7 +17,7 @@ namespace Flow.Launcher.Test.Plugins return new List(); } - private List MethodDirectoryInfoClassSearchReturnsTwoResults(string dummyString) + private List MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString) { return new List { @@ -111,7 +112,8 @@ namespace Flow.Launcher.Test.Plugins MethodWindowsIndexSearchReturnsZeroResults, MethodDirectoryInfoClassSearchReturnsTwoResults, MethodIndexExistsReturnsFalse, - "path string not used"); + new Query(), + "string not used"); // Then Assert.IsTrue(results.Count == 2, @@ -130,7 +132,8 @@ namespace Flow.Launcher.Test.Plugins MethodWindowsIndexSearchReturnsZeroResults, MethodDirectoryInfoClassSearchReturnsTwoResults, MethodIndexExistsReturnsTrue, - "path string not used"); + new Query(), + "string not used"); // Then Assert.IsTrue(results.Count == 0, @@ -149,9 +152,7 @@ namespace Flow.Launcher.Test.Plugins public void WhenGivenQuerySearchString_ThenShouldIndicateIfItIsLocationString(string querySearchString, bool expectedResult) { // When, Given - var searchManager = new SearchManager(new Settings(), new PluginInitContext()); - - var result = searchManager.IsLocationPathString(querySearchString); + var result = FilesFolders.IsLocationPathString(querySearchString); //Then Assert.IsTrue(result == expectedResult, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index e3f9e9abc..e23fbdb78 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Infrastructure.Storage; +using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.ViewModels; using Flow.Launcher.Plugin.Explorer.Views; @@ -42,8 +42,6 @@ namespace Flow.Launcher.Plugin.Explorer return results; return new SearchManager(_settings, Context).Search(query); - - return new SearchManager(_settings, Context).Search(query.Search); } public void Save() diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs new file mode 100644 index 000000000..7b55697a9 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -0,0 +1,114 @@ +using Flow.Launcher.Infrastructure; +using Flow.Launcher.Plugin.SharedCommands; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Windows; + +namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo +{ + public class DirectoryInfoSearch + { + private PluginInitContext _context; + + private Settings _settings; + + public DirectoryInfoSearch(Settings settings, PluginInitContext context) + { + _context = context; + _settings = settings; + } + public List TopLevelDirectorySearch(Query query, string search) + { + var results = new List(); + //var hasSpecial = search.IndexOfAny(_specialSearchChars) >= 0; + string incompleteName = ""; + //if (hasSpecial || !Directory.Exists(search + "\\")) + if (!FilesFolders.LocationExists(search + "\\")) + { + // if folder doesn't exist, we want to take the last part and use it afterwards to help the user + // find the right folder. + int index = search.LastIndexOf('\\'); + if (index > 0 && index < (search.Length - 1)) + { + incompleteName = search.Substring(index + 1).ToLower(); + search = search.Substring(0, index + 1); + if (!FilesFolders.LocationExists(search)) + { + return results; + } + } + else + { + return results; + } + } + else + { + // folder exist, add \ at the end of doesn't exist + if (!search.EndsWith("\\")) + { + search += "\\"; + } + } + + results.Add(new ResultManager().CreateOpenCurrentFolderResult(incompleteName, search)); + + var searchOption = SearchOption.TopDirectoryOnly; + incompleteName += "*"; + + //// give the ability to search all folder when starting with > + //if (incompleteName.StartsWith(">")) + //{ + // searchOption = SearchOption.AllDirectories; + + // // match everything before and after search term using supported wildcard '*', ie. *searchterm* + // incompleteName = "*" + incompleteName.Substring(1); + //} + + var folderList = new List(); + var fileList = new List(); + + var folderSubtitleString = Constants.DefaultFolderSubtitleString; + + try + { + // search folder and add results + var directoryInfo = new System.IO.DirectoryInfo(search); + var fileSystemInfos = directoryInfo.GetFileSystemInfos(incompleteName, searchOption); + + foreach (var fileSystemInfo in fileSystemInfos) + { + if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue; + + if (fileSystemInfo is System.IO.DirectoryInfo) + { + if (searchOption == SearchOption.AllDirectories) + folderSubtitleString = fileSystemInfo.FullName; + + folderList.Add(new ResultManager().CreateFolderResult(fileSystemInfo.Name, folderSubtitleString, fileSystemInfo.FullName, query)); + } + else + { + fileList.Add(new ResultManager().CreateFileResult(fileSystemInfo.FullName, query)); + } + } + } + catch (Exception e) + { + if (e is UnauthorizedAccessException || e is ArgumentException) + { + results.Add(new Result { Title = e.Message, Score = 501 }); + + return results; + } + + throw; + } + + // Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. + return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList(); //<===== MOVE ORDERING OUT + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 3ee7848d8..796650e58 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -2,6 +2,8 @@ using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Text; using System.Windows; @@ -42,6 +44,55 @@ namespace Flow.Launcher.Plugin.Explorer.Search ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path } }; } + + public Result CreateOpenCurrentFolderResult(string incompleteName, string search) + { + var firstResult = "Open current directory"; + if (incompleteName.Length > 0) + firstResult = "Open " + search; + + var folderName = search.TrimEnd('\\').Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last(); + + return new Result + { + Title = firstResult, + SubTitle = $"Use > to search files and subfolders within {folderName}, " + + $"* to search for file extensions in {folderName} or both >* to combine the search", + IcoPath = search, + Score = 500, + Action = c => + { + FilesFolders.OpenPath(search); + return true; + } + }; + } + + public Result CreateFileResult(string filePath, Query query) + { + var result = new Result + { + Title = Path.GetFileName(filePath), + SubTitle = filePath, + IcoPath = filePath, + TitleHighlightData = StringMatcher.FuzzySearch(query.Search, Path.GetFileName(filePath)).MatchData, + Action = c => + { + try + { + FilesFolders.OpenPath(filePath); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Could not start " + filePath); + } + + return true; + }, + ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath } + }; + return result; + } } public class SearchResult diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 1f2327e5c..6119f635f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,4 +1,6 @@ +using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; +using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; @@ -18,7 +20,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search searcher = new IndexSearcher(_context); } - public List Search(string querySearchString) + internal List Search(Query query) { var querySearch = query.Search; @@ -34,33 +36,36 @@ namespace Flow.Launcher.Plugin.Explorer.Search querySearch = EnvironmentVariables.TranslateEnvironmentVariablePath(querySearch); } + if (FilesFolders.IsLocationPathString(querySearch)) + { return TopLevelFolderSearchBehaviour(WindowsIndexTopLevelFolderSearch, DirectoryInfoClassSearch, WindowsIndexExists, - querySearchString); + query, + querySearch); } - return WindowsIndexFilesAndFoldersSearch(querySearchString); + return WindowsIndexFilesAndFoldersSearch(querySearch); } - private List DirectoryInfoClassSearch(string arg) + private List DirectoryInfoClassSearch(Query query, string querySearch) { - throw new NotImplementedException(); - } + var directoryInfoSearch = new DirectoryInfoSearch(_settings, _context); - /// + return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch); } public List TopLevelFolderSearchBehaviour( Func> windowsIndexSearch, - Func> directoryInfoClassSearch, + Func> directoryInfoClassSearch, Func indexExists, - string path) + Query query, + string querySearchString) { - var results = windowsIndexSearch(path); + var results = windowsIndexSearch(querySearchString); - if (results.Count == 0 && !indexExists(path)) - return directoryInfoClassSearch(path); + if (results.Count == 0 && !indexExists(querySearchString)) + return directoryInfoClassSearch(query, querySearchString); return results; } @@ -73,7 +78,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search queryConstructor.CreateQueryHelper().ConnectionString, queryConstructor.QueryForAllFilesAndFolders); } - + private List WindowsIndexTopLevelFolderSearch(string path) { var queryConstructor = new QueryConstructor(_settings); From 5150d8a1f63e5253f252099401fe89fd65aa5c4c Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 25 May 2020 07:37:05 +1000 Subject: [PATCH 32/91] Add DirectryAllFilesFolders method to DirectoryInfo search --- .../DirectoryInfo/DirectoryInfoSearch.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 7b55697a9..9462baad7 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -19,13 +19,24 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo _context = context; _settings = settings; } - public List TopLevelDirectorySearch(Query query, string search) + + internal List DirectoryAllFilesFoldersSearch(Query query, string search) + { + return DirectorySearch(SearchOption.AllDirectories, query, search); + } + + internal List TopLevelDirectorySearch(Query query, string search) + { + return DirectorySearch(SearchOption.TopDirectoryOnly, query, search); + } + + private List DirectorySearch(SearchOption searchOption, Query query, string search) { var results = new List(); //var hasSpecial = search.IndexOfAny(_specialSearchChars) >= 0; string incompleteName = ""; //if (hasSpecial || !Directory.Exists(search + "\\")) - if (!FilesFolders.LocationExists(search + "\\")) + if (!Directory.Exists(search + "\\")) { // if folder doesn't exist, we want to take the last part and use it afterwards to help the user // find the right folder. @@ -34,7 +45,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { incompleteName = search.Substring(index + 1).ToLower(); search = search.Substring(0, index + 1); - if (!FilesFolders.LocationExists(search)) + if (!Directory.Exists(search)) { return results; } @@ -55,7 +66,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo results.Add(new ResultManager().CreateOpenCurrentFolderResult(incompleteName, search)); - var searchOption = SearchOption.TopDirectoryOnly; incompleteName += "*"; //// give the ability to search all folder when starting with > @@ -108,7 +118,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo } // Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. - return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList(); //<===== MOVE ORDERING OUT + return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList(); } } } From 327ee2d0912e8c619754a6dd5e7779ad0aa18deb Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 25 May 2020 08:14:21 +1000 Subject: [PATCH 33/91] Add QuickFolderAccess functionality --- .../Search/QuickFolderLinks/FolderLink.cs | 18 +++++++++++++++ .../QuickFolderLinks/QuickFolderAccess.cs | 23 +++++++++++++++++++ .../Search/SearchManager.cs | 8 +++++++ .../Flow.Launcher.Plugin.Explorer/Settings.cs | 11 ++++++--- 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/FolderLink.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/QuickFolderAccess.cs diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/FolderLink.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/FolderLink.cs new file mode 100644 index 000000000..33c6d37dc --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/FolderLink.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; +using System; +using System.Linq; + +namespace Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks +{ + [JsonObject(MemberSerialization.OptIn)] + public class FolderLink + { + [JsonProperty] + public string Path { get; set; } + + public string Nickname => + Path.Split(new[] { System.IO.Path.DirectorySeparatorChar }, StringSplitOptions.None) + .Last() + + " (" + System.IO.Path.GetDirectoryName(Path) + ")"; + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/QuickFolderAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/QuickFolderAccess.cs new file mode 100644 index 000000000..11c735ea2 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/QuickFolderAccess.cs @@ -0,0 +1,23 @@ +using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; +using Flow.Launcher.Plugin.SharedCommands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; + +namespace Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks +{ + public class QuickFolderAccess + { + internal List FolderList(Query query, List folderLinks) + { + string search = query.Search.ToLower(); + var userFolderLinks = folderLinks.Where( + x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase)); + var results = userFolderLinks.Select(item => + new ResultManager().CreateFolderResult(item.Nickname, Constants.DefaultFolderSubtitleString, item.Path, query)).ToList(); + return results; + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 6119f635f..3cb2cf842 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,4 +1,5 @@ using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; +using Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.SharedCommands; using System; @@ -13,6 +14,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search private IndexSearcher searcher; + private QuickFolderAccess quickFolderAccess = new QuickFolderAccess(); + public SearchManager(Settings settings, PluginInitContext context) { _settings = settings; @@ -24,6 +27,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search { var querySearch = query.Search; + var quickFolderLinks = quickFolderAccess.FolderList(query, _settings.FolderLinks); + + if (quickFolderLinks.Count > 0) + return quickFolderLinks; + if (EnvironmentVariables.IsEnvironmentVariableSearch(querySearch)) { return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 2d664c3db..c8eb87c34 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -1,11 +1,16 @@ -using System; +using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; +using Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks; +using Newtonsoft.Json; using System.Collections.Generic; -using System.Text; namespace Flow.Launcher.Plugin.Explorer { public class Settings { - public int MaxResult { get; set; } = 100; + [JsonProperty] + public int MaxResult { get; set; } = 100; + + [JsonProperty] + public List FolderLinks { get; set; } = new List(); } } From 08a3215c9df2d6e215dd318e1b77d30e527685da Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 25 May 2020 18:55:50 +1000 Subject: [PATCH 34/91] Make ResultManager static --- .../Search/DirectoryInfo/DirectoryInfoSearch.cs | 6 +++--- .../Search/EnvironmentVariables.cs | 4 ++-- .../Search/QuickFolderLinks/QuickFolderAccess.cs | 2 +- .../Search/ResultManager.cs | 12 ++++++------ 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 9462baad7..fefdfb983 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -64,7 +64,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo } } - results.Add(new ResultManager().CreateOpenCurrentFolderResult(incompleteName, search)); + results.Add(ResultManager.CreateOpenCurrentFolderResult(incompleteName, search)); incompleteName += "*"; @@ -97,11 +97,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo if (searchOption == SearchOption.AllDirectories) folderSubtitleString = fileSystemInfo.FullName; - folderList.Add(new ResultManager().CreateFolderResult(fileSystemInfo.Name, folderSubtitleString, fileSystemInfo.FullName, query)); + folderList.Add(ResultManager.CreateFolderResult(fileSystemInfo.Name, folderSubtitleString, fileSystemInfo.FullName, query)); } else { - fileList.Add(new ResultManager().CreateFileResult(fileSystemInfo.FullName, query)); + fileList.Add(ResultManager.CreateFileResult(fileSystemInfo.FullName, query)); } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs index dc48512aa..a62bfb647 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs @@ -62,7 +62,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search { var expandedPath = environmentVariables[search]; - results.Add(new ResultManager().CreateFolderResult($"%{search}%", expandedPath, expandedPath, query)); + results.Add(ResultManager.CreateFolderResult($"%{search}%", expandedPath, expandedPath, query)); return results; } @@ -81,7 +81,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search { if (p.Key.StartsWith(search)) { - results.Add(new ResultManager().CreateFolderResult($"%{p.Key}%", p.Value, p.Value, query)); + results.Add(ResultManager.CreateFolderResult($"%{p.Key}%", p.Value, p.Value, query)); } } return results; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/QuickFolderAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/QuickFolderAccess.cs index 11c735ea2..4bddf8168 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/QuickFolderAccess.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/QuickFolderAccess.cs @@ -16,7 +16,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks var userFolderLinks = folderLinks.Where( x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase)); var results = userFolderLinks.Select(item => - new ResultManager().CreateFolderResult(item.Nickname, Constants.DefaultFolderSubtitleString, item.Path, query)).ToList(); + ResultManager.CreateFolderResult(item.Nickname, Constants.DefaultFolderSubtitleString, item.Path, query)).ToList(); return results; } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 796650e58..9de1bb93c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -9,9 +9,9 @@ using System.Windows; namespace Flow.Launcher.Plugin.Explorer.Search { - public class ResultManager + internal static class ResultManager { - public Result CreateFolderResult(string title, string subtitle, string path, Query query) + internal static Result CreateFolderResult(string title, string subtitle, string path, Query query) { return new Result { @@ -45,7 +45,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search }; } - public Result CreateOpenCurrentFolderResult(string incompleteName, string search) + internal static Result CreateOpenCurrentFolderResult(string incompleteName, string search) { var firstResult = "Open current directory"; if (incompleteName.Length > 0) @@ -68,7 +68,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search }; } - public Result CreateFileResult(string filePath, Query query) + internal static Result CreateFileResult(string filePath, Query query) { var result = new Result { @@ -95,13 +95,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search } } - public class SearchResult + internal class SearchResult { public string FullPath { get; set; } public ResultType Type { get; set; } } - public enum ResultType + internal enum ResultType { Volume, Folder, From b737811bdc51362069ed688ff9a2ca86d23f7591 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 25 May 2020 18:58:04 +1000 Subject: [PATCH 35/91] Add ContextMenu --- .../ContextMenu.cs | 199 ++++++++++++++++++ Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 6 +- 2 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs new file mode 100644 index 000000000..3ccffd45a --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using System.Windows; +using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Plugin.SharedCommands; +using Flow.Launcher.Plugin.Explorer.Search; + +namespace Flow.Launcher.Plugin.Explorer +{ + internal class ContextMenu : IContextMenu + { + public List LoadContextMenus(Result selectedResult) + { + var contextMenus = new List(); + if (selectedResult.ContextData is SearchResult record) + { + if (record.Type == ResultType.File) + { + contextMenus.Add(CreateOpenWithEditorResult(record)); + contextMenus.Add(CreateOpenContainingFolderResult(record)); + } + + var icoPath = (record.Type == ResultType.File) ? Constants.FileImagePath : Constants.FolderImagePath; + var fileOrFolder = (record.Type == ResultType.File) ? "file" : "folder"; + contextMenus.Add(new Result + { + Title = "Copy path", + SubTitle = $"Copy the current {fileOrFolder} path to clipboard", + Action = (context) => + { + try + { + Clipboard.SetText(record.FullPath); + return true; + } + catch (Exception e) + { + var message = "Fail to set text in clipboard"; + LogException(message, e); + Main.Context.API.ShowMsg(message); + return false; + } + }, + IcoPath = Constants.CopyImagePath + }); + + contextMenus.Add(new Result + { + Title = $"Copy {fileOrFolder}", + SubTitle = $"Copy the {fileOrFolder} to clipboard", + Action = (context) => + { + try + { + Clipboard.SetFileDropList(new System.Collections.Specialized.StringCollection { record.FullPath }); + return true; + } + catch (Exception e) + { + var message = $"Fail to set {fileOrFolder} in clipboard"; + LogException(message, e); + Main.Context.API.ShowMsg(message); + return false; + } + + }, + IcoPath = icoPath + }); + + if (record.Type == ResultType.File || record.Type == ResultType.Folder) + contextMenus.Add(new Result + { + Title = $"Delete {fileOrFolder}", + SubTitle = $"Delete the selected {fileOrFolder}", + Action = (context) => + { + try + { + if (record.Type == ResultType.File) + File.Delete(record.FullPath); + else + Directory.Delete(record.FullPath, true); + } + catch (Exception e) + { + var message = $"Fail to delete {fileOrFolder} at {record.FullPath}"; + LogException(message, e); + Main.Context.API.ShowMsg(message); + return false; + } + + return true; + }, + IcoPath = Constants.DeleteFileFolderImagePath + }); + + if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath)) + contextMenus.Add(new Result + { + Title = "Run as different user", + Action = (context) => + { + try + { + Task.Run(() => ShellCommand.RunAsDifferentUser(record.FullPath.SetProcessStartInfo())); + } + catch (FileNotFoundException e) + { + var name = "Plugin: Folder"; + var message = $"File not found: {e.Message}"; + Main.Context.API.ShowMsg(name, message); + } + + return true; + }, + IcoPath = "Images/user.png" + }); + } + + return contextMenus; + } + + private Result CreateOpenContainingFolderResult(SearchResult record) + { + return new Result + { + Title = "Open containing folder", + Action = _ => + { + try + { + Process.Start("explorer.exe", $" /select,\"{record.FullPath}\""); + } + catch (Exception e) + { + var message = $"Fail to open file at {record.FullPath}"; + LogException(message, e); + Main.Context.API.ShowMsg(message); + return false; + } + + return true; + }, + IcoPath = Constants.FolderImagePath + }; + } + + + private Result CreateOpenWithEditorResult(SearchResult record) + { + string editorPath = "notepad.exe"; // TODO add the ability to create a custom editor + + var name = "Open With Editor: " + Path.GetFileNameWithoutExtension(editorPath); + return new Result + { + Title = name, + Action = _ => + { + try + { + Process.Start(editorPath, record.FullPath); + return true; + } + catch (Exception e) + { + var message = $"Fail to editor for file at {record.FullPath}"; + LogException(message, e); + Main.Context.API.ShowMsg(message); + return false; + } + }, + IcoPath = editorPath + }; + } + + public void LogException(string message, Exception e) + { + Log.Exception($"|Flow.Launcher.Plugin.Folder.ContextMenu|{message}", e); + } + + private bool CanRunAsDifferentUser(string path) + { + switch (Path.GetExtension(path)) + { + case ".exe": + case ".bat": + case ".msi": + return true; + + default: + return false; + + } + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index e23fbdb78..8bd6812d2 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -16,6 +16,8 @@ namespace Flow.Launcher.Plugin.Explorer private SettingsViewModel _viewModel; + private IContextMenu _contextMenu; + public Control CreateSettingPanel() { return new ExplorerSettings(); @@ -24,14 +26,14 @@ namespace Flow.Launcher.Plugin.Explorer public void Init(PluginInitContext context) { Context = context; - _viewModel = new SettingsViewModel(); _settings = _viewModel.Settings; + _contextMenu = new ContextMenu(); } public List LoadContextMenus(Result selectedResult) { - return new List(); + return _contextMenu.LoadContextMenus(selectedResult); } public List Query(Query query) From f8a4b924c89b898f826fb86c4d12eaf9404238ec Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 25 May 2020 18:58:28 +1000 Subject: [PATCH 36/91] IndexSearch use ResultManager --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 6 ++-- .../Search/SearchManager.cs | 16 +++++---- .../Search/WindowsIndex/IndexSearcher.cs | 34 ++++++------------- 3 files changed, 23 insertions(+), 33 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 283e19fdf..fc247d858 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -12,7 +12,7 @@ namespace Flow.Launcher.Test.Plugins [TestFixture] public class ExplorerTest { - private List MethodWindowsIndexSearchReturnsZeroResults(string dummyString) + private List MethodWindowsIndexSearchReturnsZeroResults(Query dummyQuery, string dummyString) { return new List(); } @@ -141,8 +141,6 @@ namespace Flow.Launcher.Test.Plugins $"Actual number of results is {results.Count} {Environment.NewLine}"); } - public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString() { } - [TestCase(@"c:\\", false)] [TestCase(@"i:\", true)] [TestCase(@"\c:\", false)] @@ -160,5 +158,7 @@ namespace Flow.Launcher.Test.Plugins $"Actual check result is {result} {Environment.NewLine}"); } + + public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString() { } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 3cb2cf842..c06d1db45 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -53,7 +53,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search querySearch); } - return WindowsIndexFilesAndFoldersSearch(querySearch); + return WindowsIndexFilesAndFoldersSearch(query, querySearch); } private List DirectoryInfoClassSearch(Query query, string querySearch) @@ -64,13 +64,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search } public List TopLevelFolderSearchBehaviour( - Func> windowsIndexSearch, + Func> windowsIndexSearch, Func> directoryInfoClassSearch, Func indexExists, Query query, string querySearchString) { - var results = windowsIndexSearch(querySearchString); + var results = windowsIndexSearch(query, querySearchString); if (results.Count == 0 && !indexExists(querySearchString)) return directoryInfoClassSearch(query, querySearchString); @@ -78,22 +78,24 @@ namespace Flow.Launcher.Plugin.Explorer.Search return results; } - private List WindowsIndexFilesAndFoldersSearch(string querySearchString) + private List WindowsIndexFilesAndFoldersSearch(Query query, string querySearchString) { var queryConstructor = new QueryConstructor(_settings); return searcher.WindowsIndexSearch(querySearchString, queryConstructor.CreateQueryHelper().ConnectionString, - queryConstructor.QueryForAllFilesAndFolders); + queryConstructor.QueryForAllFilesAndFolders, + query); } - private List WindowsIndexTopLevelFolderSearch(string path) + private List WindowsIndexTopLevelFolderSearch(Query query, string path) { var queryConstructor = new QueryConstructor(_settings); return searcher.WindowsIndexSearch(path, queryConstructor.CreateQueryHelper().ConnectionString, - queryConstructor.QueryForTopLevelDirectorySearch); + queryConstructor.QueryForTopLevelDirectorySearch, + query); } private bool WindowsIndexExists(string path) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs index 6081bf119..b7f172892 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs @@ -30,7 +30,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex _context = context; } - internal List ExecuteWindowsIndexSearch(string searchString, string connectionString) + internal List ExecuteWindowsIndexSearch(string searchString, string connectionString, Query query) { var results = new List(); @@ -54,7 +54,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex results.Add(CreateResult( dataReaderResults.GetString(0), dataReaderResults.GetString(1), - dataReaderResults.GetString(2))); + dataReaderResults.GetString(2), + query)); } } } @@ -74,30 +75,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex return results; } - private Result CreateResult(string filename, string path, string fileType) + private Result CreateResult(string filename, string path, string fileType, Query query) { - return new Result + if (fileType == "Directory") + return ResultManager.CreateFolderResult(filename, path, path, query); + else { - Title = filename, - SubTitle = path, - IcoPath = fileType == "Directory" ? Constants.FolderImagePath : Constants.FileImagePath, - Action = c => - { - try - { - FilesFolders.OpenPath(path); - } - catch (Win32Exception) - { - _context.API.ShowMsg("Explorer plugin: ", "Unable to open the selected file", string.Empty); //<========= - } - - return true; - } - }; + return ResultManager.CreateFileResult(path, query); + } } - internal List WindowsIndexSearch(string searchString, string connectionString, Func constructQuery) + internal List WindowsIndexSearch(string searchString, string connectionString, Func constructQuery, Query query) { var regexMatch = Regex.Match(searchString, ReservedStringPattern); @@ -107,7 +95,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex lock (_lock) { var constructedQuery = constructQuery(searchString); - return ExecuteWindowsIndexSearch(constructedQuery, connectionString); + return ExecuteWindowsIndexSearch(constructedQuery, connectionString, query); } } From b1a0ddfe2f7fd33e5ff70f64cc25f9958103f1f8 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 25 May 2020 19:01:31 +1000 Subject: [PATCH 37/91] Remove unused context from IndexSearch --- .../Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 2 +- .../Search/WindowsIndex/IndexSearcher.cs | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index c06d1db45..b1d00acbb 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -20,7 +20,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search { _settings = settings; _context = context; - searcher = new IndexSearcher(_context); + searcher = new IndexSearcher(); } internal List Search(Query query) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs index b7f172892..79c5df68c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs @@ -20,16 +20,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex public OleDbDataReader dataReaderResults; - private PluginInitContext _context; - // Reserved keywords in oleDB private string ReservedStringPattern = @"^[\/\\\$\%]+$"; - public IndexSearcher(PluginInitContext context) - { - _context = context; - } - internal List ExecuteWindowsIndexSearch(string searchString, string connectionString, Query query) { var results = new List(); From b03ee666462313bfe02b7d7e7c754e191e03877a Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 25 May 2020 19:06:09 +1000 Subject: [PATCH 38/91] Rename IndexSearcher --- .../Search/SearchManager.cs | 10 +++++----- .../WindowsIndex/{IndexSearcher.cs => IndexSearch.cs} | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) rename Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/{IndexSearcher.cs => IndexSearch.cs} (99%) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index b1d00acbb..4bae801b3 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -12,7 +12,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search private Settings _settings; private PluginInitContext _context; - private IndexSearcher searcher; + private IndexSearch _indexSearch; private QuickFolderAccess quickFolderAccess = new QuickFolderAccess(); @@ -20,7 +20,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search { _settings = settings; _context = context; - searcher = new IndexSearcher(); + _indexSearch = new IndexSearch(); } internal List Search(Query query) @@ -82,7 +82,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search { var queryConstructor = new QueryConstructor(_settings); - return searcher.WindowsIndexSearch(querySearchString, + return _indexSearch.WindowsIndexSearch(querySearchString, queryConstructor.CreateQueryHelper().ConnectionString, queryConstructor.QueryForAllFilesAndFolders, query); @@ -92,7 +92,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search { var queryConstructor = new QueryConstructor(_settings); - return searcher.WindowsIndexSearch(path, + return _indexSearch.WindowsIndexSearch(path, queryConstructor.CreateQueryHelper().ConnectionString, queryConstructor.QueryForTopLevelDirectorySearch, query); @@ -100,7 +100,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search private bool WindowsIndexExists(string path) { - return searcher.PathIsIndexed(path); + return _indexSearch.PathIsIndexed(path); } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs similarity index 99% rename from Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs rename to Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs index 79c5df68c..2648b82be 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearcher.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs @@ -10,7 +10,7 @@ using System.Text.RegularExpressions; namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { - internal class IndexSearcher + internal class IndexSearch { private readonly object _lock = new object(); From 74b25c0bf1ec1809a50fe009625e31c9a99b2434 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 26 May 2020 09:03:27 +1000 Subject: [PATCH 39/91] Add first result as opening current directory --- .../Search/ResultManager.cs | 31 ++++++++++++---- .../Search/SearchManager.cs | 36 ++++++++++++++++--- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 9de1bb93c..ef392f920 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -45,24 +45,41 @@ namespace Flow.Launcher.Plugin.Explorer.Search }; } - internal static Result CreateOpenCurrentFolderResult(string incompleteName, string search) + internal static Result CreateOpenCurrentFolderResult(string path, bool isPreviousDirectoryLevel) { - var firstResult = "Open current directory"; - if (incompleteName.Length > 0) - firstResult = "Open " + search; + var folderName = path; - var folderName = search.TrimEnd('\\').Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last(); + if (folderName.EndsWith(":\\")) + { + var driveLetter = folderName.Substring(0, 1).ToUpper(); + folderName = driveLetter + " drive"; + } + else + { + folderName = folderName.TrimEnd('\\').Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last(); + } + + var firstResult = ""; + + if (isPreviousDirectoryLevel) + { + firstResult = "Open " + folderName; + } + else + { + firstResult = "Open current directory"; + } return new Result { Title = firstResult, SubTitle = $"Use > to search files and subfolders within {folderName}, " + $"* to search for file extensions in {folderName} or both >* to combine the search", - IcoPath = search, + IcoPath = path, Score = 500, Action = c => { - FilesFolders.OpenPath(search); + FilesFolders.OpenPath(path); return true; } }; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 4bae801b3..155b89831 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; +using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; using Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.SharedCommands; @@ -44,9 +44,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search querySearch = EnvironmentVariables.TranslateEnvironmentVariablePath(querySearch); } - if (FilesFolders.IsLocationPathString(querySearch)) - { - return TopLevelFolderSearchBehaviour(WindowsIndexTopLevelFolderSearch, + var results = new List(); + + var currentFolderResult = CreateOpenCurrentFolderResult(FilesFolders.LocationExists, querySearch); + + if (currentFolderResult == null) + return new List(); + + results.Add(currentFolderResult); DirectoryInfoClassSearch, WindowsIndexExists, query, @@ -102,5 +107,28 @@ namespace Flow.Launcher.Plugin.Explorer.Search { return _indexSearch.PathIsIndexed(path); } + + private Result CreateOpenCurrentFolderResult(Func locationExists, string querySearchString) + { + if (locationExists(querySearchString)) + return ResultManager.CreateOpenCurrentFolderResult(querySearchString, false); + + var partialPath = ""; + int index = querySearchString.LastIndexOf('\\'); + if (index > 0 && index < (querySearchString.Length - 1)) + { + partialPath = querySearchString.Substring(0, index + 1); + if (!locationExists(partialPath)) + { + return null; + } + } + else + { + return null; + } + + return ResultManager.CreateOpenCurrentFolderResult(partialPath, true); + } } } From 0ba9a86e465821f282bb181097ec3689301e0d84 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 26 May 2020 13:46:59 +1000 Subject: [PATCH 40/91] Clean up method --- .../DirectoryInfo/DirectoryInfoSearch.cs | 33 +++---------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index fefdfb983..4823c987b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -36,35 +36,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo //var hasSpecial = search.IndexOfAny(_specialSearchChars) >= 0; string incompleteName = ""; //if (hasSpecial || !Directory.Exists(search + "\\")) - if (!Directory.Exists(search + "\\")) + + // folder exist, add \ at the end of doesn't exist + if (!search.EndsWith("\\")) { - // if folder doesn't exist, we want to take the last part and use it afterwards to help the user - // find the right folder. - int index = search.LastIndexOf('\\'); - if (index > 0 && index < (search.Length - 1)) - { - incompleteName = search.Substring(index + 1).ToLower(); - search = search.Substring(0, index + 1); - if (!Directory.Exists(search)) - { - return results; - } - } - else - { - return results; - } + search += "\\"; } - else - { - // folder exist, add \ at the end of doesn't exist - if (!search.EndsWith("\\")) - { - search += "\\"; - } - } - - results.Add(ResultManager.CreateOpenCurrentFolderResult(incompleteName, search)); incompleteName += "*"; @@ -118,7 +95,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo } // Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. - return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList(); + return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList(); //<============= MOVE OUT } } } From 900502e079ea71ecf4eb9d00fd5b2e1732e99608 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 26 May 2020 19:34:45 +1000 Subject: [PATCH 41/91] Update test assert string text --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index fc247d858..90bfd88f3 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -64,7 +64,7 @@ namespace Flow.Launcher.Test.Plugins // Then Assert.IsTrue(queryString == expectedString, - $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " + + $"Expected string: {expectedString}{Environment.NewLine} " + $"Actual string was: {queryString}{Environment.NewLine}"); } From 2feba97f561b34cfa6f4ff57e8a53ea9c09c9ee8 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 26 May 2020 19:35:30 +1000 Subject: [PATCH 42/91] Add tests for index searching partial folder or file name --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 90bfd88f3..8c2612477 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -68,6 +68,43 @@ namespace Flow.Launcher.Test.Plugins $"Actual string was: {queryString}{Environment.NewLine}"); } + [TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemPathDisplay\", \"System.ItemType\" " + + "FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " + + "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033))" + + " AND directory='file:C:\\Dropbox'")] + public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString( + string userSearchString, string expectedString) + { + // Given + var queryConstructor = new QueryConstructor(new Settings()); + + //When + var queryString = queryConstructor.QueryForTopLevelDirectorySearch(userSearchString); + + // Then + Assert.IsTrue(queryString == expectedString, + $"Expected string: {expectedString}{Environment.NewLine} " + + $"Actual string was: {queryString}{Environment.NewLine}"); + } + + [TestCase("C:\\Dropbox\\App", "WHERE (System.FileName LIKE 'App%' " + + "OR CONTAINS(System.FileName,'\"App*\"',1033))" + + " AND directory='file:C:\\Dropbox'")] + public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryWhereRestrictionsShouldUseDirectoryString( + string userSearchString, string expectedString) + { + // Given + var queryConstructor = new QueryConstructor(new Settings()); + + //When + var queryString = queryConstructor.QueryWhereRestrictionsForTopLevelDirectorySearch(userSearchString); + + // Then + Assert.IsTrue(queryString == expectedString, + $"Expected string: {expectedString}{Environment.NewLine} " + + $"Actual string was: {queryString}{Environment.NewLine}"); + } + [TestCase("scope='file:'")] public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString) { From 436cfe2e2bf9640417ba2d8e732a0686abea5709 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 26 May 2020 19:49:18 +1000 Subject: [PATCH 43/91] Add tests for GetPreviousDirectory method --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 8c2612477..12121ec85 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -37,6 +37,10 @@ namespace Flow.Launcher.Test.Plugins private bool MethodIndexExistsReturnsFalse(string dummyString) => false; + private bool LocationExistsReturnsTrue(string dummyString) => true; + + private bool LocationNotExistReturnsFalse(string dummyString) => false; + [TestCase("C:\\Dropbox", "directory='file:C:\\Dropbox'")] public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestrictionsShouldUseDirectoryString(string path, string expectedString) { @@ -195,6 +199,19 @@ namespace Flow.Launcher.Test.Plugins $"Actual check result is {result} {Environment.NewLine}"); } + + [TestCase(@"C:\Dropbox\Drop", @"C:\Dropbox")] + [TestCase(@"C:\Dropbox\Drop\App", @"C:\Dropbox\Drop")] + public void GivenAPartialPath_WhenPreviousLevelDirectoryExists_ThenShouldReturnThePreviousDirectoryPathString() + { + + } + + [TestCase(@"C:\Dropbox\Drop", "")] + public void GivenAPartialPath_WhenPreviousLevelDirectoryNotExists_ThenShouldReturnEmptyString() + { + + } public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString() { } } From 2d4e2f566272936ab829fee3014830d9dfd5c651 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 26 May 2020 20:18:18 +1000 Subject: [PATCH 44/91] Add methods to test for previous directory location --- .../SharedCommands/FilesFolders.cs | 25 ++++++++++++++ Flow.Launcher.Test/Plugins/ExplorerTest.cs | 33 +++++++++++++------ .../Search/SearchManager.cs | 20 +++-------- 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 440ac6756..bac3c212b 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -160,5 +160,30 @@ namespace Flow.Launcher.Plugin.SharedCommands return false; } + + /// + /// Gets the previous level directory from a path string. + /// Checks that previous level directory exists and returns it + /// as a path string, or empty string if doesnt exit + /// + public static string GetPreviousExistingDirectory(Func locationExists, string path) + { + var previousDirectoryPath = ""; + int index = path.LastIndexOf('\\'); + if (index > 0 && index < (path.Length - 1)) + { + previousDirectoryPath = path.Substring(0, index + 1); + if (!locationExists(path)) + { + return ""; + } + } + else + { + return ""; + } + + return previousDirectoryPath; + } } } diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 12121ec85..14469242e 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -37,9 +37,9 @@ namespace Flow.Launcher.Test.Plugins private bool MethodIndexExistsReturnsFalse(string dummyString) => false; - private bool LocationExistsReturnsTrue(string dummyString) => true; + private bool PreviousLocationExistsReturnsTrue(string dummyString) => true; - private bool LocationNotExistReturnsFalse(string dummyString) => false; + private bool PreviousLocationNotExistReturnsFalse(string dummyString) => false; [TestCase("C:\\Dropbox", "directory='file:C:\\Dropbox'")] public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestrictionsShouldUseDirectoryString(string path, string expectedString) @@ -200,17 +200,30 @@ namespace Flow.Launcher.Test.Plugins } - [TestCase(@"C:\Dropbox\Drop", @"C:\Dropbox")] - [TestCase(@"C:\Dropbox\Drop\App", @"C:\Dropbox\Drop")] - public void GivenAPartialPath_WhenPreviousLevelDirectoryExists_ThenShouldReturnThePreviousDirectoryPathString() + [TestCase(@"C:\Dropbox\Drop", true, @"C:\Dropbox\")] + [TestCase(@"C:\Dropbox\Drop\App", true, @"C:\Dropbox\Drop\")] + [TestCase(@"C:\Dropbox\Drop", false, "")] + public void GivenAPartialPath_WhenPreviousLevelDirectoryExists_ThenShouldReturnThePreviousDirectoryPathString( + string path, bool previousDirectoryExists, string expectedString) { + // When + Func previousLocationExists = null; + if (previousDirectoryExists) + { + previousLocationExists = PreviousLocationExistsReturnsTrue; + } + else + { + previousLocationExists = PreviousLocationNotExistReturnsFalse; + } - } - - [TestCase(@"C:\Dropbox\Drop", "")] - public void GivenAPartialPath_WhenPreviousLevelDirectoryNotExists_ThenShouldReturnEmptyString() - { + // Given + var previousDirectoryPath = FilesFolders.GetPreviousExistingDirectory(previousLocationExists, path); + //Then + Assert.IsTrue(previousDirectoryPath == expectedString, + $"Expected path string: {expectedString} {Environment.NewLine} " + + $"Actual path string is {previousDirectoryPath} {Environment.NewLine}"); } public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString() { } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 155b89831..19fbdc9e6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -113,22 +113,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (locationExists(querySearchString)) return ResultManager.CreateOpenCurrentFolderResult(querySearchString, false); - var partialPath = ""; - int index = querySearchString.LastIndexOf('\\'); - if (index > 0 && index < (querySearchString.Length - 1)) - { - partialPath = querySearchString.Substring(0, index + 1); - if (!locationExists(partialPath)) - { - return null; - } - } - else - { - return null; - } + var previousDirectoryPath = FilesFolders.GetPreviousExistingDirectory(FilesFolders.LocationExists, querySearchString); - return ResultManager.CreateOpenCurrentFolderResult(partialPath, true); + if (string.IsNullOrEmpty(previousDirectoryPath)) + return null; + + return ResultManager.CreateOpenCurrentFolderResult(previousDirectoryPath, true); } } } From 355a787b5e125efd21344c1e880e1f71b5e04ddf Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 26 May 2020 21:47:04 +1000 Subject: [PATCH 45/91] Fix test expected result --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 14469242e..6007970ba 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -91,7 +91,7 @@ namespace Flow.Launcher.Test.Plugins $"Actual string was: {queryString}{Environment.NewLine}"); } - [TestCase("C:\\Dropbox\\App", "WHERE (System.FileName LIKE 'App%' " + + [TestCase("C:\\Dropbox\\App", "(System.FileName LIKE 'App%' " + "OR CONTAINS(System.FileName,'\"App*\"',1033))" + " AND directory='file:C:\\Dropbox'")] public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryWhereRestrictionsShouldUseDirectoryString( From 3caafa325f8e0917372f9a3e0521238fc64bb94d Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 26 May 2020 21:47:47 +1000 Subject: [PATCH 46/91] Update getting previous directory method --- Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index bac3c212b..a81ff8008 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -164,16 +164,16 @@ namespace Flow.Launcher.Plugin.SharedCommands /// /// Gets the previous level directory from a path string. /// Checks that previous level directory exists and returns it - /// as a path string, or empty string if doesnt exit + /// as a path string, or empty string if doesn't exit /// public static string GetPreviousExistingDirectory(Func locationExists, string path) { var previousDirectoryPath = ""; - int index = path.LastIndexOf('\\'); + var index = path.LastIndexOf('\\'); if (index > 0 && index < (path.Length - 1)) { previousDirectoryPath = path.Substring(0, index + 1); - if (!locationExists(path)) + if (!locationExists(previousDirectoryPath)) { return ""; } From 353fa026af9e967083102f08e12e69bfb50f6d46 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 26 May 2020 21:49:59 +1000 Subject: [PATCH 47/91] rename variables and update comment --- .../Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 3 +-- .../Search/WindowsIndex/IndexSearch.cs | 4 ++-- .../Search/WindowsIndex/QueryConstructor.cs | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 19fbdc9e6..703667714 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -37,8 +37,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query); } - // Query is a location path with a full environment variable- starts with a % - // and contains another % somewhere before the end of the path + // Query is a location path with a full environment variable, eg. %appdata%\somefolder\ if (querySearch.Substring(1).Contains("%")) { querySearch = EnvironmentVariables.TranslateEnvironmentVariablePath(querySearch); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs index 2648b82be..4f0f8439a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs @@ -23,7 +23,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex // Reserved keywords in oleDB private string ReservedStringPattern = @"^[\/\\\$\%]+$"; - internal List ExecuteWindowsIndexSearch(string searchString, string connectionString, Query query) + internal List ExecuteWindowsIndexSearch(string indexQueryString, string connectionString, Query query) { var results = new List(); @@ -33,7 +33,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { conn.Open(); - using (command = new OleDbCommand(searchString, conn)) + using (command = new OleDbCommand(indexQueryString, conn)) { // Results return as an OleDbDataReader. using (dataReaderResults = command.ExecuteReader()) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index d74a11b48..2c2f197c0 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -1,4 +1,4 @@ -using Microsoft.Search.Interop; +using Microsoft.Search.Interop; namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { @@ -57,11 +57,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex /// /// Search will be performed on all folders and files on the first level of a specified directory. /// - public string QueryForTopLevelDirectorySearch(string folderPath) + public string QueryForTopLevelDirectorySearch(string path) { string query = "SELECT TOP " + _settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE "; - query += QueryWhereRestrictionsForTopLevelDirectorySearch(folderPath); + query += QueryWhereRestrictionsForTopLevelDirectorySearch(path); return query; } From ea9992475e031d7572c6448bce022775440552dd Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 26 May 2020 21:51:05 +1000 Subject: [PATCH 48/91] move method up for readability --- Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 703667714..2b5744b19 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -32,6 +32,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (quickFolderLinks.Count > 0) return quickFolderLinks; + if (!FilesFolders.IsLocationPathString(querySearch)) + return WindowsIndexFilesAndFoldersSearch(query, querySearch); + if (EnvironmentVariables.IsEnvironmentVariableSearch(querySearch)) { return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query); From 5b2f06bf106a6f34983a7d4b42d874f615f96b05 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 26 May 2020 21:55:07 +1000 Subject: [PATCH 49/91] add logic for index search to search with partial item name --- .../Search/SearchManager.cs | 15 ++++++++------- .../Search/WindowsIndex/QueryConstructor.cs | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 2b5744b19..1bca58366 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; +using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; using Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.SharedCommands; @@ -54,13 +54,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search return new List(); results.Add(currentFolderResult); - DirectoryInfoClassSearch, - WindowsIndexExists, - query, - querySearch); - } - return WindowsIndexFilesAndFoldersSearch(query, querySearch); + results.AddRange(TopLevelFolderSearchBehaviour(WindowsIndexTopLevelFolderSearch, + DirectoryInfoClassSearch, + WindowsIndexExists, + query, + querySearch)); + + return results; } private List DirectoryInfoClassSearch(Query query, string querySearch) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index 2c2f197c0..cbf896d26 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -51,7 +51,20 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex /// public string QueryWhereRestrictionsForTopLevelDirectorySearch(string path) { - return $"directory='file:{path}'"; + var directoryWhereRestriction = $"directory='file:"; + + if (path.EndsWith("\\")) + return directoryWhereRestriction + $"{path}'"; + + var indexOfSeparator = path.LastIndexOf('\\'); + + var itemName = path.Substring(indexOfSeparator + 1); + + var previousLevelDirectory = path.Substring(0, indexOfSeparator); + + return $"(System.FileName LIKE '{itemName}%' " + + $"OR CONTAINS(System.FileName,'\"{itemName}*\"',1033)) AND " + + directoryWhereRestriction + $"{previousLevelDirectory}'"; } /// From e22abba18ca8cefd8c31ff5ec86c118fd17d7e51 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 27 May 2020 07:30:27 +1000 Subject: [PATCH 50/91] DirectoryInfoSearch- remove auto open folder via appending '\' to path --- .../Search/DirectoryInfo/DirectoryInfoSearch.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 4823c987b..11d820913 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Infrastructure; +using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; @@ -36,15 +36,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo //var hasSpecial = search.IndexOfAny(_specialSearchChars) >= 0; string incompleteName = ""; //if (hasSpecial || !Directory.Exists(search + "\\")) - - // folder exist, add \ at the end of doesn't exist - if (!search.EndsWith("\\")) - { - search += "\\"; - } - - incompleteName += "*"; - //// give the ability to search all folder when starting with > //if (incompleteName.StartsWith(">")) //{ From c93f4a09ea56474766dba2d2678bd1ef1292562f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 27 May 2020 07:30:52 +1000 Subject: [PATCH 51/91] DirectoryInfoPath- fix partial item search --- .../Search/DirectoryInfo/DirectoryInfoSearch.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 11d820913..5511ee8c1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Infrastructure; +using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; @@ -45,6 +45,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo // incompleteName = "*" + incompleteName.Substring(1); //} + if (!search.EndsWith("\\")) + { + // not full path, get previous level directory string + var indexOfSeparator = search.LastIndexOf('\\'); + + incompleteName = search.Substring(indexOfSeparator + 1).ToLower(); + + search = search.Substring(0, indexOfSeparator + 1); + } + + incompleteName += "*"; + var folderList = new List(); var fileList = new List(); From af00ec58792c46b85762e24b14f7b6e6909f493f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 27 May 2020 07:41:08 +1000 Subject: [PATCH 52/91] remove obsolete variable folderSubtitleString in DirectoryInfoSeach --- .../Search/DirectoryInfo/DirectoryInfoSearch.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 5511ee8c1..31697aa95 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -60,11 +60,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo var folderList = new List(); var fileList = new List(); - var folderSubtitleString = Constants.DefaultFolderSubtitleString; - try { - // search folder and add results var directoryInfo = new System.IO.DirectoryInfo(search); var fileSystemInfos = directoryInfo.GetFileSystemInfos(incompleteName, searchOption); @@ -74,10 +71,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo if (fileSystemInfo is System.IO.DirectoryInfo) { - if (searchOption == SearchOption.AllDirectories) - folderSubtitleString = fileSystemInfo.FullName; - - folderList.Add(ResultManager.CreateFolderResult(fileSystemInfo.Name, folderSubtitleString, fileSystemInfo.FullName, query)); + folderList.Add(ResultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, fileSystemInfo.FullName, query)); } else { From 2ea6642248e61d42c06e6b9893ecb01c5dab1305 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 27 May 2020 19:36:49 +1000 Subject: [PATCH 53/91] show if file or folder is indexed in context menu --- .../ContextMenu.cs | 4 ++++ .../Flow.Launcher.Plugin.Explorer.csproj | 4 ++++ .../Images/index.png | Bin 0 -> 19078 bytes .../Search/Constants.cs | 1 + .../Search/DirectoryInfo/DirectoryInfoSearch.cs | 4 ++-- .../Search/ResultManager.cs | 12 ++++++++---- .../Search/WindowsIndex/IndexSearch.cs | 4 ++-- 7 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Images/index.png diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 3ccffd45a..544b74abe 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -23,6 +23,10 @@ namespace Flow.Launcher.Plugin.Explorer contextMenus.Add(CreateOpenContainingFolderResult(record)); } + if (record.ShowIndexState) + contextMenus.Add(new Result { Title = "Indexed: " + (record.WindowsIndexed ? "Yes" : "No"), + Score = 501, IcoPath = Constants.IndexImagePath }); + var icoPath = (record.Type == ResultType.File) ? Constants.FileImagePath : Constants.FolderImagePath; var fileOrFolder = (record.Type == ResultType.File) ? "file" : "folder"; contextMenus.Add(new Result 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 0261da36b..3f3206567 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -27,6 +27,10 @@ PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/index.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/index.png new file mode 100644 index 0000000000000000000000000000000000000000..c63fc8069f08e9e118ab623a0e243aab1d4cbb6a GIT binary patch literal 19078 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 literal 0 HcmV?d00001 diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs index 2be1dfd7a..17070fbc1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs @@ -10,6 +10,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search public const string FileImagePath = "Images\\file.png"; public const string DeleteFileFolderImagePath = "Images\\deletefilefolder.png"; public const string CopyImagePath = "Images\\copy.png"; + public const string IndexImagePath = "Images\\index.png"; public const string DefaultFolderSubtitleString = "Ctrl + Enter to open the directory"; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 31697aa95..0faae15b5 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -71,11 +71,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo if (fileSystemInfo is System.IO.DirectoryInfo) { - folderList.Add(ResultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, fileSystemInfo.FullName, query)); + folderList.Add(ResultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, fileSystemInfo.FullName, query, true, false)); } else { - fileList.Add(ResultManager.CreateFileResult(fileSystemInfo.FullName, query)); + fileList.Add(ResultManager.CreateFileResult(fileSystemInfo.FullName, query, true, false)); } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index ef392f920..50791251f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -11,7 +11,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search { internal static class ResultManager { - internal static Result CreateFolderResult(string title, string subtitle, string path, Query query) + internal static Result CreateFolderResult(string title, string subtitle, string path, Query query, bool showIndexState = false, bool windowsIndexed = false) { return new Result { @@ -41,7 +41,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search query.ActionKeyword + " " + changeTo); return false; }, - ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path } + ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, ShowIndexState = showIndexState, WindowsIndexed = windowsIndexed } }; } @@ -85,7 +85,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search }; } - internal static Result CreateFileResult(string filePath, Query query) + internal static Result CreateFileResult(string filePath, Query query, bool showIndexState = false, bool windowsIndexed = false) { var result = new Result { @@ -106,7 +106,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search return true; }, - ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath } + ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, ShowIndexState = showIndexState, WindowsIndexed = windowsIndexed } }; return result; } @@ -116,6 +116,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search { public string FullPath { get; set; } public ResultType Type { get; set; } + + public bool WindowsIndexed { get; set; } + + public bool ShowIndexState { get; set; } } internal enum ResultType diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs index 4f0f8439a..c2513820b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs @@ -71,10 +71,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex private Result CreateResult(string filename, string path, string fileType, Query query) { if (fileType == "Directory") - return ResultManager.CreateFolderResult(filename, path, path, query); + return ResultManager.CreateFolderResult(filename, path, path, query, true, true); else { - return ResultManager.CreateFileResult(path, query); + return ResultManager.CreateFileResult(path, query, true, true); } } From 9bce1a593eda728b688006cce49f9340699189fd Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 28 May 2020 07:38:41 +1000 Subject: [PATCH 54/91] Add body for WindowsIndex search all hotkey test --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 6007970ba..64d6a91f3 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -226,6 +226,22 @@ namespace Flow.Launcher.Test.Plugins $"Actual path string is {previousDirectoryPath} {Environment.NewLine}"); } - public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString() { } + [TestCase("c:\\NonExistentFolder\\>", "scope='file:c:\\NonExistentFolder'")] + [TestCase("c:\\NonExistentFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' " + + "OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND " + + "scope='file:c:\\NonExistentFolder'")] + public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString(string path, string expectedString) + { + // Given + var queryConstructor = new QueryConstructor(new Settings()); + + //When + var resultString = queryConstructor.QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path); + + // Then + Assert.IsTrue(resultString == expectedString, + $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " + + $"Actual string was: {resultString}{Environment.NewLine}"); + } } } From b5e9ec3a9f81cbcff99bba8211c2611e8b40863d Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 28 May 2020 07:41:14 +1000 Subject: [PATCH 55/91] Add search hotkeys to Constants --- .../Search/Constants.cs | 7 ++-- .../Search/WindowsIndex/QueryConstructor.cs | 32 ++++++++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs index 17070fbc1..20d8b8261 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Text; @@ -12,6 +12,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search public const string CopyImagePath = "Images\\copy.png"; public const string IndexImagePath = "Images\\index.png"; - public const string DefaultFolderSubtitleString = "Ctrl + Enter to open the directory"; + internal static readonly char[] SpecialSearchChars = new char[] + { + '?', '*', '>' + }; } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index cbf896d26..8a6c0f798 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -51,20 +51,41 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex /// public string QueryWhereRestrictionsForTopLevelDirectorySearch(string path) { - var directoryWhereRestriction = $"directory='file:"; + var searchDepth = $"directory='file:"; + return QueryWhereRestrictionsFromLocationPath(path, searchDepth); + } + + /// + /// Set the required WHERE clause restriction to search all files and subfolders of a specified directory. + /// + public string QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(string path) + { + var searchDepth = $"scope='file:"; + + return QueryWhereRestrictionsFromLocationPath(path, searchDepth); + } + + private string QueryWhereRestrictionsFromLocationPath(string path, string searchDepth) + { if (path.EndsWith("\\")) - return directoryWhereRestriction + $"{path}'"; + return searchDepth + $"{path}'"; var indexOfSeparator = path.LastIndexOf('\\'); var itemName = path.Substring(indexOfSeparator + 1); + if (itemName.StartsWith('>')) + itemName = itemName.Substring(1); + var previousLevelDirectory = path.Substring(0, indexOfSeparator); + if (string.IsNullOrEmpty(itemName)) + return searchDepth + $"{previousLevelDirectory}'"; + return $"(System.FileName LIKE '{itemName}%' " + $"OR CONTAINS(System.FileName,'\"{itemName}*\"',1033)) AND " + - directoryWhereRestriction + $"{previousLevelDirectory}'"; + searchDepth + $"{previousLevelDirectory}'"; } /// @@ -74,9 +95,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { string query = "SELECT TOP " + _settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE "; - query += QueryWhereRestrictionsForTopLevelDirectorySearch(path); + if (path.IndexOfAny(Constants.SpecialSearchChars) >= 0) + return query + QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path); - return query; + return query + QueryWhereRestrictionsForTopLevelDirectorySearch(path); } /// From 03bcef56e2af166711459fd3b7eea0498a19191e Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 28 May 2020 07:41:41 +1000 Subject: [PATCH 56/91] Change Constants class visibility to internal --- .../Search/Constants.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs index 20d8b8261..2953e4329 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs @@ -1,16 +1,18 @@ -using System; +using System; using System.Collections.Generic; using System.Text; namespace Flow.Launcher.Plugin.Explorer.Search { - public static class Constants + internal static class Constants { - public const string FolderImagePath = "Images\\folder.png"; - public const string FileImagePath = "Images\\file.png"; - public const string DeleteFileFolderImagePath = "Images\\deletefilefolder.png"; - public const string CopyImagePath = "Images\\copy.png"; - public const string IndexImagePath = "Images\\index.png"; + internal const string FolderImagePath = "Images\\folder.png"; + internal const string FileImagePath = "Images\\file.png"; + internal const string DeleteFileFolderImagePath = "Images\\deletefilefolder.png"; + internal const string CopyImagePath = "Images\\copy.png"; + internal const string IndexImagePath = "Images\\index.png"; + + internal const string DefaultFolderSubtitleString = "Ctrl + Enter to open the directory"; internal static readonly char[] SpecialSearchChars = new char[] { From 64986b609f15393f890929f31d57c7070d65bf49 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 28 May 2020 08:06:49 +1000 Subject: [PATCH 57/91] rename test parameter variables --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 64d6a91f3..d662eb6f8 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -41,7 +41,7 @@ namespace Flow.Launcher.Test.Plugins private bool PreviousLocationNotExistReturnsFalse(string dummyString) => false; - [TestCase("C:\\Dropbox", "directory='file:C:\\Dropbox'")] + [TestCase("C:\\SomeFolder", "directory='file:C:\\SomeFolder'")] public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestrictionsShouldUseDirectoryString(string path, string expectedString) { // Given @@ -57,7 +57,7 @@ namespace Flow.Launcher.Test.Plugins $"Actual: {result}{Environment.NewLine}"); } - [TestCase("C:\\Dropbox", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType FROM SystemIndex WHERE directory='file:C:\\Dropbox'")] + [TestCase("C:\\SomeFolder", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder'")] public void GivenWindowsIndexSearch_WhenSearchTypeIsSearchTopFolderLevel_ThenQueryShouldUseExpectedString(string folderPath, string expectedString) { // Given @@ -75,7 +75,7 @@ namespace Flow.Launcher.Test.Plugins [TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemPathDisplay\", \"System.ItemType\" " + "FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " + "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033))" + - " AND directory='file:C:\\Dropbox'")] + " AND directory='file:C:\\SomeFolder'")] public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString( string userSearchString, string expectedString) { @@ -91,9 +91,9 @@ namespace Flow.Launcher.Test.Plugins $"Actual string was: {queryString}{Environment.NewLine}"); } - [TestCase("C:\\Dropbox\\App", "(System.FileName LIKE 'App%' " + - "OR CONTAINS(System.FileName,'\"App*\"',1033))" + - " AND directory='file:C:\\Dropbox'")] + [TestCase("C:\\SomeFolder\\SomeApp", "(System.FileName LIKE 'SomeApp%' " + + "OR CONTAINS(System.FileName,'\"SomeApp*\"',1033))" + + " AND directory='file:C:\\SomeFolder'")] public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryWhereRestrictionsShouldUseDirectoryString( string userSearchString, string expectedString) { @@ -200,9 +200,9 @@ namespace Flow.Launcher.Test.Plugins } - [TestCase(@"C:\Dropbox\Drop", true, @"C:\Dropbox\")] - [TestCase(@"C:\Dropbox\Drop\App", true, @"C:\Dropbox\Drop\")] - [TestCase(@"C:\Dropbox\Drop", false, "")] + [TestCase(@"C:\SomeFolder\SomeApp", true, @"C:\SomeFolder\")] + [TestCase(@"C:\SomeFolder\SomeApp\SomeFile", true, @"C:\SomeFolder\SomeApp\")] + [TestCase(@"C:\NonExistentFolder\SomeApp", false, "")] public void GivenAPartialPath_WhenPreviousLevelDirectoryExists_ThenShouldReturnThePreviousDirectoryPathString( string path, bool previousDirectoryExists, string expectedString) { @@ -226,10 +226,10 @@ namespace Flow.Launcher.Test.Plugins $"Actual path string is {previousDirectoryPath} {Environment.NewLine}"); } - [TestCase("c:\\NonExistentFolder\\>", "scope='file:c:\\NonExistentFolder'")] - [TestCase("c:\\NonExistentFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' " + + [TestCase("c:\\SomeFolder\\>", "scope='file:c:\\SomeFolder'")] + [TestCase("c:\\SomeFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' " + "OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND " + - "scope='file:c:\\NonExistentFolder'")] + "scope='file:c:\\SomeFolder'")] public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString(string path, string expectedString) { // Given From a478e203522af879a3177cfbaf010d87dd3404a3 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 28 May 2020 08:55:41 +1000 Subject: [PATCH 58/91] Add test for DirectoryInfoSearch search criteria --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index d662eb6f8..048ed3963 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -1,6 +1,7 @@ using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.Explorer; using Flow.Launcher.Plugin.Explorer.Search; +using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.SharedCommands; using NUnit.Framework; @@ -243,5 +244,22 @@ namespace Flow.Launcher.Test.Plugins $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " + $"Actual string was: {resultString}{Environment.NewLine}"); } + + [TestCase("c:\\somefolder\\>somefile","*somefile*")] + [TestCase("c:\\somefolder\\somefile", "somefile*")] + [TestCase("c:\\somefolder\\", "*")] + public void GivenDirectoryInfoSearch_WhenSearchPatternHotKeyIsSearchAll_ThenSearchCriteriaShouldUseCriteriaString(string path, string expectedString) + { + // Given + var criteriaConstructor = new DirectoryInfoSearch(new Settings()); + + //When + var resultString = criteriaConstructor.ConstructSearchCriteria(path); + + // Then + Assert.IsTrue(resultString == expectedString, + $"Expected criteria string: {expectedString}{Environment.NewLine} " + + $"Actual criteria string was: {resultString}{Environment.NewLine}"); + } } } From ddffa52acaae8e83e5820c68edbd1b94674ed151 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 28 May 2020 08:56:55 +1000 Subject: [PATCH 59/91] Add method to construct search criteria for DirectoryInfoSeach --- .../DirectoryInfo/DirectoryInfoSearch.cs | 54 ++++++++++--------- .../Search/SearchManager.cs | 2 +- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 0faae15b5..4ed63b7c3 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -10,40 +10,26 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { public class DirectoryInfoSearch { - private PluginInitContext _context; - private Settings _settings; - public DirectoryInfoSearch(Settings settings, PluginInitContext context) + public DirectoryInfoSearch(Settings settings) { - _context = context; _settings = settings; } - internal List DirectoryAllFilesFoldersSearch(Query query, string search) - { - return DirectorySearch(SearchOption.AllDirectories, query, search); - } - internal List TopLevelDirectorySearch(Query query, string search) { - return DirectorySearch(SearchOption.TopDirectoryOnly, query, search); + var criteria = ConstructSearchCriteria(search); + + if (search.IndexOfAny(Constants.SpecialSearchChars) >= 0) + return DirectorySearch(SearchOption.AllDirectories, query, search, criteria); + + return DirectorySearch(SearchOption.TopDirectoryOnly, query, search, criteria); } - private List DirectorySearch(SearchOption searchOption, Query query, string search) + public string ConstructSearchCriteria(string search) { - var results = new List(); - //var hasSpecial = search.IndexOfAny(_specialSearchChars) >= 0; string incompleteName = ""; - //if (hasSpecial || !Directory.Exists(search + "\\")) - //// give the ability to search all folder when starting with > - //if (incompleteName.StartsWith(">")) - //{ - // searchOption = SearchOption.AllDirectories; - - // // match everything before and after search term using supported wildcard '*', ie. *searchterm* - // incompleteName = "*" + incompleteName.Substring(1); - //} if (!search.EndsWith("\\")) { @@ -52,18 +38,36 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo incompleteName = search.Substring(indexOfSeparator + 1).ToLower(); - search = search.Substring(0, indexOfSeparator + 1); + if (incompleteName.StartsWith(">")) + incompleteName = "*" + incompleteName.Substring(1); } incompleteName += "*"; + return incompleteName; + } + + private List DirectorySearch(SearchOption searchOption, Query query, string search, string searchCriteria) + { + var results = new List(); + + var path = search; + + if (!search.EndsWith("\\")) + { + // not full path, get previous level directory string + var indexOfSeparator = search.LastIndexOf('\\'); + + path = search.Substring(0, indexOfSeparator + 1); + } + var folderList = new List(); var fileList = new List(); try { - var directoryInfo = new System.IO.DirectoryInfo(search); - var fileSystemInfos = directoryInfo.GetFileSystemInfos(incompleteName, searchOption); + var directoryInfo = new System.IO.DirectoryInfo(path); + var fileSystemInfos = directoryInfo.GetFileSystemInfos(searchCriteria, searchOption); foreach (var fileSystemInfo in fileSystemInfos) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 1bca58366..f4d64157c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -66,7 +66,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search private List DirectoryInfoClassSearch(Query query, string querySearch) { - var directoryInfoSearch = new DirectoryInfoSearch(_settings, _context); + var directoryInfoSearch = new DirectoryInfoSearch(_settings); return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch); } From c7642b73c0c8a92e3128da3c9f3cd367de4ce403 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 28 May 2020 18:23:00 +1000 Subject: [PATCH 60/91] update tests --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 048ed3963..508468ea1 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -42,7 +42,7 @@ namespace Flow.Launcher.Test.Plugins private bool PreviousLocationNotExistReturnsFalse(string dummyString) => false; - [TestCase("C:\\SomeFolder", "directory='file:C:\\SomeFolder'")] + [TestCase("C:\\SomeFolder\\", "directory='file:C:\\SomeFolder\\'")] public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestrictionsShouldUseDirectoryString(string path, string expectedString) { // Given @@ -58,8 +58,9 @@ namespace Flow.Launcher.Test.Plugins $"Actual: {result}{Environment.NewLine}"); } - [TestCase("C:\\SomeFolder", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder'")] - public void GivenWindowsIndexSearch_WhenSearchTypeIsSearchTopFolderLevel_ThenQueryShouldUseExpectedString(string folderPath, string expectedString) + [TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType FROM SystemIndex WHERE directory='file:C:\\'")] + [TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\'")] + public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString) { // Given var queryConstructor = new QueryConstructor(new Settings()); @@ -73,8 +74,8 @@ namespace Flow.Launcher.Test.Plugins $"Actual string was: {queryString}{Environment.NewLine}"); } - [TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemPathDisplay\", \"System.ItemType\" " + - "FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " + + [TestCase("C:\\SomeFolder\\flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType " + + "FROM SystemIndex WHERE (System.FileName LIKE 'flow.launcher.sln%' " + "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033))" + " AND directory='file:C:\\SomeFolder'")] public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString( @@ -188,8 +189,8 @@ namespace Flow.Launcher.Test.Plugins [TestCase(@"\c:\", false)] [TestCase(@"cc:\", false)] [TestCase(@"\\\SomeNetworkLocation\", false)] - [TestCase("RandomString", false)] - public void WhenGivenQuerySearchString_ThenShouldIndicateIfItIsLocationString(string querySearchString, bool expectedResult) + [TestCase("RandomFile", false)] + public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult) { // When, Given var result = FilesFolders.IsLocationPathString(querySearchString); From 0c53d76fec4db7248ebaa577b6e0c8a200a1fcb5 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 28 May 2020 21:33:18 +1000 Subject: [PATCH 61/91] fix TopLevelDirectory search activation --- .../SharedCommands/FilesFolders.cs | 18 ++++++++++++++++++ Flow.Launcher.Test/Plugins/ExplorerTest.cs | 13 +++++++++++++ .../DirectoryInfo/DirectoryInfoSearch.cs | 15 +++------------ .../Search/SearchManager.cs | 2 ++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index a81ff8008..c41eddceb 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -185,5 +185,23 @@ namespace Flow.Launcher.Plugin.SharedCommands return previousDirectoryPath; } + + /// + /// Gets the previous level directory from a path string. + /// Does not check that previous level directory exists and returns + /// passed in string if is complete path + /// + public static string GetPreviousLevelDirectoryIfPathIncomplete(string path) + { + if (!path.EndsWith("\\")) + { + // not full path, get previous level directory string + var indexOfSeparator = path.LastIndexOf('\\'); + + return path.Substring(0, indexOfSeparator + 1); + } + + return path; + } } } diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 508468ea1..7f049fe3e 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -228,6 +228,19 @@ namespace Flow.Launcher.Test.Plugins $"Actual path string is {previousDirectoryPath} {Environment.NewLine}"); } + [TestCase(@"C:\NonExistentFolder\SomeApp", @"C:\NonExistentFolder\")] + [TestCase(@"C:\NonExistentFolder\SomeApp\", @"C:\NonExistentFolder\SomeApp\")] + public void WhenGivenAPath_ThenShouldReturnThePreviousDirectoryPathIfIncompleteOrOriginalString( + string path, string expectedString) + { + var returnedPath = FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(path); + + //Then + Assert.IsTrue(returnedPath == expectedString, + $"Expected path string: {expectedString} {Environment.NewLine} " + + $"Actual path string is {returnedPath} {Environment.NewLine}"); + } + [TestCase("c:\\SomeFolder\\>", "scope='file:c:\\SomeFolder'")] [TestCase("c:\\SomeFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' " + "OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND " + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 4ed63b7c3..c535fef99 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -21,7 +21,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { var criteria = ConstructSearchCriteria(search); - if (search.IndexOfAny(Constants.SpecialSearchChars) >= 0) + if (search.LastIndexOf('>') > search.LastIndexOf('\\')) return DirectorySearch(SearchOption.AllDirectories, query, search, criteria); return DirectorySearch(SearchOption.TopDirectoryOnly, query, search, criteria); @@ -33,12 +33,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo if (!search.EndsWith("\\")) { - // not full path, get previous level directory string var indexOfSeparator = search.LastIndexOf('\\'); incompleteName = search.Substring(indexOfSeparator + 1).ToLower(); - if (incompleteName.StartsWith(">")) + if (incompleteName.StartsWith('>')) incompleteName = "*" + incompleteName.Substring(1); } @@ -51,15 +50,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { var results = new List(); - var path = search; - - if (!search.EndsWith("\\")) - { - // not full path, get previous level directory string - var indexOfSeparator = search.LastIndexOf('\\'); - - path = search.Substring(0, indexOfSeparator + 1); - } + var path = FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(search); var folderList = new List(); var fileList = new List(); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index f4d64157c..7bc038900 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -108,6 +108,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search private bool WindowsIndexExists(string path) { + path = FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(path); + return _indexSearch.PathIsIndexed(path); } From 758b565f01288d1eefef6c8a476e54df9b631f22 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 28 May 2020 21:35:12 +1000 Subject: [PATCH 62/91] change strings to Constants --- Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs | 7 +++---- .../Search/DirectoryInfo/DirectoryInfoSearch.cs | 8 ++++---- .../Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs | 4 ++-- .../Search/WindowsIndex/QueryConstructor.cs | 8 ++++---- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs index 2953e4329..a299f24c3 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs @@ -14,9 +14,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal const string DefaultFolderSubtitleString = "Ctrl + Enter to open the directory"; - internal static readonly char[] SpecialSearchChars = new char[] - { - '?', '*', '>' - }; + internal const char AllFilesFolderSearchWildcard = '>'; + + internal const char DirectorySeperator = '\\'; } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index c535fef99..f68e8143e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -21,7 +21,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { var criteria = ConstructSearchCriteria(search); - if (search.LastIndexOf('>') > search.LastIndexOf('\\')) + if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > search.LastIndexOf(Constants.DirectorySeperator)) return DirectorySearch(SearchOption.AllDirectories, query, search, criteria); return DirectorySearch(SearchOption.TopDirectoryOnly, query, search, criteria); @@ -31,13 +31,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { string incompleteName = ""; - if (!search.EndsWith("\\")) + if (!search.EndsWith(Constants.DirectorySeperator)) { - var indexOfSeparator = search.LastIndexOf('\\'); + var indexOfSeparator = search.LastIndexOf(Constants.DirectorySeperator); incompleteName = search.Substring(indexOfSeparator + 1).ToLower(); - if (incompleteName.StartsWith('>')) + if (incompleteName.StartsWith(Constants.AllFilesFolderSearchWildcard)) incompleteName = "*" + incompleteName.Substring(1); } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 50791251f..ec9c793ac 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -35,7 +35,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search } } - string changeTo = path.EndsWith("\\") ? path : path + "\\"; + string changeTo = path.EndsWith(Constants.DirectorySeperator) ? path : path + Constants.DirectorySeperator; Main.Context.API.ChangeQuery(string.IsNullOrEmpty(query.ActionKeyword) ? changeTo : query.ActionKeyword + " " + changeTo); @@ -56,7 +56,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search } else { - folderName = folderName.TrimEnd('\\').Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last(); + folderName = folderName.TrimEnd(Constants.DirectorySeperator).Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last(); } var firstResult = ""; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index 8a6c0f798..e037cc8c9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -68,14 +68,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex private string QueryWhereRestrictionsFromLocationPath(string path, string searchDepth) { - if (path.EndsWith("\\")) + if (path.EndsWith(Constants.DirectorySeperator)) return searchDepth + $"{path}'"; - var indexOfSeparator = path.LastIndexOf('\\'); + var indexOfSeparator = path.LastIndexOf(Constants.DirectorySeperator); var itemName = path.Substring(indexOfSeparator + 1); - if (itemName.StartsWith('>')) + if (itemName.StartsWith(Constants.AllFilesFolderSearchWildcard)) itemName = itemName.Substring(1); var previousLevelDirectory = path.Substring(0, indexOfSeparator); @@ -95,7 +95,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { string query = "SELECT TOP " + _settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE "; - if (path.IndexOfAny(Constants.SpecialSearchChars) >= 0) + if (path.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > path.LastIndexOf(Constants.DirectorySeperator)) return query + QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path); return query + QueryWhereRestrictionsForTopLevelDirectorySearch(path); From 5f13fd8a802f4a2154544efdaf1ca89a2ab82690 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 1 Jun 2020 05:52:01 +1000 Subject: [PATCH 63/91] add index info on Open Current Folder result --- Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs | 8 +++++--- .../Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs | 5 +++-- .../Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 8 ++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 544b74abe..ab9c8f10f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -20,12 +20,14 @@ namespace Flow.Launcher.Plugin.Explorer if (record.Type == ResultType.File) { contextMenus.Add(CreateOpenWithEditorResult(record)); - contextMenus.Add(CreateOpenContainingFolderResult(record)); } + contextMenus.Add(CreateOpenContainingFolderResult(record)); + if (record.ShowIndexState) - contextMenus.Add(new Result { Title = "Indexed: " + (record.WindowsIndexed ? "Yes" : "No"), - Score = 501, IcoPath = Constants.IndexImagePath }); + contextMenus.Add(new Result {Title = "Indexed: " + (record.WindowsIndexed ? "Yes" : "No"), + SubTitle = "Location: " + record.FullPath, + Score = 501, IcoPath = Constants.IndexImagePath}); var icoPath = (record.Type == ResultType.File) ? Constants.FileImagePath : Constants.FolderImagePath; var fileOrFolder = (record.Type == ResultType.File) ? "file" : "folder"; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index ec9c793ac..d9035ea9d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -45,7 +45,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search }; } - internal static Result CreateOpenCurrentFolderResult(string path, bool isPreviousDirectoryLevel) + internal static Result CreateOpenCurrentFolderResult(string path, bool isPreviousDirectoryLevel, bool windowsIndexed = false) { var folderName = path; @@ -81,7 +81,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search { FilesFolders.OpenPath(path); return true; - } + }, + ContextData = new SearchResult { Type = ResultType.Folder, FullPath = folderName, ShowIndexState = true, WindowsIndexed = windowsIndexed } }; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 7bc038900..d6dec77d2 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -48,7 +48,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search var results = new List(); - var currentFolderResult = CreateOpenCurrentFolderResult(FilesFolders.LocationExists, querySearch); + var currentFolderResult = CreateOpenCurrentFolderResult(FilesFolders.LocationExists, querySearch, WindowsIndexExists(querySearch)); if (currentFolderResult == null) return new List(); @@ -113,17 +113,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search return _indexSearch.PathIsIndexed(path); } - private Result CreateOpenCurrentFolderResult(Func locationExists, string querySearchString) + private Result CreateOpenCurrentFolderResult(Func locationExists, string querySearchString, bool indexExists) { if (locationExists(querySearchString)) - return ResultManager.CreateOpenCurrentFolderResult(querySearchString, false); + return ResultManager.CreateOpenCurrentFolderResult(querySearchString, false, indexExists); var previousDirectoryPath = FilesFolders.GetPreviousExistingDirectory(FilesFolders.LocationExists, querySearchString); if (string.IsNullOrEmpty(previousDirectoryPath)) return null; - return ResultManager.CreateOpenCurrentFolderResult(previousDirectoryPath, true); + return ResultManager.CreateOpenCurrentFolderResult(previousDirectoryPath, true, indexExists); } } } From 1cea02a30027856517fc00f2a1ec63dec1c08d8a Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 1 Jun 2020 07:53:34 +1000 Subject: [PATCH 64/91] mini refactor to check current or previous directory once --- .../Search/SearchManager.cs | 52 +++++++------------ 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index d6dec77d2..44a685427 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; +using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; using Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.SharedCommands; @@ -35,31 +35,35 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (!FilesFolders.IsLocationPathString(querySearch)) return WindowsIndexFilesAndFoldersSearch(query, querySearch); - if (EnvironmentVariables.IsEnvironmentVariableSearch(querySearch)) + var locationPath = query.Search; + + if (EnvironmentVariables.IsEnvironmentVariableSearch(locationPath)) { - return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query); + return EnvironmentVariables.GetEnvironmentStringPathSuggestions(locationPath, query); } // Query is a location path with a full environment variable, eg. %appdata%\somefolder\ - if (querySearch.Substring(1).Contains("%")) + if (locationPath.Substring(1).Contains("%")) { - querySearch = EnvironmentVariables.TranslateEnvironmentVariablePath(querySearch); + locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath); } var results = new List(); - var currentFolderResult = CreateOpenCurrentFolderResult(FilesFolders.LocationExists, querySearch, WindowsIndexExists(querySearch)); + if (!FilesFolders.LocationExists(FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(locationPath))) + return results; - if (currentFolderResult == null) - return new List(); + var indexExists = _indexSearch.PathIsIndexed(locationPath); - results.Add(currentFolderResult); + results.Add(ResultManager.CreateOpenCurrentFolderResult(FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(locationPath), + true, + indexExists)); results.AddRange(TopLevelFolderSearchBehaviour(WindowsIndexTopLevelFolderSearch, DirectoryInfoClassSearch, - WindowsIndexExists, + indexExists, query, - querySearch)); + locationPath)); return results; } @@ -101,29 +105,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search var queryConstructor = new QueryConstructor(_settings); return _indexSearch.WindowsIndexSearch(path, - queryConstructor.CreateQueryHelper().ConnectionString, - queryConstructor.QueryForTopLevelDirectorySearch, - query); - } - - private bool WindowsIndexExists(string path) - { - path = FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(path); - - return _indexSearch.PathIsIndexed(path); - } - - private Result CreateOpenCurrentFolderResult(Func locationExists, string querySearchString, bool indexExists) - { - if (locationExists(querySearchString)) - return ResultManager.CreateOpenCurrentFolderResult(querySearchString, false, indexExists); - - var previousDirectoryPath = FilesFolders.GetPreviousExistingDirectory(FilesFolders.LocationExists, querySearchString); - - if (string.IsNullOrEmpty(previousDirectoryPath)) - return null; - - return ResultManager.CreateOpenCurrentFolderResult(previousDirectoryPath, true, indexExists); + queryConstructor.CreateQueryHelper().ConnectionString, + queryConstructor.QueryForTopLevelDirectorySearch, + query); } } } From f2690ebfe29796111995b89c07af2ea8da2a4807 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 1 Jun 2020 08:03:04 +1000 Subject: [PATCH 65/91] fix scenario windows index still returned indexed subfolder results despite top level is not indexed when searching for top level. eg. searching for c:\ --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 8 ++------ .../Search/SearchManager.cs | 10 ++++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 7f049fe3e..ca4e96a07 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -34,10 +34,6 @@ namespace Flow.Launcher.Test.Plugins }; } - private bool MethodIndexExistsReturnsTrue(string dummyString) => true; - - private bool MethodIndexExistsReturnsFalse(string dummyString) => false; - private bool PreviousLocationExistsReturnsTrue(string dummyString) => true; private bool PreviousLocationNotExistReturnsFalse(string dummyString) => false; @@ -154,7 +150,7 @@ namespace Flow.Launcher.Test.Plugins var results = searchManager.TopLevelFolderSearchBehaviour( MethodWindowsIndexSearchReturnsZeroResults, MethodDirectoryInfoClassSearchReturnsTwoResults, - MethodIndexExistsReturnsFalse, + false, new Query(), "string not used"); @@ -174,7 +170,7 @@ namespace Flow.Launcher.Test.Plugins var results = searchManager.TopLevelFolderSearchBehaviour( MethodWindowsIndexSearchReturnsZeroResults, MethodDirectoryInfoClassSearchReturnsTwoResults, - MethodIndexExistsReturnsTrue, + true, new Query(), "string not used"); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 44a685427..cc6b5ce79 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; +using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; using Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.SharedCommands; @@ -78,16 +78,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search public List TopLevelFolderSearchBehaviour( Func> windowsIndexSearch, Func> directoryInfoClassSearch, - Func indexExists, + bool indexExists, Query query, string querySearchString) { - var results = windowsIndexSearch(query, querySearchString); - - if (results.Count == 0 && !indexExists(querySearchString)) + if (!indexExists) return directoryInfoClassSearch(query, querySearchString); - return results; + return windowsIndexSearch(query, querySearchString); } private List WindowsIndexFilesAndFoldersSearch(Query query, string querySearchString) From cc3d7d7dedd06978f8351c5fb0093c208ef34cd2 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 1 Jun 2020 14:11:51 +1000 Subject: [PATCH 66/91] fix CreateOpenCurrentFolderResult incorrectly using foldername --- .../Search/ResultManager.cs | 44 +++++++++---------- .../Search/SearchManager.cs | 4 +- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index d9035ea9d..ac1588371 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -45,44 +45,44 @@ namespace Flow.Launcher.Plugin.Explorer.Search }; } - internal static Result CreateOpenCurrentFolderResult(string path, bool isPreviousDirectoryLevel, bool windowsIndexed = false) + internal static Result CreateOpenCurrentFolderResult(string path, bool windowsIndexed = false) { - var folderName = path; + var folderName = ""; - if (folderName.EndsWith(":\\")) + var title = "Open current directory"; + + var previousDirectoryPath = FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(path); + + if (previousDirectoryPath != path) { - var driveLetter = folderName.Substring(0, 1).ToUpper(); - folderName = driveLetter + " drive"; - } - else - { - folderName = folderName.TrimEnd(Constants.DirectorySeperator).Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last(); + if (previousDirectoryPath.EndsWith(":\\")) + { + var driveLetter = path.Substring(0, 1).ToUpper(); + folderName = driveLetter + " drive"; + } + else + { + folderName = previousDirectoryPath.TrimEnd(Constants.DirectorySeperator).Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last(); + } + + title = "Open " + folderName; } - var firstResult = ""; - - if (isPreviousDirectoryLevel) - { - firstResult = "Open " + folderName; - } - else - { - firstResult = "Open current directory"; - } + var fullPath = previousDirectoryPath != path ? previousDirectoryPath : path; return new Result { - Title = firstResult, + Title = title, SubTitle = $"Use > to search files and subfolders within {folderName}, " + $"* to search for file extensions in {folderName} or both >* to combine the search", - IcoPath = path, + IcoPath = fullPath, Score = 500, Action = c => { FilesFolders.OpenPath(path); return true; }, - ContextData = new SearchResult { Type = ResultType.Folder, FullPath = folderName, ShowIndexState = true, WindowsIndexed = windowsIndexed } + ContextData = new SearchResult { Type = ResultType.Folder, FullPath = fullPath, ShowIndexState = true, WindowsIndexed = windowsIndexed } }; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index cc6b5ce79..f77da2eaa 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -55,9 +55,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search var indexExists = _indexSearch.PathIsIndexed(locationPath); - results.Add(ResultManager.CreateOpenCurrentFolderResult(FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(locationPath), - true, - indexExists)); + results.Add(ResultManager.CreateOpenCurrentFolderResult(locationPath, indexExists)); results.AddRange(TopLevelFolderSearchBehaviour(WindowsIndexTopLevelFolderSearch, DirectoryInfoClassSearch, From dcca88d7462d1ec54a7b9ff8f61c8c38f32c405a Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 1 Jun 2020 21:11:29 +1000 Subject: [PATCH 67/91] fix CreateOpenCurrentFolderResult's opening path and text display --- .../Search/ResultManager.cs | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index ac1588371..5995ad905 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -47,42 +47,41 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal static Result CreateOpenCurrentFolderResult(string path, bool windowsIndexed = false) { - var folderName = ""; + var retrievedDirectoryPath = FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(path); + + var folderName = retrievedDirectoryPath.TrimEnd(Constants.DirectorySeperator).Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last(); + + if (retrievedDirectoryPath.EndsWith(":\\")) + { + var driveLetter = path.Substring(0, 1).ToUpper(); + folderName = driveLetter + " drive"; + } var title = "Open current directory"; - var previousDirectoryPath = FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(path); - - if (previousDirectoryPath != path) - { - if (previousDirectoryPath.EndsWith(":\\")) - { - var driveLetter = path.Substring(0, 1).ToUpper(); - folderName = driveLetter + " drive"; - } - else - { - folderName = previousDirectoryPath.TrimEnd(Constants.DirectorySeperator).Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last(); - } - + if (retrievedDirectoryPath != path) title = "Open " + folderName; - } - var fullPath = previousDirectoryPath != path ? previousDirectoryPath : path; + + var subtitleFolderName = folderName; + + // ie. max characters can be displayed without subtitle cutting off: "Program Files (x86)" + if (folderName.Length > 19) + subtitleFolderName = "directory"; return new Result { Title = title, - SubTitle = $"Use > to search files and subfolders within {folderName}, " + - $"* to search for file extensions in {folderName} or both >* to combine the search", - IcoPath = fullPath, + SubTitle = $"Use > to search within {subtitleFolderName}, " + + $"* to search for file extensions or >* to combine both searches.", + IcoPath = retrievedDirectoryPath, Score = 500, Action = c => { - FilesFolders.OpenPath(path); + FilesFolders.OpenPath(retrievedDirectoryPath); return true; }, - ContextData = new SearchResult { Type = ResultType.Folder, FullPath = fullPath, ShowIndexState = true, WindowsIndexed = windowsIndexed } + ContextData = new SearchResult { Type = ResultType.Folder, FullPath = retrievedDirectoryPath, ShowIndexState = true, WindowsIndexed = windowsIndexed } }; } From 4eb2fe9297688f0fadf060a2ca32afcb676ef228 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 2 Jun 2020 13:26:33 +1000 Subject: [PATCH 68/91] fix typo --- Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 5995ad905..6da36af06 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -67,7 +67,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search // ie. max characters can be displayed without subtitle cutting off: "Program Files (x86)" if (folderName.Length > 19) - subtitleFolderName = "directory"; + subtitleFolderName = "the directory"; return new Result { From 76297786102900b6618bb430a6a67f6741cd0b2f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 2 Jun 2020 20:04:22 +1000 Subject: [PATCH 69/91] rename top level directory search method and tests to reflect intent --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 8 ++++---- .../Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index ca4e96a07..261dfd593 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -141,13 +141,13 @@ namespace Flow.Launcher.Test.Plugins } [TestCase] - public void GivenWindowsIndexSearch_WhenReturnedZeroResultsAndIsNotIndexed_ThenSearchMethodShouldContinueDirectoryInfoClassSearch() + public void GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldContinueDirectoryInfoClassSearch() { // Given var searchManager = new SearchManager(new Settings(), new PluginInitContext()); // When - var results = searchManager.TopLevelFolderSearchBehaviour( + var results = searchManager.TopLevelDirectorySearchBehaviour( MethodWindowsIndexSearchReturnsZeroResults, MethodDirectoryInfoClassSearchReturnsTwoResults, false, @@ -161,13 +161,13 @@ namespace Flow.Launcher.Test.Plugins } [TestCase] - public void GivenWindowsIndexSearch_WhenReturnedZeroResultsAndIsIndexed_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearch() + public void GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearch() { // Given var searchManager = new SearchManager(new Settings(), new PluginInitContext()); // When - var results = searchManager.TopLevelFolderSearchBehaviour( + var results = searchManager.TopLevelDirectorySearchBehaviour( MethodWindowsIndexSearchReturnsZeroResults, MethodDirectoryInfoClassSearchReturnsTwoResults, true, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index f77da2eaa..2a1c7676d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; +using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; using Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.SharedCommands; @@ -57,8 +57,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search results.Add(ResultManager.CreateOpenCurrentFolderResult(locationPath, indexExists)); - results.AddRange(TopLevelFolderSearchBehaviour(WindowsIndexTopLevelFolderSearch, - DirectoryInfoClassSearch, + results.AddRange(TopLevelDirectorySearchBehaviour(WindowsIndexTopLevelFolderSearch, + DirectoryInfoClassSearch, indexExists, query, locationPath)); @@ -73,7 +73,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch); } - public List TopLevelFolderSearchBehaviour( + public List TopLevelDirectorySearchBehaviour( Func> windowsIndexSearch, Func> directoryInfoClassSearch, bool indexExists, From 5c46464a4196dd188cda623f88f9a271137c9ff1 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 2 Jun 2020 20:12:14 +1000 Subject: [PATCH 70/91] add condition for using Windows Index Search --- .../Search/SearchManager.cs | 35 +++++++++++++------ .../Flow.Launcher.Plugin.Explorer/Settings.cs | 6 ++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 2a1c7676d..6df71f39d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,9 +1,10 @@ -using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; +using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; using Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; +using System.Linq; namespace Flow.Launcher.Plugin.Explorer.Search { @@ -53,15 +54,15 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (!FilesFolders.LocationExists(FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(locationPath))) return results; - var indexExists = _indexSearch.PathIsIndexed(locationPath); + var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath); - results.Add(ResultManager.CreateOpenCurrentFolderResult(locationPath, indexExists)); + results.Add(ResultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch)); results.AddRange(TopLevelDirectorySearchBehaviour(WindowsIndexTopLevelFolderSearch, DirectoryInfoClassSearch, - indexExists, - query, - locationPath)); + useIndexSearch, + query, + locationPath)); return results; } @@ -76,11 +77,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search public List TopLevelDirectorySearchBehaviour( Func> windowsIndexSearch, Func> directoryInfoClassSearch, - bool indexExists, + bool useIndexSearch, Query query, string querySearchString) { - if (!indexExists) + if (!useIndexSearch) return directoryInfoClassSearch(query, querySearchString); return windowsIndexSearch(query, querySearchString); @@ -91,9 +92,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search var queryConstructor = new QueryConstructor(_settings); return _indexSearch.WindowsIndexSearch(querySearchString, - queryConstructor.CreateQueryHelper().ConnectionString, - queryConstructor.QueryForAllFilesAndFolders, - query); + queryConstructor.CreateQueryHelper().ConnectionString, + queryConstructor.QueryForAllFilesAndFolders, + query); } private List WindowsIndexTopLevelFolderSearch(Query query, string path) @@ -105,5 +106,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search queryConstructor.QueryForTopLevelDirectorySearch, query); } + + private bool UseWindowsIndexForDirectorySearch(string locationPath) + { + if (!_settings.UseWindowsIndexForDirectorySearch) + return false; + + if (_settings.WindowsIndexExcludedDirectories + .Any(x => x.Path == FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(locationPath))) + return false; + + return _indexSearch.PathIsIndexed(locationPath); + } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index c8eb87c34..abe452481 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -12,5 +12,11 @@ namespace Flow.Launcher.Plugin.Explorer [JsonProperty] public List FolderLinks { get; set; } = new List(); + + [JsonProperty] + public bool UseWindowsIndexForDirectorySearch { get; set; } = true; + + [JsonProperty] + public List WindowsIndexExcludedDirectories { get; set; } = new List(); } } From 2225cbff9219cdbe6d823d374760054088e7e2fe Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 2 Jun 2020 20:21:28 +1000 Subject: [PATCH 71/91] rename method to reflect behaviour --- Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs | 8 ++++---- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 2 +- .../Search/DirectoryInfo/DirectoryInfoSearch.cs | 2 +- .../Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs | 2 +- .../Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index c41eddceb..32f6507fa 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -187,11 +187,11 @@ namespace Flow.Launcher.Plugin.SharedCommands } /// - /// Gets the previous level directory from a path string. - /// Does not check that previous level directory exists and returns - /// passed in string if is complete path + /// Returns the previous level directory if path incomplete. + /// Does not check if previous level directory exists. + /// Returns passed in string if is complete path /// - public static string GetPreviousLevelDirectoryIfPathIncomplete(string path) + public static string ReturnPreviousDirectoryIfIncompleteString(string path) { if (!path.EndsWith("\\")) { diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 261dfd593..ffdc1eed8 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -229,7 +229,7 @@ namespace Flow.Launcher.Test.Plugins public void WhenGivenAPath_ThenShouldReturnThePreviousDirectoryPathIfIncompleteOrOriginalString( string path, string expectedString) { - var returnedPath = FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(path); + var returnedPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path); //Then Assert.IsTrue(returnedPath == expectedString, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index f68e8143e..f70c4d125 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -50,7 +50,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { var results = new List(); - var path = FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(search); + var path = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(search); var folderList = new List(); var fileList = new List(); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 6da36af06..22b17a985 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -47,7 +47,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal static Result CreateOpenCurrentFolderResult(string path, bool windowsIndexed = false) { - var retrievedDirectoryPath = FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(path); + var retrievedDirectoryPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path); var folderName = retrievedDirectoryPath.TrimEnd(Constants.DirectorySeperator).Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last(); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 6df71f39d..33d255ae0 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -51,7 +51,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search var results = new List(); - if (!FilesFolders.LocationExists(FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(locationPath))) + if (!FilesFolders.LocationExists(FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath))) return results; var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath); @@ -113,7 +113,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search return false; if (_settings.WindowsIndexExcludedDirectories - .Any(x => x.Path == FilesFolders.GetPreviousLevelDirectoryIfPathIncomplete(locationPath))) + .Any(x => x.Path == FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath))) return false; return _indexSearch.PathIsIndexed(locationPath); From 6330a784e82a740ba765d719c553e27b4c92dd1a Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 2 Jun 2020 20:58:22 +1000 Subject: [PATCH 72/91] add context menu to exclude indexed path from using index search --- .../ContextMenu.cs | 23 +++++++++++++++--- .../Flow.Launcher.Plugin.Explorer.csproj | 4 +++ .../Images/excludeindexpath.png | Bin 0 -> 32667 bytes Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 8 +++--- .../Search/Constants.cs | 1 + .../Search/SearchManager.cs | 4 +-- .../Flow.Launcher.Plugin.Explorer/Settings.cs | 2 +- 7 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Images/excludeindexpath.png diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index ab9c8f10f..964a8897c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -7,6 +7,7 @@ using System.Windows; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.Explorer.Search; +using System.Windows.Media; namespace Flow.Launcher.Plugin.Explorer { @@ -18,9 +19,10 @@ namespace Flow.Launcher.Plugin.Explorer if (selectedResult.ContextData is SearchResult record) { if (record.Type == ResultType.File) - { contextMenus.Add(CreateOpenWithEditorResult(record)); - } + + if (record.Type == ResultType.Folder && record.WindowsIndexed) + contextMenus.Add(CreateAddToIndexSearchExclusionListResult(record)); contextMenus.Add(CreateOpenContainingFolderResult(record)); @@ -154,7 +156,6 @@ namespace Flow.Launcher.Plugin.Explorer }; } - private Result CreateOpenWithEditorResult(SearchResult record) { string editorPath = "notepad.exe"; // TODO add the ability to create a custom editor @@ -182,6 +183,22 @@ namespace Flow.Launcher.Plugin.Explorer }; } + private Result CreateAddToIndexSearchExclusionListResult(SearchResult record) + { + return new Result + { + Title = "Exclude path from index search", + SubTitle = record.FullPath, + Action = _ => + { + Main.Settings.IndexSearchExcludedDirectories.Add(record.FullPath); + + return false; + }, + IcoPath = Constants.ExcludeFromIndexImagePath + }; + } + public void LogException(string message, Exception e) { Log.Exception($"|Flow.Launcher.Plugin.Folder.ContextMenu|{message}", e); 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 3f3206567..65eed12d6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -31,6 +31,10 @@ PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/excludeindexpath.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/excludeindexpath.png new file mode 100644 index 0000000000000000000000000000000000000000..8578ac7fb61f2167c5194494ff8aa4a618d45dbe GIT binary patch literal 32667 zcmeGDWmJ{j_XP}JhejHuK|)GWKnanM5Dq2MB_Q2MBY7|YX$~cgNOyNBC9QOWq_lu^ zz596o{^R}je1FDxbPR=kt-bczbIm#Tz7G0PRYi{A7WFL%f(YR9GHMWn0lvh5aInB% z{&bx0z@H{?nWq|_)7xJIz!-$cxXP! z3spxH9&bK9%Jfoe&cCZ!>JAbtmWrfzeBI#kn{2GqZY+6>SQe!#mewS`kp^YXF-9(wPL*bIcS*l;WCNHP`E z{Z7fLez-BzyvfZyd4Gd$Tx9()U{B)p@wDyDINkBok5x|>(T?0<;sy5B`_Dz(!>;VN zyA%kQMn=OG)8`@%bmNDrd}>w&c+%a@`e{IoIx1VW-R0J#yb97Ov#X~{9If?(PVT+s z-rH*mg#Cw;1E!6A)$9{y8`CKUC7SgX1Ypi(y1-I!!d~!tF0CW&g`H5x$uy&T4y||B zZvIMJq1=L)sdw)qu89hR5oi0pM?4b^FbFd43J+@S6gP}I_mrFI)*dF7&_7qczE&VC z(K|bi+~JvMDGI0{zcdEvjYHyYZX12yPkPv~zUwRPNAK_aW1_vPwJ5qmt2gPsZFeKe zQaEB*_p984g|Zo2Qk?B*8Z(j7**UG8T{Usln&UdUwx~imOMPlDmxxR4gM{rj%35oY z?=shNn@wkWskv;+i+VsBL^p%UOVN)ReyY&Z#VstqK*|M=a#fPG~!~#Sm6+^-=TK))#G5)BrFMZ!}vo*=bkp1f@61^m zRLp3Ij>qtu{ckmq&vV87Vw`97dVVPu6zlhisqMx&4|xWGYE+vK<~HmjJluUR8CECN zdwwY-)f~rq=glgvrrCh!;|3iKos5E6wEEMuph+Iu&IqJMc4NYBG54zB;RI+$Tfx+m zkKJw}^C`|L8?~3i1+|vj{mw-3-sRw-jtZOd(*2haMA@`6UB{Zv#K^T~Uw~S(?(R>3 z*<#sEdTS(+yNF+c^Q>l1fZXsq5AAyGKCz1eyk$6Q5A7mOa!)?*EH~fpF73Wn@A>*{ z__Ld3!t%B=Z-O_94eWwDAW53}(2&1PVqVJb{S1g(DYBDpe_OD7LOr56j`jhwvYfX% zz0r*DN6SYLB$+xa>*Tt*On1M#-V~ctL`gnrkT4=u|2Vdy_958bw4#WLyswYIYF!TU z-Mg%*&7^FxwfCSh9c4ww{k>)1`*I~K2nK7yP3OPnd;8qrh%5U>e(niRUY@z%O;AmX z!#rRv6x*CPN5$&^XtNqU__4cIMEvn_w*r6EM_gR(xBz4ncD1Kx%b~SAAM}H( z4;PzS#y1xN{O&&OXaL*~-zhT7NXTm6Xet+q7b^x2c57eR(#G_r|5&T5L;iY+jQPrYCc(l9slAfN|+Y&%OV!V7T+|uXd)$8<&nZKSJz zg1gB4LJEl-Ai!(k-Um}h`)RSuNQFQH_^`AGT&Sb*iUG7zj@RU+P{;lI-9{W#HZ1VS z8u9DdHBbGiK41@?>@On{B6kW*g5#L!+|7B7woQTB5ZHRcylIK&!iDi)HIY?M;+mVc z_W>4%_9G?2CI4XZCVN-FY|YZpynSRo
    HK(eJ4q27J0!Jndl)M^o1T`id>0u5nn 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�z=R%N0lXV3%mb$l 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 YF!4ZnUmtr8T#2kCuP#^o)co!L0ef9{uK)l5 literal 0 HcmV?d00001 diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 8bd6812d2..18691c579 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Infrastructure.Storage; +using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.ViewModels; using Flow.Launcher.Plugin.Explorer.Views; @@ -12,7 +12,7 @@ namespace Flow.Launcher.Plugin.Explorer { internal static PluginInitContext Context { get; set; } - private Settings _settings; + internal static Settings Settings; private SettingsViewModel _viewModel; @@ -27,7 +27,7 @@ namespace Flow.Launcher.Plugin.Explorer { Context = context; _viewModel = new SettingsViewModel(); - _settings = _viewModel.Settings; + Settings = _viewModel.Settings; _contextMenu = new ContextMenu(); } @@ -43,7 +43,7 @@ namespace Flow.Launcher.Plugin.Explorer if (string.IsNullOrEmpty(query.Search)) return results; - return new SearchManager(_settings, Context).Search(query); + return new SearchManager(Settings, Context).Search(query); } public void Save() diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs index a299f24c3..2f6a6f2ea 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs @@ -11,6 +11,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal const string DeleteFileFolderImagePath = "Images\\deletefilefolder.png"; internal const string CopyImagePath = "Images\\copy.png"; internal const string IndexImagePath = "Images\\index.png"; + internal const string ExcludeFromIndexImagePath = "Images\\excludeindexpath.png"; internal const string DefaultFolderSubtitleString = "Ctrl + Enter to open the directory"; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 33d255ae0..1ffc9ee51 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -112,8 +112,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (!_settings.UseWindowsIndexForDirectorySearch) return false; - if (_settings.WindowsIndexExcludedDirectories - .Any(x => x.Path == FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath))) + if (_settings.IndexSearchExcludedDirectories + .Any(x => x == FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath))) return false; return _indexSearch.PathIsIndexed(locationPath); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index abe452481..53011bf18 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -17,6 +17,6 @@ namespace Flow.Launcher.Plugin.Explorer public bool UseWindowsIndexForDirectorySearch { get; set; } = true; [JsonProperty] - public List WindowsIndexExcludedDirectories { get; set; } = new List(); + public List IndexSearchExcludedDirectories { get; set; } = new List(); } } From bbca3cf202ed75543119f7408d463fa47041f0f3 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 2 Jun 2020 21:08:41 +1000 Subject: [PATCH 73/91] context menu- update text display and add check if already exists --- Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 964a8897c..7a24d58fb 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -8,6 +8,7 @@ using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.Explorer.Search; using System.Windows.Media; +using System.Linq; namespace Flow.Launcher.Plugin.Explorer { @@ -27,7 +28,7 @@ namespace Flow.Launcher.Plugin.Explorer contextMenus.Add(CreateOpenContainingFolderResult(record)); if (record.ShowIndexState) - contextMenus.Add(new Result {Title = "Indexed: " + (record.WindowsIndexed ? "Yes" : "No"), + contextMenus.Add(new Result {Title = "From index search: " + (record.WindowsIndexed ? "Yes" : "No"), SubTitle = "Location: " + record.FullPath, Score = 501, IcoPath = Constants.IndexImagePath}); @@ -188,10 +189,11 @@ namespace Flow.Launcher.Plugin.Explorer return new Result { Title = "Exclude path from index search", - SubTitle = record.FullPath, + SubTitle = "Path: " + record.FullPath, Action = _ => { - Main.Settings.IndexSearchExcludedDirectories.Add(record.FullPath); + if(!Main.Settings.IndexSearchExcludedDirectories.Any(x => x == record.FullPath)) + Main.Settings.IndexSearchExcludedDirectories.Add(record.FullPath); return false; }, From f2fffcb67531edbe04c2b5eb2712d52438027a10 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 2 Jun 2020 21:11:16 +1000 Subject: [PATCH 74/91] rename method for better description --- Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs | 4 ++-- Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 2 +- Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 7a24d58fb..e3617b037 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -192,8 +192,8 @@ namespace Flow.Launcher.Plugin.Explorer SubTitle = "Path: " + record.FullPath, Action = _ => { - if(!Main.Settings.IndexSearchExcludedDirectories.Any(x => x == record.FullPath)) - Main.Settings.IndexSearchExcludedDirectories.Add(record.FullPath); + if(!Main.Settings.IndexSearchExcludedPaths.Any(x => x == record.FullPath)) + Main.Settings.IndexSearchExcludedPaths.Add(record.FullPath); return false; }, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 1ffc9ee51..ce94c67c6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -112,7 +112,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (!_settings.UseWindowsIndexForDirectorySearch) return false; - if (_settings.IndexSearchExcludedDirectories + if (_settings.IndexSearchExcludedPaths .Any(x => x == FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath))) return false; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 53011bf18..37f687d86 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -17,6 +17,6 @@ namespace Flow.Launcher.Plugin.Explorer public bool UseWindowsIndexForDirectorySearch { get; set; } = true; [JsonProperty] - public List IndexSearchExcludedDirectories { get; set; } = new List(); + public List IndexSearchExcludedPaths { get; set; } = new List(); } } From 2c15f1ce70a0c74a7409248ac84969c4afe35f0f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 2 Jun 2020 21:14:03 +1000 Subject: [PATCH 75/91] rename method for better description --- Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs | 4 ++-- Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 2 +- Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index e3617b037..6aba9fcb5 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -192,8 +192,8 @@ namespace Flow.Launcher.Plugin.Explorer SubTitle = "Path: " + record.FullPath, Action = _ => { - if(!Main.Settings.IndexSearchExcludedPaths.Any(x => x == record.FullPath)) - Main.Settings.IndexSearchExcludedPaths.Add(record.FullPath); + if(!Main.Settings.IndexSearchExcludedDirectoryPaths.Any(x => x == record.FullPath)) + Main.Settings.IndexSearchExcludedDirectoryPaths.Add(record.FullPath); return false; }, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index ce94c67c6..ad6cdd9dc 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -112,7 +112,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (!_settings.UseWindowsIndexForDirectorySearch) return false; - if (_settings.IndexSearchExcludedPaths + if (_settings.IndexSearchExcludedDirectoryPaths .Any(x => x == FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath))) return false; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 37f687d86..ffa7fadd1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -17,6 +17,6 @@ namespace Flow.Launcher.Plugin.Explorer public bool UseWindowsIndexForDirectorySearch { get; set; } = true; [JsonProperty] - public List IndexSearchExcludedPaths { get; set; } = new List(); + public List IndexSearchExcludedDirectoryPaths { get; set; } = new List(); } } From 06877ca06b8a68d00d3709075751d0d31a165b52 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 3 Jun 2020 20:15:21 +1000 Subject: [PATCH 76/91] change to exclude sub-directories too from index search --- Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs | 4 ++-- Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs | 4 ++-- Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 6aba9fcb5..38bf63f89 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -192,8 +192,8 @@ namespace Flow.Launcher.Plugin.Explorer SubTitle = "Path: " + record.FullPath, Action = _ => { - if(!Main.Settings.IndexSearchExcludedDirectoryPaths.Any(x => x == record.FullPath)) - Main.Settings.IndexSearchExcludedDirectoryPaths.Add(record.FullPath); + if(!Main.Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x == record.FullPath)) + Main.Settings.IndexSearchExcludedSubdirectoryPaths.Add(record.FullPath); return false; }, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index ad6cdd9dc..65a2f4ac9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -112,8 +112,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (!_settings.UseWindowsIndexForDirectorySearch) return false; - if (_settings.IndexSearchExcludedDirectoryPaths - .Any(x => x == FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath))) + if (_settings.IndexSearchExcludedSubdirectoryPaths + .Any(x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).StartsWith(x))) return false; return _indexSearch.PathIsIndexed(locationPath); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index ffa7fadd1..6b442384d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -17,6 +17,6 @@ namespace Flow.Launcher.Plugin.Explorer public bool UseWindowsIndexForDirectorySearch { get; set; } = true; [JsonProperty] - public List IndexSearchExcludedDirectoryPaths { get; set; } = new List(); + public List IndexSearchExcludedSubdirectoryPaths { get; set; } = new List(); } } From b90664d7601b2bc055fe23c98e699845a358500e Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 3 Jun 2020 22:00:18 +1000 Subject: [PATCH 77/91] add exclude index search confirmation and persist settings to storage --- .../Flow.Launcher.Plugin.Explorer/ContextMenu.cs | 16 ++++++++++++++-- .../Search/Constants.cs | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 38bf63f89..e36b09eb7 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -7,8 +7,8 @@ using System.Windows; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.Explorer.Search; -using System.Windows.Media; using System.Linq; +using System.Reflection; namespace Flow.Launcher.Plugin.Explorer { @@ -188,13 +188,25 @@ namespace Flow.Launcher.Plugin.Explorer { return new Result { - Title = "Exclude path from index search", + Title = "Exclude current and sub-directories from index search", SubTitle = "Path: " + record.FullPath, Action = _ => { if(!Main.Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x == record.FullPath)) Main.Settings.IndexSearchExcludedSubdirectoryPaths.Add(record.FullPath); + var pluginDirectory = Directory.GetParent(Assembly.GetExecutingAssembly().Location.ToString()); + + var iconPath = pluginDirectory + "\\" + Constants.ExplorerIconImagePath; + + Task.Run(() => + { + Main.Context.API.ShowMsg("Excluded from Index Search", "Path: " + record.FullPath, iconPath); + + // so the new path can be persisted to storage and not wait till next ViewModel save. + Main.Context.API.SaveAppAllSettings(); + }); + return false; }, IcoPath = Constants.ExcludeFromIndexImagePath diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs index 2f6a6f2ea..c033e3165 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs @@ -12,6 +12,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal const string CopyImagePath = "Images\\copy.png"; internal const string IndexImagePath = "Images\\index.png"; internal const string ExcludeFromIndexImagePath = "Images\\excludeindexpath.png"; + internal const string ExplorerIconImagePath = "Images\\explorer.png"; internal const string DefaultFolderSubtitleString = "Ctrl + Enter to open the directory"; From e34bcbd1ba311f347c3490828d374fbe5f6901e0 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 4 Jun 2020 19:06:03 +1000 Subject: [PATCH 78/91] add prompt press ctrl to open folder directly to folder results --- .../Search/DirectoryInfo/DirectoryInfoSearch.cs | 2 +- .../Search/WindowsIndex/IndexSearch.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index f70c4d125..0d7f726b5 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -66,7 +66,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo if (fileSystemInfo is System.IO.DirectoryInfo) { - folderList.Add(ResultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, fileSystemInfo.FullName, query, true, false)); + folderList.Add(ResultManager.CreateFolderResult(fileSystemInfo.Name, Constants.DefaultFolderSubtitleString, fileSystemInfo.FullName, query, true, false)); } else { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs index c2513820b..d224c9734 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs @@ -71,7 +71,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex private Result CreateResult(string filename, string path, string fileType, Query query) { if (fileType == "Directory") - return ResultManager.CreateFolderResult(filename, path, path, query, true, true); + return ResultManager.CreateFolderResult(filename, Constants.DefaultFolderSubtitleString, path, query, true, true); else { return ResultManager.CreateFileResult(path, query, true, true); From 0ee0fb8d2bf717abfbc1d30fa843c054c3040c98 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 6 Jun 2020 22:13:22 +1000 Subject: [PATCH 79/91] add Explorer plugin's settings page --- .../SharedCommands/FilesFolders.cs | 2 +- .../ContextMenu.cs | 5 +- .../Flow.Launcher.Plugin.Explorer.csproj | 1 + .../Search/FolderLinks/FolderLink.cs | 29 +++ .../QuickFolderAccess.cs | 2 +- .../Search/QuickFolderLinks/FolderLink.cs | 18 -- .../Search/SearchManager.cs | 6 +- .../Flow.Launcher.Plugin.Explorer/Settings.cs | 9 +- .../Views/ExplorerSettings.xaml | 45 +++- .../Views/ExplorerSettings.xaml.cs | 231 +++++++++++++++++- 10 files changed, 305 insertions(+), 43 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/FolderLink.cs rename Plugins/Flow.Launcher.Plugin.Explorer/Search/{QuickFolderLinks => FolderLinks}/QuickFolderAccess.cs (92%) delete mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/FolderLink.cs diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 32f6507fa..ce067e8a0 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -187,7 +187,7 @@ namespace Flow.Launcher.Plugin.SharedCommands } /// - /// Returns the previous level directory if path incomplete. + /// Returns the previous level directory if path incomplete (does not end with '\'). /// Does not check if previous level directory exists. /// Returns passed in string if is complete path /// diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index e36b09eb7..417161300 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -7,6 +7,7 @@ using System.Windows; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.Explorer.Search; +using Flow.Launcher.Plugin.Explorer.Search.FolderLinks; using System.Linq; using System.Reflection; @@ -192,8 +193,8 @@ namespace Flow.Launcher.Plugin.Explorer SubTitle = "Path: " + record.FullPath, Action = _ => { - if(!Main.Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x == record.FullPath)) - Main.Settings.IndexSearchExcludedSubdirectoryPaths.Add(record.FullPath); + if(!Main.Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x.Path == record.FullPath)) + Main.Settings.IndexSearchExcludedSubdirectoryPaths.Add(new FolderLink { Path = record.FullPath }); var pluginDirectory = Directory.GetParent(Assembly.GetExecutingAssembly().Location.ToString()); 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 65eed12d6..f45a70c52 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -4,6 +4,7 @@ Library netcoreapp3.1 true + true false diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/FolderLink.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/FolderLink.cs new file mode 100644 index 000000000..379b5848f --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/FolderLink.cs @@ -0,0 +1,29 @@ +using Newtonsoft.Json; +using System; +using System.Linq; + +namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks +{ + [JsonObject(MemberSerialization.OptIn)] + public class FolderLink + { + [JsonProperty] + public string Path { get; set; } + + public string Nickname + { + get + { + var path = Path.EndsWith(Constants.DirectorySeperator) ? Path[0..^1] : Path; + + if (path.EndsWith(':')) + return path[0..^1] + " Drive"; + + return path.Split(new[] { System.IO.Path.DirectorySeparatorChar }, StringSplitOptions.None) + .Last() + + " (" + System.IO.Path.GetDirectoryName(Path) + ")"; + } + } + } + +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/QuickFolderAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs similarity index 92% rename from Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/QuickFolderAccess.cs rename to Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs index 4bddf8168..1ffd8378b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/QuickFolderAccess.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Text; using System.Windows; -namespace Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks +namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks { public class QuickFolderAccess { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/FolderLink.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/FolderLink.cs deleted file mode 100644 index 33c6d37dc..000000000 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickFolderLinks/FolderLink.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Linq; - -namespace Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks -{ - [JsonObject(MemberSerialization.OptIn)] - public class FolderLink - { - [JsonProperty] - public string Path { get; set; } - - public string Nickname => - Path.Split(new[] { System.IO.Path.DirectorySeparatorChar }, StringSplitOptions.None) - .Last() - + " (" + System.IO.Path.GetDirectoryName(Path) + ")"; - } -} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 65a2f4ac9..5d6550f74 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,5 +1,5 @@ using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; -using Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks; +using Flow.Launcher.Plugin.Explorer.Search.FolderLinks; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.SharedCommands; using System; @@ -28,7 +28,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search { var querySearch = query.Search; - var quickFolderLinks = quickFolderAccess.FolderList(query, _settings.FolderLinks); + var quickFolderLinks = quickFolderAccess.FolderList(query, _settings.QuickFolderAccessLinks); if (quickFolderLinks.Count > 0) return quickFolderLinks; @@ -113,7 +113,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search return false; if (_settings.IndexSearchExcludedSubdirectoryPaths - .Any(x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).StartsWith(x))) + .Any(x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).StartsWith(x.Path))) return false; return _indexSearch.PathIsIndexed(locationPath); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 6b442384d..d4bfdbbfc 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -1,5 +1,4 @@ -using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; -using Flow.Launcher.Plugin.Explorer.Search.QuickFolderLinks; +using Flow.Launcher.Plugin.Explorer.Search.FolderLinks; using Newtonsoft.Json; using System.Collections.Generic; @@ -11,12 +10,12 @@ namespace Flow.Launcher.Plugin.Explorer public int MaxResult { get; set; } = 100; [JsonProperty] - public List FolderLinks { get; set; } = new List(); + public List QuickFolderAccessLinks { get; set; } = new List(); [JsonProperty] public bool UseWindowsIndexForDirectorySearch { get; set; } = true; [JsonProperty] - public List IndexSearchExcludedSubdirectoryPaths { get; set; } = new List(); + public List IndexSearchExcludedSubdirectoryPaths { get; set; } = new List(); } -} +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml index 93defd985..9e04c8b8f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml @@ -6,7 +6,48 @@ xmlns:viewModels="clr-namespace:Flow.Launcher.Plugin.Explorer.ViewModels" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - - + + + + + + + + + + + + + + + + + + + + + + + + +
public string QueryForTopLevelDirectorySearch(string path) { - string query = "SELECT TOP " + _settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE "; + string query = "SELECT TOP " + settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE "; if (path.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > path.LastIndexOf(Constants.DirectorySeperator)) return query + QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index 298476abd..c0bb60c40 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -1,4 +1,5 @@ using Flow.Launcher.Infrastructure.Storage; +using Flow.Launcher.Plugin.Explorer.Search.FolderLinks; using System; using System.Collections.Generic; using System.Text; @@ -7,19 +8,26 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels { public class SettingsViewModel { - private readonly PluginJsonStorage _storage; + private readonly PluginJsonStorage storage; - public Settings Settings { get; set; } + internal Settings Settings { get; set; } - public SettingsViewModel() + internal PluginInitContext Context { get; set; } + + public SettingsViewModel(PluginInitContext context) { - _storage = new PluginJsonStorage(); - Settings = _storage.Load(); + Context = context; + storage = new PluginJsonStorage(); + Settings = storage.Load(); } public void Save() { - _storage.Save(); + storage.Save(); } + + internal void RemoveFolderLinkFromQuickFolders(FolderLink selectedRow) => Settings.QuickFolderAccessLinks.Remove(selectedRow); + + internal void RemoveFolderLinkFromExcludedIndexPaths(FolderLink selectedRow) => Settings.IndexSearchExcludedSubdirectoryPaths.Remove(selectedRow); } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs index 104bef336..f36c3442e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs @@ -1,4 +1,5 @@ using Flow.Launcher.Plugin.Explorer.Search.FolderLinks; +using Flow.Launcher.Plugin.Explorer.ViewModels; using System; using System.Collections.Generic; using System.ComponentModel; @@ -18,13 +19,16 @@ namespace Flow.Launcher.Plugin.Explorer.Views ///
public partial class ExplorerSettings { - public ExplorerSettings() + private readonly SettingsViewModel viewModel; + public ExplorerSettings(SettingsViewModel viewModel) { InitializeComponent(); - lbxFolderLinks.ItemsSource = Main.Settings.QuickFolderAccessLinks; + this.viewModel = viewModel; - lbxExcludedPaths.ItemsSource = Main.Settings.IndexSearchExcludedSubdirectoryPaths; + lbxFolderLinks.ItemsSource = this.viewModel.Settings.QuickFolderAccessLinks; + + lbxExcludedPaths.ItemsSource = this.viewModel.Settings.IndexSearchExcludedSubdirectoryPaths; RefreshView(); } @@ -106,22 +110,22 @@ namespace Flow.Launcher.Plugin.Explorer.Views if (selectedRow != null) { - string msg = string.Format(Main.Context.API.GetTranslation("flowlauncher_plugin_folder_delete_folder_link"), selectedRow.Path); + string msg = string.Format(viewModel.Context.API.GetTranslation("flowlauncher_plugin_folder_delete_folder_link"), selectedRow.Path); if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes) { if (expFolderLinks.IsExpanded) - Main.Settings.QuickFolderAccessLinks.Remove(selectedRow); + viewModel.RemoveFolderLinkFromQuickFolders(selectedRow); if (expExcludedPaths.IsExpanded) - Main.Settings.IndexSearchExcludedSubdirectoryPaths.Remove(selectedRow); + viewModel.RemoveFolderLinkFromExcludedIndexPaths(selectedRow); RefreshView(); } } else { - string warning = Main.Context.API.GetTranslation("flowlauncher_plugin_folder_select_folder_link_warning"); + string warning = viewModel.Context.API.GetTranslation("flowlauncher_plugin_folder_select_folder_link_warning"); MessageBox.Show(warning); } } @@ -138,13 +142,13 @@ namespace Flow.Launcher.Plugin.Explorer.Views { if (expFolderLinks.IsExpanded) { - var link = Main.Settings.QuickFolderAccessLinks.First(x => x.Path == selectedRow.Path); + var link = viewModel.Settings.QuickFolderAccessLinks.First(x => x.Path == selectedRow.Path); link.Path = folderBrowserDialog.SelectedPath; } if (expExcludedPaths.IsExpanded) { - var link = Main.Settings.IndexSearchExcludedSubdirectoryPaths.First(x => x.Path == selectedRow.Path); + var link = viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.First(x => x.Path == selectedRow.Path); link.Path = folderBrowserDialog.SelectedPath; } } @@ -153,7 +157,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views } else { - string warning = Main.Context.API.GetTranslation("flowlauncher_plugin_folder_select_folder_link_warning"); + string warning = viewModel.Context.API.GetTranslation("flowlauncher_plugin_folder_select_folder_link_warning"); MessageBox.Show(warning); } } @@ -180,8 +184,8 @@ namespace Flow.Launcher.Plugin.Explorer.Views if (files != null && files.Count() > 0) { - if (expFolderLinks.IsExpanded && Main.Settings.QuickFolderAccessLinks == null) - Main.Settings.QuickFolderAccessLinks = new List(); + if (expFolderLinks.IsExpanded && viewModel.Settings.QuickFolderAccessLinks == null) + viewModel.Settings.QuickFolderAccessLinks = new List(); foreach (string s in files) { @@ -203,21 +207,21 @@ namespace Flow.Launcher.Plugin.Explorer.Views private void AddFolderLink(FolderLink newFolderLink) { if (expFolderLinks.IsExpanded - && !Main.Settings.QuickFolderAccessLinks.Any(x => x.Path == newFolderLink.Path)) + && !viewModel.Settings.QuickFolderAccessLinks.Any(x => x.Path == newFolderLink.Path)) { - if (Main.Settings.QuickFolderAccessLinks == null) - Main.Settings.QuickFolderAccessLinks = new List(); + if (viewModel.Settings.QuickFolderAccessLinks == null) + viewModel.Settings.QuickFolderAccessLinks = new List(); - Main.Settings.QuickFolderAccessLinks.Add(newFolderLink); + viewModel.Settings.QuickFolderAccessLinks.Add(newFolderLink); } if (expExcludedPaths.IsExpanded - && !Main.Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x.Path == newFolderLink.Path)) + && !viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x.Path == newFolderLink.Path)) { - if (Main.Settings.IndexSearchExcludedSubdirectoryPaths == null) - Main.Settings.IndexSearchExcludedSubdirectoryPaths = new List(); + if (viewModel.Settings.IndexSearchExcludedSubdirectoryPaths == null) + viewModel.Settings.IndexSearchExcludedSubdirectoryPaths = new List(); - Main.Settings.IndexSearchExcludedSubdirectoryPaths.Add(newFolderLink); + viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.Add(newFolderLink); } } From 6befd6cbb14b8c9ea2a92711a379b1afef952b1b Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 8 Jun 2020 14:47:57 +1000 Subject: [PATCH 81/91] small fixes - ignore upper case when determine index search - add open with different user image - add subtitle text for context menu containing folder --- .../ContextMenu.cs | 3 ++- .../Flow.Launcher.Plugin.Explorer.csproj | 6 +++++- .../Images/user.png | Bin 0 -> 65650 bytes .../Search/Constants.cs | 1 + .../Search/SearchManager.cs | 5 +++-- 5 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Images/user.png diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 26ee16975..49318cc86 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -136,7 +136,7 @@ namespace Flow.Launcher.Plugin.Explorer return true; }, - IcoPath = "Images/user.png" + IcoPath = Constants.DifferentUserIconImagePath }); } @@ -148,6 +148,7 @@ namespace Flow.Launcher.Plugin.Explorer return new Result { Title = "Open containing folder", + SubTitle = "Opens the location that contains the file or folder", Action = _ => { try 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 f45a70c52..0cbe94d86 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -51,7 +51,11 @@ PreserveNewest - + + + + PreserveNewest + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/user.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/user.png new file mode 100644 index 0000000000000000000000000000000000000000..2d45c1ee919304a973641b387934dbd517979271 GIT binary patch literal 65650 zcmV*nKuEudP)KoEefB#uyts#u%fQWvShg+Wqc(&#Brg ze^l+-YrpDLoqKNg?e4Q|^{uMkUTgnq*WPQdy=&J#goP|*R{5P100{Wb>wy2|3E)4y z1vh`_6rhkZBgguMoQv@ggV8b+Eu$DOV=`W$e6T|Ka1}(WD9U9$K6gF1+y$aFkXi$1 z6+~-b9)a}=z(W8dFmiwu00jVC%?_NSy`II+#xaI1Sb(!TKarc0vtz zJ`a_h2lzA>jYd()+z44*}#L%E7Gah&3T1hHbkXb+{1)p_i~MjcO2SFc$y{C^ZJD z^I*LW%1+Z@=>*F0aX>vygQdUYvV4?TKZ(KUB=qF`WU#cZM_NOT^>c69*P7Ep7P4@> z3#sHco~gXkHA5VHE`y#R=kbLhseBcLJt)ilAUed83ojy-A7ot|gf91h)CgJ+!8)%D zF&I!yyotJ!MVuf5AkQN@Seu?lf4WTugF12H8X#*!+elhfPFd@9sO&gp`ID@R$7#6Z zGtm4vvg~OvjlXfhT`cPHE?EJMejefaLNl=INpW)+DYsxCm89$kc!DNsca3C z@c{sbpeJ7o&9_0R!(2}G16YABR?P69!HU>8r0R+}VXiz^=pY$j0#SVofIv2YWd;e4 zC)_1iV7~LM@-x;v+cR?UbK?4#Y-$al;-vDk0M1f2cp6$i3Z=dPmH#ckk0W1ticy@@ z+3@@Z@%TiZ18=>i?Wru}3YUfBy-H=@uRN>Nq!<$AYplzI#C$uzUk@&CgVwjRE_VaG zQcV@Gs+?4}?~#sHlql|2WgJ`c*i0HXiMM4u;>J;}N_sRx7cGw07~{Nye3daMh%Ol0AB zuOeCc&11Os>@Xj4zJ~FIBhdOTl#`c&a68IkFTg9z

PbOc!t+-8s$(Xa?|2G$~zC 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≠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 literal 0 HcmV?d00001 diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs index c033e3165..aa16c64ad 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs @@ -13,6 +13,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal const string IndexImagePath = "Images\\index.png"; internal const string ExcludeFromIndexImagePath = "Images\\excludeindexpath.png"; internal const string ExplorerIconImagePath = "Images\\explorer.png"; + internal const string DifferentUserIconImagePath = "Images\\user.png"; internal const string DefaultFolderSubtitleString = "Ctrl + Enter to open the directory"; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 79641fe87..845b43c54 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; +using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; using Flow.Launcher.Plugin.Explorer.Search.FolderLinks; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.SharedCommands; @@ -117,7 +117,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search return false; if (settings.IndexSearchExcludedSubdirectoryPaths - .Any(x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).StartsWith(x.Path))) + .Any(x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath) + .StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))) return false; return indexSearch.PathIsIndexed(locationPath); From 407864beb72a7c3a2ebb9bfe58aa00c647bd78c0 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 8 Jun 2020 18:41:59 +1000 Subject: [PATCH 82/91] add option to open Indexing Options in context menu and settings page --- .../ContextMenu.cs | 38 +++++++++++++++++- .../Flow.Launcher.Plugin.Explorer.csproj | 4 ++ .../Images/windowsindexingoptions.png | Bin 0 -> 3772 bytes .../Search/Constants.cs | 3 ++ .../ViewModels/SettingsViewModel.cs | 17 ++++++-- .../Views/ExplorerSettings.xaml | 19 ++++++--- .../Views/ExplorerSettings.xaml.cs | 5 +++ 7 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Images/windowsindexingoptions.png diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 49318cc86..02195db98 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -38,6 +38,8 @@ namespace Flow.Launcher.Plugin.Explorer contextMenus.Add(CreateOpenContainingFolderResult(record)); + contextMenus.Add(CreateOpenWindowsIndexingOptions(record)); + if (record.ShowIndexState) contextMenus.Add(new Result {Title = "From index search: " + (record.WindowsIndexed ? "Yes" : "No"), SubTitle = "Location: " + record.FullPath, @@ -186,13 +188,13 @@ namespace Flow.Launcher.Plugin.Explorer } catch (Exception e) { - var message = $"Fail to editor for file at {record.FullPath}"; + var message = $"Failed to open editor for file at {record.FullPath}"; LogException(message, e); Context.API.ShowMsg(message); return false; } }, - IcoPath = editorPath + IcoPath = Constants.FileImagePath }; } @@ -225,6 +227,38 @@ namespace Flow.Launcher.Plugin.Explorer }; } + private Result CreateOpenWindowsIndexingOptions(SearchResult record) + { + return new Result + { + Title = "Open Windows Indexing Options", + SubTitle = "Manage indexed files and folders", + Action = _ => + { + try + { + var psi = new ProcessStartInfo + { + FileName = "control.exe", + UseShellExecute = true, + Arguments = "srchadmin.dll" + }; + + Process.Start(psi); + return true; + } + catch (Exception e) + { + var message = $"Failed to open Windows Indexing Options"; + LogException(message, e); + Context.API.ShowMsg(message); + return false; + } + }, + IcoPath = Constants.IndexingOptionsIconImagePath + }; + } + public void LogException(string message, Exception e) { Log.Exception($"|Flow.Launcher.Plugin.Folder.ContextMenu|{message}", e); 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 0cbe94d86..3025f8fb7 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -56,6 +56,10 @@ PreserveNewest + + + PreserveNewest + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/windowsindexingoptions.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/windowsindexingoptions.png new file mode 100644 index 0000000000000000000000000000000000000000..78a015715ed228d84d4a2066a2f42d7e827efbd3 GIT binary patch literal 3772 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 mc0wd{3wPp9e6wQFqW=N1$a(d|v)2s(0000 Settings.QuickFolderAccessLinks.Remove(selectedRow); internal void RemoveFolderLinkFromExcludedIndexPaths(FolderLink selectedRow) => Settings.IndexSearchExcludedSubdirectoryPaths.Remove(selectedRow); + + internal void OpenWindowsIndexingOptions() + { + var psi = new ProcessStartInfo + { + FileName = "control.exe", + UseShellExecute = true, + Arguments = Constants.WindowsIndexingOptions + }; + + Process.Start(psi); + } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml index 9e04c8b8f..b2f999111 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml @@ -44,10 +44,19 @@ - -