--- /dev/null
+//
+// TaskDelegate.swift
+//
+// Copyright (c) 2014-2017 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
+
+/// The task delegate is responsible for handling all delegate callbacks for the underlying task as well as
+/// executing all operations attached to the serial operation queue upon task completion.
+open class TaskDelegate: NSObject {
+
+ // MARK: Properties
+
+ /// The serial operation queue used to execute all operations after the task completes.
+ open let queue: OperationQueue
+
+ /// The data returned by the server.
+ public var data: Data? { return nil }
+
+ /// The error generated throughout the lifecyle of the task.
+ public var error: Error?
+
+ var task: URLSessionTask? {
+ set {
+ taskLock.lock(); defer { taskLock.unlock() }
+ _task = newValue
+ }
+ get {
+ taskLock.lock(); defer { taskLock.unlock() }
+ return _task
+ }
+ }
+
+ var initialResponseTime: CFAbsoluteTime?
+ var credential: URLCredential?
+ var metrics: AnyObject? // URLSessionTaskMetrics
+
+ private var _task: URLSessionTask? {
+ didSet { reset() }
+ }
+
+ private let taskLock = NSLock()
+
+ // MARK: Lifecycle
+
+ init(task: URLSessionTask?) {
+ _task = task
+
+ self.queue = {
+ let operationQueue = OperationQueue()
+
+ operationQueue.maxConcurrentOperationCount = 1
+ operationQueue.isSuspended = true
+ operationQueue.qualityOfService = .utility
+
+ return operationQueue
+ }()
+ }
+
+ func reset() {
+ error = nil
+ initialResponseTime = nil
+ }
+
+ // MARK: URLSessionTaskDelegate
+
+ var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?
+ var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
+ var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)?
+ var taskDidCompleteWithError: ((URLSession, URLSessionTask, Error?) -> Void)?
+
+ @objc(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)
+ func urlSession(
+ _ session: URLSession,
+ task: URLSessionTask,
+ willPerformHTTPRedirection response: HTTPURLResponse,
+ newRequest request: URLRequest,
+ completionHandler: @escaping (URLRequest?) -> Void)
+ {
+ var redirectRequest: URLRequest? = request
+
+ if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection {
+ redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request)
+ }
+
+ completionHandler(redirectRequest)
+ }
+
+ @objc(URLSession:task:didReceiveChallenge:completionHandler:)
+ func urlSession(
+ _ session: URLSession,
+ task: URLSessionTask,
+ didReceive challenge: URLAuthenticationChallenge,
+ completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
+ {
+ var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
+ var credential: URLCredential?
+
+ if let taskDidReceiveChallenge = taskDidReceiveChallenge {
+ (disposition, credential) = taskDidReceiveChallenge(session, task, challenge)
+ } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
+ let host = challenge.protectionSpace.host
+
+ if
+ let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),
+ let serverTrust = challenge.protectionSpace.serverTrust
+ {
+ if serverTrustPolicy.evaluate(serverTrust, forHost: host) {
+ disposition = .useCredential
+ credential = URLCredential(trust: serverTrust)
+ } else {
+ disposition = .cancelAuthenticationChallenge
+ }
+ }
+ } else {
+ if challenge.previousFailureCount > 0 {
+ disposition = .rejectProtectionSpace
+ } else {
+ credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
+
+ if credential != nil {
+ disposition = .useCredential
+ }
+ }
+ }
+
+ completionHandler(disposition, credential)
+ }
+
+ @objc(URLSession:task:needNewBodyStream:)
+ func urlSession(
+ _ session: URLSession,
+ task: URLSessionTask,
+ needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
+ {
+ var bodyStream: InputStream?
+
+ if let taskNeedNewBodyStream = taskNeedNewBodyStream {
+ bodyStream = taskNeedNewBodyStream(session, task)
+ }
+
+ completionHandler(bodyStream)
+ }
+
+ @objc(URLSession:task:didCompleteWithError:)
+ func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
+ if let taskDidCompleteWithError = taskDidCompleteWithError {
+ taskDidCompleteWithError(session, task, error)
+ } else {
+ if let error = error {
+ if self.error == nil { self.error = error }
+
+ if
+ let downloadDelegate = self as? DownloadTaskDelegate,
+ let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data
+ {
+ downloadDelegate.resumeData = resumeData
+ }
+ }
+
+ queue.isSuspended = false
+ }
+ }
+}
+
+// MARK: -
+
+class DataTaskDelegate: TaskDelegate, URLSessionDataDelegate {
+
+ // MARK: Properties
+
+ var dataTask: URLSessionDataTask { return task as! URLSessionDataTask }
+
+ override var data: Data? {
+ if dataStream != nil {
+ return nil
+ } else {
+ return mutableData
+ }
+ }
+
+ var progress: Progress
+ var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
+
+ var dataStream: ((_ data: Data) -> Void)?
+
+ private var totalBytesReceived: Int64 = 0
+ private var mutableData: Data
+
+ private var expectedContentLength: Int64?
+
+ // MARK: Lifecycle
+
+ override init(task: URLSessionTask?) {
+ mutableData = Data()
+ progress = Progress(totalUnitCount: 0)
+
+ super.init(task: task)
+ }
+
+ override func reset() {
+ super.reset()
+
+ progress = Progress(totalUnitCount: 0)
+ totalBytesReceived = 0
+ mutableData = Data()
+ expectedContentLength = nil
+ }
+
+ // MARK: URLSessionDataDelegate
+
+ var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)?
+ var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)?
+ var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
+ var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
+
+ func urlSession(
+ _ session: URLSession,
+ dataTask: URLSessionDataTask,
+ didReceive response: URLResponse,
+ completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
+ {
+ var disposition: URLSession.ResponseDisposition = .allow
+
+ expectedContentLength = response.expectedContentLength
+
+ if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
+ disposition = dataTaskDidReceiveResponse(session, dataTask, response)
+ }
+
+ completionHandler(disposition)
+ }
+
+ func urlSession(
+ _ session: URLSession,
+ dataTask: URLSessionDataTask,
+ didBecome downloadTask: URLSessionDownloadTask)
+ {
+ dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask)
+ }
+
+ func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
+ if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
+
+ if let dataTaskDidReceiveData = dataTaskDidReceiveData {
+ dataTaskDidReceiveData(session, dataTask, data)
+ } else {
+ if let dataStream = dataStream {
+ dataStream(data)
+ } else {
+ mutableData.append(data)
+ }
+
+ let bytesReceived = Int64(data.count)
+ totalBytesReceived += bytesReceived
+ let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
+
+ progress.totalUnitCount = totalBytesExpected
+ progress.completedUnitCount = totalBytesReceived
+
+ if let progressHandler = progressHandler {
+ progressHandler.queue.async { progressHandler.closure(self.progress) }
+ }
+ }
+ }
+
+ func urlSession(
+ _ session: URLSession,
+ dataTask: URLSessionDataTask,
+ willCacheResponse proposedResponse: CachedURLResponse,
+ completionHandler: @escaping (CachedURLResponse?) -> Void)
+ {
+ var cachedResponse: CachedURLResponse? = proposedResponse
+
+ if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
+ cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse)
+ }
+
+ completionHandler(cachedResponse)
+ }
+}
+
+// MARK: -
+
+class DownloadTaskDelegate: TaskDelegate, URLSessionDownloadDelegate {
+
+ // MARK: Properties
+
+ var downloadTask: URLSessionDownloadTask { return task as! URLSessionDownloadTask }
+
+ var progress: Progress
+ var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
+
+ var resumeData: Data?
+ override var data: Data? { return resumeData }
+
+ var destination: DownloadRequest.DownloadFileDestination?
+
+ var temporaryURL: URL?
+ var destinationURL: URL?
+
+ var fileURL: URL? { return destination != nil ? destinationURL : temporaryURL }
+
+ // MARK: Lifecycle
+
+ override init(task: URLSessionTask?) {
+ progress = Progress(totalUnitCount: 0)
+ super.init(task: task)
+ }
+
+ override func reset() {
+ super.reset()
+
+ progress = Progress(totalUnitCount: 0)
+ resumeData = nil
+ }
+
+ // MARK: URLSessionDownloadDelegate
+
+ var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> URL)?
+ var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
+ var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)?
+
+ func urlSession(
+ _ session: URLSession,
+ downloadTask: URLSessionDownloadTask,
+ didFinishDownloadingTo location: URL)
+ {
+ temporaryURL = location
+
+ guard
+ let destination = destination,
+ let response = downloadTask.response as? HTTPURLResponse
+ else { return }
+
+ let result = destination(location, response)
+ let destinationURL = result.destinationURL
+ let options = result.options
+
+ self.destinationURL = destinationURL
+
+ do {
+ if options.contains(.removePreviousFile), FileManager.default.fileExists(atPath: destinationURL.path) {
+ try FileManager.default.removeItem(at: destinationURL)
+ }
+
+ if options.contains(.createIntermediateDirectories) {
+ let directory = destinationURL.deletingLastPathComponent()
+ try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
+ }
+
+ try FileManager.default.moveItem(at: location, to: destinationURL)
+ } catch {
+ self.error = error
+ }
+ }
+
+ func urlSession(
+ _ session: URLSession,
+ downloadTask: URLSessionDownloadTask,
+ didWriteData bytesWritten: Int64,
+ totalBytesWritten: Int64,
+ totalBytesExpectedToWrite: Int64)
+ {
+ if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
+
+ if let downloadTaskDidWriteData = downloadTaskDidWriteData {
+ downloadTaskDidWriteData(
+ session,
+ downloadTask,
+ bytesWritten,
+ totalBytesWritten,
+ totalBytesExpectedToWrite
+ )
+ } else {
+ progress.totalUnitCount = totalBytesExpectedToWrite
+ progress.completedUnitCount = totalBytesWritten
+
+ if let progressHandler = progressHandler {
+ progressHandler.queue.async { progressHandler.closure(self.progress) }
+ }
+ }
+ }
+
+ func urlSession(
+ _ session: URLSession,
+ downloadTask: URLSessionDownloadTask,
+ didResumeAtOffset fileOffset: Int64,
+ expectedTotalBytes: Int64)
+ {
+ if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {
+ downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)
+ } else {
+ progress.totalUnitCount = expectedTotalBytes
+ progress.completedUnitCount = fileOffset
+ }
+ }
+}
+
+// MARK: -
+
+class UploadTaskDelegate: DataTaskDelegate {
+
+ // MARK: Properties
+
+ var uploadTask: URLSessionUploadTask { return task as! URLSessionUploadTask }
+
+ var uploadProgress: Progress
+ var uploadProgressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
+
+ // MARK: Lifecycle
+
+ override init(task: URLSessionTask?) {
+ uploadProgress = Progress(totalUnitCount: 0)
+ super.init(task: task)
+ }
+
+ override func reset() {
+ super.reset()
+ uploadProgress = Progress(totalUnitCount: 0)
+ }
+
+ // MARK: URLSessionTaskDelegate
+
+ var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)?
+
+ func URLSession(
+ _ session: URLSession,
+ task: URLSessionTask,
+ didSendBodyData bytesSent: Int64,
+ totalBytesSent: Int64,
+ totalBytesExpectedToSend: Int64)
+ {
+ if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
+
+ if let taskDidSendBodyData = taskDidSendBodyData {
+ taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
+ } else {
+ uploadProgress.totalUnitCount = totalBytesExpectedToSend
+ uploadProgress.completedUnitCount = totalBytesSent
+
+ if let uploadProgressHandler = uploadProgressHandler {
+ uploadProgressHandler.queue.async { uploadProgressHandler.closure(self.uploadProgress) }
+ }
+ }
+ }
+}