Legt das Fundament für den Plex-Client an: - XcodeGen-Projekt nach dem Muster von Onyx (com.scarriffleservices, strict concurrency, Projektdatei nicht eingecheckt) - FlixyKit mit vier Modulen: PlexKit, FlixyDesign, FlixyPlayback, FlixyDownloads. macOS steht als Plattform mit drin, damit die Logik-Tests ohne Simulator laufen. - FlixyDesign: dunkle Farb-Tokens, Schriftskala, Poster- und Querformat-Kacheln, Aufmacher-Bild. Die Akzentfarbe fließt über die Umgebung und wird per WCAG-Rechnung lesbar gehalten. - PlexKit: Kopfzeilen-Identität, Transport-Protokoll, Fehlerarten und die Medienmodelle samt nachsichtigem Decoder. - FlixyPlayback: VideoPlayerEngine-Protokoll und PlaybackDecider — die eine Funktion, an der Phase 2 das Transcoding andockt. - Ausfahrbare Seitenleiste auf dem iPhone, Split-View auf dem iPad. - 45 Tests, alle grün. VLCKit 4.0.0-a24 ist vorab geprüft: baut über SPM unter Swift 6 mit strict concurrency. Die iOS-Slice ist mit 239 MB allerdings gewichtig, deshalb bekommt sie in Phase 1 ein eigenes Target. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
72 lines
2.5 KiB
Swift
72 lines
2.5 KiB
Swift
import Foundation
|
|
|
|
/// Der schmale Spalt, durch den PlexKit ans Netz kommt.
|
|
///
|
|
/// Alles darüber kennt nur dieses Protokoll. Dadurch laufen die Tests gegen
|
|
/// aufgezeichnete Antworten statt gegen einen echten Server — und zwar ohne
|
|
/// `URLProtocol`-Verrenkungen, die bei paralleler Testausführung erfahrungs-
|
|
/// gemäß ins Rutschen kommen.
|
|
public protocol PlexTransport: Sendable {
|
|
func data(for request: URLRequest) async throws -> (Data, URLResponse)
|
|
}
|
|
|
|
extension URLSession: PlexTransport {}
|
|
|
|
/// Eine Anfrage an Plex, unabhängig davon, an welchen Host sie geht.
|
|
///
|
|
/// Plex nimmt den Token wahlweise als Kopfzeile oder als Abfrageparameter.
|
|
/// Hier steht er in der Kopfzeile — sonst landet er in jedem Protokoll und in
|
|
/// jedem Absturzbericht, der URLs mitschreibt.
|
|
public struct PlexRequest: Sendable, Equatable {
|
|
public enum Method: String, Sendable { case get = "GET", post = "POST", put = "PUT", delete = "DELETE" }
|
|
|
|
public var method: Method
|
|
public var path: String
|
|
public var query: [URLQueryItem]
|
|
public var additionalHeaders: [String: String]
|
|
|
|
public init(
|
|
method: Method = .get,
|
|
path: String,
|
|
query: [URLQueryItem] = [],
|
|
additionalHeaders: [String: String] = [:]
|
|
) {
|
|
self.method = method
|
|
self.path = path
|
|
self.query = query
|
|
self.additionalHeaders = additionalHeaders
|
|
}
|
|
|
|
/// Setzt die Anfrage gegen einen Host zusammen.
|
|
public func urlRequest(
|
|
baseURL: URL,
|
|
identity: PlexClientIdentity,
|
|
token: String?
|
|
) throws(PlexError) -> URLRequest {
|
|
guard var components = URLComponents(
|
|
url: baseURL.appending(path: path),
|
|
resolvingAgainstBaseURL: false
|
|
) else {
|
|
throw .transport(description: "Ungültige Basisadresse: \(baseURL)")
|
|
}
|
|
components.queryItems = query.isEmpty ? nil : query
|
|
|
|
guard let url = components.url else {
|
|
throw .transport(description: "Anfrage ließ sich nicht zusammensetzen: \(path)")
|
|
}
|
|
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = method.rawValue
|
|
for (field, value) in identity.headers {
|
|
request.setValue(value, forHTTPHeaderField: field)
|
|
}
|
|
for (field, value) in additionalHeaders {
|
|
request.setValue(value, forHTTPHeaderField: field)
|
|
}
|
|
if let token {
|
|
request.setValue(token, forHTTPHeaderField: "X-Plex-Token")
|
|
}
|
|
return request
|
|
}
|
|
}
|