161 lines
6.0 KiB
C#
161 lines
6.0 KiB
C#
using Explorer.Application;
|
|
using Explorer.Domain;
|
|
using Explorer.Domain.Abstractions;
|
|
using Explorer.Storage.Sqlite;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
|
|
namespace Explorer.Application.Tests;
|
|
|
|
public class EntryClassificationServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task Archive_children_override_zip_as_photos()
|
|
{
|
|
await using var store = await OpenStore();
|
|
var source = await AddSource(store, @"C:\media");
|
|
var zip = await Upsert(store, new IndexEntry
|
|
{
|
|
SourceId = source.Id,
|
|
Name = "photos.zip",
|
|
NameNorm = "photos.zip",
|
|
Extension = "zip",
|
|
PathRel = "photos.zip",
|
|
LastSeenUtc = DateTimeOffset.UtcNow
|
|
});
|
|
await Upsert(store, Child(source.Id, zip.Id, "a.jpg", "jpg"));
|
|
await Upsert(store, Child(source.Id, zip.Id, "b.jpg", "jpg"));
|
|
await Upsert(store, Child(source.Id, zip.Id, "c.jpg", "jpg"));
|
|
|
|
var service = Create(store, new MemoryEnumerator());
|
|
service.Resume();
|
|
Assert.True(await service.ProcessPendingAsync());
|
|
var loaded = await store.Entries.GetAsync(zip.Id);
|
|
Assert.Equal(FileCategory.Photos, loaded!.Category);
|
|
Assert.Equal(CategorySources.ArchiveContents, loaded.CategorySource);
|
|
Assert.False(await service.ProcessPendingAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Signature_classifies_jpeg_without_extension()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "ew-classify", Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(root);
|
|
var file = Path.Combine(root, "pic.bin");
|
|
File.WriteAllBytes(file, [0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01]);
|
|
await using var store = await OpenStore();
|
|
var source = await AddSource(store, root);
|
|
var entry = await Upsert(store, new IndexEntry
|
|
{
|
|
SourceId = source.Id,
|
|
Name = "pic.bin",
|
|
NameNorm = "pic.bin",
|
|
Extension = "bin",
|
|
PathRel = "pic.bin",
|
|
LastSeenUtc = DateTimeOffset.UtcNow
|
|
});
|
|
Assert.Equal(FileCategory.Unknown, entry.Category);
|
|
|
|
var enumerator = new MemoryEnumerator();
|
|
enumerator.Add(new FileSystemItem { FullPath = file, Name = "pic.bin" });
|
|
var service = Create(store, enumerator);
|
|
service.Resume();
|
|
Assert.True(await service.ProcessPendingAsync());
|
|
var loaded = await store.Entries.GetAsync(entry.Id);
|
|
Assert.Equal(FileCategory.Photos, loaded!.Category);
|
|
Assert.Equal(CategorySources.Mime, loaded.CategorySource);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Pause_skips_work()
|
|
{
|
|
await using var store = await OpenStore();
|
|
var source = await AddSource(store, @"C:\media");
|
|
var zip = await Upsert(store, new IndexEntry
|
|
{
|
|
SourceId = source.Id,
|
|
Name = "photos.zip",
|
|
NameNorm = "photos.zip",
|
|
Extension = "zip",
|
|
PathRel = "photos.zip",
|
|
LastSeenUtc = DateTimeOffset.UtcNow
|
|
});
|
|
await Upsert(store, Child(source.Id, zip.Id, "a.jpg", "jpg"));
|
|
var service = Create(store, new MemoryEnumerator());
|
|
Assert.True(service.IsPaused);
|
|
Assert.False(await service.ProcessPendingAsync());
|
|
var loaded = await store.Entries.GetAsync(zip.Id);
|
|
Assert.Equal(FileCategory.Archive, loaded!.Category);
|
|
}
|
|
|
|
private static EntryClassificationService Create(IIndexStore store, IFileSystemEnumerator enumerator)
|
|
=> new(
|
|
store,
|
|
enumerator,
|
|
new NeverHydrate(),
|
|
NullLogger<EntryClassificationService>.Instance);
|
|
|
|
private static async Task<SqliteIndexStore> OpenStore()
|
|
{
|
|
var db = Path.Combine(Path.GetTempPath(), "ew-classify", Guid.NewGuid().ToString("N"), "index.db");
|
|
var store = new SqliteIndexStore(db, NullLogger<SqliteIndexStore>.Instance);
|
|
await store.OpenAsync();
|
|
return store;
|
|
}
|
|
|
|
private static async Task<Source> AddSource(IIndexStore store, string root)
|
|
{
|
|
var source = new Source
|
|
{
|
|
StableKey = Guid.NewGuid().ToString("N"),
|
|
Kind = SourceKind.NtfsLocal,
|
|
DisplayName = "Test",
|
|
LastRootPath = root,
|
|
Status = SourceStatus.Online
|
|
};
|
|
source.Id = await store.Sources.UpsertAsync(source);
|
|
return source;
|
|
}
|
|
|
|
private static async Task<IndexEntry> Upsert(IIndexStore store, IndexEntry entry)
|
|
{
|
|
entry.Id = await store.Entries.UpsertAsync(entry);
|
|
return (await store.Entries.GetAsync(entry.Id))!;
|
|
}
|
|
|
|
private static IndexEntry Child(long sourceId, long parentId, string name, string ext)
|
|
=> new()
|
|
{
|
|
SourceId = sourceId,
|
|
ParentId = parentId,
|
|
Name = name,
|
|
NameNorm = name,
|
|
Extension = ext,
|
|
PathRel = name,
|
|
LastSeenUtc = DateTimeOffset.UtcNow
|
|
};
|
|
|
|
private sealed class NeverHydrate : IHydrationGuard
|
|
{
|
|
public bool WouldHydrateOnRead(FileSystemItem item) => false;
|
|
public bool WouldHydrateOnRead(int attributes, CloudAvailability? availability) => false;
|
|
public Task<bool> WouldHydrateOnReadAsync(string path, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(false);
|
|
}
|
|
|
|
private sealed class MemoryEnumerator : IFileSystemEnumerator
|
|
{
|
|
private readonly Dictionary<string, FileSystemItem> _items = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
public void Add(FileSystemItem item) => _items[item.FullPath] = item;
|
|
|
|
public IEnumerable<FileSystemItem> EnumerateChildren(string directoryPath) => [];
|
|
public FileSystemItem? GetItem(string path)
|
|
=> _items.TryGetValue(path, out var item) ? item : null;
|
|
public IReadOnlyList<FileSystemItem> EnumerateChildrenSafe(string directoryPath, out string? error)
|
|
{
|
|
error = null;
|
|
return [];
|
|
}
|
|
}
|
|
}
|