diff --git a/config/swift5-uploader.yaml b/config/swift5-uploader.yaml index bd03a328..04950a58 100644 --- a/config/swift5-uploader.yaml +++ b/config/swift5-uploader.yaml @@ -31,7 +31,7 @@ changelog: - Fix large upload request time out - Fix callback on wrong token -library: alamofire +library: urlsession generateAliasAsModel: true removeMigrationProjectNameClass: true swiftPackagePath: "Sources" @@ -46,17 +46,12 @@ 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 files: - Auth/ApiVideoCredential.mustache: - folder: Sources/Auth - destinationFilename: ApiVideoCredential.swift - Auth/ApiVideoAuthenticator.mustache: - folder: Sources/Auth - destinationFilename: ApiVideoAuthenticator.swift Environment.mustache: folder: Sources/Models destinationFilename: Environment.swift diff --git a/config/swift5.yaml b/config/swift5.yaml index 9a96a35a..b9424a77 100644 --- a/config/swift5.yaml +++ b/config/swift5.yaml @@ -36,7 +36,7 @@ changelog: - 0.1.0 (2021-12-06): - Initial release -library: alamofire +library: urlsession generateAliasAsModel: true removeMigrationProjectNameClass: true swiftPackagePath: "Sources" @@ -51,17 +51,12 @@ 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 files: - Auth/ApiVideoCredential.mustache: - folder: Sources/Auth - destinationFilename: ApiVideoCredential.swift - Auth/ApiVideoAuthenticator.mustache: - folder: Sources/Auth - destinationFilename: ApiVideoAuthenticator.swift Environment.mustache: folder: Sources/Models destinationFilename: Environment.swift diff --git a/templates/swift5/APIs.mustache b/templates/swift5/APIs.mustache index b341eee8..09029084 100644 --- a/templates/swift5/APIs.mustache +++ b/templates/swift5/APIs.mustache @@ -16,21 +16,38 @@ import Vapor {{/removeMigrationProjectNameClass}} public class {{projectName}} { - public static var apiKey: String? = nil + private static var apiKey: String? = nil public static var basePath = "{{basePath}}" {{#useVapor}} - internal static var customHeaders: [String: String] = [:] + internal static var defaultHeaders: HTTPHeaders = ["AV-Origin-Client": "{{ originClient }}:{{ podVersion }}"] {{/useVapor}} {{^useVapor}} - internal static var customHeaders:[String: String] = ["AV-Origin-Client": "{{ originClient }}:{{ podVersion }}"] + internal static var defaultHeaders:[String: String] = ["AV-Origin-Client": "{{ originClient }}:{{ podVersion }}"] + internal static var credential: URLCredential? private static var chunkSize: Int = {{defaultChunkSize}}{{#useAlamofire}} internal static var requestBuilderFactory: RequestBuilderFactory = AlamofireRequestBuilderFactory(){{/useAlamofire}}{{#useURLSession}} internal static var requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(){{/useURLSession}} - internal static var credential = ApiVideoCredential() public static var apiResponseQueue: DispatchQueue = .main - 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 { + headers["Authorization"] = apiKey + } + return headers + } + + public static func setApiKey(_ apiKey: String?) { + if let apiKey = apiKey { + self.apiKey = "Basic " + "\(apiKey):".toBase64() + } else { + self.apiKey = nil + } + } - public static func setChunkSize(chunkSize: Int) throws { + public static func setChunkSize(_ chunkSize: Int) throws { if (chunkSize > {{maxChunkSize}}) { throw ParameterError.outOfRange } else if (chunkSize < {{minChunkSize}}) { @@ -55,25 +72,25 @@ public class {{projectName}} { } } - static func isValidVersion(version: String) -> Bool { + static func isValidVersion(_ version: String) -> Bool { let pattern = #"^\d{1,3}(\.\d{1,3}(\.\d{1,3})?)?$"# return isValid(pattern: pattern, field: version) } - static func isValidName(name: String) -> Bool { + static func isValidName(_ name: String) -> Bool { let pattern = #"^[\w\-]{1,50}$"# return isValid(pattern: pattern, field: name) } static func setName(key: String, name: String, version: String) throws { - if(!isValidName(name: name)) { + if(!isValidName(name)) { throw ParameterError.invalidName } - if(!isValidVersion(version: version)) { + if(!isValidVersion(version)) { throw ParameterError.invalidVersion } - {{projectName}}.customHeaders[key] = name + ":" + version + {{projectName}}.defaultHeaders[key] = name + ":" + version } public static func setSdkName(name: String, version: String) throws { @@ -83,11 +100,10 @@ public class {{projectName}} { public static func setApplicationName(name: String, version: String) throws { try setName(key: "AV-Origin-App", name: name, version: version) } - - {{/useVapor}} }{{^useVapor}} {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class RequestBuilder { + var credential: URLCredential? var headers: [String: String] {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var parameters: [String: Any]? {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let method: String @@ -126,9 +142,15 @@ public class {{projectName}} { } return self } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func addCredential() -> Self { + credential = {{projectName}}.credential + return self + } } {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol RequestBuilderFactory { func getNonDecodableBuilder() -> RequestBuilder.Type - func getBuilder() -> RequestBuilder.Type -}{{/useVapor}} + func getBuilder() -> RequestBuilder.Type{{#backgroundSupport}} + func getBackgroundBuilder() -> RequestBuilder.Type +{{/backgroundSupport}}}{{/useVapor}} diff --git a/templates/swift5/Auth/ApiVideoCredential.mustache b/templates/swift5/Auth/ApiVideoCredential.mustache deleted file mode 100644 index 04add438..00000000 --- a/templates/swift5/Auth/ApiVideoCredential.mustache +++ /dev/null @@ -1,31 +0,0 @@ -// ApiVideoCredential.swift -// - -import Foundation -import Alamofire - -struct ApiVideoCredential: AuthenticationCredential { - let accessToken: String - let refreshToken: String - let tokenType: String - - let expiration: Date - - // Require refresh if within 5 minutes of expiration - var requiresRefresh: Bool { Date(timeIntervalSinceNow: 60 * 5) > expiration } - - - public init(accessToken: AccessToken) { - self.accessToken = accessToken.accessToken! - self.refreshToken = accessToken.refreshToken! - self.tokenType = accessToken.tokenType! - self.expiration = Date(timeIntervalSinceNow: Double(accessToken.expiresIn!)) - } - - public init() { - self.accessToken = "" - self.refreshToken = "" - self.tokenType = "" - self.expiration = Date() - } -} diff --git a/templates/swift5/Extensions.mustache b/templates/swift5/Extensions.mustache index 9ebfcae7..27a4567f 100644 --- a/templates/swift5/Extensions.mustache +++ b/templates/swift5/Extensions.mustache @@ -117,6 +117,12 @@ extension String: CodingKey { } +extension String { + func toBase64() -> String { + return Data(self.utf8).base64EncodedString() + } +} + extension KeyedEncodingContainerProtocol { {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeArray(_ values: [T], forKey key: Self.Key) throws where T: Encodable { diff --git a/templates/swift5/Models.mustache b/templates/swift5/Models.mustache index 2b72c763..3165c72f 100644 --- a/templates/swift5/Models.mustache +++ b/templates/swift5/Models.mustache @@ -81,16 +81,15 @@ protocol JSONEncodable { request = nil } - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var state: Request.State { - request?.state ?? .initialized + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var isFinished: Bool { + request?.isFinished ?? false } - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var uploadProgress: Progress? { - request?.uploadProgress - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var downloadProgress: Progress? { - request?.downloadProgress + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var progress: Progress? { + // TODO: Add upload and download progress as subprogress + let progress = Progress(totalUnitCount: (request?.uploadProgress.totalUnitCount ?? 0) + (request?.downloadProgress.totalUnitCount ?? 0)) + progress.completedUnitCount = (request?.uploadProgress.completedUnitCount ?? 0) + (request?.downloadProgress.completedUnitCount ?? 0) + return progress } {{/useAlamofire}} {{^useAlamofire}} @@ -108,5 +107,18 @@ protocol JSONEncodable { task?.cancel() task = nil } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var isFinished: Bool { + guard let state = task?.state else { + return false + } + + return state == URLSessionTask.State.completed + } + + @available(iOS 11.0, macOS 10.13, macCatalyst 13.0, tvOS 11.0, watchOS 4.0, *) + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var progress: Progress? { + return task?.progress + } {{/useAlamofire}} } \ No newline at end of file diff --git a/templates/swift5/Upload/FileChunkInputStream.mustache b/templates/swift5/Upload/FileChunkInputStream.mustache index 61ed2948..6d3043ee 100644 --- a/templates/swift5/Upload/FileChunkInputStream.mustache +++ b/templates/swift5/Upload/FileChunkInputStream.mustache @@ -1,6 +1,3 @@ -// FileChunkInputStream.swift -// - import Foundation public class FileChunkInputStream: InputStream { diff --git a/templates/swift5/Upload/ProgressiveUploadSessionProtocol.mustache b/templates/swift5/Upload/ProgressiveUploadSessionProtocol.mustache index c60261c0..88a093a3 100644 --- a/templates/swift5/Upload/ProgressiveUploadSessionProtocol.mustache +++ b/templates/swift5/Upload/ProgressiveUploadSessionProtocol.mustache @@ -1,6 +1,3 @@ -// ProgressiveUploadSessionProtocol.swift -// - import Foundation public protocol ProgressiveUploadSessionProtocol { diff --git a/templates/swift5/Upload/RequestTaskQueue.mustache b/templates/swift5/Upload/RequestTaskQueue.mustache index 9f975924..93f92c37 100644 --- a/templates/swift5/Upload/RequestTaskQueue.mustache +++ b/templates/swift5/Upload/RequestTaskQueue.mustache @@ -1,12 +1,10 @@ import Foundation -import Alamofire public class RequestTaskQueue: RequestTask { private let operationQueue: OperationQueue private var requestBuilders: [RequestBuilder] = [] - private let _downloadProgress = Progress(totalUnitCount: 0) - private let _uploadProgress = Progress(totalUnitCount: 0) + private let _progress = Progress(totalUnitCount: 0) internal init(queueLabel: String) { operationQueue = OperationQueue() @@ -15,34 +13,20 @@ public class RequestTaskQueue: RequestTask { super.init() } - override public var uploadProgress: Progress { + {{#useURLSession}}@available(iOS 11.0, macOS 10.13, macCatalyst 13.0, tvOS 11.0, watchOS 4.0, *) + {{/useURLSession}}override public var progress: Progress { var completedUnitCount: Int64 = 0 var totalUnitCount: Int64 = 0 requestBuilders.forEach { - if let progress = $0.requestTask.uploadProgress { + if let progress = $0.requestTask.progress { completedUnitCount += progress.completedUnitCount totalUnitCount += progress.totalUnitCount } } - _uploadProgress.totalUnitCount = totalUnitCount - _uploadProgress.completedUnitCount = completedUnitCount - return _uploadProgress - } - - override public var downloadProgress: Progress { - var completedUnitCount: Int64 = 0 - var totalUnitCount: Int64 = 0 - requestBuilders.forEach { - if let progress = $0.requestTask.downloadProgress { - completedUnitCount += progress.completedUnitCount - totalUnitCount += progress.totalUnitCount - } - } - - _downloadProgress.totalUnitCount = totalUnitCount - _downloadProgress.completedUnitCount = completedUnitCount - return _downloadProgress + _progress.totalUnitCount = totalUnitCount + _progress.completedUnitCount = completedUnitCount + return _progress } internal func willExecuteRequestBuilder(requestBuilder: RequestBuilder) -> Void { @@ -62,6 +46,10 @@ public class RequestTaskQueue: RequestTask { } operationQueue.cancelAllOperations() } + + override public var isFinished: Bool { + requestBuilders.allSatisfy { $0.requestTask.isFinished } + } } final class RequestOperation: Operation { diff --git a/templates/swift5/Upload/UploadChunkRequestTaskQueue.mustache b/templates/swift5/Upload/UploadChunkRequestTaskQueue.mustache index c7056bec..9bcbc138 100644 --- a/templates/swift5/Upload/UploadChunkRequestTaskQueue.mustache +++ b/templates/swift5/Upload/UploadChunkRequestTaskQueue.mustache @@ -1,6 +1,4 @@ import Foundation -import Alamofire - public class UploadChunkRequestTaskQueue: RequestTaskQueue