From 361d3f0b2b3212ef571e06a0306f50d2bc4b42b5 Mon Sep 17 00:00:00 2001 From: sergeych Date: Fri, 20 Jan 2023 02:54:36 +0100 Subject: [PATCH] improved KVStorage support --- build.gradle.kts | 2 +- .../kotlin/net.sergeych.parsec3/KVStorage.kt | 33 ++++++++++++++-- .../net/sergeych/parsec3/BrowserKVStorage.kt | 39 +++++++++++++++++++ .../sergeych/parsec3/defaultNamedStorage.kt | 8 ++-- src/jsTest/kotlin/testBrowserKVStorage.kt | 36 +++++++++++++++++ .../net/sergeych/parsec3/WsServerKtTest.kt | 4 +- 6 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 src/jsMain/kotlin/net/sergeych/parsec3/BrowserKVStorage.kt create mode 100644 src/jsTest/kotlin/testBrowserKVStorage.kt diff --git a/build.gradle.kts b/build.gradle.kts index 2d9f5b9..2e8829e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { } group = "net.sergeych" -version = "0.4.2-SNAPSHOT" +version = "0.4.3-SNAPSHOT" repositories { mavenCentral() diff --git a/src/commonMain/kotlin/net.sergeych.parsec3/KVStorage.kt b/src/commonMain/kotlin/net.sergeych.parsec3/KVStorage.kt index 0725dcd..a510836 100644 --- a/src/commonMain/kotlin/net.sergeych.parsec3/KVStorage.kt +++ b/src/commonMain/kotlin/net.sergeych.parsec3/KVStorage.kt @@ -16,15 +16,40 @@ import kotlin.reflect.typeOf interface KVStorage { operator fun get(key: String): ByteArray? operator fun set(key: String, value: ByteArray?) - operator fun contains(key: String): Boolean + + /** + * Check whether key is in storage. + * Default implementation uses [keys]. You may override it for performance + */ + operator fun contains(key: String): Boolean = key in keys + + val keys: Set - val keys: Collection + /** + * Get number of object in the storage + * Default implementation uses [keys]. You may override it for performance + */ + val size: Int get() = keys.size + /** + * Clears all objects in the storage + * Default implementation uses [keys]. You may override it for performance + */ fun clear() { for (k in keys) this[k] = null } + /** + * Default implementation uses [keys]. You may override it for performance + */ + fun isEmpty() = size == 0 + + /** + * Default implementation uses [keys]. You may override it for performance + */ + fun isNotEmpty() = size != 0 + fun addAll(other: KVStorage) { for (k in other.keys) { this[k] = other[k] @@ -38,6 +63,8 @@ inline operator fun KVStorage.invoke(defaultValue: T,overrideName: S inline fun KVStorage.stored(defaultValue: T, overrideName: String? = null) = KVStorageDelegate(this, typeOf(), defaultValue, overrideName) +inline fun KVStorage.optStored(defaultValue: T?=null, overrideName: String? = null) = + KVStorageDelegate(this, typeOf(), defaultValue, overrideName) class KVStorageDelegate( private val storage: KVStorage, @@ -110,7 +137,7 @@ class MemoryKVStorage(copyFrom: KVStorage? = null) : KVStorage { return key in data } - override val keys: Collection + override val keys: Set get() = underlying?.keys ?: data.keys override fun clear() { diff --git a/src/jsMain/kotlin/net/sergeych/parsec3/BrowserKVStorage.kt b/src/jsMain/kotlin/net/sergeych/parsec3/BrowserKVStorage.kt new file mode 100644 index 0000000..29b7b0e --- /dev/null +++ b/src/jsMain/kotlin/net/sergeych/parsec3/BrowserKVStorage.kt @@ -0,0 +1,39 @@ +package net.sergeych.parsec3 + +import net.sergeych.mp_tools.decodeBase64Compact +import net.sergeych.mp_tools.encodeToBase64Compact +import org.w3c.dom.Storage +import org.w3c.dom.set + +/** + * Default KV storage in browser. Use if with `localStorage` or `sessionStorage`. Uses + * prefix for storage values to not to collide with other data + */ +class BrowserKVStorage(keyPrefix: String, private val bst: Storage) : KVStorage { + + private val prefix = "$keyPrefix:" + fun k(key: String) = "$prefix$key" + override fun get(key: String): ByteArray? { + return bst.getItem(k(key))?.decodeBase64Compact() + } + + override fun set(key: String, value: ByteArray?) { + val corrected = k(key) + if (value == null) + bst.removeItem(corrected) + else + bst.set(corrected, value.encodeToBase64Compact()) + } + + override val keys: Set + get() { + val kk = mutableListOf() + for (i in 0 until bst.length) { + val k = bst.key(i) ?: break + if( k.startsWith(prefix)) { + kk += k.substring(prefix.length) + } + } + return kk.toSet() + } +} \ No newline at end of file diff --git a/src/jsMain/kotlin/net/sergeych/parsec3/defaultNamedStorage.kt b/src/jsMain/kotlin/net/sergeych/parsec3/defaultNamedStorage.kt index f345136..0d4ab59 100644 --- a/src/jsMain/kotlin/net/sergeych/parsec3/defaultNamedStorage.kt +++ b/src/jsMain/kotlin/net/sergeych/parsec3/defaultNamedStorage.kt @@ -1,5 +1,7 @@ package net.sergeych.parsec3 -actual fun defaultNamedStorage(name: String): KVStorage { - TODO("Not yet implemented") -} \ No newline at end of file +import kotlinx.browser.localStorage + + +@Suppress("unused") +actual fun defaultNamedStorage(name: String): KVStorage = BrowserKVStorage(name, localStorage) \ No newline at end of file diff --git a/src/jsTest/kotlin/testBrowserKVStorage.kt b/src/jsTest/kotlin/testBrowserKVStorage.kt new file mode 100644 index 0000000..db8be99 --- /dev/null +++ b/src/jsTest/kotlin/testBrowserKVStorage.kt @@ -0,0 +1,36 @@ +import kotlinx.browser.sessionStorage +import net.sergeych.parsec3.BrowserKVStorage +import net.sergeych.parsec3.optStored +import kotlin.test.* + +class testBrowserKVStorage { + @Test + fun testStorage() { + val st = BrowserKVStorage("tpr", sessionStorage) + var foo: String? by st.optStored() + assertNull(foo) + foo = "bar" + assertEquals("bar", foo) + println(st.keys) + assertTrue { "foo" in st.keys } + assertFalse { "bar" in st.keys } + var bar: Int? by st.optStored() + assertNull(bar) + bar = 42 + assertEquals(42, bar) + assertTrue { "foo" in st.keys } + assertTrue { "bar" in st.keys } + assertEquals("bar", foo) + + val st2 = BrowserKVStorage("tpr", sessionStorage) + val foo2: String? by st.optStored(overrideName = "foo") + assertEquals("bar", foo2) + + assertTrue(st2.isNotEmpty()) + assertTrue(st.isNotEmpty()) + + st.clear() + assertTrue(st.isEmpty()) + + } +} \ No newline at end of file diff --git a/src/jvmTest/kotlin/net/sergeych/parsec3/WsServerKtTest.kt b/src/jvmTest/kotlin/net/sergeych/parsec3/WsServerKtTest.kt index b8c845f..4a50c64 100644 --- a/src/jvmTest/kotlin/net/sergeych/parsec3/WsServerKtTest.kt +++ b/src/jvmTest/kotlin/net/sergeych/parsec3/WsServerKtTest.kt @@ -97,7 +97,7 @@ internal class WsServerKtTest { @Test fun testWsServerReconnect() { - embeddedServer(Netty, port = 8080) { + embeddedServer(Netty, port = 8090) { parsec3TransportServer( TestApiServer, ) { @@ -112,7 +112,7 @@ internal class WsServerKtTest { }.start(wait = false) Log.connectConsole(Log.Level.DEBUG) - val client = Parsec3WSClient("ws://localhost:8080/api/p3", TestApiClient) { + val client = Parsec3WSClient("ws://localhost:8090/api/p3", TestApiClient) { on(api.bar) { "bar:$it" }