Skip to content

Code Quality: Migrate LayoutPreferences and FileTags keys to SHA256 hash #17389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
71 changes: 68 additions & 3 deletions src/Files.App/Helpers/Layout/LayoutPreferencesDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -139,7 +142,7 @@ private void IterateKeys(List<LayoutPreferencesDatabaseItem> 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();
Expand Down Expand Up @@ -174,5 +177,67 @@ private void IterateKeys(List<LayoutPreferencesDatabaseItem> 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');
}
}
}
82 changes: 75 additions & 7 deletions src/Files.App/Utils/FileTags/FileTagsDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 [])
{
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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)
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -143,14 +145,15 @@ 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);
}
}
}

public string[] GetTags(string? filePath, ulong? frn)
{
MigrateExistingKeys();
return FindTag(filePath, frn)?.Tags ?? [];
}

Expand Down Expand Up @@ -182,7 +185,7 @@ public IEnumerable<TaggedFile> GetAllUnderPath(string folderPath)
{
try
{
IterateKeys(list, CombineKeys(FileTagsKey, folderPath), 0);
IterateKeys(list, CombineKeys(FileTagsKey, ChecksumHelpers.CreateSHA256(folderPath)), 0);
}
catch (SecurityException)
{
Expand All @@ -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)
{
Expand Down Expand Up @@ -249,5 +252,70 @@ private void IterateKeys(List<TaggedFile> 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');
}
Comment on lines +316 to +319
Copy link
Preview

Copilot AI Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IsHexString method is duplicated in both FileTagsDatabase and LayoutPreferencesDatabase. Consider moving this utility method to a shared location like ChecksumHelpers to avoid code duplication.

Suggested change
private static bool IsHexString(string value)
{
return value.All(c => c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F');
}
// Removed IsHexString method. Use ChecksumHelpers.IsHexString instead.

Copilot uses AI. Check for mistakes.

}
}
8 changes: 8 additions & 0 deletions src/Files.Shared/Helpers/ChecksumHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte> hash = stackalloc byte[SHA256.HashSizeInBytes];
SHA256.HashData(buffer, hash);
return Convert.ToHexString(hash).ToLower();
}

public async static Task<string> CreateCRC32(Stream stream, CancellationToken cancellationToken)
{
var crc32 = new Crc32();
Expand Down
Loading