diff --git a/Packages/FlaroKit/Sources/FlaroServerKit/FlaroBackend.swift b/Packages/FlaroKit/Sources/FlaroServerKit/FlaroBackend.swift index 852a432..010787f 100644 --- a/Packages/FlaroKit/Sources/FlaroServerKit/FlaroBackend.swift +++ b/Packages/FlaroKit/Sources/FlaroServerKit/FlaroBackend.swift @@ -86,38 +86,67 @@ public actor FlaroBackend: MediaBackend { // Einstellungen — „Gesehenes in Neuzugängen ausblenden" und // „Aufmacher nur aus Neuzugängen" —, und beide taten an einem // Flaro-Server deshalb nichts. - for mediathek in mediatheken { - let pfad = mediathek.kind == .show ? "/api/v1/series" : "/api/v1/movies" - let abfrage = [ - URLQueryItem(name: "library", value: mediathek.id), - URLQueryItem(name: "sort", value: "added"), - URLQueryItem(name: "take", value: "24"), - ] - let titel: [MediaItem] - if mediathek.kind == .show { - guard let seite = try? await client.send( - FlaroRequest(path: pfad, query: abfrage), - as: Wire.Page.self) else { continue } - titel = seite.items.map { $0.neutral() } - } else { - guard let seite = try? await client.send( - FlaroRequest(path: pfad, query: abfrage), - as: Wire.Page.self) else { continue } - titel = seite.items.map { $0.neutral() } + // **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)) } } - 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)) + + // **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 abfrage = [ + URLQueryItem(name: "library", value: mediathek.id), + URLQueryItem(name: "sort", value: "added"), + URLQueryItem(name: "take", value: "24"), + ] + + let titel: [MediaItem] + if mediathek.kind == .show { + guard let seite = try? await client.send( + FlaroRequest(path: pfad, query: abfrage), + as: Wire.Page.self) else { return nil } + titel = seite.items.map { $0.neutral() } + } else { + guard let seite = try? await client.send( + FlaroRequest(path: pfad, query: abfrage), + as: Wire.Page.self) else { return nil } + titel = seite.items.map { $0.neutral() } + } + + 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( inLibrary library: MediaLibrary, query: MediaQuery, diff --git a/Packages/FlaroKit/Sources/JellyfinKit/JellyfinBackend.swift b/Packages/FlaroKit/Sources/JellyfinKit/JellyfinBackend.swift index 1b2b99a..c8f3c0d 100644 --- a/Packages/FlaroKit/Sources/JellyfinKit/JellyfinBackend.swift +++ b/Packages/FlaroKit/Sources/JellyfinKit/JellyfinBackend.swift @@ -130,19 +130,34 @@ public func isInLocalNetwork() async -> Bool { // sind zwei verschiedene Fragen — in einer gemeinsamen Reihe steht am // Ende beides durcheinander, und wer nach einem Film sucht, blättert // an Folgen vorbei. - for library in libraries { - let items = try? await latestItems(limit: 20, inLibrary: library.id) - rows.append(MediaRowGroup( - id: "latest.\(library.id)", - title: String( - localized: "row.latestIn \(library.title)", - bundle: .module - ), - libraryID: library.id, - items: items ?? [] - )) + // + // **Nebeneinander, nicht nacheinander.** Hier stand ein `await` in + // 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)", + title: String( + localized: "row.latestIn \(library.title)", + bundle: .module + ), + libraryID: library.id, + 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( id: "favourites", title: String(localized: "row.favourites", bundle: .module),