gloss-ios/gloss/AppDatabase.swift

209 lines
6.8 KiB
Swift
Raw Normal View History

import Foundation
import GRDB
/// AppDatabase lets the application access the database.
///
/// It applies the pratices recommended at
/// <https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md>
struct AppDatabase {
/// Creates an `AppDatabase`, and make sure the database schema is ready.
init(_ dbWriter: any DatabaseWriter) throws {
self.dbWriter = dbWriter
try migrator.migrate(dbWriter)
}
/// Provides access to the database.
///
/// Application can use a `DatabasePool`, while SwiftUI previews and tests
/// can use a fast in-memory `DatabaseQueue`.
///
/// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections>
private let dbWriter: any DatabaseWriter
/// The DatabaseMigrator that defines the database schema.
///
/// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>
private var migrator: DatabaseMigrator {
var migrator = DatabaseMigrator()
#if DEBUG
// Speed up development by nuking the database when migrations change
// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>
migrator.eraseDatabaseOnSchemaChange = true
#endif
migrator.registerMigration("createLine") { db in
// Create a table
// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseschema>
try db.create(table: "Line") { t in
t.autoIncrementedPrimaryKey("id")
t.column("body", .text).notNull()
t.column("chap", .integer).notNull()
t.column("book", .text).notNull()
t.column("verse", .integer)
}
2023-03-11 20:31:48 -08:00
try db.create(table: "Seg") { t in
t.autoIncrementedPrimaryKey("id")
t.column("seg_id", .integer).notNull()
t.column("line_id", .integer).notNull()
t.column("book", .text).notNull()
}
try db.create(table: "Ribbon") { t in
t.autoIncrementedPrimaryKey("id")
2023-07-21 17:19:30 -07:00
t.column("title", .text).notNull()
t.column("book", .text).notNull()
t.column("scrollOffset", .integer).notNull()
2023-04-21 17:06:30 -07:00
t.column("scrollId", .text)
}
2023-02-28 14:03:58 -08:00
try db.create(table: "SelectedRibbon") { t in
t.autoIncrementedPrimaryKey("id")
t.column("ribbonId", .integer).notNull()
}
2023-03-11 20:31:48 -08:00
2023-04-21 17:06:30 -07:00
try db.create(table: "ScrollState") { t in
t.autoIncrementedPrimaryKey("id")
t.column("scrollId", .text).notNull()
t.column("scrollOffset", .integer).notNull()
}
2023-07-21 17:19:30 -07:00
try db.create(table: "foo4") { t in
2023-03-11 20:31:48 -08:00
t.autoIncrementedPrimaryKey("id")
t.column("ribbonId", .integer).notNull()
}
}
// Migrations for future application versions will be inserted here:
// migrator.registerMigration(...) { db in
// ...
// }
return migrator
}
}
// MARK: - Database Access: Writes
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
extension AppDatabase {
2023-02-28 14:03:58 -08:00
func saveRibbon(_ ribbon: inout Ribbon) async throws {
// if ribbon.name.isEmpty {
// throw ValidationError.missingName
// }
ribbon = try await dbWriter.write { [ribbon] db in
try ribbon.saved(db)
}
}
2023-03-01 10:47:34 -08:00
2023-02-28 14:03:58 -08:00
func saveSelectedRibbon(_ selectedRibbon: inout SelectedRibbon) async throws {
2023-03-01 10:47:34 -08:00
// if ribbon.name.isEmpty {
2023-02-28 14:03:58 -08:00
// throw ValidationError.missingName
// }
2023-03-01 10:47:34 -08:00
try await dbWriter.write { [selectedRibbon] db in
try selectedRibbon.update(db)
2023-02-28 14:03:58 -08:00
}
}
2023-04-21 17:06:30 -07:00
func saveScrollState(_ scrollState: inout ScrollState) async throws {
// if ribbon.name.isEmpty {
// throw ValidationError.missingName
// }
try await dbWriter.write { [scrollState] db in
try scrollState.update(db)
}
}
2023-07-21 17:19:30 -07:00
func importJson(_ filename: String, _ db: Database) throws {
let importJson : JsonImport = load(filename)
2023-03-01 10:47:34 -08:00
2023-07-21 17:19:30 -07:00
if try Line.all().isEmpty(db) {
for l in importJson.lines {
print("importing Lines")
_ = try l.inserted(db)
}
2023-07-21 17:19:30 -07:00
for l in importJson.segs {
print("importing SEGS")
_ = try l.inserted(db)
}
}
}
2023-07-21 17:19:30 -07:00
/// Create random Lines if the database is empty.
func initDatabase() throws {
2023-03-11 20:31:48 -08:00
2023-07-21 17:19:30 -07:00
try dbWriter.write { db in
2023-03-11 20:31:48 -08:00
if try Line.all().isEmpty(db) {
2023-07-21 17:19:30 -07:00
try importJson("john_export.json", db)
try importJson("mark_export.json", db)
_ = try Ribbon(id: 1, title: "John", book: "bible.john", scrollId: "1", scrollOffset: 0).inserted(db)
_ = try Ribbon(id: 2, title: "Gospel of Mark", book: "bible.mark", scrollId: "1", scrollOffset: 300).inserted(db)
2023-02-28 14:03:58 -08:00
_ = try SelectedRibbon(id: 1, ribbonId: 1).inserted(db)
}
}
}
// static let uiTestLines = [
// Line(id: nil, name: "Arthur", score: 5),
// Line(id: nil, name: "Barbara", score: 6),
// Line(id: nil, name: "Craig", score: 8),
// Line(id: nil, name: "David", score: 4),
// Line(id: nil, name: "Elena", score: 1),
// Line(id: nil, name: "Frederik", score: 2),
// Line(id: nil, name: "Gilbert", score: 7),
// Line(id: nil, name: "Henriette", score: 3)]
// func createLinesForUITests() throws {
// try dbWriter.write { db in
// try AppDatabase.uiTestLines.forEach { Line in
// _ = try Line.inserted(db) // insert but ignore inserted id
// }
// }
// }
}
// MARK: - Database Access: Reads
2023-02-28 14:03:58 -08:00
// This demo app does not provide any specific reading method, and insteadKK
// gives an unrestricted read-only access to the rest of the application.
// In your app, you are free to choose another path, and define focused
// reading methods.
extension AppDatabase {
/// Provides a read-only access to the database
var reader: DatabaseReader {
dbWriter
}
}