Persist file categories on the index and classify archives and unknown types during idle maintenance.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-28 11:37:29 +02:00
parent b33a78dbbe
commit 222b5d9969
42 changed files with 1719 additions and 80 deletions

View File

@@ -484,6 +484,108 @@ public class FileClassifierTests
=> FileClassifier.Classify(name, @"C:\Downloads\" + name, isDirectory, isDirectory ? AttributeFlags.Directory : 0, isRepoRoot).Category;
}
public class EntryCategoryAssignerTests
{
[Fact]
public void ApplyAutomatic_sets_extension_category_on_files()
{
var entry = new IndexEntry
{
Name = "clip.mkv",
NameNorm = "clip.mkv",
PathRel = "clip.mkv",
Extension = "mkv"
};
EntryCategoryAssigner.ApplyAutomatic(entry);
Assert.Equal(FileCategory.Video, entry.Category);
Assert.Equal(CategorySources.Extension, entry.CategorySource);
Assert.True(entry.CategoryConfidence >= 80);
}
[Fact]
public void User_category_is_not_overwritten()
{
var entry = new IndexEntry
{
Name = "clip.mkv",
NameNorm = "clip.mkv",
PathRel = "clip.mkv",
Extension = "mkv"
};
EntryCategoryAssigner.ApplyUser(entry, FileCategory.Documents, "Manual");
EntryCategoryAssigner.ApplyAutomatic(entry);
Assert.Equal(FileCategory.Documents, entry.Category);
Assert.Equal(CategorySources.User, entry.CategorySource);
}
[Fact]
public void Children_majority_sets_folder_or_archive_source()
{
var folder = new IndexEntry
{
Name = "Vacation",
NameNorm = "vacation",
PathRel = "Vacation",
IsDirectory = true,
Attributes = AttributeFlags.Directory
};
EntryCategoryAssigner.ApplyAutomatic(
folder,
childCategories: [FileCategory.Photos, FileCategory.Photos, FileCategory.Photos, FileCategory.Documents]);
Assert.Equal(FileCategory.Photos, folder.Category);
Assert.Equal(CategorySources.Children, folder.CategorySource);
var zip = new IndexEntry
{
Name = "photos.zip",
NameNorm = "photos.zip",
PathRel = "photos.zip",
Extension = "zip"
};
EntryCategoryAssigner.ApplyAutomatic(
zip,
childCategories: [FileCategory.Photos, FileCategory.Photos, FileCategory.Photos]);
Assert.Equal(FileCategory.Photos, zip.Category);
Assert.Equal(CategorySources.ArchiveContents, zip.CategorySource);
}
}
public class OrganizeDestinationsTests
{
[Fact]
public void Parses_category_aliases()
{
Assert.True(OrganizeDestinations.TryParse("photos", out var photos));
Assert.Equal(FileCategory.Photos, photos);
Assert.True(OrganizeDestinations.TryParse("zip", out var zip));
Assert.Equal(FileCategory.Archive, zip);
Assert.True(OrganizeDestinations.TryParse("unknown", out var unknown));
Assert.Equal(FileCategory.Unknown, unknown);
Assert.False(OrganizeDestinations.TryParse("not-a-category", out _));
}
}
public class FileSignatureClassifierTests
{
[Fact]
public void Recognizes_common_headers()
{
Assert.Equal(FileCategory.Photos, Hit([0xFF, 0xD8, 0xFF, 0xE0]));
Assert.Equal(FileCategory.Photos, Hit([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]));
Assert.Equal(FileCategory.Documents, Hit([0x25, 0x50, 0x44, 0x46]));
Assert.Equal(FileCategory.Archive, Hit([0x50, 0x4B, 0x03, 0x04]));
Assert.Equal(FileCategory.Archive, Hit([0x37, 0x7A, 0xBC, 0xAF]));
Assert.Equal(FileCategory.Installer, Hit([0x4D, 0x5A, 0x90, 0x00]));
Assert.Equal(FileCategory.Audio, Hit("RIFF....WAVE"u8.ToArray()));
Assert.Equal(FileCategory.Video, Hit("RIFF....AVI "u8.ToArray()));
Assert.Null(FileSignatureClassifier.TryClassify([0x00, 0x01, 0x02, 0x03]));
Assert.Null(FileSignatureClassifier.TryClassify([0xFF, 0xD8]));
}
private static FileCategory Hit(byte[] header)
=> FileSignatureClassifier.TryClassify(header)!.Category;
}
public class GitChangeTests
{
[Fact]