Basic Persistent Store using Xodus

This commit is contained in:
Kenneth Endfinger 2021-12-22 19:53:27 -05:00
parent 3591906076
commit e2e7cc4840
No known key found for this signature in database
GPG Key ID: C4E68E5647420E10
6 changed files with 97 additions and 4 deletions

View File

@ -85,6 +85,10 @@ subprojects {
implementation("com.charleskorn.kaml:kaml:0.38.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
// Persistence
implementation("org.jetbrains.xodus:xodus-openAPI:1.3.232")
implementation("org.jetbrains.xodus:xodus-entity-store:1.3.232")
// Paper API
compileOnly("io.papermc.paper:paper-api:1.18.1-R0.1-SNAPSHOT")
}

View File

@ -1,17 +1,22 @@
package cloud.kubelet.foundation.core
import cloud.kubelet.foundation.core.command.BackupCommand
import cloud.kubelet.foundation.core.command.GamemodeCommand
import cloud.kubelet.foundation.core.command.LeaderboardCommand
import cloud.kubelet.foundation.core.command.UpdateCommand
import cloud.kubelet.foundation.core.command.*
import cloud.kubelet.foundation.core.persist.PersistentStore
import cloud.kubelet.foundation.core.persist.setAllProperties
import io.papermc.paper.event.player.AsyncChatEvent
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.TextComponent
import org.bukkit.GameMode
import org.bukkit.command.CommandExecutor
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.plugin.java.JavaPlugin
import java.nio.file.Path
import java.time.Instant
import java.util.concurrent.ConcurrentHashMap
class FoundationCorePlugin : JavaPlugin(), Listener {
internal val persistentStores = ConcurrentHashMap<String, PersistentStore>()
private lateinit var _pluginDataPath: Path
var pluginDataPath: Path
@ -29,6 +34,13 @@ class FoundationCorePlugin : JavaPlugin(), Listener {
_pluginDataPath = value
}
/**
* Fetch a persistent store by name. Make sure the name is path-safe, descriptive and consistent across server runs.
*/
fun getPersistentStore(name: String) = persistentStores.getOrPut(name) { PersistentStore(this, name) }
private lateinit var chatLogStore: PersistentStore
override fun onEnable() {
pluginDataPath = dataFolder.toPath()
val backupPath = pluginDataPath.resolve(BACKUPS_DIRECTORY)
@ -48,10 +60,12 @@ class FoundationCorePlugin : JavaPlugin(), Listener {
registerCommandExecutor(listOf("adventure", "a"), GamemodeCommand(GameMode.ADVENTURE))
registerCommandExecutor(listOf("spectator", "sp"), GamemodeCommand(GameMode.SPECTATOR))
registerCommandExecutor(listOf("leaderboard", "lb"), LeaderboardCommand())
registerCommandExecutor(listOf("pstorestats"), StoreStatsCommand(this))
val log = slF4JLogger
log.info("Features:")
Util.printFeatureStatus(log, "Backup", BACKUP_ENABLED)
chatLogStore = getPersistentStore("chat-logs")
}
private fun registerCommandExecutor(name: String, executor: CommandExecutor) {
@ -81,6 +95,26 @@ class FoundationCorePlugin : JavaPlugin(), Listener {
server.sendMessage(component)
}*/
@EventHandler
private fun logOnChatMessage(e: AsyncChatEvent) {
val player = e.player
val message = e.message()
if (message !is TextComponent) {
return
}
val content = message.content()
chatLogStore.create("ChatMessageEvent") {
setAllProperties(
"timestamp" to Instant.now().toEpochMilli(),
"player.id" to player.identity().uuid().toString(),
"player.name" to player.name,
"message.content" to content
)
}
}
companion object {
private const val BACKUPS_DIRECTORY = "backups"

View File

@ -0,0 +1,19 @@
package cloud.kubelet.foundation.core.command
import cloud.kubelet.foundation.core.FoundationCorePlugin
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
class StoreStatsCommand(private val plugin: FoundationCorePlugin) : CommandExecutor {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
plugin.persistentStores.forEach { (name, store) ->
store.transact { tx ->
val types = tx.entityTypes
val counts = types.associateWith { type -> tx.getAll(type).size() }.toSortedMap()
sender.sendMessage("Store $name ->", *counts.map { " ${it.key} -> ${it.value} entries" }.toTypedArray())
}
}
return true
}
}

View File

@ -0,0 +1,25 @@
package cloud.kubelet.foundation.core.persist
import cloud.kubelet.foundation.core.FoundationCorePlugin
import jetbrains.exodus.entitystore.Entity
import jetbrains.exodus.entitystore.PersistentEntityStores
import jetbrains.exodus.entitystore.StoreTransaction
class PersistentStore(corePlugin: FoundationCorePlugin, fileStoreName: String) : AutoCloseable {
private val fileStorePath = corePlugin.pluginDataPath.resolve("persistence/${fileStoreName}")
internal val entityStore = PersistentEntityStores.newInstance(fileStorePath.toFile())
fun transact(block: (StoreTransaction) -> Unit) = entityStore.executeInTransaction(block)
fun create(entityTypeName: String, populate: Entity.() -> Unit) = transact { tx ->
val entity = tx.newEntity(entityTypeName)
populate(entity)
}
fun <T> find(entityTypeName: String, propertyName: String, value: Comparable<T>) =
transact { tx -> tx.find(entityTypeName, propertyName, value) }
override fun close() {
entityStore.close()
}
}

View File

@ -0,0 +1,7 @@
package cloud.kubelet.foundation.core.persist
import jetbrains.exodus.entitystore.Entity
fun <T : Comparable<*>> Entity.setAllProperties(vararg entries: Pair<String, T>) = entries.forEach { entry ->
setProperty(entry.first, entry.second)
}

View File

@ -45,3 +45,7 @@ commands:
aliases:
- lb
permission: foundation.command.leaderboard
pstorestats:
description: Persistent Store Stats
usage: /pstorestats
permission: foundation.command.pstorestats