152 lines
5 KiB
Swift
152 lines
5 KiB
Swift
//
|
|
// SQLiteStore.swift
|
|
//
|
|
// Copyright 2026 Brendan Szymanski <hello@bscubed.dev>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
|
|
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")
|
|
}
|
|
}
|