Skip to content
Merged
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
46 changes: 36 additions & 10 deletions PhotoCleaner/ImportTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,12 @@ private async Task<ImportResult> ImportFileAsync(

string? sha256 = null;
string? sha1 = null;
FileRecord? cached = null;
if (database is not null || skipDatabase is not null || trashDatabase is not null)
{
// ResolveHashesAsync returns cached hashes when size and mtime still match disk.
// The cache is keyed by source path because import inserts source paths.
FileRecord? cached = database is null
cached = database is null
? null
: await database.GetByPathAsync(file, cancellationToken).ConfigureAwait(false);
Log.Debug("Hashing '{FilePath}'", file);
Expand Down Expand Up @@ -341,20 +342,45 @@ await MediaUtilities
// Lookups are by source content hash.
if (database is not null && sha256 is not null && sha1 is not null)
{
Log.Debug("Inserting source '{SourcePath}' with SHA-256 '{Sha256}'", file, sha256);
await database
.InsertAsync(
new FileRecord(
if (cached is null)
{
Log.Debug(
"Inserting source '{SourcePath}' with SHA-256 '{Sha256}'",
file,
sha256
);
await database
.InsertAsync(
new FileRecord(
file,
sha256,
sha1,
sourceInfo.Length,
sourceInfo.LastWriteTimeUtc.Ticks,
false
),
cancellationToken
)
.ConfigureAwait(false);
}
else
{
Log.Debug(
"Updating source '{SourcePath}' with SHA-256 '{Sha256}'",
file,
sha256
);
await database
.UpdateHashesAsync(
file,
sha256,
sha1,
sourceInfo.Length,
sourceInfo.LastWriteTimeUtc.Ticks,
false
),
cancellationToken
)
.ConfigureAwait(false);
cancellationToken
)
.ConfigureAwait(false);
}
}

return ImportResult.Imported;
Expand Down
77 changes: 77 additions & 0 deletions PhotoCleanerTests/ImportTaskTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,83 @@ public async Task ExecuteAsync_ImportTwice_SecondRunSkipsAndCreatesNoDuplicate()
}
}

[Fact]
public async Task ExecuteAsync_ReplacedSource_UpdatesRecordAndNextRunSkips()
{
string srcDir = TempDir();
string outDir = TempDir();
string dbPath = TempDb();
try
{
string jpg = Path.Combine(srcDir, "photo.jpg");
File.Copy(fixture.SourceFile(TempDirectoryFixture.SmallJpegFile), jpg);
await SetExifDateAsync(jpg, "2024:06:15 10:00:00");

await using Database db = new(dbPath);
await db.InitializeAsync(TestContext.Current.CancellationToken);
ImportTask task = new(
CreateOptions(outDir),
database: db,
skipDatabase: null,
trashDatabase: null,
new()
);

(int organized1, _, int skipped1, _, _, _, int failed1, _) = await task.ExecuteAsync(
[jpg],
new DirectoryInfo(srcDir),
TestContext.Current.CancellationToken
);
organized1.Should().Be(1);
skipped1.Should().Be(0);
failed1.Should().Be(0);

DateTime firstMtime = File.GetLastWriteTimeUtc(jpg);
await SetExifDateAsync(jpg, "2024:07:15 10:00:00");
File.SetLastWriteTimeUtc(jpg, firstMtime.AddSeconds(2));
FileInfo replacedInfo = new(jpg);
(string replacedSha256, string replacedSha1) = await Database.ComputeHashesAsync(
jpg,
cancellationToken: TestContext.Current.CancellationToken
);

(int organized2, _, int skipped2, _, _, _, int failed2, _) = await task.ExecuteAsync(
[jpg],
new DirectoryInfo(srcDir),
TestContext.Current.CancellationToken
);
organized2.Should().Be(1);
skipped2.Should().Be(0);
failed2.Should().Be(0);

FileRecord? row = await db.GetByPathAsync(
jpg,
cancellationToken: TestContext.Current.CancellationToken
);
row.Should().NotBeNull();
row.Sha256.Should().Be(replacedSha256);
row.Sha1.Should().Be(replacedSha1);
row.FileSize.Should().Be(replacedInfo.Length);
row.MtimeTicks.Should().Be(replacedInfo.LastWriteTimeUtc.Ticks);

(int organized3, _, int skipped3, _, _, _, int failed3, _) = await task.ExecuteAsync(
[jpg],
new DirectoryInfo(srcDir),
TestContext.Current.CancellationToken
);
organized3.Should().Be(0);
skipped3.Should().Be(1);
failed3.Should().Be(0);
File.Exists(Path.Combine(outDir, "2024-07", "photo_1.jpg")).Should().BeFalse();
}
finally
{
Directory.Delete(srcDir, recursive: true);
Directory.Delete(outDir, recursive: true);
File.Delete(dbPath);
}
}

// -- Helpers --------------------------------------------------------------

private static async Task<string[]> GetXmpSubjectAsync(string filePath)
Expand Down