Преглед изворни кода

Merge branch 'master' into xcode11

# Conflicts:
#	Sources/Cache/ImageCache.swift
#	Sources/General/Deprecated.swift
onevcat пре 6 година
родитељ
комит
cdb6aab7a0

+ 31 - 13
Sources/Cache/DiskStorage.swift

@@ -119,11 +119,16 @@ public enum DiskStorage {
             config.fileManager.createFile(atPath: fileURL.path, contents: data, attributes: attributes)
         }
 
-        func value(forKey key: String) throws -> T? {
-            return try value(forKey: key, referenceDate: Date(), actuallyLoad: true)
+        func value(forKey key: String, extendingExpiration: ExpirationExtending = .cacheTime) throws -> T? {
+            return try value(forKey: key, referenceDate: Date(), actuallyLoad: true, extendingExpiration: extendingExpiration)
         }
 
-        func value(forKey key: String, referenceDate: Date, actuallyLoad: Bool) throws -> T? {
+        func value(
+            forKey key: String,
+            referenceDate: Date,
+            actuallyLoad: Bool,
+            extendingExpiration: ExpirationExtending) throws -> T?
+        {
             let fileManager = config.fileManager
             let fileURL = cacheFileURL(forKey: key)
             let filePath = fileURL.path
@@ -148,7 +153,7 @@ public enum DiskStorage {
             do {
                 let data = try Data(contentsOf: fileURL)
                 let obj = try T.fromData(data)
-                metaChangingQueue.async { meta.extendExpiration(with: fileManager) }
+                metaChangingQueue.async { meta.extendExpiration(with: fileManager, extendingExpiration: extendingExpiration) }
                 return obj
             } catch {
                 throw KingfisherError.cacheError(reason: .cannotLoadDataFromDisk(url: fileURL, error: error))
@@ -161,7 +166,7 @@ public enum DiskStorage {
 
         func isCached(forKey key: String, referenceDate: Date) -> Bool {
             do {
-                guard let _ = try value(forKey: key, referenceDate: referenceDate, actuallyLoad: false) else {
+                guard let _ = try value(forKey: key, referenceDate: referenceDate, actuallyLoad: false, extendingExpiration: .none) else {
                     return false
                 }
                 return true
@@ -404,19 +409,32 @@ extension DiskStorage {
             return estimatedExpirationDate?.isPast(referenceDate: referenceDate) ?? true
         }
         
-        func extendExpiration(with fileManager: FileManager) {
+        func extendExpiration(with fileManager: FileManager, extendingExpiration: ExpirationExtending) {
             guard let lastAccessDate = lastAccessDate,
                   let lastEstimatedExpiration = estimatedExpirationDate else
             {
                 return
             }
-            
-            let originalExpiration: StorageExpiration =
-                .seconds(lastEstimatedExpiration.timeIntervalSince(lastAccessDate))
-            let attributes: [FileAttributeKey : Any] = [
-                .creationDate: Date().fileAttributeDate,
-                .modificationDate: originalExpiration.estimatedExpirationSinceNow.fileAttributeDate
-            ]
+
+            let attributes: [FileAttributeKey : Any]
+
+            switch extendingExpiration {
+            case .none:
+                // not extending expiration time here
+                return
+            case .cacheTime:
+                let originalExpiration: StorageExpiration =
+                    .seconds(lastEstimatedExpiration.timeIntervalSince(lastAccessDate))
+                attributes = [
+                    .creationDate: Date().fileAttributeDate,
+                    .modificationDate: originalExpiration.estimatedExpirationSinceNow.fileAttributeDate
+                ]
+            case .expirationTime(let expirationTime):
+                attributes = [
+                    .creationDate: Date().fileAttributeDate,
+                    .modificationDate: expirationTime.estimatedExpirationSinceNow.fileAttributeDate
+                ]
+            }
 
             try? fileManager.setAttributes(attributes, ofItemAtPath: url.path)
         }

+ 1 - 1
Sources/Cache/ImageCache.swift

@@ -553,7 +553,7 @@ open class ImageCache {
         loadingQueue.execute {
             do {
                 var image: KFCrossPlatformImage? = nil
-                if let data = try self.diskStorage.value(forKey: computedKey) {
+                if let data = try self.diskStorage.value(forKey: computedKey, extendingExpiration: options.diskCacheAccessExtendingExpiration) {
                     image = options.cacheSerializer.image(with: data, options: options)
                 }
                 callbackQueue.execute { completionHandler(.success(image)) }

+ 2 - 2
Sources/General/Deprecated.swift

@@ -369,10 +369,10 @@ extension ImageCache {
     message: "Use `Result` based `retrieveImageInDiskCache(forKey:options:callbackQueue:completionHandler:)` instead.",
     renamed: "retrieveImageInDiskCache(forKey:options:callbackQueue:completionHandler:)")
     open func retrieveImageInDiskCache(forKey key: String, options: KingfisherOptionsInfo? = nil) -> KFCrossPlatformImage? {
-        let options = options ?? .empty
+        let options = KingfisherParsedOptionsInfo(options ?? .empty)
         let computedKey = key.computedKey(with: options.processor.identifier)
         do {
-            if let data = try diskStorage.value(forKey: computedKey) {
+            if let data = try diskStorage.value(forKey: computedKey, extendingExpiration: options.diskCacheAccessExtendingExpiration) {
                 return options.cacheSerializer.image(with: data, options: options)
             }
         } catch {}

+ 7 - 0
Sources/General/KingfisherOptionsInfo.swift

@@ -215,6 +215,11 @@ public enum KingfisherOptionsInfoItem {
     /// expiration in its config for all items. If set, the `DiskStorage.Backend` will use this associated
     /// value to overwrite the config setting for this caching item.
     case diskCacheExpiration(StorageExpiration)
+
+    /// The expiration extending setting for disk cache. The item expiration time will be incremented by this value after access.
+    /// By default, the underlying `DiskStorage.Backend` uses the initial cache expiration as extending value: .cacheTime.
+    /// To disable extending option at all add diskCacheAccessExtendingExpiration(.none) to options.
+    case diskCacheAccessExtendingExpiration(ExpirationExtending)
     
     /// Decides on which queue the image processing should happen. By default, Kingfisher uses a pre-defined serial
     /// queue to process images. Use this option to change this behavior. For example, specify a `.mainCurrentOrAsync`
@@ -262,6 +267,7 @@ public struct KingfisherParsedOptionsInfo {
     public var memoryCacheExpiration: StorageExpiration? = nil
     public var memoryCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime
     public var diskCacheExpiration: StorageExpiration? = nil
+    public var diskCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime
     public var processingQueue: CallbackQueue? = nil
     public var progressiveJPEG: ImageProgressive? = nil
 
@@ -301,6 +307,7 @@ public struct KingfisherParsedOptionsInfo {
             case .memoryCacheExpiration(let expiration): memoryCacheExpiration = expiration
             case .memoryCacheAccessExtendingExpiration(let expirationExtending): memoryCacheAccessExtendingExpiration = expirationExtending
             case .diskCacheExpiration(let expiration): diskCacheExpiration = expiration
+            case .diskCacheAccessExtendingExpiration(let expirationExtending): diskCacheAccessExtendingExpiration = expirationExtending
             case .processingQueue(let queue): processingQueue = queue
             case .progressiveJPEG(let value): progressiveJPEG = value
             }

+ 22 - 0
Tests/KingfisherTests/DiskStorageTests.swift

@@ -131,6 +131,28 @@ class DiskStorageTests: XCTestCase {
         waitForExpectations(timeout: 2, handler: nil)
     }
 
+    func testNotExtendExpirationByAccessing() {
+
+        let exp = expectation(description: #function)
+        let now = Date()
+        try! storage.store(value: "1", forKey: "1", expiration: .seconds(2))
+        XCTAssertTrue(storage.isCached(forKey: "1"))
+        XCTAssertFalse(storage.isCached(forKey: "1", referenceDate: now.addingTimeInterval(3)))
+
+        delay(1) {
+            let v = try! self.storage.value(forKey: "1", extendingExpiration: .none)
+            XCTAssertNotNil(v)
+            // The meta extending happens on its own queue.
+            self.storage.metaChangingQueue.async {
+                XCTAssertFalse(self.storage.isCached(forKey: "1", referenceDate: now.addingTimeInterval(3)))
+                XCTAssertFalse(self.storage.isCached(forKey: "1", referenceDate: now.addingTimeInterval(10)))
+                exp.fulfill()
+            }
+        }
+
+        waitForExpectations(timeout: 2, handler: nil)
+    }
+
     func testRemoveExpired() {
 
         let expiration = StorageExpiration.seconds(1)

+ 69 - 2
Tests/KingfisherTests/ImageViewExtensionTests.swift

@@ -653,7 +653,7 @@ class ImageViewExtensionTests: XCTestCase {
         waitForExpectations(timeout: 3, handler: nil)
     }
     
-    func testImageCacheExtendingExpirationTask() {
+    func testMemoryImageCacheExtendingExpirationTask() {
         let exp = expectation(description: #function)
         let url = testURLs[0]
         stub(url, data: testImageData)
@@ -688,7 +688,7 @@ class ImageViewExtensionTests: XCTestCase {
         waitForExpectations(timeout: 3, handler: nil)
     }
     
-    func testImageCacheNotExtendingExpirationTask() {
+    func testMemoryImageCacheNotExtendingExpirationTask() {
         let exp = expectation(description: #function)
         let url = testURLs[0]
         stub(url, data: testImageData)
@@ -720,6 +720,73 @@ class ImageViewExtensionTests: XCTestCase {
         
         waitForExpectations(timeout: 3, handler: nil)
     }
+
+    func testDiskImageCacheExtendingExpirationTask() {
+        let exp = expectation(description: #function)
+        let url = testURLs[0]
+        stub(url, data: testImageData)
+
+        let options: KingfisherOptionsInfo = [.memoryCacheExpiration(.expired),
+                                              .diskCacheExpiration(.seconds(2)),
+                                              .diskCacheAccessExtendingExpiration(.expirationTime(.seconds(100)))]
+
+        imageView.kf.setImage(with: url, options: options) { result in
+            XCTAssertNotNil(result.value?.image)
+            XCTAssertTrue(result.value!.cacheType == .none)
+
+            delay(1, block: {
+                self.imageView.kf.setImage(with: url, options: options) { result in
+                    XCTAssertNotNil(result.value?.image)
+                    XCTAssertTrue(result.value!.cacheType == .disk)
+                    delay(2, block: {
+                        self.imageView.kf.setImage(with: url, options: options) { result in
+                            XCTAssertNotNil(result.value?.image)
+                            XCTAssertTrue(result.value!.cacheType == .disk)
+
+                            exp.fulfill()
+                        }
+                    })
+                }
+            })
+        }
+
+        waitForExpectations(timeout: 5, handler: nil)
+    }
+
+    func testDiskImageCacheNotExtendingExpirationTask() {
+        let exp = expectation(description: #function)
+        let url = testURLs[0]
+        stub(url, data: testImageData)
+
+        let options: KingfisherOptionsInfo = [.memoryCacheExpiration(.expired),
+                                              .diskCacheExpiration(.seconds(2)),
+                                              .diskCacheAccessExtendingExpiration(.none)]
+
+        imageView.kf.setImage(with: url, options: options) { result in
+            XCTAssertNotNil(result.value?.image)
+            XCTAssertTrue(result.value!.cacheType == .none)
+
+            delay(1, block: {
+                self.imageView.kf.setImage(with: url, options: options) { result in
+                    XCTAssertNotNil(result.value?.image)
+                    XCTAssertTrue(result.value!.cacheType == .disk)
+
+                        delay(2, block: {
+                            self.imageView.kf.setImage(with: url, options: options) { result in
+                                XCTAssertNotNil(result.value?.image)
+                                XCTAssertTrue(result.value!.cacheType == .none)
+
+                                exp.fulfill()
+                            }
+                        })
+                }
+            })
+        }
+
+        waitForExpectations(timeout: 5, handler: nil)
+    }
+
+
 }
 
 extension KFCrossPlatformView: Placeholder {}