| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 | ////  SessionDelegate.swift//  Kingfisher////  Created by Wei Wang on 2018/11/1.////  Copyright (c) 2019 Wei Wang <onevcat@gmail.com>////  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// Represents the delegate object of downloader session. It also behave like a task manager for downloading.class SessionDelegate: NSObject {    typealias SessionChallengeFunc = (        URLSession,        URLAuthenticationChallenge,        (URLSession.AuthChallengeDisposition, URLCredential?) -> Void    )    typealias SessionTaskChallengeFunc = (        URLSession,        URLSessionTask,        URLAuthenticationChallenge,        (URLSession.AuthChallengeDisposition, URLCredential?) -> Void    )    private var tasks: [URL: SessionDataTask] = [:]    private let lock = NSLock()    let onValidStatusCode = Delegate<Int, Bool>()    let onDownloadingFinished = Delegate<(URL, Result<URLResponse, KingfisherError>), Void>()    let onDidDownloadData = Delegate<SessionDataTask, Data?>()    let onReceiveSessionChallenge = Delegate<SessionChallengeFunc, Void>()    let onReceiveSessionTaskChallenge = Delegate<SessionTaskChallengeFunc, Void>()    func add(        _ dataTask: URLSessionDataTask,        url: URL,        callback: SessionDataTask.TaskCallback) -> DownloadTask    {        lock.lock()        defer { lock.unlock() }        // Create a new task if necessary.        let task = SessionDataTask(task: dataTask)        task.onCallbackCancelled.delegate(on: self) { [unowned task] (self, value) in            let (token, callback) = value            let error = KingfisherError.requestError(reason: .taskCancelled(task: task, token: token))            task.onTaskDone.call((.failure(error), [callback]))            // No other callbacks waiting, we can clear the task now.            if !task.containsCallbacks {                let dataTask = task.task                self.remove(dataTask)            }        }        let token = task.addCallback(callback)        tasks[url] = task        return DownloadTask(sessionTask: task, cancelToken: token)    }    func append(        _ task: SessionDataTask,        url: URL,        callback: SessionDataTask.TaskCallback) -> DownloadTask    {        let token = task.addCallback(callback)        return DownloadTask(sessionTask: task, cancelToken: token)    }    private func remove(_ task: URLSessionTask) {        guard let url = task.originalRequest?.url else {            return        }        lock.lock()        defer {lock.unlock()}        tasks[url] = nil    }    private func task(for task: URLSessionTask) -> SessionDataTask? {        guard let url = task.originalRequest?.url else {            return nil        }        lock.lock()        defer { lock.unlock() }        guard let sessionTask = tasks[url] else {            return nil        }        guard sessionTask.task.taskIdentifier == task.taskIdentifier else {            return nil        }        return sessionTask    }    func task(for url: URL) -> SessionDataTask? {        lock.lock()        defer { lock.unlock() }        return tasks[url]    }    func cancelAll() {        lock.lock()        let taskValues = tasks.values        lock.unlock()        for task in taskValues {            task.forceCancel()        }    }    func cancel(url: URL) {        lock.lock()        let task = tasks[url]        lock.unlock()        task?.forceCancel()    }}extension SessionDelegate: URLSessionDataDelegate {    func urlSession(        _ session: URLSession,        dataTask: URLSessionDataTask,        didReceive response: URLResponse,        completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)    {        guard let httpResponse = response as? HTTPURLResponse else {            let error = KingfisherError.responseError(reason: .invalidURLResponse(response: response))            onCompleted(task: dataTask, result: .failure(error))            completionHandler(.cancel)            return        }        let httpStatusCode = httpResponse.statusCode        guard onValidStatusCode.call(httpStatusCode) == true else {            let error = KingfisherError.responseError(reason: .invalidHTTPStatusCode(response: httpResponse))            onCompleted(task: dataTask, result: .failure(error))            completionHandler(.cancel)            return        }        completionHandler(.allow)    }    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {        guard let task = self.task(for: dataTask) else {            return        }                task.didReceiveData(data)                task.callbacks.forEach { callback in            callback.options.onDataReceived?.forEach { sideEffect in                sideEffect.onDataReceived(session, task: task, data: data)            }        }    }    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {        guard let sessionTask = self.task(for: task) else { return }        if let url = task.originalRequest?.url {            let result: Result<URLResponse, KingfisherError>            if let error = error {                result = .failure(KingfisherError.responseError(reason: .URLSessionError(error: error)))            } else if let response = task.response {                result = .success(response)            } else {                result = .failure(KingfisherError.responseError(reason: .noURLResponse(task: sessionTask)))            }            onDownloadingFinished.call((url, result))        }        let result: Result<(Data, URLResponse?), KingfisherError>        if let error = error {            result = .failure(KingfisherError.responseError(reason: .URLSessionError(error: error)))        } else {            if let data = onDidDownloadData.call(sessionTask), let finalData = data {                result = .success((finalData, task.response))            } else {                result = .failure(KingfisherError.responseError(reason: .dataModifyingFailed(task: sessionTask)))            }        }        onCompleted(task: task, result: result)    }    func urlSession(        _ session: URLSession,        didReceive challenge: URLAuthenticationChallenge,        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)    {        onReceiveSessionChallenge.call((session, challenge, completionHandler))    }    func urlSession(        _ session: URLSession,        task: URLSessionTask,        didReceive challenge: URLAuthenticationChallenge,        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)    {        onReceiveSessionTaskChallenge.call((session, task, challenge, completionHandler))    }        func urlSession(        _ session: URLSession,        task: URLSessionTask,        willPerformHTTPRedirection response: HTTPURLResponse,        newRequest request: URLRequest,        completionHandler: @escaping (URLRequest?) -> Void)    {        guard let sessionDataTask = self.task(for: task),              let redirectHandler = Array(sessionDataTask.callbacks).last?.options.redirectHandler else        {            completionHandler(request)            return        }                redirectHandler.handleHTTPRedirection(            for: sessionDataTask,            response: response,            newRequest: request,            completionHandler: completionHandler)    }    private func onCompleted(task: URLSessionTask, result: Result<(Data, URLResponse?), KingfisherError>) {        guard let sessionTask = self.task(for: task) else {            return        }        remove(task)        sessionTask.onTaskDone.call((result, sessionTask.callbacks))    }}
 |