Przeglądaj źródła

Add context based requesting

onevcat 6 lat temu
rodzic
commit
b60d6971b3

+ 6 - 0
Sources/General/KingfisherError.swift

@@ -176,6 +176,9 @@ public enum KingfisherError: Error {
 
         /// An error happens during getting data from an `ImageDataProvider`. Code 5003.
         case dataProviderError(provider: ImageDataProvider, error: Error)
+
+        /// Code 5004.
+        case alternativeSourcesFailed([PropagationError])
     }
 
     // MARK: Member Cases
@@ -388,6 +391,8 @@ extension KingfisherError.ImageSettingErrorReason {
             }
         case .dataProviderError(let provider, let error):
             return "Image data provider fails to provide data. Provider: \(provider), error: \(error)"
+        case .alternativeSourcesFailed(let errors):
+            return "Image setting from alternaive sources failed: \(errors)"
         }
     }
     
@@ -396,6 +401,7 @@ extension KingfisherError.ImageSettingErrorReason {
         case .emptySource: return 5001
         case .notCurrentSourceTask: return 5002
         case .dataProviderError: return 5003
+        case .alternativeSourcesFailed: return 5004
         }
     }
 }

+ 88 - 28
Sources/General/KingfisherManager.swift

@@ -45,6 +45,8 @@ public struct RetrieveImageResult {
 
     /// The `Source` from which the retrieve task begins.
     public let source: Source
+
+    public let originalSource: Source
 }
 
 /// Main manager class of Kingfisher. It connects Kingfisher downloader and cache,
@@ -171,19 +173,37 @@ public class KingfisherManager {
         options: KingfisherParsedOptionsInfo,
         completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
     {
-        let context = RetrievingContext(options: options)
-        return retrieveImage(
-            with: source,
-            context: context)
-        {
-            result in
+        var context = RetrievingContext(options: options, originalSource: source)
+
+        func handler(result: (Result<RetrieveImageResult, KingfisherError>)) -> Void {
             switch result {
             case .success:
                 completionHandler?(result)
             case .failure(let error):
-                completionHandler?(result)
+                if let nextSource = context.popAlternativeSource() {
+                    context.appendError(error, to: source)
+                    _ = self.retrieveImage(with: nextSource, context: context, completionHandler: handler)
+
+                } else {
+                    // No other alternative source. Finish with error.
+                    if context.propagationErrors.isEmpty {
+                        completionHandler?(.failure(error))
+                    } else {
+                        context.appendError(error, to: source)
+                        let finalError = KingfisherError.imageSettingError(
+                            reason: .alternativeSourcesFailed(context.propagationErrors)
+                        )
+                        completionHandler?(.failure(finalError))
+                    }
+                }
             }
         }
+
+        return retrieveImage(
+            with: source,
+            context: context,
+            completionHandler: handler)
+
     }
     
     private func retrieveImage(
@@ -195,13 +215,13 @@ public class KingfisherManager {
         if options.forceRefresh {
             return loadAndCacheImage(
                 source: source,
-                options: options,
+                context: context,
                 completionHandler: completionHandler)?.value
             
         } else {
             let loadedFromCache = retrieveImageFromCache(
                 source: source,
-                options: options,
+                context: context,
                 completionHandler: completionHandler)
             
             if loadedFromCache {
@@ -216,7 +236,7 @@ public class KingfisherManager {
             
             return loadAndCacheImage(
                 source: source,
-                options: options,
+                context: context,
                 completionHandler: completionHandler)?.value
         }
     }
@@ -261,9 +281,10 @@ public class KingfisherManager {
     @discardableResult
     func loadAndCacheImage(
         source: Source,
-        options: KingfisherParsedOptionsInfo,
+        context: RetrievingContext,
         completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask.WrappedTask?
     {
+        let options = context.options
         func cacheImage(_ result: Result<ImageLoadingResult, KingfisherError>)
         {
             switch result {
@@ -279,7 +300,12 @@ public class KingfisherManager {
                 {
                     _ in
                     if options.waitForCache {
-                        let result = RetrieveImageResult(image: value.image, cacheType: .none, source: source)
+                        let result = RetrieveImageResult(
+                            image: value.image,
+                            cacheType: .none,
+                            source: source,
+                            originalSource: context.originalSource
+                        )
                         completionHandler?(.success(result))
                     }
                 }
@@ -297,7 +323,12 @@ public class KingfisherManager {
                 }
 
                 if !options.waitForCache {
-                    let result = RetrieveImageResult(image: value.image, cacheType: .none, source: source)
+                    let result = RetrieveImageResult(
+                        image: value.image,
+                        cacheType: .none,
+                        source: source,
+                        originalSource: context.originalSource
+                    )
                     completionHandler?(.success(result))
                 }
                 
@@ -344,9 +375,10 @@ public class KingfisherManager {
     ///    back to cache for later use.
     func retrieveImageFromCache(
         source: Source,
-        options: KingfisherParsedOptionsInfo,
+        context: RetrievingContext,
         completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> Bool
     {
+        let options = context.options
         // 1. Check whether the image was already in target cache. If so, just get it.
         let targetCache = options.targetCache ?? cache
         let key = source.cacheKey
@@ -364,7 +396,12 @@ public class KingfisherManager {
                             let value: Result<RetrieveImageResult, KingfisherError>
                             if let image = cacheResult.image {
                                 value = result.map {
-                                    RetrieveImageResult(image: image, cacheType: $0.cacheType, source: source)
+                                    RetrieveImageResult(
+                                        image: image,
+                                        cacheType: $0.cacheType,
+                                        source: source,
+                                        originalSource: context.originalSource
+                                    )
                                 }
                             } else {
                                 value = .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))
@@ -424,13 +461,22 @@ public class KingfisherManager {
                                 _ in
                                 if options.waitForCache {
                                     let value = RetrieveImageResult(
-                                        image: processedImage, cacheType: .none, source: source)
+                                        image: processedImage,
+                                        cacheType: .none,
+                                        source: source,
+                                        originalSource: context.originalSource
+                                    )
                                     options.callbackQueue.execute { completionHandler?(.success(value)) }
                                 }
                             }
 
                             if !options.waitForCache {
-                                let value = RetrieveImageResult(image: processedImage, cacheType: .none, source: source)
+                                let value = RetrieveImageResult(
+                                    image: processedImage,
+                                    cacheType: .none,
+                                    source: source,
+                                    originalSource: context.originalSource
+                                )
                                 options.callbackQueue.execute { completionHandler?(.success(value)) }
                             }
                         }
@@ -453,17 +499,31 @@ public class KingfisherManager {
     }
 }
 
-extension KingfisherManager {
-    struct RetrievingContext {
-        var options: KingfisherParsedOptionsInfo
-        
-        mutating func popAlternativeSource() -> Source? {
-            guard var alternativeSources = options.alternativeSources, !alternativeSources.isEmpty else {
-                return nil
-            }
-            let nextSource = alternativeSources.removeFirst()
-            options.alternativeSources = alternativeSources
-            return nextSource
+public struct PropagationError {
+    let source: Source
+    let error: KingfisherError
+}
+
+struct RetrievingContext {
+
+    var options: KingfisherParsedOptionsInfo
+
+    let originalSource: Source
+    var propagationErrors: [PropagationError] = []
+
+    mutating func popAlternativeSource() -> Source? {
+        guard var alternativeSources = options.alternativeSources, !alternativeSources.isEmpty else {
+            return nil
         }
+        let nextSource = alternativeSources.removeFirst()
+        options.alternativeSources = alternativeSources
+        return nextSource
+    }
+
+    @discardableResult
+    mutating func appendError(_ error: KingfisherError, to source: Source) -> [PropagationError] {
+        let item = PropagationError(source: source, error: error)
+        propagationErrors.append(item)
+        return propagationErrors
     }
 }

+ 6 - 2
Sources/Networking/ImagePrefetcher.swift

@@ -266,9 +266,12 @@ public class ImagePrefetcher: CustomStringConvertible {
 
         var downloadTask: DownloadTask.WrappedTask?
         ImagePrefetcher.requestingQueue.sync {
+            let context = RetrievingContext(
+                options: optionsInfo, originalSource: source
+            )
             downloadTask = manager.loadAndCacheImage(
                 source: source,
-                options: optionsInfo,
+                context: context,
                 completionHandler: downloadTaskCompletionHandler)
         }
 
@@ -299,9 +302,10 @@ public class ImagePrefetcher: CustomStringConvertible {
             append(cached: source)
         case .disk:
             if optionsInfo.alsoPrefetchToMemory {
+                let context = RetrievingContext(options: optionsInfo, originalSource: source)
                 _ = manager.retrieveImageFromCache(
                     source: source,
-                    options: optionsInfo)
+                    context: context)
                 {
                     _ in
                     self.append(cached: source)

+ 8 - 3
Tests/KingfisherTests/KingfisherManagerTests.swift

@@ -260,7 +260,9 @@ class KingfisherManagerTests: XCTestCase {
         let manager = self.manager!
         let p = SimpleProcessor()
         let options = KingfisherParsedOptionsInfo([.processor(p), .cacheOriginalImage, .waitForCache])
-        manager.loadAndCacheImage(source: .network(url), options: options) { result in
+        let source = Source.network(url)
+        let context = RetrievingContext(options: options, originalSource: source)
+        manager.loadAndCacheImage(source: .network(url), context: context) { result in
             
             var imageCached = manager.cache.imageCachedType(forKey: url.cacheKey, processorIdentifier: p.identifier)
             var originalCached = manager.cache.imageCachedType(forKey: url.cacheKey)
@@ -289,7 +291,9 @@ class KingfisherManagerTests: XCTestCase {
 
         let p = SimpleProcessor()
         let options = KingfisherParsedOptionsInfo([.processor(p), .waitForCache])
-        manager.loadAndCacheImage(source: .network(url), options: options) {
+        let source = Source.network(url)
+        let context = RetrievingContext(options: options, originalSource: source)
+        manager.loadAndCacheImage(source: .network(url), context: context) {
             result in
             var imageCached = self.manager.cache.imageCachedType(forKey: url.cacheKey, processorIdentifier: p.identifier)
             var originalCached = self.manager.cache.imageCachedType(forKey: url.cacheKey)
@@ -694,7 +698,8 @@ class KingfisherManagerTests: XCTestCase {
             .network(URL(string: "2")!)
         ]
         let info = KingfisherParsedOptionsInfo([.alternativeSources(allSources)])
-        var context = KingfisherManager.RetrievingContext(options: info)
+        var context = RetrievingContext(
+            options: info, originalSource: .network(URL(string: "0")!), propagationErrors: [])
 
         let source1 = context.popAlternativeSource()
         XCTAssertNotNil(source1)