Der Scan schreibt auch dann zwischendurch, wenn ffprobe scheitert

Gemessen an achtzig Dateien: der Zählstand steht nach einer Sekunde bei
38, nach zwei bei 75, nach drei bei 80. Der Scan liefert also längst
laufend — anders als ich behauptet hatte. Der Grund steht in
UntersuchenAsync, das je Datei speichert; ich hatte nur das
SaveChangesAsync nach der Schleife gesehen.

Eine Lücke bleibt: scheitert die Untersuchung, kehrt sie vor dem
Speichern um. Ein Bestand aus Dateien, die ffprobe nicht lesen kann,
bliebe damit bis zum Schluss unsichtbar, und wer zusieht, hält den Scan
für hängend. Deshalb jetzt zusätzlich alle fünfzig Dateien ein Schreiben
aus der Schleife heraus — fünfzig und nicht jede, weil ein Schreibvorgang
je Datei bei vierzigtausend mehr kostet, als die Sichtbarkeit wert ist.

Für das Aufräumen danach ist das unbedenklich, aber nicht offensichtlich:
es löscht alles mit älterer SeenAt-Zeit als der Lauf, und würde die Zeit
irgendwo neu genommen statt vor der Schleife festgehalten, risse es den
halben Bestand mit. Ein Test über 120 Nulldateien — an denen ffprobe
scheitert, also genau der neue Weg — hält das fest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-04 11:27:41 +02:00
co-authored by Claude Opus 5
parent b25c7a4712
commit d01c5d43f4
2 changed files with 58 additions and 0 deletions
+22
View File
@@ -62,6 +62,7 @@ public sealed class LibraryScanner(
: [];
int neu = 0, geändert = 0, unerkannt = 0, untersucht = 0;
var seitLetztemSchreiben = 0;
foreach (var datei in ergebnis.Files)
{
@@ -129,6 +130,27 @@ public sealed class LibraryScanner(
}
if (mussUntersucht && await UntersuchenAsync(eintrag, laufZeit, ct)) untersucht++;
// **Zwischendurch schreiben, nicht erst am Ende.**
//
// Gemessen an achtzig Dateien erscheinen die Titel ohnehin
// laufend — denn <see cref="UntersuchenAsync"/> speichert je
// Datei. Nur gilt das eben *nur* dort: scheitert die
// Untersuchung, kehrt sie vor dem Speichern um. Ein Bestand aus
// Dateien, die ffprobe nicht lesen kann, bliebe bis zum Schluss
// unsichtbar — und wer dabei zusieht, hält den Scan für hängend.
//
// Fünfzig, nicht jede: ein Schreibvorgang je Datei kostet bei
// vierzigtausend mehr, als die Sichtbarkeit wert ist.
//
// Für das Aufräumen weiter unten ist das unbedenklich. Es
// vergleicht gegen `laufZeit`, und die steht vor der Schleife
// fest; jede hier geschriebene Datei trägt genau diesen Wert.
if (++seitLetztemSchreiben >= 50)
{
await db.SaveChangesAsync(ct);
seitLetztemSchreiben = 0;
}
}
await db.SaveChangesAsync(ct);
+36
View File
@@ -118,6 +118,42 @@ public class ScanTests
Assert.That(filme!.Items.Select(m => m.Title), Is.EqualTo(new[] { "The Matrix" }));
}
/// <summary>Das Aufräumen darf nicht wegräumen, was derselbe Lauf schrieb.</summary>
///
/// <remarks>
/// <b>Mehr als fünfzig Dateien, und das ist der Punkt.</b> Seit der Scan
/// alle fünfzig Dateien zwischenspeichert, liegt ein Teil des Bestands
/// schon in der Datenbank, während der Lauf noch läuft. Direkt danach
/// löscht er alles, was eine ältere <c>SeenAt</c>-Zeit trägt als dieser
/// Lauf — würde die Zeit irgendwo neu genommen statt vor der Schleife
/// festgehalten, risse dieser Schritt den halben Bestand mit.
///
/// Die Dateien hier sind Nullbytes, an denen ffprobe scheitert. Damit
/// greift das Zwischenspeichern aus der Schleife und nicht das aus der
/// Untersuchung — also genau der Weg, der sonst ungeprüft bliebe.
/// </remarks>
[Test]
public async Task Ein_grosser_Bestand_ueberlebt_das_Aufraeumen()
{
for (var i = 1; i <= 120; i++) Datei($"Prüffilm {i:000} (2024).mkv");
using var server = new FlaroServer();
var (client, bibliothek) = await MitBibliothek(server, "Movies");
var erster = await Scannen(client, bibliothek);
Assert.That(erster.Added, Is.EqualTo(120));
Assert.That(erster.Removed, Is.Zero, "Ein erster Lauf darf nichts entfernen.");
Assert.That((await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies"))!.Total,
Is.EqualTo(120));
// Und ein zweiter Lauf bleibt ebenso vollständig.
var zweiter = await Scannen(client, bibliothek);
Assert.That(zweiter.Removed, Is.Zero);
Assert.That((await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies"))!.Total,
Is.EqualTo(120));
}
/// <summary>4K und 1080p sind ein Film mit zwei Dateien, nicht zwei Filme.</summary>
[Test]
public async Task Zwei_Fassungen_desselben_Films_bleiben_ein_Eintrag()