Ohne Server stand man vor der Tür, mit dem Film in der Tasche

Fiel der Server aus, endete der Start bei einem Fehlerbildschirm mit
zwei Knöpfen: „Erneut versuchen" und „Abmelden". Beide nützen im
Flugzeug nichts — und dahinter lagen die Dateien, die man genau für
diesen Fall heruntergeladen hatte.

Jetzt geht die App trotzdem auf: Token und Konto liegen auf dem Gerät,
die Mediathekenliste auch, und die Startseite kommt neuerdings aus
demselben Speicher. Oben die Downloads, darunter der Stand vom letzten
Mal, ausgegraut, mit einem Satz, der sagt warum. Die Detailseite nimmt
den Titel vom Gerät, wenn der Server schweigt, und der Abspielknopf
bleibt an, weil die Datei ja da ist.

Und was man offline geschaut hat, zählt: Fortschritt, den der Server
nicht annimmt, wird zurückgelegt und nachgereicht, sobald er wieder
antwortet. Dafür sagt `reportProgress` jetzt, ob die Meldung ankam.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-12 19:41:22 +02:00
co-authored by Claude Opus 5
parent 77593105de
commit 8f0568cb6c
17 changed files with 613 additions and 39 deletions
@@ -111,6 +111,41 @@ public actor LibraryCache {
}
}
/// Die Reihen der Startseite.
///
/// **Weil es sonst nichts gibt, womit sie sich füllen liesse.** Die
/// Mediathekenliste und die Titel darin liegen schon auf der Platte; die
/// Startseite dagegen entstand bei jedem Aufruf neu am Server, und ohne
/// ihn blieb sie leer. Im Flugzeug ist das der Unterschied zwischen
/// „sehen, was es gibt" und einem Fehlerbildschirm.
public func homeRows(server: String) -> [MediaRowGroup]? {
guard let daten = try? Data(contentsOf: homeRowsFile(server: server)) else { return nil }
return try? JSONDecoder().decode([MediaRowGroup].self, from: daten)
}
public func storeHomeRows(_ rows: [MediaRowGroup], server: String) {
// Je Reihe eine Bildschirmbreite, mehr sieht ohnehin niemand, bevor
// der Server wieder antwortet.
let gekürzt = rows.map {
MediaRowGroup(
id: $0.id,
title: $0.title,
moreKey: $0.moreKey,
libraryID: $0.libraryID,
items: $0.items.prefix(20).map(\.listEssentials)
)
}
let ziel = homeRowsFile(server: server)
do {
try FileManager.default.createDirectory(
at: ziel.deletingLastPathComponent(), withIntermediateDirectories: true
)
try JSONEncoder().encode(gekürzt).write(to: ziel, options: .atomic)
} catch {
logger.warning("Startseite nicht gespeichert: \(error.localizedDescription, privacy: .public)")
}
}
// MARK: - Aufräumen
/// Alles zu einem Server vergessen — beim Abmelden.
@@ -139,6 +174,10 @@ public actor LibraryCache {
directory(server: server).appending(path: "libraries.json")
}
private func homeRowsFile(server: String) -> URL {
directory(server: server).appending(path: "home.json")
}
/// Ein Name, der als Datei taugt.
///
/// Kennungen kommen vom Server: bei Jellyfin GUIDs, bei Plex Zahlen, und