diff --git a/server/src/Flaro.Data/LibraryScanner.cs b/server/src/Flaro.Data/LibraryScanner.cs index 142eb14..aeee126 100644 --- a/server/src/Flaro.Data/LibraryScanner.cs +++ b/server/src/Flaro.Data/LibraryScanner.cs @@ -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 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); diff --git a/server/tests/Flaro.Api.Tests/ScanTests.cs b/server/tests/Flaro.Api.Tests/ScanTests.cs index 4e0ba08..94699bc 100644 --- a/server/tests/Flaro.Api.Tests/ScanTests.cs +++ b/server/tests/Flaro.Api.Tests/ScanTests.cs @@ -118,6 +118,42 @@ public class ScanTests Assert.That(filme!.Items.Select(m => m.Title), Is.EqualTo(new[] { "The Matrix" })); } + /// Das Aufräumen darf nicht wegräumen, was derselbe Lauf schrieb. + /// + /// + /// Mehr als fünfzig Dateien, und das ist der Punkt. 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 SeenAt-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. + /// + [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>("/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>("/api/v1/movies"))!.Total, + Is.EqualTo(120)); + } + /// 4K und 1080p sind ein Film mit zwei Dateien, nicht zwei Filme. [Test] public async Task Zwei_Fassungen_desselben_Films_bleiben_ein_Eintrag()