2023-02-22 17:56:37 +00:00
|
|
|
import Bodega
|
2023-02-01 11:43:11 +00:00
|
|
|
import Models
|
|
|
|
import Network
|
2023-02-01 11:49:59 +00:00
|
|
|
import SwiftUI
|
2023-02-01 11:43:11 +00:00
|
|
|
|
2023-02-04 14:08:54 +00:00
|
|
|
public actor TimelineCache {
|
2023-02-19 06:51:16 +00:00
|
|
|
private func storageFor(_ client: String) -> SQLiteStorageEngine {
|
|
|
|
SQLiteStorageEngine.default(appendingPath: client)
|
2023-02-04 11:33:28 +00:00
|
|
|
}
|
2023-02-04 16:17:38 +00:00
|
|
|
|
2023-02-04 11:33:28 +00:00
|
|
|
private let decoder = JSONDecoder()
|
|
|
|
private let encoder = JSONEncoder()
|
2023-02-01 11:49:59 +00:00
|
|
|
|
2023-09-22 06:32:13 +00:00
|
|
|
public init() {}
|
2023-02-01 11:49:59 +00:00
|
|
|
|
2023-02-19 06:51:16 +00:00
|
|
|
public func cachedPostsCount(for client: String) async -> Int {
|
2023-02-04 14:08:54 +00:00
|
|
|
await storageFor(client).allKeys().count
|
|
|
|
}
|
2023-02-04 16:17:38 +00:00
|
|
|
|
2023-02-19 06:51:16 +00:00
|
|
|
public func clearCache(for client: String) async {
|
2023-02-04 14:08:54 +00:00
|
|
|
let engine = storageFor(client)
|
|
|
|
do {
|
|
|
|
try await engine.removeAllData()
|
2023-02-04 16:17:38 +00:00
|
|
|
} catch {}
|
2023-02-04 14:08:54 +00:00
|
|
|
}
|
2023-02-04 16:17:38 +00:00
|
|
|
|
2023-02-19 06:51:16 +00:00
|
|
|
func set(statuses: [Status], client: String) async {
|
2023-02-02 20:21:07 +00:00
|
|
|
guard !statuses.isEmpty else { return }
|
2023-02-08 07:48:18 +00:00
|
|
|
let statuses = statuses.prefix(upTo: min(600, statuses.count - 1)).map { $0 }
|
2023-02-04 11:33:28 +00:00
|
|
|
do {
|
|
|
|
let engine = storageFor(client)
|
|
|
|
try await engine.removeAllData()
|
2023-02-04 16:17:38 +00:00
|
|
|
let itemKeys = statuses.map { CacheKey($0[keyPath: \.id]) }
|
2023-02-04 11:33:28 +00:00
|
|
|
let dataAndKeys = try zip(itemKeys, statuses)
|
2023-03-13 12:38:28 +00:00
|
|
|
.map { try (key: $0, data: encoder.encode($1)) }
|
2023-02-04 11:33:28 +00:00
|
|
|
try await engine.write(dataAndKeys)
|
2023-02-04 16:17:38 +00:00
|
|
|
} catch {}
|
2023-02-01 11:43:11 +00:00
|
|
|
}
|
2023-02-01 11:49:59 +00:00
|
|
|
|
2023-02-19 06:51:16 +00:00
|
|
|
func getStatuses(for client: String) async -> [Status]? {
|
2023-02-04 11:33:28 +00:00
|
|
|
let engine = storageFor(client)
|
|
|
|
do {
|
|
|
|
return try await engine
|
|
|
|
.readAllData()
|
2023-02-04 16:17:38 +00:00
|
|
|
.map { try decoder.decode(Status.self, from: $0) }
|
2023-02-09 08:12:44 +00:00
|
|
|
.sorted(by: { $0.createdAt.asDate > $1.createdAt.asDate })
|
2023-02-04 11:33:28 +00:00
|
|
|
} catch {
|
|
|
|
return nil
|
|
|
|
}
|
2023-02-01 11:43:11 +00:00
|
|
|
}
|
2023-02-04 16:17:38 +00:00
|
|
|
|
2023-02-04 13:42:10 +00:00
|
|
|
func setLatestSeenStatuses(ids: [String], for client: Client) {
|
2023-02-04 16:17:38 +00:00
|
|
|
UserDefaults.standard.set(ids, forKey: "timeline-last-seen-\(client.id)")
|
2023-02-04 13:42:10 +00:00
|
|
|
}
|
2023-02-04 16:17:38 +00:00
|
|
|
|
2023-02-04 13:42:10 +00:00
|
|
|
func getLatestSeenStatus(for client: Client) -> [String]? {
|
2023-02-04 16:17:38 +00:00
|
|
|
UserDefaults.standard.array(forKey: "timeline-last-seen-\(client.id)") as? [String]
|
2023-02-04 13:42:10 +00:00
|
|
|
}
|
2023-02-01 11:43:11 +00:00
|
|
|
}
|
2023-02-27 05:39:07 +00:00
|
|
|
|
|
|
|
// Quiets down the warnings from this one. Bodega is nicely async so we don't
|
|
|
|
// want to just use `@preconcurrency`, but the CacheKey type is (incorrectly)
|
|
|
|
// not marked as `Sendable`---it's a value type containing two `String`
|
|
|
|
// properties.
|
|
|
|
extension Bodega.CacheKey: @unchecked Sendable {}
|