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