diff --git a/build.gradle.kts b/build.gradle.kts index 4af3758b..befd681a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,6 +40,14 @@ dependencies { compileOnly("net.md-5:bungeecord-api:1.20-R0.1") compileOnly("com.velocitypowered:velocity-api:3.1.2-SNAPSHOT") + /* Logging Module */ + implementation("org.apache.logging.log4j:log4j-api:2.21.0") + implementation("org.apache.logging.log4j:log4j-core:2.21.0") + + /* Files Module */ + implementation("me.carleslc.Simple-YAML:Simple-Yaml:1.8.4") + + /* Global Depends */ implementation("org.jetbrains:annotations:24.0.1") implementation("commons-io:commons-io:2.15.0") implementation("com.google.code.gson:gson:2.10.1") diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/bungee/modules/tasksmodule/BungeeTasksModule.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/bungee/modules/tasksmodule/BungeeTasksModule.kt new file mode 100644 index 00000000..ec5c9ba0 --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/bungee/modules/tasksmodule/BungeeTasksModule.kt @@ -0,0 +1,80 @@ +package xyz.theprogramsrc.simplecoreapi.bungee.modules.tasksmodule + +import net.md_5.bungee.api.scheduler.ScheduledTask +import net.md_5.bungee.api.scheduler.TaskScheduler +import xyz.theprogramsrc.simplecoreapi.bungee.BungeeLoader +import xyz.theprogramsrc.simplecoreapi.global.module.Module +import xyz.theprogramsrc.simplecoreapi.global.module.ModuleDescription +import xyz.theprogramsrc.simplecoreapi.global.modules.tasksmodule.models.RecurringTask +import java.util.concurrent.TimeUnit + +class BungeeTasksModule: Module { + + + companion object { + /** + * Instance of the [BungeeTasksModule]. + */ + lateinit var instance: BungeeTasksModule + private set + } + + override val description: ModuleDescription = ModuleDescription( + name = "TasksModule", + version = "0.3.0", + authors = listOf("Im-Fran"), + ) + + /** + * BungeeCord Scheduler + */ + val scheduler: TaskScheduler = BungeeLoader.instance.proxy.scheduler + + /** + * Instance of Bungee Loader + */ + private val plugin = BungeeLoader.instance + + override fun onEnable() { + instance = this + } + + override fun onDisable() { + } + + /** + * Runs an async task after the specified delay in ticks + * @param delay The delay in ticks. Defaults to 1 (1 tick = 0.05 seconds) + * @param task The task to run + * @return the [ScheduledTask] + */ + fun runAsync(delay: Int = 1, task: () -> Unit): ScheduledTask = scheduler.schedule(plugin, task, delay.times(50).toLong(), TimeUnit.MILLISECONDS) + + /** + * Runs a repeating task asynchronously every given ticks (1 tick = 0.05 seconds) after the given ticks (1 tick = 0.05 seconds) + * @param delay The delay in ticks. Defaults to 1 + * @param period The period in ticks. Defaults to 1 + * @param task The task to run + * @return the [RecurringTask] + */ + fun runAsyncRepeating(delay: Int = 1, period: Int = 1, task: () -> Unit): RecurringTask { + val bungeeTask = scheduler.schedule(plugin, task, delay.times(50).toLong(), period.times(50).toLong(), TimeUnit.MILLISECONDS) + return object: RecurringTask(){ + var cancelled: Boolean = false + + override fun start(): RecurringTask = this.apply { + if(cancelled) { + this@BungeeTasksModule.runAsyncRepeating(delay, period, task) + cancelled = false + } + } + + override fun stop(): RecurringTask = this.apply { + bungeeTask.cancel() + cancelled = true + } + + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/SimpleCoreAPI.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/SimpleCoreAPI.kt index 6a3e9ac1..74cdade2 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/SimpleCoreAPI.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/SimpleCoreAPI.kt @@ -3,23 +3,17 @@ package xyz.theprogramsrc.simplecoreapi.global import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.simple.SimpleLogger -import xyz.theprogramsrc.simplecoreapi.global.dependencydownloader.DependencyDownloader -import xyz.theprogramsrc.simplecoreapi.global.dependencydownloader.DependencyLoader -import xyz.theprogramsrc.simplecoreapi.global.dependencydownloader.interfaces.DependencyLoader +import xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule.extensions.file +import xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule.extensions.folder import xyz.theprogramsrc.simplecoreapi.global.utils.SoftwareType -import xyz.theprogramsrc.simplecoreapi.global.utils.extensions.file -import xyz.theprogramsrc.simplecoreapi.global.utils.extensions.folder import xyz.theprogramsrc.simplecoreapi.global.utils.update.GitHubUpdateChecker import xyz.theprogramsrc.simplecoreapi.standalone.StandaloneLoader import java.io.File /** * Class used to initialize SimpleCoreAPI (DO NOT CALL IT FROM EXTERNAL PLUGINS, IT MAY CRASH) - * @param dependencyClassLoader The [DependencyLoader] to use to load the dependencies */ -class SimpleCoreAPI( - private val dependencyClassLoader: xyz.theprogramsrc.simplecoreapi.global.dependencydownloader.interfaces.DependencyLoader -){ +class SimpleCoreAPI { companion object { /** @@ -83,39 +77,6 @@ class SimpleCoreAPI( } else { logger.info("Running on unknown server software. Some features might not work as expected!") } - - // Now we'll download the dependencies - measureLoad("Downloaded dependencies in {time}") { - DependencyDownloader() - } - - // Now we load the downloaded dependencies - measureLoad("Loaded dependencies in {time}") { - DependencyLoader(dependencyClassLoader = dependencyClassLoader) - } - } - - /** - * Measures the amount of time in milliseconds it takes to execute the given block. Example: - * ```kt - * measureLoad("Waited for {time}") { - * // wait for 100 ms - * Thread.sleep(100) - * } - * ``` - * - * Sample console output: - * ```log - * Waited for 100ms - * ``` - * @param message The message to print. You can use '{time}' to replace with the amount of time in ms - * @param block The block to execute - */ - fun measureLoad(message: String, block: () -> T): T { - val now = System.currentTimeMillis() - val response = block() - logger.info(message.replace("{time}", "${System.currentTimeMillis() - now}ms")) - return response } /** diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/dependencydownloader/DependencyDownloader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/dependencydownloader/DependencyDownloader.kt deleted file mode 100644 index 884b3927..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/dependencydownloader/DependencyDownloader.kt +++ /dev/null @@ -1,128 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.dependencydownloader - -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import org.apache.commons.io.FileUtils -import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI -import xyz.theprogramsrc.simplecoreapi.global.models.Dependency -import xyz.theprogramsrc.simplecoreapi.global.models.Repository -import xyz.theprogramsrc.simplecoreapi.global.utils.extensions.file -import java.io.File -import java.net.URL - -class DependencyDownloader { - - private val logger = SimpleCoreAPI.logger - private val librariesFolder = SimpleCoreAPI.dataFolder("libraries/") - private val modulesRepositoryFile = SimpleCoreAPI.dataFolder(path = "modules-repository.json", asFolder = false) - - init { - // Download and update the modules repository file - updateModulesRepository() - - // Downloads the pending dependencies - val pendingDependencies = Dependency.dependencies.filter { !it.asFile().exists() } - if(pendingDependencies.isNotEmpty()) { - logger.info("Downloading ${pendingDependencies.size} pending dependencies...") - pendingDependencies.forEach { dependency -> - logger.info("Downloading ${dependency.group}:${dependency.artifact}:${dependency.version}...") - downloadDependency(dependency) - } - } - } - - /** - * Downloads a dependency - * @param dependency The dependency to download - */ - private fun downloadDependency(dependency: Dependency) { - val destination = dependency.asFile() // Where to save the dependency - val artifactUri = Repository.findArtifact(dependency) // Get the repository where the dependency is located - val url = URL(artifactUri) // Create the URL to download the dependency - val connection = url.openConnection() // Open the connection - connection.setRequestProperty("User-Agent", "SimpleCoreAPI") // Set the user agent - connection.connect() // Connect to the URL - val contentLength = connection.contentLengthLong // Get the content length - val inputStream = connection.getInputStream() // Get the input stream - val outputStream = destination.outputStream() // Get the output stream - val buffer = ByteArray(1024) // Create a buffer - var read: Int // Variable to store the read bytes - var downloaded: Long = 0 // Variable to store the downloaded bytes - while (inputStream.read(buffer).also { read = it } > 0) { // While there are bytes to read - outputStream.write(buffer, 0, read) // Write the bytes to the output stream - downloaded += read // Add the read bytes to the downloaded bytes - val percentage = downloaded * 100 / contentLength // Calculate the percentage - logger.info("Downloading ${dependency.group}:${dependency.artifact}:${dependency.version}... $percentage%") // Log the percentage - } - outputStream.close() // Close the output stream - inputStream.close() // Close the input stream - logger.info("Downloaded ${dependency.group}:${dependency.artifact}:${dependency.version} to ${destination.relativeTo(File(".")).path}") // Log the download - } - - /** - * Checks for updates and removes any old dependency from the - * libraries folder - */ - private fun updateModulesRepository() { - fun jsonDependToMap(json: JsonObject, map: MutableMap) { - json["dependencies"].asJsonArray.forEach { dependency -> - val dependencyObject = dependency.asJsonObject - map["${dependencyObject["group"].asString}:${dependencyObject["artifact"].asString}"] = dependencyObject["version"].asString - } - } - - val cachedDependencies = mutableMapOf() - val onlineDependencies = mutableMapOf() - if(modulesRepositoryFile.exists() && modulesRepositoryFile.readText().isNotBlank()) { - val cachedJson = JsonParser.parseString(modulesRepositoryFile.readText()).asJsonObject - jsonDependToMap(cachedJson, cachedDependencies) - } - - downloadModulesRepository() - - val downloadedJson = JsonParser.parseString(modulesRepositoryFile.readText()).asJsonObject - jsonDependToMap(downloadedJson, onlineDependencies) - - cachedDependencies.forEach { (key, version) -> - val newVersion = onlineDependencies[key] - if(newVersion != null && newVersion != version) { - logger.info("Found update for dependency $key from $version to $newVersion. Removing old file to download update...") - val (group, artifact) = key.split(":") - val file = File(librariesFolder, "$group-$artifact-$version.jar") - if(file.exists()) { - FileUtils.forceDelete(file) - } - } - } - } - - /** - * Downloads the modules repository file from the GitHub repository. - * If the download fails, it will create a new file with the default content (empty repositories and dependencies) - */ - private fun downloadModulesRepository() { - val destination = modulesRepositoryFile.file() - val content = try { - URL("https://raw.githubusercontent.com/TheProgramSrc/GlobalDatabase/master/SimpleCoreAPI/modules-repository.json").readBytes() - } catch (_: Exception) { - JsonObject().apply { - add("repositories", JsonArray().apply { - add("repositories", JsonArray().apply { - add("https://s01.oss.sonatype.org/content/groups/public/") - add("https://oss.sonatype.org/content/groups/public") - add("https://oss.sonatype.org/content/repositories/snapshots/") - add("https://oss.sonatype.org/content/repositories/releases/") - add("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") - add("https://repo.papermc.io/repository/maven-public/") - add("https://repo.codemc.io/repository/maven-public/") - add("https://jitpack.io/") - }) - }) - add("dependencies", JsonArray()) - }.toString().toByteArray() - } - destination.writeBytes(content) - } - -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/dependencydownloader/DependencyLoader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/dependencydownloader/DependencyLoader.kt deleted file mode 100644 index be88a767..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/dependencydownloader/DependencyLoader.kt +++ /dev/null @@ -1,31 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.dependencydownloader - -import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI -import xyz.theprogramsrc.simplecoreapi.global.dependencydownloader.interfaces.DependencyLoader - -/** - * Representation of the dependency loader - * @param dependencyClassLoader The [DependencyLoader] to use to load the dependencies - */ -class DependencyLoader(val dependencyClassLoader: DependencyLoader) { - - private val librariesFolder = SimpleCoreAPI.dataFolder("libraries/") - - init { - // Load all the dependencies from the libraries folder - val dependencies = resolveDependencies() - - // Now using the dependencyClassLoader, load all the dependencies - dependencies.forEach { - SimpleCoreAPI.logger.info("Loading dependency ${it.nameWithoutExtension.replace('-', ':')}...") - dependencyClassLoader.loadIntoClasspath(it) - } - } - - /** - * Method in charge of resolving the dependencies - * @return The list of dependencies available to load - */ - private fun resolveDependencies() = (librariesFolder.listFiles() ?: arrayOf()) - .filter { it.extension == "jar" } -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/dependencydownloader/interfaces/DependencyLoader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/dependencydownloader/interfaces/DependencyLoader.kt deleted file mode 100644 index bd09fbaf..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/dependencydownloader/interfaces/DependencyLoader.kt +++ /dev/null @@ -1,10 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.dependencydownloader.interfaces - -import java.io.File - -interface DependencyLoader { - - fun loadIntoClasspath(file: File) - - fun findClassByName(name: String): Class<*> -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/Dependency.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/Dependency.kt deleted file mode 100644 index ade50e38..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/Dependency.kt +++ /dev/null @@ -1,72 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.models - -import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI -import xyz.theprogramsrc.simplecoreapi.global.utils.extensions.folder -import java.io.File -import java.net.URL -import java.security.MessageDigest - -/** - * Representation of Dependency - * @param group The group id of the dependency - * @param artifact The artifact id of the dependency - * @param version The version of the dependency - * @param md5Hash The md5 hash of the dependency, if null the downloader will not check the md5 hash (Which is not recommended) - */ -data class Dependency(val group: String, val artifact: String, val version: String, val md5Hash: String? = null) { - - companion object { - private val librariesFolder = SimpleCoreAPI.dataFolder("libraries/") - val dependencies = mutableListOf() - - /** - * Adds a dependency to the list of dependencies - * @param dependency The [Dependency] to load - */ - fun addDependency(dependency: Dependency) { - val found = dependencies.find { it.group == dependency.group && it.artifact == dependency.artifact } - if (found == null) { - dependencies.add(dependency) - return - } - - if (found.version != dependency.version) { - SimpleCoreAPI.logger.warn("Dependency '${found.group}:${found.artifact}' already exists with version '${found.version}'!") - } - } - } - - /** - * Retrieves the [Dependency] file inside the local storage - * @return The [Dependency] file - */ - fun asFile(): File = File(librariesFolder, "$group-$artifact-$version.jar") - - /** - * Downloads a [Dependency] into the local storage if is not already downloaded - * - * @return The [Dependency] file if is successfully downloaded, null otherwise - */ - fun download(): File? { - val file = asFile() - - if(!file.exists()){ - val repo = Repository.repositories.firstOrNull { it.findArtifact(this) != null } ?: return null - val artifactUrl = repo.findArtifact(this) ?: return null - val downloadBytes = URL(artifactUrl).readBytes() - if(md5Hash != null){ - val digest = MessageDigest.getInstance("MD5") - digest.reset() - val downloadMd5 = digest.digest(downloadBytes).joinToString("") { "%02x".format(it) } - if(downloadMd5 != md5Hash){ - SimpleCoreAPI.logger.error("MD5 mismatch for $group:$artifact! Expected: '${md5Hash}', Got: '$downloadMd5'") - return null - } - } - - file.writeBytes(downloadBytes) - } - - return file - } -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/Repository.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/Repository.kt deleted file mode 100644 index 2f1f71d3..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/Repository.kt +++ /dev/null @@ -1,127 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.models - -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import org.json.XML -import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI -import java.net.URL - -/** - * Representation of a Repository - * @param url The url of the repository. Example: https://repo1.maven.org/maven2/ - */ -data class Repository(val url: String) { - - companion object { - val repositories = mutableListOf() - - /** - * Adds a repository to the list of repositories - * @param repository The repository to add - */ - fun addRepository(repository: Repository) { - val isValidURL = try { - URL(repository.url) - true - }catch (_: Exception){ - false - } - - if(!isValidURL) { - SimpleCoreAPI.logger.error("Repository ${repository.url} must have a valid url!") - return - } - - if(repositories.any { it.url == repository.url }) { - SimpleCoreAPI.logger.warn("Repository ${repository.url} already exists!") - } - - repositories.add(repository) - } - - fun findArtifact(dependency: Dependency): String? { - for(repository in repositories){ - val result = repository.findArtifact(dependency) - if(result != null) return result - } - return null - } - } - - private val mavenUrlFormat = "%s/%s/%s/%s-%s.jar" - - /** - * Finds the artifact url to download - * @return The artifact url to download - */ - fun findArtifact(dependency: Dependency): String? { - val cachedResolved = SimpleCoreAPI.dataFolder("dependency-resolutions.cache.json") - if(cachedResolved.exists() && cachedResolved.readText().isNotBlank()) { - val json = JsonParser.parseString(cachedResolved.readText()).asJsonObject - if(json.has(dependency.toString())) { - return json.get(dependency.toString()).asString - } - } - - val resolution = try { - val parsedVersion = if(dependency.version.endsWith("-SNAPSHOT")){ - parseSnapshotVersion(dependency) - } else if(dependency.version == "LATEST") { - try { - val result = JsonParser.parseString(XML.toJSONObject(URL("$url/${rewriteEscaping(dependency.group).replace('.','/')}/${dependency.artifact}/maven-metadata.xml").readText()).toString()) - .asJsonObject - .getAsJsonObject("metadata") - .getAsJsonObject("versioning") - .get("latest") - .asString - if(result.endsWith("-SNAPSHOT")) { - parseSnapshotVersion(Dependency(dependency.group, dependency.artifact, result, dependency.md5Hash)) - }else { - result - } - }catch (e: Exception){ - null - } - }else{ - dependency.version - } ?: return null - - val result = url + String.format(mavenUrlFormat, - rewriteEscaping(dependency.group).replace('.', '/'), - rewriteEscaping(dependency.artifact), - dependency.version, - rewriteEscaping(dependency.artifact), - parsedVersion - ) - URL(result) - result - }catch (e: Exception){ - null - } - - if(resolution != null) { - val json = if(cachedResolved.exists() && cachedResolved.readText().isNotBlank()) { - JsonParser.parseString(cachedResolved.readText()).asJsonObject - }else{ - JsonObject() - } - json.addProperty(dependency.toString(), resolution) - cachedResolved.writeText(json.toString()) - } - - return resolution - } - - private fun rewriteEscaping(data: String) = data.replace("{}", ".") - - private fun parseSnapshotVersion(dependency: Dependency): String? = try { - val json = JsonParser.parseString(XML.toJSONObject(URL("$url/${rewriteEscaping(dependency.group).replace('.','/')}/${dependency.artifact}/${dependency.version}/maven-metadata.xml").readText()).toString()) - .asJsonObject - .getAsJsonObject("metadata") - .getAsJsonObject("versioning") - .getAsJsonObject("snapshot") - dependency.version.replace("-SNAPSHOT", "-${json.get("timestamp").asString}-${json.get("buildNumber").asString}") - }catch (e: Exception){ - null - } -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/module/Module.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/Module.kt similarity index 85% rename from src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/module/Module.kt rename to src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/Module.kt index 61469b29..86a3c003 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/module/Module.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/Module.kt @@ -1,7 +1,7 @@ -package xyz.theprogramsrc.simplecoreapi.global.models.module +package xyz.theprogramsrc.simplecoreapi.global.module -import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI -import java.util.UUID +import xyz.theprogramsrc.simplecoreapi.global.utils.measureLoad +import java.util.* /** * This interface represents a module. @@ -58,12 +58,12 @@ inline fun requireModule(): T { val moduleInstance = T::class.java.getConstructor().newInstance() Module.loadedModules[id] = moduleInstance - SimpleCoreAPI.instance.measureLoad("Module ${moduleInstance.description.name} enabled in {time}") { + measureLoad("Module ${moduleInstance.description.name} enabled in {time}") { moduleInstance.onEnable() } Runtime.getRuntime().addShutdownHook(Thread { - SimpleCoreAPI.instance.measureLoad("Module ${moduleInstance.description.name} disabled in {time}") { + measureLoad("Module ${moduleInstance.description.name} disabled in {time}") { moduleInstance.onDisable() } }) diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/module/ModuleDescription.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleDescription.kt similarity index 64% rename from src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/module/ModuleDescription.kt rename to src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleDescription.kt index 114307cf..2092e799 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/module/ModuleDescription.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleDescription.kt @@ -1,4 +1,4 @@ -package xyz.theprogramsrc.simplecoreapi.global.models.module +package xyz.theprogramsrc.simplecoreapi.global.module data class ModuleDescription( val name: String, diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/FilesModule.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/FilesModule.kt new file mode 100644 index 00000000..e8a4c0dc --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/FilesModule.kt @@ -0,0 +1,23 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule + +import xyz.theprogramsrc.simplecoreapi.global.module.Module +import xyz.theprogramsrc.simplecoreapi.global.module.ModuleDescription + +class FilesModule: Module { + + override val description: ModuleDescription = + ModuleDescription( + name = "FilesModule", + version = "0.4.0", + authors = listOf("Im-Fran") + ) + + override fun onEnable() { + + } + + override fun onDisable() { + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/config/JsonConfig.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/config/JsonConfig.kt new file mode 100644 index 00000000..f0caeaf9 --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/config/JsonConfig.kt @@ -0,0 +1,444 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule.config + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule.extensions.file +import java.io.File + +/** + * Representation of a json config + */ +class JsonConfig { + + var file: File? = null + var jsonObject = JsonObject() + + /** + * Loads the config from the given object + * (but not saved to a file) + * + * @param jsonObject the json object + */ + constructor(jsonObject: JsonObject){ + this.jsonObject = jsonObject + } + + /** + * Loads the config from the given file + * + * @param file the file + */ + constructor(file: File){ + this.file = file + load() + } + + /** + * Loads the config from the file, and overrides the cache + */ + fun load() = run { + if(file != null){ + val file = file!!.file() + val text = file.readText() + if(file.length() > 1000000000L) { + throw Exception("File is too big to be loaded!") + } + jsonObject = if(text.isBlank()) JsonObject() else JsonParser.parseString(text).asJsonObject + } + this + } + + /** + * Saves the config to the file + */ + fun save() = run { + if(file != null){ + file?.file()?.writeText(jsonObject.toString()) + } + this + } + + /** + * Destroys the config + */ + fun destroy() = run { + if(file?.exists() == true) file?.delete() + this + } + + /** + * Checks if the config has the specified key + * + * @param key The key to check + */ + fun has(key: String) = jsonObject.has(key) + + /** + * Sets a [JsonConfig] to the given key, and + * if the member already exists, it will be replaced + * + * @param key The key to set + * @param value The [JsonConfig] + */ + fun set(key: String, value: JsonConfig) = run { + jsonObject.add(key, value.jsonObject) + this + } + + /** + * Adds a [JsonConfig] to the given key, and + * if the member already exists, it won't be replaced + * + * @param key The key to add + * @param value The [JsonConfig] + */ + fun add(key: String, value: JsonConfig) = run { + if(!has(key)) { + set(key, value) + } + this + } + + /** + * Sets a [JsonElement] to the given key, and + * if the member already exists, it will be replaced. + * + * @param key The key of the element + * @param element The [JsonElement] + */ + fun set(key: String, element: JsonElement) = run { + jsonObject.add(key, element) + save() + this + } + + /** + * Adds an element to the given key, and + * if the member already exists, it won't be replaced. + * + * @param key The key of the element + * @param element The element + */ + fun add(key: String, element: JsonElement) = run { + if (!has(key)) { + set(key, element) + } + this + } + + /** + * Gets an element from the given key + * + * @param key The key of the element + * @return The element + */ + fun get(key: String) = jsonObject.get(key) + + /** + * Sets a [String] to the given key, and + * if the member already exists, it will be replaced. + * + * @param key The key of the [String] + * @param value The [String] value + */ + fun set(key: String, value: String) = run { + jsonObject.addProperty(key, value) + save() + this + } + + /** + * Adds a [String] to the given key, and + * if the member already exists, it won't be replaced. + * + * @param key The key of the [String] + * @param value The [String] value + */ + fun add(key: String, value: String) = run { + if (!has(key)) { + set(key, value) + } + this + } + + /** + * Gets a [String] from the given key + * + * @param key The key of the [String] + * @return The [String] value + */ + fun getString(key: String) = jsonObject.get(key).asString + + /** + * Sets an [Int] to the given key, and + * if the member already exists, it will be replaced. + * + * @param key The key of the [Int] + * @param value The [Int] value + */ + fun set(key: String, value: Int) = run { + jsonObject.addProperty(key, value) + save() + this + } + + /** + * Adds an [Int] to the given key, and + * if the member already exists, it won't be replaced. + * + * @param key The key of the [Int] + * @param value The [Int] value + */ + fun add(key: String, value: Int) = run { + if (!has(key)) { + set(key, value) + } + this + } + + /** + * Gets an [Int] from the given key + * + * @param key The key of the [Int] + * @return The [Int] value + */ + fun getInt(key: String) = jsonObject.get(key).asInt + + /** + * Increases an [Int] by the given value + * or by 1 if no value is specified. + * + * If the member doesn't exist, it will be set to 0. + * + * @param key The key of the [Int] + * @param value The value to increase by. Defaults to 1 + * @return The new value + */ + fun increase(key: String, value: Int = 1): Int { + if (!has(key)) { + set(key, 0) + }else { + set(key, getInt(key) + value) + } + return getInt(key) + } + + /** + * Decreases an [Int] by the given value + * or by 1 if no value is specified. + * + * If the member doesn't exist, it will be set to 0. + * + * @param key The key of the [Int] + * @param value The value to decrease by. Defaults to 1 + * @return The new value + */ + fun decrease(key: String, value: Int = 1): Int { + if (!has(key)) { + set(key, 0) + } else { + set(key, getInt(key) - value) + } + return getInt(key) + } + + /** + * Sets a [Number] to the given key, and + * if the member already exists, it will be replaced. + * + * @param key The key of the [Number] + * @param value The [Number] value + */ + fun set(key: String, value: Number) = run { + jsonObject.addProperty(key, value) + save() + this + } + + /** + * Adds a [Number] to the given key, and + * if the member already exists, it won't be replaced. + * + * @param key The key of the [Number] + * @param value The [Number] value + */ + fun add(key: String, value: Number) = run { + if (!has(key)) { + set(key, value) + } + this + } + + /** + * Gets a [Number] from the given key + * + * @param key The key of the [Number] + * @return The [Number] value + */ + fun getNumber(key: String) = jsonObject.get(key).asNumber + + /** + * Sets a [Boolean] to the given key, and + * if the member already exists, it will be replaced. + * + * @param key The key of the [Boolean] + * @param value The [Boolean] value + */ + fun set(key: String, value: Boolean) = run { + jsonObject.addProperty(key, value) + save() + this + } + + /** + * Adds a [Boolean] to the given key, and + * if the member already exists, it won't be replaced. + * + * @param key The key of the [Boolean] + * @param value The [Boolean] value + */ + fun add(key: String, value: Boolean) = run { + if (!has(key)) { + set(key, value) + } + this + } + + /** + * Gets a [Boolean] from the given key + * + * @param key The key of the [Boolean] + * @return The [Boolean] value + */ + fun getBoolean(key: String) = jsonObject.get(key).asBoolean + + /** + * Toggles a [Boolean] to the given key, and + * if the member doesn't exist, it will be set to true. + * + * @param key The key of the [Boolean] + * @return The new [Boolean] value + */ + fun toggle(key: String): Boolean { + if (!has(key)) { + set(key, true) + return true + } + val value = getBoolean(key) + set(key, !value) + return !value + } + + + /** + * Sets a [JsonArray] to the given key, and + * if the member already exists, it will be replaced. + * + * @param key The key of the [JsonArray] + * @param value The [JsonArray] value + */ + fun set(key: String, value: JsonArray) = run { + jsonObject.add(key, value) + save() + this + } + + /** + * Adds a [JsonArray] to the given key, and + * if the member already exists, it won't be replaced. + * + * @param key The key of the [JsonArray] + * @param value The [JsonArray] value + */ + fun add(key: String, value: JsonArray) = run { + if (!has(key)) { + set(key, value) + } + this + } + + /** + * Gets a [JsonArray] from the given key + * + * @param key The key of the [JsonArray] + * @return The [JsonArray] value + */ + fun getJsonArray(key: String) = jsonObject.get(key).asJsonArray + + /** + * Gets a [JsonArray] from the given key + * or creates a new one if it doesn't exist + * + * @param key The key of the [JsonArray] + * @return The [JsonArray] value + */ + fun getOrCreateJsonArray(key: String): JsonArray { + if (!has(key)) { + set(key, JsonArray()) + } + return getJsonArray(key) + } + + /** + * Sets a [JsonObject] to the given key, and + * if the member already exists, it will be replaced. + * + * @param key The key of the [JsonObject] + * @param value The [JsonObject] value + */ + fun set(key: String, value: JsonObject) = run { + jsonObject.add(key, value) + save() + this + } + + /** + * Adds a [JsonObject] to the given key, and + * if the member already exists, it won't be replaced. + * + * @param key The key of the [JsonObject] + * @param value The [JsonObject] value + */ + fun add(key: String, value: JsonObject) = run { + if (!has(key)) { + set(key, value) + } + this + } + + /** + * Gets a [JsonConfig] from the given key + * + * @param key The key of the [JsonConfig] + * @return The [JsonConfig] value + */ + fun getJsonObject(key: String): JsonConfig = JsonConfig(jsonObject.get(key).asJsonObject) + + /** + * Gets a [JsonConfig] from the given key + * or creates a new one if it doesn't exist + * + * @param key The key of the [JsonConfig] + * @return The [JsonConfig] value + */ + fun getOrCreateJsonObject(key: String): JsonConfig { + if (!has(key)) { + set(key, JsonObject()) + } + return getJsonObject(key) + } + + /** + * Removes the given key from the [JsonObject] + * + * @param key The key of the member + * @return The [JsonElement] that is being removed + */ + fun remove(key: String): JsonElement? { + val result = jsonObject.remove(key) + save() + return result + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/config/PropertiesConfig.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/config/PropertiesConfig.kt new file mode 100644 index 00000000..c2a6f25c --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/config/PropertiesConfig.kt @@ -0,0 +1,111 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule.config + +import xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule.extensions.file +import java.io.File +import java.util.* + +/** + * Representation of a [java.util.Properties] based config file + * @param file the file to load the config from (usually a file ending with .properties) + */ +class PropertiesConfig(val file: File){ + + val props = Properties() + + init { + load() + } + + /** + * Loads the config from the file + * @return This [PropertiesConfig] + */ + fun load() = run { + props.load(file.file().inputStream()) + this + } + + /** + * Saves the config to the file + * @return This [PropertiesConfig] + */ + fun save(vararg comments: String) = run { + props.store(file.file().outputStream(), comments.joinToString("\n")) + this + } + + /** + * Checks if the config contains the given key + * + * @param key the key to check + */ + fun has(key: String) = props.containsKey(key) + + /** + * Gets the value of the given key + * @param key the key to get the value of + * @return the value of the key if it exists, null otherwise + */ + fun get(key: String): String? = if(has(key)) props.getProperty(key) else null + + /** + * Gets the value of the given key, or + * sets the given default value if the key doesn't exist + * + * @param key the key to get the value of + * @param default the default value to return if the key doesn't exist + * @return the value of the key if it exists, the default value otherwise + */ + fun getOrDefault(key: String, default: String): String { + add(key, default) + return get(key) ?: default + } + + /** + * Sets the value of the given key + * @param key the key to set the value of + * @param value the value to set + * @return This [PropertiesConfig] + */ + fun set(key: String, value: String) = run { + props.setProperty(key, value) + save() + this + } + + /** + * Adds the given key-value pair to the config + * and if the key already exists, it won't be overwritten + * + * @param key the key to add + * @param value the value to add + * @return This [PropertiesConfig] + */ + fun add(key: String, value: String) = run { + if (!has(key)) { + props.setProperty(key, value) + save() + } + this + } + + /** + * Removes the given key + * @param key the key to remove + * @return This [PropertiesConfig] + */ + fun remove(key: String) = run { + props.remove(key) + save() + this + } + + /** + * Destroys the config + * @return This [PropertiesConfig] + */ + fun destroy() = run { + if(file.exists()) file.delete() + this + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/config/YmlConfig.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/config/YmlConfig.kt new file mode 100644 index 00000000..399fcbdd --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/config/YmlConfig.kt @@ -0,0 +1,373 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule.config + +import org.simpleyaml.configuration.ConfigurationSection +import org.simpleyaml.configuration.file.YamlConfiguration +import xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule.extensions.file +import java.io.File + +/** + * Representation of a YAML config file + * + * @param file The file to load the config from (usually a file ending in .yml) + */ +class YmlConfig(val file: File){ + + /** + * The raw [YamlConfiguration] file + */ + var config = YamlConfiguration.loadConfiguration(file.file()) + + init { + load() + } + + /** + * Loads the config file and clears the current cache + * @return This [YmlConfig] + */ + fun load() = run { + config = YamlConfiguration.loadConfiguration(file.file()) + this + } + + /** + * Saves the config file + * @return This [YmlConfig] + */ + fun save() = run { + config.save(file.file()) + this + } + + /** + * Destroys the config file + * @return This [YmlConfig] + */ + fun destroy() = run { + if (file.exists()) file.delete() + this + } + + /** + * Checks if the given path exists + * + * @param path The path to check + */ + fun has(path: String): Boolean = config.contains(path) + + /** + * Sets the value of the given path + * + * @param path The path to set the value of + * @param value The value to set + * @return This [YmlConfig] + */ + fun set(path: String, value: Any) = run { + config.set(path, value) + save() + this + } + + /** + * Adds the value of the given path without + * replacing it if it already exists + * + * @param path The path to set the value of + * @param value The value to set + * @return This [YmlConfig] + */ + fun add(path: String, value: Any) = run { + if(!has(path)){ + set(path, value) + } + this + } + + /** + * Removes the value of the given path + * @param path The path to remove the value of + * @return This [YmlConfig] + */ + fun remove(path: String) = run { + config.remove(path) + save() + this + } + + /** + * Gets the value of the given path + * + * @param path The path to get the value of + * @return The value of the given path as [Any] + */ + fun get(path: String): Any = config.get(path) + + /** + * Gets the value of the given path + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [Any] + */ + fun getOrSet(path: String, default: Any): Any = if(has(path)) get(path) else set(path, default).get(path) + + /** + * Gets the value of the given path as a [String] + * + * @param path The path to get the value of + * @return The value of the given path as [String] + */ + fun getString(path: String): String = config.getString(path) + + /** + * Gets the value of the given path as a [String] + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [String] + */ + fun getStringOrSet(path: String, default: String): String = if(has(path)) getString(path) else set(path, default).getString(path) + + /** + * Gets the value of the given path as a [Int] + * + * @param path The path to get the value of + * @return The value of the given path as [Int] + */ + fun getInt(path: String): Int = config.getInt(path) + + /** + * Gets the value of the given path as a [Int] + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [Int] + */ + fun getIntOrSet(path: String, default: Int): Int = if(has(path)) getInt(path) else set(path, default).getInt(path) + + /** + * Gets the value of the given path as a [Double] + * + * @param path The path to get the value of + * @return The value of the given path as [Double] + */ + fun getDouble(path: String): Double = config.getDouble(path) + + /** + * Gets the value of the given path as a [Double] + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [Double] + */ + fun getDoubleOrSet(path: String, default: Double): Double = if(has(path)) getDouble(path) else set(path, default).getDouble(path) + + /** + * Gets the value of the given path as a [Float] + * + * @param path The path to get the value of + * @return The value of the given path as [Float] + */ + fun getFloat(path: String): Float = config.get(path) as Float + + /** + * Gets the value of the given path as a [Float] + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [Float] + */ + fun getFloatOrSet(path: String, default: Float): Float = if(has(path)) getFloat(path) else set(path, default).getFloat(path) + + /** + * Gets the value of the given path as a [Long] + * + * @param path The path to get the value of + * @return The value of the given path as [Long] + */ + fun getLong(path: String): Long = config.getLong(path) + + /** + * Gets the value of the given path as a [Long] + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [Long] + */ + fun getLongOrSet(path: String, default: Long): Long = if(has(path)) getLong(path) else set(path, default).getLong(path) + + /** + * Gets the value of the given path as a [Boolean] + * + * @param path The path to get the value of + * @return The value of the given path as [Boolean] + */ + fun getBoolean(path: String): Boolean = config.getBoolean(path) + + /** + * Gets the value of the given path as a [Boolean] + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [Boolean] + */ + fun getBooleanOrSet(path: String, default: Boolean): Boolean = if(has(path)) getBoolean(path) else set(path, default).getBoolean(path) + + /** + * Gets a [List] of [String] from the given path + * + * @param path The path to get the value of + * @return The value of the given path as [List] of [String] + */ + fun getStringList(path: String): List = config.getStringList(path) + + /** + * Gets a [List] of [String] from the given path + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [List] of [String] + */ + fun getStringListOrSet(path: String, default: List): List = if(has(path)) getStringList(path) else set(path, default).getStringList(path) + + /** + * Gets a [List] of [Int] from the given path + * + * @param path The path to get the value of + * @return The value of the given path as [List] of [Int] + */ + fun getIntList(path: String): List = config.getIntegerList(path) + + /** + * Gets a [List] of [Int] from the given path + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [List] of [Int] + */ + fun getIntListOrSet(path: String, default: List): List = if(has(path)) getIntList(path) else set(path, default).getIntList(path) + + /** + * Gets a [List] of [Double] from the given path + * + * @param path The path to get the value of + * @return The value of the given path as [List] of [Double] + */ + fun getDoubleList(path: String): List = config.getDoubleList(path) + + /** + * Gets a [List] of [Double] from the given path + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [List] of [Double] + */ + fun getDoubleListOrSet(path: String, default: List): List = if(has(path)) getDoubleList(path) else set(path, default).getDoubleList(path) + + /** + * Gets a [List] of [Float] from the given path + * + * @param path The path to get the value of + * @return The value of the given path as [List] of [Float] + */ + fun getFloatList(path: String): List = config.getFloatList(path) + + /** + * Gets a [List] of [Float] from the given path + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [List] of [Float] + */ + fun getFloatListOrSet(path: String, default: List): List = if(has(path)) getFloatList(path) else set(path, default).getFloatList(path) + + /** + * Gets a [List] of [Long] from the given path + * + * @param path The path to get the value of + * @return The value of the given path as [List] of [Long] + */ + fun getLongList(path: String): List = config.getLongList(path) + + /** + * Gets a [List] of [Long] from the given path + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [List] of [Long] + */ + fun getLongListOrSet(path: String, default: List): List = if(has(path)) getLongList(path) else set(path, default).getLongList(path) + + /** + * Gets a [List] of [Boolean] from the given path + * + * @param path The path to get the value of + * @return The value of the given path as [List] of [Boolean] + */ + fun getBooleanList(path: String): List = config.getBooleanList(path) + + /** + * Gets a [List] of [Boolean] from the given path + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [List] of [Boolean] + */ + fun getBooleanListOrSet(path: String, default: List): List = if(has(path)) getBooleanList(path) else set(path, default).getBooleanList(path) + + /** + * Gets a [List] from the given path + * + * @param path The path to get the value of + * @return The value of the given path as [List] + */ + fun getList(path: String): List<*> = config.getList(path) + + /** + * Gets a [List] from the given path + * or sets the default value if it doesn't exist + * + * @param path The path to get the value of + * @param default The default value to set if the path doesn't exist + * @return The value of the given path as [List] + */ + fun getListOrSet(path: String, default: List<*>): List<*> = if(has(path)) getList(path) else set(path, default).getList(path) + + /** + * Gets the entry set of the [YmlConfig] + * + * @param deep If true, the keys will contain all the keys within any child node (and their children, recursively). Otherwise, this will contain only the keys of any direct children, and not their own children. + * @return The entry set of the [YmlConfig] + */ + fun entries(deep: Boolean = false): Set> = config.getKeys(deep).associateWith { get(it) }.entries + + /** + * Gets the keys of the [YmlConfig] + * + * @param deep If true, the keys will contain all the keys within any child node (and their children, recursively). Otherwise, this will contain only the keys of any direct children, and not their own children. + * @return The keys of the [YmlConfig] + */ + fun keys(deep: Boolean = false): Set = config.getKeys(deep) + + /** + * Gets a configuration section of the given path + * + * @param path The path to get the section of + * @return The configuration section of the given path + */ + fun getSection(path: String): ConfigurationSection = config.getConfigurationSection(path) + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/extensions/FileExtensions.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/extensions/FileExtensions.kt similarity index 95% rename from src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/extensions/FileExtensions.kt rename to src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/extensions/FileExtensions.kt index 06cfd170..084edcaa 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/extensions/FileExtensions.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/filesmodule/extensions/FileExtensions.kt @@ -1,4 +1,4 @@ -package xyz.theprogramsrc.simplecoreapi.global.utils.extensions +package xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule.extensions import java.io.File diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/loggingmodule/LoggingModule.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/loggingmodule/LoggingModule.kt new file mode 100644 index 00000000..1d650ed2 --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/loggingmodule/LoggingModule.kt @@ -0,0 +1,20 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules.loggingmodule + +import xyz.theprogramsrc.simplecoreapi.global.module.Module +import xyz.theprogramsrc.simplecoreapi.global.module.ModuleDescription + +class LoggingModule: Module { + + override val description: ModuleDescription = ModuleDescription( + name = "LoggingModule", + version = "0.4.1", + authors = listOf("Im-Fran") + ) + + override fun onEnable() { + } + + override fun onDisable() { + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/loggingmodule/filter/FilterResult.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/loggingmodule/filter/FilterResult.kt new file mode 100644 index 00000000..4ed9a843 --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/loggingmodule/filter/FilterResult.kt @@ -0,0 +1,7 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules.loggingmodule.filter + +enum class FilterResult { + DENY, + NEUTRAL, + NONE, +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/loggingmodule/filter/LogFilter.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/loggingmodule/filter/LogFilter.kt new file mode 100644 index 00000000..9de520de --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/loggingmodule/filter/LogFilter.kt @@ -0,0 +1,43 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules.loggingmodule.filter + +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Marker +import org.apache.logging.log4j.core.Filter +import org.apache.logging.log4j.core.LogEvent +import org.apache.logging.log4j.core.Logger +import org.apache.logging.log4j.core.filter.AbstractFilter +import org.apache.logging.log4j.message.Message + +/** + * Representation of a LogFilter. This can be used to filter messages from the console. + * + * @param filter The function to use to filter the messages and return the result of whether to filter or not. + */ +class LogFilter(private val filter: (String) -> FilterResult): AbstractFilter() { + + companion object { + private val logger = LogManager.getRootLogger() as Logger + } + + init { + logger.addFilter(this) + } + + private fun process(message: String?): Filter.Result { + if (message == null) { + return Filter.Result.NEUTRAL + } + + return Filter.Result.valueOf(filter(message).name) + } + + override fun filter(event: LogEvent?): Filter.Result = process(event?.message?.formattedMessage) + + override fun filter(logger: Logger?, level: Level?, marker: Marker?, msg: Message, t: Throwable?): Filter.Result = process(msg.formattedMessage) + + override fun filter(logger: Logger?, level: Level?, marker: Marker?, msg: Any, t: Throwable?): Filter.Result = process(msg.toString()) + + override fun filter(logger: Logger?, level: Level?, marker: Marker?, msg: String?, vararg params: Any?): Filter.Result = process(msg) + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/networkingmodule/NetworkingModule.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/networkingmodule/NetworkingModule.kt new file mode 100644 index 00000000..d4c93b7b --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/networkingmodule/NetworkingModule.kt @@ -0,0 +1,26 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules.networkingmodule + +import xyz.theprogramsrc.simplecoreapi.global.module.Module +import xyz.theprogramsrc.simplecoreapi.global.module.ModuleDescription +import xyz.theprogramsrc.simplecoreapi.global.modules.networkingmodule.models.Request + +class NetworkingModule: Module { + override val description: ModuleDescription = ModuleDescription( + name = "NetworkingModule", + version = "0.1.0", + authors = listOf("Im-Fran") + ) + + override fun onEnable() { + } + + override fun onDisable() { + } + + companion object { + + fun request(request: Request) { + + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/networkingmodule/models/Request.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/networkingmodule/models/Request.kt new file mode 100644 index 00000000..afac1c40 --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/networkingmodule/models/Request.kt @@ -0,0 +1,276 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules.networkingmodule.models + +import java.net.HttpURLConnection +import java.net.URL + +/** + * Represents a request method + * @see RequestBuilder.method + */ +enum class RequestMethod { + GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS +} + +/** + * Represents the protocol to use (http, https) + * @see RequestBuilder.protocol + */ +enum class RequestProtocol { + HTTP, HTTPS +} + +/** + * Represents a request builder + * Example usage: + * ``` + * RequestBuilder() + * .protocol(RequestProtocol.HTTPS) + * .host("google.com") + * .path("/search") + * .parameter("q", "Hello World") + */ +class RequestBuilder { + + /** + * The protocol to use (http, https) + * Default: https + */ + var protocol: RequestProtocol = RequestProtocol.HTTPS + + /** + * The host of the server + */ + var host: String = "" + + /** + * The port of the server + * Default: null (No port) + */ + var port: Int? = null + + /** + * The path of the request + * Default: null (No path) + */ + var path: String? = null + + /** + * The parameters of the request + * Default: Empty map + */ + var parameters: MutableMap = mutableMapOf() + + /** + * The headers of the request + * Default: User-Agent, Accept + */ + var headers: MutableMap = mutableMapOf( + "User-Agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36", + "Accept" to "*/*", + ) + + /** + * The body of the request + * Default: null (No body is sent) + */ + var body: ByteArray? = null + + /** + * The timeout of the request + * Default: -1 (No timeout) + */ + var timeout: Int = -1 + + /** + * The request method + * Default: GET + */ + var method: RequestMethod = RequestMethod.GET + + /** + * Follow redirects + * Default: true + */ + var followRedirects: Boolean = true + + /** + * The maximum amount of redirects to follow + * Default: -1 (No limit) + */ + var maxRedirects: Int = -1 + + /** + * Cache the response + * Default: false + */ + var cacheResponse: Boolean = false + + /** + * Constructs a new request builder + */ + constructor(url: String) { + if(url.contains("://")) { + protocol = RequestProtocol.valueOf(url.split("://")[0].uppercase()) + } + + val data = if(url.contains("://")) { + url.split("://")[1] + } else { + url + } + + val hostElements = data.split("/")[0] + if(hostElements.contains(":")) { + host = hostElements.split(":")[0] + port = hostElements.split(":")[1].toInt() + } else { + host = hostElements + } + + val extra + path = data.split("/").drop(1).joinToString("/") + } + + /** + * Sets the protocol of the request + * @param protocol The protocol to use (http, https) + * @return The request builder + * @see RequestProtocol + */ + fun protocol(protocol: RequestProtocol): RequestBuilder = apply { + this.protocol = protocol + } + + /** + * Sets the host of the request + * @param host The host of the server + * @return The request builder + */ + fun host(host: String): RequestBuilder = apply { + this.host = host + } + + /** + * Sets the port of the request + * @param port The port of the server + * @return The request builder + */ + fun port(port: Int): RequestBuilder = apply { + this.port = port + } + + /** + * Sets the path of the request + * @param path The path of the request + * @return The request builder + */ + fun path(path: String): RequestBuilder = apply { + this.path = path + } + + /** + * Sets a parameter to the request, it also overrides the value if the key already exists + * @param key The key of the parameter + * @param value The value of the parameter + * @return The request builder + */ + fun parameter(key: String, value: String): RequestBuilder = apply { + this.parameters[key] = value + } + + /** + * Adds a parameter to the request, it doesn't override the value if the key already exists + * @param key The key of the parameter + * @param value The value of the parameter + * @return The request builder + */ + fun addParameter(key: String, value: String): RequestBuilder = apply { + if (!this.parameters.containsKey(key)) this.parameters[key] = value + } + + /** + * Sets a header to the request, it also overrides the value if the key already exists + * @param key The key of the header + * @param value The value of the header + * @return The request builder + */ + fun header(key: String, value: String): RequestBuilder = apply { + this.headers[key] = value + } + + /** + * Adds a header to the request, it doesn't override the value if the key already exists + * @param key The key of the header + * @param value The value of the header + * @return The request builder + */ + fun addHeader(key: String, value: String): RequestBuilder = apply { + if (!this.headers.containsKey(key)) this.headers[key] = value + } + + /** + * Sets the body of the request + * @param body The body of the request + * @return The request builder + */ + fun body(body: ByteArray): RequestBuilder = apply { + this.body = body + } + + /** + * Sets the timeout of the request + * @param timeout The timeout of the request + * @return The request builder + */ + fun timeout(timeout: Int): RequestBuilder = apply { + this.timeout = timeout + } + + /** + * Sets the request method + * @param method The request method + * @return The request builder + */ + fun method(method: RequestMethod): RequestBuilder = apply { + this.method = method + } + + /** + * Sets the follow redirects option + * @param followRedirects The follow redirects option + * @return The request builder + */ + fun followRedirects(followRedirects: Boolean): RequestBuilder = apply { + this.followRedirects = followRedirects + } + + /** + * Sets the maximum amount of redirects to follow + * @param maxRedirects The maximum amount of redirects to follow + * @return The request builder + */ + fun maxRedirects(maxRedirects: Int): RequestBuilder = apply { + this.maxRedirects = maxRedirects + } + + /** + * Sets the cache response option + * @param cacheResponse The cache response option + * @return The request builder + */ + fun cacheResponse(cacheResponse: Boolean): RequestBuilder = apply { + this.cacheResponse = cacheResponse + } + + /** + * Sends the request + * @return The response + */ + fun send() { + val urlString = "${this.protocol.name.lowercase()}://${this.host}${if(this.port != null) ":${this.port}" else ""}${if(this.path != null) "/${this.path}" else ""}${if(this.parameters.isNotEmpty()) "?${this.parameters.map { "${it.key}=${it.value}" }.joinToString("&")}" else ""}" + val url = URL(urlString) + val connection = url.openConnection() as HttpURLConnection + this.headers.forEach { (key, value) -> connection.setRequestProperty(key, value) } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/tasksmodule/models/RecurringTask.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/tasksmodule/models/RecurringTask.kt new file mode 100644 index 00000000..d6f3a46b --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/tasksmodule/models/RecurringTask.kt @@ -0,0 +1,8 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules.tasksmodule.models + +abstract class RecurringTask { + + abstract fun start(): RecurringTask + + abstract fun stop(): RecurringTask +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/translationsmodule/TranslationsModule.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/translationsmodule/TranslationsModule.kt new file mode 100644 index 00000000..064c8469 --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/translationsmodule/TranslationsModule.kt @@ -0,0 +1,23 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules.translationsmodule + +import xyz.theprogramsrc.simplecoreapi.global.module.Module +import xyz.theprogramsrc.simplecoreapi.global.module.ModuleDescription +import xyz.theprogramsrc.simplecoreapi.global.modules.translationsmodule.managers.TranslationManager +import xyz.theprogramsrc.simplecoreapi.global.utils.measureLoad + +class TranslationsModule: Module { + override val description: ModuleDescription = ModuleDescription( + name = "TranslationsModule", + version = "0.4.0", + authors = listOf("Im-Fran") + ) + + override fun onEnable() { + measureLoad("'TranslationManager' loaded in {time}"){ + TranslationManager() + } + } + + override fun onDisable() { + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/translationsmodule/managers/TranslationManager.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/translationsmodule/managers/TranslationManager.kt new file mode 100644 index 00000000..11bdcf8f --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/translationsmodule/managers/TranslationManager.kt @@ -0,0 +1,137 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules.translationsmodule.managers + +import xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule.config.YmlConfig +import xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule.extensions.folder +import xyz.theprogramsrc.simplecoreapi.global.modules.translationsmodule.models.Translation +import java.io.File + +class TranslationManager { + + private val translationsCache = mutableMapOf>() + private val cache = mutableMapOf>>() + + companion object { + private val translationSettings = YmlConfig(File(File("plugins/SimpleCoreAPI").folder(), "TranslationSettings.yml")).add("language", "en") + lateinit var instance: TranslationManager + private set + + fun getCurrentLanguage(): String = translationSettings.getStringOrSet("language", "en") + } + + init { + instance = this + loadTranslations() + } + + /** + * Register the given translations to the given group id + * @param group The group (folder) of the translation. Defaults to "common" + * @param translation The translations to register + */ + fun registerTranslation(group: String = "common", translation: Translation) = this.registerTranslations(group, listOf(translation)) + + /** + * Register the given translations to the given group id + * @param group The group (folder) of the translation. Defaults to "common" + * @param translations The translations to register + */ + fun registerTranslations(group: String = "common", vararg translations: Translation) = this.registerTranslations(group, translations.toList()) + + /** + * Register the given translations to the given group id + * @param group The group (folder) of the translation. Defaults to "common" + * @param translations The translations to register + */ + fun registerTranslations(group: String = "common", translations: Collection) { + val translationsCache = this.translationsCache[group] ?: mutableListOf() + translations.forEach { t -> + if (translationsCache.find { it == t } == null) { + translationsCache.add(t) + } + } + this.translationsCache[group] = translationsCache + loadTranslations() + } + + /** + * Get the translation for the given id in the given language + * @param id The id of the translation + * @param language The language of the translation. Set to null to use the default language. Defaults to null + * @param group The group (folder) of the translation. Defaults to "common" + * @param placeholders The placeholders to replace in the translation. Defaults to empty map + * @return The translation. If the translation is not found, the id is returned + */ + fun translate(id: String, language: String? = null, group: String = "common", placeholders: Map = emptyMap()): String { + val cached = cache[group] ?: return id + var translation = ((cached[language] ?: cached[getCurrentLanguage()] ?: return id)[id] ?: return id) + placeholders.forEach { (key, value) -> + translation = translation.replace("{$key}", value).replace("%$key%", value) + } + return translation + } + + /** + * Loads the translations to the cache. + */ + fun loadTranslations() { + val translationsFolder = File("translations/").folder() + // First load the default translations + translationsCache.forEach { (group, translations) -> + val folder = File(translationsFolder, group).folder() + translations.forEach { + val languageFile = YmlConfig(File(folder, "${it.language}.lang")) + languageFile.add(it.id, it.defaultValue) + } + } + + // Then load the translations from the language files + (translationsFolder.listFiles() ?: emptyArray()).filter(File::isDirectory).forEach { groupFolder -> + val cached = cache[groupFolder.name] ?: mutableMapOf() + (groupFolder.listFiles() ?: emptyArray()).filter { it.extension == "lang" }.forEach { + val langCache = cached[it.nameWithoutExtension] ?: mutableMapOf() + val cfg = YmlConfig(it) + cfg.keys(true).forEach { id -> + val t = translationsCache[groupFolder.name]?.find { t1 -> t1.id == id } + if (t != null) { + langCache[id] = t.translate(it.nameWithoutExtension) + } + } + if (langCache.isNotEmpty()) { + cached[it.nameWithoutExtension] = langCache + } + } + + if (cached.isNotEmpty()) { + cache[groupFolder.name] = cached + } + } + } + + /** + * Count the amount of translations + * @param group The group (folder) of the translation. If null it'll count all the groups. Defaults to null + * @param lang The language of the translation. If null it'll count all the languages. Defaults to null + */ + fun countTranslations(group: String? = null, lang: String? = null): Int { + return if (group == null) { + cache.values.sumOf { + if (lang != null) { + it[lang]?.size ?: 0 + } else { + it.values.sumOf { it2 -> + it2.values.size + } + } + } + } else { + val c = (this.cache[group] ?: return -2) + if (lang != null) { + c[lang]?.size ?: 0 + } else { + c.values.sumOf { it2 -> + it2.values.size + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/translationsmodule/models/Translation.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/translationsmodule/models/Translation.kt new file mode 100644 index 00000000..714bf0bf --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/translationsmodule/models/Translation.kt @@ -0,0 +1,100 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules.translationsmodule.models + +import xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule.config.YmlConfig +import xyz.theprogramsrc.simplecoreapi.global.modules.filesmodule.extensions.folder +import xyz.theprogramsrc.simplecoreapi.global.modules.translationsmodule.managers.TranslationManager +import java.io.File + +/** + * Representation of a translation. + * @param id The id of the translation + * @param defaultValue The default value of the translation. + * @param group The group (folder) of the translation. Defaults to "common" + * @param language The language of the translation. (Defaults to "en") + * @param mainColor The main color of the translation. (Defaults to null) + * @param colors The colors to use in the translation replacing strings. Example (using color '&c'): '**test**' should return '&ctest'. Defaults to empty array. + * @param autoRegister If the translation should be automatically registered. (Defaults to true) It is recommended to disable if you're going to initialize the same translation multiple times (for example inside a loop) + */ +data class Translation( + val id: String, + val defaultValue: String, + val group: String = "common", + val language: String = "en", + val mainColor: String? = null, + val colors: Array = emptyArray(), + val autoRegister: Boolean = true +) { + + init { + if (autoRegister) { + TranslationManager.instance.registerTranslations(group, this) + } + } + + /** + * Translates this [Translation] to the current language. + * @param language The language of the translation. Set to null to use the default language. Defaults to null + * @param placeholders The placeholders to use in the translation replacing strings. Example (using placeholder id 'test' and value 'test_value'): '{test}' should return 'test_value'. + * You can use '{}' or '%%' as placeholder identifiers like '{test}' or '%test%'. Defaults to empty map. + * @return The translated string. + */ + fun translate(language: String? = null, placeholders: Map = emptyMap(), colorize: Boolean = true): String { + val file = YmlConfig(File( + File("translations/${if (group.endsWith("/")) group else "$group/"}").folder(), (language + ?: TranslationManager.getCurrentLanguage()) + ".lang")) // Get the file of the translation + val mainColor = this.mainColor ?: "" // Get the main color of the translation + var translation = mainColor.plus( + if (file.has(id)) { // If the translation exists + file.getString(id) // Get the translation from the file + } else { // If the translation doesn't exist + defaultValue // Get the default value + } + ) + for (i in colors.indices) { // For each color + try { + val color = colors[i] // Get the color + val string = Regex("\\*\\*(.+?)\\*\\*").findAll(translation).first().groupValues[1] // Get the string to replace + translation = translation.replaceFirst("**$string**", "$color$string$mainColor") // Replace the first match with the colorized string + } catch (_: Exception) { + } // Ignore errors + } + + placeholders.forEach { (key, value) -> // For each placeholder + translation = translation.replace("{$key}", value).replace("%$key%", value) // Replace the placeholder using %% and {} + } + + return if (colorize) { // Return the translated string + translation.replace("&", "ยง") + } else { + translation + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Translation + + if (id != other.id) return false + if (defaultValue != other.defaultValue) return false + if (!colors.contentEquals(other.colors)) return false + if (group != other.group) return false + if (language != other.language) return false + if (mainColor != other.mainColor) return false + if (autoRegister != other.autoRegister) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + defaultValue.hashCode() + result = 31 * result + colors.contentHashCode() + result = 31 * result + group.hashCode() + result = 31 * result + language.hashCode() + result = 31 * result + (mainColor?.hashCode() ?: 0) + result = 31 * result + autoRegister.hashCode() + return result + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/GlobalUtils.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/GlobalUtils.kt new file mode 100644 index 00000000..aea3c322 --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/GlobalUtils.kt @@ -0,0 +1,26 @@ +package xyz.theprogramsrc.simplecoreapi.global.utils + +import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI + +/** + * Measures the amount of time in milliseconds it takes to execute the given block. Example: + * ```kt + * measureLoad("Waited for {time}") { + * // wait for 100 ms + * Thread.sleep(100) + * } + * ``` + * + * Sample console output: + * ```log + * Waited for 100ms + * ``` + * @param message The message to print. You can use '{time}' to replace with the amount of time in ms + * @param block The block to execute + */ +inline fun measureLoad(message: String, block: () -> T): T { + val now = System.currentTimeMillis() + val response = block() + SimpleCoreAPI.logger.info(message.replace("{time}", "${System.currentTimeMillis() - now}ms")) + return response +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/SpigotLoader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/SpigotLoader.kt index 6d8841da..5699030a 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/SpigotLoader.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/SpigotLoader.kt @@ -2,7 +2,6 @@ package xyz.theprogramsrc.simplecoreapi.spigot import org.bukkit.plugin.java.JavaPlugin import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI -import xyz.theprogramsrc.simplecoreapi.spigot.classloader.SpigotDependencyLoader /** * Representation of the Spigot plugin loader. @@ -21,9 +20,7 @@ class SpigotLoader: JavaPlugin() { override fun onLoad() { instance = this - SimpleCoreAPI( - dependencyClassLoader = SpigotDependencyLoader() - ) + SimpleCoreAPI() } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/classloader/SpigotClassLoader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/classloader/SpigotClassLoader.kt deleted file mode 100644 index ab5cfd11..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/classloader/SpigotClassLoader.kt +++ /dev/null @@ -1,38 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.spigot.classloader - -import java.net.URL -import java.net.URLClassLoader - -class SpigotClassLoader( - private val standaloneDependencyLoader: SpigotDependencyLoader, - urls: Array -) : URLClassLoader(urls) { - - override fun loadClass(name: String, resolve: Boolean): Class<*> = - loadClass0( - name = name, - resolve = resolve, - checkOthers = true - ) - - fun loadClass0(name: String, resolve: Boolean, checkOthers: Boolean): Class<*> { - try { - val `class` = super.loadClass(name, resolve) - - if(checkOthers || `class`.classLoader == this) { - return `class` - } - } catch (_: ClassNotFoundException){} - - if (checkOthers) { - try { - val `class` = standaloneDependencyLoader.findClassByName(name) - if (`class`.classLoader is SpigotClassLoader) { - return `class` - } - } catch (_: ClassNotFoundException) {} - } - - throw ClassNotFoundException(name) - } -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/classloader/SpigotDependencyLoader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/classloader/SpigotDependencyLoader.kt deleted file mode 100644 index ca0a83b4..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/classloader/SpigotDependencyLoader.kt +++ /dev/null @@ -1,19 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.spigot.classloader - -import xyz.theprogramsrc.simplecoreapi.global.dependencydownloader.interfaces.DependencyLoader -import java.io.File - -class SpigotDependencyLoader: DependencyLoader { - - companion object { - - } - - override fun loadIntoClasspath(file: File) { - TODO("Not yet implemented") - } - - override fun findClassByName(name: String): Class<*> { - TODO("Not yet implemented") - } -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/modules/tasksmodule/SpigotTasksModule.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/modules/tasksmodule/SpigotTasksModule.kt new file mode 100644 index 00000000..04976e11 --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/modules/tasksmodule/SpigotTasksModule.kt @@ -0,0 +1,125 @@ +package xyz.theprogramsrc.simplecoreapi.spigot.modules.tasksmodule + +import org.bukkit.Bukkit +import org.bukkit.scheduler.BukkitScheduler +import org.bukkit.scheduler.BukkitTask +import xyz.theprogramsrc.simplecoreapi.global.module.Module +import xyz.theprogramsrc.simplecoreapi.global.module.ModuleDescription +import xyz.theprogramsrc.simplecoreapi.global.modules.tasksmodule.models.RecurringTask +import xyz.theprogramsrc.simplecoreapi.spigot.SpigotLoader + +class SpigotTasksModule: Module { + + + companion object { + /** + * Instance of the [SpigotTasksModule]. + */ + lateinit var instance: SpigotTasksModule + private set + } + + override val description: ModuleDescription = ModuleDescription( + name = "TasksModule", + version = "0.3.0", + authors = listOf("Im-Fran"), + ) + + /** + * Instance of the [BukkitScheduler] (Spigot Scheduler) + */ + val scheduler: BukkitScheduler = Bukkit.getScheduler() + + /** + * Instance of Spigot Loader + */ + private val plugin = SpigotLoader.instance + + override fun onEnable() { + instance = this + } + + override fun onDisable() { + } + + /** + * Runs a task after 1 tick (0.05 seconds) + * @param task The task to run + * @return the [BukkitTask] + */ + fun runTask(task: () -> Unit): BukkitTask = runTaskLater(task = task) + + /** + * Runs an async task after 1 tick (0.05 seconds) + * @param task The task to run + * @return the [BukkitTask] + */ + fun runTaskAsynchronously(task: () -> Unit): BukkitTask = runTaskLaterAsynchronously(task = task) + + /** + * Runs a task after the given ticks (1 tick = 0.05 seconds) + * @param ticks The ticks to run after. Defaults to 1 tick (0.05 seconds) + * @param task The task to run + * @return the [BukkitTask] + */ + fun runTaskLater(ticks: Long = 1, task: () -> Unit): BukkitTask = scheduler.runTaskLater(plugin, task, ticks) + + /** + * Runs an async task after the given ticks (1 tick = 0.05 seconds) + * @param ticks The ticks to run after. Defaults to 1 tick (0.05 seconds) + * @param task The task to run + * @return the [BukkitTask] + */ + fun runTaskLaterAsynchronously(ticks: Long = 1, task: () -> Unit): BukkitTask = scheduler.runTaskLaterAsynchronously(plugin, task, ticks) + + /** + * Runs a repeating task every given ticks (1 tick = 0.05 seconds) after the given ticks (1 tick = 0.05 seconds) + * @param period The ticks to wait between each run. Defaults to 1 tick (0.05 seconds) + * @param delay The ticks to wait before the first run. Defaults to 1 tick (0.05 seconds) + * @param task The task to run + * @return the [RecurringTask] + */ + fun runTaskTimer(period: Long = 1, delay: Long = 1, task: () -> Unit): RecurringTask = createRecurringTask { + scheduler.runTaskTimer(plugin, task, delay, period) + } + + /** + * Runs a repeating task asynchronously every given ticks (1 tick = 0.05 seconds) after the given ticks (1 tick = 0.05 seconds) + * @param period The ticks to wait between each run. Defaults to 1 tick (0.05 seconds) + * @param delay The ticks to wait before the first run. Defaults to 1 tick (0.05 seconds) + * @param task The task to run + * @return the [RecurringTask] + */ + fun runTaskTimerAsynchronously(period: Long = 1, delay: Long = 1, task: () -> Unit): RecurringTask = createRecurringTask { + scheduler.runTaskTimerAsynchronously(plugin, task, delay, period) + } + + private fun createRecurringTask(bukkitTask: () -> BukkitTask): RecurringTask { + return object:RecurringTask() { + private var task: BukkitTask? = null + + init { + task = bukkitTask.invoke() + } + + override fun start(): RecurringTask { + if(task == null){ + task = bukkitTask.invoke() + return this + } + + if(!scheduler.isCurrentlyRunning(task!!.taskId)) { + task = bukkitTask.invoke() + } + return this + } + + override fun stop(): RecurringTask { + task?.cancel() + task = null + return this + } + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/EntrypointLoader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/EntrypointLoader.kt index 85f45938..5a51eb08 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/EntrypointLoader.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/EntrypointLoader.kt @@ -2,7 +2,7 @@ package xyz.theprogramsrc.simplecoreapi.standalone import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI import java.io.File -import java.util.Properties +import java.util.* import java.util.jar.JarFile import kotlin.system.exitProcess @@ -70,14 +70,18 @@ class EntrypointLoader { // Check if the module.properties has the entrypoint property to load the entrypoint class val entrypointClass = moduleProperties.getProperty("entrypoint") ?: return logger.info("No entrypoint class found in the module.properties. Skipping...") logger.debug("Loading entrypoint class $entrypointClass...") -// val loader = ModuleClassLoader(entrypointFile) -// val entrypoint = loader.loadClass(entrypointClass).getDeclaredConstructor().newInstance() as? EntryPoint ?: return logger.info("Entrypoint class does not implement EntryPoint interface. Skipping...") -// // Call the onLoad method -// entrypoint.onLoad() -// // Call the onEnable method -// entrypoint.onEnable() -// // Register the onDisable method to be called when the app is disabled -// Runtime.getRuntime().addShutdownHook(Thread { entrypoint.onDisable() }) + val entrypoint = Class.forName(entrypointClass).getConstructor().newInstance() as? EntryPoint ?: return logger.info("Entrypoint class $entrypointClass does not implement EntryPoint. Skipping...") + // Call the onLoad method + logger.debug("Calling onLoad method...") + entrypoint.onLoad() + // Call the onEnable method + logger.debug("Calling onEnable method...") + entrypoint.onEnable() + // Add a shutdown hook to call the onDisable method + Runtime.getRuntime().addShutdownHook(Thread { + logger.debug("Calling onDisable method...") + entrypoint.onDisable() + }) } } diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/StandaloneLoader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/StandaloneLoader.kt index cd25df2e..6ddd305b 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/StandaloneLoader.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/StandaloneLoader.kt @@ -1,7 +1,7 @@ package xyz.theprogramsrc.simplecoreapi.standalone import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI -import xyz.theprogramsrc.simplecoreapi.standalone.classloader.StandaloneDependencyLoader +import xyz.theprogramsrc.simplecoreapi.global.utils.measureLoad fun main() { StandaloneLoader() @@ -20,11 +20,9 @@ class StandaloneLoader { init { instance = this isRunning = true - val simpleCoreAPI = SimpleCoreAPI( - dependencyClassLoader = StandaloneDependencyLoader(), - ) + SimpleCoreAPI() - simpleCoreAPI.measureLoad("Loaded entrypoint in {time}") { + measureLoad("Loaded entrypoint in {time}") { EntrypointLoader() } } diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/classloader/StandaloneClassLoader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/classloader/StandaloneClassLoader.kt deleted file mode 100644 index 69a45c21..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/classloader/StandaloneClassLoader.kt +++ /dev/null @@ -1,38 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.standalone.classloader - -import java.net.URL -import java.net.URLClassLoader - -class StandaloneClassLoader( - private val standaloneDependencyLoader: StandaloneDependencyLoader, - urls: Array -) : URLClassLoader(urls) { - - override fun loadClass(name: String, resolve: Boolean): Class<*> = - loadClass0( - name = name, - resolve = resolve, - checkOthers = true - ) - - fun loadClass0(name: String, resolve: Boolean, checkOthers: Boolean): Class<*> { - try { - val `class` = super.loadClass(name, resolve) - - if(checkOthers || `class`.classLoader == this) { - return `class` - } - } catch (_: ClassNotFoundException){} - - if (checkOthers) { - try { - val `class` = standaloneDependencyLoader.findClassByName(name) - if (`class`.classLoader is StandaloneClassLoader) { - return `class` - } - } catch (_: ClassNotFoundException) {} - } - - throw ClassNotFoundException(name) - } -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/classloader/StandaloneDependencyLoader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/classloader/StandaloneDependencyLoader.kt deleted file mode 100644 index 84627908..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/classloader/StandaloneDependencyLoader.kt +++ /dev/null @@ -1,26 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.standalone.classloader - -import xyz.theprogramsrc.simplecoreapi.global.dependencydownloader.interfaces.DependencyLoader -import java.io.File - -class StandaloneDependencyLoader : DependencyLoader { - - private val classLoaders = mutableListOf() - - override fun loadIntoClasspath(file: File) { - val classLoader = StandaloneClassLoader(this, arrayOf(file.toURI().toURL())) - classLoaders.add(classLoader) - } - - override fun findClassByName(name: String): Class<*> = classLoaders.firstNotNullOfOrNull { - try { - it.loadClass0( - name = name, - resolve = true, - checkOthers = false - ) - } catch (_: ClassNotFoundException) { - null - } - } ?: throw ClassNotFoundException(name) -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/velocity/modules/tasksmodule/VelocityTasksModule.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/velocity/modules/tasksmodule/VelocityTasksModule.kt new file mode 100644 index 00000000..b2407f78 --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/velocity/modules/tasksmodule/VelocityTasksModule.kt @@ -0,0 +1,89 @@ +package xyz.theprogramsrc.simplecoreapi.velocity.modules.tasksmodule + +import com.velocitypowered.api.scheduler.ScheduledTask +import com.velocitypowered.api.scheduler.Scheduler +import xyz.theprogramsrc.simplecoreapi.global.module.Module +import xyz.theprogramsrc.simplecoreapi.global.module.ModuleDescription +import xyz.theprogramsrc.simplecoreapi.global.modules.tasksmodule.models.RecurringTask +import xyz.theprogramsrc.simplecoreapi.velocity.VelocityLoader +import java.util.concurrent.TimeUnit + +/** + * Representation of the [VelocityTasksModule]. + */ +class VelocityTasksModule: Module { + + companion object { + /** + * Instance of the [VelocityTasksModule]. + */ + lateinit var instance: VelocityTasksModule + private set + } + + /** + * Instance of the [com.velocitypowered.api.scheduler.Scheduler] + * from the [com.velocitypowered.api.proxy.ProxyServer]. + */ + val scheduler: Scheduler = VelocityLoader.instance.server.scheduler + + /** + * Instance of the [VelocityLoader] + */ + private val plugin = VelocityLoader.instance + + + override val description: ModuleDescription = ModuleDescription( + name = "TasksModule", + version = "0.3.0", + authors = listOf("Im-Fran"), + ) + + override fun onEnable() { + instance = this + } + + override fun onDisable() { + } + + /** + * Runs an async task after the specified delay in ticks + * @param delay The delay in ticks. Defaults to 1 (1 tick = 0.05 seconds) + * @param task The task to run + * @return the [ScheduledTask] + */ + fun runAsync(delay: Int = 1, task: () -> Unit): ScheduledTask = + scheduler.buildTask(plugin, task) + .delay(delay.times(50).toLong(), TimeUnit.MILLISECONDS) + .schedule() + + /** + * Runs a repeating task asynchronously every given ticks (1 tick = 0.05 seconds) after the given ticks (1 tick = 0.05 seconds) + * @param delay The delay in ticks. Defaults to 1 + * @param period The period in ticks. Defaults to 1 + * @param task The task to run + * @return the [RecurringTask] + */ + fun runAsyncRepeating(delay: Int = 1, period: Int = 1, task: () -> Unit): RecurringTask { + val velocityTask = scheduler.buildTask(plugin, task) + .delay(delay.times(50).toLong(), TimeUnit.MILLISECONDS) + .repeat(period.times(50).toLong(), TimeUnit.MILLISECONDS) + .schedule() + return object:RecurringTask(){ + var cancelled: Boolean = false + + override fun start(): RecurringTask = this.apply { + if(cancelled) { + this@VelocityTasksModule.runAsyncRepeating(delay, period, task) + cancelled = false + } + } + + override fun stop(): RecurringTask = this.apply { + velocityTask.cancel() + cancelled = true + } + + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/dependencydownloader/DependencyDownloaderTest.kt b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/dependencydownloader/DependencyDownloaderTest.kt index 3cd6712b..10d81d6c 100644 --- a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/dependencydownloader/DependencyDownloaderTest.kt +++ b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/dependencydownloader/DependencyDownloaderTest.kt @@ -2,10 +2,10 @@ package xyz.theprogramsrc.simplecoreapi.global.dependencydownloader import org.apache.commons.io.FileUtils import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test -import xyz.theprogramsrc.simplecoreapi.global.models.module.Module +import xyz.theprogramsrc.simplecoreapi.global.module.Module import xyz.theprogramsrc.simplecoreapi.standalone.StandaloneLoader import java.io.File diff --git a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/ModuleTest.kt b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/ModuleTest.kt index 6dc832ce..4e8c9478 100644 --- a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/ModuleTest.kt +++ b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/models/ModuleTest.kt @@ -6,10 +6,10 @@ import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI -import xyz.theprogramsrc.simplecoreapi.global.models.module.Module -import xyz.theprogramsrc.simplecoreapi.global.models.module.ModuleDescription -import xyz.theprogramsrc.simplecoreapi.global.models.module.isModuleLoaded -import xyz.theprogramsrc.simplecoreapi.global.models.module.requireModule +import xyz.theprogramsrc.simplecoreapi.global.module.Module +import xyz.theprogramsrc.simplecoreapi.global.module.ModuleDescription +import xyz.theprogramsrc.simplecoreapi.global.module.isModuleLoaded +import xyz.theprogramsrc.simplecoreapi.global.module.requireModule import xyz.theprogramsrc.simplecoreapi.standalone.StandaloneLoader import java.io.File diff --git a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/ModuleInteroperabilityTest.kt b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/ModuleInteroperabilityTest.kt index 2f611b8e..9474b40b 100644 --- a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/ModuleInteroperabilityTest.kt +++ b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/ModuleInteroperabilityTest.kt @@ -6,12 +6,11 @@ import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI -import xyz.theprogramsrc.simplecoreapi.global.dependencydownloader.DependencyDownloader import xyz.theprogramsrc.simplecoreapi.global.models.SumModule -import xyz.theprogramsrc.simplecoreapi.global.models.module.Module -import xyz.theprogramsrc.simplecoreapi.global.models.module.ModuleDescription -import xyz.theprogramsrc.simplecoreapi.global.models.module.isModuleLoaded -import xyz.theprogramsrc.simplecoreapi.global.models.module.requireModule +import xyz.theprogramsrc.simplecoreapi.global.module.Module +import xyz.theprogramsrc.simplecoreapi.global.module.ModuleDescription +import xyz.theprogramsrc.simplecoreapi.global.module.isModuleLoaded +import xyz.theprogramsrc.simplecoreapi.global.module.requireModule import xyz.theprogramsrc.simplecoreapi.standalone.StandaloneLoader import java.io.File