diff --git a/IceCubesApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/IceCubesApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4b1a80f8..db9cd940 100644 --- a/IceCubesApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/IceCubesApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,23 @@ { "pins" : [ + { + "identity" : "bodega", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mergesort/Bodega.git", + "state" : { + "revision" : "3e7c1c58ad9a46aa8551cebfe87770003cdaaaca", + "version" : "2.0.2" + } + }, + { + "identity" : "boutique", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mergesort/Boutique", + "state" : { + "revision" : "b5b697de67100edc4b2d5c74724f3c1068b49d4e", + "version" : "2.1.1" + } + }, { "identity" : "emojitext", "kind" : "remoteSourceControl", @@ -45,6 +63,24 @@ "version" : "4.16.0" } }, + { + "identity" : "sqlite.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/stephencelis/SQLite.swift.git", + "state" : { + "revision" : "4d543d811ee644fa4cc4bfa0be996b4dd6ba0f54", + "version" : "0.13.3" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "48254824bb4248676bf7ce56014ff57b142b77eb", + "version" : "1.0.2" + } + }, { "identity" : "swiftsoup", "kind" : "remoteSourceControl", diff --git a/Packages/Timeline/Package.swift b/Packages/Timeline/Package.swift index 9a193171..08b65c7f 100644 --- a/Packages/Timeline/Package.swift +++ b/Packages/Timeline/Package.swift @@ -22,6 +22,7 @@ let package = Package( .package(name: "Status", path: "../Status"), .package(name: "DesignSystem", path: "../DesignSystem"), .package(url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.4"), + .package(url: "https://github.com/mergesort/Boutique", from: "2.1.1"), ], targets: [ .target( @@ -33,6 +34,7 @@ let package = Package( .product(name: "Status", package: "Status"), .product(name: "DesignSystem", package: "DesignSystem"), .product(name: "Introspect", package: "SwiftUI-Introspect"), + .product(name: "Boutique", package: "Boutique"), ] ), .testTarget( diff --git a/Packages/Timeline/Sources/Timeline/TimelineCache.swift b/Packages/Timeline/Sources/Timeline/TimelineCache.swift index 342bae54..7eeb7a4b 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineCache.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineCache.swift @@ -1,20 +1,44 @@ import Models import Network import SwiftUI +import Boutique actor TimelineCache { static let shared: TimelineCache = .init() - private var memoryCache: [Client: [Status]] = [:] + private func storageFor(_ client: Client) -> SQLiteStorageEngine { + SQLiteStorageEngine.default(appendingPath: client.id) + } + + private let decoder = JSONDecoder() + private let encoder = JSONEncoder() private init() {} - func set(statuses: [Status], client: Client) { + func set(statuses: [Status], client: Client) async { guard !statuses.isEmpty else { return } - memoryCache[client] = statuses.prefix(upTo: min(100, statuses.count - 1)).map { $0 } + let statuses = statuses.prefix(upTo: min(300, statuses.count - 1)).map { $0 } + do { + let engine = storageFor(client) + try await engine.removeAllData() + let itemKeys = statuses.map({ CacheKey($0[keyPath: \.id]) }) + let dataAndKeys = try zip(itemKeys, statuses) + .map({ (key: $0, data: try encoder.encode($1)) }) + try await engine.write(dataAndKeys) + } catch { + + } } - func getStatuses(for client: Client) -> [Status]? { - memoryCache[client] + func getStatuses(for client: Client) async -> [Status]? { + let engine = storageFor(client) + do { + return try await engine + .readAllData() + .map({ try decoder.decode(Status.self, from: $0) }) + .sorted(by: { $0.createdAt > $1.createdAt }) + } catch { + return nil + } } }