|
3 | 3 |
|
4 | 4 | using Microsoft.Extensions.Logging; |
5 | 5 | using System.IO; |
| 6 | +using System.Text.RegularExpressions; |
6 | 7 | using Windows.Storage; |
7 | 8 | using Windows.Storage.FileProperties; |
8 | 9 | using Windows.Storage.Search; |
@@ -94,7 +95,7 @@ public Task SearchAsync(IList<ListedItem> results, CancellationToken token) |
94 | 95 |
|
95 | 96 | private async Task AddItemsForHomeAsync(IList<ListedItem> results, CancellationToken token) |
96 | 97 | { |
97 | | - if (AQSQuery.StartsWith("tag:", StringComparison.Ordinal)) |
| 98 | + if (IsTagQuery(AQSQuery)) |
98 | 99 | { |
99 | 100 | await SearchTagsAsync("", results, token); // Search tags everywhere, not only local drives |
100 | 101 | } |
@@ -183,19 +184,99 @@ private async Task AddItemsForLibraryAsync(LibraryLocationItem library, IList<Li |
183 | 184 | } |
184 | 185 | } |
185 | 186 |
|
| 187 | + private bool IsTagQuery(string query) |
| 188 | + { |
| 189 | + return query?.Contains("tag:", StringComparison.OrdinalIgnoreCase) == true; |
| 190 | + } |
| 191 | + |
| 192 | + private TagQueryExpression ParseTagQuery(string query) |
| 193 | + { |
| 194 | + var expression = new TagQueryExpression(); |
| 195 | + var orParts = Regex.Split(query, @"\s+OR\s+", RegexOptions.IgnoreCase); |
| 196 | + |
| 197 | + foreach (var orPart in orParts) |
| 198 | + { |
| 199 | + var andGroup = new List<TagTerm>(); |
| 200 | + var andParts = Regex.Split(orPart, @"\s+AND\s+", RegexOptions.IgnoreCase); |
| 201 | + |
| 202 | + foreach (var andPart in andParts) |
| 203 | + { |
| 204 | + var matches = Regex.Matches(andPart.Trim(), @"(NOT\s+)?tag:([^\s]+)", RegexOptions.IgnoreCase); |
| 205 | + foreach (Match match in matches) |
| 206 | + { |
| 207 | + var isExclude = !string.IsNullOrEmpty(match.Groups[1].Value); |
| 208 | + var tagValues = match.Groups[2].Value.Split(',', StringSplitOptions.RemoveEmptyEntries); |
| 209 | + var tagUids = new HashSet<string>(); |
| 210 | + |
| 211 | + foreach (var tagName in tagValues) |
| 212 | + { |
| 213 | + var uids = fileTagsSettingsService.GetTagsByName(tagName).Select(t => t.Uid); |
| 214 | + foreach (var uid in uids) |
| 215 | + { |
| 216 | + tagUids.Add(uid); |
| 217 | + } |
| 218 | + } |
| 219 | + |
| 220 | + andGroup.Add(new TagTerm { TagUids = tagUids, IsExclude = isExclude }); |
| 221 | + } |
| 222 | + } |
| 223 | + |
| 224 | + if (andGroup.Count > 0) |
| 225 | + { |
| 226 | + expression.OrGroups.Add(andGroup); |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + return expression; |
| 231 | + } |
| 232 | + |
| 233 | + private bool MatchesTagExpression(IEnumerable<string> fileTags, TagQueryExpression expression) |
| 234 | + { |
| 235 | + foreach (var orGroup in expression.OrGroups) |
| 236 | + { |
| 237 | + bool groupMatches = true; |
| 238 | + foreach (var term in orGroup) |
| 239 | + { |
| 240 | + if (term.IsExclude) |
| 241 | + { |
| 242 | + if (term.TagUids.Count > 0 && term.TagUids.Any(fileTags.Contains)) |
| 243 | + { |
| 244 | + groupMatches = false; |
| 245 | + break; |
| 246 | + } |
| 247 | + } |
| 248 | + else |
| 249 | + { |
| 250 | + if (term.TagUids.Count == 0 || !term.TagUids.Any(fileTags.Contains)) |
| 251 | + { |
| 252 | + groupMatches = false; |
| 253 | + break; |
| 254 | + } |
| 255 | + } |
| 256 | + } |
| 257 | + |
| 258 | + if (groupMatches) |
| 259 | + { |
| 260 | + return true; |
| 261 | + } |
| 262 | + } |
| 263 | + |
| 264 | + return false; |
| 265 | + } |
| 266 | + |
186 | 267 | private async Task SearchTagsAsync(string folder, IList<ListedItem> results, CancellationToken token) |
187 | 268 | { |
188 | 269 | //var sampler = new IntervalSampler(500); |
189 | | - var tags = AQSQuery.Substring("tag:".Length)?.Split(',').Where(t => !string.IsNullOrWhiteSpace(t)) |
190 | | - .SelectMany(t => fileTagsSettingsService.GetTagsByName(t), (_, t) => t.Uid).ToHashSet(); |
191 | | - if (tags?.Any() != true) |
| 270 | + var expression = ParseTagQuery(AQSQuery); |
| 271 | + |
| 272 | + if (expression.OrGroups.Count == 0) |
192 | 273 | { |
193 | 274 | return; |
194 | 275 | } |
195 | 276 |
|
196 | 277 | var dbInstance = FileTagsHelper.GetDbInstance(); |
197 | 278 | var matches = dbInstance.GetAllUnderPath(folder) |
198 | | - .Where(x => tags.All(x.Tags.Contains)); |
| 279 | + .Where(x => MatchesTagExpression(x.Tags, expression)); |
199 | 280 | if (string.IsNullOrEmpty(folder)) |
200 | 281 | matches = matches.Where(x => !StorageTrashBinService.IsUnderTrashBin(x.FilePath)); |
201 | 282 |
|
@@ -258,7 +339,7 @@ private async Task SearchTagsAsync(string folder, IList<ListedItem> results, Can |
258 | 339 |
|
259 | 340 | private async Task AddItemsAsync(string folder, IList<ListedItem> results, CancellationToken token) |
260 | 341 | { |
261 | | - if (AQSQuery.StartsWith("tag:", StringComparison.Ordinal)) |
| 342 | + if (IsTagQuery(AQSQuery)) |
262 | 343 | { |
263 | 344 | await SearchTagsAsync(folder, results, token); |
264 | 345 | } |
|
0 commit comments