IceCubesApp/Packages/Timeline/Sources/Timeline/actors/TimelineCache.swift

96 lines
3.3 KiB
Swift
Raw Normal View History

import Bodega
import Models
import Network
2023-02-01 11:49:59 +00:00
import SwiftUI
2023-02-04 14:08:54 +00:00
public actor TimelineCache {
private func storageFor(_ client: String, _ filter: String) -> SQLiteStorageEngine {
if filter == "Home" {
SQLiteStorageEngine.default(appendingPath: "\(client)")
} else {
SQLiteStorageEngine.default(appendingPath: "\(client)/\(filter)")
}
}
2023-02-04 16:17:38 +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() {}
2024-02-14 11:48:14 +00:00
public func cachedPostsCount(for client: String) async -> Int {
do {
let directory = FileManager.Directory.defaultStorageDirectory(appendingPath: client).url
let content = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil)
var total: Int = await storageFor(client, "Home").allKeys().count
for storage in content {
if !storage.lastPathComponent.hasSuffix("sqlite3") {
total += await storageFor(client, storage.lastPathComponent).allKeys().count
}
}
return total
} catch {
return 0
}
2023-02-04 14:08:54 +00:00
}
2024-02-14 11:48:14 +00:00
public func clearCache(for client: String) async {
let directory = FileManager.Directory.defaultStorageDirectory(appendingPath: client)
try? FileManager.default.removeItem(at: directory.url)
}
public func clearCache(for client: String, filter: String) async {
let engine = storageFor(client, filter)
2023-02-04 14:08:54 +00:00
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
func set(statuses: [Status], client: String, filter: String) async {
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 }
do {
let engine = storageFor(client, filter)
try await engine.removeAllData()
2023-02-04 16:17:38 +00:00
let itemKeys = statuses.map { CacheKey($0[keyPath: \.id]) }
let dataAndKeys = try zip(itemKeys, statuses)
2023-03-13 12:38:28 +00:00
.map { try (key: $0, data: encoder.encode($1)) }
try await engine.write(dataAndKeys)
2023-02-04 16:17:38 +00:00
} catch {}
}
2023-02-01 11:49:59 +00:00
func getStatuses(for client: String, filter: String) async -> [Status]? {
let engine = storageFor(client, filter)
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 })
} catch {
return nil
}
}
2023-02-04 16:17:38 +00:00
func setLatestSeenStatuses(_ statuses: [Status], for client: Client, filter: String) {
let statuses = statuses.sorted(by: { $0.createdAt.asDate > $1.createdAt.asDate })
if filter == "Home" {
2024-02-14 11:48:14 +00:00
UserDefaults.standard.set(statuses.map { $0.id }, forKey: "timeline-last-seen-\(client.id)")
} else {
2024-02-14 11:48:14 +00:00
UserDefaults.standard.set(statuses.map { $0.id }, forKey: "timeline-last-seen-\(client.id)-\(filter)")
}
2023-02-04 13:42:10 +00:00
}
2023-02-04 16:17:38 +00:00
func getLatestSeenStatus(for client: Client, filter: String) -> [String]? {
if filter == "Home" {
UserDefaults.standard.array(forKey: "timeline-last-seen-\(client.id)") as? [String]
} else {
UserDefaults.standard.array(forKey: "timeline-last-seen-\(client.id)-\(filter)") as? [String]
}
2023-02-04 13:42:10 +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 {}