Skip to content
This repository was archived by the owner on Oct 24, 2025. It is now read-only.

Commit 16f7e80

Browse files
committed
rewrite
1 parent 5454b79 commit 16f7e80

File tree

21 files changed

+231
-212
lines changed

21 files changed

+231
-212
lines changed

samples/android/src/main/java/com/asyncstorage/sqlite/android/example/MainActivity.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ import android.os.Bundle
44
import androidx.activity.compose.setContent
55
import androidx.appcompat.app.AppCompatActivity
66
import com.asyncstorage.sqlite.android.example.ui.MainScreen
7-
import org.asyncstorage.sqlitestorage.SQLiteStorageFactory
87
import org.asyncstorage.sqlitestorage.SqliteStorage
98

109
class MainActivity : AppCompatActivity() {
1110
lateinit var db: SqliteStorage
1211

1312
override fun onCreate(savedInstanceState: Bundle?) {
1413
super.onCreate(savedInstanceState)
15-
db = SQLiteStorageFactory(this).create("my_db")
14+
db = StorageFactory(this).create("my_db")
1615
setContent {
1716
MainScreen(db)
1817
}

sqlite-storage/build.gradle.kts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ kotlin {
2323
}
2424
}
2525

26+
27+
28+
// disable expect-actual warning
29+
targets.all {
30+
compilations.all {
31+
compilerOptions.configure {
32+
freeCompilerArgs.add("-Xexpect-actual-classes")
33+
}
34+
}
35+
}
36+
37+
2638
jvmToolchain(11)
2739

2840
sourceSets {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.asyncstorage.sqlitestorage
2+
3+
import java.io.File
4+
5+
internal class AndroidDatabaseFile(private val dbFile: File) : DatabaseFile {
6+
override fun path(): String = dbFile.absolutePath
7+
8+
override fun dirPath(): String = dbFile.parentFile!!.absolutePath
9+
10+
override fun delete(): Boolean {
11+
val dbName = dbFile.name
12+
return try {
13+
val parent = dbFile.parentFile!!
14+
val deleted = mutableListOf<Boolean>()
15+
16+
listOf(dbName, "$dbName-wal", "$dbName-shm").forEach { dbFile ->
17+
deleted += File(parent, dbFile).delete()
18+
}
19+
true
20+
} catch (e: SecurityException) {
21+
e.printStackTrace()
22+
return false
23+
}
24+
}
25+
26+
override fun size(): Long {
27+
var size = -1L
28+
if (dbFile.exists()) {
29+
size = (dbFile.length() / 1024L)
30+
}
31+
return size
32+
}
33+
}

sqlite-storage/src/androidMain/kotlin/org/asyncstorage/sqlitestorage/SQLiteStorageFactory.kt

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.asyncstorage.sqlitestorage
2+
3+
import android.content.Context
4+
import androidx.sqlite.db.SupportSQLiteDatabase
5+
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
6+
import org.asyncstorage.sqlitestorage.db.AsyncStorageDB
7+
8+
actual class SqliteStorageFactory(private val ctx: Context) {
9+
actual fun create(dbName: String): SqliteStorage {
10+
val driver = createDriver(dbName)
11+
val dbUtils = AndroidDatabaseFile(ctx.getDatabasePath(dbName))
12+
return SqliteStorage(
13+
driver = driver,
14+
dbFile = dbUtils,
15+
)
16+
}
17+
18+
private fun createDriver(dbName: String): AndroidSqliteDriver {
19+
val callback =
20+
object : AndroidSqliteDriver.Callback(AsyncStorageDB.Schema) {
21+
override fun onConfigure(db: SupportSQLiteDatabase) {
22+
/**
23+
* Makes this database work in Write-ahead log journal mode
24+
* Available for SQLite version 3.7.0 (2010-07-21) or later
25+
* Android supports 3.7.0+ since API 11 (https://developer.android.com/reference/android/database/sqlite/package-summary)
26+
*/
27+
db.enableWriteAheadLogging()
28+
/**
29+
* Makes sqlite use Normal mode for synchronizing DB content with filesystem only in crucial times
30+
* WAL file is synchronized before each checkpoint and the database file is synchronized after each completed checkpoint
31+
*
32+
* https://sqlite.org/pragma.html#pragma_synchronous
33+
*/
34+
db.execSQL("PRAGMA synchronous = NORMAL")
35+
}
36+
}
37+
38+
return AndroidSqliteDriver(
39+
schema = AsyncStorageDB.Schema,
40+
context = ctx,
41+
name = dbName,
42+
callback = callback,
43+
)
44+
}
45+
}

sqlite-storage/src/androidMain/kotlin/org/asyncstorage/sqlitestorage/utils/DatabaseUtils.kt

Lines changed: 0 additions & 31 deletions
This file was deleted.

sqlite-storage/src/androidUnitTest/kotlin/org/asyncstorage/sqlitestorage/DbUtilsTest.kt renamed to sqlite-storage/src/androidUnitTest/kotlin/org/asyncstorage/sqlitestorage/DatabaseFileTest.android.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
package org.asyncstorage.sqlitestorage
22

3+
import android.content.Context
34
import androidx.test.core.app.ApplicationProvider
4-
import org.asyncstorage.sqlitestorage.utils.DatabaseUtils
55
import org.asyncstorage.sqlitestorage.utils.JunitRunner
66
import org.junit.Test
77
import org.junit.runner.RunWith
88

9-
internal actual fun createUtils(name: String): DatabaseUtils {
10-
return DatabaseUtils(name, ApplicationProvider.getApplicationContext())
9+
internal actual fun createDatabaseFile(name: String): DatabaseFile {
10+
val ctx: Context = ApplicationProvider.getApplicationContext()
11+
return AndroidDatabaseFile(ctx.getDatabasePath(name))
1112
}
1213

1314
@RunWith(JunitRunner::class)
14-
class AndroidDbUtilsTest {
15+
class AndroidDatabaseFileTest {
1516
@Test
1617
fun `correct database path is returned`() {
1718
val dbName = "testing_my_db.db"
18-
val utils = createUtils(dbName)
19-
val dbPath = utils.getDbFilePath()
19+
val file = createDatabaseFile(dbName)
20+
val dbPath = file.path()
2021
val expected = "databases/$dbName"
2122
assert(dbPath.endsWith("databases/$dbName")) {
2223
"db path not matching, expected: $expected, received: $dbPath"

sqlite-storage/src/androidUnitTest/kotlin/org/asyncstorage/sqlitestorage/utils/CreateTestDb.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.content.Context
44
import androidx.test.core.app.ApplicationProvider
55
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
66
import kotlinx.coroutines.CoroutineDispatcher
7+
import org.asyncstorage.sqlitestorage.AndroidDatabaseFile
78
import org.asyncstorage.sqlitestorage.DefaultSqliteStorage
89
import org.asyncstorage.sqlitestorage.SqliteStorage
910
import org.asyncstorage.sqlitestorage.db.AsyncStorageDB
@@ -15,6 +16,6 @@ actual fun createTestDatabase(
1516
val ctx = ApplicationProvider.getApplicationContext<Context>()
1617
// passing name as null to SupportSQLiteOpenHelper creates in-memory db
1718
val driver = AndroidSqliteDriver(AsyncStorageDB.Schema, ctx, null)
18-
val dbUtils = DatabaseUtils(dbName, ctx)
19-
return DefaultSqliteStorage(driver, dbUtils, dispatcher, dispatcher)
19+
val dbUtils = AndroidDatabaseFile(ctx.getDatabasePath(dbName))
20+
return DefaultSqliteStorage(driver, dbUtils, dispatcher)
2021
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.asyncstorage.sqlitestorage
2+
3+
interface DatabaseFile {
4+
/**
5+
* Returns an absolute path to main .db file
6+
*/
7+
fun path(): String
8+
9+
/**
10+
* An absolute path to a directory containing database files
11+
*/
12+
fun dirPath(): String
13+
14+
/**
15+
* Removes the database file and all related WAL files (-wal and -shm).
16+
*/
17+
fun delete(): Boolean
18+
19+
/**
20+
* Returns database file size in KB.
21+
* If file does not exists or cannot be reached due to security returns 0.
22+
* This only checks the size of main database file, without log file (-wal) or wal-index file (-shm)
23+
* To get the correct values, call this after manual WAL checkpoint.
24+
*/
25+
fun size(): Long
26+
}

sqlite-storage/src/commonMain/kotlin/org/asyncstorage/sqlitestorage/DefaultSqliteStorage.kt

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,22 @@ import kotlinx.coroutines.flow.Flow
77
import kotlinx.coroutines.flow.map
88
import kotlinx.coroutines.withContext
99
import org.asyncstorage.sqlitestorage.db.AsyncStorageDB
10+
import org.asyncstorage.sqlitestorage.dispatchers.DispatcherIO
1011
import org.asyncstorage.sqlitestorage.extensions.executePragmaOptimize
11-
import org.asyncstorage.sqlitestorage.extensions.executePragmaSyncNormal
1212
import org.asyncstorage.sqlitestorage.extensions.executePragmaWalCheckpoint
13-
import org.asyncstorage.sqlitestorage.extensions.executePragmaWalJournalMode
1413
import org.asyncstorage.sqlitestorage.models.Entry
1514
import org.asyncstorage.sqlitestorage.models.Key
16-
import org.asyncstorage.sqlitestorage.utils.DatabaseUtils
1715
import org.asyncstorage.sqlitestorage.utils.mergePossibleJsonValues
1816

1917
internal class DefaultSqliteStorage(
2018
private val driver: SqlDriver,
21-
private val dbUtils: DatabaseUtils,
22-
private val readDispatcher: CoroutineDispatcher,
23-
private val writeDispatcher: CoroutineDispatcher,
19+
private val dbFile: DatabaseFile,
20+
private val dispatcher: CoroutineDispatcher = DispatcherIO,
2421
) : SqliteStorage {
2522
private val queries = AsyncStorageDB(driver).async_storage_entriesQueries
2623

27-
init {
28-
/**
29-
* Makes this database work in Write-ahead log journal mode
30-
* Available for SQLite version 3.7.0 (2010-07-21) or later
31-
* Android supports 3.7.0+ since API 11 (https://developer.android.com/reference/android/database/sqlite/package-summary)
32-
* iOS supports 3.7.0+ since iOS 4.3.x (https://www.theiphonewiki.com/wiki/Databases)
33-
*/
34-
driver.executePragmaWalJournalMode()
35-
driver.executePragmaSyncNormal()
36-
}
37-
3824
override suspend fun read(key: Key): Entry =
39-
withContext(readDispatcher) {
25+
withContext(dispatcher) {
4026
queries.getOne(key) { k, v -> Entry(k, v) }.executeAsOneOrNull() ?: Entry(key)
4127
}
4228

@@ -49,12 +35,12 @@ internal class DefaultSqliteStorage(
4935
}
5036

5137
override suspend fun write(entry: Entry) =
52-
withContext(writeDispatcher) {
38+
withContext(dispatcher) {
5339
queries.insertOne(entry.key, entry.value)
5440
}
5541

5642
override suspend fun merge(entry: Entry) =
57-
withContext(writeDispatcher) {
43+
withContext(dispatcher) {
5844
queries.transactionWithResult {
5945
val current = queries.getOne(entry.key).executeAsOneOrNull()
6046
if (current == null) {
@@ -69,12 +55,12 @@ internal class DefaultSqliteStorage(
6955
}
7056

7157
override suspend fun remove(key: Key) =
72-
withContext(writeDispatcher) {
58+
withContext(dispatcher) {
7359
queries.deleteOne(key)
7460
}
7561

7662
override suspend fun readMany(keys: List<Key>): List<Entry> =
77-
withContext(readDispatcher) {
63+
withContext(dispatcher) {
7864
val found = queries.getMany(keys) { k, v -> Entry(k, v) }.executeAsList()
7965
keys.map { key ->
8066
found.find { it.key == key } ?: Entry(key)
@@ -93,7 +79,7 @@ internal class DefaultSqliteStorage(
9379
}
9480

9581
override suspend fun writeMany(entries: List<Entry>) =
96-
withContext(writeDispatcher) {
82+
withContext(dispatcher) {
9783
queries.transaction {
9884
entries.forEach { entry ->
9985
queries.insertOne(entry.key, entry.value)
@@ -102,7 +88,7 @@ internal class DefaultSqliteStorage(
10288
}
10389

10490
override suspend fun mergeMany(entries: List<Entry>) =
105-
withContext<List<Entry>>(writeDispatcher) {
91+
withContext<List<Entry>>(dispatcher) {
10692
queries.transactionWithResult {
10793
val result = mutableListOf<Entry>()
10894
for (entry in entries) {
@@ -121,17 +107,17 @@ internal class DefaultSqliteStorage(
121107
}
122108

123109
override suspend fun removeMany(keys: List<Key>) =
124-
withContext(writeDispatcher) {
110+
withContext(dispatcher) {
125111
queries.deleteMany(keys)
126112
}
127113

128114
override suspend fun clear() =
129-
withContext(writeDispatcher) {
115+
withContext(dispatcher) {
130116
queries.deleteAll()
131117
}
132118

133119
override suspend fun getKeys(): List<Key> =
134-
withContext(readDispatcher) {
120+
withContext(dispatcher) {
135121
queries.getAllKeys().executeAsList()
136122
}
137123

@@ -144,22 +130,22 @@ internal class DefaultSqliteStorage(
144130
}
145131

146132
override suspend fun closeConnection() =
147-
withContext(writeDispatcher) {
133+
withContext(dispatcher) {
148134
driver.executePragmaOptimize()
149135
driver.close()
150136
}
151137

152-
override fun getDbPath() = dbUtils.getDbFilePath()
138+
override fun getDbPath() = dbFile.path()
153139

154140
override suspend fun getDbSize(): Long =
155-
withContext(writeDispatcher) {
141+
withContext(dispatcher) {
156142
driver.executePragmaWalCheckpoint()
157-
dbUtils.getDbFileSize()
143+
dbFile.size()
158144
}
159145

160146
override suspend fun dropDatabase() =
161-
withContext(writeDispatcher) {
147+
withContext(dispatcher) {
162148
driver.close()
163-
dbUtils.removeDbFiles()
149+
dbFile.delete()
164150
}
165151
}

0 commit comments

Comments
 (0)