123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- #if os(macOS)
- import AppKit
- #else
- import UIKit
- #endif
- typealias DownloadResult = Result<ImageLoadingResult, KingfisherError>
- public struct ImageLoadingResult {
-
- public let image: KFCrossPlatformImage
-
- public let url: URL?
-
- public let originalData: Data
- }
- public struct DownloadTask {
-
-
-
-
-
-
- public let sessionTask: SessionDataTask
-
-
- public let cancelToken: SessionDataTask.CancelToken
-
-
-
-
-
-
-
-
-
-
-
- public func cancel() {
- sessionTask.cancel(token: cancelToken)
- }
- }
- extension DownloadTask {
- enum WrappedTask {
- case download(DownloadTask)
- case dataProviding
- func cancel() {
- switch self {
- case .download(let task): task.cancel()
- case .dataProviding: break
- }
- }
- var value: DownloadTask? {
- switch self {
- case .download(let task): return task
- case .dataProviding: return nil
- }
- }
- }
- }
- open class ImageDownloader {
-
-
- public static let `default` = ImageDownloader(name: "default")
-
-
- open var downloadTimeout: TimeInterval = 15.0
-
-
-
-
-
-
-
- open var trustedHosts: Set<String>?
-
-
-
-
-
-
- open var sessionConfiguration = URLSessionConfiguration.ephemeral {
- didSet {
- session.invalidateAndCancel()
- session = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil)
- }
- }
- open var sessionDelegate: SessionDelegate {
- didSet {
- session.invalidateAndCancel()
- session = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil)
- setupSessionHandler()
- }
- }
-
-
- open var requestsUsePipelining = false
-
- open weak var delegate: ImageDownloaderDelegate?
-
-
-
- open weak var authenticationChallengeResponder: AuthenticationChallengeResponsable?
- private let name: String
- private var session: URLSession
-
-
-
-
- public init(name: String) {
- if name.isEmpty {
- fatalError("[Kingfisher] You should specify a name for the downloader. "
- + "A downloader with empty name is not permitted.")
- }
- self.name = name
- sessionDelegate = SessionDelegate()
- session = URLSession(
- configuration: sessionConfiguration,
- delegate: sessionDelegate,
- delegateQueue: nil)
- authenticationChallengeResponder = self
- setupSessionHandler()
- }
- deinit { session.invalidateAndCancel() }
- private func setupSessionHandler() {
- sessionDelegate.onReceiveSessionChallenge.delegate(on: self) { (self, invoke) in
- self.authenticationChallengeResponder?.downloader(self, didReceive: invoke.1, completionHandler: invoke.2)
- }
- sessionDelegate.onReceiveSessionTaskChallenge.delegate(on: self) { (self, invoke) in
- self.authenticationChallengeResponder?.downloader(
- self, task: invoke.1, didReceive: invoke.2, completionHandler: invoke.3)
- }
- sessionDelegate.onValidStatusCode.delegate(on: self) { (self, code) in
- return (self.delegate ?? self).isValidStatusCode(code, for: self)
- }
- sessionDelegate.onDownloadingFinished.delegate(on: self) { (self, value) in
- let (url, result) = value
- do {
- let value = try result.get()
- self.delegate?.imageDownloader(self, didFinishDownloadingImageForURL: url, with: value, error: nil)
- } catch {
- self.delegate?.imageDownloader(self, didFinishDownloadingImageForURL: url, with: nil, error: error)
- }
- }
- sessionDelegate.onDidDownloadData.delegate(on: self) { (self, task) in
- return (self.delegate ?? self).imageDownloader(self, didDownload: task.mutableData, with: task)
- }
- }
-
- private func createCompletionCallBack(_ completionHandler: ((DownloadResult) -> Void)?) -> Delegate<DownloadResult, Void>? {
- return completionHandler.map { block -> Delegate<DownloadResult, Void> in
- let delegate = Delegate<Result<ImageLoadingResult, KingfisherError>, Void>()
- delegate.delegate(on: self) { (self, callback) in
- block(callback)
- }
- return delegate
- }
- }
- private func createTaskCallback(
- _ completionHandler: ((DownloadResult) -> Void)?,
- options: KingfisherParsedOptionsInfo
- ) -> SessionDataTask.TaskCallback
- {
- return SessionDataTask.TaskCallback(
- onCompleted: createCompletionCallBack(completionHandler),
- options: options
- )
- }
- private func createDownloadContext(
- with url: URL,
- options: KingfisherParsedOptionsInfo,
- done: @escaping ((Result<DownloadingContext, KingfisherError>) -> Void)
- )
- {
- func checkRequestAndDone(r: URLRequest) {
-
-
- guard let url = r.url, !url.absoluteString.isEmpty else {
- done(.failure(KingfisherError.requestError(reason: .invalidURL(request: r))))
- return
- }
- done(.success(DownloadingContext(url: url, request: r, options: options)))
- }
-
- var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: downloadTimeout)
- request.httpShouldUsePipelining = requestsUsePipelining
- if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) , options.lowDataModeSource != nil {
- request.allowsConstrainedNetworkAccess = false
- }
- if let requestModifier = options.requestModifier {
-
- requestModifier.modified(for: request) { result in
- guard let finalRequest = result else {
- done(.failure(KingfisherError.requestError(reason: .emptyRequest)))
- return
- }
- checkRequestAndDone(r: finalRequest)
- }
- } else {
- checkRequestAndDone(r: request)
- }
- }
- private func addDownloadTask(
- context: DownloadingContext,
- callback: SessionDataTask.TaskCallback
- ) -> DownloadTask
- {
-
- let downloadTask: DownloadTask
- if let existingTask = sessionDelegate.task(for: context.url) {
- downloadTask = sessionDelegate.append(existingTask, url: context.url, callback: callback)
- } else {
- let sessionDataTask = session.dataTask(with: context.request)
- sessionDataTask.priority = context.options.downloadPriority
- downloadTask = sessionDelegate.add(sessionDataTask, url: context.url, callback: callback)
- }
- return downloadTask
- }
- private func reportWillDownloadImage(url: URL, request: URLRequest) {
- delegate?.imageDownloader(self, willDownloadImageForURL: url, with: request)
- }
- private func reportDidDownloadImageData(result: Result<(Data, URLResponse?), KingfisherError>, url: URL) {
- var response: URLResponse?
- var err: Error?
- do {
- response = try result.get().1
- } catch {
- err = error
- }
- self.delegate?.imageDownloader(
- self,
- didFinishDownloadingImageForURL: url,
- with: response,
- error: err
- )
- }
- private func reportDidProcessImage(
- result: Result<KFCrossPlatformImage, KingfisherError>, url: URL, response: URLResponse?
- )
- {
- if let image = try? result.get() {
- self.delegate?.imageDownloader(self, didDownload: image, for: url, with: response)
- }
- }
- private func startDownloadTask(
- context: DownloadingContext,
- callback: SessionDataTask.TaskCallback
- ) -> DownloadTask
- {
- let downloadTask = addDownloadTask(context: context, callback: callback)
- let sessionTask = downloadTask.sessionTask
- guard !sessionTask.started else {
- return downloadTask
- }
- sessionTask.onTaskDone.delegate(on: self) { (self, done) in
-
-
- let (result, callbacks) = done
-
- self.reportDidDownloadImageData(result: result, url: context.url)
- switch result {
-
- case .success(let (data, response)):
- let processor = ImageDataProcessor(
- data: data, callbacks: callbacks, processingQueue: context.options.processingQueue
- )
- processor.onImageProcessed.delegate(on: self) { (self, done) in
-
-
-
- let (result, callback) = done
- self.reportDidProcessImage(result: result, url: context.url, response: response)
- let imageResult = result.map { ImageLoadingResult(image: $0, url: context.url, originalData: data) }
- let queue = callback.options.callbackQueue
- queue.execute { callback.onCompleted?.call(imageResult) }
- }
- processor.process()
- case .failure(let error):
- callbacks.forEach { callback in
- let queue = callback.options.callbackQueue
- queue.execute { callback.onCompleted?.call(.failure(error)) }
- }
- }
- }
- reportWillDownloadImage(url: context.url, request: context.request)
- sessionTask.resume()
- return downloadTask
- }
-
-
-
-
-
-
-
-
-
- @discardableResult
- open func downloadImage(
- with url: URL,
- options: KingfisherParsedOptionsInfo,
- completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
- {
- var downloadTask: DownloadTask?
- createDownloadContext(with: url, options: options) { result in
- switch result {
- case .success(let context):
-
-
-
-
-
- downloadTask = self.startDownloadTask(
- context: context,
- callback: self.createTaskCallback(completionHandler, options: options)
- )
- if let modifier = options.requestModifier {
- modifier.onDownloadTaskStarted?(downloadTask)
- }
- case .failure(let error):
- options.callbackQueue.execute {
- completionHandler?(.failure(error))
- }
- }
- }
- return downloadTask
- }
-
-
-
-
-
-
-
-
-
- @discardableResult
- open func downloadImage(
- with url: URL,
- options: KingfisherOptionsInfo? = nil,
- progressBlock: DownloadProgressBlock? = nil,
- completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
- {
- var info = KingfisherParsedOptionsInfo(options)
- if let block = progressBlock {
- info.onDataReceived = (info.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)]
- }
- return downloadImage(
- with: url,
- options: info,
- completionHandler: completionHandler)
- }
-
-
-
-
-
-
-
-
- @discardableResult
- open func downloadImage(
- with url: URL,
- options: KingfisherOptionsInfo? = nil,
- completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
- {
- downloadImage(
- with: url,
- options: KingfisherParsedOptionsInfo(options),
- completionHandler: completionHandler
- )
- }
- }
- extension ImageDownloader {
-
-
-
-
-
-
- public func cancelAll() {
- sessionDelegate.cancelAll()
- }
-
-
-
-
- public func cancel(url: URL) {
- sessionDelegate.cancel(url: url)
- }
- }
- extension ImageDownloader: AuthenticationChallengeResponsable {}
- extension ImageDownloader: ImageDownloaderDelegate {}
- extension ImageDownloader {
- struct DownloadingContext {
- let url: URL
- let request: URLRequest
- let options: KingfisherParsedOptionsInfo
- }
- }
|