diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index 35672f9..ce42184 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -2,6 +2,15 @@ name: Create Release on: workflow_dispatch: + inputs: + branch: + description: 'Branch to build and publish' + required: true + default: 'develop' + type: choice + options: + - develop + - feat/platform jobs: build: @@ -12,7 +21,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 with: - ref: develop # Ensure it works from the develop branch + ref: ${{ inputs.branch }} - name: Set up JDK 21 uses: actions/setup-java@v3 @@ -56,7 +65,7 @@ jobs: release_name: v${{ env.GRADLE_VERSION }}-dev.${{ env.COMMIT_HASH }} draft: false prerelease: true - commitish: develop + commitish: ${{ inputs.branch }} body: | This release contains dev builds for all Gradle modules. env: diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..30cf57e --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/build.gradle.kts b/build.gradle.kts index 945f334..54a111e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { val baseVersion = "0.0.1" val commitHash = System.getenv("COMMIT_HASH") val timestamp = System.currentTimeMillis() // Temporary to be able to build and publish directly out of fix branch with same commit hash -val snapshotVersion = "${baseVersion}-dev.${timestamp}-${commitHash}" +val snapshotVersion = "${baseVersion}-platform.${timestamp}-${commitHash}" allprojects { group = "app.simplecloud.plugin" @@ -19,7 +19,6 @@ allprojects { maven("https://libraries.minecraft.net") maven("https://buf.build/gen/maven") maven("https://repo.simplecloud.app/snapshots") - maven("https://buf.build/gen/maven") maven("https://repo.papermc.io/repository/maven-public") } } @@ -31,8 +30,11 @@ subprojects { dependencies { testImplementation(rootProject.libs.kotlin.test) compileOnly(rootProject.libs.kotlin.jvm) - compileOnly(rootProject.libs.bundles.simpleCloudController) + compileOnly(rootProject.libs.kotlin.coroutines) + compileOnly(rootProject.libs.simplecloud.api) compileOnly(rootProject.libs.bundles.adventure) + compileOnly(rootProject.libs.bundles.configurate) + compileOnly(rootProject.libs.slf4j.api) } kotlin { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 842a576..1f8286b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,22 +1,38 @@ [versions] -kotlin = "2.0.20" -simpleCloudController = "0.0.30-dev.e3e27fc" -sonatype-central-portal-publisher = "1.2.3" -adventure = "4.18.0" +kotlin = "2.2.20" +kotlin-coroutines = "1.10.2" + +sonatype-central-portal-publisher = "1.2.4" + +simplecloud-api = "0.1.0-platform.23-dev.1775473227066-1c0c25a" + +velocity = "3.5.0-SNAPSHOT" +adventure = "4.26.1" + +configurate = "4.2.0" +slf4j = "2.0.17" [libraries] kotlin-jvm = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } -simpleCloudControllerApi = { module = "app.simplecloud.controller:controller-api", version.ref = "simpleCloudController" } -simpleCloudControllerShared = { module = "app.simplecloud.controller:controller-shared", version.ref = "simpleCloudController" } +simplecloud-api = { module = "app.simplecloud.api:api", version.ref = "simplecloud-api" } + +velocity-api = { module = "com.velocitypowered:velocity-api", version.ref = "velocity" } adventure = { module = "net.kyori:adventure-api", version.ref = "adventure" } -adventureMiniMessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" } +adventure-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" } + +configurate-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate" } +configurate-gson = { module = "org.spongepowered:configurate-gson", version.ref = "configurate" } +configurate-extra-kotlin = { module = "org.spongepowered:configurate-extra-kotlin", version.ref = "configurate" } + +slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } [bundles] -simpleCloudController = ["simpleCloudControllerApi", "simpleCloudControllerShared"] -adventure = ["adventure", "adventureMiniMessage"] +adventure = ["adventure", "adventure-minimessage"] +configurate = ["configurate-yaml", "configurate-gson", "configurate-extra-kotlin"] [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ed33eb3..221c4f9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Sun Dec 29 18:17:48 CET 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/ConfigFactory.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/ConfigFactory.kt deleted file mode 100644 index b6f8d74..0000000 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/ConfigFactory.kt +++ /dev/null @@ -1,270 +0,0 @@ -package app.simplecloud.plugin.api.shared.config - -import app.simplecloud.plugin.api.shared.exception.ConfigurationException -import app.simplecloud.plugin.api.shared.repository.GenericEnumSerializer -import kotlinx.coroutines.* -import org.slf4j.LoggerFactory -import org.spongepowered.configurate.ConfigurationOptions -import org.spongepowered.configurate.kotlin.objectMapperFactory -import org.spongepowered.configurate.yaml.NodeStyle -import org.spongepowered.configurate.yaml.YamlConfigurationLoader -import java.io.File -import java.nio.file.* -import java.util.concurrent.atomic.AtomicReference -import kotlin.coroutines.CoroutineContext - -/** - * A configuration factory that loads, saves and watches configuration files. - * The factory automatically reloads the configuration when the file changes. - * - * Features: - * - Thread-safe configuration handling - * - File watching with automatic reloading - * - Configuration change callbacks - * - Validation support - * - * Usage: - * ``` - * // Using create - * val factory = ConfigFactory.create(File("config.yaml")) - * - * // Using create with custom coroutineContext - * val factory = ConfigFactory.create(File("config.json"), Dispatchers.Default) - * - * // Add change listener - * factory.onConfigChanged { oldConfig, newConfig -> - * println("Config updated from $oldConfig to $newConfig") - * } - * - * // Save the modified config - * factory.save(modifiedConfig) - * - * // Optionally with validation - * factory.save(modifiedConfig) { config -> - * config.someField.isNotEmpty() // return true/false for validation - * } - * ``` - */ -class ConfigFactory( - private val file: File, - private val configClass: Class, - private val coroutineContext: CoroutineContext = Dispatchers.IO -) : AutoCloseable { - - private val logger = LoggerFactory.getLogger(ConfigFactory::class.java) - private val configRef = AtomicReference() - private val path: Path = file.toPath() - private var watchJob: Job? = null - private val changeListeners = mutableListOf Unit>() - private val saveLock = Object() - private val scope = CoroutineScope(coroutineContext + SupervisorJob()) - - private val configurationLoader = YamlConfigurationLoader.builder() - .path(path) - .nodeStyle(NodeStyle.BLOCK) - .defaultOptions { options -> - options.serializers { builder -> - builder.registerAnnotatedObjects(objectMapperFactory()) - builder.register(Enum::class.java, GenericEnumSerializer) - } - } - .build() - - /** - * Loads existing configuration or creates a new one with default values. - * @param defaultConfig The default configuration to use if no file exists - * @param validator Optional validation function for the configuration - */ - fun loadOrCreate(defaultConfig: T, validator: ((T) -> Boolean)? = null) { - if (!configClass.isInstance(defaultConfig)) { - throw IllegalArgumentException("Default config must be an instance of ${configClass.name}") - } - - if (file.exists()) { - loadConfig(validator) - } else { - createDefaultConfig(defaultConfig, validator) - } - - registerWatcher() - } - - /** - * Checks if the configuration file exists. - * @return true if the configuration file exists - */ - fun exists(): Boolean = file.exists() - - /** - * Adds a listener that will be called whenever the configuration changes. - * @param listener The listener function that receives old and new config - */ - fun onConfigChanged(listener: suspend (T?, T) -> Unit) { - synchronized(changeListeners) { - changeListeners.add(listener) - } - } - - private fun createDefaultConfig(defaultConfig: T, validator: ((T) -> Boolean)?) { - path.parent?.let { Files.createDirectories(it) } - Files.createFile(path) - - if (validator?.invoke(defaultConfig) == false) { - throw ConfigurationException("Default configuration failed validation") - } - - synchronized(saveLock) { - try { - val node = configurationLoader.createNode() - node.set(configClass, defaultConfig) - configurationLoader.save(node) - runBlocking(coroutineContext) { - updateConfig(defaultConfig) - } - } catch (e: Exception) { - throw ConfigurationException("Failed to save default configuration", e) - } - } - } - - /** - * Gets the current configuration. - * @throws IllegalStateException if configuration is not loaded - */ - fun getConfig(): T { - return configRef.get() ?: throw IllegalStateException("Configuration not loaded or invalid type") - } - - /** - * Manually reloads the configuration from disk. - * @param validator Optional validation function for the loaded configuration - * @throws ConfigurationException if loading or validation fails - */ - @Throws(ConfigurationException::class) - fun reloadConfig(validator: ((T) -> Boolean)? = null) { - loadConfig(validator) - } - - @Throws(ConfigurationException::class) - private fun loadConfig(validator: ((T) -> Boolean)? = null) { - try { - val node = configurationLoader.load(ConfigurationOptions.defaults()) - val loadedConfig = node.get(configClass) - ?: throw ConfigurationException("Failed to parse configuration file") - - if (validator?.invoke(loadedConfig) == false) { - throw ConfigurationException("Configuration failed validation") - } - - runBlocking(coroutineContext) { - updateConfig(loadedConfig) - } - } catch (e: Exception) { - throw ConfigurationException("Failed to load configuration", e) - } - } - - /** - * Saves the provided configuration to disk. - * @param config The configuration to save - * @param validator Optional validation function to run before saving - * @throws ConfigurationException if saving or validation fails - */ - @Throws(ConfigurationException::class) - fun save(config: T, validator: ((T) -> Boolean)? = null) { - if (validator?.invoke(config) == false) { - throw ConfigurationException("Configuration failed validation") - } - - synchronized(saveLock) { - try { - val node = configurationLoader.createNode() - node.set(configClass, config) - configurationLoader.save(node) - runBlocking(coroutineContext) { - updateConfig(config) - } - } catch (e: Exception) { - throw ConfigurationException("Failed to save configuration", e) - } - } - } - - private suspend fun updateConfig(newConfig: T) { - val oldConfig = configRef.get() - configRef.set(newConfig) - - val listeners = synchronized(changeListeners) { - changeListeners.toList() - } - - listeners.forEach { listener -> - try { - withContext(coroutineContext) { - listener(oldConfig, newConfig) - } - } catch (e: Exception) { - logger.error("Error in config change listener", e) - } - } - } - - private fun registerWatcher(): Job { - val watchService = FileSystems.getDefault().newWatchService() - path.parent?.register( - watchService, - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_MODIFY - ) - - return scope.launch { - watchService.use { watchService -> - while (isActive) { - val key = watchService.take() - key.pollEvents().forEach { event -> - handleWatchEvent(event) - } - if (!key.reset()) { - break - } - } - } - }.also { watchJob = it } - } - - private suspend fun handleWatchEvent(event: WatchEvent<*>) { - val path = event.context() as? Path ?: return - if (!file.name.contains(path.toString())) return - - when (event.kind()) { - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_MODIFY -> { - delay(100) - try { - loadConfig() - } catch (e: ConfigurationException) { - logger.error("Failed to reload configuration: ${e.message}", e) - } - } - - else -> {} - } - } - - override fun close() { - scope.cancel() - watchJob?.cancel() - } - - companion object { - /** - * Creates a new ConfigFactory instance. - * @param file The configuration file - * @param coroutineContext The coroutine context to use for async operations - */ - inline fun create( - file: File, - coroutineContext: CoroutineContext = Dispatchers.IO - ): ConfigFactory = ConfigFactory(file, T::class.java, coroutineContext) - } -} \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/ConfigurateWatcherRegistry.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/ConfigurateWatcherRegistry.kt new file mode 100644 index 0000000..8cc2660 --- /dev/null +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/ConfigurateWatcherRegistry.kt @@ -0,0 +1,75 @@ +package app.simplecloud.plugin.api.shared.config + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import java.io.File +import java.nio.file.FileSystems +import java.nio.file.Path +import java.nio.file.WatchEvent + +/** + * @author Niklas Nieberler + */ + +class ConfigurateWatcherRegistry { + + private val events = hashMapOf, (File) -> Unit>() + private var watcherRequirements: (Path) -> Boolean = { false } + + /** + * Adds an event for the watcher + * @param event to register + * @param function to handle + */ + fun withEvent(vararg event: WatchEvent.Kind<*>, function: (File) -> Unit): ConfigurateWatcherRegistry { + event.forEach { this.events[it] = function } + return this + } + + /** + * Adds a requirement for the event watcher + * @param function the requirement + */ + fun withWatcherRequirements(function: (Path) -> Boolean): ConfigurateWatcherRegistry { + this.watcherRequirements = function + return this + } + + /** + * Registers a new [java.nio.file.WatchService] for a path + * @param path to register the watcher + */ + fun register(path: Path): Job { + val watchService = FileSystems.getDefault().newWatchService() + + path.parent.register( + watchService, + *this.events.keys.toTypedArray() + ) + + return CoroutineScope(Dispatchers.IO).launch { + while (isActive) { + val watchKey = watchService.take() + watchKey.pollEvents().forEach { watchEvent(path, it) } + watchKey.reset() + } + } + } + + private fun watchEvent(directoryPath: Path, event: WatchEvent<*>) { + val path = event.context() as? Path ?: return + if (!path.toString().endsWith(".yml")) + return + + val resolvedPath = directoryPath.resolve(path) + if (this.watcherRequirements(resolvedPath)) + return + + val kind = event.kind() + this.events[kind]?.invoke(resolvedPath.toFile()) + } + +} \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/ConfigurationFactory.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/ConfigurationFactory.kt new file mode 100644 index 0000000..fe2cbf7 --- /dev/null +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/ConfigurationFactory.kt @@ -0,0 +1,51 @@ +package app.simplecloud.plugin.api.shared.config + +import java.io.File + +/** + * @author Niklas Nieberler + */ + +class ConfigurationFactory( + private val file: File, + javaClass: Class, +) { + + private val yamlFileConfigurator = YamlFileConfigurator(javaClass) + + private var config: E? = null + + /** + * Loads the config and if it does not exist, it creates a default config + * @param defaultConfig the default configuration + */ + fun loadOrCreate(defaultConfig: E): E { + if (this.file.exists()) { + return loadConfiguration() + ?: throw NullPointerException("failed to save config") + } + + this.yamlFileConfigurator.save(this.file, defaultConfig) + this.config = defaultConfig + return defaultConfig + } + + /** + * Gets the cached config file + */ + fun get(): E { + return this.config ?: throw NullPointerException("failed to find config") + } + + fun save(entry: E) { + this.yamlFileConfigurator.save(this.file, entry) + this.config = entry + } + + private fun loadConfiguration(): E? { + val configuration = this.yamlFileConfigurator.load(this.file) + this.config = configuration + return configuration + } + +} \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/YamlDirectoryRepository.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/YamlDirectoryRepository.kt new file mode 100644 index 0000000..c3ca904 --- /dev/null +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/YamlDirectoryRepository.kt @@ -0,0 +1,99 @@ +package app.simplecloud.plugin.api.shared.config + +import java.io.File +import java.nio.file.* + +/** + * @author Niklas Nieberler + */ + +abstract class YamlDirectoryRepository( + private val directory: Path, + javaClass: Class +) { + + private val yamlFileConfigurator = YamlFileConfigurator(javaClass) + private val fileDirectory = directory.toFile() + + private val cachedEntities = mutableMapOf() + + /** + * Saves an element to the file directory + * @param entity to save + */ + abstract fun save(entity: E) + + abstract fun find(identifier: I): E? + + /** + * Loads all cached entities from a directory + */ + fun findAll(): List { + return this.cachedEntities.values.toList() + } + + /** + * Loads the yaml directory repository with all elements + * @return list of all file entities + */ + fun load(): List { + if (!this.fileDirectory.exists()) + this.fileDirectory.mkdirs() + + registerWatcher() + + return Files.walk(this.directory) + .toList() + .map { it.toFile() } + .filter { it.name.endsWith(".yml") || it.name.endsWith(".yaml") } + .mapNotNull { load(it) } + } + + /** + * Loads an entity from a file + * @param file to load + */ + fun load(file: File): E? { + val entity = this.yamlFileConfigurator.load(file) ?: return null + this.cachedEntities[file] = entity + return entity + } + + /** + * Saves an entity to the yaml formation + * @param name of the file + * @param entity to save + */ + protected fun save(name: String, entity: E) { + val file = this.directory.resolve(name).toFile() + this.yamlFileConfigurator.save(file, entity) + this.cachedEntities[file] = entity + } + + /** + * Deletes an entity file + * @param entity to delete + */ + fun delete(entity: E) { + val file = this.cachedEntities.keys + .find { this.cachedEntities[it] == entity } ?: return + delete(file) + } + + open fun watchUpdateEvent(file: File) {} + + private fun delete(file: File) { + file.delete() + this.cachedEntities.remove(file) + } + + private fun registerWatcher() { + ConfigurateWatcherRegistry() + .withEvent(StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY) { load(it) } + .withEvent(StandardWatchEventKinds.ENTRY_DELETE) { delete(it) } + .withEvent(StandardWatchEventKinds.ENTRY_MODIFY) { watchUpdateEvent(it) } + .withWatcherRequirements { Files.isDirectory(it) } + .register(this.directory) + } + +} \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/YamlFileConfigurator.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/YamlFileConfigurator.kt new file mode 100644 index 0000000..7dddd82 --- /dev/null +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/YamlFileConfigurator.kt @@ -0,0 +1,54 @@ +package app.simplecloud.plugin.api.shared.config + +import app.simplecloud.plugin.api.shared.config.serializer.GenericEnumSerializer +import org.spongepowered.configurate.CommentedConfigurationNode +import org.spongepowered.configurate.kotlin.objectMapperFactory +import org.spongepowered.configurate.yaml.NodeStyle +import org.spongepowered.configurate.yaml.YamlConfigurationLoader +import java.io.File + +/** + * @author Niklas Nieberler + */ + +class YamlFileConfigurator( + private val javaClass: Class +) { + + private val configurationLoaders = hashMapOf() + + private val defaultConfigurationBuilder = YamlConfigurationLoader.builder() + .nodeStyle(NodeStyle.BLOCK) + .defaultOptions { options -> + options.serializers { builder -> + builder.registerAnnotatedObjects(objectMapperFactory()) + builder.register(Enum::class.java, GenericEnumSerializer) + builder.register({ type -> type is Class<*> && type.isEnum }, GenericEnumSerializer) + } + } + + fun save(file: File, entity: E) { + val (node, loader) = buildNode(file) + node.set(this.javaClass, entity) + loader.save(node) + } + + fun load(file: File): E? { + val (node, _) = buildNode(file) + return node.get(this.javaClass) + } + + fun buildNode(file: File): Pair { + val loader = getOrCreateConfigurationLoader(file) + return Pair(loader.load(), loader) + } + + fun getOrCreateConfigurationLoader(file: File): YamlConfigurationLoader { + return this.configurationLoaders.getOrPut(file) { + this.defaultConfigurationBuilder + .path(file.toPath()) + .build() + } + } + +} diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/repository/DirectoryRepository.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/repository/DirectoryRepository.kt deleted file mode 100644 index a3b7805..0000000 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/repository/DirectoryRepository.kt +++ /dev/null @@ -1,362 +0,0 @@ -package app.simplecloud.plugin.api.shared.config.repository - -import app.simplecloud.plugin.api.shared.config.repository.handler.FileHandler -import app.simplecloud.plugin.api.shared.exception.RepositoryException -import kotlinx.coroutines.* -import org.slf4j.LoggerFactory -import java.io.File -import java.io.FileOutputStream -import java.net.URL -import java.nio.file.* -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicReference -import java.util.jar.JarFile -import kotlin.coroutines.CoroutineContext -import kotlin.io.path.pathString - -/** - * A directory repository that manages files of a specific type. - * Features: - * - Thread-safe entity handling - * - File watching with automatic reloading - * - Entity change callbacks - * - Validation support - * - Automatic resource cleanup - * - * @param I The type of the identifier - * @param T The type of the entity - */ -class DirectoryRepository constructor( - private val directory: Path, - private val fileHandler: FileHandler, - private val coroutineContext: CoroutineContext, - private val validator: ((T) -> Boolean)? = null -) : AutoCloseable { - - private val logger = LoggerFactory.getLogger(DirectoryRepository::class.java) - private val entities = ConcurrentHashMap>() - private val identifiers = ConcurrentHashMap() - private val changeListeners = mutableListOf Unit>() - private val errorHandlers = mutableListOf<(Exception) -> Unit>() - private val saveLock = Object() - private val scope = CoroutineScope(coroutineContext + SupervisorJob()) - private var watchJob: Job? = null - - init { - if (!directory.toFile().exists()) { - directory.toFile().mkdirs() - } - } - - /** - * Loads all entities from the directory or creates them using default values. - * @param defaultEntities Default entities to use if no existing files are found - * @throws RepositoryException if loading or creation fails - */ - @Throws(RepositoryException::class) - fun loadOrCreate(defaultEntities: Map = emptyMap()) { - try { - if (Files.newDirectoryStream(directory).use { it.none() }) { - loadFromResources(defaultEntities) - } - - load() - registerWatcher() - } catch (e: Exception) { - handleError(RepositoryException("Failed to load repository", e)) - } - } - - private fun loadFromResources( - defaultEntities: Map, - writeBom: Boolean = true - ) { - val targetDirectory = File(directory.toUri()).apply { mkdirs() } - val last = directory.pathString.split('/').last() - - val resourceUrl = DirectoryRepository::class.java.getResource("/$last") ?: run { - logger.warn("$last folder not found in resources") - return - } - - when (resourceUrl.protocol) { - "file" -> handleFileProtocol(resourceUrl, targetDirectory) - "jar" -> handleJarProtocol(resourceUrl, targetDirectory, writeBom) - else -> logger.error("Unsupported protocol: ${resourceUrl.protocol}") - } - - defaultEntities.forEach { (id, entity) -> save(id, entity) } - } - - private fun handleFileProtocol(resourceUrl: URL, targetDirectory: File) { - val resourceDir = File(resourceUrl.toURI()) - - if (resourceDir.exists()) { - resourceDir.copyRecursively(targetDirectory, overwrite = true) - } else { - logger.warn("Resource directory does not exist: ${resourceUrl.path}") - } - } - - private fun handleJarProtocol( - resourceUrl: URL, - targetDirectory: File, - writeBom: Boolean - ) { - val jarPath = resourceUrl.path.substringBefore("!").removePrefix("file:") - try { - JarFile(jarPath).use { jarFile -> - val last = directory.pathString.split('/').last() - var filesProcessed = 0 - var filesFailed = 0 - - jarFile.entries().asSequence() - .filter { it.name.startsWith("$last/") && !it.isDirectory } - .forEach { entry -> - val targetFile = File(targetDirectory, entry.name.removePrefix("$last/")) - targetFile.parentFile.mkdirs() - try { - jarFile.getInputStream(entry).use { inputStream -> - FileOutputStream(targetFile).use { fos -> - if (writeBom) { - fos.write(0xEF) - fos.write(0xBB) - fos.write(0xBF) - } - inputStream.copyTo(fos) - } - } - filesProcessed++ - logger.debug("Successfully extracted: ${entry.name}") - } catch (e: Exception) { - filesFailed++ - logger.error("Error copying file ${entry.name}") - } - } - - logger.debug("Processed $filesProcessed files from JAR ($filesFailed failed)") - } - } catch (e: Exception) { - logger.error("Error processing JAR file") - } - } - - private fun load() = - Files.walk(directory) - .filter { !it.toFile().isDirectory && it.toString().endsWith(fileHandler.fileExtension) } - .forEach { loadFile(it.toFile()) } - - private fun loadFile(file: File) { - try { - logger.info("Loading file ${file.name}") - fileHandler.load(file)?.let { entity -> - logger.info("Reached First ${file.name}") - if (validateEntity(entity)) { - logger.info("Reached ${file.name}") - entities[file] = AtomicReference(entity) - } - } - } catch (e: Exception) { - handleError(RepositoryException("Error loading file ${file.name}", e)) - } - } - - /** - * Saves an entity to the repository - * @throws RepositoryException if saving fails or validation fails - */ - @Throws(RepositoryException::class) - fun save(identifier: I, entity: T) { - if (!validateEntity(entity)) { - throw RepositoryException("Entity validation failed") - } - - synchronized(saveLock) { - try { - val file = getFile(identifier) - file.parentFile?.mkdirs() - - fileHandler.save(file, entity) - val oldEntity = entities[file]?.get() - entities[file] = AtomicReference(entity) - identifiers[file] = identifier - - scope.launch { - notifyChangeListeners(identifier, oldEntity, entity) - } - } catch (e: Exception) { - handleError(RepositoryException("Failed to save entity", e)) - throw e - } - } - } - - /** - * Deletes an entity from the repository - */ - fun delete(identifier: I): Boolean { - val file = getFile(identifier) - if (!file.exists()) return false - - synchronized(saveLock) { - return try { - val deleted = file.delete() - - if (deleted) { - val oldEntity = entities.remove(file)?.get() - identifiers.remove(file) - if (oldEntity != null) { - scope.launch { - notifyChangeListeners(identifier, oldEntity, null) - } - } - } - - deleted - } catch (e: Exception) { - handleError(RepositoryException("Failed to delete entity", e)) - false - } - } - } - - /** - * Finds an entity by identifier - */ - fun find(identifier: I): T? = - entities[getFile(identifier)]?.get() - - - /** - * Gets all entities in the repository - */ - fun getAll(): List = entities.values.mapNotNull { it.get() } - - /** - * Gets all identifiers in the repository - */ - fun getAllIdentifiers(): Set = identifiers.values.toSet() - - /** - * Adds a listener for entity changes - */ - fun onEntityChanged(listener: suspend (I, T?, T?) -> Unit) { - synchronized(changeListeners) { - changeListeners.add(listener) - } - } - - /** - * Adds an error handler - */ - fun onError(handler: (Exception) -> Unit) { - synchronized(errorHandlers) { - errorHandlers.add(handler) - } - } - - private fun validateEntity(entity: T): Boolean { - return validator?.invoke(entity) ?: fileHandler.validate(entity) - } - - private fun handleError(error: Exception) { - logger.error(error.message, error) - synchronized(errorHandlers) { - errorHandlers.forEach { it(error) } - } - } - - private suspend fun notifyChangeListeners(identifier: I, oldEntity: T?, newEntity: T?) { - val listeners = synchronized(changeListeners) { changeListeners.toList() } - - listeners.forEach { listener -> - try { - withContext(coroutineContext) { - if (oldEntity != newEntity) { - listener(identifier, oldEntity, newEntity) - } - } - } catch (e: Exception) { - handleError(RepositoryException("Error in change listener", e)) - } - } - } - - private fun getFile(identifier: I): File = - directory.resolve("$identifier${fileHandler.fileExtension}").toFile() - - - private fun registerWatcher(): Job { - val watchService = FileSystems.getDefault().newWatchService() - directory.register( - watchService, - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_MODIFY, - StandardWatchEventKinds.ENTRY_DELETE - ) - - return scope.launch { - watchService.use { service -> - while (isActive) { - val key = service.take() - - key.pollEvents().forEach { event -> - handleWatchEvent(event) - } - - if (!key.reset()) break - } - } - }.also { watchJob = it } - } - - private suspend fun handleWatchEvent(event: WatchEvent<*>) { - val path = event.context() as? Path ?: return - if (!path.toString().endsWith(fileHandler.fileExtension)) return - - val resolvedPath = directory.resolve(path) - if (Files.isDirectory(resolvedPath)) return - - when (event.kind()) { - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_MODIFY -> { - delay(100) - loadFile(resolvedPath.toFile()) - } - - StandardWatchEventKinds.ENTRY_DELETE -> { - val file = resolvedPath.toFile() - val identifier = identifiers[file] - val oldEntity = entities.remove(file)?.get() - - if (identifier != null && oldEntity != null) { - notifyChangeListeners(identifier, oldEntity, null) - } - } - - else -> {} - } - } - - override fun close() { - scope.cancel() - watchJob?.cancel() - } - - companion object { - /** - * Creates a new DirectoryRepository instance - */ - inline fun create( - directory: Path, - fileHandler: FileHandler, - noinline validator: ((T) -> Boolean)? = null, - coroutineContext: CoroutineContext = Dispatchers.IO - ): DirectoryRepository = DirectoryRepository( - directory = directory, - fileHandler = fileHandler, - validator = validator, - coroutineContext = coroutineContext - ) - } -} diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/repository/handler/FileHandler.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/repository/handler/FileHandler.kt deleted file mode 100644 index a144ba6..0000000 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/repository/handler/FileHandler.kt +++ /dev/null @@ -1,15 +0,0 @@ -package app.simplecloud.plugin.api.shared.config.repository.handler - -import java.io.File - -interface FileHandler { - - val fileExtension: String - - fun load(file: File): T? - - fun save(file: File, entity: T) - - fun validate(entity: T): Boolean = true - -} \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/repository/handler/PNGFileHandler.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/repository/handler/PNGFileHandler.kt deleted file mode 100644 index 2135c4e..0000000 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/repository/handler/PNGFileHandler.kt +++ /dev/null @@ -1,22 +0,0 @@ -package app.simplecloud.plugin.api.shared.config.repository.handler - -import java.awt.image.BufferedImage -import java.io.File -import javax.imageio.ImageIO - -class PNGFileHandler : FileHandler { - - override val fileExtension: String = ".png" - - override fun load(file: File): BufferedImage? = - runCatching { ImageIO.read(file) }.getOrNull() - - - override fun save(file: File, entity: BufferedImage) { - ImageIO.write(entity, "png", file) - } - - override fun validate(entity: BufferedImage): Boolean = - entity.width > 0 && entity.height > 0 - -} \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/repository/handler/YamlFileHandler.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/repository/handler/YamlFileHandler.kt deleted file mode 100644 index 4a7b4bc..0000000 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/repository/handler/YamlFileHandler.kt +++ /dev/null @@ -1,50 +0,0 @@ -package app.simplecloud.plugin.api.shared.config.repository.handler - -import app.simplecloud.plugin.api.shared.repository.GenericEnumSerializer -import org.spongepowered.configurate.kotlin.objectMapperFactory -import org.spongepowered.configurate.yaml.NodeStyle -import org.spongepowered.configurate.yaml.YamlConfigurationLoader -import java.io.File - -class YamlFileHandler( - private val clazz: Class -) : FileHandler { - - override val fileExtension: String = ".yml" - - private val loaders = mutableMapOf() - - private fun getOrCreateLoader(file: File): YamlConfigurationLoader { - return loaders.getOrPut(file) { - YamlConfigurationLoader.builder() - .path(file.toPath()) - .nodeStyle(NodeStyle.BLOCK) - .defaultOptions { options -> - options.serializers { builder -> - builder.registerAnnotatedObjects(objectMapperFactory()) - builder.register(Enum::class.java, GenericEnumSerializer) - } - } - .build() - } - } - - override fun load(file: File): T? { - return try { - val loader = getOrCreateLoader(file) - val node = loader.load() - node.get(clazz) - } catch (e: Exception) { - println("Error loading file ${file.name}: ${e.message}") - e.printStackTrace() - null - } - } - - override fun save(file: File, entity: T) { - val loader = getOrCreateLoader(file) - val node = loader.createNode() - node.set(clazz, entity) - loader.save(node) - } -} diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/GenericEnumSerializer.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/serializer/GenericEnumSerializer.kt similarity index 81% rename from plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/GenericEnumSerializer.kt rename to plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/serializer/GenericEnumSerializer.kt index 26b4da9..59deb61 100644 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/GenericEnumSerializer.kt +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/config/serializer/GenericEnumSerializer.kt @@ -1,23 +1,26 @@ -package app.simplecloud.plugin.api.shared.repository +package app.simplecloud.plugin.api.shared.config.serializer import org.spongepowered.configurate.ConfigurationNode import org.spongepowered.configurate.serialize.SerializationException import org.spongepowered.configurate.serialize.TypeSerializer import java.lang.reflect.Type +/** + * @author Niklas Nieberler + */ + object GenericEnumSerializer : TypeSerializer> { @Suppress("UNCHECKED_CAST") override fun deserialize(type: Type, node: ConfigurationNode): Enum<*> { val value = node.string ?: throw SerializationException("No value present in node") - if (type !is Class<*> || !type.isEnum) { + if (type !is Class<*> || !type.isEnum) throw SerializationException("Type is not an enum class") - } return try { java.lang.Enum.valueOf(type as Class>, value) - } catch (e: IllegalArgumentException) { + } catch (_: IllegalArgumentException) { throw SerializationException("Invalid enum constant") } } diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/exception/ConfigurationException.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/exception/ConfigurationException.kt deleted file mode 100644 index b22ab88..0000000 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/exception/ConfigurationException.kt +++ /dev/null @@ -1,4 +0,0 @@ -package app.simplecloud.plugin.api.shared.exception - -class ConfigurationException(message: String, cause: Throwable? = null) : - Exception(message, cause) \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/exception/RepositoryException.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/exception/RepositoryException.kt deleted file mode 100644 index a698774..0000000 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/exception/RepositoryException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package app.simplecloud.plugin.api.shared.exception - -class RepositoryException(message: String, cause: Throwable? = null) : Exception(message, cause) \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/extension/Extension.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/extension/Extension.kt index 913c9b7..9ac234a 100644 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/extension/Extension.kt +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/extension/Extension.kt @@ -1,7 +1,8 @@ package app.simplecloud.plugin.api.shared.extension -import app.simplecloud.controller.shared.group.Group -import app.simplecloud.controller.shared.server.Server +import app.simplecloud.api.group.Group +import app.simplecloud.api.persistentserver.PersistentServer +import app.simplecloud.api.server.Server import app.simplecloud.plugin.api.shared.placeholder.PlaceholderProvider import app.simplecloud.plugin.api.shared.placeholder.argument.ArgumentsResolver import net.kyori.adventure.text.Component @@ -32,4 +33,12 @@ suspend fun String.appendToComponent( vararg argumentsResolver: ArgumentsResolver ): Component { return PlaceholderProvider.groupPlaceholderProvider.append(group, this, prefix, *argumentsResolver) +} + +suspend fun String.appendToComponent( + persistentServer: PersistentServer, + prefix: String? = null, + vararg argumentsResolver: ArgumentsResolver +): Component { + return PlaceholderProvider.persistentServerPlaceholderProvider.append(persistentServer, this, prefix, *argumentsResolver) } \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/pattern/ServerPatternIdentifier.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/pattern/ServerPatternIdentifier.kt index 1f06f4d..e1ce9b4 100644 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/pattern/ServerPatternIdentifier.kt +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/pattern/ServerPatternIdentifier.kt @@ -1,29 +1,21 @@ package app.simplecloud.plugin.api.shared.pattern -import app.simplecloud.controller.api.ControllerApi -import app.simplecloud.controller.shared.group.Group -import app.simplecloud.controller.shared.server.Server +import app.simplecloud.api.CloudApi +import app.simplecloud.api.group.Group +import app.simplecloud.api.server.Server import app.simplecloud.plugin.api.shared.pretty.StringPrettifier - -/** - * @author Niklas Nieberler - */ +import kotlinx.coroutines.future.await class ServerPatternIdentifier( private val pattern: String = "-", regexPattern: String = pattern .replace("", "(?[a-zA-Z]+)") .replace("", "(?\\d+)"), - private val controllerApi: ControllerApi.Coroutine = ControllerApi.createCoroutineApi() + private val cloudApi: CloudApi = CloudApi.create() ) { private val regex = Regex(regexPattern) - /** - * Gets the group and numerical id matching the [pattern] in a [Pair] - * @param name the server name - * @param customRegex custom regex pattern - */ fun parse(name: String, customRegex: Regex? = null): Pair { val matchResult = customRegex?.matchEntire(name) ?: this.regex.matchEntire(name) if (matchResult == null) @@ -36,34 +28,26 @@ class ServerPatternIdentifier( return Pair(groupName, numericalId) } - /** - * Gets a server string as a set in the pattern - * @param server replaces it to pattern - */ fun parseServerToPattern(server: Server): String { return this.pattern - .replace("", server.group) - .replace("", server.properties["pretty-name"] ?: StringPrettifier.prettify(server.group)) - .replace("", server.uniqueId) - .replace("", server.uniqueId) + .replace("", server.serverBase.name) + .replace("", server.properties["pretty-name"]?.toString() ?: StringPrettifier.prettify(server.serverBase.name)) + .replace("", server.serverId) + .replace("", server.serverId) .replace("", server.numericalId.toString()) } - /** - * Gets the [Group] by the matching [pattern] - * @param name the server name - */ suspend fun getGroup(name: String): Group? { val groupName = parse(name).first - return this.controllerApi.getGroups().getGroupByName(groupName) + return try { + this.cloudApi.group().getGroupByName(groupName).await() + } catch (e: Exception) { + null + } } - /** - * Gets the numerical id by the matching [pattern] - * @param name the server name - */ fun getNumericalId(name: String): Int { return parse(name).second } -} \ No newline at end of file +} diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/PlaceholderProvider.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/PlaceholderProvider.kt index ea02261..24205dd 100644 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/PlaceholderProvider.kt +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/PlaceholderProvider.kt @@ -1,6 +1,7 @@ package app.simplecloud.plugin.api.shared.placeholder import app.simplecloud.plugin.api.shared.placeholder.provider.GroupPlaceholderProvider +import app.simplecloud.plugin.api.shared.placeholder.provider.PersistentServerPlaceholderProvider import app.simplecloud.plugin.api.shared.placeholder.provider.ServerPlaceholderProvider /** @@ -13,4 +14,6 @@ object PlaceholderProvider { val groupPlaceholderProvider = GroupPlaceholderProvider() + val persistentServerPlaceholderProvider = PersistentServerPlaceholderProvider() + } \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/argument/PropertiesArgumentsResolver.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/argument/PropertiesArgumentsResolver.kt index 5ae25e2..44badb3 100644 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/argument/PropertiesArgumentsResolver.kt +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/argument/PropertiesArgumentsResolver.kt @@ -8,7 +8,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue */ class PropertiesArgumentsResolver( - private val properties: Map + private val properties: Map ) : ArgumentsResolver { override fun getKey() = "property" @@ -17,7 +17,7 @@ class PropertiesArgumentsResolver( val argumentName = arguments.popOr("property expected").value() val defaultArgument = arguments.peek()?.value() ?: "" val string = this.properties[argumentName] ?: defaultArgument - return Tag.preProcessParsed(string) + return Tag.preProcessParsed(string.toString()) } } \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/argument/group/PlayerCountArgumentsResolver.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/argument/group/PlayerCountArgumentsResolver.kt index ee13478..c319290 100644 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/argument/group/PlayerCountArgumentsResolver.kt +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/argument/group/PlayerCountArgumentsResolver.kt @@ -1,21 +1,15 @@ package app.simplecloud.plugin.api.shared.placeholder.argument.group -import app.simplecloud.controller.api.ControllerApi -import app.simplecloud.controller.shared.group.Group +import app.simplecloud.api.CloudApi +import app.simplecloud.api.group.Group +import app.simplecloud.api.server.ServerState import app.simplecloud.plugin.api.shared.placeholder.argument.ArgumentsResolver -import build.buf.gen.simplecloud.controller.v1.ServerState +import kotlinx.coroutines.future.await import net.kyori.adventure.text.minimessage.tag.Tag import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue -import kotlin.collections.filter -import kotlin.collections.firstOrNull -import kotlin.collections.sumOf - -/** - * @author Niklas Nieberler - */ class PlayerCountArgumentsResolver( - private val controllerApi: ControllerApi.Coroutine, + private val cloudApi: CloudApi, private val group: Group, ) : ArgumentsResolver { @@ -27,10 +21,13 @@ class PlayerCountArgumentsResolver( return Tag.preProcessParsed(findPlayerCount(this.group, serverState).toString()) } - private suspend fun findPlayerCount(group: Group, state: ServerState?): Long { - return this.controllerApi.getServers().getServersByGroup(group) - .filter { it.state == state } - .sumOf { it.playerCount } + private suspend fun findPlayerCount(group: Group, state: ServerState?): Int { + val servers = this.cloudApi.server().getServersByGroup(group.name).await() + return if (state != null) { + servers.filter { it.state == state }.sumOf { it.playerCount } + } else { + servers.sumOf { it.playerCount } + } } -} \ No newline at end of file +} diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/argument/group/ServerCountArgumentsResolver.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/argument/group/ServerCountArgumentsResolver.kt index 5e2dd5c..2afedc6 100644 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/argument/group/ServerCountArgumentsResolver.kt +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/argument/group/ServerCountArgumentsResolver.kt @@ -1,20 +1,15 @@ package app.simplecloud.plugin.api.shared.placeholder.argument.group -import app.simplecloud.controller.api.ControllerApi -import app.simplecloud.controller.shared.group.Group +import app.simplecloud.api.CloudApi +import app.simplecloud.api.group.Group +import app.simplecloud.api.server.ServerState import app.simplecloud.plugin.api.shared.placeholder.argument.ArgumentsResolver -import build.buf.gen.simplecloud.controller.v1.ServerState +import kotlinx.coroutines.future.await import net.kyori.adventure.text.minimessage.tag.Tag import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue -import kotlin.collections.filter -import kotlin.collections.firstOrNull - -/** - * @author Niklas Nieberler - */ class ServerCountArgumentsResolver( - private val controllerApi: ControllerApi.Coroutine, + private val cloudApi: CloudApi, private val group: Group, ) : ArgumentsResolver { @@ -23,13 +18,16 @@ class ServerCountArgumentsResolver( override suspend fun resolve(arguments: ArgumentQueue): Tag? { val text = arguments.popOr("all").value() val serverState = ServerState.entries.firstOrNull { it.name.equals(text, true) } - return Tag.preProcessParsed(findPlayerCount(this.group, serverState).toString()) + return Tag.preProcessParsed(findServerCount(this.group, serverState).toString()) } - private suspend fun findPlayerCount(group: Group, state: ServerState?): Int { - return this.controllerApi.getServers().getServersByGroup(group) - .filter { it.state == state } - .size + private suspend fun findServerCount(group: Group, state: ServerState?): Int { + val servers = this.cloudApi.server().getServersByGroup(group.name).await() + return if (state != null) { + servers.filter { it.state == state }.size + } else { + servers.size + } } -} \ No newline at end of file +} diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/AbstractPlaceholderProvider.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/AbstractPlaceholderProvider.kt index bffc9dd..4a971de 100644 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/AbstractPlaceholderProvider.kt +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/AbstractPlaceholderProvider.kt @@ -1,6 +1,6 @@ package app.simplecloud.plugin.api.shared.placeholder.provider -import app.simplecloud.controller.api.ControllerApi +import app.simplecloud.api.CloudApi import app.simplecloud.plugin.api.shared.extension.text import app.simplecloud.plugin.api.shared.placeholder.argument.ArgumentsResolver import app.simplecloud.plugin.api.shared.placeholder.single.SinglePlaceholderExecutor @@ -16,35 +16,35 @@ abstract class AbstractPlaceholderProvider( private val executor: SinglePlaceholderExecutor, ) { - private val controllerApi = ControllerApi.createCoroutineApi() + private val cloudApi = CloudApi.create() /** * Gets the list of all available [ArgumentsResolver] - * @param controllerApi the instance of [ControllerApi.Coroutine] + * @param cloudApi the instance of [CloudApi] * @param value for the placeholder */ abstract suspend fun getArgumentsResolvers( - controllerApi: ControllerApi.Coroutine, + cloudApi: CloudApi, value: T, ): List /** * Gets the sum of all [TagResolver] - * @param value for the placeholder + * @param values for the placeholder * @param prefix of the placeholder key */ suspend fun getTagResolver( - value: T, + values: List, prefix: String? = null, vararg argumentsResolver: ArgumentsResolver, ): TagResolver { - val availableArgumentsResolver = listOf( - *getArgumentsResolvers(this.controllerApi, value).toTypedArray(), - *argumentsResolver - ) - val singleTagResolver = this.executor.getTagResolver(this.controllerApi, value, prefix) + val availableArgumentsResolver = buildList { + addAll(values.flatMap { getArgumentsResolvers(cloudApi, it) }) + addAll(argumentsResolver) + } + val singleTagResolver = values.map { this.executor.getTagResolver(this.cloudApi, it, prefix) } return TagResolver.resolver( - singleTagResolver, + *singleTagResolver.toTypedArray(), *availableArgumentsResolver .map { convertArgumentsResolverToTagResolver(it, prefix) } .toTypedArray() @@ -65,7 +65,25 @@ abstract class AbstractPlaceholderProvider( ): Component { return text( string, - getTagResolver(value, prefix, *argumentsResolver), + getTagResolver(listOf(value), prefix, *argumentsResolver), + ) + } + + /** + * Serializes the string to a [Component] + * @param values for the placeholder + * @param string the message + * @param prefix of the placeholder key + */ + suspend fun append( + values: List, + string: String, + prefix: String? = null, + vararg argumentsResolver: ArgumentsResolver, + ): Component { + return text( + string, + getTagResolver(values, prefix, *argumentsResolver), ) } diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/GroupPlaceholderProvider.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/GroupPlaceholderProvider.kt index 1d68f33..16e9726 100644 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/GroupPlaceholderProvider.kt +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/GroupPlaceholderProvider.kt @@ -1,24 +1,20 @@ package app.simplecloud.plugin.api.shared.placeholder.provider -import app.simplecloud.controller.api.ControllerApi -import app.simplecloud.controller.shared.group.Group +import app.simplecloud.api.CloudApi +import app.simplecloud.api.group.Group import app.simplecloud.plugin.api.shared.placeholder.argument.PropertiesArgumentsResolver import app.simplecloud.plugin.api.shared.placeholder.argument.group.PlayerCountArgumentsResolver import app.simplecloud.plugin.api.shared.placeholder.argument.group.ServerCountArgumentsResolver import app.simplecloud.plugin.api.shared.placeholder.single.SingleGroupPlaceholderExecutor -/** - * @author Niklas Nieberler - */ - class GroupPlaceholderProvider : AbstractPlaceholderProvider( SingleGroupPlaceholderExecutor() ) { - override suspend fun getArgumentsResolvers(controllerApi: ControllerApi.Coroutine, value: Group) = listOf( + override suspend fun getArgumentsResolvers(cloudApi: CloudApi, value: Group) = listOf( PropertiesArgumentsResolver(value.properties), - PlayerCountArgumentsResolver(controllerApi, value), - ServerCountArgumentsResolver(controllerApi, value) + PlayerCountArgumentsResolver(cloudApi, value), + ServerCountArgumentsResolver(cloudApi, value) ) -} \ No newline at end of file +} diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/PersistentServerPlaceholderProvider.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/PersistentServerPlaceholderProvider.kt new file mode 100644 index 0000000..1ade4b9 --- /dev/null +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/PersistentServerPlaceholderProvider.kt @@ -0,0 +1,20 @@ +package app.simplecloud.plugin.api.shared.placeholder.provider + +import app.simplecloud.api.CloudApi +import app.simplecloud.api.persistentserver.PersistentServer +import app.simplecloud.plugin.api.shared.placeholder.argument.* +import app.simplecloud.plugin.api.shared.placeholder.single.SinglePersistentServerPlaceholderExecutor + +/** + * @author Niklas Nieberler + */ + +class PersistentServerPlaceholderProvider : AbstractPlaceholderProvider( + SinglePersistentServerPlaceholderExecutor() +) { + + override suspend fun getArgumentsResolvers(cloudApi: CloudApi, value: PersistentServer) = listOf( + PropertiesArgumentsResolver(value.properties) + ) + +} \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/ServerPlaceholderProvider.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/ServerPlaceholderProvider.kt index c08f797..8a78b99 100644 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/ServerPlaceholderProvider.kt +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/provider/ServerPlaceholderProvider.kt @@ -1,7 +1,7 @@ package app.simplecloud.plugin.api.shared.placeholder.provider -import app.simplecloud.controller.api.ControllerApi -import app.simplecloud.controller.shared.server.Server +import app.simplecloud.api.CloudApi +import app.simplecloud.api.server.Server import app.simplecloud.plugin.api.shared.placeholder.argument.* import app.simplecloud.plugin.api.shared.placeholder.single.SingleServerPlaceholderExecutor @@ -13,7 +13,7 @@ class ServerPlaceholderProvider : AbstractPlaceholderProvider( SingleServerPlaceholderExecutor() ) { - override suspend fun getArgumentsResolvers(controllerApi: ControllerApi.Coroutine, value: Server) = listOf( + override suspend fun getArgumentsResolvers(cloudApi: CloudApi, value: Server) = listOf( PropertiesArgumentsResolver(value.properties) ) diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SingleGroupPlaceholderExecutor.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SingleGroupPlaceholderExecutor.kt index d81439d..0eb254d 100644 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SingleGroupPlaceholderExecutor.kt +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SingleGroupPlaceholderExecutor.kt @@ -1,30 +1,24 @@ package app.simplecloud.plugin.api.shared.placeholder.single -import app.simplecloud.controller.api.ControllerApi -import app.simplecloud.controller.shared.group.Group +import app.simplecloud.api.CloudApi +import app.simplecloud.api.group.Group import app.simplecloud.plugin.api.shared.placeholder.async.AsyncPlaceholder - -/** - * @author Niklas Nieberler - */ +import kotlinx.coroutines.future.await class SingleGroupPlaceholderExecutor : SinglePlaceholderExecutor { - override fun getAsyncPlaceholders(controllerApi: ControllerApi.Coroutine) = listOf>( + override fun getAsyncPlaceholders(cloudApi: CloudApi) = listOf>( AsyncPlaceholder("name") { it.name }, AsyncPlaceholder("type") { it.type }, AsyncPlaceholder("max_players") { it.maxPlayers }, AsyncPlaceholder("min_memory") { it.minMemory }, AsyncPlaceholder("max_memory") { it.maxMemory }, - AsyncPlaceholder("start_port") { it.startPort }, - AsyncPlaceholder("min_online_count") { it.minOnlineCount }, - AsyncPlaceholder("max_online_count") { it.maxOnlineCount }, - AsyncPlaceholder("online_players") { getOnlinePlayersByGroup(controllerApi, it) }, + AsyncPlaceholder("online_players") { getOnlinePlayersByGroup(cloudApi, it) }, ) - private suspend fun getOnlinePlayersByGroup(controllerApi: ControllerApi.Coroutine, group: Group): Long { - return controllerApi.getServers().getServersByGroup(group) + private suspend fun getOnlinePlayersByGroup(cloudApi: CloudApi, group: Group): Int { + return cloudApi.server().getServersByGroup(group.name).await() .sumOf { it.playerCount } } -} \ No newline at end of file +} diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SinglePersistentServerPlaceholderExecutor.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SinglePersistentServerPlaceholderExecutor.kt new file mode 100644 index 0000000..2e99d5c --- /dev/null +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SinglePersistentServerPlaceholderExecutor.kt @@ -0,0 +1,29 @@ +package app.simplecloud.plugin.api.shared.placeholder.single + +import app.simplecloud.api.CloudApi +import app.simplecloud.api.persistentserver.PersistentServer +import app.simplecloud.api.server.Server +import app.simplecloud.plugin.api.shared.placeholder.async.AsyncPlaceholder +import app.simplecloud.plugin.api.shared.pretty.StringPrettifier + +/** + * @author Niklas Nieberler + */ + +class SinglePersistentServerPlaceholderExecutor : SinglePlaceholderExecutor { + + override fun getAsyncPlaceholders(cloudApi: CloudApi) = listOf>( + AsyncPlaceholder("id") { it.persistentServerId }, + AsyncPlaceholder("name") { it.name }, + AsyncPlaceholder("pretty_name") { + it.properties["pretty-name"] ?: StringPrettifier.prettify(it.name) + }, + AsyncPlaceholder("type") { it.type }, + AsyncPlaceholder("online_players") { it.playerCount }, + AsyncPlaceholder("max_players") { it.maxPlayers }, + AsyncPlaceholder("min_memory") { it.minMemory }, + AsyncPlaceholder("max_memory") { it.maxMemory }, + AsyncPlaceholder("motd") { it.properties["motd"] ?: "A Minecraft server" } + ) + +} \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SinglePlaceholderExecutor.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SinglePlaceholderExecutor.kt index 5140084..bd80632 100644 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SinglePlaceholderExecutor.kt +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SinglePlaceholderExecutor.kt @@ -1,6 +1,6 @@ package app.simplecloud.plugin.api.shared.placeholder.single -import app.simplecloud.controller.api.ControllerApi +import app.simplecloud.api.CloudApi import app.simplecloud.plugin.api.shared.placeholder.async.AsyncPlaceholder import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver @@ -12,22 +12,22 @@ interface SinglePlaceholderExecutor { /** * Gets a list with all available [AsyncPlaceholder] - * @param controllerApi the instance of [ControllerApi.Coroutine] + * @param cloudApi the instance of [app.simplecloud.api.CloudApi] */ - fun getAsyncPlaceholders(controllerApi: ControllerApi.Coroutine): List> + fun getAsyncPlaceholders(cloudApi: CloudApi): List> /** * Gets a [TagResolver] with all available tag resolvers from the [getAsyncPlaceholders] method - * @param controllerApi the instance of [ControllerApi.Coroutine] + * @param cloudApi the instance of [CloudApi] * @param value for the placeholder * @param prefix first name for the placeholder key */ suspend fun getTagResolver( - controllerApi: ControllerApi.Coroutine, + cloudApi: CloudApi, value: T, prefix: String? = null, ): TagResolver { - return TagResolver.resolver(getAsyncPlaceholders(controllerApi) + return TagResolver.resolver(getAsyncPlaceholders(cloudApi) .map { it.invokeTagResolver(value, prefix) }) } diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SingleServerPlaceholderExecutor.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SingleServerPlaceholderExecutor.kt index 938642e..d66bdc4 100644 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SingleServerPlaceholderExecutor.kt +++ b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/placeholder/single/SingleServerPlaceholderExecutor.kt @@ -1,7 +1,7 @@ package app.simplecloud.plugin.api.shared.placeholder.single -import app.simplecloud.controller.api.ControllerApi -import app.simplecloud.controller.shared.server.Server +import app.simplecloud.api.CloudApi +import app.simplecloud.api.server.Server import app.simplecloud.plugin.api.shared.placeholder.async.AsyncPlaceholder import app.simplecloud.plugin.api.shared.pretty.StringPrettifier @@ -11,14 +11,14 @@ import app.simplecloud.plugin.api.shared.pretty.StringPrettifier class SingleServerPlaceholderExecutor : SinglePlaceholderExecutor { - override fun getAsyncPlaceholders(controllerApi: ControllerApi.Coroutine) = listOf>( - AsyncPlaceholder("id") { it.uniqueId }, + override fun getAsyncPlaceholders(cloudApi: CloudApi) = listOf>( + AsyncPlaceholder("id") { it.serverId }, AsyncPlaceholder("numerical_id") { it.numericalId }, - AsyncPlaceholder("group_name") { it.group }, + AsyncPlaceholder("group_name") { it.serverBase.name }, AsyncPlaceholder("group_pretty_name") { - it.properties["pretty-name"] ?: StringPrettifier.prettify(it.group) + it.properties["pretty-name"] ?: StringPrettifier.prettify(it.serverBase.name) }, - AsyncPlaceholder("type") { it.type }, + AsyncPlaceholder("type") { it.serverBase.type }, AsyncPlaceholder("state") { it.state }, AsyncPlaceholder("ip") { it.ip }, AsyncPlaceholder("port") { it.port }, diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/LoadableRepository.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/LoadableRepository.kt deleted file mode 100644 index 822a537..0000000 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/LoadableRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package app.simplecloud.plugin.api.shared.repository - -interface LoadableRepository : Repository { - - fun load(): List - -} \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/Repository.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/Repository.kt deleted file mode 100644 index 7bd8177..0000000 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/Repository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package app.simplecloud.plugin.api.shared.repository - -interface Repository { - - fun delete(element: E): Boolean - - fun save(element: E) - - fun find(identifier: I): E? - - fun getAll(): List - -} \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/ResourcedYamlDirectoryRepository.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/ResourcedYamlDirectoryRepository.kt deleted file mode 100644 index 8436502..0000000 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/ResourcedYamlDirectoryRepository.kt +++ /dev/null @@ -1,91 +0,0 @@ -package app.simplecloud.plugin.api.shared.repository - -import java.io.File -import java.io.FileOutputStream -import java.net.URL -import java.nio.file.Path -import java.util.jar.JarFile -import kotlin.io.path.exists -import kotlin.io.path.pathString - -/** - * An extension of [YamlDirectoryRepository] that adds resource loading capabilities - * - * Key differences from [YamlDirectoryRepository] - * - Automatically load default yml configuration files from resources if the target directory doesn't exist - * - Supports both file and jar protocols for loading resources - * - Handles both standalone file system resources and jar-packed resources - * - * Use this implementation of [YamlDirectoryRepository] when you need to - * - Ship default configurations with your plugin - * - Ensure configuration files exist on first run - * - Handle both development (file) and production (jar) environments - */ -abstract class ResourcedYamlDirectoryRepository( - private val directory: Path, - private val clazz: Class, -) : YamlDirectoryRepository(directory, clazz) { - - override fun load(): List { - if (!directory.exists()) loadDefaults() - - return super.load() - } - - private fun loadDefaults() { - val targetDirectory = File(directory.toUri()).apply { mkdirs() } - - val last = directory.pathString.split('/').last() - - val resourceUrl = YamlDirectoryRepository::class.java.getResource("/$last") ?: run { - println("$last folder not found in resources") - return - } - - when (resourceUrl.protocol) { - "file" -> handleFileProtocol(resourceUrl, targetDirectory) - "jar" -> handleJarProtocol(resourceUrl, targetDirectory) - else -> println("Unsupported protocol: ${resourceUrl.protocol}") - } - } - - private fun handleFileProtocol(resourceUrl: URL, targetDirectory: File) { - val resourceDir = File(resourceUrl.toURI()) - if (resourceDir.exists()) { - resourceDir.copyRecursively(targetDirectory, overwrite = true) - } else { - println("Resource directory does not exist: ${resourceUrl.path}") - } - } - - private fun handleJarProtocol(resourceUrl: URL, targetDirectory: File) { - val jarPath = resourceUrl.path.substringBefore("!").removePrefix("file:") - try { - JarFile(jarPath).use { jarFile -> - val last = directory.pathString.split('/').last() - jarFile.entries().asSequence() - .filter { it.name.startsWith("$last/") && !it.isDirectory } - .forEach { entry -> - val targetFile = File(targetDirectory, entry.name.removePrefix("$last/")) - targetFile.parentFile.mkdirs() - try { - jarFile.getInputStream(entry).use { inputStream -> - FileOutputStream(targetFile).use { fos -> - fos.write(0xEF) - fos.write(0xBB) - fos.write(0xBF) - - inputStream.copyTo(fos) - } - } - } catch (e: Exception) { - println("Error copying file ${entry.name}: ${e.message}") - } - } - } - } catch (e: Exception) { - println("Error processing JAR file: ${e.message}") - e.printStackTrace() - } - } -} \ No newline at end of file diff --git a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/YamlDirectoryRepository.kt b/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/YamlDirectoryRepository.kt deleted file mode 100644 index 732bd7b..0000000 --- a/plugin-shared/src/main/kotlin/app/simplecloud/plugin/api/shared/repository/YamlDirectoryRepository.kt +++ /dev/null @@ -1,137 +0,0 @@ -package app.simplecloud.plugin.api.shared.repository - -import kotlinx.coroutines.* -import org.spongepowered.configurate.ConfigurationOptions -import org.spongepowered.configurate.kotlin.objectMapperFactory -import org.spongepowered.configurate.loader.ParsingException -import org.spongepowered.configurate.serialize.TypeSerializerCollection -import org.spongepowered.configurate.yaml.NodeStyle -import org.spongepowered.configurate.yaml.YamlConfigurationLoader -import java.io.File -import java.nio.file.FileSystems -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardWatchEventKinds - -abstract class YamlDirectoryRepository( - private val directory: Path, - private val clazz: Class, -) : LoadableRepository { - - private val watchService = FileSystems.getDefault().newWatchService() - private val loaders = mutableMapOf() - private val entities = mutableMapOf() - - abstract fun getFileName(identifier: I): String - - override fun delete(element: E): Boolean { - val file = entities.keys.find { entities[it] == element } ?: return false - return deleteFile(file) - } - - override fun getAll(): List { - return entities.values.toList() - } - - override fun load(): List { - if (!directory.toFile().exists()) { - directory.toFile().mkdirs() - } - - registerWatcher() - - return Files.walk(directory) - .toList() - .filter { !it.toFile().isDirectory && it.toString().endsWith(".yml") } - .mapNotNull { load(it.toFile()) } - } - - private fun load(file: File): E? { - try { - val loader = getOrCreateLoader(file) - val node = loader.load(ConfigurationOptions.defaults()) - val entity = node.get(clazz) ?: return null - entities[file] = entity - return entity - } catch (ex: ParsingException) { - val existedBefore = entities.containsKey(file) - if (existedBefore) { - return null - } - - return null - } - } - - private fun deleteFile(file: File): Boolean { - val deletedSuccessfully = file.delete() - val removedSuccessfully = entities.remove(file) != null - return deletedSuccessfully && removedSuccessfully - } - - protected fun save(fileName: String, entity: E) { - val file = directory.resolve(fileName).toFile() - val loader = getOrCreateLoader(file) - val node = loader.createNode(ConfigurationOptions.defaults()) - node.set(clazz, entity) - loader.save(node) - entities[file] = entity - } - - open fun watchUpdateEvent(file: File) {} - - protected open fun addSerializers(builder: TypeSerializerCollection.Builder) {} - - private fun getOrCreateLoader(file: File): YamlConfigurationLoader { - return loaders.getOrPut(file) { - YamlConfigurationLoader.builder() - .path(file.toPath()) - .nodeStyle(NodeStyle.BLOCK) - .defaultOptions { options -> - options.serializers { builder -> - builder.registerAnnotatedObjects(objectMapperFactory()) - builder.register(Enum::class.java, GenericEnumSerializer) - addSerializers(builder) - } - }.build() - } - } - - private fun registerWatcher(): Job { - directory.register( - watchService, - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_DELETE, - StandardWatchEventKinds.ENTRY_MODIFY - ) - - return CoroutineScope(Dispatchers.IO).launch { - while (isActive) { - val key = watchService.take() - - key.pollEvents().forEach { event -> - val path = event.context() as? Path ?: return@forEach - if (!path.toString().endsWith(".yml")) return@forEach - - val resolvedPath = directory.resolve(path) - if (Files.isDirectory(resolvedPath)) return@forEach - - when (event.kind()) { - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_MODIFY -> { - delay(100) - load(resolvedPath.toFile()) - watchUpdateEvent(resolvedPath.toFile()) - } - - StandardWatchEventKinds.ENTRY_DELETE -> { - deleteFile(resolvedPath.toFile()) - } - } - } - - key.reset() - } - } - } -} \ No newline at end of file diff --git a/plugin-velocity/build.gradle.kts b/plugin-velocity/build.gradle.kts index 3daeb6c..7468d5f 100644 --- a/plugin-velocity/build.gradle.kts +++ b/plugin-velocity/build.gradle.kts @@ -4,6 +4,6 @@ plugins { dependencies { compileOnly(project(":plugin-shared")) - compileOnly("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT") - kapt("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT") + compileOnly(libs.velocity.api) + kapt(libs.velocity.api) } \ No newline at end of file