Просмотр исходного кода

Merge pull request #2162 from onevcat/fix/cache-folder-deletion

Try to recreate the cache folder after deleted manually
Wei Wang 2 лет назад
Родитель
Сommit
bb1486a787
2 измененных файлов с 93 добавлено и 3 удалено
  1. 31 3
      Sources/Cache/DiskStorage.swift
  2. 62 0
      Tests/KingfisherTests/ImageCacheTests.swift

+ 31 - 3
Sources/Cache/DiskStorage.swift

@@ -149,9 +149,21 @@ public enum DiskStorage {
             do {
                 try data.write(to: fileURL, options: writeOptions)
             } catch {
-                throw KingfisherError.cacheError(
-                    reason: .cannotCreateCacheFile(fileURL: fileURL, key: key, data: data, error: error)
-                )
+                if error.isFolderMissing {
+                    // The whole cache folder is deleted. Try to recreate it and write file again.
+                    do {
+                        try prepareDirectory()
+                        try data.write(to: fileURL, options: writeOptions)
+                    } catch {
+                        throw KingfisherError.cacheError(
+                            reason: .cannotCreateCacheFile(fileURL: fileURL, key: key, data: data, error: error)
+                        )
+                    }
+                } else {
+                    throw KingfisherError.cacheError(
+                        reason: .cannotCreateCacheFile(fileURL: fileURL, key: key, data: data, error: error)
+                    )
+                }
             }
 
             let now = Date()
@@ -586,3 +598,19 @@ extension DiskStorage {
         }
     }
 }
+
+fileprivate extension Error {
+    var isFolderMissing: Bool {
+        let nsError = self as NSError
+        guard nsError.domain == NSCocoaErrorDomain, nsError.code == 4 else {
+            return false
+        }
+        guard let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? NSError else {
+            return false
+        }
+        guard underlyingError.domain == NSPOSIXErrorDomain, underlyingError.code == 2 else {
+            return false
+        }
+        return true
+    }
+}

+ 62 - 0
Tests/KingfisherTests/ImageCacheTests.swift

@@ -491,6 +491,68 @@ class ImageCacheTests: XCTestCase {
         waitForExpectations(timeout: 3, handler: nil)
     }
     
+    func testDiskCacheStillWorkWhenFolderDeletedExternally() {
+        let exp = expectation(description: #function)
+        let key = testKeys[0]
+        let url = URL(string: key)!
+        
+        let exists = cache.imageCachedType(forKey: url.cacheKey)
+        XCTAssertEqual(exists, .none)
+        
+        cache.store(testImage, forKey: key, toDisk: true) { _ in
+            self.cache.retrieveImage(forKey: key) { result in
+
+                XCTAssertNotNil(result.value?.image)
+                XCTAssertEqual(result.value?.cacheType, .memory)
+
+                self.cache.clearMemoryCache()
+                self.cache.retrieveImage(forKey: key) { result in
+                    XCTAssertNotNil(result.value?.image)
+                    XCTAssertEqual(result.value?.cacheType, .disk)
+                    self.cache.clearMemoryCache()
+                    
+                    try! FileManager.default.removeItem(at: self.cache.diskStorage.directoryURL)
+                    
+                    let exists = self.cache.imageCachedType(forKey: url.cacheKey)
+                    XCTAssertEqual(exists, .none)
+                    
+                    self.cache.store(testImage, forKey: key, toDisk: true) { _ in
+                        self.cache.clearMemoryCache()
+                        let cacheType = self.cache.imageCachedType(forKey: url.cacheKey)
+                        XCTAssertEqual(cacheType, .disk)
+                        exp.fulfill()
+                    }
+                }
+            }
+        }
+        
+        waitForExpectations(timeout: 3, handler: nil)
+    }
+    
+    func testDiskCacheCalculateSizeWhenFolderDeletedExternally() {
+        let exp = expectation(description: #function)
+        
+        let key = testKeys[0]
+        
+        cache.calculateDiskStorageSize { result in
+            XCTAssertEqual(result.value, 0)
+            
+            self.cache.store(testImage, forKey: key, toDisk: true) { _ in
+                self.cache.calculateDiskStorageSize { result in
+                    XCTAssertEqual(result.value, UInt(testImagePNGData.count))
+                    
+                    try! FileManager.default.removeItem(at: self.cache.diskStorage.directoryURL)
+                    self.cache.calculateDiskStorageSize { result in
+                        XCTAssertEqual(result.value, 0)
+                        exp.fulfill()
+                    }
+                    
+                }
+            }
+        }
+        waitForExpectations(timeout: 3, handler: nil)
+    }
+    
     #if swift(>=5.5)
     #if canImport(_Concurrency)
     @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)