import Foundation import FlixyDownloads import PlexKit /// Füllt den Vorrat an ungesehenen Folgen wieder auf. /// /// Die Regel-Engine rechnet aus, *wie viele* Folgen je Serie fehlen; hier wird /// entschieden, *welche*. Die Trennung ist Absicht: das Rechnen braucht keinen /// Server und ist deshalb prüfbar, das Auswählen braucht ihn zwangsläufig. @MainActor struct DownloadRefiller { let server: PlexServer? let coordinator: DownloadCoordinator /// Lädt nach, was den Regeln nach fehlt. /// /// Läuft nach dem Ansehen einer Folge und beim Start — nicht auf einem /// Zeitgeber. Eine App, die im Hintergrund unaufgefordert Gigabytes lädt, /// wäre eine unangenehme Überraschung auf der Mobilfunkrechnung. @discardableResult func refill() async -> Int { guard let server, coordinator.rules.keepUnwatchedEpisodes != nil else { return 0 } let needed = coordinator.rules.seriesNeedingRefill(in: coordinator.items) guard !needed.isEmpty else { return 0 } var added = 0 let service = DownloadService(server: server, coordinator: coordinator) for (seriesKey, count) in needed { let candidates = await nextUnwatchedEpisodes( inSeries: seriesKey, count: count, server: server ) for episode in candidates { // Die Kinderliste bringt keine Dateiangaben mit — ohne sie // ließe sich weder das Speicherlimit prüfen noch ein // Fortschritt anzeigen. let detailed = (try? await server.metadata(ratingKey: episode.ratingKey)) ?? episode // Folgen ohne abrufbare Datei still überspringen: das Nachladen // läuft im Hintergrund, da hilft keine Fehlermeldung. guard let plan = try? service.plan(for: detailed) else { continue } if (try? coordinator.enqueue(plan)) != nil { added += 1 } } } return added } /// Die nächsten ungesehenen Folgen einer Serie, die noch nicht offline sind. /// /// In Ausstrahlungsreihenfolge, nicht nach Beliebtheit oder Zufall: wer /// drei Folgen vorhalten lässt, will die drei, die als Nächstes dran sind. private func nextUnwatchedEpisodes( inSeries seriesKey: String, count: Int, server: PlexServer ) async -> [PlexItem] { guard let seasons = try? await server.children(of: seriesKey) else { return [] } let offlineKeys = Set(coordinator.items.map(\.ratingKey)) var result: [PlexItem] = [] for season in seasons.sorted(by: { ($0.index ?? 0) < ($1.index ?? 0) }) { // Specials führt Plex als Staffel 0. Sie gehören nicht in den // Vorrat für „was kommt als Nächstes". guard (season.index ?? 0) > 0 else { continue } guard let episodes = try? await server.children(of: season.ratingKey) else { continue } for episode in episodes.sorted(by: { ($0.index ?? 0) < ($1.index ?? 0) }) { guard !episode.isWatched, !offlineKeys.contains(episode.ratingKey) else { continue } result.append(episode) if result.count >= count { return result } } } return result } }