Online-Folgencheck: iTunes API vs. lokale Ordner (Teil 27)

Hörspielserien erscheinen regelmäßig mit neuen Folgen. EchoPlay prüft beim Öffnen des Dashboards automatisch, ob online bei iTunes neue Folgen verfügbar sind. Die Ergebnisse werden in einer SQLite-Tabelle gecacht, damit sie beim nächsten App-Start sofort sichtbar sind. Dieses Feature kombiniert mehrere Konzepte: einen externen API-Aufruf mit Rate-Limiting, einen DB-Cache mit Upsert-Logik, einen Vergleich zwischen Online-Daten und lokalem Dateisystem, und ein Hintergrund-Update-Pattern für die UI.

Das Problem: Langsame API, schnelle UI

Die iTunes Search API erlaubt etwa 20 Anfragen pro Minute. Bei 73 abonnierten Serien und 1,5 Sekunden Pause zwischen den Aufrufen dauert eine vollständige Prüfung fast zwei Minuten. Die UI darf in dieser Zeit nicht blockiert sein — der Nutzer will sofort seine Neuerscheinungen sehen.

DB-Cache: Sofortige Anzeige beim App-Start

Die Lösung ist ein zweistufiger Ansatz: Beim App-Start werden die gecachten Ergebnisse aus der Datenbank geladen und sofort angezeigt. Im Hintergrund läuft ein Update gegen die iTunes API, das nur alle 24 Stunden nötig ist.

Die gecachten Ergebnisse liegen in der Tabelle CachedNewReleases. Jeder Eintrag enthält die iTunes-Collection-ID als fachlichen Schlüssel, den Albumnamen, die extrahierte Folgennummer, das Erscheinungsdatum und eine Cover-URL:

public class CachedNewRelease : BaseEntity
{
    public Guid SeriesId { get; set; }
    public Series Series { get; set; } = null!;
    public string Title { get; set; } = string.Empty;
    public int? EpisodeNumber { get; set; }
    public DateTime ReleaseDate { get; set; }
    public string? CoverUrl { get; set; }
    public long CollectionId { get; set; }
    public DateTime CheckedAtUtc { get; set; }
}

Der CachedNewReleaseDataService bietet eine Upsert-Methode, die bestehende Einträge anhand der CollectionId aktualisiert und neue einfügt. Alte Einträge außerhalb des konfigurierbaren Zeitfensters werden automatisch bereinigt.

Hintergrund-Update: Nur bei Bedarf

Das Dashboard prüft nach dem Laden der gecachten Ergebnisse, ob die letzte iTunes-Abfrage länger als 24 Stunden her ist. Nur dann startet ein vollständiges Update im Hintergrund:

// Schritt 1: Gecachte Ergebnisse sofort anzeigen
await BuildNewReleaseTilesFromCacheAsync(subscribedSeries);

// Schritt 2: Im Hintergrund aktualisieren (fire-and-forget)
_ = RefreshNewReleaseCacheAsync(subscribedSeries, cutoffDate);

Während das Update läuft, zeigt ein kleiner Lade-Indikator an, dass neue Daten abgerufen werden. Wenn das Update abgeschlossen ist, aktualisiert sich die Kachelansicht automatisch.

Monatliche Gruppierung statt Serien-Gruppierung

Neuerscheinungen werden nicht nach Serie gruppiert — das war bei vielen Serien mit je ein bis zwei Folgen unübersichtlich — sondern nach Monat. Ganz oben steht die Gruppe „Angekündigt“ für Episoden mit Datum in der Zukunft, darunter die Monate absteigend. So sieht der Nutzer auf einen Blick, was im März erschienen ist, was im Februar, und was noch kommt.

Die Gruppierung entsteht im ViewModel, nicht in der Datenbank. Aus der flachen Liste der Cache-Einträge werden Tupel (Card, ReleaseDate) gebildet und nach Jahr und Monat gruppiert:

List<IGrouping<(int Year, int Month), ...>> monthGroups = cardEntries
    .Where(e => !e.Card.IsAnnounced)
    .GroupBy(e => (e.ReleaseDate.Year, e.ReleaseDate.Month))
    .OrderByDescending(g => g.Key.Year)
    .ThenByDescending(g => g.Key.Month)
    .ToList();

Badges: Visuelle Orientierung auf den Kacheln

Jede Kachel kann ein kleines Badge oben links tragen. Ein blaues „Angekündigt“-Badge markiert Episoden mit Veröffentlichungsdatum in der Zukunft. Ein grünes „Neu“-Badge zeigt Episoden, die innerhalb der letzten 7 Tage erschienen sind. Die Badge-Logik liegt im Konstruktor des NewEpisodeCardViewModel. Sie nutzt WinUI-Ressourcen für die Farben, fängt aber COMException ab, damit Unit-Tests ohne WinUI-Runtime nicht abstürzen.

Cover-Priorität: Lokal vor iTunes vor Serie

Jede Kachel zeigt ein Cover-Bild. Die Reihenfolge der Quellen ist klar definiert: Zuerst wird das lokale Episoden-Cover geprüft (aus der Datenbank, cover.jpg oder ID3-Tag). Wird dort nichts gefunden, kommt das iTunes-Album-Cover aus der gecachten CoverUrl zum Einsatz, auf 600×600 Pixel hochskaliert. Als letzter Fallback dient das Serien-Cover.

Das iTunes-Cover wird über ein URL-Pattern hochskaliert: Die API liefert URLs mit 100x100bb, die durch Ersetzen auf 600x600bb auf höhere Auflösung gebracht werden können.

Nummern aus Ordnernamen und Albumnamen extrahieren

Für die lokalen Ordner verwendet EchoPlay acht verschiedene Parser-Muster — dasselbe System wie bei der Fehlende-Folgen-Analyse. Das Muster mit den meisten Treffern gewinnt. Ein Parser wie {number:000} - {title} erkennt „229 – Drehbuch der Täuschung“ und extrahiert die Nummer 229. Für die iTunes-Albumnamen reicht ein einfacher Regex (d+), der die erste Zahl im Titel findet. Nur Nummern zwischen 1 und 999 werden akzeptiert, um Sonderalben auszufiltern.

Fehlerresilienz: Jede Serie einzeln

Jede Serie wird einzeln geprüft und einzeln in try/catch gewrappt. Wenn die API für eine Serie fehlschlägt, wird diese übersprungen und die nächste geprüft. OperationCanceledException wird durchgereicht, damit ein App-Beenden nicht blockiert wird.

Auto-Match: Serien ohne Apple Music ID

Viele Serien werden über Spotify importiert und haben keine Apple Music Artist ID. Der Online-Checker sucht in diesem Fall per Serienname bei iTunes und speichert die gefundene ID in der Datenbank. Beim nächsten Check ist die Suche nicht mehr nötig.

Fehlende Folgen ermitteln: Lokal und Online

Neben der automatischen Dashboard-Prüfung gibt es eine manuelle Funktion „Fehlende Folgen ermitteln“ im Kontextmenü jeder Serie — und einen Button „Alle Serien prüfen“ für die gesamte Mediathek. Vor der Prüfung erscheint ein Drei-Optionen-Dialog: „Online + Offline“ kombiniert lokale Nummerierungslücken mit einem Live-iTunes-Abgleich, wobei die iTunes API zu jeder fehlenden Folge den Albumnamen liefert. „Nur offline“ prüft ausschließlich die lokale Ordnerstruktur ohne Netzwerkzugriff — die fehlenden Folgen werden nur als Nummer angezeigt. „Abbrechen“ bricht die Prüfung ab, ohne Fallback.

Dieses Drei-Optionen-Pattern löst ein häufiges UX-Problem: Viele Dialoge bieten nur „Ja/Nein“, was den Nutzer zwingt, den Offline-Modus manuell umzuschalten. Mit drei Buttons bleibt die Kontrolle beim Nutzer, ohne den Workflow zu unterbrechen.

TXT-Export

Das Ergebnis jeder Prüfung — ob Einzelserie oder Gesamtprüfung — kann als Textdatei gespeichert werden. Ein FileSavePicker mit vorgeschlagenem Dateinamen Fehlende-Folgen-YYYY-MM-DD.txt öffnet sich auf Knopfdruck. Das Format ist bewusst einfach gehalten:

Fehlende Folgen – EchoPlay (31.03.2026 16:00)
===================================

TKKG (lokal: 1–210)
  Lokale Lücken: Folge 042, Folge 087
  Online verfügbar: Folge 211 – TKKG - Folge 211 - Titel

Die drei ??? (lokal: 1–229)
  Keine lokalen Lücken.
  Online verfügbar: Folge 230 – Die drei ??? - Folge 230 - Titel

===================================
Geprüft: 12 Serien | Lokale Lücken: 2 | Online neu: 1

Der MissingEpisodesReportFormatter ist eine statische Klasse in EchoPlay.Core/Models, die den Bericht aus dem Datenmodell MissingEpisodesReport formatiert. Da die Formatierung reine Textverarbeitung ohne UI-Abhängigkeit ist, lässt sie sich in Unit-Tests vollständig prüfen.

Temporärer Online-Status

Während eines Online-Abgleichs wechselt das Offline-Symbol in der StatusBar von Grau/Flugzeug auf Grün/Wifi — obwohl der Offline-Modus in den Einstellungen aktiv bleibt. Dafür sorgt der IOnlineAccessGuard mit einem Disposable-Pattern: Die RequestOnlineAccessAsync-Methode gibt ein IDisposable zurück, das den temporären Online-Status beim Dispose() automatisch zurücksetzt. Der using-Block garantiert das Zurücksetzen auch bei Exceptions.

Architektur: Interface in Core, Cache in Data, Implementation in App

Das Interface IOnlineEpisodeChecker und die Ergebnis-DTOs liegen in EchoPlay.Core. Die Cache-Entity CachedNewRelease mit DataService liegt in EchoPlay.Data. Die Implementation OnlineEpisodeChecker liegt in EchoPlay.App/Services/, weil sie sowohl die iTunes API als auch die Datenbank und das Dateisystem benötigt — die App-Schicht ist der einzige Ort mit Zugriff auf alle drei. Diese Aufteilung folgt dem gleichen Prinzip wie bei allen anderen Services in EchoPlay: Abstraktionen nach unten, Implementierungen nach oben, Composition Root verdrahtet alles.

Die gezeigten Code-Beispiele dienen zur Veranschaulichung. Nutzung auf eigene Verantwortung. Mehr dazu