diff --git a/CHANGELOG.md b/CHANGELOG.md index 5064d4db..8a652310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v0.4.2 - Snapshot +* Updated dependencies +* Moving to jitpack + ## v0.4.1 - Snapshot * Added custom blossom injector dependency * Updated dependencies diff --git a/README.md b/README.md index 7135a69f..f2bb8b06 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ +[![](https://jitci.com/gh/TheProgramSrc/SimpleCoreAPI/svg)](https://jitci.com/gh/TheProgramSrc/SimpleCoreAPI) +[![](https://jitpack.io/v/TheProgramSrc/SimpleCoreAPI.svg)](https://jitpack.io/#TheProgramSrc/SimpleCoreAPI) + # SimpleCoreAPI _The best way to create a plugin_
-[![Latest Release](https://img.shields.io/nexus/r/xyz.theprogramsrc/simplecoreapi?color=%2300ff00&label=Latest%20Release&nexusVersion=3&server=https%3A%2F%2Frepo.theprogramsrc.xyz)](https://repo.theprogramsrc.xyz/#browse/browse:maven-releases) -[![Latest Snapshot](https://img.shields.io/badge/dynamic/xml?url=https://repo.theprogramsrc.xyz/repository/maven-snapshots/xyz/theprogramsrc/simplecoreapi/maven-metadata.xml&label=Latest%20Snapshot&color=orange&query=.//versioning/latest&prefix=v)](https://repo.theprogramsrc.xyz/#browse/browse:maven-snapshots) +
[![Discord](https://i.imgur.com/J1XhmMd.png)](https://go.theprogramsrc.xyz/discord) [![Terms of Service](https://i.imgur.com/4tFAGtE.png)](https://go.theprogramsrc.xyz/tos) diff --git a/build.gradle b/build.gradle index 6f5edb61..db78c314 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { id 'org.jetbrains.dokka' version '1.7.10' } -def projectVersion = (System.getenv("VERSION") ?: '0.4.1-SNAPSHOT').replaceFirst("v", "").replace('/', '') +def projectVersion = (System.getenv("VERSION") ?: '0.4.2-SNAPSHOT').replaceFirst("v", "").replace('/', '') group 'xyz.theprogramsrc' version projectVersion @@ -15,7 +15,6 @@ description 'The best way to create a plugin' repositories { mavenCentral() - maven { url 'https://repo.theprogramsrc.xyz/repository/maven-public/' } maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } maven { url 'https://oss.sonatype.org/content/repositories/releases/' } maven { url 'https://oss.sonatype.org/content/groups/public/' } @@ -34,7 +33,7 @@ dependencies { implementation 'org.jetbrains:annotations:23.0.0' implementation 'commons-io:commons-io:2.11.0' implementation 'com.google.code.gson:gson:2.9.1' - implementation 'net.lingala.zip4j:zip4j:2.11.1' + implementation 'net.lingala.zip4j:zip4j:2.11.2' annotationProcessor 'com.velocitypowered:velocity-api:3.1.1' @@ -101,12 +100,6 @@ tasks.named("dokkaHtml") { publishing { repositories { if(System.getenv('env') == 'prod') { - maven { - name = 'TheProgramSrc' - credentials.username = System.getenv('NEXUS_USERNAME') - credentials.password = System.getenv('NEXUS_PASSWORD') - url = uri(version.contains('-SNAPSHOT') ? 'https://repo.theprogramsrc.xyz/repository/maven-snapshots/' : 'https://repo.theprogramsrc.xyz/repository/maven-releases/') - } maven { name = 'GitHubPackages' url = 'https://maven.pkg.github.com/TheProgramSrc/SimpleCoreAPI' diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelper.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelper.kt index 17609be9..25dcec18 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelper.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelper.kt @@ -1,12 +1,11 @@ package xyz.theprogramsrc.simplecoreapi.global.module +import com.google.gson.JsonObject import com.google.gson.JsonParser import java.io.BufferedReader import java.io.File import java.io.InputStreamReader import java.net.URL -import java.security.MessageDigest -import java.util.* import java.util.jar.JarFile /** @@ -14,51 +13,31 @@ import java.util.jar.JarFile */ object ModuleHelper { + private var lastRepoUpdate = 0L // Used to avoid timeouts + /** * Downloads a Module from the database - * @param repositoryId Identifier of the artifact inside the repository + * @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 plugins/SimpleCoreAPI/modules/) * @return true if the module was downloaded, false otherwise */ - fun downloadModule(repositoryId: String, downloadLocation: File = File("plugins/SimpleCoreAPI/modules/")): Boolean{ + fun downloadModule(repository: String, fileName: String, downloadLocation: File = File("plugins/SimpleCoreAPI/modules/")): Boolean{ if(!downloadLocation.exists()) downloadLocation.mkdirs() - validateRepositories() - val repo = (JsonParser.parseString(File("plugins/SimpleCoreAPI/repositories.json").readText()).asJsonArray.firstOrNull { element -> - JsonParser.parseString(URL("https://${parseHost(element.asJsonObject.get("url").asString)}/service/rest/v1/search?repository=${element.asJsonObject.get("repo").asString}&format=maven2&maven.artifactId=$repositoryId&maven.extension=jar&sort=version").readText()).asJsonObject.get("items").asJsonArray.size() > 0 - } ?: return false).asJsonObject - val artifact = ((JsonParser.parseString(URL("https://${parseHost(repo.get("url").asString)}/service/rest/v1/search/?repository=${repo.get("repo").asString}&format=maven2&maven.artifactId=$repositoryId&maven.extension=jar&sort=version").readText()).asJsonObject.get("items").asJsonArray.firstOrNull { it.asJsonObject.get("repository").asString.equals(repo.get("repo").asString) } ?: return false).asJsonObject.get("assets").asJsonArray.firstOrNull { - if(!it.asJsonObject.get("maven2").asJsonObject.get("extension").asString.equals("jar")){ - return@firstOrNull false // Check that this is a jar file (not checksums or pom) - } - - if(it.asJsonObject.get("maven2").asJsonObject.has("classifier")){ - if(it.asJsonObject.get("maven2").asJsonObject.get("classifier").asString.equals("sources")){ - return@firstOrNull false // Check that this is not a sources jar file - } - - if(it.asJsonObject.get("maven2").asJsonObject.get("classifier").asString.equals("javadoc")){ - return@firstOrNull false // Check that this is not a javadoc jar file - } - } - - if(!it.asJsonObject.get("maven2").asJsonObject.get("artifactId").asString.equals(repositoryId)) { - return@firstOrNull false // Check that this is the correct artifact - } - - if(!it.asJsonObject.get("contentType").asString.equals("application/java-archive")){ - return@firstOrNull false // Check that this is a jar file - } - - return@firstOrNull true - } ?: return false).asJsonObject - return artifact.get("downloadUrl").asString.let { - val bytes = URL(it).readBytes() - val sha512 = MessageDigest.getInstance("SHA-512").digest(bytes) - val file = File(downloadLocation, "$repositoryId.jar") - file.writeBytes(bytes) - val fileSha512 = MessageDigest.getInstance("SHA-512").digest(file.readBytes()) - Arrays.equals(sha512, fileSha512) + 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! } /** @@ -87,26 +66,9 @@ object ModuleHelper { } /** - * Ensures that the repositories are up-to-date - */ - private fun validateRepositories(){ - val file = File("plugins/SimpleCoreAPI/repositories.json") - val onlineBytes = URL("https://raw.githubusercontent.com/TheProgramSrc/PluginsResources/master/SimpleCoreAPI/repositories.json").readBytes() - if(!file.exists()) file.createNewFile() - file.writeBytes(onlineBytes) // Always overwrite the file - } - - /** - * Parses the host from a URL - * @param url URL to parse - * @return Host of the URL - */ - private fun parseHost(url: String): String = if(url.startsWith("http")) url.split("://")[1].split("/")[0] else url.split("/")[0] - - /** - * Scans the given folder for jar files and then scan - * every jar file to download the required modules - */ + * 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) @@ -115,6 +77,35 @@ object ModuleHelper { } } + /** + * 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 = File("plugins/SimpleCoreAPI/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(File("plugins/SimpleCoreAPI/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 @@ -122,17 +113,22 @@ object ModuleHelper { * @param downloadLocation Location to download the modules. (Defaults to plugins/SimpleCoreAPI/modules/) */ fun downloadRequiredModules(file: File, downloadLocation: File = File("plugins/SimpleCoreAPI/modules/")){ + updateRepository() // First we update the repository if(file.extension != "jar") return try { - JarFile(file).use { jarFile -> - val jarEntry = jarFile.getJarEntry("simplecoreapi.modules") + 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) - val reader = BufferedReader(InputStreamReader(inputStream)) - reader.readLines().forEach { - if(it.isNotBlank() && it.isNotEmpty() && !it.startsWith("#")) { - if(!File(downloadLocation, "$it.jar").exists()){ - downloadModule(it) + 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 + } } } } diff --git a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleManager.kt b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleManager.kt index fd2499fa..f2982334 100644 --- a/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleManager.kt +++ b/src/main/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleManager.kt @@ -133,11 +133,17 @@ class ModuleManager(private val logger: ILogger) { 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...") - if(ModuleHelper.downloadModule(description.repositoryId, File("plugins/SimpleCoreAPI/update/").apply { if(!exists()) mkdirs() })){ - logger.info("Successfully updated the module ${description.name}") - updatedModules.add(description.name) + 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."}") } else { - logger.error("Failed to update the module ${description.name}. Please download manually from https://github.com/${description.githubRepository}/releases/latest") + 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") + } } } else if(isAvailable){ // Notify the user that an update is available checker.checkWithPrint() @@ -171,7 +177,9 @@ class ModuleManager(private val logger: ILogger) { // 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.repositoryId == it } }) { - if (ModuleHelper.downloadModule(dependencyId)) { + 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'") diff --git a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelperTest.kt b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelperTest.kt index c8abff19..ef15f9ff 100644 --- a/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelperTest.kt +++ b/src/test/kotlin/xyz/theprogramsrc/simplecoreapi/global/module/ModuleHelperTest.kt @@ -9,7 +9,8 @@ internal class ModuleHelperTest { @Test fun downloadModule() { - assertTrue(ModuleHelper.downloadModule("loggingmodule")) // Test the download + assertTrue(ModuleHelper.downloadModule("TheProgramSrc/SimpleCore-TasksModule", "TasksModule")) // Test the download + assertTrue(File("plugins/SimpleCoreAPI/modules/TasksModule.jar").exists()) File("plugins/").deleteRecursively() // Recursively delete the plugins folder }