diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index dedc0782..18088465 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -134,6 +134,7 @@ D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0DC8D216EDF1E00369478 /* Previewing.swift */; }; D6E6F26321603F8B006A8599 /* CharacterCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E6F26221603F8B006A8599 /* CharacterCounter.swift */; }; D6E6F26521604242006A8599 /* CharacterCounterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E6F26421604242006A8599 /* CharacterCounterTests.swift */; }; + D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F1F84C2193B56E00F5FE67 /* Cache.swift */; }; D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EB212519E700CF0F2B /* TimelineTableViewController.swift */; }; D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; }; /* End PBXBuildFile section */ @@ -333,6 +334,7 @@ D6E0DC8D216EDF1E00369478 /* Previewing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Previewing.swift; sourceTree = ""; }; D6E6F26221603F8B006A8599 /* CharacterCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterCounter.swift; sourceTree = ""; }; D6E6F26421604242006A8599 /* CharacterCounterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterCounterTests.swift; sourceTree = ""; }; + D6F1F84C2193B56E00F5FE67 /* Cache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = ""; }; D6F953EB212519E700CF0F2B /* TimelineTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewController.swift; sourceTree = ""; }; D6F953EF21251A2900CF0F2B /* MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonController.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -773,9 +775,9 @@ D627FF73217BBC9700CC0648 /* AppRouter.swift */, D64D0AAC2128D88B005A6F37 /* LocalData.swift */, D627FF75217E923E00CC0648 /* DraftsManager.swift */, - 04DACE8D212CC7CC009840C4 /* ImageCache.swift */, D6028B9A2150811100F223B9 /* MastodonCache.swift */, D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */, + D6F1F84E2193B9BE00F5FE67 /* Caching */, D6757A7A2157E00100721E32 /* XCallbackURL */, D62D241E217AA46B005076CC /* Shortcuts */, D663626021360A9600C9CBA2 /* Preferences */, @@ -808,6 +810,15 @@ path = TuskerUITests; sourceTree = ""; }; + D6F1F84E2193B9BE00F5FE67 /* Caching */ = { + isa = PBXGroup; + children = ( + D6F1F84C2193B56E00F5FE67 /* Cache.swift */, + 04DACE8D212CC7CC009840C4 /* ImageCache.swift */, + ); + path = Caching; + sourceTree = ""; + }; D6F953F121251A2F00CF0F2B /* Controllers */ = { isa = PBXGroup; children = ( @@ -1188,6 +1199,7 @@ D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */, D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */, D6C693CF216125FC007D6A6D /* SilentActionPermissionTableViewCell.swift in Sources */, + D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */, D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */, D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */, D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */, diff --git a/Tusker/Caching/Cache.swift b/Tusker/Caching/Cache.swift new file mode 100644 index 00000000..9f0ab5b1 --- /dev/null +++ b/Tusker/Caching/Cache.swift @@ -0,0 +1,50 @@ +// +// 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 { + case memory(MemoryStorage) + case disk(DiskStorage) + case hybrid(HybridStorage) + + 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) + } + } +} diff --git a/Tusker/ImageCache.swift b/Tusker/Caching/ImageCache.swift similarity index 58% rename from Tusker/ImageCache.swift rename to Tusker/Caching/ImageCache.swift index 0d4afeb5..f772a0d8 100644 --- a/Tusker/ImageCache.swift +++ b/Tusker/Caching/ImageCache.swift @@ -11,25 +11,34 @@ import Cache class ImageCache { - static let avatars = ImageCache(name: "Avatars") - static let headers = ImageCache(name: "Headers") - static let attachments = ImageCache(name: "Attachments", diskExpiry: .seconds(60 * 60), memoryExpiry: .seconds(60)) + static let avatars = ImageCache(name: "Avatars", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24)) + static let headers = ImageCache(name: "Headers", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24)) + static let attachments = ImageCache(name: "Attachments", memoryExpiry: .seconds(60 * 2)) - let storage: Storage + let cache: Cache var requests = [URL: Request]() - init(name: String, diskExpiry: Expiry = .seconds(60 * 60 * 24), memoryExpiry: Expiry = .seconds(60 * 60)) { - self.storage = try! Storage( - diskConfig: DiskConfig(name: name, expiry: diskExpiry), - memoryConfig: MemoryConfig(expiry: memoryExpiry), - transformer: TransformerFactory.forImage()) + init(name: String, memoryExpiry expiry: Expiry) { + let storage = MemoryStorage(config: MemoryConfig(expiry: expiry)) + self.cache = .memory(storage) + } + + init(name: String, diskExpiry expiry: Expiry) { + let storage = try! DiskStorage(config: DiskConfig(name: name, expiry: expiry), transformer: TransformerFactory.forImage()) + self.cache = .disk(storage) + } + + init(name: String, memoryExpiry: Expiry, diskExpiry: Expiry) { + let memory = MemoryStorage(config: MemoryConfig(expiry: memoryExpiry)) + let disk = try! DiskStorage(config: DiskConfig(name: name, expiry: diskExpiry), transformer: TransformerFactory.forImage()) + self.cache = .hybrid(HybridStorage(memoryStorage: memory, diskStorage: disk)) } func get(_ url: URL, completion: ((UIImage?) -> Void)?) { let key = url.absoluteString - if (try? storage.existsObject(forKey: key)) ?? false, - let image = try? storage.object(forKey: key) { + if (try? cache.existsObject(forKey: key)) ?? false, + let image = try? cache.object(forKey: key) { completion?(image) } else { if let completion = completion, let request = requests[url] { @@ -38,14 +47,14 @@ class ImageCache { let request = Request(url: url, completion: completion) requests[url] = request request.run { (image) in - try? self.storage.setObject(image, forKey: key) + try? self.cache.setObject(image, forKey: key) } } } } func get(_ url: URL) -> UIImage? { - return try? storage.object(forKey: url.absoluteString) + return try? cache.object(forKey: url.absoluteString) } func cancel(_ url: URL) {