Add abstraction for writeable magix history

This commit is contained in:
Alexander Nozik 2023-08-03 19:21:35 +03:00
parent dc2d0094fc
commit a172da0a3d
5 changed files with 70 additions and 18 deletions

View File

@ -1,7 +1,7 @@
package space.kscience.magix.api package space.kscience.magix.api
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.*
/* /*
@ -33,3 +33,14 @@ public data class MagixMessage(
val parentId: String? = null, val parentId: String? = null,
val user: JsonElement? = null, val user: JsonElement? = null,
) )
/**
* The default accessor for username. If `user` is an object, take it's "name" field.
* If it is primitive, take its content. Return "@error" if it is an array.
*/
public val MagixMessage.userName: String? get() = when(user){
null, JsonNull -> null
is JsonObject -> user.jsonObject["name"]?.jsonPrimitive?.content
is JsonPrimitive -> user.content
else -> "@error"
}

View File

@ -15,6 +15,7 @@ kscience {
useSerialization { useSerialization {
json() json()
} }
useCoroutines()
dependencies { dependencies {
api(projects.magix.magixApi) api(projects.magix.magixApi)
api(spclibs.kotlinx.datetime) api(spclibs.kotlinx.datetime)

View File

@ -6,12 +6,11 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonPrimitive
import space.kscience.magix.api.MagixEndpoint import space.kscience.magix.api.MagixEndpoint
import space.kscience.magix.api.MagixEndpoint.Companion.magixJson import space.kscience.magix.api.MagixEndpoint.Companion.magixJson
import space.kscience.magix.api.MagixMessage import space.kscience.magix.api.MagixMessage
import space.kscience.magix.api.MagixMessageFilter import space.kscience.magix.api.MagixMessageFilter
import space.kscience.magix.api.userName
import space.kscience.magix.storage.* import space.kscience.magix.storage.*
import java.nio.file.Path import java.nio.file.Path
import kotlin.sequences.Sequence import kotlin.sequences.Sequence
@ -31,7 +30,7 @@ private fun Entity.parseMagixMessage(): MagixMessage = MagixMessage(
}, },
) )
public class XodusMagixHistory(private val store: PersistentEntityStore) : MagixHistory { public class XodusMagixHistory(private val store: PersistentEntityStore) : WriteableMagixHistory {
public fun writeMessage(storeTransaction: StoreTransaction, message: MagixMessage) { public fun writeMessage(storeTransaction: StoreTransaction, message: MagixMessage) {
storeTransaction.newEntity(XodusMagixStorage.MAGIC_MESSAGE_ENTITY_TYPE).apply { storeTransaction.newEntity(XodusMagixStorage.MAGIC_MESSAGE_ENTITY_TYPE).apply {
@ -49,20 +48,13 @@ public class XodusMagixHistory(private val store: PersistentEntityStore) : Magix
message.parentId?.let { message.parentId?.let {
setProperty(MagixMessage::parentId.name, it) setProperty(MagixMessage::parentId.name, it)
} }
message.user?.let { message.userName?.let {
setProperty( setProperty(MagixMessage::user.name, it)
MagixMessage::user.name,
when (it) {
is JsonObject -> it["name"]?.jsonPrimitive?.content ?: "@error"
is JsonPrimitive -> it.content
else -> "@error"
}
)
} }
} }
} }
public fun sendMessage(message: MagixMessage) { override suspend fun send(message: MagixMessage) {
store.executeInTransaction { transaction -> store.executeInTransaction { transaction ->
writeMessage(transaction, message) writeMessage(transaction, message)
} }
@ -139,6 +131,7 @@ public class XodusMagixHistory(private val store: PersistentEntityStore) : Magix
} }
} }
/** /**
* Attach a Xodus storage process to the given endpoint. * Attach a Xodus storage process to the given endpoint.
*/ */
@ -154,7 +147,7 @@ public class XodusMagixStorage(
//TODO consider message buffering //TODO consider message buffering
private val subscriptionJob = endpoint.subscribe(subscriptionFilter).onEach { message -> private val subscriptionJob = endpoint.subscribe(subscriptionFilter).onEach { message ->
history.sendMessage(message) history.send(message)
}.launchIn(scope) }.launchIn(scope)
private val broadcastJob = endpoint.launchHistory(scope, history, endpointName = endpointName) private val broadcastJob = endpoint.launchHistory(scope, history, endpointName = endpointName)

View File

@ -4,7 +4,9 @@ import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.doubleOrNull
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import space.kscience.magix.api.MagixFormat import space.kscience.magix.api.MagixFormat
import space.kscience.magix.api.MagixMessage import space.kscience.magix.api.MagixMessage
import space.kscience.magix.api.MagixMessageFilter import space.kscience.magix.api.MagixMessageFilter
@ -56,8 +58,17 @@ private fun JsonElement.takeElement(path: String): JsonElement? = if (path.isEmp
public fun MagixPayloadFilter.test(element: JsonElement): Boolean = when (this) { public fun MagixPayloadFilter.test(element: JsonElement): Boolean = when (this) {
is MagixPayloadFilter.Equals -> element.takeElement(path) == value is MagixPayloadFilter.Equals -> element.takeElement(path) == value
is MagixPayloadFilter.DateTimeInRange -> TODO() is MagixPayloadFilter.DateTimeInRange -> {
is MagixPayloadFilter.NumberInRange -> TODO() element.takeElement(path)?.jsonPrimitive?.content?.let {
LocalDateTime.parse(it) in from..to
} ?: false
}
is MagixPayloadFilter.NumberInRange -> {
element.takeElement(path)?.jsonPrimitive?.doubleOrNull?.let {
it in (from.toDouble()..to.toDouble())
} ?: false
}
is MagixPayloadFilter.Not -> !argument.test(element) is MagixPayloadFilter.Not -> !argument.test(element)
is MagixPayloadFilter.And -> left.test(element) && right.test(element) is MagixPayloadFilter.And -> left.test(element) && right.test(element)
is MagixPayloadFilter.Or -> left.test(element) || right.test(element) is MagixPayloadFilter.Or -> left.test(element) || right.test(element)
@ -103,3 +114,7 @@ public interface MagixHistory {
} }
} }
public interface WriteableMagixHistory: MagixHistory{
public suspend fun send(message: MagixMessage)
}

View File

@ -0,0 +1,32 @@
package space.kscience.magix.storage
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import space.kscience.magix.api.MagixMessage
import space.kscience.magix.api.MagixMessageFilter
class InMemoryMagixHistory() : WriteableMagixHistory {
private val cache = mutableListOf<MagixMessage>()
private val mutex = Mutex()
override suspend fun send(message: MagixMessage) {
mutex.withLock {
cache.add(message)
}
}
override suspend fun useMessages(
magixFilter: MagixMessageFilter?,
payloadFilter: MagixPayloadFilter?,
userFilter: MagixUsernameFilter?,
callback: (Sequence<MagixMessage>) -> Unit,
) = mutex.withLock {
val sequence = cache.asSequence().filter { message ->
(magixFilter?.accepts(message) ?: true) &&
(userFilter?.userName?.equals(message.user) ?: true) &&
payloadFilter?.test(message.payload) ?: true
}
callback(sequence)
}
}