Die Startseite holte ihre Reihen im Gänsemarsch

Die Weboberfläche holt alles, was auf der Startseite steht, in einem
einzigen `Promise.all` — und sie holt sogar mehr davon: Vorschlag und
saisonale Reihe kennt die App gar nicht. Trotzdem war sie spürbar
langsamer. Der Grund stand in einer Schleife:

    for mediathek in mediatheken {
        ... await client.send(...)
    }

Ein `await` im Schleifenrumpf heisst: Rundreise, warten, nächste
Rundreise. Bei vier Mediatheken viermal hintereinander, nachdem
„Fortsetzen" und „Als Nächstes" längst da waren. Dieselbe Schleife stand
in beiden Hintergründen, für Flaro wie für Jellyfin.

Jetzt laufen sie nebeneinander, in einer `TaskGroup`. Die Reihenfolge der
Mediatheken bleibt erhalten — nach den Antworten zu ordnen hiesse, dass
die Reihen nach jedem Start woanders stünden. Ein Actor steht dem nicht
im Weg: jede Anfrage gibt ihn beim Warten frei, genau wie die beiden
`async let` darüber es schon tun.

Nebenbei: ein Fehler bei einer Mediathek nimmt die anderen nicht mehr
mit. Eine Startseite mit drei von vier Reihen ist besser als keine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-10 13:55:24 +02:00
co-authored by Claude Opus 5
parent 8397e042b8
commit c5ab200846
2 changed files with 81 additions and 37 deletions
@@ -86,36 +86,65 @@ public actor FlaroBackend: MediaBackend {
// Einstellungen — „Gesehenes in Neuzugängen ausblenden" und // Einstellungen — „Gesehenes in Neuzugängen ausblenden" und
// „Aufmacher nur aus Neuzugängen" —, und beide taten an einem // „Aufmacher nur aus Neuzugängen" —, und beide taten an einem
// Flaro-Server deshalb nichts. // Flaro-Server deshalb nichts.
for mediathek in mediatheken { // **Nebeneinander, nicht nacheinander.**
//
// Hier stand eine Schleife mit `await` darin: bei vier Mediatheken
// vier Rundreisen hintereinander, jede erst nach der vorigen. Die
// Weboberfläche holt dieselben Reihen in einem `Promise.all` — und
// sie holt sogar mehr davon. Genau das war der Unterschied, den man
// der App ansah.
//
// Ein Actor steht dem nicht im Weg: jede Anfrage gibt ihn beim
// Warten wieder frei, so wie die beiden darüber es schon tun.
let neuzugaenge = await withTaskGroup(of: (Int, MediaRowGroup?).self) { gruppe in
for (platz, mediathek) in mediatheken.enumerated() {
gruppe.addTask { (platz, await self.neuzugang(in: mediathek)) }
}
// **Die Reihenfolge der Mediatheken, nicht die der Antworten.**
// Sonst stünden die Reihen nach jedem Start woanders — je
// nachdem, welche Abfrage zuerst zurückkam.
var gesammelt: [(Int, MediaRowGroup?)] = []
for await ergebnis in gruppe { gesammelt.append(ergebnis) }
return gesammelt.sorted { $0.0 < $1.0 }.compactMap(\.1)
}
reihen.append(contentsOf: neuzugaenge)
return reihen
}
/// Die Reihe „Zuletzt hinzugefügt" einer Mediathek — oder nichts.
///
/// **Ein Fehler bei einer Mediathek nimmt nicht die anderen mit.** Die
/// Startseite mit drei von vier Reihen ist besser als gar keine.
private func neuzugang(in mediathek: MediaLibrary) async -> MediaRowGroup? {
let pfad = mediathek.kind == .show ? "/api/v1/series" : "/api/v1/movies" let pfad = mediathek.kind == .show ? "/api/v1/series" : "/api/v1/movies"
let abfrage = [ let abfrage = [
URLQueryItem(name: "library", value: mediathek.id), URLQueryItem(name: "library", value: mediathek.id),
URLQueryItem(name: "sort", value: "added"), URLQueryItem(name: "sort", value: "added"),
URLQueryItem(name: "take", value: "24"), URLQueryItem(name: "take", value: "24"),
] ]
let titel: [MediaItem] let titel: [MediaItem]
if mediathek.kind == .show { if mediathek.kind == .show {
guard let seite = try? await client.send( guard let seite = try? await client.send(
FlaroRequest(path: pfad, query: abfrage), FlaroRequest(path: pfad, query: abfrage),
as: Wire.Page<Wire.Series>.self) else { continue } as: Wire.Page<Wire.Series>.self) else { return nil }
titel = seite.items.map { $0.neutral() } titel = seite.items.map { $0.neutral() }
} else { } else {
guard let seite = try? await client.send( guard let seite = try? await client.send(
FlaroRequest(path: pfad, query: abfrage), FlaroRequest(path: pfad, query: abfrage),
as: Wire.Page<Wire.Movie>.self) else { continue } as: Wire.Page<Wire.Movie>.self) else { return nil }
titel = seite.items.map { $0.neutral() } titel = seite.items.map { $0.neutral() }
} }
guard !titel.isEmpty else { continue }
reihen.append(MediaRowGroup(
id: "latest.\(mediathek.id)",
title: String(
localized: "flaro.row.latestIn \(mediathek.title)",
bundle: .module),
libraryID: mediathek.id,
items: titel))
}
return reihen guard !titel.isEmpty else { return nil }
return MediaRowGroup(
id: "latest.\(mediathek.id)",
title: String(localized: "flaro.row.latestIn \(mediathek.title)", bundle: .module),
libraryID: mediathek.id,
items: titel)
} }
public func items( public func items(
@@ -130,9 +130,16 @@ public func isInLocalNetwork() async -> Bool {
// sind zwei verschiedene Fragen — in einer gemeinsamen Reihe steht am // sind zwei verschiedene Fragen — in einer gemeinsamen Reihe steht am
// Ende beides durcheinander, und wer nach einem Film sucht, blättert // Ende beides durcheinander, und wer nach einem Film sucht, blättert
// an Folgen vorbei. // an Folgen vorbei.
for library in libraries { //
let items = try? await latestItems(limit: 20, inLibrary: library.id) // **Nebeneinander, nicht nacheinander.** Hier stand ein `await` in
rows.append(MediaRowGroup( // der Schleife: bei vier Mediatheken vier Rundreisen hintereinander.
// Die Reihenfolge der Mediatheken bleibt trotzdem erhalten — sonst
// stünden die Reihen nach jedem Start woanders.
let latest = await withTaskGroup(of: (Int, MediaRowGroup).self) { group in
for (place, library) in libraries.enumerated() {
group.addTask {
let items = try? await self.latestItems(limit: 20, inLibrary: library.id)
return (place, MediaRowGroup(
id: "latest.\(library.id)", id: "latest.\(library.id)",
title: String( title: String(
localized: "row.latestIn \(library.title)", localized: "row.latestIn \(library.title)",
@@ -142,6 +149,14 @@ public func isInLocalNetwork() async -> Bool {
items: items ?? [] items: items ?? []
)) ))
} }
}
var collected: [(Int, MediaRowGroup)] = []
for await result in group { collected.append(result) }
return collected.sorted { $0.0 < $1.0 }.map(\.1)
}
rows.append(contentsOf: latest)
rows.append(MediaRowGroup( rows.append(MediaRowGroup(
id: "favourites", id: "favourites",