|
|
|
|
@ -121,11 +121,24 @@ nonisolated final class DatabaseService: Sendable { |
|
|
|
|
|
|
|
|
|
// MARK: - Maintenance |
|
|
|
|
|
|
|
|
|
/// Create a self-contained copy of the database at the given path using |
|
|
|
|
/// SQLite's online backup API. The copy includes all WAL data and is safe |
|
|
|
|
/// to serve or transfer without additional files. |
|
|
|
|
/// Create a self-contained copy of the database at the given path. |
|
|
|
|
/// |
|
|
|
|
/// Uses SQLite's `VACUUM INTO` rather than the online backup API. The online |
|
|
|
|
/// backup (`dbPool.backup(to:)`) produced copies whose FTS5 `tracks_ft` shadow |
|
|
|
|
/// tables were not transferred, so any `ValueObservation` opened on the copy |
|
|
|
|
/// failed with "no such table: tracks_ft" — which left the remote client's |
|
|
|
|
/// track list empty even though the row data copied fine. `VACUUM INTO` writes |
|
|
|
|
/// a fresh, fully-rebuilt, self-contained database that includes a functional |
|
|
|
|
/// FTS5 index and all committed data, with no WAL/SHM side files. |
|
|
|
|
/// |
|
|
|
|
/// `VACUUM INTO` requires the destination file to not already exist, so any |
|
|
|
|
/// stale file at `destinationPath` is removed first. |
|
|
|
|
func backup(to destinationPath: String) throws { |
|
|
|
|
try dbPool.backup(to: DatabaseQueue(path: destinationPath)) |
|
|
|
|
try? FileManager.default.removeItem(atPath: destinationPath) |
|
|
|
|
let escaped = destinationPath.replacingOccurrences(of: "'", with: "''") |
|
|
|
|
try dbPool.writeWithoutTransaction { db in |
|
|
|
|
try db.execute(sql: "VACUUM INTO '\(escaped)'") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - Write |
|
|
|
|
@ -447,7 +460,7 @@ nonisolated final class DatabaseService: Sendable { |
|
|
|
|
func createSmartPlaylist(name: String, searchQuery: String) throws -> SmartPlaylist { |
|
|
|
|
try dbPool.write { db in |
|
|
|
|
var smartPlaylist = SmartPlaylist( |
|
|
|
|
id: nil, name: name, searchQuery: searchQuery, createdAt: Date() |
|
|
|
|
id: nil, name: name, searchQuery: searchQuery, createdAt: Date(), conditions: nil |
|
|
|
|
) |
|
|
|
|
try smartPlaylist.insert(db) |
|
|
|
|
return smartPlaylist |
|
|
|
|
|