Skip to content

Commit ff75101

Browse files
committed
Refactor database initialization and management
This commit refactors the database initialization and management logic across different platforms (Android, iOS, JVM, WebAssembly). Key changes include: - Modified `SafeRepo` and its platform-specific implementations (`AndroidSafeRepo`, `IosSafeRepo`, `JvmSafeRepo`, `WebSafeRepo`, `JvmTestSafeRepo`) to make database operations (`buildDbIfNeed`, `decrypt`, `rekey`, `encrypt`, `closeDatabase`) suspend functions. - Introduced a `createSchema` suspend function in `SqlDelightDbHolder` and its implementations (`JdbcDatabaseHolder`, `WebDatabaseHolder`) to handle asynchronous schema creation using `awaitCreate` and `awaitMigrate` from SQLDelight. - Updated `SplashViewModel` to call `safeRepo.buildDbIfNeed()` before checking the database state, ensuring the database is initialized. - Removed direct database schema creation from the `init` blocks of `WebDatabaseHolder` and `JdbcDatabaseTestHolder`. - Adjusted `ChangePasswordUseCase` to be a suspend function. - Updated Koin module for WebAssembly (`sharedModules.wasmJs.kt`) to reflect changes in `WebSafeRepo` instantiation. - Replaced `runBlocking` with `runTest` in `CryptInstrumentedTest` for testing suspend functions. - Ensured `noteDAO` in platform-specific `SafeRepo` implementations accesses `databaseHolder` only after it's been initialized by `buildDbIfNeed`.
1 parent 1ed8d23 commit ff75101

File tree

15 files changed

+76
-74
lines changed

15 files changed

+76
-74
lines changed

app/android/src/androidTest/java/com/softartdev/notedelight/CryptInstrumentedTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import androidx.test.filters.MediumTest
66
import com.softartdev.notedelight.model.PlatformSQLiteState
77
import com.softartdev.notedelight.repository.SafeRepo
88
import io.github.aakira.napier.Napier
9-
import kotlinx.coroutines.runBlocking
9+
import kotlinx.coroutines.test.runTest
1010
import org.junit.Assert.assertEquals
1111
import org.junit.Test
1212
import org.junit.runner.RunWith
@@ -20,10 +20,10 @@ class CryptInstrumentedTest {
2020
private val password = "password"
2121

2222
@Test
23-
fun cryptTest() {
23+
fun cryptTest() = runTest {
2424
assertEquals(PlatformSQLiteState.DOES_NOT_EXIST, safeRepo.databaseState)
2525
safeRepo.buildDbIfNeed()
26-
val count: Long = runBlocking { safeRepo.noteDAO.count() }
26+
val count: Long = safeRepo.noteDAO.count()
2727
Napier.d("notes count = $count")
2828
assertEquals(PlatformSQLiteState.UNENCRYPTED, safeRepo.databaseState)
2929
safeRepo.encrypt(SpannableStringBuilder(password))

core/data/db-sqldelight/src/androidMain/kotlin/com/softartdev/notedelight/repository/AndroidSafeRepo.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,23 @@ class AndroidSafeRepo(
2929
}
3030

3131
override val noteDAO: NoteDAO
32-
get() = SqlDelightNoteDAO(buildDbIfNeed().noteQueries, coroutineDispatchers)
32+
get() = SqlDelightNoteDAO(databaseHolder!!.noteQueries, coroutineDispatchers)
3333

3434
override val dbPath: String
3535
get() = context.getDatabasePath(DB_NAME).absolutePath
3636

37-
override fun buildDbIfNeed(passphrase: CharSequence): SqlDelightDbHolder = synchronized(this) {
37+
override suspend fun buildDbIfNeed(passphrase: CharSequence): SqlDelightDbHolder {
3838
var instance = databaseHolder
3939
if (instance == null) {
4040
val passCopy = SpannableStringBuilder(passphrase) // threadsafe
4141
instance = AndroidDatabaseHolder(context, passCopy)
42+
instance.createSchema()
4243
databaseHolder = instance
4344
}
4445
return instance
4546
}
4647

47-
override fun decrypt(oldPass: CharSequence) {
48+
override suspend fun decrypt(oldPass: CharSequence) {
4849
val originalFile = context.getDatabasePath(DB_NAME)
4950

5051
val oldCopy = SpannableStringBuilder(oldPass) // threadsafe
@@ -57,7 +58,7 @@ class AndroidSafeRepo(
5758
buildDbIfNeed()
5859
}
5960

60-
override fun rekey(oldPass: CharSequence, newPass: CharSequence) {
61+
override suspend fun rekey(oldPass: CharSequence, newPass: CharSequence) {
6162
val passphrase = SpannableStringBuilder(newPass) // threadsafe
6263

6364
val androidDatabaseHolder = buildDbIfNeed(oldPass) as AndroidDatabaseHolder
@@ -67,7 +68,7 @@ class AndroidSafeRepo(
6768
buildDbIfNeed(newPass)
6869
}
6970

70-
override fun encrypt(newPass: CharSequence) {
71+
override suspend fun encrypt(newPass: CharSequence) {
7172
val passphrase = SpannableStringBuilder(newPass) // threadsafe
7273

7374
closeDatabase()
@@ -91,7 +92,7 @@ class AndroidSafeRepo(
9192
value = if (sqlCursor.next().value) sqlCursor.getString(0) else null
9293
)
9394

94-
override fun closeDatabase() = synchronized(this) {
95+
override suspend fun closeDatabase() = synchronized(this) {
9596
databaseHolder?.close()
9697
databaseHolder = null
9798
}

core/data/db-sqldelight/src/androidUnitTest/kotlin/com/softartdev/notedelight/JdbcDatabaseTestHolder.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,11 @@ import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
44
import com.softartdev.notedelight.db.NoteDb
55
import com.softartdev.notedelight.db.SqlDelightDbHolder
66
import com.softartdev.notedelight.db.createQueryWrapper
7-
import kotlinx.coroutines.runBlocking
87

98
class JdbcDatabaseTestHolder: SqlDelightDbHolder {
109
override val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
1110
override val noteDb: NoteDb = createQueryWrapper(driver)
1211
override val noteQueries = noteDb.noteQueries
1312

14-
init {
15-
runBlocking { NoteDb.Schema.create(driver).await() }
16-
}
17-
1813
override fun close() = driver.close()
1914
}

core/data/db-sqldelight/src/androidUnitTest/kotlin/com/softartdev/notedelight/JvmTestSafeRepo.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import com.softartdev.notedelight.util.CoroutineDispatchers
1414
*/
1515
class JvmTestSafeRepo(private val coroutineDispatchers: CoroutineDispatchers) : SafeRepo() {
1616
@Volatile
17-
private var databaseHolder: JdbcDatabaseTestHolder? = buildDbIfNeed()
17+
private var databaseHolder: JdbcDatabaseTestHolder? = null
1818

1919
override val databaseState: PlatformSQLiteState
2020
get() = TODO("Not yet implemented")
@@ -28,26 +28,27 @@ class JvmTestSafeRepo(private val coroutineDispatchers: CoroutineDispatchers) :
2828
override val dbPath: String
2929
get() = TODO("Not yet implemented")
3030

31-
override fun buildDbIfNeed(passphrase: CharSequence): JdbcDatabaseTestHolder = synchronized(this) {
31+
override suspend fun buildDbIfNeed(passphrase: CharSequence): JdbcDatabaseTestHolder {
3232
var instance = databaseHolder
3333
if (instance == null) {
3434
instance = JdbcDatabaseTestHolder()
35+
instance.createSchema()
3536
databaseHolder = instance
3637
}
3738
return instance
3839
}
3940

40-
override fun decrypt(oldPass: CharSequence) {
41+
override suspend fun decrypt(oldPass: CharSequence) {
4142
closeDatabase()
4243
buildDbIfNeed()
4344
}
4445

45-
override fun rekey(oldPass: CharSequence, newPass: CharSequence) {
46+
override suspend fun rekey(oldPass: CharSequence, newPass: CharSequence) {
4647
closeDatabase()
4748
buildDbIfNeed(newPass)
4849
}
4950

50-
override fun encrypt(newPass: CharSequence) {
51+
override suspend fun encrypt(newPass: CharSequence) {
5152
closeDatabase()
5253
buildDbIfNeed(newPass)
5354
}
@@ -67,7 +68,7 @@ class JvmTestSafeRepo(private val coroutineDispatchers: CoroutineDispatchers) :
6768
value = if (sqlCursor.next().value) sqlCursor.getString(0) else null
6869
)
6970

70-
override fun closeDatabase() = synchronized(this) {
71+
override suspend fun closeDatabase() = synchronized(this) {
7172
databaseHolder?.close()
7273
databaseHolder = null
7374
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
package com.softartdev.notedelight.db
22

3+
import app.cash.sqldelight.async.coroutines.awaitCreate
34
import app.cash.sqldelight.db.SqlDriver
45
import com.softartdev.notedelight.shared.db.NoteQueries
6+
import io.github.aakira.napier.Napier
57

68
interface SqlDelightDbHolder : DatabaseHolder {
79
val driver: SqlDriver
810
val noteDb: NoteDb
911
val noteQueries: NoteQueries
12+
13+
suspend fun createSchema() {
14+
try {
15+
Napier.d("Creating database schema")
16+
NoteDb.Schema.awaitCreate(driver)
17+
Napier.d("Database schema created")
18+
} catch (t: Throwable) {
19+
Napier.e("Error creating database schema", t)
20+
}
21+
}
1022
}

core/data/db-sqldelight/src/iosMain/kotlin/com/softartdev/notedelight/repository/IosSafeRepo.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,35 @@ class IosSafeRepo(private val coroutineDispatchers: CoroutineDispatchers) : Safe
1717
get() = IosCipherUtils.getDatabaseState(DB_NAME)
1818

1919
override val noteDAO: NoteDAO
20-
get() = SqlDelightNoteDAO(buildDbIfNeed().noteQueries, coroutineDispatchers)
20+
get() = SqlDelightNoteDAO(dbHolder!!.noteQueries, coroutineDispatchers)
2121

2222
override val dbPath: String
2323
get() = IosCipherUtils.getDatabasePath(DB_NAME)
2424

25-
override fun buildDbIfNeed(passphrase: CharSequence): IosDatabaseHolder {
25+
override suspend fun buildDbIfNeed(passphrase: CharSequence): IosDatabaseHolder {
2626
var instance = dbHolder
2727
if (instance == null) {
2828
val passCopy: String? = if (passphrase.isNotEmpty()) passphrase.toString() else null
2929
instance = IosDatabaseHolder(key = passCopy)
30+
instance.createSchema()
3031
dbHolder = instance
3132
}
3233
return instance
3334
}
3435

35-
override fun decrypt(oldPass: CharSequence) {
36+
override suspend fun decrypt(oldPass: CharSequence) {
3637
closeDatabase()
3738
IosCipherUtils.decrypt(oldPass.toString(), DB_NAME)
3839
dbHolder = IosDatabaseHolder()
3940
}
4041

41-
override fun rekey(oldPass: CharSequence, newPass: CharSequence) {
42+
override suspend fun rekey(oldPass: CharSequence, newPass: CharSequence) {
4243
closeDatabase()
4344
dbHolder = IosDatabaseHolder(key = oldPass.toString(), rekey = newPass.toString())
4445
dbHolder?.driver?.execute(null, "VACUUM;", 0)
4546
}
4647

47-
override fun encrypt(newPass: CharSequence) {
48+
override suspend fun encrypt(newPass: CharSequence) {
4849
closeDatabase()
4950
IosCipherUtils.encrypt(newPass.toString(), DB_NAME)
5051
dbHolder = IosDatabaseHolder(key = newPass.toString())
@@ -65,7 +66,7 @@ class IosSafeRepo(private val coroutineDispatchers: CoroutineDispatchers) : Safe
6566
value = if (sqlCursor.next().value) sqlCursor.getString(0) else null
6667
)
6768

68-
override fun closeDatabase() {
69+
override suspend fun closeDatabase() {
6970
dbHolder?.close()
7071
dbHolder = null
7172
}

core/data/db-sqldelight/src/jvmMain/kotlin/com/softartdev/notedelight/db/JdbcDatabaseHolder.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.softartdev.notedelight.db
22

3+
import app.cash.sqldelight.async.coroutines.awaitCreate
4+
import app.cash.sqldelight.async.coroutines.awaitMigrate
35
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
46
import io.github.aakira.napier.Napier
57
import java.sql.SQLException
@@ -23,18 +25,18 @@ class JdbcDatabaseHolder(props: Properties = Properties()) : SqlDelightDbHolder
2325
driver.execute(null, "PRAGMA user_version = $value;", 0, null)
2426
}
2527

26-
init {
28+
override suspend fun createSchema() {
2729
if (currentVersion == 0) {
2830
try {
29-
NoteDb.Schema.create(driver)
31+
NoteDb.Schema.awaitCreate(driver)
3032
} catch (sqlException: SQLException) {
3133
Napier.e(message = sqlException.localizedMessage)
3234
} catch (t: Throwable) {
3335
Napier.e(message = "Error creating database schema", throwable = t)
3436
}
3537
currentVersion = 1
3638
} else if (NoteDb.Schema.version > currentVersion) {
37-
NoteDb.Schema.migrate(driver, currentVersion.toLong(), NoteDb.Schema.version)
39+
NoteDb.Schema.awaitMigrate(driver, currentVersion.toLong(), NoteDb.Schema.version)
3840
}
3941
}
4042

core/data/db-sqldelight/src/jvmMain/kotlin/com/softartdev/notedelight/repository/JvmSafeRepo.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,24 @@ class JvmSafeRepo(private val coroutineDispatchers: CoroutineDispatchers) : Safe
1919
get() = JvmCipherUtils.getDatabaseState(DB_NAME)
2020

2121
override val noteDAO: NoteDAO
22-
get() = SqlDelightNoteDAO(buildDbIfNeed().noteQueries, coroutineDispatchers)
22+
get() = SqlDelightNoteDAO(databaseHolder!!.noteQueries, coroutineDispatchers)
2323

2424
override val dbPath: String
2525
get() = FilePathResolver().invoke()
2626

27-
override fun buildDbIfNeed(passphrase: CharSequence): JdbcDatabaseHolder {
27+
override suspend fun buildDbIfNeed(passphrase: CharSequence): JdbcDatabaseHolder {
2828
var instance = databaseHolder
2929
if (instance == null) {
3030
val properties = Properties()
3131
if (passphrase.isNotEmpty()) properties["password"] = StringBuilder(passphrase).toString()
3232
instance = JdbcDatabaseHolder(properties)
33+
instance.createSchema()
3334
databaseHolder = instance
3435
}
3536
return instance
3637
}
3738

38-
override fun decrypt(oldPass: CharSequence) {
39+
override suspend fun decrypt(oldPass: CharSequence) {
3940
closeDatabase()
4041
JvmCipherUtils.decrypt(
4142
password = StringBuilder(oldPass).toString(),
@@ -44,7 +45,7 @@ class JvmSafeRepo(private val coroutineDispatchers: CoroutineDispatchers) : Safe
4445
buildDbIfNeed()
4546
}
4647

47-
override fun rekey(oldPass: CharSequence, newPass: CharSequence) {
48+
override suspend fun rekey(oldPass: CharSequence, newPass: CharSequence) {
4849
decrypt(oldPass)
4950
encrypt(newPass)
5051
}
@@ -64,7 +65,7 @@ class JvmSafeRepo(private val coroutineDispatchers: CoroutineDispatchers) : Safe
6465
value = if (sqlCursor.next().value) sqlCursor.getString(0) else null
6566
)
6667

67-
override fun encrypt(newPass: CharSequence) {
68+
override suspend fun encrypt(newPass: CharSequence) {
6869
closeDatabase()
6970
JvmCipherUtils.encrypt(
7071
password = StringBuilder(newPass).toString(),
@@ -73,7 +74,7 @@ class JvmSafeRepo(private val coroutineDispatchers: CoroutineDispatchers) : Safe
7374
buildDbIfNeed(newPass)
7475
}
7576

76-
override fun closeDatabase() = synchronized(this) {
77+
override suspend fun closeDatabase() = synchronized(this) {
7778
databaseHolder?.close()
7879
databaseHolder = null
7980
}

core/data/db-sqldelight/src/wasmJsMain/kotlin/com/softartdev/notedelight/db/WebDatabaseHolder.kt

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,14 @@
22

33
package com.softartdev.notedelight.db
44

5-
import app.cash.sqldelight.async.coroutines.awaitCreate
65
import app.cash.sqldelight.db.SqlDriver
76
import app.cash.sqldelight.driver.worker.createDefaultWebWorkerDriver
87
import com.softartdev.notedelight.shared.db.NoteQueries
9-
import io.github.aakira.napier.Napier
10-
import kotlinx.coroutines.CoroutineScope
11-
import kotlinx.coroutines.MainScope
12-
import kotlinx.coroutines.launch
138

149
class WebDatabaseHolder : SqlDelightDbHolder {
1510
override val driver: SqlDriver = createDefaultWebWorkerDriver()
1611
override val noteDb: NoteDb = createQueryWrapper(driver)
1712
override val noteQueries: NoteQueries = noteDb.noteQueries
1813

19-
val coroutineScope: CoroutineScope = MainScope()
20-
21-
init {
22-
coroutineScope.launch {
23-
try {
24-
NoteDb.Schema.awaitCreate(driver)
25-
} catch (t: Throwable) {
26-
Napier.e("Error creating database schema", t)
27-
}
28-
}
29-
}
30-
3114
override fun close() = driver.close()
3215
}

core/data/db-sqldelight/src/wasmJsMain/kotlin/com/softartdev/notedelight/repository/WebSafeRepo.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.softartdev.notedelight.repository
22

3-
import app.cash.sqldelight.async.coroutines.awaitQuery
43
import app.cash.sqldelight.db.QueryResult
54
import app.cash.sqldelight.db.SqlCursor
65
import com.softartdev.notedelight.db.NoteDAO
@@ -17,29 +16,30 @@ class WebSafeRepo(private val coroutineDispatchers: CoroutineDispatchers) : Safe
1716
get() = PlatformSQLiteState.UNENCRYPTED
1817

1918
override val noteDAO: NoteDAO
20-
get() = SqlDelightNoteDAO(buildDbIfNeed().noteQueries, coroutineDispatchers)
19+
get() = SqlDelightNoteDAO(dbHolder!!.noteQueries, coroutineDispatchers)
2120

2221
override val dbPath: String
2322
get() = TODO("Not yet implemented")
2423

25-
override fun buildDbIfNeed(passphrase: CharSequence): WebDatabaseHolder {
24+
override suspend fun buildDbIfNeed(passphrase: CharSequence): WebDatabaseHolder {
2625
var instance = dbHolder
2726
if (instance == null) {
2827
instance = WebDatabaseHolder()
28+
instance.createSchema()
2929
dbHolder = instance
3030
}
3131
return instance
3232
}
3333

34-
override fun decrypt(oldPass: CharSequence) {
34+
override suspend fun decrypt(oldPass: CharSequence) {
3535
TODO("Not yet implemented")
3636
}
3737

38-
override fun rekey(oldPass: CharSequence, newPass: CharSequence) {
38+
override suspend fun rekey(oldPass: CharSequence, newPass: CharSequence) {
3939
TODO("Not yet implemented")
4040
}
4141

42-
override fun encrypt(newPass: CharSequence) {
42+
override suspend fun encrypt(newPass: CharSequence) {
4343
TODO("Not yet implemented")
4444
}
4545

@@ -58,7 +58,7 @@ class WebSafeRepo(private val coroutineDispatchers: CoroutineDispatchers) : Safe
5858
value = if (sqlCursor.next().value) sqlCursor.getString(0) else null
5959
)
6060

61-
override fun closeDatabase() {
61+
override suspend fun closeDatabase() {
6262
dbHolder?.close()
6363
dbHolder = null
6464
}

0 commit comments

Comments
 (0)