Skip to content

Commit

Permalink
Merge pull request onevcat#1287 from makesource/feature/enable-disk-c…
Browse files Browse the repository at this point in the history
…ache-ttl-extending-options

Add options regarding disk cache TTL extending
  • Loading branch information
onevcat authored Sep 24, 2019
2 parents baffaca + ad420ec commit b87b535
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 18 deletions.
44 changes: 31 additions & 13 deletions Sources/Cache/DiskStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Cache/ImageCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ open class ImageCache {
loadingQueue.execute {
do {
var image: Image? = 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)) }
Expand Down
4 changes: 2 additions & 2 deletions Sources/General/Deprecated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) -> Image? {
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 {}
Expand Down
7 changes: 7 additions & 0 deletions Sources/General/KingfisherOptionsInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,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`
Expand Down Expand Up @@ -259,6 +264,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

Expand Down Expand Up @@ -298,6 +304,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
}
Expand Down
22 changes: 22 additions & 0 deletions Tests/KingfisherTests/DiskStorageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
71 changes: 69 additions & 2 deletions Tests/KingfisherTests/ImageViewExtensionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 View: Placeholder {}

0 comments on commit b87b535

Please sign in to comment.