Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion android-compose-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ dependencies {
exclude(group = "org.jetbrains.runtime", module = "jbr-api")
}
androidTestImplementation(libs.commonsware.saferoom)
androidTestImplementation(libs.sqlDelight.android)
// androidTestImplementation(libs.sqlDelight.android)
androidTestImplementation(libs.sqliteMc.android)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.runner)
androidTestUtil(libs.androidx.test.orchestrator)
Expand Down
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ plugins {
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.sqlDelight) apply false
// alias(libs.plugins.sqlDelight) apply false
alias(libs.plugins.sqliteMc) apply false
alias(libs.plugins.room) apply false
alias(libs.plugins.gms) apply false
alias(libs.plugins.crashlytics) apply false
Expand Down
23 changes: 14 additions & 9 deletions core/data/db-sqldelight/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ plugins {
alias(libs.plugins.gradle.convention)
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.library)
alias(libs.plugins.sqlDelight)
// alias(libs.plugins.sqlDelight)
alias(libs.plugins.sqliteMc)
alias(libs.plugins.kotlin.cocoapods)
}

Expand All @@ -18,9 +19,12 @@ kotlin {
sourceSets {
commonMain.dependencies {
implementation(project(":core:domain"))
implementation(libs.sqlDelight.runtime)
/*implementation(libs.sqlDelight.runtime)
implementation(libs.sqlDelight.coroutinesExt)
implementation(libs.sqlDelight.paging)
implementation(libs.sqlDelight.paging)*/
// implementation(libs.sqliteMc.runtime)
// implementation(libs.sqliteMc.coroutinesExt)
// implementation(libs.sqliteMc.paging)
implementation(libs.kotlinx.datetime)
implementation(libs.stately.common)
}
Expand All @@ -32,28 +36,30 @@ kotlin {
implementation(libs.napier)
}
androidMain.dependencies {
implementation(libs.sqlDelight.android)
// implementation(libs.sqlDelight.android)
// implementation(libs.sqliteMc.android)
implementation(libs.commonsware.saferoom)
implementation(libs.android.sqlcipher)
}
androidUnitTest.dependencies {
implementation(kotlin("test-junit"))
implementation(libs.bundles.mockito)
implementation(libs.sqlDelight.jvm)
// implementation(libs.sqlDelight.jvm)
// implementation(libs.sqliteMc.jvm)
}
androidInstrumentedTest.dependencies {
implementation(libs.androidx.test.ext.junit)
implementation(libs.androidx.test.runner)
}
iosMain.dependencies {
implementation(libs.sqlDelight.native)
// implementation(libs.sqlDelight.native)
// implementation(libs.sqliteMc.native)
implementation(libs.stately.isolate)
implementation(libs.stately.iso.collections)
}
iosTest.dependencies {
}
jvmMain.dependencies {
implementation(libs.sqlDelight.jvm)
implementation(libs.appdirs)
implementation(libs.napier)
}
Comment on lines 62 to 65
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Declare SQLiteMC driver dependency for JVM sources

The Kotlin classes now import io.toxicity.sqlite.mc.driver.SQLiteMCDriver and related configuration, but the jvmMain source set no longer pulls in any driver dependency—implementation(libs.sqlDelight.jvm) was removed and nothing replaced it. Without a runtime such as io.toxicity.sqlite-mc:sqlite-driver the JVM module will not compile (unresolved reference) or will crash at runtime with ClassNotFoundException. Add the SQLiteMC driver dependency to the jvmMain (and corresponding test) dependencies to match the new imports.

Useful? React with 👍 / 👎.

Expand Down Expand Up @@ -91,11 +97,10 @@ android {
testOptions.unitTests.isReturnDefaultValues = true
}

sqldelight {
sqliteMC {
databases {
create("NoteDb") {
packageName.set("com.softartdev.notedelight.db")
}
}
linkSqlite.set(false)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ SELECT * FROM note WHERE id = :noteId;
insert:
INSERT INTO note VALUES ?;

lastInsertRowId:
SELECT last_insert_rowid();

update:
INSERT OR REPLACE INTO note VALUES ?;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package com.softartdev.notedelight.db

import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
import io.github.aakira.napier.Napier
import io.toxicity.sqlite.mc.driver.SQLiteMCDriver
import java.sql.SQLException
import java.util.Properties

class JdbcDatabaseHolder(props: Properties = Properties()) : SqlDelightDbHolder {
override val driver = JdbcSqliteDriver(
url = JdbcSqliteDriver.IN_MEMORY + FilePathResolver().invoke(),// jdbc:sqlite:/.../notes.db
properties = props
)
class JdbcDatabaseHolder(
passphrase: CharSequence = "",
rekey: CharSequence? = null,
) : SqlDelightDbHolder {
override val driver: SQLiteMCDriver = JvmCipherUtils.createDriver(passphrase, rekey)
override val noteDb: NoteDb = createQueryWrapper(driver)
override val noteQueries = noteDb.noteQueries

Expand Down Expand Up @@ -39,4 +38,4 @@ class JdbcDatabaseHolder(props: Properties = Properties()) : SqlDelightDbHolder
}

override fun close() = driver.close()
}
}
Original file line number Diff line number Diff line change
@@ -1,96 +1,42 @@
package com.softartdev.notedelight.db

import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
import com.softartdev.notedelight.model.PlatformSQLiteState
import io.toxicity.sqlite.mc.driver.SQLiteMCDriver
import io.toxicity.sqlite.mc.driver.config.DatabasesDir
import io.toxicity.sqlite.mc.driver.config.encryption.Key
import java.io.File
import java.io.FileNotFoundException
import java.sql.Connection
import java.sql.DriverManager
import java.sql.ResultSet
import java.sql.Statement

object JvmCipherUtils {

fun getDatabaseState(dbName: String): PlatformSQLiteState {
var result = PlatformSQLiteState.DOES_NOT_EXIST
val dbPath = File(dbName)
if (dbPath.exists()) {
result = PlatformSQLiteState.UNENCRYPTED
var connection: Connection? = null
try {
val url = JdbcSqliteDriver.IN_MEMORY + dbName // jdbc:sqlite:$dbName
connection = DriverManager.getConnection(url)
val statement: Statement = connection.createStatement()
val resultSet: ResultSet = statement.executeQuery("PRAGMA user_version;")
val version: Long = resultSet.getLong(1)
println("db version = $version")
} catch (throwable: Throwable) {
throwable.printStackTrace()
result = PlatformSQLiteState.ENCRYPTED
} finally {
connection?.close()
private val dbFile = File(FilePathResolver().invoke())

private val factory = SQLiteMCDriver.Factory(
dbName = dbFile.name,
schema = NoteDb.Schema
) {
filesystem(DatabasesDir(dbFile.parentFile!!)) {
encryption {
sqlCipher { v4() }
}
}
return result
}

fun decrypt(password: String, dbName: String) {
val originalFile = File(dbName)
if (originalFile.exists()) {
val newFile = File.createTempFile("sqlcipherutils", "tmp", null)

var url = JdbcSqliteDriver.IN_MEMORY + dbName // jdbc:sqlite:$dbName
var connection = DriverManager.getConnection(url, null, password)
val statement = connection.prepareStatement("ATTACH DATABASE ? AS plaintext KEY ''")
statement.setString(1, newFile.absolutePath)
statement.execute()
connection.createStatement().executeQuery("SELECT sqlcipher_export('plaintext')")
connection.createStatement().executeQuery("DETACH DATABASE plaintext")
val resultSet: ResultSet = statement.executeQuery("PRAGMA user_version;")
val version: Long = resultSet.getLong(1)
statement.close()
connection.close()

url = JdbcSqliteDriver.IN_MEMORY + newFile.absolutePath
connection = DriverManager.getConnection(url)
connection.createStatement().executeQuery("PRAGMA user_version = $version")
connection.close()

originalFile.delete()
newFile.renameTo(originalFile)
} else {
throw FileNotFoundException(originalFile.absolutePath + " not found")
fun getDatabaseState(): PlatformSQLiteState {
if (!dbFile.exists()) return PlatformSQLiteState.DOES_NOT_EXIST
return try {
factory.createBlocking(Key.Empty).close()
PlatformSQLiteState.UNENCRYPTED
} catch (_: Throwable) {
PlatformSQLiteState.ENCRYPTED
}
}

fun encrypt(password: String?, dbName: String) {
val originalFile = File(dbName)
if (originalFile.exists()) {
val newFile = File.createTempFile("sqlcipherutils", "tmp", null)

var url = JdbcSqliteDriver.IN_MEMORY + originalFile.absolutePath // jdbc:sqlite:${originalFile.absolutePath}
var connection = DriverManager.getConnection(url)
var statement: Statement = connection.createStatement()
val resultSet: ResultSet = statement.executeQuery("PRAGMA user_version;")
val version: Long = resultSet.getLong(1)
statement.close()
connection.close()

url = JdbcSqliteDriver.IN_MEMORY + newFile.absolutePath
connection = DriverManager.getConnection(url, null, password)
statement = connection.prepareStatement("ATTACH DATABASE ? AS plaintext KEY ''")
statement.setString(1, originalFile.absolutePath)
statement.execute()
connection.createStatement().executeQuery("SELECT sqlcipher_export('plaintext')")
connection.createStatement().executeQuery("DETACH DATABASE plaintext")
connection.createStatement().executeQuery("PRAGMA user_version = $version")
statement.close()
connection.close()

originalFile.delete()
newFile.renameTo(originalFile)
fun createDriver(passphrase: CharSequence = "", rekey: CharSequence? = null): SQLiteMCDriver {
val key = if (passphrase.isEmpty()) Key.Empty else Key.passphrase(passphrase.toString())
return if (rekey != null) {
val rekeyKey = if (rekey.isEmpty()) Key.Empty else Key.passphrase(rekey.toString())
factory.createBlocking(key, rekeyKey)
} else {
throw FileNotFoundException(originalFile.absolutePath + " not found")
factory.createBlocking(key)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ import com.softartdev.notedelight.db.JvmCipherUtils
import com.softartdev.notedelight.db.NoteDAO
import com.softartdev.notedelight.db.SqlDelightNoteDAO
import com.softartdev.notedelight.model.PlatformSQLiteState
import java.util.Properties

class JvmSafeRepo : SafeRepo() {
@Volatile
private var databaseHolder: JdbcDatabaseHolder? = null

override val databaseState: PlatformSQLiteState
get() = JvmCipherUtils.getDatabaseState(DB_NAME)
get() = JvmCipherUtils.getDatabaseState()

override val noteDAO: NoteDAO
get() = SqlDelightNoteDAO(buildDbIfNeed().noteQueries)
Expand All @@ -26,26 +25,22 @@ class JvmSafeRepo : SafeRepo() {
override fun buildDbIfNeed(passphrase: CharSequence): JdbcDatabaseHolder {
var instance = databaseHolder
if (instance == null) {
val properties = Properties()
if (passphrase.isNotEmpty()) properties["password"] = StringBuilder(passphrase).toString()
instance = JdbcDatabaseHolder(properties)
instance = JdbcDatabaseHolder(passphrase)
databaseHolder = instance
}
return instance
}

override fun decrypt(oldPass: CharSequence) {
closeDatabase()
JvmCipherUtils.decrypt(
password = StringBuilder(oldPass).toString(),
dbName = DB_NAME
)
JdbcDatabaseHolder(oldPass, "").close()
buildDbIfNeed()
}

override fun rekey(oldPass: CharSequence, newPass: CharSequence) {
decrypt(oldPass)
encrypt(newPass)
closeDatabase()
JdbcDatabaseHolder(oldPass, newPass).close()
buildDbIfNeed(newPass)
}

override fun execute(query: String): String? {
Expand All @@ -65,10 +60,7 @@ class JvmSafeRepo : SafeRepo() {

override fun encrypt(newPass: CharSequence) {
closeDatabase()
JvmCipherUtils.encrypt(
password = StringBuilder(newPass).toString(),
dbName = DB_NAME
)
JdbcDatabaseHolder("", newPass).close()
buildDbIfNeed(newPass)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.softartdev.notedelight.db

import com.softartdev.notedelight.model.PlatformSQLiteState
import com.softartdev.notedelight.repository.SafeRepo.Companion.DB_NAME
import kotlin.test.*

class JvmCipherUtilsTest {
Expand All @@ -17,7 +16,7 @@ class JvmCipherUtilsTest {
@Test
fun getDatabaseState() {
val exp = PlatformSQLiteState.ENCRYPTED
val act = JvmCipherUtils.getDatabaseState(DB_NAME)
val act = JvmCipherUtils.getDatabaseState()
assertNotEquals(exp, act)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,9 @@ class DesktopUiTests : AbstractUiTests() {
@Test
override fun prepopulateDatabase() = super.prepopulateDatabase()

@Ignore("Desktop app doesn't support encryption yet")
@Test
override fun flowAfterCryptTest() = super.flowAfterCryptTest()

@Ignore("Desktop app doesn't support encryption yet")
@Test
override fun settingPasswordTest() = super.settingPasswordTest()

Expand Down
25 changes: 17 additions & 8 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ gms = "4.4.3"
crashlytics = "3.0.6"
compose = "1.9.0-rc02"
coroutines = "1.10.2"
sqlDelight = "2.1.0"
#sqlDelight = "2.1.0"
sqliteMc = "2.1.0-2.2.3-0"
room = "2.8.0"
androidxSqlite = "2.6.0"
saferoom = "1.3.0"
Expand Down Expand Up @@ -53,12 +54,19 @@ coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-androi
coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing" }
coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test" }

sqlDelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqlDelight" }
sqlDelight-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqlDelight" }
sqlDelight-native = { module = "app.cash.sqldelight:native-driver", version.ref = "sqlDelight" }
sqlDelight-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqlDelight" }
sqlDelight-paging = { module = "app.cash.sqldelight:androidx-paging3-extensions", version.ref = "sqlDelight" }
sqlDelight-coroutinesExt = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqlDelight" }
#sqlDelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqlDelight" }
#sqlDelight-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqlDelight" }
#sqlDelight-native = { module = "app.cash.sqldelight:native-driver", version.ref = "sqlDelight" }
#sqlDelight-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqlDelight" }
#sqlDelight-paging = { module = "app.cash.sqldelight:androidx-paging3-extensions", version.ref = "sqlDelight" }
#sqlDelight-coroutinesExt = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqlDelight" }

sqliteMc-runtime = { module = "io.toxicity.sqlite-mc:runtime", version.ref = "sqliteMc" }
sqliteMc-android = { module = "io.toxicity.sqlite-mc:android-driver", version.ref = "sqliteMc" }
#sqliteMc-native = { module = "io.toxicity.sqlite-mc:native-driver", version.ref = "sqliteMc" }
sqliteMc-jvm = { module = "io.toxicity.sqlite-mc:sqlite-driver", version.ref = "sqliteMc" }
#sqliteMc-paging = { module = "io.toxicity.sqlite-mc:androidx-paging3-extensions", version.ref = "sqliteMc" }
#sqliteMc-coroutinesExt = { module = "io.toxicity.sqlite-mc:coroutines-extensions", version.ref = "sqliteMc" }

stately-common = { module = "co.touchlab:stately-common", version.ref = "stately" } #FIXME https://github.com/cashapp/sqldelight/issues/4357
stately-isolate = { module = "co.touchlab:stately-isolate", version.ref = "stately" }
Expand Down Expand Up @@ -166,7 +174,8 @@ compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "
compose = { id = "org.jetbrains.compose", version.ref = "compose" }
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
sqlDelight = { id = "app.cash.sqldelight", version.ref = "sqlDelight" }
#sqlDelight = { id = "app.cash.sqldelight", version.ref = "sqlDelight" }
sqliteMc = { id = "io.toxicity.sqlite-mc", version.ref = "sqliteMc" }
room = { id = "androidx.room", version.ref = "room" }
gms = { id = "com.google.gms.google-services", version.ref = "gms" }
crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "crashlytics" }
Expand Down
Loading