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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 87 additions & 6 deletions src/Files.App/Utils/Storage/Search/FolderSearch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -94,7 +95,7 @@ public Task SearchAsync(IList<ListedItem> results, CancellationToken token)

private async Task AddItemsForHomeAsync(IList<ListedItem> results, CancellationToken token)
{
if (AQSQuery.StartsWith("tag:", StringComparison.Ordinal))
if (IsTagQuery(AQSQuery))
{
await SearchTagsAsync("", results, token); // Search tags everywhere, not only local drives
}
Expand Down Expand Up @@ -183,19 +184,99 @@ private async Task AddItemsForLibraryAsync(LibraryLocationItem library, IList<Li
}
}

private bool IsTagQuery(string query)
{
return query?.Contains("tag:", StringComparison.OrdinalIgnoreCase) == true;
}

private TagQueryExpression ParseTagQuery(string query)
{
var expression = new TagQueryExpression();
var orParts = Regex.Split(query, @"\s+OR\s+", RegexOptions.IgnoreCase);

foreach (var orPart in orParts)
{
var andGroup = new List<TagTerm>();
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<string>();

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<string> 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<ListedItem> 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));

Expand Down Expand Up @@ -258,7 +339,7 @@ private async Task SearchTagsAsync(string folder, IList<ListedItem> results, Can

private async Task AddItemsAsync(string folder, IList<ListedItem> results, CancellationToken token)
{
if (AQSQuery.StartsWith("tag:", StringComparison.Ordinal))
if (IsTagQuery(AQSQuery))
{
await SearchTagsAsync(folder, results, token);
}
Expand Down
10 changes: 10 additions & 0 deletions src/Files.App/Utils/Storage/Search/TagQueryExpression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

namespace Files.App.Utils.Storage
{
public sealed class TagQueryExpression
{
public List<List<TagTerm>> OrGroups { get; set; } = new();
}
}
12 changes: 12 additions & 0 deletions src/Files.App/Utils/Storage/Search/TagTerm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

namespace Files.App.Utils.Storage
{
public class TagTerm
{
public HashSet<string> TagUids { get; set; } = new();

public bool IsExclude { get; set; }
}
}
Loading