Skip to content

Commit a245067

Browse files
committed
shared: move to interface
1 parent bfa62db commit a245067

File tree

8 files changed

+102
-79
lines changed

8 files changed

+102
-79
lines changed
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package tests
22

3+
import org.asyncstorage.shared_storage.SharedStorageImpl
34
import org.asyncstorage.shared_storage.SharedStorage
4-
import org.asyncstorage.shared_storage.createInMemory
5+
import org.asyncstorage.shared_storage.sharedStorageInMemory
56
import org.junit.runner.RunWith
67
import org.robolectric.RobolectricTestRunner
78
import org.robolectric.RuntimeEnvironment
@@ -12,12 +13,10 @@ actual class StorageUtils actual constructor() {
1213
private var storage: SharedStorage? = null
1314

1415
actual fun getStorage(): SharedStorage {
15-
return SharedStorage.createInMemory(RuntimeEnvironment.getApplication()).also {
16-
storage = it
17-
}
16+
return sharedStorageInMemory(RuntimeEnvironment.getApplication()).also { storage = it }
1817
}
1918

2019
actual fun cleanup() {
21-
storage?.database?.close()
20+
storage?.let { (it as SharedStorageImpl).database.close() }
2221
}
2322
}

shared-storage/src/androidMain/kotlin/org/asyncstorage/shared_storage/SharedStorage.android.kt

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ import kotlin.math.max
1313

1414
@Suppress("unused") // used in consumer app
1515
@OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class)
16-
actual fun SharedStorage.Companion.create(
17-
context: PlatformContext,
18-
databaseName: String,
19-
): SharedStorage {
16+
actual fun SharedStorage(context: PlatformContext, databaseName: String): SharedStorage {
2017

2118
val dbFile = context.getDatabasePath(databaseName)
2219
val writeDispatcher = newSingleThreadContext("$databaseName-writer")
@@ -31,18 +28,16 @@ actual fun SharedStorage.Companion.create(
3128
.setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
3229
.build()
3330

34-
return SharedStorage(db)
31+
return SharedStorageImpl(db)
3532
}
3633

37-
internal actual fun SharedStorage.Companion.createInMemory(
38-
context: PlatformContext
39-
): SharedStorage {
34+
internal actual fun sharedStorageInMemory(context: PlatformContext): SharedStorage {
4035
val db =
4136
Room.inMemoryDatabaseBuilder<StorageDatabase>(context)
4237
.setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
4338
.build()
4439

45-
return SharedStorage(db)
40+
return SharedStorageImpl(db)
4641
}
4742

4843
// as per https://blog.p-y.wtf/parallelism-with-android-sqlite

shared-storage/src/appleMain/kotlin/org/asyncstorage/shared_storage/SharedStorage.apple.kt

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ import org.asyncstorage.shared_storage.database.StorageDatabase
88
import platform.Foundation.*
99

1010
@Suppress("unused") // used on iOS side
11-
actual fun SharedStorage.Companion.create(
12-
context: PlatformContext,
13-
databaseName: String,
14-
): SharedStorage {
11+
actual fun SharedStorage(context: PlatformContext, databaseName: String): SharedStorage {
1512
val databases = getDatabasesPath()
1613
createDbDirectory(databases)
1714
val dbFilePath = "$databases/$databaseName"
@@ -21,19 +18,17 @@ actual fun SharedStorage.Companion.create(
2118
.setDriver(BundledSQLiteDriver())
2219
.build()
2320

24-
return SharedStorage(database = db)
21+
return SharedStorageImpl(database = db)
2522
}
2623

27-
internal actual fun SharedStorage.Companion.createInMemory(
28-
context: PlatformContext
29-
): SharedStorage {
24+
internal actual fun sharedStorageInMemory(context: PlatformContext): SharedStorage {
3025
val db =
3126
Room.inMemoryDatabaseBuilder<StorageDatabase>()
3227
.setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
3328
.setDriver(BundledSQLiteDriver())
3429
.build()
3530

36-
return SharedStorage(database = db)
31+
return SharedStorageImpl(database = db)
3732
}
3833

3934
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
package tests
22

3+
import org.asyncstorage.shared_storage.SharedStorageImpl
34
import org.asyncstorage.shared_storage.PlatformContext
45
import org.asyncstorage.shared_storage.SharedStorage
5-
import org.asyncstorage.shared_storage.createInMemory
6+
import org.asyncstorage.shared_storage.sharedStorageInMemory
67

78
actual open class TestRunner actual constructor()
89

910
actual class StorageUtils actual constructor() {
1011
private var storage: SharedStorage? = null
1112

1213
actual fun getStorage(): SharedStorage {
13-
return SharedStorage.createInMemory(PlatformContext.Instance).also { storage = it }
14+
return sharedStorageInMemory(PlatformContext.Instance).also { storage = it }
1415
}
1516

1617
actual fun cleanup() {
17-
storage?.database?.close()
18+
storage?.let { (it as SharedStorageImpl).database.close() }
1819
}
1920
}
Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,30 @@
11
package org.asyncstorage.shared_storage
22

33
import kotlinx.coroutines.flow.Flow
4-
import kotlinx.coroutines.flow.distinctUntilChanged
5-
import kotlinx.coroutines.flow.map
6-
import org.asyncstorage.shared_storage.database.StorageDatabase
7-
import org.asyncstorage.shared_storage.database.StorageEntry
84

95
/**
10-
* Persistent Storage backed by SQLite.
6+
* Cross-platform abstraction for a key-value storage backed by SQLite.
117
*
12-
* Note about [getValuesFlow]: Since SQLite database trigger notifications only on table level, not
13-
* row level, non-observed requested keys will trigger emits. Therefor, flow returned has
14-
* .distinctUntilChanged to mimic row level update.
8+
* This interface is designed to provide both direct suspend-based access and reactive Flow-based
9+
* access to stored values.
1510
*/
16-
class SharedStorage internal constructor(internal val database: StorageDatabase) {
11+
interface SharedStorage {
1712

18-
private val storage = database.storageDao()
13+
suspend fun getValues(keys: List<String>): List<Entry>
1914

20-
suspend fun getValues(keys: List<String>): List<Entry> {
21-
return storage.getValues(keys).map(StorageEntry::toEntry)
22-
}
15+
fun getValuesFlow(keys: List<String>): Flow<List<Entry>>
2316

24-
fun getValuesFlow(keys: List<String>): Flow<List<Entry>> =
25-
storage
26-
.getValuesFlow(keys)
27-
.map { list -> list.map(StorageEntry::toEntry) }
28-
.distinctUntilChanged()
17+
suspend fun setValues(entries: List<Entry>): List<Entry>
2918

30-
suspend fun setValues(entries: List<Entry>): List<Entry> {
31-
val values = entries.map(Entry::toStorageEntry)
32-
return storage.setValuesAndGet(values).map(StorageEntry::toEntry)
33-
}
19+
suspend fun removeValues(keys: List<String>)
3420

35-
suspend fun removeValues(keys: List<String>) {
36-
return storage.removeValues(keys)
37-
}
21+
suspend fun getKeys(): List<String>
3822

39-
suspend fun getKeys(): List<String> {
40-
return storage.getKeys()
41-
}
23+
fun getKeysFlow(): Flow<List<String>>
4224

43-
fun getKeysFlow(): Flow<List<String>> = storage.getKeysFlow()
44-
45-
suspend fun clear() {
46-
storage.clear()
47-
}
48-
49-
companion object
25+
suspend fun clear()
5026
}
5127

52-
expect fun SharedStorage.Companion.create(
53-
context: PlatformContext,
54-
databaseName: String,
55-
): SharedStorage
28+
expect fun SharedStorage(context: PlatformContext, databaseName: String): SharedStorage
5629

57-
internal expect fun SharedStorage.Companion.createInMemory(context: PlatformContext): SharedStorage
30+
internal expect fun sharedStorageInMemory(context: PlatformContext): SharedStorage
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.asyncstorage.shared_storage
2+
3+
import androidx.sqlite.SQLiteException
4+
import kotlinx.coroutines.CancellationException
5+
import kotlinx.coroutines.flow.Flow
6+
import kotlinx.coroutines.flow.distinctUntilChanged
7+
import kotlinx.coroutines.flow.map
8+
import org.asyncstorage.shared_storage.database.StorageDatabase
9+
import org.asyncstorage.shared_storage.database.StorageEntry
10+
11+
/**
12+
* Default implementation of [SharedStorage] interface.
13+
*
14+
* Note about [getValuesFlow]: Since SQLite database trigger notifications only on table level, not
15+
* row level, non-observed requested keys will trigger emits. Therefor, flow returned has
16+
* .distinctUntilChanged to mimic row level update.
17+
*/
18+
internal class SharedStorageImpl(val database: StorageDatabase) : SharedStorage {
19+
20+
private val storage = database.storageDao()
21+
22+
override suspend fun getValues(keys: List<String>): List<Entry> = catchStorageException {
23+
storage.getValues(keys).map(StorageEntry::toEntry)
24+
}
25+
26+
override fun getValuesFlow(keys: List<String>): Flow<List<Entry>> =
27+
storage
28+
.getValuesFlow(keys)
29+
.map { list -> list.map(StorageEntry::toEntry) }
30+
.distinctUntilChanged()
31+
32+
override suspend fun setValues(entries: List<Entry>): List<Entry> = catchStorageException {
33+
val values = entries.map(Entry::toStorageEntry)
34+
storage.setValuesAndGet(values).map(StorageEntry::toEntry)
35+
}
36+
37+
override suspend fun removeValues(keys: List<String>) = catchStorageException {
38+
storage.removeValues(keys)
39+
}
40+
41+
override suspend fun getKeys(): List<String> = catchStorageException { storage.getKeys() }
42+
43+
override fun getKeysFlow(): Flow<List<String>> = storage.getKeysFlow()
44+
45+
override suspend fun clear() = catchStorageException { storage.clear() }
46+
}
47+
48+
private suspend fun <T> catchStorageException(block: suspend () -> T): T {
49+
try {
50+
return block()
51+
} catch (e: CancellationException) {
52+
throw e
53+
} catch (e: SQLiteException) {
54+
throw StorageException.SqliteException(e.message ?: "Unexcepted Sqlite exception", e.cause)
55+
} catch (e: Exception) {
56+
throw StorageException.OtherException(
57+
e.message ?: "Unknown exception: ${e::class.simpleName}",
58+
e.cause,
59+
)
60+
}
61+
}
Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
package org.asyncstorage.shared_storage
22

3-
actual fun SharedStorage.Companion.create(
4-
context: PlatformContext,
5-
databaseName: String
6-
): SharedStorage {
7-
TODO("JVM Not yet implemented")
3+
actual fun SharedStorage(context: PlatformContext, databaseName: String): SharedStorage {
4+
TODO("JVM Not yet implemented")
85
}
96

10-
internal actual fun SharedStorage.Companion.createInMemory(
11-
context: PlatformContext
12-
): SharedStorage {
13-
TODO("JVM Not yet implemented")
14-
}
7+
internal actual fun sharedStorageInMemory(context: PlatformContext): SharedStorage {
8+
TODO("JVM Not yet implemented")
9+
}

shared-storage/src/jvmTest/kotlin/tests/TestUtils.jvm.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import org.asyncstorage.shared_storage.SharedStorage
55
actual open class TestRunner actual constructor()
66

77
actual class StorageUtils actual constructor() {
8-
actual fun getStorage(): SharedStorage {
9-
TODO("Not yet implemented")
10-
}
8+
actual fun getStorage(): SharedStorage {
9+
TODO("Not yet implemented")
10+
}
11+
12+
actual fun cleanup() {
13+
TODO("Not yet implemented")
14+
}
1115
}

0 commit comments

Comments
 (0)