Skip to content

Commit a93ba6d

Browse files
authored
Start allowing multiple file-tags (#9442)
1 parent 98da915 commit a93ba6d

24 files changed

+259
-227
lines changed

src/Files.Backend/Services/Settings/IFileTagsSettingsService.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ public interface IFileTagsSettingsService : IBaseSettingsService
1212

1313
FileTagViewModel GetTagById(string uid);
1414

15+
IList<FileTagViewModel> GetTagsByIds(string[] uids);
16+
1517
IEnumerable<FileTagViewModel> GetTagsByName(string tagName);
1618

1719
IEnumerable<FileTagViewModel> SearchTagsByName(string tagName);
@@ -20,4 +22,4 @@ public interface IFileTagsSettingsService : IBaseSettingsService
2022

2123
bool ImportSettings(object import);
2224
}
23-
}
25+
}

src/Files.FullTrust/MessageHandlers/FileOperationsHandler.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -868,16 +868,16 @@ private void UpdateFileTagsDb(ShellFileOperations.ShellFileOpEventArgs e, string
868868
};
869869
if (destination == null)
870870
{
871-
dbInstance.SetTag(sourcePath, null, null); // remove tag from deleted files
871+
dbInstance.SetTags(sourcePath, null, null); // remove tag from deleted files
872872
}
873873
else
874874
{
875875
SafetyExtensions.IgnoreExceptions(() =>
876876
{
877877
if (operationType == "copy")
878878
{
879-
var tag = dbInstance.GetTag(sourcePath);
880-
dbInstance.SetTag(destination, FileTagsHandler.GetFileFRN(destination), tag); // copy tag to new files
879+
var tag = dbInstance.GetTags(sourcePath);
880+
dbInstance.SetTags(destination, FileTagsHandler.GetFileFRN(destination), tag); // copy tag to new files
881881
using var si = new ShellItem(destination);
882882
if (si.IsFolder) // File tag is not copied automatically for folders
883883
{
@@ -895,7 +895,7 @@ private void UpdateFileTagsDb(ShellFileOperations.ShellFileOpEventArgs e, string
895895
var tags = dbInstance.GetAllUnderPath(sourcePath).ToList();
896896
if (destination == null) // remove tag for items contained in the folder
897897
{
898-
tags.ForEach(t => dbInstance.SetTag(t.FilePath, null, null));
898+
tags.ForEach(t => dbInstance.SetTags(t.FilePath, null, null));
899899
}
900900
else
901901
{
@@ -906,7 +906,7 @@ private void UpdateFileTagsDb(ShellFileOperations.ShellFileOpEventArgs e, string
906906
SafetyExtensions.IgnoreExceptions(() =>
907907
{
908908
var subPath = t.FilePath.Replace(sourcePath, destination, StringComparison.Ordinal);
909-
dbInstance.SetTag(subPath, FileTagsHandler.GetFileFRN(subPath), t.Tag);
909+
dbInstance.SetTags(subPath, FileTagsHandler.GetFileFRN(subPath), t.Tags);
910910
}, Program.Logger);
911911
});
912912
}

src/Files.FullTrust/MessageHandlers/FileTagsHandler.cs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Collections.Generic;
44
using System.IO;
55
using System.IO.Pipes;
6+
using System.Linq;
67
using System.Runtime.InteropServices.ComTypes;
78
using System.Runtime.Versioning;
89
using System.Threading.Tasks;
@@ -14,7 +15,7 @@ namespace Files.FullTrust.MessageHandlers
1415
[SupportedOSPlatform("Windows10.0.10240")]
1516
public class FileTagsHandler : Disposable, IMessageHandler
1617
{
17-
public static string ReadFileTag(string filePath)
18+
public static string[] ReadFileTag(string filePath)
1819
{
1920
using var hStream = Kernel32.CreateFile($"{filePath}:files",
2021
Kernel32.FileAccess.GENERIC_READ, 0, null, FileMode.Open, FileFlagsAndAttributes.FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);
@@ -28,14 +29,15 @@ public static string ReadFileTag(string filePath)
2829
{
2930
return null;
3031
}
31-
return System.Text.Encoding.UTF8.GetString(bytes, 0, (int)read);
32+
var tagString = System.Text.Encoding.UTF8.GetString(bytes, 0, (int)read);
33+
return tagString.Split(',');
3234
}
3335

34-
public static bool WriteFileTag(string filePath, string tag)
36+
public static bool WriteFileTag(string filePath, string[] tag)
3537
{
3638
var dateOk = GetFileDateModified(filePath, out var dateModified); // Backup date modified
3739
bool result = false;
38-
if (tag == null)
40+
if (tag is null || !tag.Any())
3941
{
4042
result = Kernel32.DeleteFile($"{filePath}:files");
4143
}
@@ -47,7 +49,7 @@ public static bool WriteFileTag(string filePath, string tag)
4749
{
4850
return false;
4951
}
50-
byte[] buff = System.Text.Encoding.UTF8.GetBytes(tag);
52+
byte[] buff = System.Text.Encoding.UTF8.GetBytes(string.Join(',', tag));
5153
result = Kernel32.WriteFile(hStream, buff, (uint)buff.Length, out var written, IntPtr.Zero);
5254
}
5355
if (dateOk)
@@ -86,34 +88,34 @@ public void UpdateTagsDb()
8688
{
8789
// Frn is valid, update file path
8890
var tag = ReadFileTag(pathFromFrn.Replace(@"\\?\", "", StringComparison.Ordinal));
89-
if (tag != null)
91+
if (tag is not null && tag.Any())
9092
{
9193
dbInstance.UpdateTag(file.Frn ?? 0, null, pathFromFrn.Replace(@"\\?\", "", StringComparison.Ordinal));
92-
dbInstance.SetTag(pathFromFrn.Replace(@"\\?\", "", StringComparison.Ordinal), file.Frn, tag);
94+
dbInstance.SetTags(pathFromFrn.Replace(@"\\?\", "", StringComparison.Ordinal), file.Frn, tag);
9395
}
9496
else
9597
{
96-
dbInstance.SetTag(null, file.Frn, null);
98+
dbInstance.SetTags(null, file.Frn, null);
9799
}
98100
}
99101
else
100102
{
101103
var tag = ReadFileTag(file.FilePath);
102-
if (tag != null)
104+
if (tag is not null && tag.Any())
103105
{
104106
if (!SafetyExtensions.IgnoreExceptions(() =>
105107
{
106108
var frn = GetFileFRN(file.FilePath);
107109
dbInstance.UpdateTag(file.FilePath, frn, null);
108-
dbInstance.SetTag(file.FilePath, (ulong?)frn, tag);
110+
dbInstance.SetTags(file.FilePath, (ulong?)frn, tag);
109111
}, Program.Logger))
110112
{
111-
dbInstance.SetTag(file.FilePath, null, null);
113+
dbInstance.SetTags(file.FilePath, null, null);
112114
}
113115
}
114116
else
115117
{
116-
dbInstance.SetTag(file.FilePath, null, null);
118+
dbInstance.SetTags(file.FilePath, null, null);
117119
}
118120
}
119121
}

src/Files.Shared/FileTagsDb.cs

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,43 @@ public class FileTagsDb : IDisposable
99
{
1010
private readonly LiteDatabase db;
1111

12+
private const string TaggedFiles = "taggedfiles";
13+
1214
public FileTagsDb(string connection, bool shared = false)
1315
{
1416
db = new LiteDatabase(new ConnectionString(connection)
1517
{
1618
Mode = shared ? FileMode.Shared : FileMode.Exclusive
1719
});
20+
UpdateDb();
1821
}
1922

20-
public void SetTag(string filePath, ulong? frn, string tag)
23+
public void SetTags(string filePath, ulong? frn, string[]? tags)
2124
{
2225
// Get a collection (or create, if doesn't exist)
23-
var col = db.GetCollection<TaggedFile>("taggedfiles");
26+
var col = db.GetCollection<TaggedFile>(TaggedFiles);
2427

2528
var tmp = _FindTag(filePath, frn);
2629
if (tmp == null)
2730
{
28-
if (tag != null)
31+
if (tags != null && tags.Any())
2932
{
3033
// Insert new tagged file (Id will be auto-incremented)
3134
var newTag = new TaggedFile
3235
{
3336
FilePath = filePath,
3437
Frn = frn,
35-
Tag = tag
38+
Tags = tags
3639
};
3740
col.Insert(newTag);
3841
}
3942
}
4043
else
4144
{
42-
if (tag != null)
45+
if (tags != null && tags.Any())
4346
{
4447
// Update file tag
45-
tmp.Tag = tag;
48+
tmp.Tags = tags;
4649
col.Update(tmp);
4750
}
4851
else
@@ -53,10 +56,10 @@ public void SetTag(string filePath, ulong? frn, string tag)
5356
}
5457
}
5558

56-
private TaggedFile _FindTag(string filePath = null, ulong? frn = null)
59+
private TaggedFile? _FindTag(string? filePath = null, ulong? frn = null)
5760
{
5861
// Get a collection (or create, if doesn't exist)
59-
var col = db.GetCollection<TaggedFile>("taggedfiles");
62+
var col = db.GetCollection<TaggedFile>(TaggedFiles);
6063
if (filePath != null)
6164
{
6265
var tmp = col.FindOne(x => x.FilePath == filePath);
@@ -68,9 +71,11 @@ private TaggedFile _FindTag(string filePath = null, ulong? frn = null)
6871
tmp.Frn = frn;
6972
col.Update(tmp);
7073
}
74+
7175
return tmp;
7276
}
7377
}
78+
7479
if (frn != null)
7580
{
7681
var tmp = col.FindOne(x => x.Frn == frn);
@@ -82,16 +87,18 @@ private TaggedFile _FindTag(string filePath = null, ulong? frn = null)
8287
tmp.FilePath = filePath;
8388
col.Update(tmp);
8489
}
90+
8591
return tmp;
8692
}
8793
}
94+
8895
return null;
8996
}
9097

91-
public void UpdateTag(string oldFilePath, ulong? frn = null, string newFilePath = null)
98+
public void UpdateTag(string oldFilePath, ulong? frn = null, string? newFilePath = null)
9299
{
93100
// Get a collection (or create, if doesn't exist)
94-
var col = db.GetCollection<TaggedFile>("taggedfiles");
101+
var col = db.GetCollection<TaggedFile>(TaggedFiles);
95102
var tmp = col.FindOne(x => x.FilePath == oldFilePath);
96103
if (tmp != null)
97104
{
@@ -100,6 +107,7 @@ public void UpdateTag(string oldFilePath, ulong? frn = null, string newFilePath
100107
tmp.Frn = frn;
101108
col.Update(tmp);
102109
}
110+
103111
if (newFilePath != null)
104112
{
105113
tmp.FilePath = newFilePath;
@@ -108,10 +116,10 @@ public void UpdateTag(string oldFilePath, ulong? frn = null, string newFilePath
108116
}
109117
}
110118

111-
public void UpdateTag(ulong oldFrn, ulong? frn = null, string newFilePath = null)
119+
public void UpdateTag(ulong oldFrn, ulong? frn = null, string? newFilePath = null)
112120
{
113121
// Get a collection (or create, if doesn't exist)
114-
var col = db.GetCollection<TaggedFile>("taggedfiles");
122+
var col = db.GetCollection<TaggedFile>(TaggedFiles);
115123
var tmp = col.FindOne(x => x.Frn == oldFrn);
116124
if (tmp != null)
117125
{
@@ -120,6 +128,7 @@ public void UpdateTag(ulong oldFrn, ulong? frn = null, string newFilePath = null
120128
tmp.Frn = frn;
121129
col.Update(tmp);
122130
}
131+
123132
if (newFilePath != null)
124133
{
125134
tmp.FilePath = newFilePath;
@@ -128,20 +137,20 @@ public void UpdateTag(ulong oldFrn, ulong? frn = null, string newFilePath = null
128137
}
129138
}
130139

131-
public string GetTag(string filePath = null, ulong? frn = null)
140+
public string[]? GetTags(string? filePath = null, ulong? frn = null)
132141
{
133-
return _FindTag(filePath, frn)?.Tag;
142+
return _FindTag(filePath, frn)?.Tags;
134143
}
135144

136145
public IEnumerable<TaggedFile> GetAll()
137146
{
138-
var col = db.GetCollection<TaggedFile>("taggedfiles");
147+
var col = db.GetCollection<TaggedFile>(TaggedFiles);
139148
return col.FindAll();
140149
}
141150

142151
public IEnumerable<TaggedFile> GetAllUnderPath(string folderPath)
143152
{
144-
var col = db.GetCollection<TaggedFile>("taggedfiles");
153+
var col = db.GetCollection<TaggedFile>(TaggedFiles);
145154
return col.Find(x => x.FilePath.StartsWith(folderPath, StringComparison.OrdinalIgnoreCase));
146155
}
147156

@@ -153,22 +162,35 @@ public void Dispose()
153162
public void Import(string json)
154163
{
155164
var dataValues = JsonSerializer.DeserializeArray(json);
156-
db.Engine.Delete("taggedfiles", Query.All());
157-
db.Engine.InsertBulk("taggedfiles", dataValues.Select(x => x.AsDocument));
165+
db.Engine.Delete(TaggedFiles, Query.All());
166+
db.Engine.InsertBulk(TaggedFiles, dataValues.Select(x => x.AsDocument));
158167
}
159168

160169
public string Export()
161170
{
162-
return JsonSerializer.Serialize(new BsonArray(db.Engine.FindAll("taggedfiles")));
171+
return JsonSerializer.Serialize(new BsonArray(db.Engine.FindAll(TaggedFiles)));
172+
}
173+
174+
private void UpdateDb()
175+
{
176+
if (db.Engine.UserVersion == 0)
177+
{
178+
foreach (var doc in db.Engine.FindAll(TaggedFiles))
179+
{
180+
doc["Tags"] = new BsonValue(new[] { doc["Tag"].AsString });
181+
doc.Remove("Tags");
182+
db.Engine.Update(TaggedFiles, doc);
183+
}
184+
db.Engine.UserVersion = 1;
185+
}
163186
}
164187

165188
public class TaggedFile
166189
{
167-
[BsonId]
168-
public int Id { get; set; }
190+
[BsonId] public int Id { get; set; }
169191
public ulong? Frn { get; set; }
170-
public string FilePath { get; set; }
171-
public string Tag { get; set; }
192+
public string FilePath { get; set; } = null!;
193+
public string[] Tags { get; set; } = null!;
172194
}
173195
}
174-
}
196+
}

src/Files.Uwp/App.xaml.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -528,8 +528,9 @@ async Task PerformNavigation(string payload, string selectItem = null)
528528
.OnSuccess(item => FileTagsHelper.GetFileFRN(item));
529529
if (fileFRN is not null)
530530
{
531-
FileTagsHelper.DbInstance.SetTag(file, fileFRN, tag?.Uid);
532-
FileTagsHelper.WriteFileTag(file, tag?.Uid);
531+
var tagUid = tag is not null ? new[] { tag.Uid } : null;
532+
FileTagsHelper.DbInstance.SetTags(file, fileFRN, tagUid);
533+
FileTagsHelper.WriteFileTag(file, tagUid);
533534
}
534535
}
535536
break;

src/Files.Uwp/BaseLayout.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
using Windows.UI.Xaml.Data;
3636
using Windows.UI.Xaml.Input;
3737
using Windows.UI.Xaml.Navigation;
38+
using Files.Uwp.UserControls.Menus;
3839
using static Files.Uwp.Helpers.PathNormalization;
3940

4041
namespace Files.Uwp
@@ -664,7 +665,7 @@ private async Task LoadMenuItemsAsync()
664665

665666
if (UserSettingsService.PreferencesSettingsService.AreFileTagsEnabled && InstanceViewModel.CanTagFilesInPage)
666667
{
667-
AddFileTagsItemToMenu(ItemContextMenuFlyout);
668+
AddNewFileTagsToMenu(ItemContextMenuFlyout);
668669
}
669670

670671
if (!InstanceViewModel.IsPageTypeZipFolder)
@@ -678,20 +679,19 @@ private async Task LoadMenuItemsAsync()
678679
}
679680
}
680681

681-
private void AddFileTagsItemToMenu(Microsoft.UI.Xaml.Controls.CommandBarFlyout contextMenu)
682+
private void AddNewFileTagsToMenu(Microsoft.UI.Xaml.Controls.CommandBarFlyout contextMenu)
682683
{
683-
var fileTagMenuFlyout = new MenuFlyoutItemFileTag()
684+
var fileTagsContextMenu = new FileTagsContextMenu()
684685
{
685-
ItemsSource = FileTagsSettingsService.FileTagList,
686-
SelectedItems = SelectedItems
686+
SelectedListedItems = SelectedItems
687687
};
688688
var overflowSeparator = contextMenu.SecondaryCommands.FirstOrDefault(x => x is FrameworkElement fe && fe.Tag as string == "OverflowSeparator") as AppBarSeparator;
689689
var index = contextMenu.SecondaryCommands.IndexOf(overflowSeparator);
690690
index = index >= 0 ? index : contextMenu.SecondaryCommands.Count;
691691
contextMenu.SecondaryCommands.Insert(index, new AppBarSeparator());
692692
contextMenu.SecondaryCommands.Insert(index + 1, new AppBarElementContainer()
693693
{
694-
Content = fileTagMenuFlyout
694+
Content = fileTagsContextMenu
695695
});
696696
}
697697

0 commit comments

Comments
 (0)