diff --git a/src/Files.App/Utils/Storage/Search/FolderSearch.cs b/src/Files.App/Utils/Storage/Search/FolderSearch.cs index 29042ca0f959..9d8fcb21f497 100644 --- a/src/Files.App/Utils/Storage/Search/FolderSearch.cs +++ b/src/Files.App/Utils/Storage/Search/FolderSearch.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using System.IO; +using System.Text.RegularExpressions; using Windows.Storage; using Windows.Storage.FileProperties; using Windows.Storage.Search; @@ -94,7 +95,7 @@ public Task SearchAsync(IList results, CancellationToken token) private async Task AddItemsForHomeAsync(IList results, CancellationToken token) { - if (AQSQuery.StartsWith("tag:", StringComparison.Ordinal)) + if (IsTagQuery(AQSQuery)) { await SearchTagsAsync("", results, token); // Search tags everywhere, not only local drives } @@ -183,19 +184,99 @@ private async Task AddItemsForLibraryAsync(LibraryLocationItem library, IList
  • (); + var andParts = Regex.Split(orPart, @"\s+AND\s+", RegexOptions.IgnoreCase); + + foreach (var andPart in andParts) + { + var matches = Regex.Matches(andPart.Trim(), @"(NOT\s+)?tag:([^\s]+)", RegexOptions.IgnoreCase); + foreach (Match match in matches) + { + var isExclude = !string.IsNullOrEmpty(match.Groups[1].Value); + var tagValues = match.Groups[2].Value.Split(',', StringSplitOptions.RemoveEmptyEntries); + var tagUids = new HashSet(); + + foreach (var tagName in tagValues) + { + var uids = fileTagsSettingsService.GetTagsByName(tagName).Select(t => t.Uid); + foreach (var uid in uids) + { + tagUids.Add(uid); + } + } + + andGroup.Add(new TagTerm { TagUids = tagUids, IsExclude = isExclude }); + } + } + + if (andGroup.Count > 0) + { + expression.OrGroups.Add(andGroup); + } + } + + return expression; + } + + private bool MatchesTagExpression(IEnumerable fileTags, TagQueryExpression expression) + { + foreach (var orGroup in expression.OrGroups) + { + bool groupMatches = true; + foreach (var term in orGroup) + { + if (term.IsExclude) + { + if (term.TagUids.Count > 0 && term.TagUids.Any(fileTags.Contains)) + { + groupMatches = false; + break; + } + } + else + { + if (term.TagUids.Count == 0 || !term.TagUids.Any(fileTags.Contains)) + { + groupMatches = false; + break; + } + } + } + + if (groupMatches) + { + return true; + } + } + + return false; + } + private async Task SearchTagsAsync(string folder, IList results, CancellationToken token) { //var sampler = new IntervalSampler(500); - var tags = AQSQuery.Substring("tag:".Length)?.Split(',').Where(t => !string.IsNullOrWhiteSpace(t)) - .SelectMany(t => fileTagsSettingsService.GetTagsByName(t), (_, t) => t.Uid).ToHashSet(); - if (tags?.Any() != true) + var expression = ParseTagQuery(AQSQuery); + + if (expression.OrGroups.Count == 0) { return; } var dbInstance = FileTagsHelper.GetDbInstance(); var matches = dbInstance.GetAllUnderPath(folder) - .Where(x => tags.All(x.Tags.Contains)); + .Where(x => MatchesTagExpression(x.Tags, expression)); if (string.IsNullOrEmpty(folder)) matches = matches.Where(x => !StorageTrashBinService.IsUnderTrashBin(x.FilePath)); @@ -258,7 +339,7 @@ private async Task SearchTagsAsync(string folder, IList results, Can private async Task AddItemsAsync(string folder, IList results, CancellationToken token) { - if (AQSQuery.StartsWith("tag:", StringComparison.Ordinal)) + if (IsTagQuery(AQSQuery)) { await SearchTagsAsync(folder, results, token); } diff --git a/src/Files.App/Utils/Storage/Search/TagQueryExpression.cs b/src/Files.App/Utils/Storage/Search/TagQueryExpression.cs new file mode 100644 index 000000000000..df34979d1743 --- /dev/null +++ b/src/Files.App/Utils/Storage/Search/TagQueryExpression.cs @@ -0,0 +1,10 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.App.Utils.Storage +{ + public sealed class TagQueryExpression + { + public List> OrGroups { get; set; } = new(); + } +} diff --git a/src/Files.App/Utils/Storage/Search/TagTerm.cs b/src/Files.App/Utils/Storage/Search/TagTerm.cs new file mode 100644 index 000000000000..0614dfd35013 --- /dev/null +++ b/src/Files.App/Utils/Storage/Search/TagTerm.cs @@ -0,0 +1,12 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.App.Utils.Storage +{ + public class TagTerm + { + public HashSet TagUids { get; set; } = new(); + + public bool IsExclude { get; set; } + } +}