diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 40580955..1f9f49e1 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -19,23 +19,57 @@ - + - + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 0fc31131..f8467b45 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index f333c4c7..4963d923 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -4,6 +4,8 @@ + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 2f0429cb..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,178 +0,0 @@ -## v0.6.3 - Snapshot -* Improved dependency load sorter -* Improved folder system for Standalone software -* Fixed modules not working for Standalone software -* Improved gradle-test workflow - -## v0.6.2 - Snapshot -* Improved .env loader -* Fixed "ENV" variable to be uppercase -* Fixed dokkaJavadocJar task dependency -* Fixed CHANGELOG.md -* Add maven local to repos - -## v0.6.1 - Snapshot -* Added Maven Central Repository -* Updated SECURITY.md - -## v0.6.0 - Snapshot -* Added Standalone support -* Improved velocity SoftwareType check - -#### BREAKING CHANGE - -* Renamed `repositoryId` to `moduleId` in ModuleDescription and `repository-id` to `module-id` in module.properties - -## v0.5.0 - Snapshot -* Add Velocity SoftwareType -* Migrated to Gradle Kotlin DSL -* Updated Dependencies -* Added .sdkmanrc - -## v0.4.6 - Snapshot -* Updated dependencies -* Added caching for workflows - -## v0.4.5 - Snapshot -* Updated spigot -* Updated gson - -## v0.4.4 - Snapshot -* Updated some dependencies -* Removed debug logs - -## v0.4.3 - Snapshot -* Updated kotlin 1.7.20 -* Updated junit 5.9.1 - -## v0.4.2 - Snapshot -* Updated dependencies -* Moving to jitpack - -## v0.4.1 - Snapshot -* Added custom blossom injector dependency -* Updated dependencies -* Moved github update check util -* Added new update check utils -* Updated .idea files -* Removed simplecoreapi properties to use injected variables -* Added tests for every update checker (available at the moment) - -## v0.4.0 - Snapshot -* Added Velocity Support -* Now we use the ILogger util to allow the usage of slf4j and JUL -* Now we use blossom to inject variables into the jar and the code - -## v0.3.6 - Snapshot -* Updated Dependencies -* Spigot/Bungee 1.19 Support - -## v0.3.5 - Snapshot -* Dependency Updates - -## v0.3.4 - Snapshot -* Fixed SoftwareType Checker - -## v0.3.3 - Snapshot -* Configured Renovate -* Updated dependencies -* Fixed GitHubUpdateCheckerTest.kt - -## v0.3.2 - Snapshot -* Fixed class loader issue causing ClassNotFoundException for some servers. -* Now the class loader will only load the main class of the modules. - -## v0.3.1 - Snapshot -* Added class `SoftwareType` to list all available software types. -* Added Method `SimpleCoreAPI.softwareType` to get the current software running the server -* Added Method `SimpleCoreAPI#isRunningSoftwareType` to check if the server is running a specific software - -## v0.3.0 - Snapshot -* Now the API will be initialized at the load stage of the server -* Added Module#onLoad function to add the ability of loading a module at the load stage of the server -* Added Module#isEnabled function to check if the module is enabled -* Added Module#isLoaded function to check if the module is loaded -* Improved the Jar update process (where the files from plugins/SimpleCoreAPI/update/ are moved to the modules folder) -* Fixed Settings.yml not being properly updated - -## v0.2.5 - Snapshot -* Improved dependency sorting (It puts first the modules with no dependencies, then the modules with dependencies) -* Added GitHub Update Checker and GitHub Auto Updater for the modules -* Added `github-repository` to the module properties -* Added configuration - -## v0.2.4 - Snapshot -* Fixed Class Not Found error -* Improvements to the Module Loader - -## v0.2.3 - Snapshot -* Fixes to update checker to print the message -* Now GitHub actions will automatically upload the jar file - -## v0.2.2 - Snapshot -* Fixed module loader not loading all the classes and stopping when the module is initialized. - -## v0.2.1 - Snapshot -* Fixed loop on load while downloading certain dependencies - -## v0.2.0 - Snapshot -* Added Static Instance for Spigot and Bungee Loaders -* Set initializer private to avoid issues - -## v0.1.12 - Snapshot -* Updated Dependencies -* Updated Deprecated Code - -## v0.1.11 - Snapshot -* Added scanner to load all required modules - -## v0.1.10 - Snapshot -* Added GitHub update checker -* Added method to download modules from SimpleCoreAPI.kt - -## v0.1.9 - Snapshot -* Fixed when trying to get simplecoreapi.properties resource returning null - -## v0.1.8 - Snapshot -* Updated gradle to v7.3.2 - -## v0.1.7 - Snapshot -* Fixes to the module download -* Migrated to new module download system (repository based) -* Added required repository-id field to module properties -* Removed CloudModule.kt due to migration to new module download system - -## v0.1.6 - Snapshot -* Added update system like update folder in bukkit servers -* Fixed modules not being loaded (Class Not Found Exception) -* Fixed Null Pointer Exception when abruptly disabling the api -* Improvements to the module load order -* Fixes to issue templates - -## v0.1.4 - Snapshot / v0.1.5 - Snapshot -* Fixed build script not working -* Implemented custom actions - -## v0.1.3 - Snapshot -* Added ability to re-deploy a version by specifying it in the environment variable `VERSION` -* Add a way to let know the user the current commit hash of the running release. -* Added Dokka -* Added Gradle Wrapper Validator -* Improve deploy script to update edited releases. -* Fixed shadowJar not saving the file without version -* Cleanup some code - -## v0.1.2 - Snapshot -* Fixed fat jar not recognized by kotlin plugin [KTIJ-20430](http://youtrack.jetbrains.com/issue/KTIJ-20430) -* Added dokka -* Fixed `minimize()` removing dependencies -* Updated maven publish config -* Removed code-ql analysis because kotlin is not supported :/ - -## v0.1.1 - Snapshot -* Fixed empty dependency being loaded (Currently being fixed) -* Cleanup of the code -* Migration to Kotlin - -## v0.1.0 - Snapshot -Hello, World! diff --git a/README.md b/README.md index 821e1c37..36072d62 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,17 @@ _The best way to create a plugin_
## Where is the documentation? This can be found [here](https://docs.theprogramsrc.xyz/SimpleCoreAPI/) (it's a Dokka Resource), everything is documented through the Kotlin Docs (Similar to JavaDocs but for Kotlin :p ) + +## How does this work? +Ok, so we have multiple types of initializers for different software, Spigot, Bungee, Velocity and Standalone. + +The Standalone system works by loading all jars under the modules/ folder into the current classpath.
+The other kinds of software will use their embedded plugin system to avoid difficult tasks, like sorting modules to be loaded. + +## Ok, and how do I use the Standalone mode? +In the Standalone system you will naturally shade SimpleCoreAPI and use the `xyz.theprogramsrc.simplecoreapi.standalone.StandaloneLoader` as your main class.
+Later you will mark your main class with the `@EntryPoint` annotation. Then SimpleCoreAPI will load all modules, then execute the `@EntryPoint` class. + +## How do I use the other modes? +Depending on the software you're using you'll need to mark SimpleCoreAPI and the modules you need as `dependency` (NOT `soft-dependency`) so the system will load all +modules and files you need before your plugin is loaded. diff --git a/build.gradle.kts b/build.gradle.kts index 9b1f9580..900b0022 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,7 +15,7 @@ val env = project.rootProject.file(".env").let { file -> if(file.exists()) file.readLines().filter { it.isNotBlank() && !it.startsWith("#") && it.split("=").size == 2 }.associate { it.split("=")[0] to it.split("=")[1] } else emptyMap() }.toMutableMap().apply { putAll(System.getenv()) } -val projectVersion = env["VERSION"] ?: "0.6.3-SNAPSHOT" +val projectVersion = env["VERSION"] ?: "0.7.0-SNAPSHOT" group = "xyz.theprogramsrc" version = projectVersion.replaceFirst("v", "").replace("/", "") @@ -114,7 +114,8 @@ tasks { } dokkaHtml { - outputDirectory.set(file(project.buildDir.absolutePath + "/dokka")) + outputDirectory.set(layout.buildDirectory.dir("dokka/")) + } } @@ -189,7 +190,7 @@ publishing { if(env["ENV"] == "prod") { nexusPublishing { - repositories { + this.repositories { sonatype { nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/bungee/BungeeLoader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/bungee/BungeeLoader.kt index 5f49547f..060762ef 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/bungee/BungeeLoader.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/bungee/BungeeLoader.kt @@ -16,12 +16,4 @@ class BungeeLoader: Plugin() { SimpleCoreAPI(JavaLogger(this.logger)) } - override fun onEnable() { - SimpleCoreAPI.instance.moduleManager?.enableModules() - } - - override fun onDisable() { - SimpleCoreAPI.instance.moduleManager?.disableModules() - } - } \ 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 69e7940e..f9c884ae 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/SimpleCoreAPI.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/SimpleCoreAPI.kt @@ -1,17 +1,18 @@ package xyz.theprogramsrc.simplecoreapi.global -import xyz.theprogramsrc.simplecoreapi.global.module.ModuleManager +import xyz.theprogramsrc.simplecoreapi.global.modules.ModuleManager import xyz.theprogramsrc.simplecoreapi.global.utils.ILogger import xyz.theprogramsrc.simplecoreapi.global.utils.SoftwareType import xyz.theprogramsrc.simplecoreapi.global.utils.update.GitHubUpdateChecker import xyz.theprogramsrc.simplecoreapi.standalone.StandaloneLoader import java.io.File +import java.lang.RuntimeException /** * Class used to initialize SimpleCoreAPI (DO NOT CALL IT FROM EXTERNAL PLUGINS, IT MAY CRASH) * @param logger The logger to use */ -class SimpleCoreAPI(logger: ILogger) { +class SimpleCoreAPI(val logger: ILogger) { companion object { /** @@ -27,14 +28,48 @@ class SimpleCoreAPI(logger: ILogger) { * * @return The file relative to the data folder */ - fun dataFolder(path: String = ""): File = File(if (StandaloneLoader.isRunning) "./SimpleCoreAPI" else "plugins/SimpleCoreAPI", path) - } + fun dataFolder(path: String = ""): File = File(if (StandaloneLoader.isRunning) "./SimpleCoreAPI" else "plugins/SimpleCoreAPI", path).apply { + if(!exists()) + mkdirs() + } - /** - * The Module Manager - * @return The [ModuleManager] - */ - val moduleManager: ModuleManager? + /** + * Checks if the current [SoftwareType] is the one specified + * @param softwareType The [SoftwareType] to check + * @return true if the current [SoftwareType] is the one specified + */ + fun isRunningSoftwareType(softwareType: SoftwareType) = softwareType.check() + + /** + * The given module is added to the required modules list. + * If the module is not found, it will be downloaded and automatically loaded. + * + * @param id The module id. Should be in the format author/repo + */ + fun requireModule(id: String) { + assert(id.split("/").size == 2) { "Invalid repositoryId format. It should be /"} + val isStandalone = isRunningSoftwareType(SoftwareType.STANDALONE) || isRunningSoftwareType(SoftwareType.UNKNOWN) + val moduleFile = if(isStandalone) { + File(dataFolder("modules"), "${id.split("/")[1]}.jar") + } else { + File(File("plugins/"), "${id.split("/")[1]}.jar") + } + + if(moduleFile.exists()) { + return + } + + val downloaded = ModuleManager.downloadModule(id) ?: throw RuntimeException("Module $id could not be downloaded!") + if(isStandalone) { + return // Is automatically loaded later + } + + // Load the module + if(!ModuleManager.loadModule(downloaded)) { + throw RuntimeException("Module $id could not be loaded!") + } + } + } /** * The [SoftwareType] type running on the server @@ -50,13 +85,35 @@ class SimpleCoreAPI(logger: ILogger) { GitHubUpdateChecker(logger, "TheProgramSrc/SimpleCoreAPI", getVersion()).checkWithPrint() } - softwareType = SoftwareType.values().firstOrNull { it.check() } ?: SoftwareType.UNKNOWN + softwareType = SoftwareType.entries.firstOrNull { it.check() } ?: SoftwareType.UNKNOWN if(softwareType != SoftwareType.UNKNOWN && softwareType.display != null) { logger.info("Running API with software ${softwareType.display}") } else { logger.info("Running on unknown server software. Some features might not work as expected!") } - moduleManager = ModuleManager.init(logger) + } + + /** + * 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: () -> OBJECT): OBJECT { + val now = System.currentTimeMillis() + val obj = block() + logger.info(message.replace("{time}", "${System.currentTimeMillis() - now}ms")) + return obj } /** @@ -76,11 +133,4 @@ class SimpleCoreAPI(logger: ILogger) { * @return The version of SimpleCoreAPI */ fun getVersion(): String = "@version@" - - /** - * Checks if the current [SoftwareType] is the one specified - * @param softwareType The [SoftwareType] to check - * @return true if the current [SoftwareType] is the one specified - */ - fun isRunningSoftwareType(softwareType: SoftwareType) = softwareType.check() } \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/InvalidModuleDependencyException.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/InvalidModuleDependencyException.kt deleted file mode 100644 index 270fcbbf..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/InvalidModuleDependencyException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.exceptions - -class InvalidModuleDependencyException: RuntimeException { - constructor(message: String): super(message) - constructor(message: String, cause: Throwable): super(message, cause) -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/InvalidModuleDescriptionException.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/InvalidModuleDescriptionException.kt deleted file mode 100644 index 6410e9ba..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/InvalidModuleDescriptionException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.exceptions - -class InvalidModuleDescriptionException: RuntimeException { - constructor(message: String): super(message) - constructor(message: String, cause: Throwable): super(message, cause) -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/InvalidModuleException.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/InvalidModuleException.kt deleted file mode 100644 index 344cf8dd..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/InvalidModuleException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.exceptions - -class InvalidModuleException: RuntimeException { - constructor(message: String): super(message) - constructor(message: String, cause: Throwable): super(message, cause) -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/ModuleDisableException.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/ModuleDisableException.kt deleted file mode 100644 index 397b7952..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/ModuleDisableException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.exceptions - -class ModuleDisableException: RuntimeException { - constructor(message: String): super(message) - constructor(message: String, cause: Throwable): super(message, cause) -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/ModuleDownloadException.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/ModuleDownloadException.kt deleted file mode 100644 index 541810d3..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/ModuleDownloadException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.exceptions - -class ModuleDownloadException: RuntimeException { - constructor(message: String): super(message) - constructor(message: String, cause: Throwable): super(message, cause) -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/ModuleEnableException.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/ModuleEnableException.kt deleted file mode 100644 index f8cb4c94..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/ModuleEnableException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.exceptions - -class ModuleEnableException: RuntimeException { - constructor(message: String): super(message) - constructor(message: String, cause: Throwable): super(message, cause) -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/ModuleLoadException.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/ModuleLoadException.kt deleted file mode 100644 index 314902a4..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/exceptions/ModuleLoadException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.exceptions - -class ModuleLoadException: RuntimeException { - constructor(message: String): super(message) - constructor(message: String, cause: Throwable): super(message, cause) -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/Module.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/Module.kt deleted file mode 100644 index 6dfc5b5d..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/Module.kt +++ /dev/null @@ -1,89 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.module - -import java.io.File - -/** - * Representation of a Module - */ -open class Module { - private lateinit var file: File - private lateinit var moduleDescription: ModuleDescription - private var enabled: Boolean = false - private var loaded: Boolean = false - - /** - * Initializer of the Module (DO NOT CALL FROM EXTERNAL PLUGINS, IT MAY CRASH) - */ - fun init(file: File, moduleDescription: ModuleDescription) { - this.file = file - this.moduleDescription = moduleDescription - this.onLoad() - this.loaded = true - } - - /** - * Marks this module as enabled - */ - fun enable(){ - if(enabled) { - throw IllegalStateException("Module is already enabled") - } - enabled = true - this.onEnable() - } - - /** - * Gets the file containing the module - * @return the file containing the module - */ - fun getFile(): File = file - - /** - * Gets the Module Description - * @return ModuleDescription of the module - */ - fun getModuleDescription(): ModuleDescription = moduleDescription - - /** - * Gets the name of the Module - * @return the name of the module - */ - fun getName(): String = moduleDescription.name - - /** - * Gets the version of the Module - * @return the version of the module - */ - fun getVersion(): String = moduleDescription.version - - /** - * Gets the author of the Module - * @return the author of the module - */ - fun getAuthor(): String = moduleDescription.author - - /** - * Checks if the module is enabled - * @return true if the module is enabled, false otherwise - */ - fun isEnabled(): Boolean = enabled - - /** - * Checks if the module is loaded - * @return true if the module is loaded, false otherwise - */ - fun isLoaded(): Boolean = loaded - - open fun onLoad() { - - } - - open fun onEnable(){ - - } - - open fun onDisable(){ - - } - -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleDescription.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleDescription.kt deleted file mode 100644 index 4b0cb1a6..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleDescription.kt +++ /dev/null @@ -1,59 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.module - -/** - * Represents a Module Description - * @param mainClass The main class of the module - * @param name The name of the module - * @param version The version of the module - * @param author The author of the module - * @param description The description of the module - * @param moduleId The identifier of this module in the global database (usually the name in lowercase) - * @param dependencies The dependencies of the module (must be the name of the module) - * @param githubRepository The GitHub repository of the module. (Used for the update checker and auto updater) - * @param disableStandalone If true the module won't be loaded if it's in standalone mode. - */ -data class ModuleDescription( - val mainClass: String, - val name: String, - val version: String, - val author: String, - val description: String, - val moduleId: String, - val dependencies: Array, - val githubRepository: String, - val disableStandalone: Boolean = false, -) { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ModuleDescription - - if (mainClass != other.mainClass) return false - if (name != other.name) return false - if (version != other.version) return false - if (author != other.author) return false - if (description != other.description) return false - if (moduleId != other.moduleId) return false - if (!dependencies.contentEquals(other.dependencies)) return false - if (githubRepository != other.githubRepository) return false - if (disableStandalone != other.disableStandalone) return false - - return true - } - - override fun hashCode(): Int { - var result = mainClass.hashCode() - result = 31 * result + name.hashCode() - result = 31 * result + version.hashCode() - result = 31 * result + author.hashCode() - result = 31 * result + description.hashCode() - result = 31 * result + moduleId.hashCode() - result = 31 * result + dependencies.contentHashCode() - result = 31 * result + githubRepository.hashCode() - result = 31 * result + disableStandalone.hashCode() - return result - } - -} diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelper.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelper.kt deleted file mode 100644 index f017d974..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelper.kt +++ /dev/null @@ -1,144 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.module - -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI -import java.io.BufferedReader -import java.io.File -import java.io.InputStreamReader -import java.net.URL -import java.util.jar.JarFile - -/** - * Module Helper to Download or Sort modules - */ -object ModuleHelper { - - private var lastRepoUpdate = 0L // Used to avoid timeouts - - /** - * Downloads a Module from the database - * @param repository Repository of a module to download. (Must be in GitHub format 'User/Repository'. Example: 'TheProgramSrc/SimpleCore-UIsModule') - * @param fileName The name of the file. This can be fetched using the repository metadata. - * @param downloadLocation Location to download the module. (Defaults to SimpleCoreAPI/modules/) - * @return true if the module was downloaded, false otherwise - */ - fun downloadModule(repository: String, fileName: String, downloadLocation: File = SimpleCoreAPI.dataFolder("modules/")): Boolean{ - if(!downloadLocation.exists()) downloadLocation.mkdirs() - val releases = JsonParser.parseString(URL("https://api.github.com/repos/$repository/releases").readText()).asJsonArray // Get the repo releases list - if(releases.isEmpty) // If empty stop - return false - val latestRelease = releases[0].asJsonObject - val assets = JsonParser.parseString(URL(latestRelease.get("assets_url").asString).readText()).asJsonArray // List all the available assets - if(assets.isEmpty) - return false - assets.find { it.asJsonObject.get("name").asString.endsWith(".jar") }?.asJsonObject?.get("browser_download_url")?.asString.let { // Find the first asset that's a .jar (Should be only one, but let's check just in case) - val bytes = URL(it).readBytes() // Read bytes - val file = File(downloadLocation, "$fileName.jar") // Create the file - if(!file.exists()) file.createNewFile() - file.writeBytes(bytes) // Overwrite the bytes with the new data - } - return true // At this point everything went well! - } - - /** - * Generate a new list with the correct order to load the modules - * @param dependencies List of dependencies - * @return List of the sorted modules to load - */ - fun sortModuleDependencies(dependencies: Map>): List { - val visited = mutableSetOf() - val result = mutableListOf() - - fun dfs(node: String) { - visited.add(node) - for (neighbor in dependencies[node] ?: emptySet()) { - if (neighbor !in visited) { - dfs(neighbor) - } - } - - result.add(node) - } - - for (node in dependencies.keys) { - if (node !in visited) { - dfs(node) - } - } - - return result - } - - /** - * Scans the given folder for jar files and then scan - * every jar file to download the required modules - */ - fun scanRequiredModules(folder: File = File(".")): Unit = (folder.listFiles() ?: emptyArray()).forEach { - if(it.isDirectory) { - scanRequiredModules(it) - }else if(it.extension == "jar") { - downloadRequiredModules(it) - } - } - - /** - * Updates the modules repository cache - */ - fun updateRepository(){ - // To allow the development of modules and testing we'll let devs provide the environment variable 'SCAPI_NO_REPO_UPDATE' - if(System.getenv("SCAPI_NO_REPO_UPDATE") != null) - return - - val now = System.currentTimeMillis() - if(lastRepoUpdate == 0L || (lastRepoUpdate - now) > 30000L){ - val file = SimpleCoreAPI.dataFolder("modules-repository.json") - val onlineBytes = URL("https://github.com/TheProgramSrc/GlobalDatabase/raw/master/SimpleCoreAPI/modules-repository.json").readBytes() // Get the online version - if(!file.exists()) file.createNewFile() // Create the file - file.writeBytes(onlineBytes) // Overwrite file - lastRepoUpdate = now // Update the update time - } - } - - /** - * Gets the module metadata from the repository - * @param moduleId The id of the module to fetch the metadata - * @return the given module metadata if it's under the modules reposutory, otherwise null. - */ - fun getModuleMeta(moduleId: String): JsonObject? { - updateRepository() // First we update the repo - val json = JsonParser.parseString(SimpleCoreAPI.dataFolder("modules-repository.json").readText()).asJsonObject - return if(json.has(moduleId)) json.getAsJsonObject(moduleId) else null - } - - /** - * Scans the given [File] for the simplecoreapi.modules - * file and loads the required modules if any - * @param file File to scan. - * @param downloadLocation Location to download the modules. (Defaults to SimpleCoreAPI/modules/) - */ - fun downloadRequiredModules(file: File, downloadLocation: File = SimpleCoreAPI.dataFolder("modules/")){ - updateRepository() // First we update the repository - if(file.extension != "jar") return - try { - JarFile(file).use { jarFile -> // Now we check for every file - val jarEntry = jarFile.getJarEntry("simplecoreapi.modules") // If we find simplecoreapi.modules - if (jarEntry != null) { - val inputStream = jarFile.getInputStream(jarEntry) // Read the file - val reader = BufferedReader(InputStreamReader(inputStream)) // Create the reader - reader.readLines().forEach { // Read every line - if(it.isNotBlank() && it.isNotEmpty() && !it.startsWith("#")) { // Check that is not a blank line nor a comment - val meta = getModuleMeta(it) // Fetch the metadata - if(meta != null){ - if(!File(downloadLocation, "${meta.get("file_name").asString}.jar").exists()){ - val repo = if(meta.has("repository")) meta.get("repository").asString else "TheProgramSrc/SimpleCore-$it" // Generate default repo if not found - downloadModule(repo, meta.get("file_name").asString) // Download the module - } - } - } - } - } - } - } catch (ignored: Exception) {} - } -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleManager.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleManager.kt deleted file mode 100644 index f31b926b..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleManager.kt +++ /dev/null @@ -1,343 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.module - -import org.apache.commons.io.FileUtils -import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI -import xyz.theprogramsrc.simplecoreapi.global.exceptions.* -import xyz.theprogramsrc.simplecoreapi.global.utils.ILogger -import xyz.theprogramsrc.simplecoreapi.global.utils.update.GitHubUpdateChecker -import xyz.theprogramsrc.simplecoreapi.standalone.StandaloneLoader -import java.io.File -import java.io.FileInputStream -import java.io.IOException -import java.net.URLClassLoader -import java.util.* -import java.util.jar.JarFile -import java.util.jar.JarInputStream - -class ModuleManager(private val logger: ILogger) { - - private val modulesFolder = SimpleCoreAPI.dataFolder("modules").apply { - !exists() && mkdirs() - } - private val updatesFolder = SimpleCoreAPI.dataFolder("update").apply { - !exists() && mkdirs() - } - private var loadedModules = LinkedHashMap() - - init { - if (!modulesFolder.exists()) modulesFolder.mkdirs() - if (!updatesFolder.exists()) updatesFolder.mkdirs() - } - - companion object { - private var isLoaded = false - - fun init(logger: ILogger): ModuleManager { - check(!isLoaded) { "ModuleManager is already loaded!" } - isLoaded = true - val moduleManager = ModuleManager(logger) - moduleManager.load() - return moduleManager - } - } - - private var config = mutableMapOf() - - private fun loadConfig(){ - logger.info("Loading config...") - val file = SimpleCoreAPI.dataFolder("Settings.yml").apply { - if(!exists()){ - parentFile.mkdirs() - createNewFile() - } - } - config = FileUtils.readLines(file, Charsets.UTF_8).filter { !it.startsWith("#") }.map { it.split(": ") }.filter { it.size == 2 }.associate { it[0] to it[1] }.toMutableMap() - } - - private fun saveConfig(){ - val file = SimpleCoreAPI.dataFolder("Settings.yml").apply { - if(!exists()){ - parentFile.mkdirs() - createNewFile() - } - } - val lines = file.readLines().toMutableList() - this.config.forEach { (key, value) -> - val index = lines.indexOfFirst { it.startsWith(key) } - if(index == -1){ - lines.add("$key: $value") - } else { - lines[index] = "$key: $value" - } - } - FileUtils.writeLines(file, lines) - } - - init { - loadConfig() - if(!config.containsKey("auto-update")){ // Load defaults - config["auto-update"] = "false" - saveConfig() - } - } - - /** - * Gets a module from the loaded modules - * @param name The name of the module - * @return The requested module, or null if is not found or enabled - */ - fun getModule(name: String): Module? = loadedModules[name] - - private fun load() { - val start = System.currentTimeMillis() - - // First we update the module jars (moving the ones from update/ to the modules/ folder) - updateJars() - - // Now we load the modules - ModuleHelper.scanRequiredModules() - val files = (modulesFolder.listFiles() ?: emptyArray()).filter { it.name.endsWith(".jar") } - if (files.isEmpty()) return - val modules = mutableMapOf() - val dependencies = mutableListOf() // Used to download missing dependencies - val loadedModules = mutableSetOf() // Used to check if a module is already loaded - - // Now we load and save all the module descriptions from the available modules - val updatedModules = mutableListOf() - files.mapNotNull { file -> - try { - // Validate that this file is a module - val props = loadDescription(file) ?: throw InvalidModuleDescriptionException("Failed to load module description for " + file!!.name) - val required = arrayOf("main", "name", "version", "author", "description", "module-id") - for (req in required) { - if (!props.containsKey(req)) { - throw InvalidModuleDescriptionException("Missing required property " + req + " in module " + file!!.name) - } - } - - // Load the module description - file to ModuleDescription( - props.getProperty("main").replace("\"(.+)\"".toRegex(), "$1"), - props.getProperty("name").replace("\"(.+)\"".toRegex(), "$1"), - props.getProperty("version").replace("\"(.+)\"".toRegex(), "$1"), - props.getProperty("author").replace("\"(.+)\"".toRegex(), "$1"), - props.getProperty("description").replace("\"(.+)\"".toRegex(), "$1"), - props.getProperty("module-id").replace("\"(.+)\"".toRegex(), "$1"), - (if (props.containsKey("dependencies")) props.getProperty("dependencies") else "").replace( - "\"(.+)\"".toRegex(), - "$1" - ).split(",") - .filter { it.isNotBlank() } - .toTypedArray(), - props.getProperty("github-repository") ?: "", - props.getProperty("disable-standalone", "false") == "true" - ) - } catch (e: Exception) { - e.printStackTrace() - null - } - }.filter { !(it.second.disableStandalone && StandaloneLoader.isRunning) }.forEach { // Filter not null descriptions and filter to only run available modules - val file = it.first - val description = it.second - - if(description.githubRepository.isNotBlank()){ - // Check for updates - val checker = GitHubUpdateChecker(logger, description.githubRepository, description.version) // Generate a new update checker - val isAvailable = checker.checkForUpdates() // Check for the updates - val autoUpdate = config["auto-update"] == "true" // Check if we have enabled the auto updater - if(isAvailable && autoUpdate){ // Download an update if there is one available and the auto updater is enabled - logger.info("An update for the module ${description.name} is available. Downloading and updating...") - val meta = ModuleHelper.getModuleMeta(description.moduleId) - if(meta == null) { - logger.error("Failed to update the module ${description.name}. Please download manually from ${if(description.githubRepository.isBlank()) "https://github.com/${description.githubRepository}/releases/latest" else " the module page."}") - } else { - val repo = if(meta.has("repository")) meta.get("repository").asString else "TheProgramSrc/SimpleCore-${description.moduleId}" // Generate default repo if not found - if(ModuleHelper.downloadModule(repo, meta.get("file_name").asString, updatesFolder.apply { if(!exists()) mkdirs() })){ - logger.info("Successfully updated the module ${description.name}") - updatedModules.add(description.name) - } else { - logger.error("Failed to update the module ${description.name}. Please download manually from https://github.com/${description.githubRepository}/releases/latest") - } - } - } else if(isAvailable){ // Notify the user that an update is available - checker.checkWithPrint() - } - } - - // Validate the module name - if (description.name.indexOf(' ') != -1) { - throw InvalidModuleDescriptionException("Module name cannot contain spaces!") - } - - // Save to load later - modules[file] = description - - // Save the dependencies to download the missing ones - description.dependencies.forEach { dependency -> - if (!dependencies.contains(dependency)) { - dependencies.add(dependency) - } - } - } - - if(updatedModules.isNotEmpty()){ - load() - return - } - - // Loop through the dependencies and download the missing ones - val downloadedModules: MutableList = ArrayList() - for (dependencyId in dependencies.filter { it.isNotBlank() && !modules.any { entry -> entry.value.moduleId == it } }) { - val meta = ModuleHelper.getModuleMeta(dependencyId) ?: throw ModuleDownloadException("Failed to download module with id '$dependencyId'") - val repo = if(meta.has("repository")) meta.get("repository").asString else "TheProgramSrc/SimpleCore-${dependencyId}" // Generate default repo if not found - if (ModuleHelper.downloadModule(repo, meta.get("file_name").asString)) { - downloadedModules.add(dependencyId) - } else { - throw ModuleDownloadException("Failed to download module with id '$dependencyId'") - } - } - - // Load the modules again if there are new dependencies - if (downloadedModules.isNotEmpty()) { - load() - return - } - - // Sort the modules to load dependencies first - val moduleDependencies = mutableMapOf>() - modules.values.forEach { - moduleDependencies[it.moduleId] = it.dependencies.toList() - } - - val urlClassLoader = URLClassLoader(modules.map { it.key }.map { it.toURI().toURL() }.toTypedArray(), SimpleCoreAPI::class.java.classLoader) - val sorted = ModuleHelper.sortModuleDependencies(moduleDependencies).filter { it.isNotBlank() } - - sorted.forEach { moduleName -> - if(!loadedModules.contains(moduleName)) { - modules.entries.firstOrNull { it.value.moduleId == moduleName }?.let { entry -> - try { - loadIntoClasspath(urlClassLoader, entry.key, entry.value) - } catch (e: InvalidModuleException) { - e.printStackTrace() - } catch (e: ModuleLoadException) { - e.printStackTrace() - } - } - } - } - logger.info("Successfully loaded ${this.loadedModules.size} modules (${System.currentTimeMillis() - start}ms)") - } - - /** - * Enables the modules by running the [Module.onEnable] method. - */ - fun enableModules(){ - val start = System.currentTimeMillis() - val isStandalone = StandaloneLoader.isRunning - loadedModules.filter { !it.value.isEnabled() }.filter { if(isStandalone) !it.value.getModuleDescription().disableStandalone else true }.forEach { (_, module) -> - val description = module.getModuleDescription() - try { - logger.info("Enabling module ${description.name} v${description.version}") - val enableStart = System.currentTimeMillis() - module.enable() - logger.info("Module ${description.name} v${description.version} enabled! (${System.currentTimeMillis() - enableStart}ms)") - } catch (e: Exception) { - ModuleEnableException("Failed to enable module ${description.name} v${description.version}", e).printStackTrace() // Just print the stack trace to not interfere with the loading process - } - } - logger.info("Successfully enabled ${this.loadedModules.values.filter { it.isEnabled() }.size} modules (${System.currentTimeMillis() - start}ms)") - } - - /** - * Loads a module into the classpath - * @param loader The [URLClassLoader] to load the module into - * @param file The file to load - * @param description The module description - * @throws InvalidModuleException If the main module class is invalid - * @throws ModuleLoadException If the module failed to load - */ - @Throws(InvalidModuleException::class, ModuleLoadException::class) - private fun loadIntoClasspath(loader: URLClassLoader, file: File, description: ModuleDescription) { - try { - JarInputStream(FileInputStream(file)).use { - try { - val mainClass = loader.loadClass(description.mainClass) - if(!Module::class.java.isAssignableFrom(mainClass)){ - throw InvalidModuleException("The class ${description.mainClass} must be extended to the Module class!") - } - logger.info("Loading module ${description.name} v${description.version}") - val start = System.currentTimeMillis() - val moduleClass = mainClass.asSubclass(Module::class.java) - val module = moduleClass.getConstructor().newInstance() - module.init(file, description) // onLoad is automatically called - loadedModules[description.moduleId] = module - logger.info("Module ${description.name} v${description.version} loaded! (${System.currentTimeMillis() - start}ms)") - } catch (e: Exception) { - throw ModuleLoadException("Failed to load module ${description.name} v${description.version}", e) - } - } - } catch (e: Exception) { - throw ModuleLoadException("Failed to load module ${file.name}", e) - } - } - - /** - * Loads (if possible) the module.properties file into a [Properties] object - * @param from The file to load the properties from - * @return The [Properties] object - */ - private fun loadDescription(from: File?): Properties? { - // Search the file 'module.properties' inside the jar file - try { - JarFile(from).use { jarFile -> - val jarEntry = jarFile.getJarEntry("module.properties") - if (jarEntry != null) { - val inputStream = jarFile.getInputStream(jarEntry) - val properties = Properties() - properties.load(inputStream) - return properties - } - } - } catch (e: IOException) { - e.printStackTrace() - } - return null - } - - /** - * Disable all the loaded modules - */ - fun disableModules() { - val start = System.currentTimeMillis() - val iterator = this.loadedModules.iterator() - while(iterator.hasNext()){ - val module = iterator.next().value - val description = module.getModuleDescription() - try { - val disableStart = System.currentTimeMillis() - logger.info("Disabling module ${description.name} v${description.version}") - module.onDisable() - logger.info("Successfully disabled module ${description.name} v${description.version} (${System.currentTimeMillis() - disableStart}ms)") - }catch (e: Exception){ - ModuleLoadException("Failed to disable module ${description.name} v${description.version}", e).printStackTrace() // Just print the stack trace to not interfere with the disabling process - } - iterator.remove() - } - logger.info("Successfully disabled all modules (${System.currentTimeMillis() - start}ms)") - } - - /** - * Updates all the jars placed under the update/ folder - */ - private fun updateJars(){ - updatesFolder.listFiles()?.filter { it.name.endsWith(".jar") }?.filter { loadDescription(it) != null }?.forEach { - val description = loadDescription(it) ?: return@forEach - val name = description.getProperty("name") ?: return@forEach - val version = description.getProperty("version") ?: return@forEach - val outdatedFile = modulesFolder.listFiles()?.filter { jar -> jar.name.endsWith(".jar") }?.firstOrNull { jar -> loadDescription(jar)?.getProperty("name")?.equals(name) ?: false } ?: return@forEach - FileUtils.forceDelete(outdatedFile) - FileUtils.moveFile(it, File(modulesFolder, it.name)) - logger.info("Updated module $name to version v$version") - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/ModuleManager.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/ModuleManager.kt new file mode 100644 index 00000000..eff58e2e --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/ModuleManager.kt @@ -0,0 +1,76 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules + +import com.google.gson.JsonParser +import xyz.theprogramsrc.simplecoreapi.bungee.BungeeLoader +import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI +import xyz.theprogramsrc.simplecoreapi.global.utils.SoftwareType +import xyz.theprogramsrc.simplecoreapi.spigot.SpigotLoader +import xyz.theprogramsrc.simplecoreapi.standalone.StandaloneLoader +import java.io.File +import java.net.URL + +object ModuleManager { + + /** + * Downloads the given module from the GitHub releases. + * + * @param moduleId The module id (format: /) + * @param version The version to download. If null, the latest release will be downloaded + * + * @return The downloaded file + */ + fun downloadModule(moduleId: String, version: String? = null): File? = try { + assert(moduleId.split("/").size == 2) { "Invalid repositoryId format. It should be /"} + + val releaseManifest = URL(if (version == null) "https://api.github.com/repos/$moduleId/releases/latest" else "https://api.github.com/repos/$moduleId/releases/tags/$version" ).let { + JsonParser.parseReader(it.openStream().reader()).asJsonObject + } + + val assets = releaseManifest.get("assets").asJsonArray + // Sort by created_at (newest first) and filter by file name ending with .jar + val asset = assets.sortedByDescending { it.asJsonObject.get("created_at").asString }.firstOrNull { it.asJsonObject.get("name").asString.endsWith(".jar") } ?: throw NullPointerException("No jar file found in the latest release of $moduleId") + val downloadUrl = asset.asJsonObject.get("browser_download_url").asString + val file = File(if(SimpleCoreAPI.let { it.isRunningSoftwareType(SoftwareType.STANDALONE) || it.isRunningSoftwareType(SoftwareType.UNKNOWN) }) SimpleCoreAPI.dataFolder("modules/") else File("plugins/"), asset.asJsonObject.get("name").asString) + if(!file.exists()){ + file.createNewFile() + } + + file.writeBytes(URL(downloadUrl).readBytes()) + file + } catch(e: Exception) { + e.printStackTrace() + null + } + + /** + * Loads the given module file. + * If running in standalone mode the module will be loaded later. + * If running in bukkit/spigot/paper/purpur mode the module will be loaded using the bukkit class loader. + * If running in bungee mode will be loaded using bungee + * + * @param file The module file + */ + fun loadModule(file: File): Boolean = when(SimpleCoreAPI.instance.softwareType) { + SoftwareType.STANDALONE -> { + true // Is automatically loaded later + } + SoftwareType.BUKKIT, + SoftwareType.SPIGOT, + SoftwareType.PAPER, + SoftwareType.PURPUR, -> { + // Load the module using bukkit + SpigotLoader.instance.pluginLoader.loadPlugin(file).apply { + onLoad() + SpigotLoader.instance.pluginLoader.enablePlugin(this@apply) + } + true + } + SoftwareType.BUNGEE -> { + BungeeLoader.instance.proxy.pluginManager.detectPlugins(BungeeLoader.instance.proxy.pluginsFolder) // Try to detect any new plugins. This is needed because the plugin manager is not updated when a plugin is loaded + false + } + else -> { + false + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/SoftwareType.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/SoftwareType.kt index 7f628423..324e49f2 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/SoftwareType.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/SoftwareType.kt @@ -90,5 +90,7 @@ enum class SoftwareType(val check: () -> Boolean = { false }, val display: Strin StandaloneLoader.isRunning }, "Standalone"), - UNKNOWN; + UNKNOWN(check = { + true + }, "Unknown"); } \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/GitHubUpdateChecker.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/GitHubUpdateChecker.kt index f46c0533..2befe509 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/GitHubUpdateChecker.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/GitHubUpdateChecker.kt @@ -8,13 +8,35 @@ import java.time.Instant import java.time.format.DateTimeFormatter /** - * Representation of the GitHub Update Checker - * @param logger The logger to use, it must be an instance of [ILogger] - * @param repo The repository to check. The format should be /, for example TheProgramSrc/SimpleCoreAPI - * @param currentVersion the current version (tag name) of the product + * With this class you can check if there is an update available using the GitHub releases API. + * Sample usage: + * ```kt + * GitHubUpdateChecker(logger, "TheProgramSrc/SimpleCoreAPI", "v0.4.1-SNAPSHOT") + * .checkWithPrint() // This will print the message if there is an update available + * + * // You can also check if there is an update available without printing a message + * val githubUpdateChecker = GitHubUpdateChecker(logger, "TheProgramSrc/SimpleCoreAPI", "v0.4.1-SNAPSHOT") + * if(githubUpdateChecker.checkForUpdates()){ + * // There is an update available + * // Do something here + * // For example: + * logger.info("Please update! Download it now from here: https://example.com/download") + * // Or + * logger.info("Please update! Download it now from here: ${githubUpdateChecker.getReleaseData().get("url").asString}") + * } + * ``` + * + * @param logger The logger to use, it must be an instance of [ILogger]. You can get it using [xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI.instance]. + * @param repo The repository to check. The format should be 'Holder/Repository', for example 'TheProgramSrc/SimpleCoreAPI' + * @param currentVersion the current version (tag name) of the product. (Example: "v0.1.0-SNAPSHOT") * @param latestReleaseTag The tag name of the latest release. (Defaults to "latest") */ -class GitHubUpdateChecker(val logger: ILogger, val repo: String, val currentVersion: String, val latestReleaseTag: String = "latest"): UpdateChecker { +class GitHubUpdateChecker( + val logger: ILogger, + val repo: String, + val currentVersion: String, + val latestReleaseTag: String = "latest" +): UpdateChecker { private var lastCheck = 0L private var lastCheckResult = false diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/SongodaUpdateChecker.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/SongodaUpdateChecker.kt deleted file mode 100644 index ee5e7787..00000000 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/SongodaUpdateChecker.kt +++ /dev/null @@ -1,92 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.utils.update - -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import xyz.theprogramsrc.simplecoreapi.global.utils.ILogger -import java.net.URL -import java.time.Instant -import java.time.format.DateTimeFormatter - -class SongodaUpdateChecker(val logger: ILogger, val productId: String, val currentVersion: String): UpdateChecker { - - private var lastCheck = 0L - private var lastCheckResult = false - private val requestedData = mutableMapOf>() - private val current = if(currentVersion.startsWith("v")) currentVersion else "v$currentVersion" - - /** - * Checks if there is an update available and prints - * a message if there is one asking the end user to - * update the product. - */ - override fun checkWithPrint() { - val latestData = getReleaseData() - val latestVersion = latestData.get("version").asString - if(checkForUpdates()){ - logger.info("Please update (from $current to $latestVersion)! Download it now from here: https://marketplace.songoda.org/marketplace/product/$productId") - } - } - - /** - * Checks if there is an update available - * @return true if there is an update available, false otherwise - */ - override fun checkForUpdates(): Boolean { - val difference = System.currentTimeMillis() - lastCheck - if(difference > 60000 || lastCheck == 0L) { - lastCheckResult = try { - val parser = DateTimeFormatter.ISO_INSTANT - val currentReleasedAt = Instant.from(parser.parse(getReleaseData(currentVersion).get("published_at").asString)) - val latestReleasedAt = Instant.from(parser.parse(getReleaseData().get("published_at").asString)) - currentReleasedAt.isBefore(latestReleasedAt) - }catch (e: Exception) { - e.printStackTrace() - false - } - } - - return lastCheckResult - } - - /** - * Gets the information of a single release - * Object Sample: - * { "published_at": "2022-07-15T21:51:46.397962Z", "version": "v0.4.1-SNAPSHOT", "url": "https://github.com/TheProgramSrc/SimpleCoreAPI/releases/tag/v0.4.1-SNAPSHOT", "author_url": "https://github.com/Im-Fran" } - * - published_at: Is the date when the version was made public. This date must be able to be parsed by Instant#from - * - version: The version of the latest asset - * - url: The url to the version page (null if not available) - * - author_url: The url to the author profile (null if not available) - * - * @param id the name of the release. (If none specified the latest data is fetched. Defaults to "latest") - * @return The information of the given release name - * @since 0.4.1-SNAPSHOT - */ - override fun getReleaseData(id: String): JsonObject { - var cached = requestedData.getOrDefault(id, Pair(JsonObject(), 0L)) - val difference = System.currentTimeMillis() - cached.second - if(difference > 60000 || cached.second == 0L){ - val url = if(id == "latest"){ - "https://marketplace.songoda.com/api/v2/products/id/$productId/versions?sort=-created_at&per_page=1" - } else { - "https://marketplace.songoda.com/api/v2/products/id/$productId/versions?sort=-created_at&per_page=1&filter[version]=$id" - } - - val data = JsonParser.parseString(URL(url).readText()).asJsonObject.getAsJsonArray("data") - if(data.isEmpty){ - throw RuntimeException("We couldn't find any data for the product with id '$productId' and version '$id'. Please try again later.") - } - val json = data.get(0).asJsonObject - - cached = Pair(JsonObject().apply { - addProperty("published_at", DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(json.get("created_at").asLong * 1000L))) - addProperty("version", json.get("version").asString) - addProperty("url", json.get("url").asString) - addProperty("author_url", "https://marketplace.songoda.com/profiles/${json.get("uploaded_by").asJsonObject.get("name").asString}") - }, System.currentTimeMillis()) - requestedData[id] = cached - } - return cached.first - } - -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/SpigotUpdateChecker.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/SpigotUpdateChecker.kt index 7c684529..64308570 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/SpigotUpdateChecker.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/SpigotUpdateChecker.kt @@ -69,7 +69,7 @@ class SpigotUpdateChecker(val logger: ILogger, val resourceId: String, val curre var page = 1 var data: JsonObject? = null while(data == null) { - val versions = JsonParser.parseString(URL("http://api.spiget.org/v2/resources/$resourceId/versions?size=50&page=$page").readText()).asJsonArray + val versions = JsonParser.parseString(URL("https://api.spiget.org/v2/resources/$resourceId/versions?size=50&page=$page").readText()).asJsonArray if(versions.isEmpty) throw RuntimeException("Couldn't find any version for the given id: $id! Make sure you're using a valid version") data = versions.firstOrNull { it.asJsonObject.get("name").asString == id diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/SpigotLoader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/SpigotLoader.kt index 43252e17..d8ffd94f 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/SpigotLoader.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/spigot/SpigotLoader.kt @@ -1,14 +1,25 @@ package xyz.theprogramsrc.simplecoreapi.spigot +import org.bukkit.Bukkit import org.bukkit.plugin.java.JavaPlugin import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI import xyz.theprogramsrc.simplecoreapi.global.utils.logger.JavaLogger +import java.io.File class SpigotLoader: JavaPlugin() { companion object { lateinit var instance: SpigotLoader private set + + /** + * **INTERNAL USE ONLY** + * Loads the given file using bukkit plugin manager. + */ + fun loadFile(file: File) = Bukkit.getPluginManager().loadPlugin(file)?.apply { + onLoad() + Bukkit.getPluginManager().enablePlugin(this@apply) + } } override fun onLoad() { @@ -16,12 +27,4 @@ class SpigotLoader: JavaPlugin() { SimpleCoreAPI(JavaLogger(this.logger)) } - override fun onEnable() { - SimpleCoreAPI.instance.moduleManager?.enableModules() - } - - override fun onDisable() { - SimpleCoreAPI.instance.moduleManager?.disableModules() - } - } \ 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 new file mode 100644 index 00000000..839571c3 --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/EntrypointLoader.kt @@ -0,0 +1,83 @@ +package xyz.theprogramsrc.simplecoreapi.standalone + +import java.util.Properties +import java.util.zip.ZipInputStream + +/** + * Interface that will be used to load the entry point of the app. + */ +interface EntryPoint { + + /** + * Called when the app is loaded + */ + fun onLoad() + + /** + * Called when the app is enabled + */ + fun onEnable() + + /** + * Called when the app is disabled + */ + fun onDisable() +} + +/** + * Class that manages the entry point of the app. It will be in charge of running the onLoad, onEnable and onDisable methods of the class importing [EntryPoint]. + */ +class EntrypointLoader { + companion object { + private var entryPoint: EntryPoint? = null + + /** + * Manually register the entrypoint. + * Currently, this is used for testing purposes, but if you have issues with the entrypoint not being loaded, you can use this method to register it manually. + * + * @param clazz The entrypoint class. It must implement [EntryPoint] + */ + fun registerEntrypoint(clazz: Class) { + entryPoint = clazz.getConstructor().newInstance() as EntryPoint + } + } + private var enabled: Boolean = false + + init { + if(entryPoint == null) { + // First get the resource 'module.properties' located at the root of the jar file + val moduleProperties = EntrypointLoader::class.java.getResourceAsStream("/module.properties") + if(moduleProperties != null) { + // Now read the 'entrypoint' property + val entrypoint = (Properties().let { + it.load(moduleProperties) + it.getProperty("entrypoint") + } ?: "").replace("\"", "") + + assert(entrypoint.isNotBlank()) { "Entrypoint cannot be blank!" } + + // Now load the class + val clazz = this::class.java.classLoader.loadClass(entrypoint) + + // Now check if the class itself is an entrypoint, if it is, initialize it, if not check for the first method that is an entrypoint + if(clazz.isAssignableFrom(EntryPoint::class.java)){ + entryPoint = clazz.getConstructor().newInstance() as EntryPoint + } + } + } + + entryPoint?.onLoad() + } + + fun enable() { + assert(!enabled) { "App already enabled! Please avoid calling this method more than once." } + entryPoint?.onEnable() + enabled = true + } + + fun disable() { + assert(enabled) { "App already disabled! Please avoid calling this method more than once." } + entryPoint?.onDisable() + enabled = false + } +} diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/ModuleLoader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/ModuleLoader.kt new file mode 100644 index 00000000..aae948ab --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/ModuleLoader.kt @@ -0,0 +1,162 @@ +package xyz.theprogramsrc.simplecoreapi.standalone + +import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI +import xyz.theprogramsrc.simplecoreapi.global.modules.ModuleManager +import java.io.File +import java.net.URLClassLoader +import java.util.Properties +import java.util.Stack +import java.util.function.Consumer +import java.util.jar.JarFile +import java.util.jar.JarInputStream + +data class ModuleDescription( + val name: String, + val version: String, + val author: String, + val main: String, + val moduleId: String, + val dependencies: List = emptyList(), +) + +interface Module { + fun onEnable() + + fun onDisable() +} + +class ModuleLoader { + + private val files = mutableMapOf() + private val modulesFolder = SimpleCoreAPI.dataFolder("modules/").apply { + if(!exists()) + mkdirs() + } + + init { + // Check for any requirements in the simplecoreapi.properties entry + val properties = Properties() + // Check if the file exists (in the jar) + val propertiesFile = this.javaClass.classLoader.getResourceAsStream("simplecoreapi.properties") + if(propertiesFile != null) { + properties.load(propertiesFile) + } + + if(properties.getProperty("require") != null) { + // Load all the required modules + properties.getProperty("require").split(",").forEach { repositoryId -> + ModuleManager.downloadModule(repositoryId) + } + } + + // Load all files from the modules folder + fun loadDescriptionFiles() { + (modulesFolder.listFiles() ?: emptyArray()).filter { it.isFile && it.extension == "jar" }.forEach { + // Read the file and check if there's a module.properties file + val jarFile = JarFile(it.absolutePath) + val moduleProperties = jarFile.getJarEntry("module.properties")?.let { entry -> + jarFile.getInputStream(entry).use { stream -> + stream.bufferedReader().use { reader -> + val props = Properties() + props.load(reader) + props + } + } + } ?: return@forEach + if(files.containsKey(it)) return@forEach // Already loaded this file + + // Check if the module.properties file contains the required fields + for (requiredField in arrayOf("name", "version", "author", "main", "module-id")) { + assert(moduleProperties.containsKey(requiredField)) { "Module ${it.nameWithoutExtension} is missing the required field '$requiredField'." } + } + // Add the file to the files map + files[it] = ModuleDescription( + name = moduleProperties.getProperty("name").let { name -> + if(name.startsWith('"') && name.endsWith('"')) name.substring(1, name.length - 1) else name + }, + version = moduleProperties.getProperty("version").let { version -> + if(version.startsWith('"') && version.endsWith('"')) version.substring(1, version.length - 1) else version + }, + author = moduleProperties.getProperty("author").let { author -> + if(author.startsWith('"') && author.endsWith('"')) author.substring(1, author.length - 1) else author + }, + main = moduleProperties.getProperty("main").let { main -> + if(main.startsWith('"') && main.endsWith('"')) main.substring(1, main.length - 1) else main + }, + dependencies = moduleProperties.getProperty("dependencies")?.let { dependencies -> + if(dependencies.startsWith('"') && dependencies.endsWith('"')) dependencies.substring(1, dependencies.length - 1) else dependencies + }?.split(",") ?: emptyList(), + moduleId = moduleProperties.getProperty("module-id").let { moduleId -> + if(moduleId.startsWith('"') && moduleId.endsWith('"')) moduleId.substring(1, moduleId.length - 1) else moduleId + } + ) + } + } + + // Load the description files + loadDescriptionFiles() + + // Now make sure all files have their dependencies + files.values.forEach { description -> + description.dependencies.forEach { dependencyId -> + if(!files.any { it.value.moduleId == dependencyId }) { + ModuleManager.downloadModule(dependencyId) + loadDescriptionFiles() + } + } + } + + // Now we need to order the modules + val visited = mutableSetOf() + val stack = Stack() + + fun topologicalSort(file: File) { + if (visited.contains(file)) return + visited.add(file) + + val description = files[file] ?: return + description.dependencies.forEach { dependency -> + val dependencyFile = files.keys.firstOrNull { it.nameWithoutExtension == dependency } + if (dependencyFile != null) { + topologicalSort(dependencyFile) + } + } + + stack.push(file) + } + + files.keys.forEach { topologicalSort(it) } + + val loadedModules = mutableListOf() + val loader = URLClassLoader(stack.map { it.toURI().toURL() }.toTypedArray(), this.javaClass.classLoader) + + // Finally we need to load the modules by loading the jar files into the classpath and then one by one calling their main class #onEnable method + for (file in stack) { + val description = files[file] ?: continue + // Load the main class + JarInputStream(file.inputStream()).use { + try { + val mainClass = loader.loadClass(description.main) + if(mainClass.interfaces.contains(Module::class.java)) { + // Load module + val instance = mainClass.getDeclaredConstructor().newInstance() as Module + instance.onEnable() + loadedModules.add(instance) + } + }catch (e: Exception) { + e.printStackTrace() + } + } + } + + // Finally we need to add a shutdown hook to disable all modules + Runtime.getRuntime().addShutdownHook(Thread { + loadedModules.forEach { it.onDisable() } + }.apply { + name = "SimpleCoreAPI Shutdown Hook" + }) + + // And we're done! + println("Loaded ${loadedModules.size} modules!") + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/StandaloneLoader.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/StandaloneLoader.kt index 61d3b90a..8b08b86f 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/StandaloneLoader.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/standalone/StandaloneLoader.kt @@ -21,13 +21,19 @@ class StandaloneLoader { init { instance = this isRunning = true - SimpleCoreAPI(JavaLogger(Logger.getAnonymousLogger())) - SimpleCoreAPI.instance.moduleManager?.enableModules() + val simpleCoreAPI = SimpleCoreAPI(JavaLogger(Logger.getAnonymousLogger())) + val entrypoint = simpleCoreAPI.measureLoad("Loaded entrypoint") { + EntrypointLoader() + } + + simpleCoreAPI.measureLoad("Loaded modules") { + ModuleLoader() // Load modules + } + + entrypoint.enable() Runtime.getRuntime().addShutdownHook(Thread { - SimpleCoreAPI.instance.moduleManager?.disableModules() - }.apply { - name = "SimpleCoreAPI Shutdown Hook" + entrypoint.disable() }) } } \ No newline at end of file diff --git a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelperTest.kt b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelperTest.kt deleted file mode 100644 index a86f7043..00000000 --- a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelperTest.kt +++ /dev/null @@ -1,41 +0,0 @@ -package xyz.theprogramsrc.simplecoreapi.global.module - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test -import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI - -internal class ModuleHelperTest { - - @Test - fun downloadModule() { - assertTrue(ModuleHelper.downloadModule("TheProgramSrc/SimpleCore-TasksModule", "TasksModule")) // Test the download - assertTrue(SimpleCoreAPI.dataFolder("modules/TasksModule.jar").exists()) - SimpleCoreAPI.dataFolder().deleteRecursively() // Recursively delete the plugins folder - } - - @Test - fun sortModules(){ - val dependencies = mutableMapOf>( - "configmodule" to listOf("filesmodule"), - "loggingmodule" to listOf("configmodule"), - "filesmodule" to emptyList(), - "translationsmodule" to listOf("loggingmodule", "filesmodule"), - "uismodule" to listOf("tasksmodule", "translationsmodule"), - "tasksmodule" to emptyList(), - ) - - val expectedOrder = listOf( - "filesmodule", - "configmodule", - "loggingmodule", - "translationsmodule", - "tasksmodule", - "uismodule", - ) - - - - assertEquals(expectedOrder, ModuleHelper.sortModuleDependencies(dependencies)) - } -} \ No newline at end of file diff --git a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/ModuleDownloaderTest.kt b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/ModuleDownloaderTest.kt new file mode 100644 index 00000000..5ab2b66c --- /dev/null +++ b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/ModuleDownloaderTest.kt @@ -0,0 +1,35 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules + +import org.apache.commons.io.FileUtils +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import xyz.theprogramsrc.simplecoreapi.standalone.StandaloneLoader +import java.io.File +import java.security.MessageDigest + +internal class ModuleDownloaderTest { + + @Test + fun `Test Files Module v0_2_1 Download`() { + val file = ModuleManager.downloadModule("TheProgramSrc/SimpleCore-FilesModule", "v0.2.1-SNAPSHOT") ?: fail("Failed to download module") + val digest = MessageDigest.getInstance("MD5") + val md5 = digest.digest(file.readBytes()).joinToString("") { "%02x".format(it) } + assertEquals("7efd5870362ece150ba2e63cfbd4847a", md5) + } + + companion object { + @BeforeAll + @JvmStatic + fun setUp() { + StandaloneLoader() + } + + @AfterAll + @JvmStatic + fun tearDown() { + arrayOf("SimpleCoreAPI/", "plugins/").map { File(it) }.filter{ it.exists() }.forEach { FileUtils.forceDelete(it) } + } + } +} \ No newline at end of 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 new file mode 100644 index 00000000..bb91a93e --- /dev/null +++ b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/modules/ModuleInteroperabilityTest.kt @@ -0,0 +1,48 @@ +package xyz.theprogramsrc.simplecoreapi.global.modules + +import org.apache.commons.io.FileUtils +import org.junit.jupiter.api.AfterAll +import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI +import xyz.theprogramsrc.simplecoreapi.standalone.EntryPoint +import xyz.theprogramsrc.simplecoreapi.standalone.EntrypointLoader +import xyz.theprogramsrc.simplecoreapi.standalone.StandaloneLoader +import java.io.File + + +// This will test if modules are able to call methods from other modules. +internal class ModuleInteroperabilityTest { + + //@Test + fun `Test if module can call methods from other modules`() { + // First we register the entrypoint + EntrypointLoader.registerEntrypoint(MockApp::class.java) + + // Start the standalone loader. + StandaloneLoader() + } + + companion object { + + @AfterAll + @JvmStatic + fun tearDown() { + arrayOf("SimpleCoreAPI/", "plugins/").map { File(it) }.filter{ it.exists() }.forEach { FileUtils.forceDelete(it) } + } + } +} + +class MockApp: EntryPoint { + + override fun onLoad() { + println("onLoad") + SimpleCoreAPI.requireModule("TheProgramSrc/SimpleCore-TranslationsModule") // Require the TranslationsModule + } + + override fun onEnable() { + println("onEnable") + } + + override fun onDisable() { + println("onDisable") + } +} \ No newline at end of file diff --git a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/SoftwareTypeTest.kt b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/SoftwareTypeTest.kt index d6b0d8ac..e59987f7 100644 --- a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/SoftwareTypeTest.kt +++ b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/SoftwareTypeTest.kt @@ -1,17 +1,32 @@ package xyz.theprogramsrc.simplecoreapi.global.utils -import org.junit.jupiter.api.Assertions.* +import org.apache.commons.io.FileUtils +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI import xyz.theprogramsrc.simplecoreapi.standalone.StandaloneLoader +import java.io.File internal class SoftwareTypeTest { @Test - fun TestStandaloneSoftwareType() { - StandaloneLoader() - + fun `Test SoftwareType detects Standalone mode`() { assertEquals(SoftwareType.STANDALONE, SimpleCoreAPI.instance.softwareType) } + companion object { + @BeforeAll + @JvmStatic + fun setUp() { + StandaloneLoader() + } + + @AfterAll + @JvmStatic + fun tearDown() { + arrayOf("SimpleCoreAPI/", "plugins/").map { File(it) }.filter{ it.exists() }.forEach { FileUtils.forceDelete(it) } + } + } } \ No newline at end of file diff --git a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/SpigotUpdateChecker.kt b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/SpigotUpdateChecker.kt index 85d62a48..cfcd69d7 100644 --- a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/SpigotUpdateChecker.kt +++ b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/utils/update/SpigotUpdateChecker.kt @@ -1,13 +1,19 @@ package xyz.theprogramsrc.simplecoreapi.global.utils.update -import org.junit.jupiter.api.Assertions.* +import com.google.gson.JsonParser +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test -import xyz.theprogramsrc.simplecoreapi.global.utils.logger.* +import xyz.theprogramsrc.simplecoreapi.global.utils.logger.JavaLogger +import xyz.theprogramsrc.simplecoreapi.global.utils.logger.SLF4JLogger +import java.net.URL import java.util.logging.Logger internal class SpigotUpdateCheckerTest { - private val check1 = SpigotUpdateChecker(JavaLogger(Logger.getLogger("SpigotUpdateCheckerTest - 1")), "77825", "3.18.1") + private val check1 = SpigotUpdateChecker(JavaLogger(Logger.getLogger("SpigotUpdateCheckerTest - 1")), "77825", URL("https://api.spigotmc.org/simple/0.1/index.php?action=getResource&id=77825").let { + JsonParser.parseString(it.readText()).asJsonObject.get("current_version").asString.replace("v", "") + }) private val check2 = SpigotUpdateChecker(SLF4JLogger(org.slf4j.LoggerFactory.getLogger("SpigotUpdateCheckerTest - 2")), "77825", "3.18.0") @Test