Merge branch 'store' into 'main'

Basic Persistent Store using Xodus

See merge request lgorence/foundation!2
This commit is contained in:
kendfinger 2021-12-23 01:08:37 +00:00
commit 1791a4ccd4
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("com.charleskorn.kaml:kaml:0.38.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1") 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 // Paper API
compileOnly("io.papermc.paper:paper-api:1.18.1-R0.1-SNAPSHOT") compileOnly("io.papermc.paper:paper-api:1.18.1-R0.1-SNAPSHOT")
} }

View File

@ -1,17 +1,22 @@
package cloud.kubelet.foundation.core package cloud.kubelet.foundation.core
import cloud.kubelet.foundation.core.command.BackupCommand import cloud.kubelet.foundation.core.command.*
import cloud.kubelet.foundation.core.command.GamemodeCommand import cloud.kubelet.foundation.core.persist.PersistentStore
import cloud.kubelet.foundation.core.command.LeaderboardCommand import cloud.kubelet.foundation.core.persist.setAllProperties
import cloud.kubelet.foundation.core.command.UpdateCommand import io.papermc.paper.event.player.AsyncChatEvent
import net.kyori.adventure.text.Component import net.kyori.adventure.text.Component
import net.kyori.adventure.text.TextComponent
import org.bukkit.GameMode import org.bukkit.GameMode
import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandExecutor
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener import org.bukkit.event.Listener
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import java.nio.file.Path import java.nio.file.Path
import java.time.Instant
import java.util.concurrent.ConcurrentHashMap
class FoundationCorePlugin : JavaPlugin(), Listener { class FoundationCorePlugin : JavaPlugin(), Listener {
internal val persistentStores = ConcurrentHashMap<String, PersistentStore>()
private lateinit var _pluginDataPath: Path private lateinit var _pluginDataPath: Path
var pluginDataPath: Path var pluginDataPath: Path
@ -29,6 +34,13 @@ class FoundationCorePlugin : JavaPlugin(), Listener {
_pluginDataPath = value _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() { override fun onEnable() {
pluginDataPath = dataFolder.toPath() pluginDataPath = dataFolder.toPath()
val backupPath = pluginDataPath.resolve(BACKUPS_DIRECTORY) val backupPath = pluginDataPath.resolve(BACKUPS_DIRECTORY)
@ -48,10 +60,12 @@ class FoundationCorePlugin : JavaPlugin(), Listener {
registerCommandExecutor(listOf("adventure", "a"), GamemodeCommand(GameMode.ADVENTURE)) registerCommandExecutor(listOf("adventure", "a"), GamemodeCommand(GameMode.ADVENTURE))
registerCommandExecutor(listOf("spectator", "sp"), GamemodeCommand(GameMode.SPECTATOR)) registerCommandExecutor(listOf("spectator", "sp"), GamemodeCommand(GameMode.SPECTATOR))
registerCommandExecutor(listOf("leaderboard", "lb"), LeaderboardCommand()) registerCommandExecutor(listOf("leaderboard", "lb"), LeaderboardCommand())
registerCommandExecutor(listOf("pstorestats"), StoreStatsCommand(this))
val log = slF4JLogger val log = slF4JLogger
log.info("Features:") log.info("Features:")
Util.printFeatureStatus(log, "Backup", BACKUP_ENABLED) Util.printFeatureStatus(log, "Backup", BACKUP_ENABLED)
chatLogStore = getPersistentStore("chat-logs")
} }
private fun registerCommandExecutor(name: String, executor: CommandExecutor) { private fun registerCommandExecutor(name: String, executor: CommandExecutor) {
@ -81,6 +95,26 @@ class FoundationCorePlugin : JavaPlugin(), Listener {
server.sendMessage(component) 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 { companion object {
private const val BACKUPS_DIRECTORY = "backups" 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: aliases:
- lb - lb
permission: foundation.command.leaderboard permission: foundation.command.leaderboard
pstorestats:
description: Persistent Store Stats
usage: /pstorestats
permission: foundation.command.pstorestats