66 lines
2.8 KiB
Swift
66 lines
2.8 KiB
Swift
// The last good feed, and the last detail of the 30 threads saved most recently, saved as JSON in
|
|
// the app's caches folder. Launch shows them at once while the first refresh runs. Signing out clears
|
|
// them, and demo mode never reads or writes them.
|
|
|
|
import Foundation
|
|
|
|
struct FeedCache: Sendable {
|
|
let directory: URL
|
|
/// How many thread details are kept. Saving one more deletes the oldest.
|
|
var threadLimit = 30
|
|
|
|
static let standard = FeedCache(directory: .cachesDirectory.appending(path: "WristCode", directoryHint: .isDirectory))
|
|
|
|
func feed() -> Feed? {
|
|
read(Feed.self, "feed.json")
|
|
}
|
|
|
|
func save(_ feed: Feed) {
|
|
write(feed, "feed.json")
|
|
}
|
|
|
|
func thread(hostId: String, threadId: String) -> ThreadDetail? {
|
|
read(ThreadDetail.self, Self.threadFile(hostId, threadId))
|
|
}
|
|
|
|
func save(_ detail: ThreadDetail) {
|
|
write(detail, Self.threadFile(detail.item.hostId, detail.item.threadId))
|
|
pruneThreads()
|
|
}
|
|
|
|
/// Deletes the thread details saved longest ago, past `threadLimit`.
|
|
private func pruneThreads() {
|
|
let fileManager = FileManager.default
|
|
let key = URLResourceKey.contentModificationDateKey
|
|
guard let files = try? fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: [key]) else { return }
|
|
let threads = files.filter { $0.lastPathComponent.hasPrefix("thread-") }
|
|
guard threads.count > threadLimit else { return }
|
|
let modified = { (url: URL) in (try? url.resourceValues(forKeys: [key]).contentModificationDate) ?? .distantPast }
|
|
for url in threads.sorted(by: { modified($0) > modified($1) }).dropFirst(threadLimit) {
|
|
try? fileManager.removeItem(at: url)
|
|
}
|
|
}
|
|
|
|
/// Deletes everything saved, as when the watch unpairs.
|
|
func clear() {
|
|
try? FileManager.default.removeItem(at: directory)
|
|
}
|
|
|
|
/// `thread-<hostId>-<threadId>.json`, with the ids percent-encoded so any id is a safe file name.
|
|
private static func threadFile(_ hostId: String, _ threadId: String) -> String {
|
|
let safe = { (id: String) in id.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? id }
|
|
return "thread-\(safe(hostId))-\(safe(threadId)).json"
|
|
}
|
|
|
|
private func read<Value: Decodable>(_ type: Value.Type, _ name: String) -> Value? {
|
|
guard let data = try? Data(contentsOf: directory.appending(path: name)) else { return nil }
|
|
return try? JSON.decoder().decode(type, from: data)
|
|
}
|
|
|
|
private func write(_ value: some Encodable, _ name: String) {
|
|
guard let data = try? JSON.encoder().encode(value) else { return }
|
|
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
|
try? data.write(to: directory.appending(path: name), options: .atomic)
|
|
}
|
|
}
|