From 52e73f58c53500dbdfbbcfd9bdd354243f347feb Mon Sep 17 00:00:00 2001 From: Tuomas Airaksinen Date: Mon, 13 Feb 2023 08:05:34 +0200 Subject: [PATCH] Backport mysword and mybible document install / backup / deletion --- .../android/control/backup/BackupControl.kt | 65 ++++-- .../view/activity/installzip/InstallZip.kt | 211 +++++++++++++----- .../bible/service/sword/SqliteSwordDriver.kt | 47 ++++ .../service/sword/mybible/MyBibleBook.kt | 38 ++-- .../service/sword/mysword/MySwordBook.kt | 22 +- 5 files changed, 275 insertions(+), 108 deletions(-) create mode 100644 app/src/main/java/net/bible/service/sword/SqliteSwordDriver.kt diff --git a/app/src/main/java/net/bible/android/control/backup/BackupControl.kt b/app/src/main/java/net/bible/android/control/backup/BackupControl.kt index c6529d6b9e..503f867d3b 100644 --- a/app/src/main/java/net/bible/android/control/backup/BackupControl.kt +++ b/app/src/main/java/net/bible/android/control/backup/BackupControl.kt @@ -34,6 +34,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import net.bible.android.BibleApplication +import net.bible.android.SharedConstants import net.bible.android.activity.BuildConfig import net.bible.android.activity.R import net.bible.android.activity.databinding.BackupViewBinding @@ -44,14 +45,17 @@ import net.bible.android.database.DATABASE_VERSION import net.bible.android.view.activity.base.ActivityBase import net.bible.android.view.activity.base.Dialogs import net.bible.android.view.activity.installzip.InstallZip +import net.bible.android.view.activity.page.MainBibleActivity import net.bible.android.view.activity.page.MainBibleActivity.Companion._mainBibleActivity import net.bible.android.view.util.Hourglass import net.bible.service.common.CommonUtils +import net.bible.service.common.CommonUtils.windowControl import net.bible.service.common.FileManager import net.bible.service.db.DATABASE_NAME import net.bible.service.db.DatabaseContainer import net.bible.service.db.DatabaseContainer.db import net.bible.service.download.isPseudoBook +import net.bible.service.sword.dbFile import net.bible.service.sword.mybible.isMyBibleBook import net.bible.service.sword.mysword.isMySwordBook import org.crosswire.jsword.book.Book @@ -73,7 +77,6 @@ import kotlin.coroutines.suspendCoroutine object BackupControl { - /** Backup database to Uri returned from ACTION_CREATE_DOCUMENT intent */ private suspend fun backupDatabaseToUri(activity: ActivityBase, uri: Uri, file: File) { @@ -118,16 +121,16 @@ object BackupControl { val subject = callingActivity.getString(R.string.backup_email_subject_2, CommonUtils.applicationNameMedium) val message = callingActivity.getString(R.string.backup_email_message_2, CommonUtils.applicationNameMedium) val uri = FileProvider.getUriForFile(callingActivity, BuildConfig.APPLICATION_ID + ".provider", targetFile) - val email = Intent(Intent.ACTION_SEND).apply { + val email = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) putExtra(Intent.EXTRA_SUBJECT, subject) putExtra(Intent.EXTRA_TEXT, message) type = "application/x-sqlite3" } - val chooserIntent = Intent.createChooser(email, getString(R.string.send_backup_file)) + val chooserIntent = Intent.createChooser(email, getString(R.string.send_backup_file)) chooserIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) hourglass.dismiss() - callingActivity.awaitIntent(chooserIntent) + callingActivity.awaitIntent(chooserIntent) } fun resetDatabase() { @@ -180,9 +183,9 @@ object BackupControl { var result: List? = null withContext(Dispatchers.Main) { result = suspendCoroutine { - val books = Books.installed().books.filter { !it.isMyBibleBook && !it.isMySwordBook && !it.isPseudoBook }.sortedBy { it.language } + val books = Books.installed().books.filter { !it.isPseudoBook }.sortedBy { it.language } val bookNames = books.map { - context.getString(R.string.something_with_parenthesis, it.name, it.language.code) + context.getString(R.string.something_with_parenthesis, it.name, "${it.initials}, ${it.language.code}") }.toTypedArray() val checkedItems = bookNames.map { false }.toBooleanArray() @@ -241,22 +244,43 @@ object BackupControl { } } + fun addModuleFile(outFile: ZipOutputStream, moduleFile: File) { + FileInputStream(moduleFile).use { inFile -> + BufferedInputStream(inFile).use { origin -> + val fileNameInsideZip = moduleFile.relativeTo(moduleDir).path + val entry = ZipEntry(fileNameInsideZip) + outFile.putNextEntry(entry) + origin.copyTo(outFile) + } + } + } + withContext(Dispatchers.IO) { ZipOutputStream(FileOutputStream(zipFile)).use { outFile -> for(b in books) { val bmd = b.bookMetaData as SwordBookMetaData - val configFile = bmd.configFile - val rootDir = configFile.parentFile!!.parentFile!! - - addFile(outFile, rootDir, configFile) - val dataPath = bmd.getProperty("DataPath") - val dataDir = File(rootDir, dataPath).run { - if(listOf(BookCategory.DICTIONARY, BookCategory.GENERAL_BOOK, BookCategory.MAPS).contains(b.bookCategory)) - parentFile - else this - } - for(f in dataDir.walkTopDown().filter { it.isFile }) { - addFile(outFile, rootDir, f) + if (b.isMyBibleBook) { + addModuleFile(outFile, b.dbFile) + } else if(b.isMySwordBook) { + addModuleFile(outFile, b.dbFile) + } else { + val configFile = bmd.configFile + val rootDir = configFile.parentFile!!.parentFile!! + addFile(outFile, rootDir, configFile) + val dataPath = bmd.getProperty("DataPath") + val dataDir = File(rootDir, dataPath).run { + if (listOf( + BookCategory.DICTIONARY, + BookCategory.GENERAL_BOOK, + BookCategory.MAPS + ).contains(b.bookCategory) + ) + parentFile + else this + } + for (f in dataDir.walkTopDown().filter { it.isFile }) { + addFile(outFile, rootDir, f) + } } } } @@ -307,7 +331,7 @@ object BackupControl { // send intent to pick file var ok = true - val result = suspendCoroutine { + val result = suspendCoroutine { AlertDialog.Builder(callingActivity) .setTitle(callingActivity.getString(R.string.backup_backup_title)) .setMessage(callingActivity.getString(R.string.backup_backup_message)) @@ -434,7 +458,7 @@ object BackupControl { suspend fun startBackupOldAppDatabase(callingActivity: ActivityBase, file: File) { val result = withContext(Dispatchers.Main) { - suspendCoroutine { + suspendCoroutine { AlertDialog.Builder(callingActivity) .setTitle(callingActivity.getString(R.string.backup_backup_title)) .setMessage(callingActivity.getString(R.string.backup_backup_message)) @@ -510,6 +534,7 @@ object BackupControl { activity.awaitIntent(intent) } + private var moduleDir: File = SharedConstants.MODULE_DIR private lateinit var internalDbDir : File private lateinit var internalDbBackupDir: File // copy of db is created in this dir when doing backups private const val MODULE_BACKUP_NAME = "modules.zip" diff --git a/app/src/main/java/net/bible/android/view/activity/installzip/InstallZip.kt b/app/src/main/java/net/bible/android/view/activity/installzip/InstallZip.kt index ffe314c99e..4648b4ce81 100644 --- a/app/src/main/java/net/bible/android/view/activity/installzip/InstallZip.kt +++ b/app/src/main/java/net/bible/android/view/activity/installzip/InstallZip.kt @@ -38,17 +38,23 @@ import android.app.AlertDialog import android.content.Intent import android.net.Uri import android.os.Bundle +import android.provider.OpenableColumns import android.util.Log import android.view.View import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import net.bible.android.SharedConstants import net.bible.android.activity.databinding.ActivityInstallZipBinding import net.bible.android.control.event.ABEventBus import net.bible.android.control.event.ToastEvent import net.bible.android.view.activity.base.ActivityBase -import net.bible.android.view.activity.page.MainBibleActivity.Companion._mainBibleActivity +import net.bible.android.view.activity.page.MainBibleActivity +import net.bible.service.sword.mybible.addManuallyInstalledMyBibleBooks +import net.bible.service.sword.mybible.addMyBibleBook +import net.bible.service.sword.mysword.addManuallyInstalledMySwordBooks +import net.bible.service.sword.mysword.addMySwordBook import java.io.InputStream import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -61,16 +67,17 @@ import kotlin.math.roundToInt */ class ModulesExists(val files: List) : Exception() +class CantOverwrite(val files: List) : Exception() class InvalidModule : Exception() const val TAG = "InstallZip" class ZipHandler( - private val newInputStream: () -> InputStream?, - private val updateProgress: (progress: Int) -> Unit, - private val finish: (finishResult: Int) -> Unit, - private val activity: Activity + private val newInputStream: () -> InputStream?, + private val updateProgress: (progress: Int) -> Unit, + private val finish: (finishResult: Int) -> Unit, + private val activity: Activity ) { private var totalEntries = 0 @@ -99,9 +106,13 @@ class ZipHandler( if (name.startsWith(SwordConstants.DIR_CONF + "/") && name.endsWith(SwordConstants.EXTENSION_CONF)) modsDirFound = true else if (name.startsWith(SwordConstants.DIR_CONF + "/")) { - } else if (name.startsWith(SwordConstants.DIR_DATA + "/")) + // Ignore directory + } else if (name.startsWith(SwordConstants.DIR_DATA + "/")) { modulesFound = true - else { + } else if (name.startsWith("mysword/") || name.startsWith("mybible/")) { + modulesFound = true + modsDirFound = true + } else { zin.close() throw InvalidModule() } @@ -120,16 +131,15 @@ class ZipHandler( @Throws(IOException::class, BookException::class) private suspend fun installZipFile() = withContext(Dispatchers.IO) { - val zin = ZipInputStream(newInputStream()) - val confFiles = ArrayList() val targetDirectory = SwordBookPath.getSwordDownloadDir() - zin.use { zin -> + val errors: MutableList = mutableListOf() + ZipInputStream(newInputStream()).use { zIn -> var ze: ZipEntry? var count: Int var entryNum = 0 val buffer = ByteArray(8192) - ze = zin.nextEntry + ze = zIn.nextEntry while (ze != null) { val name = ze.name.replace('\\', '/') @@ -143,19 +153,26 @@ class ZipHandler( throw IOException() if (ze.isDirectory) { - ze = zin.nextEntry + ze = zIn.nextEntry continue } - val fout = FileOutputStream(file) - fout.use { fout -> - count = zin.read(buffer) - while (count != -1) { - fout.write(buffer, 0, count) - count = zin.read(buffer) + try { + FileOutputStream(file).use { fOut -> + count = zIn.read(buffer) + while (count != -1) { + fOut.write(buffer, 0, count) + count = zIn.read(buffer) + } } + } catch (e: IOException) { + errors.add(file.name) + Log.e(TAG, "Error in writing ${file.name}", e); } onProgressUpdate(++entryNum) - ze = zin.nextEntry + ze = zIn.nextEntry + } + if(errors.isNotEmpty()) { + throw CantOverwrite(errors) } } // Load configuration files & register books @@ -165,6 +182,8 @@ class ZipHandler( me.driver = bookDriver SwordBookDriver.registerNewBook(me) } + addManuallyInstalledMyBibleBooks() + addManuallyInstalledMySwordBooks() } suspend fun execute() = withContext(Dispatchers.Main) { @@ -181,7 +200,7 @@ class ZipHandler( } catch (e: InvalidModule) { R_INVALID_MODULE } catch (e: ModulesExists) { - doInstall = suspendCoroutine { + doInstall = suspendCoroutine { AlertDialog.Builder(activity) .setTitle(R.string.overwrite_files_title) .setMessage(activity.getString(R.string.overwrite_files, "\n" + e.files.joinToString("\n"))) @@ -230,6 +249,10 @@ class ZipHandler( private const val R_OK = 4 } } +open class SqliteInstallError: Error() +class CantRead: SqliteInstallError() +class InvalidFile: SqliteInstallError() +class CantWrite: SqliteInstallError() class InstallZip : ActivityBase() { private lateinit var binding: ActivityInstallZipBinding @@ -239,50 +262,132 @@ class InstallZip : ActivityBase() { binding = ActivityInstallZipBinding.inflate(layoutInflater) setContentView(binding.root) super.buildActivityComponent().inject(this) - when(intent?.action) { - Intent.ACTION_VIEW -> installZip(intent!!.data!!) - Intent.ACTION_SEND -> installZip(intent.getParcelableExtra(Intent.EXTRA_STREAM)!!) - Intent.ACTION_SEND_MULTIPLE -> { - for (uri in intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)!!) { - installZip(uri) + lifecycleScope.launch { + when (intent?.action) { + Intent.ACTION_VIEW -> installZip(intent!!.data!!) + Intent.ACTION_SEND -> installZip(intent.getParcelableExtra(Intent.EXTRA_STREAM)!!) + Intent.ACTION_SEND_MULTIPLE -> { + for (uri in intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)!!) { + installZip(uri) + } + } + else -> { + getFileFromUserAndInstall() } } - else -> { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.type = "application/zip" - startActivityForResult(intent, PICK_FILE) + } + } + + private suspend fun getFileFromUserAndInstall() { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) + intent.type = "application/*" + + val result = awaitIntent(intent) + if (result?.resultCode == Activity.RESULT_OK) { + try { + installFromFile(result.resultData!!.data!!) + } catch (e: SqliteInstallError) { + Log.e(TAG, "Error occurred in installing module", e) + ABEventBus.post(ToastEvent(R.string.error_occurred)) } } + finish() } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - when (requestCode) { - PICK_FILE -> if (resultCode == Activity.RESULT_OK) { - installZip(data!!.data!!) - } else if (resultCode == Activity.RESULT_CANCELED) - finish() + private suspend fun installFromFile(uri: Uri): Boolean { + val displayName = contentResolver.query(uri, null, null, null, null)?.use { + it.moveToFirst() + val idx = it.getColumnIndex(OpenableColumns.DISPLAY_NAME) + it.getString(idx) + }?: throw CantRead() + + if (displayName.lowercase().endsWith(".zip")) + return installZip(uri) + + val filetype = when { + displayName.lowercase().endsWith(".sqlite3") -> "mybible" + displayName.lowercase().endsWith(".mybible") -> "mysword" + else -> throw InvalidFile() + } + + binding.loadingIndicator.visibility = View.VISIBLE + contentResolver.openInputStream(uri).use { fIn -> + fIn ?: throw CantRead() + val outDir = File(SharedConstants.MODULE_DIR, filetype) + outDir.mkdirs() + val outFile = File(outDir, displayName) + if(outFile.exists()) { + val doInstall = suspendCoroutine { + AlertDialog.Builder(this) + .setTitle(R.string.overwrite_files_title) + .setMessage(getString(R.string.overwrite_files, "$filetype/$displayName")) + .setPositiveButton(R.string.yes) {_, _ -> it.resume(true)} + .setNeutralButton(R.string.cancel) {_, _ -> it.resume(false)} + .setOnCancelListener {_ -> it.resume(false)} + .show() + } + if(!doInstall) { + ABEventBus.post(ToastEvent(R.string.install_zip_canceled)) + return false + } + } + + if ((outFile.exists() && !outFile.canWrite()) || (!outFile.exists() && !outDir.canWrite())) { + throw CantWrite() + } + + withContext(Dispatchers.IO) { + val header = ByteArray(16) + fIn.read(header) + if (String(header) == "SQLite format 3\u0000") { + val out = FileOutputStream(outFile) + withContext(Dispatchers.IO) { + out.write(header) + fIn.copyTo(out) + out.close() + } + val book = when(filetype) { + "mybible" -> addMyBibleBook(outFile) + "mysword" -> addMySwordBook(outFile) + else -> throw RuntimeException() + } + if(book == null) { + outFile.delete() + throw InvalidFile() + } + } + else { + throw InvalidFile() + } + } } + binding.loadingIndicator.visibility = View.GONE + ABEventBus.post(ToastEvent(R.string.install_zip_successfull)) + MainBibleActivity._mainBibleActivity?.updateDocuments() + setResult(Activity.RESULT_OK) + finish() + return true } - private fun installZip(uri: Uri) { - binding.installZipLabel.text = getString(R.string.checking_zip_file) + private suspend fun installZip(uri: Uri): Boolean { + var result = false + binding.installZipLabel.text = getString(R.string.checking_zip_file) val zh = ZipHandler( - {contentResolver.openInputStream(uri)}, - {percent -> updateProgress(percent)}, - {finishResult -> - setResult(finishResult); - _mainBibleActivity?.updateDocuments() - finish() - }, + {contentResolver.openInputStream(uri)}, + {percent -> updateProgress(percent)}, + {finishResult -> + result = finishResult == Activity.RESULT_OK + setResult(finishResult); + MainBibleActivity._mainBibleActivity?.updateDocuments() + finish() + }, this ) - lifecycleScope.launch(Dispatchers.Main) { - binding.loadingIndicator.visibility = View.VISIBLE - zh.execute() - binding.loadingIndicator.visibility = View.GONE - } + binding.loadingIndicator.visibility = View.VISIBLE + zh.execute() + binding.loadingIndicator.visibility = View.GONE + return result } override fun onBackPressed() {} @@ -293,8 +398,4 @@ class InstallZip : ActivityBase() { binding.progressBar.progress = percentValue } - - companion object { - private const val PICK_FILE = 1 - } } diff --git a/app/src/main/java/net/bible/service/sword/SqliteSwordDriver.kt b/app/src/main/java/net/bible/service/sword/SqliteSwordDriver.kt new file mode 100644 index 0000000000..0677b2a89d --- /dev/null +++ b/app/src/main/java/net/bible/service/sword/SqliteSwordDriver.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 Martin Denham, Tuomas Airaksinen and the AndBible contributors. + * + * This file is part of AndBible: Bible Study (http://github.com/AndBible/and-bible). + * + * AndBible 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. + * + * AndBible 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 AndBible. + * If not, see http://www.gnu.org/licenses/. + */ +package net.bible.service.sword + +import org.crosswire.jsword.book.Book +import org.crosswire.jsword.book.Books +import org.crosswire.jsword.book.basic.AbstractBookDriver +import java.io.File + +val Book.dbFile get() = File(getProperty("AndBibleDbFile")) + +/** + * Driver for both MySword and MyBible books + */ +class SqliteSwordDriver: AbstractBookDriver() { + override fun getBooks(): Array { + return emptyArray() + } + + override fun getDriverName(): String { + return "SqliteSwordDriver" + } + + override fun isDeletable(book: Book): Boolean { + return book.dbFile.canWrite() + } + + override fun delete(book: Book) { + book.dbFile.delete() + Books.installed().removeBook(book) + } +} + diff --git a/app/src/main/java/net/bible/service/sword/mybible/MyBibleBook.kt b/app/src/main/java/net/bible/service/sword/mybible/MyBibleBook.kt index 3b8d577b3b..8a4283d94d 100644 --- a/app/src/main/java/net/bible/service/sword/mybible/MyBibleBook.kt +++ b/app/src/main/java/net/bible/service/sword/mybible/MyBibleBook.kt @@ -21,6 +21,7 @@ import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteException import android.util.Log import net.bible.android.BibleApplication +import net.bible.service.sword.SqliteSwordDriver import org.crosswire.jsword.book.Book import org.crosswire.jsword.book.BookCategory import org.crosswire.jsword.book.Books @@ -42,13 +43,22 @@ import org.crosswire.jsword.passage.KeyUtil import java.io.File import java.io.IOException -private fun getConfig(initials: String, description: String, language: String, category: String, hasStrongsDef: Boolean, hasStrongs: Boolean): String { +private fun getConfig( + initials: String, + description: String, + language: String, + category: String, + hasStrongsDef: Boolean, + hasStrongs: Boolean, + moduleFileName: String +): String { var conf = """ [$initials] Description=$description Abbreviation=$initials Category=$category AndBibleMyBibleModule=1 +AndBibleDbFile=$moduleFileName Lang=$language Version=0.0 Encoding=UTF-8 @@ -69,20 +79,6 @@ Versification=KJVA""" const val TAG = "MyBibleBook" -class MockMyBibleDriver: AbstractBookDriver() { - override fun getBooks(): Array { - return emptyArray() - } - - override fun getDriverName(): String { - return "MyBible" - } - - override fun isDeletable(dead: Book?): Boolean { - return false - } -} - class SqliteVerseBackendState(private val sqliteFile: File, val moduleName: String?): OpenFileState { constructor(sqliteFile: File, metadata: SwordBookMetaData): this(sqliteFile, null) { this.metadata = metadata @@ -156,11 +152,19 @@ class SqliteVerseBackendState(private val sqliteFile: File, val moduleName: Stri else -> "Illegal" } - val conf = getConfig(initials, description, language, category, hasStrongsDef, hasStrongs) + val conf = getConfig( + initials = initials, + description = description, + language = language, + category = category, + hasStrongsDef = hasStrongsDef, + hasStrongs = hasStrongs, + moduleFileName = db.path, + ) Log.i(TAG, "Creating MyBibleBook metadata $initials, $description $language $category") val metadata = SwordBookMetaData(conf.toByteArray(), initials) - metadata.driver = MockMyBibleDriver() + metadata.driver = SqliteSwordDriver() this.metadata = metadata return@synchronized metadata } diff --git a/app/src/main/java/net/bible/service/sword/mysword/MySwordBook.kt b/app/src/main/java/net/bible/service/sword/mysword/MySwordBook.kt index 6e0c351f29..ad5594ff27 100644 --- a/app/src/main/java/net/bible/service/sword/mysword/MySwordBook.kt +++ b/app/src/main/java/net/bible/service/sword/mysword/MySwordBook.kt @@ -21,6 +21,7 @@ import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteException import android.util.Log import net.bible.android.BibleApplication +import net.bible.service.sword.SqliteSwordDriver import org.crosswire.jsword.book.Book import org.crosswire.jsword.book.BookCategory import org.crosswire.jsword.book.Books @@ -49,6 +50,7 @@ Description=${data.description} Abbreviation=${data.abbreviation} Category=${data.category} AndBibleMySwordModule=1 +AndBibleDbFile=${data.moduleFileName} Lang=${data.language} Version=0.0 Encoding=UTF-8 @@ -70,21 +72,8 @@ Versification=KJVA""" const val TAG = "MySwordBook" -class MockMySwordDriver: AbstractBookDriver() { - override fun getBooks(): Array { - return emptyArray() - } - - override fun getDriverName(): String { - return "MySword" - } - - override fun isDeletable(dead: Book?): Boolean { - return false - } -} - class MySwordModuleInfo ( + val moduleFileName: String, val initials: String, val title: String, val description: String, @@ -172,7 +161,8 @@ class SqliteVerseBackendState(private val sqliteFile: File, val moduleName: Stri hasStrongs = categoryAbbreviation == "bbl" && getBoolean(strongColumn), language = getString(languageColumn, "eng"), category = category, - isStrongsDict = categoryAbbreviation == "dct" && getBoolean(strongColumn) + isStrongsDict = categoryAbbreviation == "dct" && getBoolean(strongColumn), + moduleFileName = db.path ) } @@ -180,7 +170,7 @@ class SqliteVerseBackendState(private val sqliteFile: File, val moduleName: Stri Log.i(TAG, "Creating MySwordBook metadata $initials $category") val metadata = SwordBookMetaData(conf.toByteArray(), initials) - metadata.driver = MockMySwordDriver() + metadata.driver = SqliteSwordDriver() this.metadata = metadata return@synchronized metadata }