diff --git a/src/Files.App/Helpers/Layout/LayoutPreferencesDatabase.cs b/src/Files.App/Helpers/Layout/LayoutPreferencesDatabase.cs index e03b0a7e0db2..ec82d3a7d3cf 100644 --- a/src/Files.App/Helpers/Layout/LayoutPreferencesDatabase.cs +++ b/src/Files.App/Helpers/Layout/LayoutPreferencesDatabase.cs @@ -4,6 +4,7 @@ using Microsoft.Win32; using System.Runtime.CompilerServices; using Windows.ApplicationModel; +using Files.Shared.Helpers; using static Files.App.Helpers.LayoutPreferencesDatabaseItemRegistry; using static Files.App.Helpers.RegistryHelpers; using JsonSerializer = System.Text.Json.JsonSerializer; @@ -13,9 +14,11 @@ namespace Files.App.Helpers public sealed class LayoutPreferencesDatabase { private readonly static string LayoutSettingsKey = @$"Software\Files Community\{Package.Current.Id.Name}\v1\LayoutPreferences"; + private readonly static string MigrationMarkerKey = "MigrationCompleted"; public LayoutPreferencesItem? GetPreferences(string filePath, ulong? frn) { + MigrateExistingKeys(); return FindPreferences(filePath, frn)?.LayoutPreferencesManager; } @@ -58,7 +61,7 @@ void UpdateValues(LayoutPreferencesDatabaseItem? preferences) { if (filePath is not null) { - using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(LayoutSettingsKey, filePath)); + using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(LayoutSettingsKey, ChecksumHelpers.CreateSHA256(filePath))); SaveValues(filePathKey, preferences); } @@ -91,7 +94,7 @@ private static void ImportCore(LayoutPreferencesDatabaseItem[]? preferences) } foreach (var preference in preferences) { - using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(LayoutSettingsKey, preference.FilePath)); + using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(LayoutSettingsKey, ChecksumHelpers.CreateSHA256(preference.FilePath))); SaveValues(filePathKey, preference); if (preference.Frn is not null) { @@ -139,7 +142,7 @@ private void IterateKeys(List list, string path, { if (filePath is not null) { - using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(LayoutSettingsKey, filePath)); + using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(LayoutSettingsKey, ChecksumHelpers.CreateSHA256(filePath))); if (filePathKey.ValueCount > 0) { var preference = new LayoutPreferencesDatabaseItem(); @@ -174,5 +177,67 @@ private void IterateKeys(List list, string path, return null; } + + private void MigrateExistingKeys() + { + using var baseKey = Registry.CurrentUser.OpenSubKey(LayoutSettingsKey); + if (baseKey is null) + return; + + // Check if migration is already completed + if (baseKey.GetValue(MigrationMarkerKey) is not null) + return; + + var keysToMigrate = new List<(string oldKey, LayoutPreferencesDatabaseItem preference)>(); + + // Collect all keys that need migration (excluding FRN and migration marker) + foreach (var subKeyName in baseKey.GetSubKeyNames()) + { + if (subKeyName == "FRN" || subKeyName == MigrationMarkerKey) + continue; + + // Check if this is a hash key (64 characters hex) + if (subKeyName.Length == 64 && IsHexString(subKeyName)) + continue; // Already migrated + + using var subKey = baseKey.OpenSubKey(subKeyName); + if (subKey?.ValueCount > 0) + { + var preference = new LayoutPreferencesDatabaseItem(); + BindValues(subKey, preference); + keysToMigrate.Add((subKeyName, preference)); + } + } + + // Migrate collected keys + using var writerKey = Registry.CurrentUser.CreateSubKey(LayoutSettingsKey); + foreach (var (oldKey, preference) in keysToMigrate) + { + if (!string.IsNullOrEmpty(preference.FilePath)) + { + // Create new hashed key + using var newKey = Registry.CurrentUser.CreateSubKey(CombineKeys(LayoutSettingsKey, ChecksumHelpers.CreateSHA256(preference.FilePath))); + SaveValues(newKey, preference); + } + + // Delete old key + try + { + writerKey.DeleteSubKeyTree(oldKey); + } + catch + { + // Ignore deletion errors + } + } + + // Mark migration as completed + writerKey.SetValue(MigrationMarkerKey, "1", RegistryValueKind.String); + } + + private static bool IsHexString(string value) + { + return value.All(c => c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'); + } } } diff --git a/src/Files.App/Utils/FileTags/FileTagsDatabase.cs b/src/Files.App/Utils/FileTags/FileTagsDatabase.cs index e7d120850dd7..4094c19282b8 100644 --- a/src/Files.App/Utils/FileTags/FileTagsDatabase.cs +++ b/src/Files.App/Utils/FileTags/FileTagsDatabase.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using System.Security; using Windows.ApplicationModel; +using Files.Shared.Helpers; using static Files.App.Helpers.RegistryHelpers; using static Files.App.Utils.FileTags.TaggedFileRegistry; using JsonSerializer = System.Text.Json.JsonSerializer; @@ -15,13 +16,14 @@ public sealed class FileTagsDatabase { private static string? _FileTagsKey; private string? FileTagsKey => _FileTagsKey ??= SafetyExtensions.IgnoreExceptions(() => @$"Software\Files Community\{Package.Current.Id.Name}\v1\FileTags"); + private readonly static string MigrationMarkerKey = "MigrationCompleted"; public void SetTags(string filePath, ulong? frn, string[] tags) { if (FileTagsKey is null) return; - using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(FileTagsKey, filePath)); + using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(FileTagsKey, ChecksumHelpers.CreateSHA256(filePath))); if (tags is []) { @@ -57,7 +59,7 @@ public void SetTags(string filePath, ulong? frn, string[] tags) if (filePath is not null) { - using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(FileTagsKey, filePath)); + using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(FileTagsKey, ChecksumHelpers.CreateSHA256(filePath))); if (filePathKey.ValueCount > 0) { var tag = new TaggedFile(); @@ -99,7 +101,7 @@ public void UpdateTag(string oldFilePath, ulong? frn, string? newFilePath) return; var tag = FindTag(oldFilePath, null); - using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(FileTagsKey, oldFilePath)); + using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(FileTagsKey, ChecksumHelpers.CreateSHA256(oldFilePath))); SaveValues(filePathKey, null); if (tag is not null) @@ -115,7 +117,7 @@ public void UpdateTag(string oldFilePath, ulong? frn, string? newFilePath) if (newFilePath is not null) { - using var newFilePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(FileTagsKey, newFilePath)); + using var newFilePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(FileTagsKey, ChecksumHelpers.CreateSHA256(newFilePath))); SaveValues(newFilePathKey, tag); } } @@ -143,7 +145,7 @@ public void UpdateTag(ulong oldFrn, ulong? frn, string? newFilePath) if (newFilePath is not null) { - using var newFilePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(FileTagsKey, newFilePath)); + using var newFilePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(FileTagsKey, ChecksumHelpers.CreateSHA256(newFilePath))); SaveValues(newFilePathKey, tag); } } @@ -151,6 +153,7 @@ public void UpdateTag(ulong oldFrn, ulong? frn, string? newFilePath) public string[] GetTags(string? filePath, ulong? frn) { + MigrateExistingKeys(); return FindTag(filePath, frn)?.Tags ?? []; } @@ -182,7 +185,7 @@ public IEnumerable GetAllUnderPath(string folderPath) { try { - IterateKeys(list, CombineKeys(FileTagsKey, folderPath), 0); + IterateKeys(list, CombineKeys(FileTagsKey, ChecksumHelpers.CreateSHA256(folderPath)), 0); } catch (SecurityException) { @@ -207,7 +210,7 @@ public void Import(string json) } foreach (var tag in tags) { - using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(FileTagsKey, tag.FilePath)); + using var filePathKey = Registry.CurrentUser.CreateSubKey(CombineKeys(FileTagsKey, ChecksumHelpers.CreateSHA256(tag.FilePath))); SaveValues(filePathKey, tag); if (tag.Frn is not null) { @@ -249,5 +252,70 @@ private void IterateKeys(List list, string path, int depth) IterateKeys(list, CombineKeys(path, subKey), depth + 1); } } + + private void MigrateExistingKeys() + { + if (FileTagsKey is null) + return; + + using var baseKey = Registry.CurrentUser.OpenSubKey(FileTagsKey); + if (baseKey is null) + return; + + // Check if migration is already completed + if (baseKey.GetValue(MigrationMarkerKey) is not null) + return; + + var keysToMigrate = new List<(string oldKey, TaggedFile tag)>(); + + // Collect all keys that need migration (excluding FRN and migration marker) + foreach (var subKeyName in baseKey.GetSubKeyNames()) + { + if (subKeyName == "FRN" || subKeyName == MigrationMarkerKey) + continue; + + // Check if this is a hash key (64 characters hex) + if (subKeyName.Length == 64 && IsHexString(subKeyName)) + continue; // Already migrated + + using var subKey = baseKey.OpenSubKey(subKeyName); + if (subKey?.ValueCount > 0) + { + var tag = new TaggedFile(); + BindValues(subKey, tag); + keysToMigrate.Add((subKeyName, tag)); + } + } + + // Migrate collected keys + using var writerKey = Registry.CurrentUser.CreateSubKey(FileTagsKey); + foreach (var (oldKey, tag) in keysToMigrate) + { + if (!string.IsNullOrEmpty(tag.FilePath)) + { + // Create new hashed key + using var newKey = Registry.CurrentUser.CreateSubKey(CombineKeys(FileTagsKey, ChecksumHelpers.CreateSHA256(tag.FilePath))); + SaveValues(newKey, tag); + } + + // Delete old key + try + { + writerKey.DeleteSubKeyTree(oldKey); + } + catch + { + // Ignore deletion errors + } + } + + // Mark migration as completed + writerKey.SetValue(MigrationMarkerKey, "1", RegistryValueKind.String); + } + + private static bool IsHexString(string value) + { + return value.All(c => c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'); + } } } \ No newline at end of file diff --git a/src/Files.Shared/Helpers/ChecksumHelpers.cs b/src/Files.Shared/Helpers/ChecksumHelpers.cs index 4e2b061ca7c0..848900e58b1e 100644 --- a/src/Files.Shared/Helpers/ChecksumHelpers.cs +++ b/src/Files.Shared/Helpers/ChecksumHelpers.cs @@ -22,6 +22,14 @@ public static string CalculateChecksumForPath(string path) return Convert.ToHexString(hash); } + public static string CreateSHA256(string input) + { + var buffer = Encoding.UTF8.GetBytes(input); + Span hash = stackalloc byte[SHA256.HashSizeInBytes]; + SHA256.HashData(buffer, hash); + return Convert.ToHexString(hash).ToLower(); + } + public async static Task CreateCRC32(Stream stream, CancellationToken cancellationToken) { var crc32 = new Crc32();