SwiftUI FileDocument using Sqlite and GRDB

547 Views Asked by At

In a document-based SwiftUI application, I’d like to persist each document to a separate Sqlite file using GRDB as a Sqlite wrapper. It’s straightforward to load Sqlite files in a document that implements the FileDocument protocol by creating a DatabaseQueue for the file to be loaded and using its .backup(to:) method to copy to an in-memory DatabaseQueue. How should I implement saving in the func fileWrapper(configuration: WriteConfiguration) method? There doesn’t seem to be an obvious way to use the same .backup(to:) approach.

I found an example application by Andre Yonadam that approaches this in the same way in a subclass of NSDocument:

override func write(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType, originalContentsURL absoluteOriginalContentsURL: URL?) throws {
    let destination = try DatabaseQueue(path: url.path)
    do {
        try memoryDBQueue.backup(to: destination)
    } catch {
        throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
    }
}

override func read(from url: URL, ofType typeName: String) throws {
    let source = try DatabaseQueue(path: url.path)
    do {
        try source.backup(to: memoryDBQueue)
    } catch {
        throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
    }
}
1

There are 1 best solutions below

3
Rich On BEST ANSWER

It’s perhaps not the cleanest solution but I worked around this by implementing a subclass of FileWrapper that knows how to write to Sqlite files:

class SqliteFileWrapper: FileWrapper {

    var databaseQueue: DatabaseQueue

    init (fromDatabaseQueue databaseQueue: DatabaseQueue) {
        self.databaseQueue = databaseQueue
        super.init(regularFileWithContents: "".data(using: .utf8)!)
    }

    required init?(coder inCoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func write(
        to url: URL,
        options: FileWrapper.WritingOptions = [],
        originalContentsURL: URL?
    ) throws {
        let destination = try DatabaseQueue(path: url.path)
        do {
            try databaseQueue.backup(to: destination)
        } catch {
            throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
        }
    }

}

and then in my FileDocument subclass I create a SqliteFileWrapper instead of a FileWrapper:

func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
    SqliteFileWrapper(fromDatabaseQueue: memoryDBQueue)
}