Merge pull request #1817 from VictoriousRaptor/FixExplorer

Fix bugs in explorer plugin
This commit is contained in:
Jeremy Wu 2023-01-23 08:11:15 +11:00 committed by GitHub
commit db2b856a66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 265 additions and 122 deletions

View file

@ -1,7 +1,9 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
#pragma warning disable IDE0005
using System.Windows; using System.Windows;
#pragma warning restore IDE0005
namespace Flow.Launcher.Plugin.SharedCommands namespace Flow.Launcher.Plugin.SharedCommands
{ {
@ -206,22 +208,16 @@ namespace Flow.Launcher.Plugin.SharedCommands
///</summary> ///</summary>
public static string GetPreviousExistingDirectory(Func<string, bool> locationExists, string path) public static string GetPreviousExistingDirectory(Func<string, bool> locationExists, string path)
{ {
var previousDirectoryPath = "";
var index = path.LastIndexOf('\\'); var index = path.LastIndexOf('\\');
if (index > 0 && index < (path.Length - 1)) if (index > 0 && index < (path.Length - 1))
{ {
previousDirectoryPath = path.Substring(0, index + 1); string previousDirectoryPath = path.Substring(0, index + 1);
if (!locationExists(previousDirectoryPath)) return locationExists(previousDirectoryPath) ? previousDirectoryPath : "";
{
return "";
}
} }
else else
{ {
return ""; return "";
} }
return previousDirectoryPath;
} }
///<summary> ///<summary>
@ -241,5 +237,33 @@ namespace Flow.Launcher.Plugin.SharedCommands
return path; return path;
} }
/// <summary>
/// Returns if <paramref name="parentPath"/> contains <paramref name="subPath"/>.
/// From https://stackoverflow.com/a/66877016
/// </summary>
/// <param name="parentPath">Parent path</param>
/// <param name="subPath">Sub path</param>
/// <param name="allowEqual">If <see langword="true"/>, when <paramref name="parentPath"/> and <paramref name="subPath"/> are equal, returns <see langword="true"/></param>
/// <returns></returns>
public static bool PathContains(string parentPath, string subPath, bool allowEqual = false)
{
var rel = Path.GetRelativePath(parentPath.EnsureTrailingSlash(), subPath);
return (rel != "." || allowEqual)
&& rel != ".."
&& !rel.StartsWith("../")
&& !rel.StartsWith(@"..\")
&& !Path.IsPathRooted(rel);
}
/// <summary>
/// Returns path ended with "\"
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static string EnsureTrailingSlash(this string path)
{
return path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
}
} }
} }

View file

@ -0,0 +1,53 @@
using Flow.Launcher.Plugin.SharedCommands;
using NUnit.Framework;
namespace Flow.Launcher.Test
{
[TestFixture]
public class FilesFoldersTest
{
// Testcases from https://stackoverflow.com/a/31941905/20703207
// Disk
[TestCase(@"c:", @"c:\foo", true)]
[TestCase(@"c:\", @"c:\foo", true)]
// Slash
[TestCase(@"c:\foo\bar\", @"c:\foo\", false)]
[TestCase(@"c:\foo\bar", @"c:\foo\", false)]
[TestCase(@"c:\foo", @"c:\foo\bar", true)]
[TestCase(@"c:\foo\", @"c:\foo\bar", true)]
// File
[TestCase(@"c:\foo", @"c:\foo\a.txt", true)]
[TestCase(@"c:\foo", @"c:/foo/a.txt", true)]
[TestCase(@"c:\FOO\a.txt", @"c:\foo", false)]
[TestCase(@"c:\foo\a.txt", @"c:\foo\", false)]
[TestCase(@"c:\foobar\a.txt", @"c:\foo", false)]
[TestCase(@"c:\foobar\a.txt", @"c:\foo\", false)]
[TestCase(@"c:\foo\", @"c:\foo.txt", false)]
// Prefix
[TestCase(@"c:\foo", @"c:\foobar", false)]
[TestCase(@"C:\Program", @"C:\Program Files\", false)]
[TestCase(@"c:\foobar", @"c:\foo\a.txt", false)]
[TestCase(@"c:\foobar\", @"c:\foo\a.txt", false)]
// Edge case
[TestCase(@"c:\foo", @"c:\foo\..\bar\baz", false)]
[TestCase(@"c:\bar", @"c:\foo\..\bar\baz", true)]
[TestCase(@"c:\barr", @"c:\foo\..\bar\baz", false)]
// Equality
[TestCase(@"c:\foo", @"c:\foo", false)]
[TestCase(@"c:\foo\", @"c:\foo", false)]
[TestCase(@"c:\foo", @"c:\foo\", false)]
public void GivenTwoPaths_WhenCheckPathContains_ThenShouldBeExpectedResult(string parentPath, string path, bool expectedResult)
{
Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path));
}
[TestCase(@"c:\foo", @"c:\foo", true)]
[TestCase(@"c:\foo\", @"c:\foo", true)]
[TestCase(@"c:\foo", @"c:\foo\", true)]
public void GivenTwoPathsAreTheSame_WhenCheckPathContains_ThenShouldBeTrue(string parentPath, string path, bool expectedResult)
{
Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path, true));
}
}
}

View file

@ -7,9 +7,11 @@ using Flow.Launcher.Plugin.SharedCommands;
using NUnit.Framework; using NUnit.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using static Flow.Launcher.Plugin.Explorer.Search.SearchManager;
namespace Flow.Launcher.Test.Plugins namespace Flow.Launcher.Test.Plugins
{ {
@ -176,7 +178,7 @@ namespace Flow.Launcher.Test.Plugins
var searchManager = new SearchManager(new Settings(), new PluginInitContext()); var searchManager = new SearchManager(new Settings(), new PluginInitContext());
// When // When
var result = SearchManager.IsFileContentSearch(query.ActionKeyword); var result = searchManager.IsFileContentSearch(query.ActionKeyword);
// Then // Then
Assert.IsTrue(result, Assert.IsTrue(result,
@ -193,6 +195,7 @@ namespace Flow.Launcher.Test.Plugins
[TestCase(@"c:\>*", true)] [TestCase(@"c:\>*", true)]
[TestCase(@"c:\>", true)] [TestCase(@"c:\>", true)]
[TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)] [TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)]
[TestCase(@"c:\SomeLocation\SomeOtherLocation", true)]
public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult) public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult)
{ {
// When, Given // When, Given
@ -393,5 +396,68 @@ namespace Flow.Launcher.Test.Plugins
// Then // Then
Assert.AreEqual(result, expectedResult); Assert.AreEqual(result, expectedResult);
} }
[TestCase(@"c:\foo", @"c:\foo", true)]
[TestCase(@"C:\Foo\", @"c:\foo\", true)]
[TestCase(@"c:\foo", @"c:\foo\", false)]
public void GivenTwoPaths_WhenCompared_ThenShouldBeExpectedSameOrDifferent(string path1, string path2, bool expectedResult)
{
// Given
var comparator = PathEqualityComparator.Instance;
var result1 = new Result
{
Title = Path.GetFileName(path1),
SubTitle = path1
};
var result2 = new Result
{
Title = Path.GetFileName(path2),
SubTitle = path2
};
// When, Then
Assert.AreEqual(expectedResult, comparator.Equals(result1, result2));
}
[TestCase(@"c:\foo\", @"c:\foo\")]
[TestCase(@"C:\Foo\", @"c:\foo\")]
public void GivenTwoPaths_WhenComparedHasCode_ThenShouldBeSame(string path1, string path2)
{
// Given
var comparator = PathEqualityComparator.Instance;
var result1 = new Result
{
Title = Path.GetFileName(path1),
SubTitle = path1
};
var result2 = new Result
{
Title = Path.GetFileName(path2),
SubTitle = path2
};
var hash1 = comparator.GetHashCode(result1);
var hash2 = comparator.GetHashCode(result2);
// When, Then
Assert.IsTrue(hash1 == hash2);
}
[TestCase(@"%appdata%", true)]
[TestCase(@"%appdata%\123", true)]
[TestCase(@"c:\foo %appdata%\", false)]
[TestCase(@"c:\users\%USERNAME%\downloads", true)]
[TestCase(@"c:\downloads", false)]
[TestCase(@"%", false)]
[TestCase(@"%%", false)]
[TestCase(@"%bla%blabla%", false)]
public void GivenPath_WhenHavingEnvironmentVariableOrNot_ThenShouldBeExpected(string path, bool expectedResult)
{
// When
var result = EnvironmentVariables.HasEnvironmentVar(path);
// Then
Assert.AreEqual(result, expectedResult);
}
} }
} }

View file

@ -3,8 +3,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using Flow.Launcher.Plugin.Explorer.Search.IProvider;
using JetBrains.Annotations;
namespace Flow.Launcher.Plugin.Explorer.Exceptions; namespace Flow.Launcher.Plugin.Explorer.Exceptions;
@ -20,7 +18,7 @@ public class EngineNotAvailableException : Exception
string engineName, string engineName,
string resolution, string resolution,
string message, string message,
Func<ActionContext, ValueTask<bool>> action = null) : base(message) Func<ActionContext, ValueTask<bool>>? action = null) : base(message)
{ {
EngineName = engineName; EngineName = engineName;
Resolution = resolution; Resolution = resolution;
@ -40,6 +38,23 @@ public class EngineNotAvailableException : Exception
EngineName = engineName; EngineName = engineName;
Resolution = resolution; Resolution = resolution;
} }
public EngineNotAvailableException(
string engineName,
string resolution,
string message,
string errorIconPath,
Func<ActionContext, ValueTask<bool>>? action = null) : base(message)
{
EngineName = engineName;
Resolution = resolution;
ErrorIcon = errorIconPath;
Action = action ?? (_ =>
{
Clipboard.SetDataObject(this.ToString());
return ValueTask.FromResult(true);
});
}
public override string ToString() public override string ToString()
{ {

View file

@ -1,70 +1,75 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Linq;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Plugin.Explorer.Search namespace Flow.Launcher.Plugin.Explorer.Search
{ {
public static class EnvironmentVariables public static class EnvironmentVariables
{ {
internal static bool IsEnvironmentVariableSearch(string search) private static Dictionary<string, string> _envStringPaths = null;
private static Dictionary<string, string> EnvStringPaths
{ {
return search.StartsWith("%") get
&& search != "%%" {
&& !search.Contains("\\") && if (_envStringPaths == null)
LoadEnvironmentStringPaths().Count > 0; {
LoadEnvironmentStringPaths();
}
return _envStringPaths;
}
} }
internal static Dictionary<string, string> LoadEnvironmentStringPaths() internal static bool IsEnvironmentVariableSearch(string search)
{ {
var envStringPaths = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase); return search.StartsWith("%")
&& search != "%%"
&& !search.Contains('\\')
&& EnvStringPaths.Count > 0;
}
public static bool HasEnvironmentVar(string search)
{
// "c:\foo %appdata%\" returns false
var splited = search.Split(Path.DirectorySeparatorChar);
return splited.Any(dir => dir.StartsWith('%') &&
dir.EndsWith('%') &&
dir.Length > 2 &&
dir.Split('%').Length == 3);
}
private static void LoadEnvironmentStringPaths()
{
_envStringPaths = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
var homedrive = Environment.GetEnvironmentVariable("HOMEDRIVE")?.EnsureTrailingSlash() ?? "C:\\";
foreach (DictionaryEntry special in Environment.GetEnvironmentVariables()) foreach (DictionaryEntry special in Environment.GetEnvironmentVariables())
{ {
var path = special.Value.ToString(); var path = special.Value.ToString();
// we add a trailing slash to the path to make sure drive paths become valid absolute paths.
// for example, if %systemdrive% is C: we turn it to C:\
path = path.EnsureTrailingSlash();
// if we don't have an absolute path, we use Path.GetFullPath to get one.
// for example, if %homepath% is \Users\John we turn it to C:\Users\John
// Add basepath for GetFullPath() to parse %HOMEPATH% correctly
path = Path.IsPathFullyQualified(path) ? path : Path.GetFullPath(path, homedrive);
if (Directory.Exists(path)) if (Directory.Exists(path))
{ {
// we add a trailing slash to the path to make sure drive paths become valid absolute paths.
// for example, if %systemdrive% is C: we turn it to C:\
path = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
// if we don't have an absolute path, we use Path.GetFullPath to get one.
// for example, if %homepath% is \Users\John we turn it to C:\Users\John
path = Path.IsPathFullyQualified(path) ? path : Path.GetFullPath(path);
// Variables are returned with a mixture of all upper/lower case. // Variables are returned with a mixture of all upper/lower case.
// Call ToLower() to make the results look consistent // Call ToUpper() to make the results look consistent
envStringPaths.Add(special.Key.ToString().ToLower(), path); _envStringPaths.Add(special.Key.ToString().ToUpper(), path);
} }
} }
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<Result> GetEnvironmentStringPathSuggestions(string querySearch, Query query, PluginInitContext context) internal static List<Result> GetEnvironmentStringPathSuggestions(string querySearch, Query query, PluginInitContext context)
{ {
var results = new List<Result>(); var results = new List<Result>();
var environmentVariables = LoadEnvironmentStringPaths();
var search = querySearch; var search = querySearch;
if (querySearch.EndsWith("%") && search.Length > 1) if (querySearch.EndsWith("%") && search.Length > 1)
@ -72,12 +77,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
// query starts and ends with a %, find an exact match from env-string paths // query starts and ends with a %, find an exact match from env-string paths
search = querySearch.Substring(1, search.Length - 2); search = querySearch.Substring(1, search.Length - 2);
if (environmentVariables.ContainsKey(search)) if (EnvStringPaths.ContainsKey(search))
{ {
var expandedPath = environmentVariables[search]; var expandedPath = EnvStringPaths[search];
results.Add(ResultManager.CreateFolderResult($"%{search}%", expandedPath, expandedPath, query)); results.Add(ResultManager.CreateFolderResult($"%{search}%", expandedPath, expandedPath, query));
return results; return results;
} }
} }
@ -90,8 +95,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{ {
search = search.Substring(1); search = search.Substring(1);
} }
foreach (var p in environmentVariables) foreach (var p in EnvStringPaths)
{ {
if (p.Key.StartsWith(search, StringComparison.InvariantCultureIgnoreCase)) if (p.Key.StartsWith(search, StringComparison.InvariantCultureIgnoreCase))
{ {

View file

@ -27,20 +27,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!, Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_click_to_launch_or_install"), Main.Context.API.GetTranslation("flowlauncher_plugin_everything_click_to_launch_or_install"),
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running"), Main.Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running"),
ClickToInstallEverythingAsync) Constants.EverythingErrorImagePath,
{ ClickToInstallEverythingAsync);
ErrorIcon = Constants.EverythingErrorImagePath
};
} }
catch (DllNotFoundException) catch (DllNotFoundException)
{ {
throw new EngineNotAvailableException( throw new EngineNotAvailableException(
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!, Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
"Please check whether your system is x86 or x64", "Please check whether your system is x86 or x64",
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue")) Constants.GeneralSearchErrorImagePath,
{ Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue"));
ErrorIcon = Constants.GeneralSearchErrorImagePath
};
} }
} }
private async ValueTask<bool> ClickToInstallEverythingAsync(ActionContext _) private async ValueTask<bool> ClickToInstallEverythingAsync(ActionContext _)
@ -72,16 +68,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
if (!Settings.EnableEverythingContentSearch) if (!Settings.EnableEverythingContentSearch)
{ {
throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything)!, throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
"Click to Enable Everything Content Search (only applicable to Everything 1.5+ with indexed content)", Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"),
"Everything Content Search is not enabled.", Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"),
Constants.EverythingErrorImagePath,
_ => _ =>
{ {
Settings.EnableEverythingContentSearch = true; Settings.EnableEverythingContentSearch = true;
return ValueTask.FromResult(true); return ValueTask.FromResult(true);
}) });
{
ErrorIcon = Constants.EverythingErrorImagePath
};
} }
if (token.IsCancellationRequested) if (token.IsCancellationRequested)
yield break; yield break;

View file

@ -71,7 +71,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{ {
Title = title, Title = title,
IcoPath = path, IcoPath = path,
SubTitle = Path.GetDirectoryName(path), SubTitle = subtitle,
AutoCompleteText = GetAutoCompleteText(title, query, path, ResultType.Folder), AutoCompleteText = GetAutoCompleteText(title, query, path, ResultType.Folder),
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData, TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData,
CopyText = path, CopyText = path,

View file

@ -13,9 +13,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{ {
public class SearchManager public class SearchManager
{ {
internal static PluginInitContext Context; internal PluginInitContext Context;
internal static Settings Settings; internal Settings Settings;
public SearchManager(Settings settings, PluginInitContext context) public SearchManager(Settings settings, PluginInitContext context)
{ {
@ -23,19 +23,23 @@ namespace Flow.Launcher.Plugin.Explorer.Search
Settings = settings; Settings = settings;
} }
private class PathEqualityComparator : IEqualityComparer<Result> /// <summary>
/// Note: A path that ends with "\" and one that doesn't will not be regarded as equal.
/// </summary>
public class PathEqualityComparator : IEqualityComparer<Result>
{ {
private static PathEqualityComparator instance; private static PathEqualityComparator instance;
public static PathEqualityComparator Instance => instance ??= new PathEqualityComparator(); public static PathEqualityComparator Instance => instance ??= new PathEqualityComparator();
public bool Equals(Result x, Result y) public bool Equals(Result x, Result y)
{ {
return x.Title == y.Title && x.SubTitle == y.SubTitle; return x.Title.Equals(y.Title, StringComparison.OrdinalIgnoreCase)
&& string.Equals(x.SubTitle, y.SubTitle, StringComparison.OrdinalIgnoreCase);
} }
public int GetHashCode(Result obj) public int GetHashCode(Result obj)
{ {
return HashCode.Combine(obj.Title.GetHashCode(), obj.SubTitle?.GetHashCode() ?? 0); return HashCode.Combine(obj.Title.ToLowerInvariant(), obj.SubTitle?.ToLowerInvariant() ?? "");
} }
} }
@ -105,19 +109,21 @@ namespace Flow.Launcher.Plugin.Explorer.Search
await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false)) await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false))
results.Add(ResultManager.CreateResult(query, search)); results.Add(ResultManager.CreateResult(query, search));
} }
catch (OperationCanceledException)
{
return new List<Result>();
}
catch (EngineNotAvailableException)
{
throw;
}
catch (Exception e) catch (Exception e)
{ {
if (e is OperationCanceledException)
return results.ToList();
if (e is EngineNotAvailableException)
throw;
throw new SearchException(engineName, e.Message, e); throw new SearchException(engineName, e.Message, e);
} }
results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any( results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any(
excludedPath => r.SubTitle.StartsWith(excludedPath.Path, StringComparison.OrdinalIgnoreCase))); excludedPath => FilesFolders.PathContains(excludedPath.Path, r.SubTitle)));
return results.ToList(); return results.ToList();
} }
@ -142,7 +148,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
}; };
} }
private static List<Result> EverythingContentSearchResult(Query query) private List<Result> EverythingContentSearchResult(Query query)
{ {
return new List<Result>() return new List<Result>()
{ {
@ -167,18 +173,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
var results = new HashSet<Result>(PathEqualityComparator.Instance); var results = new HashSet<Result>(PathEqualityComparator.Instance);
var isEnvironmentVariable = EnvironmentVariables.IsEnvironmentVariableSearch(querySearch); if (EnvironmentVariables.IsEnvironmentVariableSearch(querySearch))
if (isEnvironmentVariable)
return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, Context); return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, Context);
// Query is a location path with a full environment variable, eg. %appdata%\somefolder\ // Query is a location path with a full environment variable, eg. %appdata%\somefolder\, c:\users\%USERNAME%\downloads
var isEnvironmentVariablePath = querySearch[1..].Contains("%\\"); var needToExpand = EnvironmentVariables.HasEnvironmentVar(querySearch);
var locationPath = needToExpand ? Environment.ExpandEnvironmentVariables(querySearch) : querySearch;
var locationPath = querySearch;
if (isEnvironmentVariablePath)
locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath);
// Check that actual location exists, otherwise directory search will throw directory not found exception // Check that actual location exists, otherwise directory search will throw directory not found exception
if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).LocationExists()) if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).LocationExists())
@ -234,7 +234,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return results.ToList(); return results.ToList();
} }
public static bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword; public bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword;
private bool UseWindowsIndexForDirectorySearch(string locationPath) private bool UseWindowsIndexForDirectorySearch(string locationPath)
@ -245,10 +245,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search
x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase)) x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))
&& WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory); && WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory);
} }
internal static bool IsEnvironmentVariableSearch(string search) internal static bool IsEnvironmentVariableSearch(string search)
{ {
return search.StartsWith("%") return search.StartsWith("%")
&& search != "%%" && search != "%%"
&& !search.Contains('\\'); && !search.Contains('\\');
} }

View file

@ -97,27 +97,25 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
private IAsyncEnumerable<SearchResult> HandledEngineNotAvailableExceptionAsync() private IAsyncEnumerable<SearchResult> HandledEngineNotAvailableExceptionAsync()
{ {
if (!SearchManager.Settings.WarnWindowsSearchServiceOff) if (!Settings.WarnWindowsSearchServiceOff)
return AsyncEnumerable.Empty<SearchResult>(); return AsyncEnumerable.Empty<SearchResult>();
var api = SearchManager.Context.API; var api = Main.Context.API;
throw new EngineNotAvailableException( throw new EngineNotAvailableException(
"Windows Index", "Windows Index",
api.GetTranslation("plugin_explorer_windowsSearchServiceFix"), api.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"), api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
Constants.WindowsIndexErrorImagePath,
c => c =>
{ {
SearchManager.Settings.WarnWindowsSearchServiceOff = false; Settings.WarnWindowsSearchServiceOff = false;
// Clears the warning message so user is not mistaken that it has not worked // Clears the warning message so user is not mistaken that it has not worked
api.ChangeQuery(string.Empty); api.ChangeQuery(string.Empty);
return ValueTask.FromResult(false); return ValueTask.FromResult(false);
}) });
{
ErrorIcon = Constants.WindowsIndexErrorImagePath
};
} }
} }
} }

View file

@ -470,8 +470,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
} }
var paths = pathEnv.Split(";", StringSplitOptions.RemoveEmptyEntries).DistinctBy(p => p.ToLowerInvariant()); var paths = pathEnv.Split(";", StringSplitOptions.RemoveEmptyEntries).DistinctBy(p => p.ToLowerInvariant());
var toFilter = paths.Where(x => commonParents.All(parent => !IsSubPathOf(x, parent))) var toFilter = paths.Where(x => commonParents.All(parent => !FilesFolders.PathContains(parent, x)))
.AsParallel() .AsParallel()
.SelectMany(p => EnumerateProgramsInDir(p, suffixes, recursive: false)); .SelectMany(p => EnumerateProgramsInDir(p, suffixes, recursive: false));
@ -763,17 +763,6 @@ namespace Flow.Launcher.Plugin.Program.Programs
} }
} }
// https://stackoverflow.com/a/66877016
private static bool IsSubPathOf(string subPath, string basePath)
{
var rel = Path.GetRelativePath(basePath, subPath);
return rel != "."
&& rel != ".."
&& !rel.StartsWith("../")
&& !rel.StartsWith(@"..\")
&& !Path.IsPathRooted(rel);
}
private static List<string> GetCommonParents(IEnumerable<ProgramSource> programSources) private static List<string> GetCommonParents(IEnumerable<ProgramSource> programSources)
{ {
// To avoid unnecessary io // To avoid unnecessary io
@ -785,8 +774,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
HashSet<ProgramSource> parents = group.ToHashSet(); HashSet<ProgramSource> parents = group.ToHashSet();
foreach (var source in group) foreach (var source in group)
{ {
if (parents.Any(p => IsSubPathOf(source.Location, p.Location) && if (parents.Any(p => FilesFolders.PathContains(p.Location, source.Location)))
source != p))
{ {
parents.Remove(source); parents.Remove(source);
} }