Der Fortschritt sagt, woran er gerade ist — und schreibt es mit
Vorher stand in der Verwaltung minutenlang "Wird durchsucht…", ohne Hinweis, ob überhaupt etwas passiert. Bei vierzigtausend Dateien ist das nicht zu unterscheiden von einem hängenden Lauf. Scanschritt trägt Phase, Gegenstand und Zählstand und bildet daraus den Satz selbst — im Kern und nicht in der Oberfläche, weil die Apps denselben brauchen und zweimal formuliert er auseinanderdriftet. Sechs Phasen von "Suche Medien in /media/Filme" über "Suche ‚Ich einfach unverbesserlich' bei der Metadatenquelle — 44 von 1204" bis "Räume auf und lege Doppeltes zusammen". Pfade werden auf die letzten zwei Bestandteile gekürzt. ScanAsync und EnrichAsync bekommen je einen optionalen IProgress; optional heisst, dass kein vorhandener Aufrufer bricht. Der Koordinator hält nur den letzten Schritt — eine Liste über vierzigtausend Titel wäre ein Leck im Arbeitsspeicher. Dazu das Tagebuch: eine Datei je Tag unter config/logs, nach lokaler Zeit und nicht nach UTC, weil ein Scan um 23 Uhr am heutigen Datum gesucht wird. Dreissig Tage Aufbewahrung, einstellbar über FLARO_LOG_TAGE — ohne Grenze läuft die config-Partition eines NAS lautlos voll. Es wirft nie: ein volles Dateisystem darf keinen Scan abbrechen. In der Verwaltung lesbar, nach Fehlern filterbar und herunterladbar. Das Containerprotokoll ist nach einem Neustart weg und nur über SSH erreichbar — die Frage "warum wurde der Film gestern Nacht nicht erkannt?" liess sich damit nicht beantworten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
637e95fbb1
commit
f359807561
@@ -38,7 +38,8 @@ public sealed class BestandVerschwundenException(string nachricht) : Exception(n
|
||||
public sealed class LibraryScanner(
|
||||
FlaroContext db, IMediaProbe sonde, TimeProvider clock, ILogger<LibraryScanner> log)
|
||||
{
|
||||
public async Task<ScanReport> ScanAsync(Guid libraryId, CancellationToken ct = default)
|
||||
public async Task<ScanReport> ScanAsync(
|
||||
Guid libraryId, IProgress<Scanschritt>? fortschritt = null, CancellationToken ct = default)
|
||||
{
|
||||
var bibliothek = await db.Libraries.FindAsync([libraryId], ct)
|
||||
?? throw new InvalidOperationException($"Keine Bibliothek mit der Kennung {libraryId}.");
|
||||
@@ -60,6 +61,7 @@ public sealed class LibraryScanner(
|
||||
"Es wurde nichts verändert.");
|
||||
}
|
||||
|
||||
fortschritt?.Report(new Scanschritt(Scanphase.Suchen, bibliothek.Path));
|
||||
var ergebnis = LibraryWalker.Walk(bibliothek.Path);
|
||||
|
||||
if (ergebnis.Files.Count == 0 && warVorherDa > 0)
|
||||
@@ -93,9 +95,13 @@ public sealed class LibraryScanner(
|
||||
int neu = 0, geändert = 0, unerkannt = 0, untersucht = 0;
|
||||
var seitLetztemSchreiben = 0;
|
||||
|
||||
var durch = 0;
|
||||
|
||||
foreach (var datei in ergebnis.Files)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
fortschritt?.Report(new Scanschritt(
|
||||
Scanphase.Pruefen, datei.Path, ++durch, ergebnis.Files.Count));
|
||||
|
||||
Guid? filmId = null, folgeId = null;
|
||||
|
||||
@@ -196,6 +202,7 @@ public sealed class LibraryScanner(
|
||||
.Where(f => f.LibraryId == libraryId && f.SeenAt < laufZeit && f.MissingSince == null)
|
||||
.ExecuteUpdateAsync(s => s.SetProperty(f => f.MissingSince, laufZeit), ct);
|
||||
|
||||
fortschritt?.Report(new Scanschritt(Scanphase.Aufraeumen, ""));
|
||||
await VerwaisteAufraeumen(libraryId, ct);
|
||||
|
||||
log.LogInformation(
|
||||
|
||||
@@ -43,7 +43,8 @@ public sealed class MetadataEnricher(
|
||||
/// Suche.</b> Eine Zuordnung von Hand bleibt damit stehen.
|
||||
/// </param>
|
||||
public async Task<EnrichReport> EnrichAsync(
|
||||
Guid libraryId, bool erneut = false, CancellationToken ct = default)
|
||||
Guid libraryId, bool erneut = false,
|
||||
IProgress<Scanschritt>? fortschritt = null, CancellationToken ct = default)
|
||||
{
|
||||
if (!await anbieter.IsConfiguredAsync(ct))
|
||||
{
|
||||
@@ -54,9 +55,9 @@ public sealed class MetadataEnricher(
|
||||
int filme = 0, serien = 0, folgen = 0;
|
||||
try
|
||||
{
|
||||
filme = await FilmeZuordnen(libraryId, erneut, ct);
|
||||
serien = await SerienZuordnen(libraryId, erneut, ct);
|
||||
folgen = await FolgenZuordnen(libraryId, erneut, ct);
|
||||
filme = await FilmeZuordnen(libraryId, erneut, fortschritt, ct);
|
||||
serien = await SerienZuordnen(libraryId, erneut, fortschritt, ct);
|
||||
folgen = await FolgenZuordnen(libraryId, erneut, fortschritt, ct);
|
||||
}
|
||||
catch (MetadataCredentialsException fehler)
|
||||
{
|
||||
@@ -71,16 +72,20 @@ public sealed class MetadataEnricher(
|
||||
return new EnrichReport(filme, serien, folgen, Skipped: false, null);
|
||||
}
|
||||
|
||||
private async Task<int> FilmeZuordnen(Guid libraryId, bool erneut, CancellationToken ct)
|
||||
private async Task<int> FilmeZuordnen(
|
||||
Guid libraryId, bool erneut, IProgress<Scanschritt>? fortschritt, CancellationToken ct)
|
||||
{
|
||||
var offen = await db.Movies
|
||||
.Where(m => m.LibraryId == libraryId && (erneut || m.TmdbId == null))
|
||||
.ToListAsync(ct);
|
||||
var zugeordnet = 0;
|
||||
var durch = 0;
|
||||
|
||||
foreach (var film in offen)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
fortschritt?.Report(new Scanschritt(
|
||||
Scanphase.Nachschlagen, film.Title, ++durch, offen.Count));
|
||||
|
||||
var datei = await db.MediaFiles.FirstOrDefaultAsync(f => f.MovieId == film.Id, ct);
|
||||
var (vorgegeben, festgelegt) = await VorhandeneZuordnung(datei, SidecarNames.MovieNfo, ct);
|
||||
@@ -142,16 +147,20 @@ public sealed class MetadataEnricher(
|
||||
return (new ProviderIds(ausNfo.Tmdb ?? ausPfad.Tmdb, ausNfo.Imdb ?? ausPfad.Imdb), Nfo.ReadLocked(nfo));
|
||||
}
|
||||
|
||||
private async Task<int> SerienZuordnen(Guid libraryId, bool erneut, CancellationToken ct)
|
||||
private async Task<int> SerienZuordnen(
|
||||
Guid libraryId, bool erneut, IProgress<Scanschritt>? fortschritt, CancellationToken ct)
|
||||
{
|
||||
var offen = await db.Series
|
||||
.Where(s => s.LibraryId == libraryId && (erneut || s.TmdbId == null))
|
||||
.ToListAsync(ct);
|
||||
var zugeordnet = 0;
|
||||
var durch = 0;
|
||||
|
||||
foreach (var serie in offen)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
fortschritt?.Report(new Scanschritt(
|
||||
Scanphase.Nachschlagen, serie.Title, ++durch, offen.Count));
|
||||
|
||||
// Bei einer Serie liegt die NFO neben der Folge, also im
|
||||
// Staffelordner — gesucht wird deshalb ab der ersten Folgendatei.
|
||||
@@ -230,7 +239,8 @@ public sealed class MetadataEnricher(
|
||||
}
|
||||
|
||||
/// <summary>Folgen nachschlagen — aber nur bei Serien, die zugeordnet sind.</summary>
|
||||
private async Task<int> FolgenZuordnen(Guid libraryId, bool erneut, CancellationToken ct)
|
||||
private async Task<int> FolgenZuordnen(
|
||||
Guid libraryId, bool erneut, IProgress<Scanschritt>? fortschritt, CancellationToken ct)
|
||||
{
|
||||
var serien = await db.Series
|
||||
.Where(s => s.LibraryId == libraryId && s.TmdbId != null)
|
||||
@@ -244,8 +254,13 @@ public sealed class MetadataEnricher(
|
||||
.ToListAsync(ct);
|
||||
|
||||
var zugeordnet = 0;
|
||||
var durch = 0;
|
||||
foreach (var folge in offen)
|
||||
{
|
||||
fortschritt?.Report(new Scanschritt(
|
||||
Scanphase.Holen,
|
||||
$"Folge {folge.SeasonNumber}x{folge.EpisodeNumber:00}", ++durch, offen.Count));
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var serie = serien.First(s => s.Id == folge.SeriesId);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user