mirror of
				https://github.com/GayPizzaSpecifications/foundation.git
				synced 2025-11-04 11:39:39 +00:00 
			
		
		
		
	Merge branch 'store' into 'main'
Basic Persistent Store using Xodus See merge request lgorence/foundation!2
This commit is contained in:
		@ -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")
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user