Remove Cache library
This commit is contained in:
parent
e7800249af
commit
2761c05a01
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
Cache
@ -1 +0,0 @@
|
||||
Subproject commit 8c42c575cf28b2ff0e780c9728721e9a8891c92e
|
@ -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
generated
3
Tusker.xcworkspace/contents.xcworkspacedata
generated
@ -7,9 +7,6 @@
|
||||
<FileRef
|
||||
location = "group:BlankSlate.xcappdata">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Cache/Cache.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Gifu/Gifu.xcodeproj">
|
||||
</FileRef>
|
||||
|
@ -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
Normal file
30
Tusker/Caching/CacheExpiry.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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
Normal file
143
Tusker/Caching/DiskCache.swift
Normal file
@ -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 }))
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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
Normal file
69
Tusker/Caching/MemoryCache.swift
Normal file
@ -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…
x
Reference in New Issue
Block a user