improved KVStorage support

This commit is contained in:
Sergey Chernov 2023-01-20 02:54:36 +01:00
parent 8175bacfb8
commit 361d3f0b2b
6 changed files with 113 additions and 9 deletions

View File

@ -10,7 +10,7 @@ plugins {
} }
group = "net.sergeych" group = "net.sergeych"
version = "0.4.2-SNAPSHOT" version = "0.4.3-SNAPSHOT"
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -16,15 +16,40 @@ import kotlin.reflect.typeOf
interface KVStorage { interface KVStorage {
operator fun get(key: String): ByteArray? operator fun get(key: String): ByteArray?
operator fun set(key: String, value: 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<String>
val keys: Collection<String> /**
* 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() { fun clear() {
for (k in keys) this[k] = null 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) { fun addAll(other: KVStorage) {
for (k in other.keys) { for (k in other.keys) {
this[k] = other[k] this[k] = other[k]
@ -38,6 +63,8 @@ inline operator fun <reified T> KVStorage.invoke(defaultValue: T,overrideName: S
inline fun <reified T> KVStorage.stored(defaultValue: T, overrideName: String? = null) = inline fun <reified T> KVStorage.stored(defaultValue: T, overrideName: String? = null) =
KVStorageDelegate<T>(this, typeOf<T>(), defaultValue, overrideName) KVStorageDelegate<T>(this, typeOf<T>(), defaultValue, overrideName)
inline fun <reified T> KVStorage.optStored(defaultValue: T?=null, overrideName: String? = null) =
KVStorageDelegate<T?>(this, typeOf<T?>(), defaultValue, overrideName)
class KVStorageDelegate<T>( class KVStorageDelegate<T>(
private val storage: KVStorage, private val storage: KVStorage,
@ -110,7 +137,7 @@ class MemoryKVStorage(copyFrom: KVStorage? = null) : KVStorage {
return key in data return key in data
} }
override val keys: Collection<String> override val keys: Set<String>
get() = underlying?.keys ?: data.keys get() = underlying?.keys ?: data.keys
override fun clear() { override fun clear() {

View File

@ -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<String>
get() {
val kk = mutableListOf<String>()
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()
}
}

View File

@ -1,5 +1,7 @@
package net.sergeych.parsec3 package net.sergeych.parsec3
actual fun defaultNamedStorage(name: String): KVStorage { import kotlinx.browser.localStorage
TODO("Not yet implemented")
}
@Suppress("unused")
actual fun defaultNamedStorage(name: String): KVStorage = BrowserKVStorage(name, localStorage)

View File

@ -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())
}
}

View File

@ -97,7 +97,7 @@ internal class WsServerKtTest {
@Test @Test
fun testWsServerReconnect() { fun testWsServerReconnect() {
embeddedServer(Netty, port = 8080) { embeddedServer(Netty, port = 8090) {
parsec3TransportServer( parsec3TransportServer(
TestApiServer, TestApiServer,
) { ) {
@ -112,7 +112,7 @@ internal class WsServerKtTest {
}.start(wait = false) }.start(wait = false)
Log.connectConsole(Log.Level.DEBUG) 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) { on(api.bar) {
"bar:$it" "bar:$it"
} }