Phase 0: Projektgerüst, Design-Tokens und Navigation
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>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user