Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Standalone Mode & Improvements #84

Merged
merged 3 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 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
Expand Down
3 changes: 1 addition & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ plugins {
}

val env = System.getenv()
val projectVersion = env["VERSION"] ?: "0.5.0-SNAPSHOT"
val projectVersion = env["VERSION"] ?: "0.6.0-SNAPSHOT"

group = "xyz.theprogramsrc"
version = projectVersion.replaceFirst("v", "").replace("/", "")
Expand Down Expand Up @@ -48,7 +48,6 @@ dependencies {
}

blossom {
System.getenv()
replaceToken("@name@", rootProject.name)
replaceToken("@version@", project.version.toString())
replaceToken("@description@", project.description)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,22 @@ package xyz.theprogramsrc.simplecoreapi.global.module
* @param version The version of the module
* @param author The author of the module
* @param description The description of the module
* @param repositoryId The identifier of this artifact in the repository (usually the name in lowercase)
* @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 repositoryId: String, val dependencies: Array<String>, val githubRepository: String) {
data class ModuleDescription(
val mainClass: String,
val name: String,
val version: String,
val author: String,
val description: String,
val moduleId: String,
val dependencies: Array<String>,
val githubRepository: String,
val disableStandalone: Boolean = false,
) {

override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand All @@ -24,9 +35,10 @@ data class ModuleDescription(val mainClass: String, val name: String, val versio
if (version != other.version) return false
if (author != other.author) return false
if (description != other.description) return false
if (repositoryId != other.repositoryId) 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
}
Expand All @@ -37,9 +49,10 @@ data class ModuleDescription(val mainClass: String, val name: String, val versio
result = 31 * result + version.hashCode()
result = 31 * result + author.hashCode()
result = 31 * result + description.hashCode()
result = 31 * result + repositoryId.hashCode()
result = 31 * result + moduleId.hashCode()
result = 31 * result + dependencies.contentHashCode()
result = 31 * result + githubRepository.hashCode()
result = 31 * result + disableStandalone.hashCode()
return result
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.apache.commons.io.FileUtils
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
Expand Down Expand Up @@ -98,74 +99,79 @@ class ModuleManager(private val logger: ILogger) {

// Now we load and save all the module descriptions from the available modules
val updatedModules = mutableListOf<String>()
files.forEach { file ->
files.map { 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", "repository-id")
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
val description = ModuleDescription(
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("repository-id").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"
)

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.repositoryId)
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."}")
} catch (e: Exception) {
e.printStackTrace()
null
}
}.filterNotNull().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, File("plugins/SimpleCoreAPI/update/").apply { if(!exists()) mkdirs() })){
logger.info("Successfully updated the module ${description.name}")
updatedModules.add(description.name)
} else {
val repo = if(meta.has("repository")) meta.get("repository").asString else "TheProgramSrc/SimpleCore-${description.repositoryId}" // Generate default repo if not found
if(ModuleHelper.downloadModule(repo, meta.get("file_name").asString, File("plugins/SimpleCoreAPI/update/").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")
}
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()
}
} 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!")
}
// Validate the module name
if (description.name.indexOf(' ') != -1) {
throw InvalidModuleDescriptionException("Module name cannot contain spaces!")
}

// Save to load later
modules[file] = description
// 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)
}
// Save the dependencies to download the missing ones
description.dependencies.forEach { dependency ->
if (!dependencies.contains(dependency)) {
dependencies.add(dependency)
}
} catch (e: Exception) {
e.printStackTrace()
}
}

Expand All @@ -176,7 +182,7 @@ class ModuleManager(private val logger: ILogger) {

// Loop through the dependencies and download the missing ones
val downloadedModules: MutableList<String> = ArrayList()
for (dependencyId in dependencies.filter { it.isNotBlank() && !modules.any { entry -> entry.value.repositoryId == it } }) {
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)) {
Expand All @@ -195,15 +201,15 @@ class ModuleManager(private val logger: ILogger) {
// Sort the modules to load dependencies first
val moduleDependencies = mutableMapOf<String, Collection<String>>()
modules.values.forEach {
moduleDependencies[it.repositoryId] = it.dependencies.toList()
moduleDependencies[it.moduleId] = it.dependencies.toList()
}

val urlClassLoader = URLClassLoader(modules.map { it.key }.map { it.toURI().toURL() }.toTypedArray(), this::class.java.classLoader)
val sorted = ModuleHelper.sortModuleDependencies(moduleDependencies).filter { it.isNotBlank() }

sorted.forEach { moduleName ->
if(!loadedModules.contains(moduleName)) {
modules.entries.firstOrNull { it.value.repositoryId == moduleName }?.let { entry ->
modules.entries.firstOrNull { it.value.moduleId == moduleName }?.let { entry ->
try {
loadIntoClasspath(urlClassLoader, entry.key, entry.value)
} catch (e: InvalidModuleException) {
Expand All @@ -222,7 +228,8 @@ class ModuleManager(private val logger: ILogger) {
*/
fun enableModules(){
val start = System.currentTimeMillis()
loadedModules.filter { !it.value.isEnabled() }.forEach { (_, module) ->
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}")
Expand Down Expand Up @@ -259,7 +266,7 @@ class ModuleManager(private val logger: ILogger) {
val moduleClass = mainClass.asSubclass(Module::class.java)
val module = moduleClass.getConstructor().newInstance()
module.init(file, description) // onLoad is automatically called
loadedModules[description.repositoryId] = module
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)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package xyz.theprogramsrc.simplecoreapi.global.utils

import xyz.theprogramsrc.simplecoreapi.standalone.StandaloneLoader
import java.util.Objects

/**
Expand Down Expand Up @@ -77,12 +78,17 @@ enum class SoftwareType(val check: () -> Boolean = { false }, val display: Strin

VELOCITY(check = {
try {
Class.forName("com.velocitypowered.api.util.ProxyVersion")
Class.forName("com.velocitypowered.proxy.VelocityServer")
true
} catch(e: Exception) {
false
}
}, "Velocity"),

// Standalone
STANDALONE(check = {
StandaloneLoader.isRunning
}, "Standalone"),

UNKNOWN;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package xyz.theprogramsrc.simplecoreapi.standalone

import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI
import xyz.theprogramsrc.simplecoreapi.global.utils.logger.JavaLogger
import java.util.logging.Logger

class StandaloneLoader {

companion object {
lateinit var instance: StandaloneLoader
private set

var isRunning = false
private set
}

init {
instance = this
isRunning = true
SimpleCoreAPI(JavaLogger(Logger.getAnonymousLogger()))
SimpleCoreAPI.instance.moduleManager?.enableModules()

Runtime.getRuntime().addShutdownHook(Thread {
SimpleCoreAPI.instance.moduleManager?.disableModules()
}.apply {
name = "SimpleCoreAPI Shutdown Hook"
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package xyz.theprogramsrc.simplecoreapi.global.utils

import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI
import xyz.theprogramsrc.simplecoreapi.standalone.StandaloneLoader

internal class SoftwareTypeTest {

@Test
fun TestStandaloneSoftwareType() {
StandaloneLoader()

assertEquals(SoftwareType.STANDALONE, SimpleCoreAPI.instance.softwareType)
}

}