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
@@ -0,0 +1,75 @@
|
||||
namespace Flaro.Core.Library;
|
||||
|
||||
/// <summary>Woran ein Lauf gerade ist.</summary>
|
||||
public enum Scanphase
|
||||
{
|
||||
/// <summary>Das Dateisystem wird durchgegangen.</summary>
|
||||
Suchen,
|
||||
|
||||
/// <summary>Eine Datei wird gelesen und vermessen.</summary>
|
||||
Pruefen,
|
||||
|
||||
/// <summary>Ein Titel wird bei der Metadatenquelle gesucht.</summary>
|
||||
Nachschlagen,
|
||||
|
||||
/// <summary>Die Angaben zu einem gefundenen Titel werden geholt.</summary>
|
||||
Holen,
|
||||
|
||||
/// <summary>Plakate und Hintergründe werden geladen.</summary>
|
||||
Bilder,
|
||||
|
||||
/// <summary>Verwaistes wird entfernt, Doppeltes zusammengelegt.</summary>
|
||||
Aufraeumen,
|
||||
}
|
||||
|
||||
/// <summary>Ein Zwischenstand: was gerade passiert, und wie weit.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Mit Namen, nicht nur mit Zahlen.</b> Vorher stand in der Verwaltung
|
||||
/// minutenlang „Wird durchsucht…" — ohne Hinweis, ob überhaupt etwas
|
||||
/// passiert. Wer bei vierzigtausend Dateien zusieht, braucht zu sehen, dass
|
||||
/// sich etwas bewegt, und woran es gerade hängt, wenn es hängt.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Ein <c>record struct</c>, weil davon im Sekundentakt viele entstehen und
|
||||
/// keiner davon überlebt.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="Phase">Welcher Abschnitt des Laufs.</param>
|
||||
/// <param name="Was">Woran genau — ein Titel oder ein Pfad.</param>
|
||||
/// <param name="Erledigt">Wie viele Stücke schon durch sind.</param>
|
||||
/// <param name="Gesamt">Wie viele es insgesamt sind; <c>0</c>, wenn unbekannt.</param>
|
||||
public readonly record struct Scanschritt(Scanphase Phase, string Was, int Erledigt = 0, int Gesamt = 0)
|
||||
{
|
||||
/// <summary>Der Satz, der in der Oberfläche steht.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <b>Hier und nicht in der Oberfläche.</b> Die Apps brauchen denselben
|
||||
/// Satz, und zweimal formuliert driftet er auseinander.
|
||||
/// </remarks>
|
||||
public string Text()
|
||||
{
|
||||
var zaehler = Gesamt > 0 ? $" — {Erledigt} von {Gesamt}" : "";
|
||||
|
||||
return Phase switch
|
||||
{
|
||||
Scanphase.Suchen => $"Suche Medien in {Kurz(Was)}",
|
||||
Scanphase.Pruefen => $"Prüfe {Kurz(Was)}{zaehler}",
|
||||
Scanphase.Nachschlagen => $"Suche „{Was}“ bei der Metadatenquelle{zaehler}",
|
||||
Scanphase.Holen => $"Hole Metadaten zu {Was}{zaehler}",
|
||||
Scanphase.Bilder => $"Lade Bilder zu {Was}{zaehler}",
|
||||
Scanphase.Aufraeumen => "Räume auf und lege Doppeltes zusammen",
|
||||
_ => Was,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Die letzten zwei Pfadbestandteile — ein ganzer Pfad sprengt die Zeile.</summary>
|
||||
private static string Kurz(string pfad)
|
||||
{
|
||||
if (string.IsNullOrEmpty(pfad)) return pfad;
|
||||
|
||||
var teile = pfad.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
return teile.Length <= 2 ? pfad : string.Join('/', teile[^2..]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user