import Foundation import SQLite extension Connection: @retroactive @unchecked Sendable {} public actor SQLiteStore: PersistenceService { private let db: Connection public init(dbURL: URL) throws { let directory = dbURL.deletingLastPathComponent() try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) db = try Connection(dbURL.path) db.busyTimeout = 5 try db.execute("PRAGMA journal_mode = WAL") try migrate() } private func migrate() throws { let version = db.userVersion switch version { case 0: try db.execute( """ CREATE TABLE IF NOT EXISTS auth ( id INTEGER PRIMARY KEY CHECK (id = 1), server_url TEXT NOT NULL, token TEXT NOT NULL, user_id TEXT NOT NULL, username TEXT NOT NULL ) """ ) try db.execute( """ CREATE TABLE IF NOT EXISTS preferences ( key TEXT PRIMARY KEY, value TEXT NOT NULL ) """ ) db.userVersion = 1 fallthrough default: break } } public static func defaultDatabaseURL() -> URL { if #available(macOS 13, *) { let appSupport = FileManager.default.urls( for: .applicationSupportDirectory, in: .userDomainMask ).first! return appSupport .appendingPathComponent("dev.bscubed.Luminate") .appendingPathComponent("db.sqlite") } else { #if os(Linux) let xdgData = ProcessInfo.processInfo.environment["XDG_DATA_HOME"] ?? "\(FileManager.default.homeDirectoryForCurrentUser.path).local/share" return URL(fileURLWithPath: xdgData) .appendingPathComponent("dev.bscubed.Luminate") .appendingPathComponent("db.sqlite") #else let appSupport = FileManager.default.urls( for: .applicationSupportDirectory, in: .userDomainMask ).first! return appSupport .appendingPathComponent("dev.bscubed.Luminate") .appendingPathComponent("db.sqlite") #endif } } public func loadAuth() async throws -> AuthData { let stmt = try db.prepare("SELECT server_url, token, user_id, username FROM auth WHERE id = 1") for row in stmt { guard let serverURL = row[0] as? String, let token = row[1] as? String, let userId = row[2] as? String, let username = row[3] as? String else { throw PersistenceError.decodingFailed } return AuthData( serverURL: serverURL, token: token, userId: userId, username: username ) } throw PersistenceError.notFound } public func saveAuth(_ auth: AuthData) async throws { try db.run( "INSERT OR REPLACE INTO auth (id, server_url, token, user_id, username) VALUES (1, ?, ?, ?, ?)", auth.serverURL, auth.token, auth.userId, auth.username ) } public func clearAuth() async throws { try db.run("DELETE FROM auth WHERE id = 1") } public func getPreference(key: String) async throws -> String? { let stmt = try db.prepare("SELECT value FROM preferences WHERE key = ?", key) for row in stmt { return row[0] as? String } return nil } public func setPreference(key: String, value: String) async throws { try db.run( "INSERT OR REPLACE INTO preferences (key, value) VALUES (?, ?)", key, value ) } public func clearAll() async throws { try db.run("DELETE FROM auth") try db.run("DELETE FROM preferences") } }