Browse Source

Remove Cache library

async
Shadowfacts 9 months ago
parent
commit
2761c05a01
  1. 3
      .gitmodules
  2. 1
      Cache
  3. 26
      Tusker.xcodeproj/project.pbxproj
  4. 3
      Tusker.xcworkspace/contents.xcworkspacedata
  5. 62
      Tusker/Caching/Cache.swift
  6. 30
      Tusker/Caching/CacheExpiry.swift
  7. 71
      Tusker/Caching/CachingDiskStorage.swift
  8. 143
      Tusker/Caching/DiskCache.swift
  9. 2
      Tusker/Caching/ImageCache.swift
  10. 14
      Tusker/Caching/ImageDataCache.swift
  11. 69
      Tusker/Caching/MemoryCache.swift

3
.gitmodules

@ -1,6 +1,3 @@
[submodule "Cache"]
path = Cache
url = git@github.com:hyperoslo/Cache.git
[submodule "Gifu"]
path = Gifu
url = git://github.com/kaishin/Gifu.git

1
Cache

@ -1 +0,0 @@
Subproject commit 8c42c575cf28b2ff0e780c9728721e9a8891c92e

26
Tusker.xcodeproj/project.pbxproj

@ -14,8 +14,6 @@
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450531E22B0097E00100BA2 /* Timline+UI.swift */; };
04586B4122B2FFB10021BD04 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04586B4022B2FFB10021BD04 /* PreferencesView.swift */; };
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04586B4222B301470021BD04 /* AppearancePrefsView.swift */; };
0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0461A38F2163CBAE00C0A807 /* Cache.framework */; };
0461A3912163CBAE00C0A807 /* Cache.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0461A38F2163CBAE00C0A807 /* Cache.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04D14BAE22B34A2800642648 /* GalleryViewController.swift */; };
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
@ -113,7 +111,6 @@
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2425217ABF63005076CC /* UserActivityType.swift */; };
D62FF04823D7CDD700909D6E /* AttributedStringHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */; };
D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C4F25B3765B00B27539 /* ImageDataCache.swift */; };
D6311C5625B4CEA000B27539 /* CachingDiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C5525B4CEA000B27539 /* CachingDiskStorage.swift */; };
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Helpers.swift */; };
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
D63569E023908A8D003DD353 /* StatusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60A4FFB238B726A008AC647 /* StatusState.swift */; };
@ -228,6 +225,9 @@
D6A5BB2F23BBAC97003BF21D /* DelegatingResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A5BB2E23BBAC97003BF21D /* DelegatingResponse.swift */; };
D6A5BB3123BBAD87003BF21D /* JSONResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A5BB3023BBAD87003BF21D /* JSONResponse.swift */; };
D6A6C10525B6138A00298D0F /* StatusTablePrefetching.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A6C10425B6138A00298D0F /* StatusTablePrefetching.swift */; };
D6A6C10F25B62D2400298D0F /* DiskCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A6C10E25B62D2400298D0F /* DiskCache.swift */; };
D6A6C11525B62E9700298D0F /* CacheExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A6C11425B62E9700298D0F /* CacheExpiry.swift */; };
D6A6C11B25B63CEE00298D0F /* MemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A6C11A25B63CEE00298D0F /* MemoryCache.swift */; };
D6AC956723C4347E008C9946 /* MainSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */; };
D6ACE1AC240C3BAD004EA8E2 /* Ambassador.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D65F613023AE99E000F3CFD3 /* Ambassador.framework */; };
D6ACE1AD240C3BAD004EA8E2 /* Embassy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D65F612D23AE990C00F3CFD3 /* Embassy.framework */; };
@ -304,7 +304,6 @@
D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EE63FA2551F7F60065485C /* StatusCollapseButton.swift */; };
D6F0B12B24A3071C001E48C3 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F0B12A24A3071C001E48C3 /* MainSplitViewController.swift */; };
D6F0B17524A3A1AA001E48C3 /* MainSidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F0B17424A3A1AA001E48C3 /* MainSidebarViewController.swift */; };
D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F1F84C2193B56E00F5FE67 /* Cache.swift */; };
D6F2E965249E8BFD005846BB /* CrashReporterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F2E963249E8BFD005846BB /* CrashReporterViewController.swift */; };
D6F2E966249E8BFD005846BB /* CrashReporterViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6F2E964249E8BFD005846BB /* CrashReporterViewController.xib */; };
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; };
@ -358,7 +357,6 @@
files = (
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
D6BC874621961F73006163F1 /* Gifu.framework in Embed Frameworks */,
0461A3912163CBAE00C0A807 /* Cache.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@ -472,7 +470,6 @@
D62D2425217ABF63005076CC /* UserActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityType.swift; sourceTree = "<group>"; };
D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringHelperTests.swift; sourceTree = "<group>"; };
D6311C4F25B3765B00B27539 /* ImageDataCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDataCache.swift; sourceTree = "<group>"; };
D6311C5525B4CEA000B27539 /* CachingDiskStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachingDiskStorage.swift; sourceTree = "<group>"; };
D6333B362137838300CE884A /* AttributedString+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Helpers.swift"; sourceTree = "<group>"; };
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; };
D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesNavigationController.swift; sourceTree = "<group>"; };
@ -589,6 +586,9 @@
D6A5BB2E23BBAC97003BF21D /* DelegatingResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatingResponse.swift; sourceTree = "<group>"; };
D6A5BB3023BBAD87003BF21D /* JSONResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONResponse.swift; sourceTree = "<group>"; };
D6A6C10425B6138A00298D0F /* StatusTablePrefetching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTablePrefetching.swift; sourceTree = "<group>"; };
D6A6C10E25B62D2400298D0F /* DiskCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskCache.swift; sourceTree = "<group>"; };
D6A6C11425B62E9700298D0F /* CacheExpiry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheExpiry.swift; sourceTree = "<group>"; };
D6A6C11A25B63CEE00298D0F /* MemoryCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryCache.swift; sourceTree = "<group>"; };
D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSceneDelegate.swift; sourceTree = "<group>"; };
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivity+Types.swift"; sourceTree = "<group>"; };
D6AEBB402321642700E5038B /* SendMesasgeActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMesasgeActivity.swift; sourceTree = "<group>"; };
@ -668,7 +668,6 @@
D6EE63FA2551F7F60065485C /* StatusCollapseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCollapseButton.swift; sourceTree = "<group>"; };
D6F0B12A24A3071C001E48C3 /* MainSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitViewController.swift; sourceTree = "<group>"; };
D6F0B17424A3A1AA001E48C3 /* MainSidebarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSidebarViewController.swift; sourceTree = "<group>"; };
D6F1F84C2193B56E00F5FE67 /* Cache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = "<group>"; };
D6F2E963249E8BFD005846BB /* CrashReporterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporterViewController.swift; sourceTree = "<group>"; };
D6F2E964249E8BFD005846BB /* CrashReporterViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CrashReporterViewController.xib; sourceTree = "<group>"; };
D6F953EF21251A2900CF0F2B /* MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonController.swift; sourceTree = "<group>"; };
@ -699,7 +698,6 @@
D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */,
D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */,
D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */,
0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */,
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1498,10 +1496,11 @@
D6F1F84E2193B9BE00F5FE67 /* Caching */ = {
isa = PBXGroup;
children = (
D6F1F84C2193B56E00F5FE67 /* Cache.swift */,
04DACE8D212CC7CC009840C4 /* ImageCache.swift */,
D6A6C11425B62E9700298D0F /* CacheExpiry.swift */,
D6A6C10E25B62D2400298D0F /* DiskCache.swift */,
D6A6C11A25B63CEE00298D0F /* MemoryCache.swift */,
D6311C4F25B3765B00B27539 /* ImageDataCache.swift */,
D6311C5525B4CEA000B27539 /* CachingDiskStorage.swift */,
04DACE8D212CC7CC009840C4 /* ImageCache.swift */,
);
path = Caching;
sourceTree = "<group>";
@ -1880,7 +1879,6 @@
D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */,
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */,
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */,
D6311C5625B4CEA000B27539 /* CachingDiskStorage.swift in Sources */,
D62275AA24F1E01C00B82A16 /* ComposeTextView.swift in Sources */,
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */,
D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */,
@ -1921,6 +1919,7 @@
D677284A24ECBDF400C732D3 /* ComposeCurrentAccount.swift in Sources */,
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */,
D6B30E09254BAF63009CAEE5 /* ImageGrayscalifier.swift in Sources */,
D6A6C10F25B62D2400298D0F /* DiskCache.swift in Sources */,
D6E426812532814100C02E1C /* MaybeLazyStack.swift in Sources */,
D6B81F3C2560365300F6E31D /* RefreshableViewController.swift in Sources */,
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */,
@ -1944,6 +1943,7 @@
D6AEBB4823216B1D00E5038B /* AccountActivity.swift in Sources */,
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */,
D677284C24ECBE9100C732D3 /* ComposeAvatarImageView.swift in Sources */,
D6A6C11B25B63CEE00298D0F /* MemoryCache.swift in Sources */,
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */,
D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */,
D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */,
@ -2020,6 +2020,7 @@
D670F8B62537DC890046588A /* EmojiPickerWrapper.swift in Sources */,
D6B81F442560390300F6E31D /* MenuController.swift in Sources */,
D65234E12561AA68001AF9CF /* NotificationsTableViewController.swift in Sources */,
D6A6C11525B62E9700298D0F /* CacheExpiry.swift in Sources */,
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */,
D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */,
D647D92824257BEB0005044F /* AttachmentPreviewViewController.swift in Sources */,
@ -2045,7 +2046,6 @@
D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */,
D65234D325618EFA001AF9CF /* TimelineTableViewController.swift in Sources */,
D68E6F5F253C9B2D001A1B4C /* BaseEmojiLabel.swift in Sources */,
D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */,
D6F0B12B24A3071C001E48C3 /* MainSplitViewController.swift in Sources */,
0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */,
D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */,

3
Tusker.xcworkspace/contents.xcworkspacedata

@ -8,9 +8,6 @@
location = "group:BlankSlate.xcappdata">
</FileRef>
<FileRef
location = "group:Cache/Cache.xcodeproj">
</FileRef>
<FileRef
location = "group:Gifu/Gifu.xcodeproj">
</FileRef>
<FileRef

62
Tusker/Caching/Cache.swift

@ -1,62 +0,0 @@
//
// Cache.swift
// Tusker
//
// Created by Shadowfacts on 11/7/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import Foundation
import Cache
/// Wrapper around Cache library that provides an API for transparently using any storage type
enum Cache<T> {
case memory(MemoryStorage<T>)
case disk(DiskStorage<T>)
case hybrid(HybridStorage<T>)
@available(*, deprecated, message: "disk-based caches synchronously interact with the file system. Avoid using if possible.")
func existsObject(forKey key: String) throws -> Bool {
switch self {
case let .memory(memory):
return try memory.existsObject(forKey: key)
case let .disk(disk):
return try disk.existsObject(forKey: key)
case let .hybrid(hybrid):
return try hybrid.existsObject(forKey: key)
}
}
func object(forKey key: String) throws -> T {
switch self {
case let .memory(memory):
return try memory.object(forKey: key)
case let .disk(disk):
return try disk.object(forKey: key)
case let .hybrid(hybrid):
return try hybrid.object(forKey: key)
}
}
func setObject(_ object: T, forKey key: String, expiry: Expiry? = nil) throws {
switch self {
case let .memory(memory):
memory.setObject(object, forKey: key, expiry: expiry)
case let .disk(disk):
try disk.setObject(object, forKey: key, expiry: expiry)
case let .hybrid(hybrid):
try hybrid.setObject(object, forKey: key, expiry: expiry)
}
}
func removeAll() throws {
switch self {
case let .memory(memory):
memory.removeAll()
case let .disk(disk):
try disk.removeAll()
case let .hybrid(hybrid):
try hybrid.removeAll()
}
}
}

30
Tusker/Caching/CacheExpiry.swift

@ -0,0 +1,30 @@
//
// CacheExpiry.swift
// Tusker
//
// Created by Shadowfacts on 1/18/21.
// Copyright © 2021 Shadowfacts. All rights reserved.
//
import Foundation
enum CacheExpiry {
case never
case seconds(TimeInterval)
case date(Date)
var date: Date {
switch self {
case .never:
return .distantFuture
case let .seconds(seconds):
return Date().addingTimeInterval(seconds)
case let .date(date):
return date
}
}
var isExpired: Bool {
return date.timeIntervalSinceNow < 0
}
}

71
Tusker/Caching/CachingDiskStorage.swift

@ -1,71 +0,0 @@
//
// CachingDiskStorage.swift
// Tusker
//
// Created by Shadowfacts on 1/17/21.
// Copyright © 2021 Shadowfacts. All rights reserved.
//
import Foundation
import Cache
/// This class wraps a `DiskStorage` and maintains an in-memory cache of whether objects
/// exist on disk to avoid unnecessary disk I/O when calling synchronous methods like
/// `existsObject(forKey:)`.
class CachingDiskStorage<T>: StorageAware {
private let storage: DiskStorage<T>
private var files = [String: FileState]()
init(config: DiskConfig, transformer: Transformer<T>) throws {
storage = try DiskStorage(config: config, transformer: transformer)
}
private func state(for key: String) -> FileState {
return files[key] ?? .unknown
}
func entry(forKey key: String) throws -> Entry<T> {
if state(for: key) == .doesNotExist {
throw StorageError.notFound
}
return try storage.entry(forKey: key)
}
func existsObject(forKey key: String) throws -> Bool {
switch state(for: key) {
case .unknown:
return try storage.existsObject(forKey: key)
case .exists:
return true
case .doesNotExist:
return false
}
}
func removeObject(forKey key: String) throws {
try storage.removeObject(forKey: key)
files[key] = .doesNotExist
}
func setObject(_ object: T, forKey key: String, expiry: Expiry? = nil) throws {
try storage.setObject(object, forKey: key, expiry: expiry)
files[key] = .exists
}
func removeAll() throws {
try storage.removeAll()
files.removeAll()
}
func removeExpiredObjects() throws {
try storage.removeExpiredObjects()
}
}
extension CachingDiskStorage {
private enum FileState {
case unknown, exists, doesNotExist
}
}

143
Tusker/Caching/DiskCache.swift

@ -0,0 +1,143 @@
//
// DiskCache.swift
// Tusker
//
// Created by Shadowfacts on 1/18/21.
// Copyright © 2021 Shadowfacts. All rights reserved.
//
import Foundation
import CryptoKit
struct DiskCacheTransformer<T> {
let toData: (T) throws -> Data
let fromData: (Data) throws -> T
}
class DiskCache<T> {
let fileManager: FileManager
let path: String
let defaultExpiry: CacheExpiry
let transformer: DiskCacheTransformer<T>
private var fileStates = [String: FileState]()
init(name: String, defaultExpiry: CacheExpiry, transformer: DiskCacheTransformer<T>, fileManager: FileManager = .default) throws {
self.defaultExpiry = defaultExpiry
self.transformer = transformer
self.fileManager = fileManager
let cacheDir = try fileManager.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
self.path = cacheDir.appendingPathComponent(name, isDirectory: true).path
try createDirectory()
}
private func createDirectory() throws {
if !fileManager.fileExists(atPath: path) {
try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
}
}
private func makeFileName(for key: String) -> String {
let ext = (key as NSString).pathExtension
let digest = Insecure.MD5.hash(data: key.data(using: .utf8)!)
let hash = digest.map { String($0, radix: 16) }.joined()
if ext.isEmpty {
return hash
} else {
return "\(hash).\(ext)"
}
}
private func makeFilePath(for key: String) -> String {
return (path as NSString).appendingPathComponent(makeFileName(for: key))
}
private func fileState(forKey key: String) -> FileState {
return fileStates[key] ?? .unknown
}
func setObject(_ object: T, forKey key: String) throws {
let data = try transformer.toData(object)
let path = makeFilePath(for: key)
guard fileManager.createFile(atPath: path, contents: data, attributes: [.modificationDate: defaultExpiry.date]) else {
throw Error.couldNotCreateFile
}
fileStates[key] = .exists
}
func removeObject(forKey key: String) throws {
let path = makeFilePath(for: key)
try fileManager.removeItem(atPath: path)
fileStates[key] = .doesNotExist
}
func existsObject(forKey key: String) throws -> Bool {
switch fileState(forKey: key) {
case .exists:
return true
case .doesNotExist:
return false
case .unknown:
let path = makeFilePath(for: key)
guard fileManager.fileExists(atPath: path) else {
return false
}
let attributes = try fileManager.attributesOfItem(atPath: path)
guard let date = attributes[.modificationDate] as? Date else {
throw Error.malformedFileAttributes
}
return date.timeIntervalSinceNow >= 0
}
}
func object(forKey key: String) throws -> T {
let path = makeFilePath(for: key)
let attributes = try fileManager.attributesOfItem(atPath: path)
guard let date = attributes[.modificationDate] as? Date else {
throw Error.malformedFileAttributes
}
guard date.timeIntervalSinceNow >= 0 else {
try fileManager.removeItem(atPath: path)
fileStates[key] = .doesNotExist
throw Error.expired
}
let data = try Data(contentsOf: URL(fileURLWithPath: path, isDirectory: false))
let object = try transformer.fromData(data)
return object
}
func removeAll() throws {
try fileManager.removeItem(atPath: path)
try createDirectory()
fileStates.removeAll()
}
}
extension DiskCache {
enum Error: Swift.Error {
case malformedFileAttributes
case couldNotCreateFile
case expired
}
}
extension DiskCache {
enum FileState {
case exists, doesNotExist, unknown
}
}
extension DiskCache where T == Data {
convenience init(name: String, defaultExpiry: CacheExpiry) throws {
try self.init(name: name, defaultExpiry: defaultExpiry, transformer: DiskCacheTransformer(toData: { $0 }, fromData: { $0 }))
}
}

2
Tusker/Caching/ImageCache.swift

@ -28,7 +28,7 @@ class ImageCache {
private var backgroundQueue = DispatchQueue(label: "ImageCache completion queue", qos: .default)
init(name: String, memoryExpiry: Expiry, diskExpiry: Expiry? = nil, desiredSize: CGSize? = nil) {
init(name: String, memoryExpiry: CacheExpiry, diskExpiry: CacheExpiry? = nil, desiredSize: CGSize? = nil) {
// todo: might not always want to use UIScreen.main for this, e.g. Catalyst?
let pixelSize = desiredSize?.applying(.init(scaleX: UIScreen.main.scale, y: UIScreen.main.scale))
self.cache = ImageDataCache(name: name, memoryExpiry: memoryExpiry, diskExpiry: diskExpiry, storeOriginalDataInMemory: diskExpiry == nil, desiredPixelSize: pixelSize)

14
Tusker/Caching/ImageDataCache.swift

@ -11,19 +11,17 @@ import Cache
class ImageDataCache {
private let memory: MemoryStorage<Entry>
private let disk: CachingDiskStorage<Data>?
private let memory: MemoryCache<Entry>
private let disk: DiskCache<Data>?
private let storeOriginalDataInMemory: Bool
private let desiredPixelSize: CGSize?
init(name: String, memoryExpiry: Expiry, diskExpiry: Expiry?, storeOriginalDataInMemory: Bool, desiredPixelSize: CGSize?) {
let memoryConfig = MemoryConfig(expiry: memoryExpiry)
self.memory = MemoryStorage(config: memoryConfig)
init(name: String, memoryExpiry: CacheExpiry, diskExpiry: CacheExpiry?, storeOriginalDataInMemory: Bool, desiredPixelSize: CGSize?) {
self.memory = MemoryCache(defaultExpiry: memoryExpiry)
if let diskExpiry = diskExpiry {
let diskConfig = DiskConfig(name: name, expiry: diskExpiry)
self.disk = try! CachingDiskStorage(config: diskConfig, transformer: TransformerFactory.forData())
self.disk = try! DiskCache(name: name, defaultExpiry: diskExpiry)
} else {
self.disk = nil
}
@ -33,7 +31,7 @@ class ImageDataCache {
}
func has(_ key: String) throws -> Bool {
if try memory.existsObject(forKey: key) {
if memory.existsObject(forKey: key) {
return true
} else if let disk = self.disk,
try disk.existsObject(forKey: key) {

69
Tusker/Caching/MemoryCache.swift

@ -0,0 +1,69 @@
//
// MemoryCache.swift
// Tusker
//
// Created by Shadowfacts on 1/18/21.
// Copyright © 2021 Shadowfacts. All rights reserved.
//
import Foundation
class MemoryCache<T> {
private let cache = NSCache<NSString, Entry>()
private let defaultExpiry: CacheExpiry
init(defaultExpiry: CacheExpiry) {
self.defaultExpiry = defaultExpiry
}
func setObject(_ object: T, forKey key: String) {
let entry = Entry(expiresAt: defaultExpiry.date, object: object)
cache.setObject(entry, forKey: key as NSString)
}
func removeObject(forKey key: String) {
cache.removeObject(forKey: key as NSString)
}
func existsObject(forKey key: String) -> Bool {
return cache.object(forKey: key as NSString) != nil
}
func object(forKey key: String) throws -> T {
guard let entry = cache.object(forKey: key as NSString) else {
throw Error.notFound
}
guard entry.expiresAt.timeIntervalSinceNow >= 0 else {
cache.removeObject(forKey: key as NSString)
throw Error.expired
}
return entry.object
}
func removeAll() {
cache.removeAllObjects()
}
}
extension MemoryCache {
enum Error: Swift.Error {
case notFound
case expired
}
}
extension MemoryCache {
class Entry {
let expiresAt: Date
let object: T
init(expiresAt: Date, object: T) {
self.expiresAt = expiresAt
self.object = object
}
}
}
Loading…
Cancel
Save