Quellcode durchsuchen

Use a stable default cache key for local files

onevcat vor 4 Jahren
Ursprung
Commit
170db84f19

+ 29 - 1
Sources/General/ImageSource/ImageDataProvider.swift

@@ -88,7 +88,7 @@ public struct LocalFileImageDataProvider: ImageDataProvider {
         loadingQueue: ExecutionQueue = .dispatch(DispatchQueue.global(qos: .userInitiated))
     ) {
         self.fileURL = fileURL
-        self.cacheKey = cacheKey ?? fileURL.absoluteString
+        self.cacheKey = cacheKey ?? fileURL.localFileCacheKey
         self.loadingQueue = loadingQueue
     }
 
@@ -109,6 +109,34 @@ public struct LocalFileImageDataProvider: ImageDataProvider {
     }
 }
 
+extension URL {
+    static let localFileCacheKeyPrefix = "kingfisher.local.cacheKey"
+    
+    // The special version of cache key for a local file on disk. Every time the app is reinstalled on the disk,
+    // the system assigns a new container folder to hold the .app (and the extensions, .appex) folder. So the URL for
+    // the same image in bundle might be different.
+    //
+    // This getter only uses the fixed part in the URL (until the bundle name folder) to provide a stable cache key
+    // for the image under the same path inside the bundle.
+    //
+    // See #1825 (https://github.com/onevcat/Kingfisher/issues/1825)
+    var localFileCacheKey: String {
+        var validComponents: [String] = []
+        for part in pathComponents.reversed() {
+            validComponents.append(part)
+            if part.hasSuffix(".app") || part.hasSuffix(".appex") {
+                break
+            }
+        }
+        let fixedPath = "\(Self.localFileCacheKeyPrefix)/\(validComponents.reversed().joined(separator: "/"))"
+        if let q = query {
+            return "\(fixedPath)?\(q)"
+        } else {
+            return fixedPath
+        }
+    }
+}
+
 /// Represents an image data provider for loading image from a given Base64 encoded string.
 public struct Base64ImageDataProvider: ImageDataProvider {
 

+ 1 - 1
Sources/General/ImageSource/Resource.swift

@@ -45,7 +45,7 @@ extension Resource {
     /// `.network` is returned.
     public func convertToSource(overrideCacheKey: String? = nil) -> Source {
         return downloadURL.isFileURL ?
-            .provider(LocalFileImageDataProvider(fileURL: downloadURL, cacheKey: overrideCacheKey ?? cacheKey)) :
+            .provider(LocalFileImageDataProvider(fileURL: downloadURL, cacheKey: overrideCacheKey ?? downloadURL.localFileCacheKey)) :
             .network(ImageResource(downloadURL: downloadURL, cacheKey: overrideCacheKey ?? cacheKey))
     }
 }

+ 20 - 3
Tests/KingfisherTests/ImageDataProviderTests.swift

@@ -25,7 +25,7 @@
 //  THE SOFTWARE.
 
 import XCTest
-import Kingfisher
+@testable import Kingfisher
 
 class ImageDataProviderTests: XCTestCase {
     
@@ -36,7 +36,7 @@ class ImageDataProviderTests: XCTestCase {
         try! testImageData.write(to: fileURL)
         
         let provider = LocalFileImageDataProvider(fileURL: fileURL)
-        XCTAssertEqual(provider.cacheKey, fileURL.absoluteString)
+        XCTAssertEqual(provider.cacheKey, fileURL.localFileCacheKey)
         XCTAssertEqual(provider.fileURL, fileURL)
         
         let exp = expectation(description: #function)
@@ -56,7 +56,7 @@ class ImageDataProviderTests: XCTestCase {
         try! testImageData.write(to: fileURL)
         
         let provider = LocalFileImageDataProvider(fileURL: fileURL, loadingQueue: .mainCurrentOrAsync)
-        XCTAssertEqual(provider.cacheKey, fileURL.absoluteString)
+        XCTAssertEqual(provider.cacheKey, fileURL.localFileCacheKey)
         XCTAssertEqual(provider.fileURL, fileURL)
         
         var called = false
@@ -69,6 +69,23 @@ class ImageDataProviderTests: XCTestCase {
         XCTAssertTrue(called)
     }
     
+    func testLocalFileCacheKey() {
+        let url1 = URL(string: "file:///Users/onevcat/Library/Developer/CoreSimulator/Devices/ABC/data/Containers/Bundle/Application/DEF/Kingfisher-Demo.app/images/kingfisher-1.jpg")!
+        XCTAssertEqual(url1.localFileCacheKey, "\(URL.localFileCacheKeyPrefix)/Kingfisher-Demo.app/images/kingfisher-1.jpg")
+    
+        let url2 = URL(string: "file:///private/var/containers/Bundle/Application/ABC/Kingfisher-Demo.app/images/kingfisher-1.jpg")!
+        XCTAssertEqual(url2.localFileCacheKey, "\(URL.localFileCacheKeyPrefix)/Kingfisher-Demo.app/images/kingfisher-1.jpg")
+        
+        let url3 = URL(string: "file:///private/var/containers/Bundle/Application/ABC/Kingfisher-Demo.app/images/kingfisher-1.jpg?foo=bar")!
+        XCTAssertEqual(url3.localFileCacheKey, "\(URL.localFileCacheKeyPrefix)/Kingfisher-Demo.app/images/kingfisher-1.jpg?foo=bar")
+        
+        let url4 = URL(string: "file:///private/var/containers/Bundle/Application/ABC/Kingfisher-Demo.appex/images/kingfisher-1.jpg")!
+        XCTAssertEqual(url4.localFileCacheKey, "\(URL.localFileCacheKeyPrefix)/Kingfisher-Demo.appex/images/kingfisher-1.jpg")
+        
+        let url5 = URL(string: "file:///private/var/containers/Bundle/Application/ABC/Kingfisher-Demo.other/images/kingfisher-1.jpg")!
+        XCTAssertEqual(url5.localFileCacheKey, "\(URL.localFileCacheKeyPrefix)///private/var/containers/Bundle/Application/ABC/Kingfisher-Demo.other/images/kingfisher-1.jpg")
+    }
+    
     func testBase64ImageDataProvider() {
         let base64String = testImageData.base64EncodedString()
         let provider = Base64ImageDataProvider(base64String: base64String, cacheKey: "123")