From bf90f379927d5480075908b022438ec277a3de51 Mon Sep 17 00:00:00 2001 From: ThibaultBee <37510686+ThibaultBee@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:40:10 +0100 Subject: [PATCH] feat(swift5): implement background upload --- config/swift5-uploader.yaml | 1 + config/swift5.yaml | 1 + templates/swift5/APIs.mustache | 5 +- templates/swift5/api.mustache | 4 +- .../URLSessionImplementations.mustache | 58 +++++++++++++++++++ 5 files changed, 65 insertions(+), 4 deletions(-) diff --git a/config/swift5-uploader.yaml b/config/swift5-uploader.yaml index 76d5bc68..04950a58 100644 --- a/config/swift5-uploader.yaml +++ b/config/swift5-uploader.yaml @@ -46,6 +46,7 @@ additionalProperties: podHomepage: https://docs.api.video podSocialMediaUrl: https://twitter.com/api_video podLicense: "{ :type => 'MIT' }" +backgroundSupport: true defaultChunkSize: 50 * 1024 * 1024 minChunkSize: 5 * 1024 * 1024 maxChunkSize: 128 * 1024 * 1024 diff --git a/config/swift5.yaml b/config/swift5.yaml index 0d172a4f..b9424a77 100644 --- a/config/swift5.yaml +++ b/config/swift5.yaml @@ -51,6 +51,7 @@ additionalProperties: podHomepage: https://docs.api.video podSocialMediaUrl: https://twitter.com/api_video podLicense: "{ :type => 'MIT' }" +backgroundSupport: true defaultChunkSize: 50 * 1024 * 1024 minChunkSize: 5 * 1024 * 1024 maxChunkSize: 128 * 1024 * 1024 diff --git a/templates/swift5/APIs.mustache b/templates/swift5/APIs.mustache index e581fdb2..eb9e6f0a 100644 --- a/templates/swift5/APIs.mustache +++ b/templates/swift5/APIs.mustache @@ -28,8 +28,9 @@ public class {{projectName}} { internal static var requestBuilderFactory: RequestBuilderFactory = AlamofireRequestBuilderFactory(){{/useAlamofire}}{{#useURLSession}} internal static var requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(){{/useURLSession}} public static var apiResponseQueue: DispatchQueue = .main - {{/useVapor}} - public static var timeout: TimeInterval = 60 + {{/useVapor}}{{#backgroundSupport}} + public static var backgroundIdentifier: String = "video.api.upload.background" + {{/backgroundSupport}}public static var timeout: TimeInterval = 60 internal static var customHeaders:[String: String] { var headers = defaultHeaders if let apiKey = apiKey { diff --git a/templates/swift5/api.mustache b/templates/swift5/api.mustache index fa2e48a1..4942413a 100644 --- a/templates/swift5/api.mustache +++ b/templates/swift5/api.mustache @@ -543,7 +543,7 @@ extension {{projectName}}API { let localVariableHeaderParameters = APIHelper.rejectNilHeaders(localVariableNillableHeaders) - let localVariableRequestBuilder: RequestBuilder<{{{returnType}}}{{^returnType}}Void{{/returnType}}>.Type = {{projectName}}.requestBuilderFactory.{{#returnType}}getBuilder(){{/returnType}}{{^returnType}}getNonDecodableBuilder(){{/returnType}} + let localVariableRequestBuilder: RequestBuilder<{{{returnType}}}{{^returnType}}Void{{/returnType}}>.Type = {{projectName}}.requestBuilderFactory.{{#vendorExtensions.x-client-chunk-upload}}getBackgroundBuilder(){{/vendorExtensions.x-client-chunk-upload}}{{^vendorExtensions.x-client-chunk-upload}}{{#returnType}}getBuilder(){{/returnType}}{{^returnType}}getNonDecodableBuilder(){{/returnType}}{{/vendorExtensions.x-client-chunk-upload}} return localVariableRequestBuilder.init(method: "{{httpMethod}}", URLString: (localVariableUrlComponents?.string ?? localVariableURLString), parameters: localVariableParameters, headers: localVariableHeaderParameters{{#vendorExtensions.x-client-chunk-upload}}, onProgressReady: onProgressReady{{/vendorExtensions.x-client-chunk-upload}}) } @@ -620,7 +620,7 @@ extension {{projectName}}API { let localVariableHeaderParameters = APIHelper.rejectNilHeaders(localVariableNillableHeaders) - let localVariableRequestBuilder: RequestBuilder<{{{returnType}}}{{^returnType}}Void{{/returnType}}>.Type = {{projectName}}.requestBuilderFactory.{{#returnType}}getBuilder(){{/returnType}}{{^returnType}}getNonDecodableBuilder(){{/returnType}} + let localVariableRequestBuilder: RequestBuilder<{{{returnType}}}{{^returnType}}Void{{/returnType}}>.Type = {{projectName}}.requestBuilderFactory.{{#backgroundSupport}}{{#vendorExtensions.x-client-chunk-upload}}getBackgroundBuilder(){{/vendorExtensions.x-client-chunk-upload}}{{^vendorExtensions.x-client-chunk-upload}}{{#returnType}}getBuilder(){{/returnType}}{{^returnType}}getNonDecodableBuilder(){{/returnType}}{{/vendorExtensions.x-client-chunk-upload}}{{/backgroundSupport}}{{^backgroundSupport}}{{#returnType}}getBuilder(){{/returnType}}{{^returnType}}getNonDecodableBuilder(){{/returnType}}{{/backgroundSupport}} return localVariableRequestBuilder.init(method: "{{httpMethod}}", URLString: (localVariableUrlComponents?.string ?? localVariableURLString), parameters: localVariableParameters, headers: localVariableHeaderParameters, onProgressReady: onProgressReady) } diff --git a/templates/swift5/libraries/urlsession/URLSessionImplementations.mustache b/templates/swift5/libraries/urlsession/URLSessionImplementations.mustache index c41c5065..9f4ce736 100644 --- a/templates/swift5/libraries/urlsession/URLSessionImplementations.mustache +++ b/templates/swift5/libraries/urlsession/URLSessionImplementations.mustache @@ -22,7 +22,12 @@ class URLSessionRequestBuilderFactory: RequestBuilderFactory { func getBuilder() -> RequestBuilder.Type { return URLSessionDecodableRequestBuilder.self + }{{#backgroundSupport}} + + func getBackgroundBuilder() -> RequestBuilder.Type { + return URLSessionDecodableBackgroundUploadRequestBuilder.self } + {{/backgroundSupport}} } {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} typealias {{projectName}}ChallengeHandler = ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?)) @@ -376,8 +381,61 @@ private var credentialStore = SynchronizedDictionary() } } } +}{{#backgroundSupport}} + +private class BackgroundUploadTaskURLSession: NSObject, URLSessionProtocol, URLSessionDelegate, URLSessionDataDelegate { + private let file: URL + private lazy var urlSession: URLSession = { + URLSession(configuration: .background(withIdentifier: ApiVideoClient.backgroundIdentifier), delegate: self, delegateQueue: nil) + }() + private var completion: ((Data?, URLResponse?, Error?) -> Void)? = nil + + init(_ file: URL) { + self.file = file + } + + func dataTask(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask { + completion = completionHandler + return urlSession.uploadTask(with: request, fromFile: file) + } + + func urlSession(_ session: URLSession, task: URLSessionTask, didBecomeInvalidWithError error: Error?) { + guard let error = error else { + return + } + completion?(nil, task.response, error) + } + + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + completion?(data, dataTask.response, nil) + } + + func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + sessionDelegate.urlSession(session, task: task, didReceive: challenge, completionHandler: completionHandler) + } } +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class URLSessionDecodableBackgroundUploadRequestBuilder: URLSessionDecodableRequestBuilder { + private var completion: ((Result, ErrorResponse>) -> Void)? = nil + + override open func createURLSession() -> URLSessionProtocol { + guard let parameters = parameters else { fatalError("No parameters found") } + + // Find file URL + var file: URL? = nil + for (_, value) in parameters { + if let fileURL = value as? URL { + file = fileURL + break + } + } + guard let fileURL = file else { + fatalError("No file URL found") + } + return BackgroundUploadTaskURLSession(fileURL) + } +}{{/backgroundSupport}} + private class SessionDelegate: NSObject, URLSessionTaskDelegate { func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {