// // UploadRequest.swift // // Copyright (c) 2014-2024 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// `DataRequest` subclass which handles `Data` upload from memory, file, or stream using `URLSessionUploadTask`. public final class UploadRequest: DataRequest { /// Type describing the origin of the upload, whether `Data`, file, or stream. public enum Uploadable { /// Upload from the provided `Data` value. case data(Data) /// Upload from the provided file `URL`, as well as a `Bool` determining whether the source file should be /// automatically removed once uploaded. case file(URL, shouldRemove: Bool) /// Upload from the provided `InputStream`. case stream(InputStream) } // MARK: Initial State /// The `UploadableConvertible` value used to produce the `Uploadable` value for this instance. public let upload: UploadableConvertible /// `FileManager` used to perform cleanup tasks, including the removal of multipart form encoded payloads written /// to disk. public let fileManager: FileManager // MARK: Mutable State /// `Uploadable` value used by the instance. public var uploadable: Uploadable? /// Creates an `UploadRequest` using the provided parameters. /// /// - Parameters: /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` by default. /// - convertible: `UploadConvertible` value used to determine the type of upload to be performed. /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default targets /// `underlyingQueue`, but can be passed another queue from a `Session`. /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. /// - fileManager: `FileManager` used to perform cleanup tasks, including the removal of multipart form /// encoded payloads written to disk. /// - delegate: `RequestDelegate` that provides an interface to actions not performed by the `Request`. init(id: UUID = UUID(), convertible: UploadConvertible, underlyingQueue: DispatchQueue, serializationQueue: DispatchQueue, eventMonitor: EventMonitor?, interceptor: RequestInterceptor?, fileManager: FileManager, delegate: RequestDelegate) { upload = convertible self.fileManager = fileManager super.init(id: id, convertible: convertible, underlyingQueue: underlyingQueue, serializationQueue: serializationQueue, eventMonitor: eventMonitor, interceptor: interceptor, delegate: delegate) } /// Called when the `Uploadable` value has been created from the `UploadConvertible`. /// /// - Parameter uploadable: The `Uploadable` that was created. func didCreateUploadable(_ uploadable: Uploadable) { self.uploadable = uploadable eventMonitor?.request(self, didCreateUploadable: uploadable) } /// Called when the `Uploadable` value could not be created. /// /// - Parameter error: `AFError` produced by the failure. func didFailToCreateUploadable(with error: AFError) { self.error = error eventMonitor?.request(self, didFailToCreateUploadableWithError: error) retryOrFinish(error: error) } override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { guard let uploadable else { fatalError("Attempting to create a URLSessionUploadTask when Uploadable value doesn't exist.") } switch uploadable { case let .data(data): return session.uploadTask(with: request, from: data) case let .file(url, _): return session.uploadTask(with: request, fromFile: url) case .stream: return session.uploadTask(withStreamedRequest: request) } } override func reset() { // Uploadable must be recreated on every retry. uploadable = nil super.reset() } /// Produces the `InputStream` from `uploadable`, if it can. /// /// - Note: Calling this method with a non-`.stream` `Uploadable` is a logic error and will crash. /// /// - Returns: The `InputStream`. func inputStream() -> InputStream { guard let uploadable else { fatalError("Attempting to access the input stream but the uploadable doesn't exist.") } guard case let .stream(stream) = uploadable else { fatalError("Attempted to access the stream of an UploadRequest that wasn't created with one.") } eventMonitor?.request(self, didProvideInputStream: stream) return stream } override public func cleanup() { defer { super.cleanup() } guard let uploadable, case let .file(url, shouldRemove) = uploadable, shouldRemove else { return } try? fileManager.removeItem(at: url) } } /// A type that can produce an `UploadRequest.Uploadable` value. public protocol UploadableConvertible { /// Produces an `UploadRequest.Uploadable` value from the instance. /// /// - Returns: The `UploadRequest.Uploadable`. /// - Throws: Any `Error` produced during creation. func createUploadable() throws -> UploadRequest.Uploadable } extension UploadRequest.Uploadable: UploadableConvertible { public func createUploadable() throws -> UploadRequest.Uploadable { self } } /// A type that can be converted to an upload, whether from an `UploadRequest.Uploadable` or `URLRequestConvertible`. public protocol UploadConvertible: UploadableConvertible & URLRequestConvertible {}