mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
c70146c4a8
add schemas sync variants formatting initial implementation use existing db, wip move to separate folder fix table definitions wip wiring it up
161 lines
5.3 KiB
Swift
161 lines
5.3 KiB
Swift
import SQLiteData
|
|
|
|
enum StoreError: Error {
|
|
case invalidJSON(String)
|
|
case invalidURL(String)
|
|
case encodingFailed
|
|
case notFound
|
|
}
|
|
|
|
protocol StoreConvertible {
|
|
static var cacheKeyPath: ReferenceWritableKeyPath<StoreCache, [StoreKey: Self]> { get }
|
|
associatedtype StorageType
|
|
static func fromValue(_ value: StorageType) throws(StoreError) -> Self
|
|
static func toValue(_ value: Self) throws(StoreError) -> StorageType
|
|
}
|
|
|
|
extension StoreConvertible {
|
|
static func get(_ cache: StoreCache, key: StoreKey) -> Self? {
|
|
os_unfair_lock_lock(&cache.lock)
|
|
defer { os_unfair_lock_unlock(&cache.lock) }
|
|
return cache[keyPath: cacheKeyPath][key]
|
|
}
|
|
|
|
static func set(_ cache: StoreCache, key: StoreKey, value: Self?) {
|
|
os_unfair_lock_lock(&cache.lock)
|
|
defer { os_unfair_lock_unlock(&cache.lock) }
|
|
cache[keyPath: cacheKeyPath][key] = value
|
|
}
|
|
}
|
|
|
|
final class StoreCache {
|
|
fileprivate var lock = os_unfair_lock()
|
|
fileprivate var intCache: [StoreKey: Int] = [:]
|
|
fileprivate var boolCache: [StoreKey: Bool] = [:]
|
|
fileprivate var dateCache: [StoreKey: Date] = [:]
|
|
fileprivate var stringCache: [StoreKey: String] = [:]
|
|
fileprivate var urlCache: [StoreKey: URL] = [:]
|
|
fileprivate var endpointArrayCache: [StoreKey: [Endpoint]] = [:]
|
|
fileprivate var stringDictCache: [StoreKey: [String: String]] = [:]
|
|
|
|
func get<T: StoreConvertible>(_ key: StoreKey.Typed<T>) -> T? {
|
|
T.get(self, key: key.rawValue)
|
|
}
|
|
|
|
func set<T: StoreConvertible>(_ key: StoreKey.Typed<T>, value: T?) {
|
|
T.set(self, key: key.rawValue, value: value)
|
|
}
|
|
}
|
|
|
|
extension Int: StoreConvertible {
|
|
static let cacheKeyPath = \StoreCache.intCache
|
|
static func fromValue(_ value: Int) -> Int { value }
|
|
static func toValue(_ value: Int) -> Int { value }
|
|
}
|
|
|
|
extension Bool: StoreConvertible {
|
|
static let cacheKeyPath = \StoreCache.boolCache
|
|
static func fromValue(_ value: Int) -> Bool { value == 1 }
|
|
static func toValue(_ value: Bool) -> Int { value ? 1 : 0 }
|
|
}
|
|
|
|
extension Date: StoreConvertible {
|
|
static let cacheKeyPath = \StoreCache.dateCache
|
|
static func fromValue(_ value: Int) -> Date { Date(timeIntervalSince1970: TimeInterval(value) / 1000) }
|
|
static func toValue(_ value: Date) -> Int { Int(value.timeIntervalSince1970 * 1000) }
|
|
}
|
|
|
|
extension String: StoreConvertible {
|
|
static let cacheKeyPath = \StoreCache.stringCache
|
|
static func fromValue(_ value: String) -> String { value }
|
|
static func toValue(_ value: String) -> String { value }
|
|
}
|
|
|
|
extension URL: StoreConvertible {
|
|
static let cacheKeyPath = \StoreCache.urlCache
|
|
static func fromValue(_ value: String) throws(StoreError) -> URL {
|
|
guard let url = URL(string: value) else {
|
|
throw StoreError.invalidURL(value)
|
|
}
|
|
return url
|
|
}
|
|
static func toValue(_ value: URL) -> String { value.absoluteString }
|
|
}
|
|
|
|
extension StoreConvertible where Self: Codable, StorageType == String {
|
|
static var jsonDecoder: JSONDecoder { JSONDecoder() }
|
|
static var jsonEncoder: JSONEncoder { JSONEncoder() }
|
|
|
|
static func fromValue(_ value: String) throws(StoreError) -> Self {
|
|
do {
|
|
return try jsonDecoder.decode(Self.self, from: Data(value.utf8))
|
|
} catch {
|
|
throw StoreError.invalidJSON(value)
|
|
}
|
|
}
|
|
|
|
static func toValue(_ value: Self) throws(StoreError) -> String {
|
|
let encoded: Data
|
|
do {
|
|
encoded = try jsonEncoder.encode(value)
|
|
} catch {
|
|
throw StoreError.encodingFailed
|
|
}
|
|
|
|
guard let string = String(data: encoded, encoding: .utf8) else {
|
|
throw StoreError.encodingFailed
|
|
}
|
|
return string
|
|
}
|
|
}
|
|
|
|
extension Array: StoreConvertible where Element == Endpoint {
|
|
static let cacheKeyPath = \StoreCache.endpointArrayCache
|
|
typealias StorageType = String
|
|
}
|
|
|
|
extension Dictionary: StoreConvertible where Key == String, Value == String {
|
|
static let cacheKeyPath = \StoreCache.stringDictCache
|
|
typealias StorageType = String
|
|
}
|
|
|
|
extension Store {
|
|
static let cache = StoreCache()
|
|
|
|
static func get<T: StoreConvertible>(_ conn: Database, _ key: StoreKey.Typed<T>) throws -> T?
|
|
where T.StorageType == Int {
|
|
if let cached = cache.get(key) { return cached }
|
|
let query = Store.select(\.intValue).where { $0.id.eq(key.rawValue) }
|
|
if let value = try query.fetchOne(conn) ?? nil {
|
|
let converted = try T.fromValue(value)
|
|
cache.set(key, value: converted)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
static func get<T: StoreConvertible>(_ conn: Database, _ key: StoreKey.Typed<T>) throws -> T?
|
|
where T.StorageType == String {
|
|
if let cached = cache.get(key) { return cached }
|
|
let query = Store.select(\.stringValue).where { $0.id.eq(key.rawValue) }
|
|
if let value = try query.fetchOne(conn) ?? nil {
|
|
let converted = try T.fromValue(value)
|
|
cache.set(key, value: converted)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
static func set<T: StoreConvertible>(_ conn: Database, _ key: StoreKey.Typed<T>, value: T) throws
|
|
where T.StorageType == Int {
|
|
let converted = try T.toValue(value)
|
|
try Store.upsert { Store(id: key.rawValue, stringValue: nil, intValue: converted) }.execute(conn)
|
|
cache.set(key, value: value)
|
|
}
|
|
|
|
static func set<T: StoreConvertible>(_ conn: Database, _ key: StoreKey.Typed<T>, value: T) throws
|
|
where T.StorageType == String {
|
|
let converted = try T.toValue(value)
|
|
try Store.upsert { Store(id: key.rawValue, stringValue: converted, intValue: nil) }.execute(conn)
|
|
cache.set(key, value: value)
|
|
}
|
|
}
|