Skip to content

Commit

Permalink
Merge pull request #2162 from onevcat/fix/cache-folder-deletion
Browse files Browse the repository at this point in the history
Try to recreate the cache folder after deleted manually
  • Loading branch information
onevcat authored Oct 29, 2023
2 parents 46530b9 + bcc4d1f commit bb1486a
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 3 deletions.
34 changes: 31 additions & 3 deletions Sources/Cache/DiskStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 changes: 62 additions & 0 deletions Tests/KingfisherTests/ImageCacheTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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, *)
Expand Down

0 comments on commit bb1486a

Please sign in to comment.