Der Abschnitt verschwand alle drei Sekunden und kam wieder
`laden()` setzte `innerHTML = ''` und baute die Mediathekenliste neu — während eines Scans alle drei Sekunden. Der ganze Abschnitt flackerte sichtbar, und wer gerade auf einen Knopf zielte, klickte ins Leere. Schlimmer: jeder Neuaufbau startete über `verfolgen(false)` einen weiteren Takt. Nach einer Minute liefen zwanzig davon nebeneinander. Jetzt wird fortgeschrieben statt neu gezeichnet: eine Zeile je Mediathek, die bleibt, und nur Kopfzeile, Prüfungsknopf und Freigabeknopf ändern sich. Belegt im Browser — nach einem Auffrischen sind es dieselben Knoten, nur die Beschriftung ist eine andere. Dieselbe Behandlung für die Seitenleiste. Und weil Seitenleiste und Verwaltung getrennt nachfragten, zeigten sie zwei Momente — 95 hier, 84 dort, während im Hintergrund Doppelte zusammengelegt wurden. Die Verwaltung reicht ihren Stand jetzt weiter: ein Abruf, zwei Anzeigen. Dazu zwei Dinge, die beim Prüfen auffielen. Zurücksetzen und Entfernen prallen jetzt serverseitig mit 409 ab, solange ein Lauf läuft — die Oberfläche sperrt die Knöpfe, aber darauf darf sich der Server nicht verlassen; ein Aufruf von Hand während eines Scans liess den Lauf an einer DbUpdateConcurrencyException sterben. Und in der Fortschrittszeile steht beim Holen der Folgenmetadaten jetzt der Serienname: „Folge 2×13" allein sagt bei 3815 Folgen nichts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a6e9a6e425
commit
960cca3dae
@@ -282,11 +282,22 @@ public static class ManagementEndpoints
|
||||
// sie weiss: Titel, Zuordnungen, Fortschritt. Ein Server, der beim
|
||||
// Aufräumen in der Oberfläche fremde Filme löscht, ist ein Server,
|
||||
// den man nicht noch einmal benutzt.
|
||||
bibliotheken.MapDelete("/{id:guid}", async (Guid id, FlaroContext db) =>
|
||||
bibliotheken.MapDelete("/{id:guid}", async (
|
||||
Guid id, FlaroContext db, ScanCoordinator koordinator) =>
|
||||
{
|
||||
var vorhanden = await db.Libraries.FindAsync(id);
|
||||
if (vorhanden is null) return Results.NotFound();
|
||||
|
||||
// Aus demselben Grund wie beim Zurücksetzen — nur dass hier die
|
||||
// ganze Mediathek unter dem laufenden Scan verschwände.
|
||||
if (koordinator.IsRunning(id))
|
||||
{
|
||||
return Results.Problem(
|
||||
"Für diese Mediathek läuft gerade ein Scan. "
|
||||
+ "Entfernen geht erst, wenn er durch ist.",
|
||||
statusCode: StatusCodes.Status409Conflict);
|
||||
}
|
||||
|
||||
db.Libraries.Remove(vorhanden);
|
||||
await db.SaveChangesAsync();
|
||||
return Results.NoContent();
|
||||
@@ -302,11 +313,27 @@ public static class ManagementEndpoints
|
||||
//
|
||||
// Nicht gelöscht wird eine einzige Mediendatei. Entfernt wird allein,
|
||||
// was Flaro über sie weiss.
|
||||
bibliotheken.MapPost("/{id:guid}/reset", async (Guid id, FlaroContext db) =>
|
||||
bibliotheken.MapPost("/{id:guid}/reset", async (
|
||||
Guid id, FlaroContext db, ScanCoordinator koordinator) =>
|
||||
{
|
||||
var vorhanden = await db.Libraries.FindAsync(id);
|
||||
if (vorhanden is null) return Results.NotFound();
|
||||
|
||||
// **Nicht während eines Laufs.**
|
||||
//
|
||||
// Die Oberfläche sperrt den Knopf, aber darauf darf sich der
|
||||
// Server nicht verlassen: ein zweiter Administrator oder ein
|
||||
// Aufruf von Hand genügt. Wer Zeilen löscht, während der Scan
|
||||
// welche schreibt, bekommt einen Lauf, der mitten im Schreiben
|
||||
// abbricht — und einen Bestand, den niemand mehr erklären kann.
|
||||
if (koordinator.IsRunning(id))
|
||||
{
|
||||
return Results.Problem(
|
||||
"Für diese Mediathek läuft gerade ein Scan. "
|
||||
+ "Zurücksetzen geht erst, wenn er durch ist.",
|
||||
statusCode: StatusCodes.Status409Conflict);
|
||||
}
|
||||
|
||||
// In dieser Reihenfolge: erst die Dateien, dann die Titel. Die
|
||||
// Fremdschlüssel zeigen von den Dateien auf Film und Folge.
|
||||
await db.MediaFiles.Where(f => f.LibraryId == id).ExecuteDeleteAsync();
|
||||
|
||||
@@ -57,6 +57,11 @@ public static partial class Fehlertext
|
||||
+ "Oft ist das ein volles Volume oder ein schlafendes Laufwerk.",
|
||||
TaskCanceledException or OperationCanceledException =>
|
||||
"Der Vorgang wurde abgebrochen.",
|
||||
// Tritt auf, wenn jemand dieselben Zeilen gleichzeitig entfernt —
|
||||
// etwa ein „Zurücksetzen" während eines laufenden Scans.
|
||||
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException =>
|
||||
"Jemand hat dieselben Einträge gleichzeitig geändert. "
|
||||
+ "Der Lauf wurde abgebrochen; starte ihn noch einmal.",
|
||||
HttpRequestException =>
|
||||
"Die Metadatenquelle war nicht erreichbar. "
|
||||
+ "Beim nächsten Lauf wird es erneut versucht.",
|
||||
|
||||
@@ -325,9 +325,12 @@ public sealed class MetadataEnricher(
|
||||
private async Task<int> FolgenZuordnen(
|
||||
Guid libraryId, bool erneut, IProgress<Scanschritt>? fortschritt, CancellationToken ct)
|
||||
{
|
||||
// **Mit Titel, nicht nur mit Kennung.** In der Fortschrittszeile stand
|
||||
// „Hole Metadaten zu Folge 2×13" — und bei 3815 Folgen aus achtzig
|
||||
// Serien sagt eine Folgennummer ohne Serie überhaupt nichts.
|
||||
var serien = await db.Series
|
||||
.Where(s => s.LibraryId == libraryId && s.TmdbId != null)
|
||||
.Select(s => new { s.Id, TmdbId = s.TmdbId!.Value })
|
||||
.Select(s => new { s.Id, TmdbId = s.TmdbId!.Value, s.Title })
|
||||
.ToListAsync(ct);
|
||||
if (serien.Count == 0) return 0;
|
||||
|
||||
@@ -336,16 +339,21 @@ public sealed class MetadataEnricher(
|
||||
.Where(f => kennungen.Contains(f.SeriesId) && (erneut || f.Overview == null))
|
||||
.ToListAsync(ct);
|
||||
|
||||
// Einmal nachschlagbar machen: `First` je Folge wäre bei 3815 Folgen
|
||||
// und achtzig Serien eine Viertelmillion Vergleiche.
|
||||
var nachKennung = serien.ToDictionary(s => s.Id);
|
||||
|
||||
var zugeordnet = 0;
|
||||
var durch = 0;
|
||||
foreach (var folge in offen)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if (!nachKennung.TryGetValue(folge.SeriesId, out var serie)) continue;
|
||||
|
||||
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);
|
||||
$"{serie.Title} {folge.SeasonNumber}x{folge.EpisodeNumber:00}",
|
||||
++durch, offen.Count));
|
||||
|
||||
var gefunden = await anbieter.FindEpisodeAsync(serie.TmdbId, folge.SeasonNumber, folge.EpisodeNumber, ct);
|
||||
if (gefunden is null) continue;
|
||||
|
||||
Reference in New Issue
Block a user