@@ -56,11 +56,29 @@ extension Components.Schemas.SwiftlyPlatformIdentifier {
5656 }
5757}
5858
59+ public struct ToolchainFile : Sendable {
60+ public var category : String
61+ public var platform : String
62+ public var version : String
63+ public var file : String
64+
65+ public init ( category: String , platform: String , version: String , file: String ) {
66+ self . category = category
67+ self . platform = platform
68+ self . version = version
69+ self . file = file
70+ }
71+ }
72+
5973public protocol HTTPRequestExecutor {
60- func execute( _ request: HTTPClientRequest , timeout: TimeAmount ) async throws -> HTTPClientResponse
6174 func getCurrentSwiftlyRelease( ) async throws -> Components . Schemas . SwiftlyRelease
6275 func getReleaseToolchains( ) async throws -> [ Components . Schemas . Release ]
6376 func getSnapshotToolchains( branch: Components . Schemas . SourceBranch , platform: Components . Schemas . PlatformIdentifier ) async throws -> Components . Schemas . DevToolchains
77+ func getGpgKeys( ) async throws -> OpenAPIRuntime . HTTPBody
78+ func getSwiftlyRelease( url: URL ) async throws -> OpenAPIRuntime . HTTPBody
79+ func getSwiftlyReleaseSignature( url: URL ) async throws -> OpenAPIRuntime . HTTPBody
80+ func getSwiftToolchainFile( _ toolchainFile: ToolchainFile ) async throws -> OpenAPIRuntime . HTTPBody
81+ func getSwiftToolchainFileSignature( _ toolchainFile: ToolchainFile ) async throws -> OpenAPIRuntime . HTTPBody
6482}
6583
6684struct SwiftlyUserAgentMiddleware : ClientMiddleware {
@@ -119,19 +137,15 @@ public class HTTPRequestExecutorImpl: HTTPRequestExecutor {
119137 }
120138 }
121139
122- public func execute( _ request: HTTPClientRequest , timeout: TimeAmount ) async throws -> HTTPClientResponse {
123- try await self . httpClient. execute ( request, timeout: timeout)
124- }
125-
126- private func client( ) throws -> Client {
140+ private func client( baseURL: URL ? = nil ) throws -> Client {
127141 let swiftlyUserAgent = SwiftlyUserAgentMiddleware ( )
128142 let transport : ClientTransport
129143
130144 let config = AsyncHTTPClientTransport . Configuration ( client: self . httpClient, timeout: . seconds( 30 ) )
131145 transport = AsyncHTTPClientTransport ( configuration: config)
132146
133147 return Client (
134- serverURL: try Servers . Server1. url ( ) ,
148+ serverURL: try baseURL ?? Servers . Server1. url ( ) ,
135149 transport: transport,
136150 middlewares: [ swiftlyUserAgent]
137151 )
@@ -151,12 +165,47 @@ public class HTTPRequestExecutorImpl: HTTPRequestExecutor {
151165 let response = try await self . client ( ) . listDevToolchains ( . init( path: . init( branch: branch, platform: platform) ) )
152166 return try response. ok. body. json
153167 }
154- }
155168
156- private func makeRequest( url: String ) -> HTTPClientRequest {
157- var request = HTTPClientRequest ( url: url)
158- request. headers. add ( name: " User-Agent " , value: " swiftly/ \( SwiftlyCore . version) " )
159- return request
169+ public func getGpgKeys( ) async throws -> OpenAPIRuntime . HTTPBody {
170+ let response = try await client ( baseURL: URL ( string: " https://www.swift.org/ " ) !) . swiftGpgKeys ( . init( ) )
171+
172+ return try response. ok. body. binary
173+ }
174+
175+ public func getSwiftlyRelease( url: URL ) async throws -> OpenAPIRuntime . HTTPBody {
176+ guard url. host ( percentEncoded: false ) == " download.swift.org " , let match = try #/\/swiftly\/(?<platform>.+)\/(?<file>.+)/# . wholeMatch ( in: url. path ( percentEncoded: false ) ) else {
177+ throw SwiftlyError ( message: " Unexpected URL format: \( url. path ( percentEncoded: false ) ) " )
178+ }
179+
180+ let response = try await client ( baseURL: URL ( string: " https://download.swift.org/ " ) !) . downloadSwiftlyRelease ( . init( path: . init( platform: String ( match. output. platform) , file: String ( match. output. file) ) ) )
181+
182+ return try response. ok. body. binary
183+ }
184+
185+ public func getSwiftlyReleaseSignature( url: URL ) async throws -> OpenAPIRuntime . HTTPBody {
186+ guard url. host ( percentEncoded: false ) == " download.swift.org " , let match = try #/\/swiftly\/(?<platform>.+)\/(?<file>.+).sig/# . wholeMatch ( in: url. path ( percentEncoded: false ) ) else {
187+ throw SwiftlyError ( message: " Unexpected URL format: \( url. path ( percentEncoded: false ) ) " )
188+ }
189+
190+ let response = try await client ( baseURL: URL ( string: " https://download.swift.org/ " ) !) . getSwiftlyReleaseSignature ( . init( path: . init( platform: String ( match. output. platform) , file: String ( match. output. file) ) ) )
191+
192+ return try response. ok. body. binary
193+ }
194+
195+ public func getSwiftToolchainFile( _ toolchainFile: ToolchainFile ) async throws -> OpenAPIRuntime . HTTPBody {
196+ let response = try await client ( baseURL: URL ( string: " https://download.swift.org/ " ) !) . downloadSwiftToolchain ( . init( path: . init( category: String ( toolchainFile. category) , platform: String ( toolchainFile. platform) , version: String ( toolchainFile. version) , file: String ( toolchainFile. file) ) ) )
197+ if response == . notFound {
198+ throw DownloadNotFoundError ( url: URL ( string: " https://download.swift.org/ \( toolchainFile. category) / \( toolchainFile. platform) / \( toolchainFile. version) / \( toolchainFile. file) " ) !)
199+ }
200+
201+ return try response. ok. body. binary
202+ }
203+
204+ public func getSwiftToolchainFileSignature( _ toolchainFile: ToolchainFile ) async throws -> OpenAPIRuntime . HTTPBody {
205+ let response = try await client ( baseURL: URL ( string: " https://download.swift.org/ " ) !) . getSwiftToolchainSignature ( . init( path: . init( category: String ( toolchainFile. category) , platform: String ( toolchainFile. platform) , version: String ( toolchainFile. version) , file: String ( toolchainFile. file) ) ) )
206+
207+ return try response. ok. body. binary
208+ }
160209}
161210
162211extension Components . Schemas . Release {
@@ -278,6 +327,19 @@ extension Components.Schemas.DevToolchainForArch {
278327 }
279328}
280329
330+ public struct DownloadProgress {
331+ public let receivedBytes : Int
332+ public let totalBytes : Int ?
333+ }
334+
335+ public struct DownloadNotFoundError : LocalizedError {
336+ public let url : URL
337+
338+ public init ( url: URL ) {
339+ self . url = url
340+ }
341+ }
342+
281343/// HTTPClient wrapper used for interfacing with various REST APIs and downloading things.
282344public struct SwiftlyHTTPClient {
283345 public let httpRequestExecutor : HTTPRequestExecutor
@@ -286,10 +348,6 @@ public struct SwiftlyHTTPClient {
286348 self . httpRequestExecutor = httpRequestExecutor
287349 }
288350
289- public struct JSONNotFoundError : LocalizedError {
290- public var url : String
291- }
292-
293351 /// Return the current Swiftly release using the swift.org API.
294352 public func getCurrentSwiftlyRelease( ) async throws -> Components . Schemas . SwiftlyRelease {
295353 try await self . httpRequestExecutor. getCurrentSwiftlyRelease ( )
@@ -417,51 +475,53 @@ public struct SwiftlyHTTPClient {
417475 }
418476 }
419477
420- public struct DownloadProgress {
421- public let receivedBytes : Int
422- public let totalBytes : Int ?
478+ public func getGpgKeys( ) async throws -> OpenAPIRuntime . HTTPBody {
479+ try await httpRequestExecutor. getGpgKeys ( )
423480 }
424481
425- public struct DownloadNotFoundError : LocalizedError {
426- public let url : String
482+ public func getSwiftlyRelease ( url : URL ) async throws -> OpenAPIRuntime . HTTPBody {
483+ try await httpRequestExecutor . getSwiftlyRelease ( url: url )
427484 }
428485
429- public func downloadFile(
430- url: URL ,
431- to destination: URL ,
432- reportProgress: ( ( DownloadProgress ) -> Void ) ? = nil
433- ) async throws {
486+ public func getSwiftlyReleaseSignature( url: URL ) async throws -> OpenAPIRuntime . HTTPBody {
487+ try await httpRequestExecutor. getSwiftlyReleaseSignature ( url: url)
488+ }
489+
490+ public func getSwiftToolchainFile( _ toolchainFile: ToolchainFile ) async throws -> OpenAPIRuntime . HTTPBody {
491+ try await httpRequestExecutor. getSwiftToolchainFile ( toolchainFile)
492+ }
493+
494+ public func getSwiftToolchainFileSignature( _ toolchainFile: ToolchainFile ) async throws -> OpenAPIRuntime . HTTPBody {
495+ try await httpRequestExecutor. getSwiftToolchainFileSignature ( toolchainFile)
496+ }
497+ }
498+
499+ extension OpenAPIRuntime . HTTPBody {
500+ public func download( to destination: URL , reportProgress: ( ( DownloadProgress ) -> Void ) ? = nil ) async throws {
434501 let fileHandle = try FileHandle ( forWritingTo: destination)
435502 defer {
436503 try ? fileHandle. close ( )
437504 }
438505
439- let request = makeRequest ( url: url. absoluteString)
440- let response = try await self . httpRequestExecutor. execute ( request, timeout: . seconds( 60 ) )
441-
442- switch response. status {
443- case . ok:
444- break
445- case . notFound:
446- throw SwiftlyHTTPClient . DownloadNotFoundError ( url: url. path)
447- default :
448- throw SwiftlyError ( message: " Received \( response. status) when trying to download \( url) " )
506+ let expectedBytes : Int ?
507+ switch self . length {
508+ case . unknown:
509+ expectedBytes = nil
510+ case let . known( count) :
511+ expectedBytes = Int ( count)
449512 }
450513
451- // if defined, the content-length headers announces the size of the body
452- let expectedBytes = response. headers. first ( name: " content-length " ) . flatMap ( Int . init)
453-
454514 var lastUpdate = Date ( )
455515 var receivedBytes = 0
456- for try await buffer in response . body {
457- receivedBytes += buffer. readableBytes
516+ for try await buffer in self {
517+ receivedBytes += buffer. count
458518
459- try fileHandle. write ( contentsOf: buffer. readableBytesView )
519+ try fileHandle. write ( contentsOf: buffer)
460520
461521 let now = Date ( )
462522 if let reportProgress, lastUpdate. distance ( to: now) > 0.25 || receivedBytes == expectedBytes {
463523 lastUpdate = now
464- reportProgress ( SwiftlyHTTPClient . DownloadProgress (
524+ reportProgress ( DownloadProgress (
465525 receivedBytes: receivedBytes,
466526 totalBytes: expectedBytes
467527 ) )
0 commit comments