added iOS source code
[wl-app.git] / iOS / Pods / Kingfisher / Sources / KingfisherManager.swift
1 //
2 //  KingfisherManager.swift
3 //  Kingfisher
4 //
5 //  Created by Wei Wang on 15/4/6.
6 //
7 //  Copyright (c) 2018 Wei Wang <onevcat@gmail.com>
8 //
9 //  Permission is hereby granted, free of charge, to any person obtaining a copy
10 //  of this software and associated documentation files (the "Software"), to deal
11 //  in the Software without restriction, including without limitation the rights
12 //  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 //  copies of the Software, and to permit persons to whom the Software is
14 //  furnished to do so, subject to the following conditions:
15 //
16 //  The above copyright notice and this permission notice shall be included in
17 //  all copies or substantial portions of the Software.
18 //
19 //  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 //  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 //  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 //  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 //  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 //  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 //  THE SOFTWARE.
26
27 #if os(macOS)
28 import AppKit
29 #else
30 import UIKit
31 #endif
32
33 public typealias DownloadProgressBlock = ((_ receivedSize: Int64, _ totalSize: Int64) -> Void)
34 public typealias CompletionHandler = ((_ image: Image?, _ error: NSError?, _ cacheType: CacheType, _ imageURL: URL?) -> Void)
35
36 /// RetrieveImageTask represents a task of image retrieving process.
37 /// It contains an async task of getting image from disk and from network.
38 public final class RetrieveImageTask {
39     
40     public static let empty = RetrieveImageTask()
41     
42     // If task is canceled before the download task started (which means the `downloadTask` is nil),
43     // the download task should not begin.
44     var cancelledBeforeDownloadStarting: Bool = false
45     
46     /// The network retrieve task in this image task.
47     public var downloadTask: RetrieveImageDownloadTask?
48     
49     /**
50     Cancel current task. If this task is already done, do nothing.
51     */
52     public func cancel() {
53         if let downloadTask = downloadTask {
54             downloadTask.cancel()
55         } else {
56             cancelledBeforeDownloadStarting = true
57         }
58     }
59 }
60
61 /// Error domain of Kingfisher
62 public let KingfisherErrorDomain = "com.onevcat.Kingfisher.Error"
63
64 /// Main manager class of Kingfisher. It connects Kingfisher downloader and cache.
65 /// You can use this class to retrieve an image via a specified URL from web or cache.
66 public class KingfisherManager {
67     
68     /// Shared manager used by the extensions across Kingfisher.
69     public static let shared = KingfisherManager()
70     
71     /// Cache used by this manager
72     public var cache: ImageCache
73     
74     /// Downloader used by this manager
75     public var downloader: ImageDownloader
76     
77     /// Default options used by the manager. This option will be used in 
78     /// Kingfisher manager related methods, including all image view and 
79     /// button extension methods. You can also passing the options per image by 
80     /// sending an `options` parameter to Kingfisher's APIs, the per image option 
81     /// will overwrite the default ones if exist.
82     ///
83     /// - Note: This option will not be applied to independent using of `ImageDownloader` or `ImageCache`.
84     public var defaultOptions = KingfisherEmptyOptionsInfo
85     
86     var currentDefaultOptions: KingfisherOptionsInfo {
87         return [.downloader(downloader), .targetCache(cache)] + defaultOptions
88     }
89     
90     convenience init() {
91         self.init(downloader: .default, cache: .default)
92     }
93     
94     init(downloader: ImageDownloader, cache: ImageCache) {
95         self.downloader = downloader
96         self.cache = cache
97     }
98     
99     /**
100     Get an image with resource.
101     If KingfisherOptions.None is used as `options`, Kingfisher will seek the image in memory and disk first.
102     If not found, it will download the image at `resource.downloadURL` and cache it with `resource.cacheKey`.
103     These default behaviors could be adjusted by passing different options. See `KingfisherOptions` for more.
104     
105     - parameter resource:          Resource object contains information such as `cacheKey` and `downloadURL`.
106     - parameter options:           A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
107     - parameter progressBlock:     Called every time downloaded data changed. This could be used as a progress UI.
108     - parameter completionHandler: Called when the whole retrieving process finished.
109     
110     - returns: A `RetrieveImageTask` task object. You can use this object to cancel the task.
111     */
112     @discardableResult
113     public func retrieveImage(with resource: Resource,
114         options: KingfisherOptionsInfo?,
115         progressBlock: DownloadProgressBlock?,
116         completionHandler: CompletionHandler?) -> RetrieveImageTask
117     {
118         let task = RetrieveImageTask()
119         let options = currentDefaultOptions + (options ?? KingfisherEmptyOptionsInfo)
120         if options.forceRefresh {
121             _ = downloadAndCacheImage(
122                 with: resource.downloadURL,
123                 forKey: resource.cacheKey,
124                 retrieveImageTask: task,
125                 progressBlock: progressBlock,
126                 completionHandler: completionHandler,
127                 options: options)
128         } else {
129             tryToRetrieveImageFromCache(
130                 forKey: resource.cacheKey,
131                 with: resource.downloadURL,
132                 retrieveImageTask: task,
133                 progressBlock: progressBlock,
134                 completionHandler: completionHandler,
135                 options: options)
136         }
137         
138         return task
139     }
140
141     @discardableResult
142     func downloadAndCacheImage(with url: URL,
143                              forKey key: String,
144                       retrieveImageTask: RetrieveImageTask,
145                           progressBlock: DownloadProgressBlock?,
146                       completionHandler: CompletionHandler?,
147                                 options: KingfisherOptionsInfo) -> RetrieveImageDownloadTask?
148     {
149         let downloader = options.downloader
150         return downloader.downloadImage(with: url, retrieveImageTask: retrieveImageTask, options: options,
151             progressBlock: { receivedSize, totalSize in
152                 progressBlock?(receivedSize, totalSize)
153             },
154             completionHandler: { image, error, imageURL, originalData in
155
156                 let targetCache = options.targetCache
157                 if let error = error, error.code == KingfisherError.notModified.rawValue {
158                     // Not modified. Try to find the image from cache.
159                     // (The image should be in cache. It should be guaranteed by the framework users.)
160                     targetCache.retrieveImage(forKey: key, options: options, completionHandler: { (cacheImage, cacheType) -> Void in
161                         completionHandler?(cacheImage, nil, cacheType, url)
162                     })
163                     return
164                 }
165                 
166                 if let image = image, let originalData = originalData {
167                     targetCache.store(image,
168                                       original: originalData,
169                                       forKey: key,
170                                       processorIdentifier:options.processor.identifier,
171                                       cacheSerializer: options.cacheSerializer,
172                                       toDisk: !options.cacheMemoryOnly,
173                                       completionHandler: nil)
174                     if options.cacheOriginalImage && options.processor != DefaultImageProcessor.default {
175                         let originalCache = options.originalCache
176                         let defaultProcessor = DefaultImageProcessor.default
177                         if let originalImage = defaultProcessor.process(item: .data(originalData), options: options) {
178                             originalCache.store(originalImage,
179                                               original: originalData,
180                                               forKey: key,
181                                               processorIdentifier: defaultProcessor.identifier,
182                                               cacheSerializer: options.cacheSerializer,
183                                               toDisk: !options.cacheMemoryOnly,
184                                               completionHandler: nil)
185                         }
186                     }
187                 }
188
189                 completionHandler?(image, error, .none, url)
190
191             })
192     }
193     
194     func tryToRetrieveImageFromCache(forKey key: String,
195                                        with url: URL,
196                               retrieveImageTask: RetrieveImageTask,
197                                   progressBlock: DownloadProgressBlock?,
198                               completionHandler: CompletionHandler?,
199                                         options: KingfisherOptionsInfo)
200     {
201
202         let diskTaskCompletionHandler: CompletionHandler = { (image, error, cacheType, imageURL) -> Void in
203             completionHandler?(image, error, cacheType, imageURL)
204         }
205         
206         func handleNoCache() {
207             if options.onlyFromCache {
208                 let error = NSError(domain: KingfisherErrorDomain, code: KingfisherError.notCached.rawValue, userInfo: nil)
209                 diskTaskCompletionHandler(nil, error, .none, url)
210                 return
211             }
212             self.downloadAndCacheImage(
213                 with: url,
214                 forKey: key,
215                 retrieveImageTask: retrieveImageTask,
216                 progressBlock: progressBlock,
217                 completionHandler: diskTaskCompletionHandler,
218                 options: options)
219             
220         }
221         
222         let targetCache = options.targetCache
223         // First, try to get the exactly image from cache
224         targetCache.retrieveImage(forKey: key, options: options) { image, cacheType in
225             // If found, we could finish now.
226             if image != nil {
227                 diskTaskCompletionHandler(image, nil, cacheType, url)
228                 return
229             }
230             
231             // If not found, and we are using a default processor, download it!
232             let processor = options.processor
233             guard processor != DefaultImageProcessor.default else {
234                 handleNoCache()
235                 return
236             }
237             
238             // If processor is not the default one, we have a chance to check whether
239             // the original image is already in cache.
240             let originalCache = options.originalCache
241             let optionsWithoutProcessor = options.removeAllMatchesIgnoringAssociatedValue(.processor(processor))
242             originalCache.retrieveImage(forKey: key, options: optionsWithoutProcessor) { image, cacheType in
243                 // If we found the original image, there is no need to download it again.
244                 // We could just apply processor to it now.
245                 guard let image = image else {
246                     handleNoCache()
247                     return
248                 }
249                 
250                 guard let processedImage = processor.process(item: .image(image), options: options) else {
251                     diskTaskCompletionHandler(nil, nil, .none, url)
252                     return
253                 }
254                 targetCache.store(processedImage,
255                                   original: nil,
256                                   forKey: key,
257                                   processorIdentifier:options.processor.identifier,
258                                   cacheSerializer: options.cacheSerializer,
259                                   toDisk: !options.cacheMemoryOnly,
260                                   completionHandler: nil)
261                 diskTaskCompletionHandler(processedImage, nil, .none, url)
262             }
263         }
264     }
265 }