diff --git a/src/main/resources/config.toml b/devconfig.yml
similarity index 50%
rename from src/main/resources/config.toml
rename to devconfig.yml
index 24a572d7..4462cd7a 100644
--- a/src/main/resources/config.toml
+++ b/devconfig.yml
@@ -1,26 +1,38 @@
-# GeyserUpdater
+# GeyserUpdater configuration with more settings enabled
# Made by Jens & YHDiamond
# NOTICE: Please read the README on our github page for full information regarding these options!
# https://github.com/ProjectG-Plugins/GeyserUpdater
-# If enabled, GeyserUpdater will check for new Geyser builds on server start, and on the interval specified by Auto-Update-Interval. If a new build exists, it will be downloaded.
-Auto-Update-Geyser=false
+default-updates:
+ geyser:
+ enable: true
+ auto-check: true
+ auto-update: true
+ floodgate:
+ enable: false
+ auto-check: true
+ auto-update: false
+
# The interval in hours between each auto update check.
-Auto-Update-Interval=24
+auto-update-interval: 24
+# The maximum amount of seconds that a download is allowed to run for. Increase if you are running into issues on a slow internet connection.
+download-time-limit: 300
+# Delete the downloaded file if the file hash of the downloaded file did not match what the download server provided.
+# Or if the file hash was not checked and the download time limit was reached, or an exception occurred.
+# If the file hash is not correct the downloaded file is likely corrupt or unfinished.
+delete-on-fail: true
# If enabled, GeyserUpdater will attempt to restart the server 10 seconds after a new version of Geyser has been successfully downloaded.
# If you aren't using a hosting provider or a server wrapper, you will need a restart script.
-Auto-Restart-Server=false
+auto-restart-server: true
# When enabled, GeyserUpdater will automatically generate a restart script for you. If you are using CraftBukkit or a proxy
# you will need to use the generated script to start your server! If you are using a hosting provider or a server wrapper you probably don't need this.
-Auto-Script-Generating=false
-
+auto-script-generating: true
# Configure the message that is sent to all online players warning them that the server will be restarting in 10 seconds.
-Restart-Message-Players='&2This server will be restarting in 10 seconds!'
+restart-message-players: "§2This server will be restarting in 10 seconds!"
# Enable debug logging
-Enable-Debug=false
-
+enable-debug: true
# Please do not change this version value!
-Config-Version=2
\ No newline at end of file
+config-version: 3
diff --git a/guDeploy.sh b/guDeploy.sh
index 19ffe375..f6f20b2e 100644
--- a/guDeploy.sh
+++ b/guDeploy.sh
@@ -14,14 +14,14 @@ waterDir="Waterfall-guDeploy"
velocityDir="Velocity-guDeploy"
#Links
-guLink="https://ci.projectg.dev/job/GeyserUpdater/job/1.5.0/lastSuccessfulBuild/artifact/target/GeyserUpdater-1.5.0.jar"
-geyserLink="https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/master/720/artifact/bootstrap/"
+guLink="https://ci.projectg.dev/job/GeyserUpdater/job/1.6.0/lastSuccessfulBuild/artifact/target/GeyserUpdater-1.6.0.jar"
+geyserLink="https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/master/821/artifact/bootstrap/"
buildToolsLink="https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar"
-paperLink="https://papermc.io/api/v2/projects/paper/versions/1.16.5/builds/750/downloads/paper-1.16.5-750.jar"
+paperLink="https://papermc.io/api/v2/projects/paper/versions/1.17.1/builds/209/downloads/paper-1.17.1-209.jar"
bungeeLink="https://ci.md-5.net/job/BungeeCord/lastSuccessfulBuild/artifact/bootstrap/target/BungeeCord.jar"
-waterLink="https://papermc.io/api/v2/projects/waterfall/versions/1.16/builds/425/downloads/waterfall-1.16-425.jar"
-velocityLink="https://versions.velocitypowered.com/download/1.1.5.jar"
+waterLink="https://papermc.io/api/v2/projects/waterfall/versions/1.17/builds/448/downloads/waterfall-1.17-448.jar"
+velocityLink="https://versions.velocitypowered.com/download/3.0.0.jar"
echo "[WARN] This script can generate up to 500MB of data!"
@@ -40,12 +40,20 @@ else
fi
# Download file for a given link
+# first arg is link, second arg is output file name (optional)
download () {
- jarURL="$1"
if [[ "$downloadCmd" == "curl" ]]; then
- curl "$jarURL" -O
+ if [[ -z "$2" ]]; then
+ curl "$1" -O
+ else
+ curl "$1" --output "$2"
+ fi
else
- wget "$jarURL"
+ if [[ -z "$2" ]]; then
+ wget "$1"
+ else
+ curl "$1" --output-document "$2"
+ fi
fi
}
@@ -57,8 +65,14 @@ getAllPlugins () {
mkdir "$pluginCache"
cd "$pluginCache" || exit
- mkdir Common
- cd Common || exit
+ mkdir "Common"
+ cd "Common" || exit
+ mkdir "GeyserUpdater"
+ cd "GeyserUpdater" || exit
+ echo
+ echo "[INFO] Downloading GeyserUpdater config"
+ download "https://raw.githubusercontent.com/ProjectG-Plugins/GeyserUpdater/1.6.0/devconfig.yml" "config.yml"
+ cd ../
echo
echo "[INFO] Downloading GeyserUpdater"
download "$guLink"
@@ -66,31 +80,13 @@ getAllPlugins () {
mkdir "Spigot"
cd "Spigot" || exit
- mkdir "GeyserUpdater"
- cd "GeyserUpdater" || exit
- echo "Auto-Update-Geyser: true
-Auto-Restart-Server: true
-Restart-Message-Players: '&2This server will be restarting in 10 seconds!'
-Auto-Script-Generating: true
-Enable-Debug: true
-Config-Version: 2" > config.yml
- cd ../
echo
echo "[INFO] Downloading Geyser-Spigot.jar"
download "$geyserLink"spigot/target/Geyser-Spigot.jar
cd ../
- mkdir BungeeCord
- cd BungeeCord || exit
- mkdir GeyserUpdater
- cd GeyserUpdater || exit
- echo "Auto-Update-Geyser: true
-Auto-Restart-Server: true
-Restart-Message-Players: '&2This server will be restarting in 10 seconds!'
-Auto-Script-Generating: true
-Enable-Debug: true
-Config-Version: 2" > config.yml
- cd ../
+ mkdir "BungeeCord"
+ cd "BungeeCord" || exit
echo
echo "[INFO] Downloading Geyser-BungeeCord.jar"
download "$geyserLink"bungeecord/target/Geyser-BungeeCord.jar
@@ -98,15 +94,6 @@ Config-Version: 2" > config.yml
mkdir "Velocity"
cd "Velocity" || exit
- mkdir "geyserupdater"
- cd "geyserupdater" || exit
- echo "Auto-Update-Geyser=true
-Auto-Restart-Server=true
-Restart-Message-Players='&2This server will be restarting in 10 seconds!'
-Auto-Script-Generating=true
-Enable-Debug=true
-Config-Version=2" > config.toml
- cd ../
echo
echo "[INFO] Downloading Geyser-Velocity"
download "$geyserLink"velocity/target/Geyser-Velocity.jar
diff --git a/pom.xml b/pom.xml
index bd451a3a..745c0cf7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,90 +4,78 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- com.projectg
+ dev.projectg
GeyserUpdater
GeyserUpdater
- 1.5.0
+ 1.6.0
UTF-8
UTF-8
1.8
1.8
+ 2.10.2
-
- bungeecord-repo
- https://oss.sonatype.org/content/repositories/snapshots
-
-
- velocity
- https://nexus.velocitypowered.com/repository/maven-public/
-
spigot-repo
https://hub.spigotmc.org/nexus/content/repositories/snapshots/
- opencollab-release-repo
- https://repo.opencollab.dev/maven-releases/
-
- true
-
-
- false
-
+ jitpack.io
+ https://jitpack.io
- opencollab-snapshot-repo
- https://repo.opencollab.dev/maven-snapshots/
-
- false
-
-
- true
-
+ velocity
+ https://nexus.velocitypowered.com/repository/maven-public/
- net.md-5
- bungeecord-api
- 1.16-R0.5-SNAPSHOT
- jar
+ org.spigotmc
+ spigot-api
+ 1.12.2-R0.1-SNAPSHOT
provided
- net.md-5
- bungeecord-api
- 1.16-R0.5-SNAPSHOT
- javadoc
+ com.github.SpigotMC.BungeeCord
+ bungeecord-proxy
+
+ a7c6ede
provided
com.velocitypowered
velocity-api
- 1.1.8
+ 3.0.0
provided
+
org.apache.logging.log4j
log4j-core
2.13.2
provided
+
- org.bukkit
- bukkit
- 1.8-R0.1-SNAPSHOT
- provided
+ org.spongepowered
+ configurate-yaml
+ 4.1.2
+
+
+
+ com.google.code.findbugs
+ jsr305
+ 3.0.2
+
- org.geysermc
- connector
- 1.4.0-SNAPSHOT
+ org.projectlombok
+ lombok
+ 1.18.22
provided
@@ -109,6 +97,27 @@
1.8
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+
+ package
+
+ shade
+
+
+
+
+ org.spongepowered.configurate
+ dev.projectg.geyserupdater.shaded.configurate
+
+
+
+
+
+
diff --git a/src/main/java/com/projectg/geyserupdater/bungee/BungeeUpdater.java b/src/main/java/com/projectg/geyserupdater/bungee/BungeeUpdater.java
deleted file mode 100644
index e61f738d..00000000
--- a/src/main/java/com/projectg/geyserupdater/bungee/BungeeUpdater.java
+++ /dev/null
@@ -1,209 +0,0 @@
-package com.projectg.geyserupdater.bungee;
-
-import com.projectg.geyserupdater.bungee.command.GeyserUpdateCommand;
-import com.projectg.geyserupdater.bungee.listeners.BungeeJoinListener;
-import com.projectg.geyserupdater.bungee.util.GeyserBungeeDownloader;
-import com.projectg.geyserupdater.bungee.util.bstats.Metrics;
-import com.projectg.geyserupdater.common.logger.JavaUtilUpdaterLogger;
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
-import com.projectg.geyserupdater.common.util.FileUtils;
-import com.projectg.geyserupdater.common.util.GeyserProperties;
-import com.projectg.geyserupdater.common.util.ScriptCreator;
-
-import com.projectg.geyserupdater.common.util.SpigotResourceUpdateChecker;
-import net.md_5.bungee.api.plugin.Plugin;
-import net.md_5.bungee.config.Configuration;
-import net.md_5.bungee.config.ConfigurationProvider;
-import net.md_5.bungee.config.YamlConfiguration;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.concurrent.TimeUnit;
-
-public final class BungeeUpdater extends Plugin {
-
- private static BungeeUpdater plugin;
- private Configuration configuration;
- private UpdaterLogger logger;
-
- @Override
- public void onEnable() {
- plugin = this;
- logger = new JavaUtilUpdaterLogger(getLogger());
- new Metrics(this, 10203);
-
- this.loadConfig();
- if (getConfig().getBoolean("Enable-Debug", false)) {
- UpdaterLogger.getLogger().info("Trying to enable debug logging.");
- UpdaterLogger.getLogger().enableDebug();
- }
-
- this.checkConfigVersion();
- // Check GeyserUpdater version
- this.checkUpdaterVersion();
-
- this.getProxy().getPluginManager().registerCommand(this, new GeyserUpdateCommand());
- // Player alert if a restart is required when they join
- getProxy().getPluginManager().registerListener(this, new BungeeJoinListener());
-
- // Make startup script
- if (configuration.getBoolean("Auto-Script-Generating")) {
- try {
- // Tell the createScript method that a loop is necessary because bungee has no restart system.
- ScriptCreator.createRestartScript(true);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- // Auto update Geyser if enabled
- if (configuration.getBoolean("Auto-Update-Geyser")) {
- scheduleAutoUpdate();
- }
- // Check if downloaded Geyser file exists periodically
- getProxy().getScheduler().schedule(this, () -> {
- if (FileUtils.checkFile("plugins/GeyserUpdater/BuildUpdate/Geyser-BungeeCord.jar", true)) {
- logger.info("A new Geyser build has been downloaded! Please restart BungeeCord in order to use the updated build!");
- }
- }, 30, 720, TimeUnit.MINUTES);
-
- }
-
- @Override
- public void onDisable() {
- // Force Geyser to disable so we can modify the jar in the plugins folder without issue
- logger.debug("Forcing Geyser to disable first...");
- getProxy().getPluginManager().getPlugin("Geyser-BungeeCord").onDisable();
- try {
- moveGeyserJar();
- for (int i = 0; i <= 2; i++) {
- try {
- deleteGeyserJar();
- break;
- } catch (IOException ioException) {
- logger.warn("An I/O error occurred while attempting to delete an unnecessary Geyser jar! Trying again " + (2 - i) + " more times.");
- ioException.printStackTrace();
- try {
- Thread.sleep(50);
- } catch (InterruptedException interruptException) {
- logger.error("Failed to delay an additional attempt!");
- interruptException.printStackTrace();
- }
- }
- }
- } catch (IOException e) {
- logger.error("An I/O error occurred while attempting to replace the current Geyser jar with the new one!");
- e.printStackTrace();
- }
- }
-
- /**
- * Load GeyserUpdater's config, create it if it doesn't exist
- */
- public void loadConfig() {
- try {
- configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(Config.startConfig(this, "config.yml"));
- } catch (IOException exception) {
- exception.printStackTrace();
- }
- }
-
- /**
- * Check the config version of GeyserUpdater
- */
- public void checkConfigVersion(){
- //Change version number only when editing config.yml!
- if (configuration.getInt("Config-Version", 0) != 2){
- logger.error("Your copy of config.yml is outdated. Please delete it and let a fresh copy of config.yml be regenerated!");
- }
- }
-
- /**
- * Check the version of GeyserUpdater against the spigot resource page
- */
- public void checkUpdaterVersion() {
- getProxy().getScheduler().runAsync(this, () -> {
- String pluginVersion = getDescription().getVersion();
- String latestVersion = SpigotResourceUpdateChecker.getVersion();
- if (latestVersion == null || latestVersion.length() == 0) {
- logger.error("Failed to determine the latest GeyserUpdater version!");
- } else {
- if (latestVersion.equals(pluginVersion)) {
- logger.info("You are using the latest version of GeyserUpdater!");
- } else {
- logger.info("Your version: " + pluginVersion + ". Latest version: " + latestVersion + ". Download the newer version at https://www.spigotmc.org/resources/geyserupdater.88555/.");
- }
- }
-
- });
- }
-
- /**
- * Check for a newer version of Geyser every 24hrs
- */
- public void scheduleAutoUpdate() {
- UpdaterLogger.getLogger().debug("Scheduling auto updater");
- // todo: build this in different way so that we don't repeat it if the Auto-Update-Interval is zero or -1 or something
- getProxy().getScheduler().schedule(this, () -> {
- logger.debug("Checking if a new build of Geyser exists.");
- try {
- // Checking for the build numbers of current build.
- boolean isLatest = GeyserProperties.isLatestBuild();
- if (!isLatest) {
- logger.info("A newer build of Geyser is available! Attempting to download the latest build now...");
- GeyserBungeeDownloader.updateGeyser();
- }
- } catch (IOException e) {
- logger.error("Failed to check for updates to Geyser! We were unable to reach the Geyser build server, or your local branch does not exist on it.");
- e.printStackTrace();
- }
- }, 1, getConfig().getLong("Auto-Update-Interval", 24L) * 60, TimeUnit.MINUTES);
- }
-
- /**
- * Replace the Geyser jar in the plugin folder with the one in GeyserUpdater/BuildUpdate
- * Should only be called once Geyser has been disabled
- *
- * @throws IOException if there was an IO failure
- */
- public void moveGeyserJar() throws IOException {
- // Moving Geyser Jar to Plugins folder "Overwriting".
- File fileToCopy = new File("plugins/GeyserUpdater/BuildUpdate/Geyser-BungeeCord.jar");
- if (fileToCopy.exists()) {
- logger.debug("Moving the new Geyser jar to the plugins folder.");
- FileInputStream input = new FileInputStream(fileToCopy);
- File newFile = new File("plugins/Geyser-BungeeCord.jar");
- FileOutputStream output = new FileOutputStream(newFile);
- byte[] buf = new byte[1024];
- int bytesRead;
- while ((bytesRead = input.read(buf)) > 0) {
- output.write(buf, 0, bytesRead);
- }
- input.close();
- output.close();
- } else {
- logger.debug("Found no new Geyser jar to copy to the plugins folder.");
- }
- }
-
- /**
- * Delete the Geyser jar in GeyserUpdater/BuildUpdate
- *
- * @throws IOException If it failed to delete
- */
- private void deleteGeyserJar() throws IOException {
- UpdaterLogger.getLogger().debug("Deleting the Geyser jar in the BuildUpdate folder if it exists");
- Path file = Paths.get("plugins/GeyserUpdater/BuildUpdate/Geyser-BungeeCord.jar");
- Files.deleteIfExists(file);
- }
- public static BungeeUpdater getPlugin() {
- return plugin;
- }
- public Configuration getConfig() {
- return configuration;
- }
-}
diff --git a/src/main/java/com/projectg/geyserupdater/bungee/Config.java b/src/main/java/com/projectg/geyserupdater/bungee/Config.java
deleted file mode 100644
index 8993d637..00000000
--- a/src/main/java/com/projectg/geyserupdater/bungee/Config.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.projectg.geyserupdater.bungee;
-
-import net.md_5.bungee.api.plugin.Plugin;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public class Config {
- public static File startConfig(Plugin plugin, String file) {
- File folder = plugin.getDataFolder();
- if (!folder.exists()) {
- folder.mkdir();
- }
- File resourceFile = new File(folder, file);
- try {
- if (!resourceFile.exists()) {
- try (InputStream in = plugin.getResourceAsStream(file);
- OutputStream out = new FileOutputStream(resourceFile)) {
- byte[] buffer = new byte[in.available()];
- in.read(buffer);
- out.write(buffer);
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- return resourceFile;
- }
-}
diff --git a/src/main/java/com/projectg/geyserupdater/bungee/listeners/BungeeJoinListener.java b/src/main/java/com/projectg/geyserupdater/bungee/listeners/BungeeJoinListener.java
deleted file mode 100644
index 8e8eb787..00000000
--- a/src/main/java/com/projectg/geyserupdater/bungee/listeners/BungeeJoinListener.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.projectg.geyserupdater.bungee.listeners;
-
-import com.projectg.geyserupdater.common.util.FileUtils;
-
-import net.md_5.bungee.api.chat.TextComponent;
-import net.md_5.bungee.api.event.PostLoginEvent;
-import net.md_5.bungee.api.plugin.Listener;
-import net.md_5.bungee.event.EventHandler;
-
-public class BungeeJoinListener implements Listener {
-
- @EventHandler
- public void onPostLogin(PostLoginEvent event) {
- // We allow a cached result of maximum age 30 minutes to be used
- if (FileUtils.checkFile("plugins/GeyserUpdater/BuildUpdate/Geyser-BungeeCord.jar", true)) {
- if (event.getPlayer().hasPermission("gupdater.geyserupdate")) {
- event.getPlayer().sendMessage(new TextComponent("[GeyserUpdater] A new Geyser build has been downloaded! Please restart BungeeCord in order to use the updated build!"));
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/projectg/geyserupdater/bungee/util/GeyserBungeeDownloader.java b/src/main/java/com/projectg/geyserupdater/bungee/util/GeyserBungeeDownloader.java
deleted file mode 100644
index 3c8cfe76..00000000
--- a/src/main/java/com/projectg/geyserupdater/bungee/util/GeyserBungeeDownloader.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package com.projectg.geyserupdater.bungee.util;
-
-import com.projectg.geyserupdater.bungee.BungeeUpdater;
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
-import com.projectg.geyserupdater.common.util.FileUtils;
-import com.projectg.geyserupdater.common.util.GeyserProperties;
-
-import net.md_5.bungee.api.ChatColor;
-import net.md_5.bungee.api.chat.TextComponent;
-import net.md_5.bungee.api.connection.ProxiedPlayer;
-
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
-public class GeyserBungeeDownloader {
- private static BungeeUpdater plugin;
- private static UpdaterLogger logger;
-
- /**
- * Download the latest build of Geyser from Jenkins CI for the currently used branch.
- * If enabled in the config, the server will also attempt to restart.
- */
- public static void updateGeyser() {
- plugin = BungeeUpdater.getPlugin();
- logger = UpdaterLogger.getLogger();
-
- UpdaterLogger.getLogger().debug("Attempting to download a new build of Geyser.");
-
- // New task so that we don't block the main thread. All new tasks on bungeecord are async.
- plugin.getProxy().getScheduler().runAsync(plugin, () -> {
- // Download the newest geyser build
- if (downloadGeyser()) {
- String successMsg = "The latest build of Geyser has been downloaded! A restart must occur in order for changes to take effect.";
- logger.info(successMsg);
- for (ProxiedPlayer player : plugin.getProxy().getPlayers()) {
- if (player.hasPermission("gupdater.geyserupdate")) {
- player.sendMessage(new TextComponent(ChatColor.GREEN + successMsg));
- }
- }
- if (plugin.getConfig().getBoolean("Auto-Restart-Server")) {
- restartServer();
- }
- } else {
- // fail messages are already sent to the logger in downloadGeyser()
- String failMsg = "A severe error occurred when download a new build of Geyser. Please check the server console for further information!";
- for (ProxiedPlayer player : plugin.getProxy().getPlayers()) {
- if (player.hasPermission("gupdater.geyserupdate")) {
- player.sendMessage(new TextComponent(ChatColor.RED + failMsg));
- }
- }
- }
- });
- }
-
- /**
- * Internal code for downloading the latest build of Geyser from Jenkins CI for the currently used branch.
- *
- * @return true if the download was successful, false if not.
- */
- private static boolean downloadGeyser() {
- String fileUrl;
- try {
- fileUrl = "https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/" + GeyserProperties.getGeyserGitPropertiesValue("git.branch") + "/lastSuccessfulBuild/artifact/bootstrap/bungeecord/target/Geyser-BungeeCord.jar";
- } catch (IOException e) {
- logger.error("Failed to get the current Geyser branch when attempting to download a new build of Geyser!");
- e.printStackTrace();
- return false;
- }
- String outputPath = "plugins/GeyserUpdater/BuildUpdate/Geyser-BungeeCord.jar";
- try {
- FileUtils.downloadFile(fileUrl, outputPath);
- } catch (IOException e) {
- logger.error("Failed to download the newest build of Geyser");
- e.printStackTrace();
- return false;
- }
-
- if (!FileUtils.checkFile(outputPath, false)) {
- logger.error("Failed to find the downloaded Geyser build!");
- return false;
- } else {
- return true;
- }
- }
-
- /**
- * Attempt to restart the server
- */
- private static void restartServer() {
- logger.warn("The server will be restarting in 10 seconds!");
- for (ProxiedPlayer player : plugin.getProxy().getPlayers()) {
- player.sendMessage(new TextComponent(ChatColor.translateAlternateColorCodes('&', plugin.getConfig().getString("Restart-Message-Players"))));
- }
- plugin.getProxy().getScheduler().schedule(plugin, () -> plugin.getProxy().stop(), 10L, TimeUnit.SECONDS);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/projectg/geyserupdater/common/util/FileUtils.java b/src/main/java/com/projectg/geyserupdater/common/util/FileUtils.java
deleted file mode 100644
index 0f9506df..00000000
--- a/src/main/java/com/projectg/geyserupdater/common/util/FileUtils.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package com.projectg.geyserupdater.common.util;
-
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
-
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URL;
-import java.net.URLConnection;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-public class FileUtils {
- // TODO: this whole cached thing only works if you're using checkFile for one file...
-
- /**
- * Epoch time of that last occurrence that {@link #checkFile(String, boolean)} directly checked a file. Returns a value of 0 if the check file method has never been called.
- */
- private static long callTime = 0;
-
- /**
- * Returns a cached result of {@link #checkFile(String, boolean)}. Returns null if the method has never been called.
- */
- private static boolean cachedResult;
-
- /**
- * Check if a file exists.
- *
- * @param path the path of the file to test
- * @param allowCached allow a cached result of maximum 30 minutes to be returned
- * @return true if the file exists, false if not
- */
- public static boolean checkFile(String path, boolean allowCached) {
- UpdaterLogger logger = UpdaterLogger.getLogger();
- if (allowCached) {
- long elapsedTime = System.currentTimeMillis() - callTime;
- if (elapsedTime < 30 * 60 * 1000) {
- logger.debug("Returning a cached result of the last time we checked if a file exists. The cached result is: " + cachedResult);
- return cachedResult;
- } else {
- logger.debug("Not returning a cached result of the last time we checked if a file exists because it has been too long.");
- }
- }
- Path p = Paths.get(path);
- boolean exists = Files.exists(p);
-
- logger.debug("Checked if a file exists. The result: " + exists);
- callTime = System.currentTimeMillis();
- cachedResult = exists;
- return exists;
- }
-
- /**
- * Download a file
- *
- * @param fileURL the url of the file
- * @param outputPath the path of the output file to write to
- */
- public static void downloadFile(String fileURL, String outputPath) throws IOException {
- // TODO: better download code?
-
- UpdaterLogger.getLogger().debug("Attempting to download a file with URL and output path: " + fileURL + " , " + outputPath);
-
- Path outputDirectory = Paths.get(outputPath).getParent();
- Files.createDirectories(outputDirectory);
-
- OutputStream os;
- InputStream is;
- // create a url object
- URL url = new URL(fileURL);
- // connection to the file
- URLConnection connection = url.openConnection();
- // get input stream to the file
- is = connection.getInputStream();
- // get output stream to download file
- os = new FileOutputStream(outputPath);
- final byte[] b = new byte[2048];
- int length;
- // read from input stream and write to output stream
- while ((length = is.read(b)) != -1) {
- os.write(b, 0, length);
- }
- // close streams
- is.close();
- os.close();
- }
-}
-
diff --git a/src/main/java/com/projectg/geyserupdater/common/util/GeyserProperties.java b/src/main/java/com/projectg/geyserupdater/common/util/GeyserProperties.java
deleted file mode 100644
index 48b63603..00000000
--- a/src/main/java/com/projectg/geyserupdater/common/util/GeyserProperties.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.projectg.geyserupdater.common.util;
-
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
-import org.geysermc.connector.utils.FileUtils;
-import org.geysermc.connector.utils.WebUtils;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.Properties;
-
-public class GeyserProperties {
-
- // todo: a check to see if the local git branch is available on the CI (that knows if it failed because of a bad connection or not)
- // todo: proper error handling
-
- /**
- * Compare the local build number to the latest build number on Geyser CI
- *
- * @return true if local build number equals latest build number on Geyser CI
- * @throws IOException if it fails to fetch either build number
- */
- public static boolean isLatestBuild() throws IOException {
- UpdaterLogger.getLogger().debug("Running isLatestBuild()");
- int jenkinsBuildNumber = getLatestGeyserBuildNumberFromJenkins(getGeyserGitPropertiesValue("git.branch"));
- int localBuildNumber = Integer.parseInt(getGeyserGitPropertiesValue("git.build.number"));
- // Compare build numbers.
- // We treat higher build numbers as "out of date" here because Geyser's build numbers have been (accidentally) reset in the past.
- // Self-compiled builds of Geyser simply do not have a `git.build.number` value, so it is /very/ unlikely that a user will ever have a Git build number higher than upstream anyway.
- return jenkinsBuildNumber == localBuildNumber;
- }
-
- /** Query the git properties of Geyser
- *
- * @param propertyKey the key of property to query
- * @return the value of the property
- * @throws IOException if failed to load the Geyser git properties
- */
- public static String getGeyserGitPropertiesValue(String propertyKey) throws IOException {
- UpdaterLogger.getLogger().debug("Running getGeyserGitPropertiesValue()");
- Properties gitProperties = new Properties();
- gitProperties.load(FileUtils.getResource("git.properties"));
- return gitProperties.getProperty(propertyKey);
- }
-
- /** Get the latest build number of a given branch of Geyser from jenkins CI
- *
- * @param gitBranch the branch to query
- * @return the latest build number
- * @throws UnsupportedEncodingException if failed to encode the given gitBranch
- */
- public static int getLatestGeyserBuildNumberFromJenkins(String gitBranch) throws UnsupportedEncodingException {
- UpdaterLogger.getLogger().debug("Running getLatestGeyserBuildNumberFromJenkins()");
- String buildXMLContents = WebUtils.getBody("https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/" + URLEncoder.encode(gitBranch, StandardCharsets.UTF_8.toString()) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber");
- return Integer.parseInt(buildXMLContents.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim());
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/projectg/geyserupdater/common/util/SpigotResourceUpdateChecker.java b/src/main/java/com/projectg/geyserupdater/common/util/SpigotResourceUpdateChecker.java
deleted file mode 100644
index c81fc9d5..00000000
--- a/src/main/java/com/projectg/geyserupdater/common/util/SpigotResourceUpdateChecker.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.projectg.geyserupdater.common.util;
-
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.Scanner;
-
-public class SpigotResourceUpdateChecker {
-
- private static final String VERSION_REGEX = "(\\d+.){1,2}\\d+";
-
- /**
- * Get the latest version of GeyserUpdater from the spigot resource page
- * @return the latest version, null if there was an error.
- */
- public static String getVersion() {
-
- try (InputStream inputStream = new URL("https://api.spigotmc.org/legacy/update.php?resource=88555").openStream(); Scanner scanner = new Scanner(inputStream)) {
- StringBuilder builder = new StringBuilder();
- while (scanner.hasNext()) {
- builder.append(scanner.next());
- }
- String version = builder.toString();
- if (version.matches(VERSION_REGEX)) {
- return version;
- } else {
- UpdaterLogger.getLogger().warn("Got unexpected string when checking Spigot resource page version: " + version);
- return null;
- }
- } catch (IOException exception) {
- UpdaterLogger.getLogger().error("Failed to check for updates: " + exception.getMessage());
- return null;
- }
- }
-}
diff --git a/src/main/java/com/projectg/geyserupdater/spigot/SpigotUpdater.java b/src/main/java/com/projectg/geyserupdater/spigot/SpigotUpdater.java
deleted file mode 100644
index 913a0cdb..00000000
--- a/src/main/java/com/projectg/geyserupdater/spigot/SpigotUpdater.java
+++ /dev/null
@@ -1,153 +0,0 @@
-package com.projectg.geyserupdater.spigot;
-
-import com.projectg.geyserupdater.common.logger.JavaUtilUpdaterLogger;
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
-import com.projectg.geyserupdater.common.util.FileUtils;
-import com.projectg.geyserupdater.common.util.GeyserProperties;
-import com.projectg.geyserupdater.spigot.command.GeyserUpdateCommand;
-import com.projectg.geyserupdater.spigot.listeners.SpigotJoinListener;
-import com.projectg.geyserupdater.spigot.util.CheckSpigotRestart;
-import com.projectg.geyserupdater.spigot.util.GeyserSpigotDownloader;
-import com.projectg.geyserupdater.common.util.SpigotResourceUpdateChecker;
-import com.projectg.geyserupdater.spigot.util.bstats.Metrics;
-
-import org.bukkit.Bukkit;
-import org.bukkit.configuration.InvalidConfigurationException;
-import org.bukkit.configuration.file.FileConfiguration;
-import org.bukkit.configuration.file.YamlConfiguration;
-import org.bukkit.plugin.java.JavaPlugin;
-import org.bukkit.scheduler.BukkitRunnable;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Objects;
-
-public class SpigotUpdater extends JavaPlugin {
- private static SpigotUpdater plugin;
-
- @Override
- public void onEnable() {
- plugin = this;
- new JavaUtilUpdaterLogger(getLogger());
- new Metrics(this, 10202);
-
- loadConfig();
- if (getConfig().getBoolean("Enable-Debug", false)) {
- UpdaterLogger.getLogger().info("Trying to enable debug logging.");
- UpdaterLogger.getLogger().enableDebug();
- }
-
- checkConfigVersion();
- // Check our version
- checkUpdaterVersion();
-
- Objects.requireNonNull(getCommand("geyserupdate")).setExecutor(new GeyserUpdateCommand());
- getCommand("geyserupdate").setPermission("gupdater.geyserupdate");
- // Player alert if a restart is required when they join
- Bukkit.getServer().getPluginManager().registerEvents(new SpigotJoinListener(), this);
-
- // Check if a restart script already exists
- // We create one if it doesn't
- if (getConfig().getBoolean("Auto-Script-Generating")) {
- try {
- CheckSpigotRestart.checkYml();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- // If true, start auto updating now and every 24 hours
- if (getConfig().getBoolean("Auto-Update-Geyser")) {
- scheduleAutoUpdate();
- }
- // Enable File Checking here. delay of 30 minutes and period of 12 hours (given in ticks)
- new BukkitRunnable() {
-
- @Override
- public void run() {
- if (FileUtils.checkFile("plugins/update/Geyser-Spigot.jar", false)) {
- UpdaterLogger.getLogger().info("A new Geyser build has been downloaded! Please restart the server in order to use the updated build!");
- }
- }
- }.runTaskTimerAsynchronously(this, 30 * 60 * 20, 12 * 60 * 60 * 20);
- }
-
- /**
- * Load GeyserUpdater's config, create it if it doesn't exist
- */
- private void loadConfig() {
- File configFile = new File(getDataFolder(), "config.yml");
- if (!configFile.exists()) {
- configFile.getParentFile().mkdirs();
- saveResource("config.yml", false);
- }
- FileConfiguration config = new YamlConfiguration();
- try {
- config.load(configFile);
- } catch (IOException | InvalidConfigurationException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Check the config version of GeyserUpdater
- */
- public void checkConfigVersion() {
- //Change version number only when editing config.yml!
- if (getConfig().getInt("Config-Version", 0) != 2) {
- UpdaterLogger.getLogger().warn("Your copy of config.yml is outdated. Please delete it and let a fresh copy of config.yml be regenerated!");
- }
- }
-
- /**
- * Check the version of GeyserUpdater against the spigot resource page
- */
- public void checkUpdaterVersion() {
- UpdaterLogger logger = UpdaterLogger.getLogger();
- String pluginVersion = plugin.getDescription().getVersion();
- new BukkitRunnable() {
- @Override
- public void run() {
- String latestVersion = SpigotResourceUpdateChecker.getVersion();
- if (latestVersion == null || latestVersion.length() == 0) {
- logger.error("Failed to determine the latest GeyserUpdater version!");
- } else {
- if (latestVersion.equals(pluginVersion)) {
- logger.info("You are using the latest version of GeyserUpdater!");
- } else {
- logger.info("Your version: " + pluginVersion + ". Latest version: " + latestVersion + ". Download the newer version at https://www.spigotmc.org/resources/geyserupdater.88555/.");
- }
- }
- }
- }.runTaskAsynchronously(this);
- }
-
- /**
- * Check for a newer version of Geyser every 24hrs
- */
- public void scheduleAutoUpdate() {
- UpdaterLogger.getLogger().debug("Scheduling auto updater");
- // todo: build this in different way so that we don't repeat it if the Auto-Update-Interval is zero or -1 or something
- new BukkitRunnable() {
-
- @Override
- public void run() {
- UpdaterLogger.getLogger().debug("Checking if a new build of Geyser exists.");
- try {
- boolean isLatest = GeyserProperties.isLatestBuild();
- if (!isLatest) {
- UpdaterLogger.getLogger().info("A newer build of Geyser is available! Attempting to download the latest build now...");
- GeyserSpigotDownloader.updateGeyser();
- }
- } catch (IOException e) {
- UpdaterLogger.getLogger().error("Failed to check for updates to Geyser! We were unable to reach the Geyser build server, or your local branch does not exist on it.");
- e.printStackTrace();
- }
- // Auto-Update-Interval is in hours. We convert it into ticks
- }
- }.runTaskTimer(this, 60 * 20, getConfig().getLong("Auto-Update-Interval", 24L) * 60 * 60 * 20);
- }
-
- public static SpigotUpdater getPlugin() {
- return plugin;
- }
-}
diff --git a/src/main/java/com/projectg/geyserupdater/spigot/listeners/SpigotJoinListener.java b/src/main/java/com/projectg/geyserupdater/spigot/listeners/SpigotJoinListener.java
deleted file mode 100644
index 6e21a573..00000000
--- a/src/main/java/com/projectg/geyserupdater/spigot/listeners/SpigotJoinListener.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.projectg.geyserupdater.spigot.listeners;
-
-import com.projectg.geyserupdater.common.util.FileUtils;
-
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.Listener;
-import org.bukkit.event.player.PlayerJoinEvent;
-
-public class SpigotJoinListener implements Listener {
-
- @EventHandler
- public void onPlayerJoin(PlayerJoinEvent event) {
- // We allow a cached result of maximum age 30 minutes to be used
- if (FileUtils.checkFile("plugins/update/Geyser-Spigot.jar", true)) {
- if (event.getPlayer().hasPermission("gupdater.geyserupdate")) {
- event.getPlayer().sendMessage("[GeyserUpdater] A new Geyser build has been downloaded! Please restart the server in order to use the updated build!");
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/projectg/geyserupdater/spigot/util/GeyserSpigotDownloader.java b/src/main/java/com/projectg/geyserupdater/spigot/util/GeyserSpigotDownloader.java
deleted file mode 100644
index 0cdbd4a1..00000000
--- a/src/main/java/com/projectg/geyserupdater/spigot/util/GeyserSpigotDownloader.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package com.projectg.geyserupdater.spigot.util;
-
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
-import com.projectg.geyserupdater.common.util.FileUtils;
-import com.projectg.geyserupdater.common.util.GeyserProperties;
-import com.projectg.geyserupdater.spigot.SpigotUpdater;
-
-import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
-import org.bukkit.entity.Player;
-import org.bukkit.scheduler.BukkitRunnable;
-
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-public class GeyserSpigotDownloader {
- private static SpigotUpdater plugin;
- private static UpdaterLogger logger;
-
- /**
- * Download the latest build of Geyser from Jenkins CI for the currently used branch.
- * If enabled in the config, the server will also attempt to restart.
- */
- public static void updateGeyser() {
- plugin = SpigotUpdater.getPlugin();
- logger = UpdaterLogger.getLogger();
-
- UpdaterLogger.getLogger().debug("Attempting to download a new build of Geyser.");
-
- boolean doRestart = plugin.getConfig().getBoolean("Auto-Restart-Server");
-
- // Start the process async
- new BukkitRunnable() {
- @Override
- public void run() {
- // Download the newest build and store the success state
- boolean downloadSuccess = downloadGeyser();
- // No additional code should be run after the following BukkitRunnable
- // Run it synchronously because it isn't thread-safe
- new BukkitRunnable() {
- @Override
- public void run() {
- if (downloadSuccess) {
- String successMsg = "The latest build of Geyser has been downloaded! A restart must occur in order for changes to take effect.";
- logger.info(successMsg);
- for (Player player : Bukkit.getOnlinePlayers()) {
- if (player.hasPermission("gupdater.geyserupdate")) {
- player.sendMessage(ChatColor.GREEN + successMsg);
- }
- }
- if (doRestart) {
- restartServer();
- }
- } else {
- // fail messages are already sent to the logger in downloadGeyser()
- String failMsg = "A error(); error occurred when download a new build of Geyser. Please check the server console for further information!";
- for (Player player : Bukkit.getOnlinePlayers()) {
- if (player.hasPermission("gupdater.geyserupdate")) {
- player.sendMessage(ChatColor.RED + failMsg);
- }
- }
- }
- }
- }.runTask(plugin);
- }
- }.runTaskAsynchronously(plugin);
- }
-
- /**
- * Internal code for downloading the latest build of Geyser from Jenkins CI for the currently used branch.
- *
- * @return true if the download was successful, false if not.
- */
- private static boolean downloadGeyser() {
- String fileUrl;
- try {
- fileUrl = "https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/" + GeyserProperties.getGeyserGitPropertiesValue("git.branch") + "/lastSuccessfulBuild/artifact/bootstrap/spigot/target/Geyser-Spigot.jar";
- } catch (IOException e) {
- logger.error("Failed to get the current Geyser branch when attempting to download a new build of Geyser!");
- e.printStackTrace();
- return false;
- }
- // todo: make sure we use the update folder defined in bukkit.yml (it can be changed)
- String outputPath = "plugins/update/Geyser-Spigot.jar";
- try {
- FileUtils.downloadFile(fileUrl, outputPath);
- } catch (IOException e) {
- logger.error("Failed to download the newest build of Geyser");
- e.printStackTrace();
- return false;
- }
-
- if (!FileUtils.checkFile(outputPath, false)) {
- logger.error("Failed to find the downloaded Geyser build!");
- return false;
- } else {
- return true;
- }
- }
-
- /**
- * Attempt to restart the server
- */
- private static void restartServer() {
- logger.warn("The server will be restarting in 10 seconds!");
- for (Player player : Bukkit.getOnlinePlayers()) {
- player.sendMessage(ChatColor.translateAlternateColorCodes('&', plugin.getConfig().getString("Restart-Message-Players")));
- }
- // Attempt to restart the server 10 seconds after the message
- new BukkitRunnable() {
- @Override
- public void run() {
- try {
- Object spigotServer;
- try {
- spigotServer = SpigotUpdater.getPlugin().getServer().getClass().getMethod("spigot").invoke(SpigotUpdater.getPlugin().getServer());
- } catch (NoSuchMethodException e) {
- logger.error("You are not running Spigot (or a fork of it, such as Paper)! GeyserUpdater cannot automatically restart your server!");
- e.printStackTrace();
- return;
- }
- Method restartMethod = spigotServer.getClass().getMethod("restart");
- restartMethod.setAccessible(true);
- restartMethod.invoke(spigotServer);
- } catch (NoSuchMethodException e) {
- logger.error("Your server version is too old to be able to be automatically restarted!");
- e.printStackTrace();
- } catch (InvocationTargetException | IllegalAccessException e) {
- logger.error("Failed to restart the server!");
- e.printStackTrace();
- }
- }
- }.runTaskLater(plugin, 200); // 200 ticks is around 10 seconds (at 20 TPS)
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/projectg/geyserupdater/velocity/VelocityUpdater.java b/src/main/java/com/projectg/geyserupdater/velocity/VelocityUpdater.java
deleted file mode 100644
index 00a61959..00000000
--- a/src/main/java/com/projectg/geyserupdater/velocity/VelocityUpdater.java
+++ /dev/null
@@ -1,249 +0,0 @@
-package com.projectg.geyserupdater.velocity;
-
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
-import com.projectg.geyserupdater.common.util.FileUtils;
-import com.projectg.geyserupdater.common.util.GeyserProperties;
-import com.projectg.geyserupdater.common.util.ScriptCreator;
-import com.projectg.geyserupdater.velocity.command.GeyserUpdateCommand;
-import com.projectg.geyserupdater.velocity.listeners.VelocityJoinListener;
-import com.projectg.geyserupdater.velocity.logger.Slf4jUpdaterLogger;
-import com.projectg.geyserupdater.velocity.util.GeyserVelocityDownloader;
-import com.projectg.geyserupdater.velocity.util.bstats.Metrics;
-
-import com.google.inject.Inject;
-
-import com.moandjiezana.toml.Toml;
-
-import org.geysermc.connector.GeyserConnector;
-import org.slf4j.Logger;
-
-import com.velocitypowered.api.event.PostOrder;
-import com.velocitypowered.api.event.Subscribe;
-import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
-import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
-import com.velocitypowered.api.plugin.Dependency;
-import com.velocitypowered.api.plugin.Plugin;
-import com.velocitypowered.api.plugin.annotation.DataDirectory;
-import com.velocitypowered.api.proxy.ProxyServer;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.concurrent.TimeUnit;
-
-@Plugin(id = "geyserupdater", name = "GeyserUpdater", version = "1.5.0", description = "Automatically or manually downloads new builds of Geyser and applies them on server restart.", authors = {"Jens"},
- dependencies = {@Dependency(id = "geyser")})
-public class VelocityUpdater {
-
- private static VelocityUpdater plugin;
- private final ProxyServer server;
- private final Logger baseLogger;
- private final Path dataDirectory;
- private final Toml config;
- private final Metrics.Factory metricsFactory;
-
- @Inject
- public VelocityUpdater(ProxyServer server, Logger baseLogger, @DataDirectory final Path folder, Metrics.Factory metricsFactory) {
- VelocityUpdater.plugin = this;
- this.server = server;
- this.baseLogger = baseLogger;
- this.dataDirectory = folder;
- this.config = loadConfig(dataDirectory);
- this.metricsFactory = metricsFactory;
- }
-
- @Subscribe
- public void onProxyInitialization(ProxyInitializeEvent event) {
- metricsFactory.make(this, 10673);
- new Slf4jUpdaterLogger(baseLogger);
-
- if (getConfig().getBoolean("Enable-Debug", false)) {
- UpdaterLogger.getLogger().info("Trying to enable debug logging.");
- UpdaterLogger.getLogger().enableDebug();
- }
-
- checkConfigVersion();
- // todo: meta version checking
-
- // Register our only command
- server.getCommandManager().register("geyserupdate", new GeyserUpdateCommand());
- // Player alert if a restart is required when they join
- server.getEventManager().register(this, new VelocityJoinListener());
-
- // Make startup script if enabled
- if (config.getBoolean("Auto-Script-Generating")) {
- try {
- ScriptCreator.createRestartScript(true);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- // Auto update Geyser if enabled in the config
- if (config.getBoolean("Auto-Update-Geyser")) {
- scheduleAutoUpdate();
- }
- // Check if downloaded Geyser file exists periodically
- server.getScheduler()
- .buildTask(this, () -> {
- FileUtils.checkFile("plugins/GeyserUpdater/BuildUpdate/Geyser-Velocity.jar", true);
- UpdaterLogger.getLogger().info("A new Geyser build has been downloaded! Please restart Velocity in order to use the updated build!");
- })
- .delay(30L, TimeUnit.MINUTES)
- .repeat(12L, TimeUnit.HOURS)
- .schedule();
- }
-
- @Subscribe(order = PostOrder.LAST)
- public void onShutdown(ProxyShutdownEvent event) {
- // This test isn't ideal but it'll work for now
- if (!GeyserConnector.getInstance().getBedrockServer().isClosed()) {
- throw new UnsupportedOperationException("Cannot shutdown GeyserUpdater before Geyser has shutdown! No updates will be applied.");
- }
- try {
- moveGeyserJar();
- for (int i = 0; i <= 2; i++) {
- try {
- deleteGeyserJar();
- break;
- } catch (IOException ioException) {
- UpdaterLogger.getLogger().warn("An I/O error occurred while attempting to delete an unnecessary Geyser jar! Trying again " + (2 - i) + " more times.");
- ioException.printStackTrace();
- try {
- Thread.sleep(50);
- } catch (InterruptedException interruptException) {
- UpdaterLogger.getLogger().error("Failed to delay an additional attempt!");
- interruptException.printStackTrace();
- }
- }
- }
- } catch (IOException e) {
- UpdaterLogger.getLogger().error("An I/O error occurred while attempting to replace the current Geyser jar with the new one!");
- e.printStackTrace();
- }
- }
-
- /**
- * Load GeyserUpdater's config
- *
- * @param path The config's directory
- * @return The configuration
- */
- private Toml loadConfig(Path path) {
- File folder = path.toFile();
- File file = new File(folder, "config.toml");
-
- if (!file.exists()) {
- if (!file.getParentFile().exists()) {
- file.getParentFile().mkdirs();
- }
- try (InputStream input = getClass().getResourceAsStream("/" + file.getName())) {
- if (input != null) {
- Files.copy(input, file.toPath());
- } else {
- file.createNewFile();
- }
- } catch (IOException exception) {
- exception.printStackTrace();
- return null;
- }
- }
- return new Toml().read(file);
- }
-
- /**
- * Check the config version of GeyserUpdater
- */
- public void checkConfigVersion() {
- //Change version number only when editing config.yml!
- if (getConfig().getLong("Config-Version", 0L).compareTo(2L) != 0) {
- UpdaterLogger.getLogger().warn("Your copy of config.yml is outdated. Please delete it and let a fresh copy of config.yml be regenerated!");
- }
- }
-
- /**
- * Check for a newer version of Geyser every 24hrs
- */
- public void scheduleAutoUpdate() {
- UpdaterLogger.getLogger().debug("Scheduling auto updater");
- // Checking for the build numbers of current build.
- // todo: build this in different way so that we don't repeat it if the Auto-Update-Interval is zero or -1 or something
- server.getScheduler()
- .buildTask(this, () -> {
- UpdaterLogger.getLogger().debug("Checking if a new build of Geyser exists.");
- try {
- boolean isLatest = GeyserProperties.isLatestBuild();
- if (!isLatest) {
- UpdaterLogger.getLogger().info("A newer build of Geyser is available! Attempting to download the latest build now...");
- GeyserVelocityDownloader.updateGeyser();
- }
- } catch (IOException e) {
- UpdaterLogger.getLogger().error("Failed to check for updates to Geyser! We were unable to reach the Geyser build server, or your local branch does not exist on it.");
- e.printStackTrace();
- }
- })
- .delay(1L, TimeUnit.MINUTES)
- .repeat(getConfig().getLong("Auto-Update-Interval", 24L), TimeUnit.HOURS)
- .schedule();
- }
-
- /**
- * Replace the Geyser jar in the plugin folder with the one in GeyserUpdater/BuildUpdate
- * Should only be called once Geyser has been disabled
- *
- * @throws IOException if there was an IO failure
- */
- public void moveGeyserJar() throws IOException {
- // Moving Geyser Jar to Plugins folder "Overwriting".
- File fileToCopy = new File("plugins/GeyserUpdater/BuildUpdate/Geyser-Velocity.jar");
- if (fileToCopy.exists()) {
- UpdaterLogger.getLogger().debug("Moving the new Geyser jar to the plugins folder.");
- FileInputStream input = new FileInputStream(fileToCopy);
- File newFile = new File("plugins/Geyser-Velocity.jar");
- FileOutputStream output = new FileOutputStream(newFile);
- byte[] buf = new byte[1024];
- int bytesRead;
- while ((bytesRead = input.read(buf)) > 0) {
- output.write(buf, 0, bytesRead);
- }
- input.close();
- output.close();
- } else {
- UpdaterLogger.getLogger().debug("Found no new Geyser jar to copy to the plugins folder.");
- }
- }
-
- /**
- * Delete the Geyser jar in GeyserUpdater/BuildUpdate
- *
- * @throws IOException if it failed to delete
- */
- private void deleteGeyserJar() throws IOException {
- UpdaterLogger.getLogger().debug("Deleting the Geyser jar in the BuildUpdate folder if it exists");
- Path file = Paths.get("plugins/GeyserUpdater/BuildUpdate/Geyser-Velocity.jar");
- Files.deleteIfExists(file);
- }
-
- public static VelocityUpdater getPlugin() {
- return plugin;
- }
- public ProxyServer getProxyServer() {
- return server;
- }
- public Path getDataDirectory() {
- return dataDirectory;
- }
- public Toml getConfig() {
- return config;
- }
-}
-
-
-
-
-
-
diff --git a/src/main/java/com/projectg/geyserupdater/velocity/listeners/VelocityJoinListener.java b/src/main/java/com/projectg/geyserupdater/velocity/listeners/VelocityJoinListener.java
deleted file mode 100644
index 70b404fa..00000000
--- a/src/main/java/com/projectg/geyserupdater/velocity/listeners/VelocityJoinListener.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.projectg.geyserupdater.velocity.listeners;
-
-import com.projectg.geyserupdater.common.util.FileUtils;
-
-import com.velocitypowered.api.event.Subscribe;
-import com.velocitypowered.api.event.connection.PostLoginEvent;
-
-import net.kyori.adventure.text.Component;
-
-public class VelocityJoinListener {
-
- @Subscribe
- public void onPostLogin(PostLoginEvent event) {
- // We allow a cached result of maximum age 30 minutes to be used
- if (FileUtils.checkFile("plugins/GeyserUpdater/BuildUpdate/Geyser-Velocity.jar",true)) {
- if (event.getPlayer().hasPermission("gupdater.geyserupdate")) {
- event.getPlayer().sendMessage(Component.text("[GeyserUpdater] A new Geyser build has been downloaded! Please restart Velocity in order to use the updated build!"));
- }
- }
- }
-}
diff --git a/src/main/java/com/projectg/geyserupdater/velocity/util/GeyserVelocityDownloader.java b/src/main/java/com/projectg/geyserupdater/velocity/util/GeyserVelocityDownloader.java
deleted file mode 100644
index a6087c9e..00000000
--- a/src/main/java/com/projectg/geyserupdater/velocity/util/GeyserVelocityDownloader.java
+++ /dev/null
@@ -1,104 +0,0 @@
-package com.projectg.geyserupdater.velocity.util;
-
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
-import com.projectg.geyserupdater.common.util.FileUtils;
-import com.projectg.geyserupdater.common.util.GeyserProperties;
-import com.projectg.geyserupdater.velocity.VelocityUpdater;
-
-import com.velocitypowered.api.proxy.Player;
-import com.velocitypowered.api.proxy.ProxyServer;
-
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.TextColor;
-
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
-public class GeyserVelocityDownloader {
- private static VelocityUpdater plugin;
- private static ProxyServer server;
- private static UpdaterLogger logger;
-
- /**
- * Download the latest build of Geyser from Jenkins CI for the currently used branch.
- * If enabled in the config, the server will also attempt to restart.
- */
- public static void updateGeyser() {
- plugin = VelocityUpdater.getPlugin();
- server = plugin.getProxyServer();
- logger = UpdaterLogger.getLogger();
-
- UpdaterLogger.getLogger().debug("Attempting to download a new build of Geyser.");
-
- // New task so that we don't block the main thread. All new tasks on velocity are async.
- plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
- // Download the newest geyser build
- // todo: do the colour codes for the Adventure text formatting work?
- if (downloadGeyser()) {
- String successMsg = "The latest build of Geyser has been downloaded! A restart must occur in order for changes to take effect.";
- logger.info(successMsg);
- for (Player player : server.getAllPlayers()) {
- if (player.hasPermission("gupdater.geyserupdate")) {
- player.sendMessage(Component.text(successMsg).color(TextColor.fromHexString("55FF55")));
- }
- }
- if (plugin.getConfig().getBoolean("Auto-Restart-Server")) {
- restartServer();
- }
- } else {
- // fail messages are already sent to the logger in downloadGeyser()
- String failMsg = "A severe error occurred when download a new build of Geyser. Please check the server console for further information!";
- for (Player player : server.getAllPlayers()) {
- if (player.hasPermission("gupdater.geyserupdate")) {
- player.sendMessage(Component.text(failMsg).color(TextColor.fromHexString("AA0000")));
- }
- }
- }
- }).schedule();
- }
-
- /**
- * Internal code for downloading the latest build of Geyser from Jenkins CI for the currently used branch.
- *
- * @return true if the download was successful, false if not.
- */
- private static boolean downloadGeyser() {
- String fileUrl;
- try {
- fileUrl = "https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/" + GeyserProperties.getGeyserGitPropertiesValue("git.branch") + "/lastSuccessfulBuild/artifact/bootstrap/velocity/target/Geyser-Velocity.jar";
- } catch (IOException e) {
- logger.error("Failed to get the current Geyser branch when attempting to download a new build of Geyser!");
- e.printStackTrace();
- return false;
- }
- String outputPath = "plugins/GeyserUpdater/BuildUpdate/Geyser-Velocity.jar";
- try {
- FileUtils.downloadFile(fileUrl, outputPath);
- } catch (IOException e) {
- logger.error("Failed to download the newest build of Geyser");
- e.printStackTrace();
- return false;
- }
-
- if (!FileUtils.checkFile(outputPath, false)) {
- logger.error("Failed to find the downloaded Geyser build!");
- return false;
- } else {
- return true;
- }
- }
- /**
- * Attempt to restart the server
- */
- private static void restartServer() {
- logger.warn("The server will be restarting in 10 seconds!");
- for (Player player : server.getAllPlayers()) {
- player.sendMessage(Component.text(plugin.getConfig().getString("Restart-Message-Players")));
- }
- server.getScheduler()
- .buildTask(plugin, server::shutdown)
- .delay(10L, TimeUnit.SECONDS)
- .schedule();
- }
-}
-
diff --git a/src/main/java/dev/projectg/geyserupdater/bungee/BungeePlayerHandler.java b/src/main/java/dev/projectg/geyserupdater/bungee/BungeePlayerHandler.java
new file mode 100644
index 00000000..3f426d26
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/bungee/BungeePlayerHandler.java
@@ -0,0 +1,28 @@
+package dev.projectg.geyserupdater.bungee;
+
+import dev.projectg.geyserupdater.common.PlayerManager;
+import net.md_5.bungee.api.ProxyServer;
+import net.md_5.bungee.api.chat.TextComponent;
+import net.md_5.bungee.api.connection.ProxiedPlayer;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class BungeePlayerHandler implements PlayerManager {
+
+ @Override
+ public @NotNull List getOnlinePlayers() {
+ List uuidList = new ArrayList<>();
+ for (ProxiedPlayer player : ProxyServer.getInstance().getPlayers()) {
+ uuidList.add(player.getUniqueId());
+ }
+ return uuidList;
+ }
+
+ @Override
+ public void sendMessage(@NotNull UUID uuid, @NotNull String message) {
+ ProxyServer.getInstance().getPlayer(uuid).sendMessage(new TextComponent(message));
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/bungee/BungeeScheduler.java b/src/main/java/dev/projectg/geyserupdater/bungee/BungeeScheduler.java
new file mode 100644
index 00000000..94387678
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/bungee/BungeeScheduler.java
@@ -0,0 +1,43 @@
+package dev.projectg.geyserupdater.bungee;
+
+import dev.projectg.geyserupdater.common.scheduler.Task;
+import dev.projectg.geyserupdater.common.scheduler.UpdaterScheduler;
+import net.md_5.bungee.api.plugin.Plugin;
+import net.md_5.bungee.api.scheduler.ScheduledTask;
+import org.jetbrains.annotations.NotNull;
+
+import javax.annotation.Nonnull;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+public class BungeeScheduler implements UpdaterScheduler {
+
+ private final Plugin plugin;
+
+ public BungeeScheduler(@Nonnull Plugin plugin) {
+ Objects.requireNonNull(plugin);
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Task schedule(@NotNull Runnable runnable, boolean async, long delay, long repeat, TimeUnit unit) {
+ // https://github.com/SpigotMC/BungeeCord/blob/master/proxy/src/main/java/net/md_5/bungee/scheduler/BungeeTask.java
+
+ Objects.requireNonNull(runnable);
+
+ return new BungeeTask(plugin.getProxy().getScheduler().schedule(plugin, runnable, delay, repeat, unit));
+ }
+
+ private static class BungeeTask implements Task {
+ private final ScheduledTask task;
+
+ public BungeeTask(ScheduledTask task) {
+ this.task = task;
+ }
+
+ @Override
+ public void cancel() {
+ task.cancel();
+ }
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/bungee/BungeeUpdater.java b/src/main/java/dev/projectg/geyserupdater/bungee/BungeeUpdater.java
new file mode 100644
index 00000000..2d29e49c
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/bungee/BungeeUpdater.java
@@ -0,0 +1,66 @@
+package dev.projectg.geyserupdater.bungee;
+
+import dev.projectg.geyserupdater.bungee.command.GeyserUpdateCommand;
+import dev.projectg.geyserupdater.bungee.bstats.Metrics;
+import dev.projectg.geyserupdater.common.GeyserUpdater;
+import dev.projectg.geyserupdater.common.UpdaterBootstrap;
+import dev.projectg.geyserupdater.common.logger.JavaUtilUpdaterLogger;
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
+
+import dev.projectg.geyserupdater.common.util.ScriptCreator;
+import net.md_5.bungee.api.plugin.Plugin;
+import space.arim.dazzleconf.error.InvalidConfigException;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class BungeeUpdater extends Plugin implements UpdaterBootstrap {
+
+ private GeyserUpdater updater;
+
+ @Override
+ public void onEnable() {
+ Path dataFolder = this.getDataFolder().toPath();
+ try {
+ updater = new GeyserUpdater(
+ dataFolder,
+ dataFolder.resolve("BuildUpdate"),
+ this.getProxy().getPluginsFolder().toPath(),
+ this,
+ new JavaUtilUpdaterLogger(getLogger()),
+ new BungeeScheduler(this),
+ new BungeePlayerHandler(),
+ this.getDescription().getVersion(),
+ "bootstrap/bungeecord/target/Geyser-BungeeCord.jar",
+ "bootstrap/bungee/target/floodgate-bungee.jar"
+ );
+ } catch (IOException | InvalidConfigException e) {
+ getLogger().severe("Failed to start GeyserUpdater! Disabling...");
+ e.printStackTrace();
+ return;
+ }
+
+ // Ensure we get disabled last
+ new PluginMapModifier().run(updater.getLogger(), this);
+
+ this.getProxy().getPluginManager().registerCommand(this, new GeyserUpdateCommand());
+ new Metrics(this, 10203);
+ }
+
+ @Override
+ public void onDisable() {
+ if (updater != null) {
+ try {
+ updater.shutdown();
+ } catch (IOException e) {
+ UpdaterLogger.getLogger().error("Failed to install ALL updates:");
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void createRestartScript() throws IOException {
+ ScriptCreator.createRestartScript(true);
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/bungee/PluginMapModifier.java b/src/main/java/dev/projectg/geyserupdater/bungee/PluginMapModifier.java
new file mode 100644
index 00000000..e6f0eabb
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/bungee/PluginMapModifier.java
@@ -0,0 +1,83 @@
+package dev.projectg.geyserupdater.bungee;
+
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
+import dev.projectg.geyserupdater.common.util.ReflectionUtils;
+import net.md_5.bungee.BungeeCord;
+import net.md_5.bungee.api.plugin.Plugin;
+import net.md_5.bungee.api.plugin.PluginManager;
+import net.md_5.bungee.api.scheduler.ScheduledTask;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A wrapper class for {@link this#run(UpdaterLogger, Plugin)}.
+ * It is wrapped to hide a {@link ScheduledTask} field, which is required for a Task which modifies the Plugin LinkedHashMap at the right moment to cancel itself.
+ */
+public class PluginMapModifier {
+
+ private ScheduledTask task = null;
+
+ private final String resultMessage = "This may result in errors and inability to apply updates during the shutdown process.";
+
+ /**
+ * Place a {@link Plugin} at the top of BungeeCord's LinkedHashMap of plugins. This will result in the plugin starting in the normal order, but being disabled last.
+ * This method is meant to be called within {@link Plugin#onEnable()}
+ * @param logger The Logger to use for messages
+ * @param plugin The Plugin to place at the top
+ */
+ protected void run(UpdaterLogger logger, Plugin plugin) {
+ // https://github.com/SpigotMC/BungeeCord/blob/master/proxy/src/main/java/net/md_5/bungee/BungeeCord.java
+ BungeeCord bungeeCord = BungeeCord.getInstance();
+ PluginManager pluginManager = bungeeCord.getPluginManager();
+
+ Collection> listeners;
+ LinkedHashMap plugins;
+ try {
+ listeners = ReflectionUtils.getFieldValue(bungeeCord, Collection.class, "listeners");
+ plugins = ReflectionUtils.getFieldValue(pluginManager, LinkedHashMap.class, "plugins");
+ } catch (IllegalAccessException | NoSuchFieldException | ClassCastException e) {
+ logger.error("Failed to get necessary private fields to modify BungeeCord's plugin list order");
+ logger.error(resultMessage);
+ e.printStackTrace();
+ return;
+ }
+
+ // modify the plugin map once it is no longer being iterated over by BungeeCord to avoid ConcurrentModificationException.
+ // onEnable() is called by BungeeCord when iterating over the plugin list, so we must modify it after bungeecord is done enabling ALL plugins
+ // the listeners List is populated after all plugin enabling is finished.
+ task = bungeeCord.getScheduler().schedule(plugin, () -> {
+ if (bungeeCord.isRunning) {
+ if (!listeners.isEmpty()) {
+ LinkedHashMap sortedPlugins = new LinkedHashMap<>();
+ String updaterName = plugin.getDescription().getName();
+ sortedPlugins.put(updaterName, plugins.get(updaterName)); // put ourselves at the very start, which means we disable last
+ sortedPlugins.putAll(plugins); // put the rest of the plugins in the default order
+
+ Set originalOrder = null;
+ if (logger.isDebug()) {
+ originalOrder = plugins.keySet();
+ }
+
+ synchronized (plugins) {
+ plugins.clear();
+ plugins.putAll(sortedPlugins);
+ }
+
+ logger.info("Successfully modified the order of BungeeCord's plugin Map.");
+ if (logger.isDebug()) {
+ logger.debug("Original order: " + originalOrder);
+ logger.debug("New order: " + plugins.keySet());
+ }
+ task.cancel();
+ }
+ } else {
+ logger.error("BungeeCord began shutdown before we were able to modify the plugin list order!");
+ logger.error(resultMessage);
+ task.cancel();
+ }
+ }, 2, 5, TimeUnit.SECONDS);
+ }
+}
diff --git a/src/main/java/com/projectg/geyserupdater/bungee/util/bstats/Metrics.java b/src/main/java/dev/projectg/geyserupdater/bungee/bstats/Metrics.java
similarity index 99%
rename from src/main/java/com/projectg/geyserupdater/bungee/util/bstats/Metrics.java
rename to src/main/java/dev/projectg/geyserupdater/bungee/bstats/Metrics.java
index f9579f84..e1ac07c6 100644
--- a/src/main/java/com/projectg/geyserupdater/bungee/util/bstats/Metrics.java
+++ b/src/main/java/dev/projectg/geyserupdater/bungee/bstats/Metrics.java
@@ -1,4 +1,4 @@
-package com.projectg.geyserupdater.bungee.util.bstats;
+package dev.projectg.geyserupdater.bungee.bstats;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
diff --git a/src/main/java/com/projectg/geyserupdater/bungee/command/GeyserUpdateCommand.java b/src/main/java/dev/projectg/geyserupdater/bungee/command/GeyserUpdateCommand.java
similarity index 78%
rename from src/main/java/com/projectg/geyserupdater/bungee/command/GeyserUpdateCommand.java
rename to src/main/java/dev/projectg/geyserupdater/bungee/command/GeyserUpdateCommand.java
index 9988beed..f2ea76b6 100644
--- a/src/main/java/com/projectg/geyserupdater/bungee/command/GeyserUpdateCommand.java
+++ b/src/main/java/dev/projectg/geyserupdater/bungee/command/GeyserUpdateCommand.java
@@ -1,18 +1,10 @@
-package com.projectg.geyserupdater.bungee.command;
+package dev.projectg.geyserupdater.bungee.command;
-import com.projectg.geyserupdater.bungee.util.GeyserBungeeDownloader;
-import com.projectg.geyserupdater.common.Messages;
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
-import com.projectg.geyserupdater.common.util.GeyserProperties;
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
-import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
-import net.md_5.bungee.api.chat.TextComponent;
-import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
-import java.io.IOException;
-
public class GeyserUpdateCommand extends Command {
@@ -23,6 +15,10 @@ public GeyserUpdateCommand() {
public void execute(CommandSender commandSender, String[] args) {
UpdaterLogger logger = UpdaterLogger.getLogger();
+ //todo: bungee command
+
+ /*
+
if (commandSender instanceof ProxiedPlayer) {
ProxiedPlayer player = (ProxiedPlayer) commandSender;
try {
@@ -40,7 +36,6 @@ public void execute(CommandSender commandSender, String[] args) {
e.printStackTrace();
}
} else {
- // TODO: filter this against command blocks
try {
logger.info(Messages.Command.CHECK_START);
boolean isLatest = GeyserProperties.isLatestBuild();
@@ -55,5 +50,7 @@ public void execute(CommandSender commandSender, String[] args) {
e.printStackTrace();
}
}
+
+ */
}
}
\ No newline at end of file
diff --git a/src/main/java/dev/projectg/geyserupdater/common/GeyserUpdater.java b/src/main/java/dev/projectg/geyserupdater/common/GeyserUpdater.java
new file mode 100644
index 00000000..177c1740
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/GeyserUpdater.java
@@ -0,0 +1,166 @@
+package dev.projectg.geyserupdater.common;
+
+import dev.projectg.geyserupdater.common.config.Configurator;
+import dev.projectg.geyserupdater.common.config.UpdaterConfiguration;
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
+import dev.projectg.geyserupdater.common.scheduler.UpdaterScheduler;
+import dev.projectg.geyserupdater.common.update.PluginId;
+import dev.projectg.geyserupdater.common.update.Updatable;
+import dev.projectg.geyserupdater.common.update.UpdateManager;
+import dev.projectg.geyserupdater.common.util.SpigotUtils;
+import space.arim.dazzleconf.error.InvalidConfigException;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+
+
+public class GeyserUpdater {
+
+ private static GeyserUpdater INSTANCE = null;
+
+ public final String version;
+
+ private final Path downloadFolder;
+ private final Path installFolder;
+ private final UpdaterLogger logger;
+ private final UpdaterScheduler scheduler;
+ private final PlayerManager playerManager;
+ private final UpdateManager updateManager;
+ private final UpdaterConfiguration config;
+
+ /**
+ * @param dataFolder The data folder for GeyserUpdater
+ * @param downloadFolder The default directory to download updates to. Updatables may override it.
+ * @param installFolder The default directory to move downloads to whenever GeyserUpdater is shown down. Updatables may override it.
+ * @param bootstrap The {@link UpdaterBootstrap} platform implemenetation
+ * @param logger The {@link UpdaterLogger} platform implemenetation
+ * @param scheduler The {@link UpdaterScheduler} platform implemenetation
+ * @param playerManager The {@link PlayerManager} platform implemenetation
+ * @param version The version of GeyserUpdater
+ * @param geyserArtifact The artifact link for Geyser. For example: "bootstrap/velocity/target/Geyser-Velocity.jar"
+ * @param floodgateArtifact The artifact link for Floodgate. For example: "bootstrap/velocity/target/floodgate-velocity.jar"
+ * @throws IOException If there was an exception loading the config. {@link this#shutdown()} Should not be called if this exception is thrown.
+ */
+ public GeyserUpdater(Path dataFolder,
+ Path downloadFolder,
+ Path installFolder,
+ UpdaterBootstrap bootstrap,
+ UpdaterLogger logger,
+ UpdaterScheduler scheduler,
+ PlayerManager playerManager,
+ String version,
+ String geyserArtifact,
+ String floodgateArtifact) throws IOException, InvalidConfigException {
+ this.downloadFolder = downloadFolder;
+ this.installFolder = installFolder;
+ this.logger = logger;
+ this.scheduler = scheduler;
+ this.playerManager = playerManager;
+ this.version = version;
+
+ INSTANCE = this;
+
+ // Meta version checking
+ scheduler.run(() -> {
+ String latestVersion = SpigotUtils.getVersion(88555);
+ if (latestVersion == null || latestVersion.isEmpty()) {
+ logger.error("Failed to determine the latest GeyserUpdater version!");
+ } else {
+ if (latestVersion.equals(version)) {
+ logger.info("You are using the latest version of GeyserUpdater!");
+ } else {
+ logger.warn("Your version: " + version + ". Latest version: " + latestVersion + ". Download the newer version at https://www.spigotmc.org/resources/geyserupdater.88555/");
+ }
+ }
+ }, true);
+
+ // Load the config
+ config = Configurator.loadConfig(dataFolder.resolve("config.yml"));
+ if (UpdaterConfiguration.DEFAULT_VERSION != config.version()) {
+ throw new IllegalStateException("Your copy of config.yml is outdated (your version: " + config.version() + ", latest version: " + UpdaterConfiguration.DEFAULT_VERSION + "). Please delete it and let a fresh copy of config.yml be regenerated!");
+ }
+ if (config.enableDebug()) {
+ logger.enableDebug();
+ }
+
+ // Make startup script if enabled
+ if (config.generateRestartScript()) {
+ try {
+ logger.debug("Attempting to create restart script");
+ bootstrap.createRestartScript();
+ } catch (IOException e) {
+ logger.error("Error while creating restart script:");
+ e.printStackTrace();
+ }
+ }
+
+ // Set the enable/autoCheck/autoUpdate values
+ PluginId.loadSettings(config);
+
+ // Set the correct download links for geyser and floodgate
+ PluginId.GEYSER.setArtifact(geyserArtifact);
+ PluginId.FLOODGATE.setArtifact(floodgateArtifact);
+
+ // Manager for updating plugins
+ this.updateManager = new UpdateManager(downloadFolder, scheduler, config);
+
+ // todo: restart after updates
+ // todo: better message sending, to players too
+ }
+
+ /**
+ * Installs all updates to the correct folder, if necessary. Will do nothing if the downloadFolder is the same file as the installFolder.
+ * @throws IOException If there was a failure moving ALL updates.
+ */
+ public void shutdown() throws IOException {
+ updateManager.shutdown(); //fixme: wait for the last download to finish, or cancel it and delete the unfinished file before copying files
+
+ UpdaterLogger.getLogger().debug("Installing plugins from the cache.");
+ Files.createDirectories(installFolder);
+
+ // Only move files that we have tracked
+ for (Updatable updatable : updateManager.getTrackedUpdatables()) {
+ Path update = updatable.outputFile;
+ if (Files.exists(update)) {
+ try {
+ if (Files.isSameFile(update.getParent(), installFolder)) {
+ // it is already where it should be, don't move it
+ continue;
+ }
+ } catch (IOException e) {
+ logger.warn("Failed to check if the install folder is the same as the download folder for " + updatable + ". Attempting to move files from the downloadFolder to the installFolder anyway...");
+ if (logger.isDebug()) {
+ e.printStackTrace();
+ }
+ }
+
+ try {
+ Files.move(update, installFolder.resolve(update.getFileName()), StandardCopyOption.REPLACE_EXISTING);
+ logger.debug("Moved " + updatable.outputFile + " to " + installFolder);
+ } catch (IOException e) {
+ UpdaterLogger.getLogger().error("Failed to copy update file " + updatable + " to directory " + installFolder);
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ public static GeyserUpdater getInstance() {
+ return INSTANCE;
+ }
+ public UpdaterConfiguration getConfig() {
+ return config;
+ }
+ public UpdaterLogger getLogger() {
+ return logger;
+ }
+ public UpdaterScheduler getScheduler() {
+ return scheduler;
+ }
+
+ public PlayerManager getPlayerHandler() {
+ return playerManager;
+ }
+}
diff --git a/src/main/java/com/projectg/geyserupdater/common/Messages.java b/src/main/java/dev/projectg/geyserupdater/common/Messages.java
similarity index 92%
rename from src/main/java/com/projectg/geyserupdater/common/Messages.java
rename to src/main/java/dev/projectg/geyserupdater/common/Messages.java
index 72d0faae..4aa965a9 100644
--- a/src/main/java/com/projectg/geyserupdater/common/Messages.java
+++ b/src/main/java/dev/projectg/geyserupdater/common/Messages.java
@@ -1,4 +1,4 @@
-package com.projectg.geyserupdater.common;
+package dev.projectg.geyserupdater.common;
public class Messages {
diff --git a/src/main/java/dev/projectg/geyserupdater/common/PlayerManager.java b/src/main/java/dev/projectg/geyserupdater/common/PlayerManager.java
new file mode 100644
index 00000000..44d3d79d
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/PlayerManager.java
@@ -0,0 +1,16 @@
+package dev.projectg.geyserupdater.common;
+
+import org.jetbrains.annotations.NotNull;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+import java.util.UUID;
+
+public interface PlayerManager {
+
+ @Nonnull
+ List getOnlinePlayers();
+
+ void sendMessage(@Nonnull UUID uuid, @NotNull String message);
+
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/UpdaterBootstrap.java b/src/main/java/dev/projectg/geyserupdater/common/UpdaterBootstrap.java
new file mode 100644
index 00000000..fd14d194
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/UpdaterBootstrap.java
@@ -0,0 +1,11 @@
+package dev.projectg.geyserupdater.common;
+
+import java.io.IOException;
+
+public interface UpdaterBootstrap {
+
+ void onDisable();
+
+ void createRestartScript() throws IOException;
+
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/command/CommandManager.java b/src/main/java/dev/projectg/geyserupdater/common/command/CommandManager.java
new file mode 100644
index 00000000..ab688e9b
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/command/CommandManager.java
@@ -0,0 +1,14 @@
+package dev.projectg.geyserupdater.common.command;
+
+public class CommandManager {
+
+
+
+ public CommandManager() {
+
+ }
+
+ public boolean process(CommandSender sender, String cmd, String[] args) {
+ return false;
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/command/CommandSender.java b/src/main/java/dev/projectg/geyserupdater/common/command/CommandSender.java
new file mode 100644
index 00000000..8612838c
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/command/CommandSender.java
@@ -0,0 +1,12 @@
+package dev.projectg.geyserupdater.common.command;
+
+import dev.projectg.geyserupdater.common.GeyserUpdater;
+import dev.projectg.geyserupdater.common.PlayerManager;
+
+public class CommandSender {
+
+ public void sendMessage() {
+ PlayerManager handler = GeyserUpdater.getInstance().getPlayerHandler();
+
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/command/UpdaterCommand.java b/src/main/java/dev/projectg/geyserupdater/common/command/UpdaterCommand.java
new file mode 100644
index 00000000..7c2bc181
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/command/UpdaterCommand.java
@@ -0,0 +1,6 @@
+package dev.projectg.geyserupdater.common.command;
+
+public interface UpdaterCommand {
+
+ boolean process(CommandSender sender, String cmd, String[] args);
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/config/Configurator.java b/src/main/java/dev/projectg/geyserupdater/common/config/Configurator.java
new file mode 100644
index 00000000..8dac2cf4
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/config/Configurator.java
@@ -0,0 +1,19 @@
+package dev.projectg.geyserupdater.common.config;
+
+import space.arim.dazzleconf.ConfigurationOptions;
+import space.arim.dazzleconf.ext.snakeyaml.CommentMode;
+import space.arim.dazzleconf.ext.snakeyaml.SnakeYamlConfigurationFactory;
+import space.arim.dazzleconf.ext.snakeyaml.SnakeYamlOptions;
+
+import java.util.Map;
+
+public class Configurator {
+
+ Map
+
+
+ static {
+
+ }
+
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/config/UpdaterConfiguration.java b/src/main/java/dev/projectg/geyserupdater/common/config/UpdaterConfiguration.java
new file mode 100644
index 00000000..30c0cc3a
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/config/UpdaterConfiguration.java
@@ -0,0 +1,40 @@
+package dev.projectg.geyserupdater.common.config;
+
+import lombok.Getter;
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
+import org.spongepowered.configurate.objectmapping.meta.Required;
+import org.spongepowered.configurate.objectmapping.meta.Setting;
+
+@Getter
+@ConfigSerializable
+@SuppressWarnings("FieldMayBeFinal")
+public class UpdaterConfiguration {
+
+ public static int DEFAULT_VERSION = 3;
+
+ @Setting("auto-check-interval")
+ private int autoUpdateInterval = 24;
+
+ @Setting("delete-on-fail")
+ private boolean deleteOnFail = true;
+
+ @Setting("restart-server")
+ private boolean restartServer = false;
+
+ @Setting("restart-script")
+ private boolean generateRestartScript = false;
+
+ @Setting("restart-message")
+ private String restartMessage = "§2This server will be restarting in 10 seconds!";
+
+ @Setting("download-time-limit")
+ private int downloadTimeLimit = 180;
+
+ @Setting("debug")
+ private boolean enableDebug = false;
+
+ @Required
+ @Setting("config-version")
+ private int version = 3;
+
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/config/lombok.config b/src/main/java/dev/projectg/geyserupdater/common/config/lombok.config
new file mode 100644
index 00000000..18f74be1
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/config/lombok.config
@@ -0,0 +1 @@
+lombok.accessors.fluent = true
\ No newline at end of file
diff --git a/src/main/java/dev/projectg/geyserupdater/common/config/module/DefinedModule.java b/src/main/java/dev/projectg/geyserupdater/common/config/module/DefinedModule.java
new file mode 100644
index 00000000..fb390801
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/config/module/DefinedModule.java
@@ -0,0 +1,23 @@
+package dev.projectg.geyserupdater.common.config.module;
+
+import dev.projectg.geyserupdater.common.config.module.project.Project;
+import lombok.Getter;
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
+import org.spongepowered.configurate.objectmapping.meta.Setting;
+
+@ConfigSerializable
+@SuppressWarnings("FieldMayBeFinal")
+@Getter
+public class DefinedModule extends PresetModule implements IModule {
+
+ @Setting("enable")
+ private boolean enable = true;
+
+ @Setting("auto-check")
+ private boolean autoCheck = true;
+
+ @Setting("auto-update")
+ private boolean autoUpdate = false;
+
+ private Project project = null;
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/config/module/IModule.java b/src/main/java/dev/projectg/geyserupdater/common/config/module/IModule.java
new file mode 100644
index 00000000..ec305f49
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/config/module/IModule.java
@@ -0,0 +1,14 @@
+package dev.projectg.geyserupdater.common.config.module;
+
+import dev.projectg.geyserupdater.common.config.module.project.Project;
+
+public interface IModule {
+
+ boolean enable();
+
+ boolean autoCheck();
+
+ boolean autoUpdate();
+
+ Project project();
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/config/module/PresetModule.java b/src/main/java/dev/projectg/geyserupdater/common/config/module/PresetModule.java
new file mode 100644
index 00000000..5413bbff
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/config/module/PresetModule.java
@@ -0,0 +1,30 @@
+package dev.projectg.geyserupdater.common.config.module;
+
+import dev.projectg.geyserupdater.common.config.module.project.Project;
+import lombok.Getter;
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
+import org.spongepowered.configurate.objectmapping.meta.NodeKey;
+import org.spongepowered.configurate.objectmapping.meta.Setting;
+
+@ConfigSerializable
+@SuppressWarnings("FieldMayBeFinal")
+@Getter
+public class PresetModule implements IModule {
+
+ @NodeKey
+ private String preset;
+
+ @Setting("enable")
+ private boolean enable = true;
+
+ @Setting("auto-check")
+ private boolean autoCheck = true;
+
+ @Setting("auto-update")
+ private boolean autoUpdate = false;
+
+ @Override
+ public Project project() {
+
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/config/module/project/JenkinsProject.java b/src/main/java/dev/projectg/geyserupdater/common/config/module/project/JenkinsProject.java
new file mode 100644
index 00000000..3b4daec0
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/config/module/project/JenkinsProject.java
@@ -0,0 +1,16 @@
+package dev.projectg.geyserupdater.common.config.module.project;
+
+import space.arim.dazzleconf.annote.ConfKey;
+import space.arim.dazzleconf.annote.ConfSerialisers;
+import space.arim.dazzleconf.serialiser.URLValueSerialiser;
+
+import java.net.URL;
+
+@ConfSerialisers(URLValueSerialiser.class)
+public interface JenkinsProject {
+ @ConfKey("project")
+ String projectLink();
+
+ @ConfKey("download")
+ URL downloadLink();
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/config/module/project/Project.java b/src/main/java/dev/projectg/geyserupdater/common/config/module/project/Project.java
new file mode 100644
index 00000000..aa9a4608
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/config/module/project/Project.java
@@ -0,0 +1,4 @@
+package dev.projectg.geyserupdater.common.config.module.project;
+
+public interface Project {
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/config/module/project/SpigotProject.java b/src/main/java/dev/projectg/geyserupdater/common/config/module/project/SpigotProject.java
new file mode 100644
index 00000000..daa01cd5
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/config/module/project/SpigotProject.java
@@ -0,0 +1,8 @@
+package dev.projectg.geyserupdater.common.config.module.project;
+
+import space.arim.dazzleconf.annote.ConfKey;
+
+public interface SpigotProject {
+ @ConfKey("resource")
+ int resourceId();
+}
diff --git a/src/main/java/com/projectg/geyserupdater/common/logger/JavaUtilUpdaterLogger.java b/src/main/java/dev/projectg/geyserupdater/common/logger/JavaUtilUpdaterLogger.java
similarity index 80%
rename from src/main/java/com/projectg/geyserupdater/common/logger/JavaUtilUpdaterLogger.java
rename to src/main/java/dev/projectg/geyserupdater/common/logger/JavaUtilUpdaterLogger.java
index 2710bdd1..00d1c2c8 100644
--- a/src/main/java/com/projectg/geyserupdater/common/logger/JavaUtilUpdaterLogger.java
+++ b/src/main/java/dev/projectg/geyserupdater/common/logger/JavaUtilUpdaterLogger.java
@@ -1,9 +1,10 @@
-package com.projectg.geyserupdater.common.logger;
+package dev.projectg.geyserupdater.common.logger;
import java.util.logging.Level;
import java.util.logging.Logger;
-public final class JavaUtilUpdaterLogger implements UpdaterLogger {
+public class JavaUtilUpdaterLogger implements UpdaterLogger {
+
private final Logger logger;
private Level originLevel;
@@ -29,12 +30,14 @@ public void info(String message) {
@Override
public void debug(String message) {
- logger.fine(message);
+ logger.info(message);
}
+ // todo: try to making debug/trace logging labbeled correctly. check how floodgate does it
+
@Override
public void trace(String message) {
- logger.finer(message);
+ logger.info(message);
}
@Override
diff --git a/src/main/java/com/projectg/geyserupdater/common/logger/UpdaterLogger.java b/src/main/java/dev/projectg/geyserupdater/common/logger/UpdaterLogger.java
similarity index 96%
rename from src/main/java/com/projectg/geyserupdater/common/logger/UpdaterLogger.java
rename to src/main/java/dev/projectg/geyserupdater/common/logger/UpdaterLogger.java
index db610f46..0fcd25e8 100644
--- a/src/main/java/com/projectg/geyserupdater/common/logger/UpdaterLogger.java
+++ b/src/main/java/dev/projectg/geyserupdater/common/logger/UpdaterLogger.java
@@ -1,4 +1,4 @@
-package com.projectg.geyserupdater.common.logger;
+package dev.projectg.geyserupdater.common.logger;
public interface UpdaterLogger {
diff --git a/src/main/java/com/projectg/geyserupdater/common/logger/UpdaterLoggerHolder.java b/src/main/java/dev/projectg/geyserupdater/common/logger/UpdaterLoggerHolder.java
similarity index 59%
rename from src/main/java/com/projectg/geyserupdater/common/logger/UpdaterLoggerHolder.java
rename to src/main/java/dev/projectg/geyserupdater/common/logger/UpdaterLoggerHolder.java
index 046a8f95..ba26b18e 100644
--- a/src/main/java/com/projectg/geyserupdater/common/logger/UpdaterLoggerHolder.java
+++ b/src/main/java/dev/projectg/geyserupdater/common/logger/UpdaterLoggerHolder.java
@@ -1,4 +1,4 @@
-package com.projectg.geyserupdater.common.logger;
+package dev.projectg.geyserupdater.common.logger;
class UpdaterLoggerHolder {
diff --git a/src/main/java/dev/projectg/geyserupdater/common/scheduler/Task.java b/src/main/java/dev/projectg/geyserupdater/common/scheduler/Task.java
new file mode 100644
index 00000000..09cf0ac2
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/scheduler/Task.java
@@ -0,0 +1,6 @@
+package dev.projectg.geyserupdater.common.scheduler;
+
+public interface Task {
+
+ void cancel();
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/scheduler/UpdaterScheduler.java b/src/main/java/dev/projectg/geyserupdater/common/scheduler/UpdaterScheduler.java
new file mode 100644
index 00000000..61a97d75
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/scheduler/UpdaterScheduler.java
@@ -0,0 +1,51 @@
+package dev.projectg.geyserupdater.common.scheduler;
+
+import javax.annotation.Nonnull;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+public interface UpdaterScheduler {
+
+ /**
+ * Schedule a Runnable to be run on a new thread.
+ * @param runnable The Runnable
+ * @param async true to run the Runnable off the main server thread, false to run it on the main server thread. Disregarded for BungeeCord and Velocity.
+ */
+ default Task run(@Nonnull Runnable runnable, boolean async) {
+ Objects.requireNonNull(runnable);
+ return schedule(runnable, async, 0, 0, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Schedule a Runnable to be run on a new thread.
+ * @param runnable The Runnable
+ * @param async true to run the Runnable off the main server thread, false to run it on the main server thread. Disregarded for BungeeCord and Velocity.
+ * @param delay The delay in milliseconds. A value less than zero should be considered unsafe.
+ */
+ default Task runDelayed(@Nonnull Runnable runnable, boolean async, long delay, TimeUnit unit) {
+ Objects.requireNonNull(runnable);
+ return schedule(runnable, async, delay, 0, unit);
+ }
+
+ /**
+ * Schedule a Runnable to be run.
+ * @param runnable The Runnable
+ * @param async true to run the Runnable off the main server thread, false to run it on the main server thread. Disregarded for BungeeCord and Velocity.
+ * @param repeat The repeat period, in milliseconds. A value of 0 or less will only run the Runnable once. A value less than zero should be considered unsafe.
+ */
+ default Task runTimer(@Nonnull Runnable runnable, boolean async, long repeat, TimeUnit unit) {
+ Objects.requireNonNull(runnable);
+ return schedule(runnable, async, 0, repeat, unit);
+ }
+
+ /**
+ * Schedule a Runnable to be run on a new thread.
+ * @param runnable The Runnable
+ * @param async true to run the Runnable off the main server thread, false to run it on the main server thread. Disregarded for BungeeCord and Velocity.
+ * @param delay The delay in milliseconds. A value less than zero should be considered unsafe.
+ * @param repeat The repeat period, in milliseconds. A value of 0 or less will only run the Runnable once. A value less than zero should be considered unsafe.
+ */
+ Task schedule(@Nonnull Runnable runnable, boolean async, long delay, long repeat, TimeUnit unit);
+}
+
+
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/DownloadManager.java b/src/main/java/dev/projectg/geyserupdater/common/update/DownloadManager.java
new file mode 100644
index 00000000..074869ad
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/DownloadManager.java
@@ -0,0 +1,119 @@
+package dev.projectg.geyserupdater.common.update;
+
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
+import dev.projectg.geyserupdater.common.scheduler.Task;
+import dev.projectg.geyserupdater.common.scheduler.UpdaterScheduler;
+import dev.projectg.geyserupdater.common.util.WebUtils;
+
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+public class DownloadManager {
+
+ private final UpdateManager updateManager;
+ private final UpdaterScheduler scheduler;
+ private final int downloadTimeLimit;
+
+ private final List queue = new LinkedList<>();
+
+ // Used by the hang checker to check if the current download is the same as when it was scheduled
+ @Nullable private Updatable currentUpdate = null;
+
+ // Used by the hang checker to cancel the download if necessary
+ @Nullable private Task downloader = null;
+
+ public DownloadManager(UpdateManager updateManager, UpdaterScheduler scheduler, int downloadTimeLimit) {
+ this.updateManager = updateManager;
+ this.scheduler = scheduler;
+ this.downloadTimeLimit = downloadTimeLimit;
+ }
+
+ public void queue(Updatable updatable) {
+ queue.add(updatable);
+ if (downloader == null) {
+ downloadAll();
+ }
+ }
+
+ private void downloadAll() {
+ // Run the download on a new thread
+ this.downloader = scheduler.run(() -> {
+
+ while (!queue.isEmpty()) {
+ currentUpdate = Objects.requireNonNull(queue.get(0));
+
+ // Create a timer to stop this download from running too long. Either the hang checker is cancelled or the hang checker cancels this.
+ Task hangChecker = scheduleHangChecker(currentUpdate);
+
+ try {
+ Files.createDirectories(currentUpdate.outputFile.getParent());
+ WebUtils.downloadFile(currentUpdate.downloadUrl, currentUpdate.outputFile);
+ } catch (IOException e) {
+ UpdaterLogger.getLogger().error("Caught exception while downloading file " + currentUpdate.outputFile + " with URL: " + currentUpdate.downloadUrl);
+ e.printStackTrace();
+ updateManager.finish(currentUpdate, DownloadResult.UNKNOWN_FAIL);
+ continue;
+ }
+
+ hangChecker.cancel();
+ queue.remove(0);
+ updateManager.finish(currentUpdate, DownloadResult.SUCCESS);
+ }
+
+ // Revert everything while having it locked so that the state is always correctly read by a different thread
+ synchronized (this) {
+ currentUpdate = null;
+ downloader = null;
+ }
+
+ // Everything should be downloaded now unless the queue was added to after the above for loop was finished
+ // But the synchronized block was not entered
+ }, true);
+ }
+
+ private Task scheduleHangChecker(Updatable updatable) {
+ // The time to allow the download to take, in seconds
+
+ return scheduler.runDelayed(() -> {
+ if (downloader == null) {
+ throw new AssertionError("HangChecker should not execute while nothing is downloading.");
+ }
+
+ if (updatable == currentUpdate) {
+ // Revert everything while having it locked so that the state is always correctly read by a different thread
+ synchronized (this) {
+ queue.clear();
+ currentUpdate = null;
+ downloader.cancel();
+ downloader = null;
+ }
+
+ UpdaterLogger.getLogger().error("The download queue has been stopped and cleared because the download for " + updatable + " took longer than " + downloadTimeLimit +
+ " seconds. Increase the download-time-limit in the config if you have a slow internet connection.");
+
+ updateManager.finish(updatable, DownloadResult.TIMEOUT);
+ }
+ }, true, downloadTimeLimit, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Shuts down any running download queues. The queue will be cleared, however the current download will be allowed to finish.
+ * @return A list of {@link Updatable} that had their download cancelled.
+ */
+ public List shutdown() {
+ List cancelled = new ArrayList<>();
+
+ // Allow the current download to finish and cancel everything else
+ while (queue.size() > 1) {
+ cancelled.add(queue.remove(1));
+ }
+
+ return cancelled;
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/DownloadResult.java b/src/main/java/dev/projectg/geyserupdater/common/update/DownloadResult.java
new file mode 100644
index 00000000..8aedb020
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/DownloadResult.java
@@ -0,0 +1,7 @@
+package dev.projectg.geyserupdater.common.update;
+
+public enum DownloadResult {
+ SUCCESS,
+ TIMEOUT,
+ UNKNOWN_FAIL
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/PluginId.java b/src/main/java/dev/projectg/geyserupdater/common/update/PluginId.java
new file mode 100644
index 00000000..3d5dc48f
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/PluginId.java
@@ -0,0 +1,80 @@
+package dev.projectg.geyserupdater.common.update;
+
+import dev.projectg.geyserupdater.common.config.UpdaterConfiguration;
+
+public enum PluginId {
+ GEYSER("https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/", "org.geysermc.connector.GeyserConnector"),
+ FLOODGATE("https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/", "org.geysermc.floodgate.FloodgatePlatform");
+
+ /**
+ * https://ci.opencollab.dev/job/GeyserMC/job/{PLUGIN_PAGE}/job/
+ */
+ private final String projectLink;
+
+ /**
+ * A class from the given plugin
+ */
+ private final String pluginClassName;
+ private String branch;
+ private String artifactLink;
+
+ private boolean enable = false;
+ private boolean autoCheck = false;
+ private boolean autoUpdate = false;
+
+ PluginId(String link, String pluginClassName) {
+ this.projectLink = link;
+ this.pluginClassName = pluginClassName;
+ }
+
+ public boolean isEnable() {
+ return enable;
+ }
+ public boolean isAutoCheck() {
+ return autoCheck;
+ }
+ public boolean isAutoUpdate() {
+ return autoUpdate;
+ }
+
+ /**
+ * @return The download link. Not usable if {@link PluginId#setArtifact(String)} has not been called.
+ */
+ public String getLatestFileLink() {
+ return projectLink + branch + "/lastSuccessfulBuild/" + artifactLink;
+ }
+
+ public String getLatestBuildNumber() {
+ return projectLink + branch + "/lastSuccessfulBuild/buildNumber";
+ }
+
+ /**
+ * @param branch The branch to be used for {@link PluginId#getLatestFileLink()}
+ */
+ public void setBranch(String branch) {
+ this.branch = branch;
+ }
+
+ /**
+ * @param artifactLink The artifact link. `bootstrap/spigot/target/Geyser-Spigot.jar` for example.
+ */
+ public void setArtifact(String artifactLink) {
+ this.artifactLink = "artifact/" + artifactLink;
+ }
+
+ /**
+ * @return A class from the plugin. Will throw an {@link ClassNotFoundException} if the class is not available, i.e. the plugin is not loaded.
+ */
+ public Class> getPluginClass() throws ClassNotFoundException {
+ return Class.forName(pluginClassName);
+ }
+
+ /**
+ * Load the enable, autoCheck, and autoUpdate configuration settings into the enum values.
+ * {@link JacksonConfiguration#getUpdateEntries()} should contain entries whose keys are equal an enum value's name in lowercase
+ * @param config The config to load from
+ */
+ public static void loadSettings(UpdaterConfiguration config) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/RecursiveDownloadManager.java b/src/main/java/dev/projectg/geyserupdater/common/update/RecursiveDownloadManager.java
new file mode 100644
index 00000000..62562ae4
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/RecursiveDownloadManager.java
@@ -0,0 +1,116 @@
+package dev.projectg.geyserupdater.common.update;
+
+import dev.projectg.geyserupdater.common.GeyserUpdater;
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
+import dev.projectg.geyserupdater.common.scheduler.Task;
+import dev.projectg.geyserupdater.common.scheduler.UpdaterScheduler;
+import dev.projectg.geyserupdater.common.util.WebUtils;
+
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@SuppressWarnings("unused") // Keeping this to compare to DownloadManager
+public class RecursiveDownloadManager {
+
+ private final GeyserUpdater updater;
+
+ private final List queue = new LinkedList<>();
+
+ // Used for making sure one download is ever running
+ private boolean isDownloading = false;
+
+ // Used by the hang checker to check if the current download is the same as when it was scheduled
+ @Nullable private Updatable currentUpdate = null;
+
+ // Used by the hang checker to cancel the download if necessary
+ @Nullable private Task downloader = null;
+
+ // maybe refactor this to use a for loop instead of being recursive? i dunno
+
+ public RecursiveDownloadManager(GeyserUpdater updater) {
+ this.updater = updater;
+ }
+
+ public void queue(Updatable updatable) {
+ queue.add(updatable);
+ downloadIfPossible();
+ }
+
+ private void download(Updatable updatable) {
+ isDownloading = true;
+ currentUpdate = updatable;
+
+ UpdaterScheduler scheduler = updater.getScheduler();
+
+ // Run the download on a new thread
+ this.downloader = scheduler.run(() -> {
+
+ // Create a timer to stop this download from running too long. Either the hang checker is cancelled or the hang checker cancels this.
+ Task hangChecker = scheduleHangChecker(updater, updatable);
+
+ try {
+ WebUtils.downloadFile(updatable.downloadUrl, updatable.outputFile);
+ } catch (IOException ioException) {
+ UpdaterLogger.getLogger().error("Failed to download file: " + updatable.downloadUrl + " for " + updatable);
+ ioException.printStackTrace();
+ }
+ hangChecker.cancel();
+
+ // Revert everything while having it locked so that the state is always correctly read by original thread
+ synchronized (this) {
+ this.queue.remove(updatable);
+ this.isDownloading = false;
+ this.currentUpdate = null;
+ this.downloader = null;
+ }
+
+ // Initiate another download if necessary
+ downloadIfPossible();
+ }, true);
+ }
+
+ private void downloadIfPossible() {
+ if (!queue.isEmpty() && !isDownloading) {
+ download(queue.get(0));
+ }
+ }
+
+ private Task scheduleHangChecker(GeyserUpdater updater, Updatable updatable) {
+ // The time to allow the download to take, in seconds
+ int downloadTimeLimit = updater.getConfig().getDownloadTimeLimit();
+
+ return updater.getScheduler().runDelayed(() -> {
+ if (!isDownloading || downloader == null) {
+ throw new AssertionError("HangChecker should not execute while nothing is downloading.");
+ }
+
+ if (updatable == currentUpdate) {
+ // Revert everything while having it locked so that the state is always correctly read by a different thread
+ synchronized (this) {
+ queue.clear();
+ isDownloading = false;
+ currentUpdate = null;
+ downloader.cancel();
+ downloader = null;
+ }
+
+ UpdaterLogger logger = UpdaterLogger.getLogger();
+
+ logger.error("The download queue has been stopped and cleared because the download for " + updatable + " took longer than " + downloadTimeLimit +
+ " seconds. Increase the download-time-limit in the config if you have a slow internet connection.");
+
+ try {
+ boolean deletedFailedFile = Files.deleteIfExists(updatable.outputFile);
+ logger.debug("Failed download for " + updatable + " had a file?: " + deletedFailedFile);
+ } catch (IOException e) {
+ logger.error("Failed to delete failed download file of " + updatable);
+ e.printStackTrace();
+ }
+ }
+ }, true, downloadTimeLimit, TimeUnit.SECONDS);
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/Updatable.java b/src/main/java/dev/projectg/geyserupdater/common/update/Updatable.java
new file mode 100644
index 00000000..0a51b9a1
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/Updatable.java
@@ -0,0 +1,66 @@
+package dev.projectg.geyserupdater.common.update;
+
+import dev.projectg.geyserupdater.common.update.identity.IdentityComparer;
+import dev.projectg.geyserupdater.common.util.WebUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+
+public class Updatable {
+
+ @Nonnull public final String pluginIdentity;
+ @Nonnull public final IdentityComparer, ?, ?> identityComparer;
+ @Nullable public final IdentityComparer, ?, ?> hashComparer;
+ @Nonnull public final String downloadUrl;
+ @Nonnull public final Path outputFile;
+ public final boolean autoCheck;
+ public final boolean autoUpdate;
+
+ /**
+ * Immutable container for information regarding how to update something
+ * @param name The name
+ * @param identityComparer The {@link IdentityComparer} to check if the plugin is outdated
+ * @param hashComparer The {@link IdentityComparer} to check if the hash of the downloaded file is acceptable.
+ * @param downloadUrl The complete download link of the plugin
+ * @param file If the Path is a file, the download will be written to that file. If the Path is a directory, the file will be written to that directory, and the filename will deduced from the link provided.
+ */
+ public Updatable(@Nonnull String name,
+ @Nonnull IdentityComparer, ?, ?> identityComparer,
+ @Nullable IdentityComparer, ?, ?> hashComparer,
+ @Nonnull String downloadUrl,
+ @Nonnull Path file,
+ boolean autoCheck,
+ boolean autoUpdate) {
+
+ this.pluginIdentity = Objects.requireNonNull(name);
+ this.identityComparer = Objects.requireNonNull(identityComparer);
+ this.hashComparer = hashComparer;
+ Objects.requireNonNull(downloadUrl);
+ Objects.requireNonNull(file);
+ this.autoCheck = autoCheck;
+ this.autoUpdate = autoUpdate;
+
+ // Remove / from the end of the link if necessary
+ if (downloadUrl.endsWith("/")) {
+ this.downloadUrl = downloadUrl.substring(0, downloadUrl.length() - 1);
+ } else {
+ this.downloadUrl = downloadUrl;
+ }
+
+ // Figure out the output file name if necessary
+ if (Files.isDirectory(file)) {
+ this.outputFile = Paths.get(WebUtils.getFileName(downloadUrl));
+ } else {
+ this.outputFile = file;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return pluginIdentity;
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/UpdateManager.java b/src/main/java/dev/projectg/geyserupdater/common/update/UpdateManager.java
new file mode 100644
index 00000000..34d556ee
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/UpdateManager.java
@@ -0,0 +1,255 @@
+package dev.projectg.geyserupdater.common.update;
+
+import com.google.common.collect.ImmutableMap;
+import dev.projectg.geyserupdater.common.config.UpdaterConfiguration;
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
+import dev.projectg.geyserupdater.common.scheduler.UpdaterScheduler;
+import dev.projectg.geyserupdater.common.update.identity.IdentityComparer;
+import dev.projectg.geyserupdater.common.update.identity.provider.FileHashProvider;
+import dev.projectg.geyserupdater.common.update.identity.provider.JenkinsBuildProvider;
+import dev.projectg.geyserupdater.common.update.identity.provider.JenkinsHashProvider;
+import dev.projectg.geyserupdater.common.update.identity.type.BuildNumber;
+import dev.projectg.geyserupdater.common.util.WebUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+public class UpdateManager {
+
+ /**
+ * The {@link DownloadManager} to use for downloading new versions of plugins.
+ */
+ private final DownloadManager downloadManager;
+ private final UpdaterScheduler scheduler;
+ private final UpdaterConfiguration config;
+
+ private final Map registry = new HashMap<>();
+
+ private boolean isAutoChecking = false;
+
+ public UpdateManager(Path defaultDownloadLocation, UpdaterScheduler scheduler, UpdaterConfiguration config) {
+ this.scheduler = scheduler;
+ this.config = config;
+ this.downloadManager = new DownloadManager(this, scheduler, config.getDownloadTimeLimit());
+ UpdaterLogger logger = UpdaterLogger.getLogger();
+
+ for (PluginId pluginId : PluginId.values()) {
+ if (pluginId.isEnable()) {
+ String name = pluginId.name();
+
+ // todo: move getting the local build number and branch into PluginId
+
+ // Get the inputstream of the git.properties
+ InputStream is;
+ try {
+ Class> clazz = pluginId.getPluginClass();
+
+ is = clazz.getClassLoader().getResourceAsStream("git.properties");
+ if (is == null) {
+ logger.error("Unable to find resource 'git.properties' for plugin: " + name);
+ continue;
+ }
+ } catch (ClassNotFoundException e) {
+ logger.error("Unable to find class for " + name + ", is it loaded? Unable to update.");
+ continue;
+ }
+
+ // load the inputstream into a Properties
+ Properties gitProperties = new Properties();
+ try {
+ gitProperties.load(is);
+ is.close();
+ } catch (IOException e) {
+ logger.error("Failed to get git.properties for plugin: " + name + ". Unable to update.");
+ e.printStackTrace();
+ continue;
+ }
+
+ // Get the build number and branch
+ String buildNumberString = gitProperties.getProperty("git.build.number");
+ String branch = gitProperties.getProperty("git.branch");
+ if (buildNumberString == null || branch == null) {
+ UpdaterLogger.getLogger().error("Failed to find build number or branch in git Properties '" + gitProperties + "' of plugin '" + name + "'. Not updating.");
+ continue;
+ }
+
+ pluginId.setBranch(branch);
+
+ // The download location, used for both downloading and hash checking
+ Path downloadLocation = defaultDownloadLocation.resolve(Paths.get(WebUtils.getFileName(pluginId.getLatestFileLink())));
+
+ // For age comparer
+ BuildNumber buildNumber = new BuildNumber(Integer.parseInt(buildNumberString));
+ JenkinsBuildProvider buildProvider;
+ try {
+ buildProvider = new JenkinsBuildProvider(pluginId.getLatestBuildNumber());
+ } catch (MalformedURLException e) {
+ UpdaterLogger.getLogger().error("Failed to create build number checker for " + name + ". Not updating.");
+ e.printStackTrace();
+ continue;
+ }
+
+ // File hash comparer
+ IdentityComparer, ?, ?> hashComparer = null;
+ try {
+ FileHashProvider localHashProvider = new FileHashProvider(downloadLocation);
+ JenkinsHashProvider jenkinsHashProvider = new JenkinsHashProvider(pluginId.getLatestFileLink() + "/*fingerprint*/");
+ hashComparer = new IdentityComparer<>(localHashProvider, jenkinsHashProvider);
+ } catch (MalformedURLException e) {
+ UpdaterLogger.getLogger().error("Failure while getting location of file for " + name + ". It will be possible to update it, but not to compare file hashes.");
+ e.printStackTrace();
+ }
+
+ register(new Updatable(
+ name,
+ new IdentityComparer<>(buildNumber, buildProvider),
+ hashComparer,
+ pluginId.getLatestFileLink(),
+ downloadLocation,
+ pluginId.isAutoCheck(),
+ pluginId.isAutoUpdate()));
+ }
+ }
+
+ // Load extra stuff from the config if we wanted, I guess
+ }
+
+ /**
+ * Register an {@link Updatable} to be updated
+ * @param updatable The {@link Updatable} to be updated
+ */
+ public void register(Updatable updatable) {
+ registry.put(updatable, UpdateStatus.UNKNOWN);
+ if (updatable.autoCheck && !isAutoChecking) {
+ scheduleUpdateChecker(scheduler, config.getAutoUpdateInterval());
+ }
+ }
+
+ public boolean isTracked(Updatable updatable) {
+ return registry.containsKey(updatable);
+ }
+
+ /**
+ * Potentially blocking
+ */
+ public boolean isOutdated(Updatable updatable) {
+ if (!isTracked(updatable)) {
+ throw new IllegalArgumentException("Updatable must be tracked by the UpdateManager in order to check if it is outdated!");
+ }
+ UpdateStatus status = Objects.requireNonNull(registry.get(updatable));
+ switch (status) {
+ case UNKNOWN:
+ // It was latest last we checked, or we haven't checker before
+ return !updatable.identityComparer.checkIfEquals();
+ case OUTDATED:
+ return true;
+ default:
+ // A new version is either being downloaded or has been already
+ return false;
+ }
+ }
+
+ /**
+ * Blocking
+ */
+ protected void finish(Updatable updatable, DownloadResult result) {
+ if (registry.get(updatable) != UpdateStatus.DOWNLOADING) {
+ throw new IllegalStateException("Cannot finish an Updatable if its current status is not DOWNLOADING");
+ }
+ UpdaterLogger logger = UpdaterLogger.getLogger();
+
+ if (updatable.hashComparer == null) {
+ if (result == DownloadResult.SUCCESS) {
+ // cant check hash, but the download result is success
+ logger.info("Successfully downloaded update for: " + updatable);
+ registry.put(updatable, UpdateStatus.DOWNLOADED);
+ return;
+ }
+ } else {
+ Object downloadHash = updatable.hashComparer.callLocalValue();
+ Object anticipatedHash = updatable.hashComparer.callExternalValue();
+ if (downloadHash == null) {
+ logger.error("Failed to find hash for downloaded update of: " + updatable);
+ } else if (anticipatedHash == null) {
+ logger.error("Failed to find anticipated hash for downloaded update of: " + updatable);
+ } else if (downloadHash.equals(anticipatedHash)) {
+ // hash is "correct"
+ logger.info("Successfully downloaded update for: " + updatable);
+ logger.debug("Downloaded file hash: <" + downloadHash + ">. Anticipated hash: <" + anticipatedHash + ">");
+ registry.put(updatable, UpdateStatus.DOWNLOADED);
+ return;
+ } else {
+ logger.warn("The file hash of the downloaded file did not match the hash provided online for " + updatable);
+ logger.warn("Downloaded file hash: <" + downloadHash + ">. Anticipated hash: <" + anticipatedHash + ">");
+ }
+ }
+
+ // Hash is not correct, or cannot check hash and there was a fail
+ if (config.isDeleteOnFail()) {
+ logger.warn("Deleting failed download.");
+ try {
+ Files.deleteIfExists(updatable.outputFile);
+ } catch (IOException e) {
+ logger.error("Failed to delete failed download file of " + updatable);
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void scheduleUpdateChecker(UpdaterScheduler scheduler, long interval) {
+ scheduler.schedule(() -> {
+ List outdatedOnes = new ArrayList<>();
+ List autoDownloads = new ArrayList<>();
+
+ for (Updatable updatable : registry.keySet()) {
+ // Only check if it is not known to be outdated, and if it should be checked automatically
+ if (registry.get(updatable) == UpdateStatus.UNKNOWN && updatable.autoCheck) {
+ // We should check if it needs an update
+ if (!updatable.identityComparer.checkIfEquals()) {
+ // It is outdated
+ registry.put(updatable, UpdateStatus.OUTDATED);
+ outdatedOnes.add(updatable.toString());
+ if (updatable.autoUpdate) {
+ registry.put(updatable, UpdateStatus.DOWNLOADING);
+ downloadManager.queue(updatable);
+ autoDownloads.add(updatable.toString());
+ }
+ }
+ }
+ }
+
+ // todo: also send messages to players with the permission
+ if (!outdatedOnes.isEmpty()) {
+ UpdaterLogger logger = UpdaterLogger.getLogger();
+ logger.info("The following updatables are outdated: " + outdatedOnes);
+ if (!autoDownloads.isEmpty()) {
+ logger.info("The following updatables are set to download automatically and have been queued for download: " + autoDownloads);
+ }
+ }
+
+ }, true, 0L, interval, TimeUnit.HOURS);
+
+ isAutoChecking = true;
+ }
+
+ /**
+ * @return a {@link Map#keySet()} of the tracked Updatable registry
+ */
+ public Set getTrackedUpdatables() {
+ return registry.keySet();
+ }
+
+ public void shutdown() {
+ List cancelled = downloadManager.shutdown();
+ if (!cancelled.isEmpty()) {
+ UpdaterLogger.getLogger().info("Cancelled the following downloads because of a shutdown: " + cancelled.stream().map(Updatable::toString).collect(Collectors.joining(", ")));
+ }
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/UpdateStatus.java b/src/main/java/dev/projectg/geyserupdater/common/update/UpdateStatus.java
new file mode 100644
index 00000000..60390bef
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/UpdateStatus.java
@@ -0,0 +1,20 @@
+package dev.projectg.geyserupdater.common.update;
+
+public enum UpdateStatus {
+ /**
+ * The Updatable has not been checked, or the last time it was checked it was not outdated. Another check may find that it is outdated.
+ */
+ UNKNOWN,
+ /**
+ * The Updatable is outdated but is not being downloaded.
+ */
+ OUTDATED,
+ /**
+ * The Updatable is outdated and is being downloaded.
+ */
+ DOWNLOADING,
+ /**
+ * The Updatable is outdated but it has been downloaded.
+ */
+ DOWNLOADED
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/identity/IdentityComparer.java b/src/main/java/dev/projectg/geyserupdater/common/update/identity/IdentityComparer.java
new file mode 100644
index 00000000..c922ad70
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/identity/IdentityComparer.java
@@ -0,0 +1,86 @@
+package dev.projectg.geyserupdater.common.update.identity;
+
+import dev.projectg.geyserupdater.common.update.identity.provider.IdentityProvider;
+import dev.projectg.geyserupdater.common.update.identity.type.Identity;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Objects;
+
+/**
+ * @param The external {@link IdentityProvider} implementation
+ * @param The {@link Identity} implementation
+ */
+public class IdentityComparer, T extends IdentityProvider, S extends Identity>> {
+
+ private final @Nullable S localIdentity;
+ private final @Nullable U localIdentityProvider;
+ private final @Nonnull T externalIdentityProvider;
+
+ /**
+ * Create an identity comparer, which stores a way to check a local identity and way to check an external identity. Both params should provide the same {@link Identity} implementation.
+ * @param localIdentityProvider The local identity provider. {@link IdentityProvider#getValue()}
+ * @param externalIdentityProvider The external identity provider. {@link IdentityProvider#getValue()}
+ */
+ public IdentityComparer(@Nonnull U localIdentityProvider, @Nonnull T externalIdentityProvider) {
+ Objects.requireNonNull(localIdentityProvider);
+ Objects.requireNonNull(externalIdentityProvider);
+
+ this.localIdentity = null;
+ this.localIdentityProvider = localIdentityProvider;
+ this.externalIdentityProvider = externalIdentityProvider;
+ }
+
+ /**
+ * Create an identity comparer, which stores a local identity and way to check an external identity.
+ * @param localIdentity The local identity.
+ * @param externalIdentityProvider The external identity provider. {@link IdentityProvider#getValue()} will be called every time {@link IdentityComparer#checkIfEquals()} is called.
+ */
+ public IdentityComparer(@Nonnull S localIdentity, @Nonnull T externalIdentityProvider) {
+ Objects.requireNonNull(localIdentity);
+ Objects.requireNonNull(externalIdentityProvider);
+
+ this.localIdentity = localIdentity;
+ this.localIdentityProvider = null;
+ this.externalIdentityProvider = externalIdentityProvider;
+ }
+
+ /**
+ * Request the {@link Identity} from the external {@link IdentityProvider} and compare it to the stored local {@link Identity}. Should be regarded as a blocking operation.
+ * @return True if the local identity {@link Object#equals(Object)} the external identity. Also returns true if either are null.
+ */
+ public boolean checkIfEquals() {
+ // todo: return an enum or wrapper, to better deal with null values
+ Object localValue = callLocalValue();
+ Object externalValue = callExternalValue();
+ if (localValue == null || externalValue == null) {
+ return true;
+ }
+
+ return localValue.equals(externalValue);
+ }
+
+ @Nullable
+ public Object callLocalValue() {
+ if (localIdentity == null) {
+ S localId = Objects.requireNonNull(localIdentityProvider).getValue();
+ if (localId == null) {
+ return null;
+ } else {
+ return localId.value();
+ }
+ } else {
+ return localIdentity.value();
+ }
+ }
+
+ @Nullable
+ public Object callExternalValue() {
+ S externalId = externalIdentityProvider.getValue();
+ if (externalId == null) {
+ return null;
+ } else {
+ return externalId.value();
+ }
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/identity/provider/FileHashProvider.java b/src/main/java/dev/projectg/geyserupdater/common/update/identity/provider/FileHashProvider.java
new file mode 100644
index 00000000..b68eac9f
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/identity/provider/FileHashProvider.java
@@ -0,0 +1,35 @@
+package dev.projectg.geyserupdater.common.update.identity.provider;
+
+import com.google.common.hash.Hashing;
+import com.google.common.io.ByteSource;
+import com.google.common.io.Files;
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
+import dev.projectg.geyserupdater.common.update.identity.type.Md5FileHash;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class FileHashProvider implements IdentityProvider {
+
+ Path file;
+
+ public FileHashProvider(Path file) {
+ this.file = file;
+ }
+
+ @Override
+ public Md5FileHash getValue() {
+ // https://stackoverflow.com/questions/304268/getting-a-files-md5-checksum-in-java
+ ByteSource byteSource = Files.asByteSource(file.toFile());
+
+ // todo: non beta usage? I dont know.
+ String md5Hash = null;
+ try {
+ md5Hash = byteSource.hash(Hashing.md5()).toString();
+ } catch (IOException e) {
+ UpdaterLogger.getLogger().error("Exception while getting md5 hash of file: " + file.getFileName());
+ e.printStackTrace();
+ }
+ return new Md5FileHash(md5Hash);
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/identity/provider/IdentityProvider.java b/src/main/java/dev/projectg/geyserupdater/common/update/identity/provider/IdentityProvider.java
new file mode 100644
index 00000000..4046996b
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/identity/provider/IdentityProvider.java
@@ -0,0 +1,11 @@
+package dev.projectg.geyserupdater.common.update.identity.provider;
+
+import dev.projectg.geyserupdater.common.update.identity.type.Identity;
+
+import javax.annotation.Nullable;
+
+public interface IdentityProvider> {
+
+ @Nullable
+ S getValue();
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/identity/provider/JenkinsBuildProvider.java b/src/main/java/dev/projectg/geyserupdater/common/update/identity/provider/JenkinsBuildProvider.java
new file mode 100644
index 00000000..219fa482
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/identity/provider/JenkinsBuildProvider.java
@@ -0,0 +1,46 @@
+package dev.projectg.geyserupdater.common.update.identity.provider;
+
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
+import dev.projectg.geyserupdater.common.update.identity.type.BuildNumber;
+import dev.projectg.geyserupdater.common.util.WebUtils;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Provides a build number from a given Jenkins link.
+ */
+public class JenkinsBuildProvider implements IdentityProvider {
+
+ private final URL url;
+
+ /**
+ * Creates a build number provider for a given link.
+ * @param link A link which onto which "/buildNumber" will be added, which should link to a page that contains only the build number as a plain integer. For example: https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/master/lastSuccessfulBuild/buildNumber
+ * @throws MalformedURLException If the link provided cannot be converted to a {@link URL}
+ */
+ public JenkinsBuildProvider(String link) throws MalformedURLException {
+ url = new URL(link + "/buildNumber");
+ }
+
+ @Override
+ public BuildNumber getValue() {
+ BuildNumber buildNumber = null;
+ try {
+ String body = WebUtils.getBody(url);
+ try {
+ buildNumber = new BuildNumber(Integer.parseInt(body));
+ } catch (NumberFormatException e) {
+ UpdaterLogger.getLogger().error("Failed to get a build number from a Jenkins server because an integer was not returned.");
+ UpdaterLogger.getLogger().error("Body returned: <" + body + "> (excluding the angle brackets)");
+ e.printStackTrace();
+ }
+ } catch (IOException e) {
+ UpdaterLogger.getLogger().error("Failed to get a build number from a Jenkins server:");
+ e.printStackTrace();
+ }
+
+ return buildNumber;
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/identity/provider/JenkinsHashProvider.java b/src/main/java/dev/projectg/geyserupdater/common/update/identity/provider/JenkinsHashProvider.java
new file mode 100644
index 00000000..5f737cbe
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/identity/provider/JenkinsHashProvider.java
@@ -0,0 +1,56 @@
+package dev.projectg.geyserupdater.common.update.identity.provider;
+
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
+import dev.projectg.geyserupdater.common.update.identity.type.Md5FileHash;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+public class JenkinsHashProvider implements IdentityProvider {
+
+ private final URL url;
+
+ public JenkinsHashProvider(String url) throws MalformedURLException {
+ this.url = new URL(url);
+ }
+
+ @Override
+ public Md5FileHash getValue() {
+ // Don't use WebUtils so that we don't have to iterate over the whole page contents, and have better error messages.
+
+ try {
+ HttpURLConnection con = (HttpURLConnection) url.openConnection();
+ con.setRequestMethod("GET");
+ con.setRequestProperty("User-Agent", "GeyserUpdater");
+
+ if (con.getResponseCode() != 200) {
+ UpdaterLogger.getLogger().error("Unable to find md5 from jenkins fingerprint at: " + url + " because the Http response code was not 200 (OK).");
+ return null;
+ }
+
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()))) {
+
+ String inputLine;
+ while ((inputLine = in.readLine()) != null) {
+ if (inputLine.contains("MD5")) {
+ con.disconnect();
+ return new Md5FileHash(inputLine.substring(inputLine.indexOf("MD5: ") + 5, inputLine.indexOf("")));
+ }
+ }
+ con.disconnect();
+
+ UpdaterLogger.getLogger().error("Unable to find md5 from jenkins fingerprint at: " + url + " because the page scan failed to find the hash.");
+ return null;
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ UpdaterLogger.getLogger().error("Unable to find md5 from jenkins fingerprint at: " + url + " because of an exception.");
+ return null;
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/identity/type/BuildNumber.java b/src/main/java/dev/projectg/geyserupdater/common/update/identity/type/BuildNumber.java
new file mode 100644
index 00000000..0f1cd600
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/identity/type/BuildNumber.java
@@ -0,0 +1,17 @@
+package dev.projectg.geyserupdater.common.update.identity.type;
+
+import org.jetbrains.annotations.NotNull;
+
+public class BuildNumber implements Identity {
+
+ private final int buildNumber;
+
+ public BuildNumber(int buildNumber) {
+ this.buildNumber = buildNumber;
+ }
+
+ @Override
+ public @NotNull Integer value() {
+ return buildNumber;
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/identity/type/Identity.java b/src/main/java/dev/projectg/geyserupdater/common/update/identity/type/Identity.java
new file mode 100644
index 00000000..5e8e269b
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/identity/type/Identity.java
@@ -0,0 +1,13 @@
+package dev.projectg.geyserupdater.common.update.identity.type;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Provides implementation for something that can be quantified as an age of something.
+ * @param The Type of the age.
+ */
+public interface Identity {
+
+ @Nonnull
+ T value();
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/update/identity/type/Md5FileHash.java b/src/main/java/dev/projectg/geyserupdater/common/update/identity/type/Md5FileHash.java
new file mode 100644
index 00000000..ba45e2e5
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/update/identity/type/Md5FileHash.java
@@ -0,0 +1,20 @@
+package dev.projectg.geyserupdater.common.update.identity.type;
+
+import org.jetbrains.annotations.NotNull;
+
+import javax.annotation.Nonnull;
+import java.util.Objects;
+
+public class Md5FileHash implements Identity {
+
+ private final String hash;
+
+ public Md5FileHash(@Nonnull String md5Hash) {
+ hash = Objects.requireNonNull(md5Hash);
+ }
+
+ @Override
+ public @NotNull String value() {
+ return hash;
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/util/FileUtils.java b/src/main/java/dev/projectg/geyserupdater/common/util/FileUtils.java
new file mode 100644
index 00000000..06573bc4
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/util/FileUtils.java
@@ -0,0 +1,19 @@
+package dev.projectg.geyserupdater.common.util;
+
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class FileUtils {
+
+ /**
+ * Get the file that a class resides in.
+ * @param clazz The class
+ * @return The file as a {@link Path}
+ * @throws URISyntaxException if there was a failure getting the {@link java.net.URL} of the {@link java.security.CodeSource}
+ */
+ public static Path getCodeSourceLocation(Class> clazz) throws URISyntaxException {
+ return Paths.get(clazz.getProtectionDomain().getCodeSource().getLocation().toURI());
+ }
+}
+
diff --git a/src/main/java/com/projectg/geyserupdater/common/util/OsUtils.java b/src/main/java/dev/projectg/geyserupdater/common/util/OsUtils.java
similarity index 95%
rename from src/main/java/com/projectg/geyserupdater/common/util/OsUtils.java
rename to src/main/java/dev/projectg/geyserupdater/common/util/OsUtils.java
index 473d7739..c5275e37 100644
--- a/src/main/java/com/projectg/geyserupdater/common/util/OsUtils.java
+++ b/src/main/java/dev/projectg/geyserupdater/common/util/OsUtils.java
@@ -1,4 +1,4 @@
-package com.projectg.geyserupdater.common.util;
+package dev.projectg.geyserupdater.common.util;
public class OsUtils {
diff --git a/src/main/java/dev/projectg/geyserupdater/common/util/ReflectionUtils.java b/src/main/java/dev/projectg/geyserupdater/common/util/ReflectionUtils.java
new file mode 100644
index 00000000..9002dfd3
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/util/ReflectionUtils.java
@@ -0,0 +1,25 @@
+package dev.projectg.geyserupdater.common.util;
+
+import javax.annotation.Nonnull;
+import java.lang.reflect.Field;
+
+public class ReflectionUtils {
+
+ /**
+ * Get the value of a {@link Field} in a {@link Object}
+ * @param obj The Object which contains the Field
+ * @param fieldClass The expected Class of the Field
+ * @param fieldName The name of the Field
+ * @param The expected Type of the Field's value
+ * @return The value of the field as Type T.
+ * @throws IllegalAccessException If the value was inaccessible, regardless of Field#setAccessible(true)
+ * @throws NoSuchFieldException If the Object does not contain the Field specified
+ * @throws ClassCastException If the Type of the Field found does not match the fieldClass given
+ */
+ @Nonnull
+ public static T getFieldValue(Object obj, Class fieldClass, String fieldName) throws IllegalAccessException, NoSuchFieldException, ClassCastException {
+ Field field = obj.getClass().getDeclaredField(fieldName); // get the field from the object
+ field.setAccessible(true); // allow getting the field's value regardless of accessibility
+ return fieldClass.cast(field.get(obj)); // get the value and cast it to the desired type
+ }
+}
diff --git a/src/main/java/com/projectg/geyserupdater/common/util/ScriptCreator.java b/src/main/java/dev/projectg/geyserupdater/common/util/ScriptCreator.java
similarity index 96%
rename from src/main/java/com/projectg/geyserupdater/common/util/ScriptCreator.java
rename to src/main/java/dev/projectg/geyserupdater/common/util/ScriptCreator.java
index c5883dcc..f18be179 100644
--- a/src/main/java/com/projectg/geyserupdater/common/util/ScriptCreator.java
+++ b/src/main/java/dev/projectg/geyserupdater/common/util/ScriptCreator.java
@@ -1,6 +1,6 @@
-package com.projectg.geyserupdater.common.util;
+package dev.projectg.geyserupdater.common.util;
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
import java.io.DataOutputStream;
import java.io.File;
diff --git a/src/main/java/dev/projectg/geyserupdater/common/util/SpigotUtils.java b/src/main/java/dev/projectg/geyserupdater/common/util/SpigotUtils.java
new file mode 100644
index 00000000..2b20d16c
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/util/SpigotUtils.java
@@ -0,0 +1,45 @@
+package dev.projectg.geyserupdater.common.util;
+
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Scanner;
+
+public class SpigotUtils {
+
+ private static final String VERSION_REGEX = "(\\d+.){1,2}\\d+(-SNAPSHOT|-RC\\d{1,2}){0,1}";
+
+ /**
+ * Get the latest version of GeyserUpdater from the spigot resource page
+ * @return the latest version, null if there was an error.
+ */
+ public static String getVersion(int resourceId) {
+
+ try (InputStream inputStream = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + resourceId).openStream(); Scanner scanner = new Scanner(inputStream)) {
+ StringBuilder builder = new StringBuilder();
+ while (scanner.hasNext()) {
+ builder.append(scanner.next());
+ }
+ String version = builder.toString();
+ if (!version.matches(VERSION_REGEX)) {
+ UpdaterLogger.getLogger().warn("Got unexpected string when checking version of Spigot resource " + resourceId + ": " + version);
+ }
+
+ return version;
+ } catch (IOException exception) {
+ UpdaterLogger.getLogger().error("Failed to check for updates: " + exception.getMessage());
+ return null;
+ }
+ }
+
+ public static URL getDownloadUrl(int resourceId) {
+ try {
+ return new URL("https://api.spiget.org/v2/resources/" + resourceId + "/download");
+ } catch (MalformedURLException e) {
+ throw new AssertionError("Unexpected MalformedURLException when getting download link for spigot resource");
+ }
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/common/util/WebUtils.java b/src/main/java/dev/projectg/geyserupdater/common/util/WebUtils.java
new file mode 100644
index 00000000..7d7de5ed
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/common/util/WebUtils.java
@@ -0,0 +1,97 @@
+package dev.projectg.geyserupdater.common.util;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import dev.projectg.geyserupdater.common.GeyserUpdater;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.stream.Collectors;
+
+// Full credit to GeyserMC
+// https://github.com/GeyserMC/Geyser/blob/master/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java
+
+public class WebUtils {
+
+ /**
+ * Makes a web request to the given URL and returns the body as a string
+ *
+ * @param reqURL URL to fetch
+ * @return Body contents
+ */
+ public static String getBody(URL reqURL) throws IOException {
+ HttpURLConnection con = (HttpURLConnection) reqURL.openConnection();
+ con.setRequestMethod("GET");
+ con.setRequestProperty("User-Agent", "GeyserUpdater-" + GeyserUpdater.getInstance().version); // Otherwise Java 8 fails on checking updates
+
+ return connectionToString(con);
+ }
+
+ public static String getBody(String reqURL) throws IOException {
+ return getBody(new URL(reqURL));
+ }
+
+ /**
+ * Makes a web request to the given URL and returns the body as a {@link JsonNode}.
+ *
+ * @param reqURL URL to fetch
+ * @return the response as JSON
+ */
+ public static JsonNode getJson(String reqURL) throws IOException {
+ HttpURLConnection con = (HttpURLConnection) new URL(reqURL).openConnection();
+ con.setRequestProperty("User-Agent", "GeyserUpdater-" + GeyserUpdater.getInstance().version);
+ return new ObjectMapper().readTree(con.getInputStream());
+ }
+
+ /**
+ * Downloads a file from the given URL and saves it to disk
+ *
+ * @param reqURL File to fetch
+ * @param fileLocation Location to save on disk
+ */
+ public static void downloadFile(String reqURL, Path fileLocation) throws IOException {
+ HttpURLConnection con = (HttpURLConnection) new URL(reqURL).openConnection();
+ con.setRequestProperty("User-Agent", "GeyserUpdater-" + GeyserUpdater.getInstance().version);
+ InputStream in = con.getInputStream();
+ Files.copy(in, fileLocation, StandardCopyOption.REPLACE_EXISTING);
+ // todo: need to close the inputstream or not?
+ in.close();
+ con.disconnect();
+ }
+
+
+ /**
+ * Get the string output from the passed {@link HttpURLConnection}
+ *
+ * @param con The connection to get the string from
+ * @return The body of the returned page
+ */
+ private static String connectionToString(HttpURLConnection con) throws IOException {
+ // Send the request (we don't use this but its required for getErrorStream() to work)
+ con.getResponseCode();
+
+ // Read the error message if there is one if not just read normally
+ InputStream inputStream = con.getErrorStream();
+ if (inputStream == null) {
+ inputStream = con.getInputStream();
+ }
+
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(inputStream))) {
+ String content = in.lines().collect(Collectors.joining("\n"));
+ con.disconnect();
+ return content;
+ }
+ }
+
+ /**
+ * Get the filename at the end of a url
+ * @param url The url to get the filename at the end from. Should not end with a /
+ */
+ public static String getFileName(String url) {
+ return url.substring(url.lastIndexOf("/") + 1);
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/spigot/SpigotPlayerHandler.java b/src/main/java/dev/projectg/geyserupdater/spigot/SpigotPlayerHandler.java
new file mode 100644
index 00000000..41f3a6f4
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/spigot/SpigotPlayerHandler.java
@@ -0,0 +1,33 @@
+package dev.projectg.geyserupdater.spigot;
+
+import dev.projectg.geyserupdater.common.PlayerManager;
+import org.bukkit.Server;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class SpigotPlayerHandler implements PlayerManager {
+
+ private final Server server;
+
+ public SpigotPlayerHandler(Server server) {
+ this.server = server;
+ }
+
+ @Override
+ public @NotNull List getOnlinePlayers() {
+ List uuidList = new ArrayList<>();
+ for (Player player : server.getOnlinePlayers()) {
+ uuidList.add(player.getUniqueId());
+ }
+ return uuidList;
+ }
+
+ @Override
+ public void sendMessage(@NotNull UUID uuid, @NotNull String message) {
+ server.getPlayer(uuid).sendMessage(message);
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/spigot/SpigotScheduler.java b/src/main/java/dev/projectg/geyserupdater/spigot/SpigotScheduler.java
new file mode 100644
index 00000000..2cf37149
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/spigot/SpigotScheduler.java
@@ -0,0 +1,61 @@
+package dev.projectg.geyserupdater.spigot;
+
+import dev.projectg.geyserupdater.common.scheduler.Task;
+import dev.projectg.geyserupdater.common.scheduler.UpdaterScheduler;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.scheduler.BukkitScheduler;
+import org.bukkit.scheduler.BukkitTask;
+import org.jetbrains.annotations.NotNull;
+
+import javax.annotation.Nonnull;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+public class SpigotScheduler implements UpdaterScheduler {
+
+ private final Plugin plugin;
+
+ public SpigotScheduler(@Nonnull Plugin plugin) {
+ Objects.requireNonNull(plugin);
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Task schedule(@NotNull Runnable runnable, boolean async, long delay, long repeat, TimeUnit unit) {
+ // https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/browse/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
+ // https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/browse/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
+
+ Objects.requireNonNull(runnable);
+
+ BukkitScheduler scheduler = plugin.getServer().getScheduler();
+
+ BukkitTask bukkitTask;
+ if (repeat <= 0) {
+ if (async) {
+ bukkitTask = scheduler.runTaskLaterAsynchronously(plugin, runnable, unit.toSeconds(delay) * 20); // 20 ticks in a second
+ } else {
+ bukkitTask = scheduler.runTaskLater(plugin, runnable, unit.toSeconds(delay) * 20);
+ }
+ } else {
+ if (async) {
+ bukkitTask = scheduler.runTaskTimerAsynchronously(plugin, runnable, unit.toSeconds(delay) * 20, unit.toSeconds(repeat) * 20);
+ } else {
+ bukkitTask = scheduler.runTaskTimer(plugin, runnable, unit.toSeconds(delay) * 20, unit.toSeconds(repeat) * 20);
+ }
+ }
+ return new SpigotTask(bukkitTask);
+ }
+
+ private static class SpigotTask implements Task {
+ private final BukkitTask task;
+
+ private SpigotTask(BukkitTask task) {
+ this.task = task;
+ }
+
+ @Override
+ public void cancel() {
+ task.cancel();
+ }
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/spigot/SpigotUpdater.java b/src/main/java/dev/projectg/geyserupdater/spigot/SpigotUpdater.java
new file mode 100644
index 00000000..8cfedd6e
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/spigot/SpigotUpdater.java
@@ -0,0 +1,69 @@
+package dev.projectg.geyserupdater.spigot;
+
+import dev.projectg.geyserupdater.common.GeyserUpdater;
+import dev.projectg.geyserupdater.common.UpdaterBootstrap;
+import dev.projectg.geyserupdater.common.logger.JavaUtilUpdaterLogger;
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
+import dev.projectg.geyserupdater.spigot.command.GeyserUpdateCommand;
+import dev.projectg.geyserupdater.spigot.util.CheckSpigotRestart;
+import dev.projectg.geyserupdater.spigot.util.bstats.Metrics;
+
+import org.bukkit.Server;
+import org.bukkit.plugin.java.JavaPlugin;
+import space.arim.dazzleconf.error.InvalidConfigException;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Objects;
+
+public class SpigotUpdater extends JavaPlugin implements UpdaterBootstrap {
+
+ private GeyserUpdater updater;
+
+
+ @Override
+ public void onEnable() {
+ Server server = this.getServer();
+ Path updateFolder = server.getUpdateFolderFile().toPath();
+ try {
+ updater = new GeyserUpdater(
+ this.getDataFolder().toPath(),
+ updateFolder,
+ updateFolder,
+ this,
+ new JavaUtilUpdaterLogger(getLogger()),
+ new SpigotScheduler(this),
+ new SpigotPlayerHandler(server),
+ this.getDescription().getVersion(),
+ "bootstrap/spigot/target/Geyser-Spigot.jar",
+ "bootstrap/spigot/target/floodgate-spigot.jar"
+ );
+ } catch (IOException | InvalidConfigException e) {
+ getLogger().severe("Failed to start GeyserUpdater! Disabling...");
+ e.printStackTrace();
+ return;
+ }
+
+ Objects.requireNonNull(getCommand("geyserupdate")).setExecutor(new GeyserUpdateCommand());
+ getCommand("geyserupdate").setPermission("gupdater.geyserupdate");
+ new Metrics(this, 10202);
+ }
+
+ @Override
+ public void onDisable() {
+ // bukkit has the native update folder to update jars on startup, which means we don't need to worry about modifying jars in use and when we shutdown
+ if (updater != null) {
+ try {
+ updater.shutdown();
+ } catch (IOException e) {
+ UpdaterLogger.getLogger().error("Failed to install ALL updates:");
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void createRestartScript() throws IOException {
+ CheckSpigotRestart.checkYml();
+ }
+}
diff --git a/src/main/java/com/projectg/geyserupdater/spigot/command/GeyserUpdateCommand.java b/src/main/java/dev/projectg/geyserupdater/spigot/command/GeyserUpdateCommand.java
similarity index 83%
rename from src/main/java/com/projectg/geyserupdater/spigot/command/GeyserUpdateCommand.java
rename to src/main/java/dev/projectg/geyserupdater/spigot/command/GeyserUpdateCommand.java
index 859e4ee0..a01e0d4a 100644
--- a/src/main/java/com/projectg/geyserupdater/spigot/command/GeyserUpdateCommand.java
+++ b/src/main/java/dev/projectg/geyserupdater/spigot/command/GeyserUpdateCommand.java
@@ -1,27 +1,23 @@
-package com.projectg.geyserupdater.spigot.command;
+package dev.projectg.geyserupdater.spigot.command;
-import com.projectg.geyserupdater.common.Messages;
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
-import com.projectg.geyserupdater.common.util.GeyserProperties;
-import com.projectg.geyserupdater.spigot.util.GeyserSpigotDownloader;
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
-import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
-import org.bukkit.command.ConsoleCommandSender;
-import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
-import java.io.IOException;
-
public class GeyserUpdateCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
UpdaterLogger logger = UpdaterLogger.getLogger();
+ //todo: spigot command
+
+ /*
+
if (sender instanceof Player) {
Player player = (Player) sender;
if (command.getName().equalsIgnoreCase("geyserupdate") && player.hasPermission("gupdater.geyserupdate")) {
@@ -57,6 +53,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
} else {
return false;
}
+ */
return true;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/projectg/geyserupdater/spigot/util/CheckSpigotRestart.java b/src/main/java/dev/projectg/geyserupdater/spigot/util/CheckSpigotRestart.java
similarity index 90%
rename from src/main/java/com/projectg/geyserupdater/spigot/util/CheckSpigotRestart.java
rename to src/main/java/dev/projectg/geyserupdater/spigot/util/CheckSpigotRestart.java
index 3c85fdcd..66f829c4 100644
--- a/src/main/java/com/projectg/geyserupdater/spigot/util/CheckSpigotRestart.java
+++ b/src/main/java/dev/projectg/geyserupdater/spigot/util/CheckSpigotRestart.java
@@ -1,8 +1,8 @@
-package com.projectg.geyserupdater.spigot.util;
+package dev.projectg.geyserupdater.spigot.util;
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
-import com.projectg.geyserupdater.common.util.OsUtils;
-import com.projectg.geyserupdater.common.util.ScriptCreator;
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
+import dev.projectg.geyserupdater.common.util.OsUtils;
+import dev.projectg.geyserupdater.common.util.ScriptCreator;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
diff --git a/src/main/java/com/projectg/geyserupdater/spigot/util/bstats/Metrics.java b/src/main/java/dev/projectg/geyserupdater/spigot/util/bstats/Metrics.java
similarity index 97%
rename from src/main/java/com/projectg/geyserupdater/spigot/util/bstats/Metrics.java
rename to src/main/java/dev/projectg/geyserupdater/spigot/util/bstats/Metrics.java
index 54c664b9..c58c626c 100644
--- a/src/main/java/com/projectg/geyserupdater/spigot/util/bstats/Metrics.java
+++ b/src/main/java/dev/projectg/geyserupdater/spigot/util/bstats/Metrics.java
@@ -1,10 +1,9 @@
-package com.projectg.geyserupdater.spigot.util.bstats;
+package dev.projectg.geyserupdater.spigot.util.bstats;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
-
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
@@ -13,26 +12,13 @@
import org.bukkit.plugin.ServicePriority;
import javax.net.ssl.HttpsURLConnection;
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
+import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
+import java.util.*;
+import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.zip.GZIPOutputStream;
@@ -72,7 +58,7 @@ public class Metrics {
private static final String URL = "https://bStats.org/submitData/bukkit";
// Is bStats enabled on this server?
- private boolean enabled;
+ private final boolean enabled;
// Should failed requests be logged?
private static boolean logFailedRequests;
diff --git a/src/main/java/dev/projectg/geyserupdater/velocity/VelocityPlayerHandler.java b/src/main/java/dev/projectg/geyserupdater/velocity/VelocityPlayerHandler.java
new file mode 100644
index 00000000..acb04caf
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/velocity/VelocityPlayerHandler.java
@@ -0,0 +1,34 @@
+package dev.projectg.geyserupdater.velocity;
+
+import dev.projectg.geyserupdater.common.PlayerManager;
+import com.velocitypowered.api.proxy.Player;
+import com.velocitypowered.api.proxy.ProxyServer;
+import org.jetbrains.annotations.NotNull;
+
+import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class VelocityPlayerHandler implements PlayerManager {
+
+ private final ProxyServer proxyServer;
+
+ public VelocityPlayerHandler(@Nonnull ProxyServer proxyServer) {
+ this.proxyServer = proxyServer;
+ }
+
+ @Override
+ public @NotNull List getOnlinePlayers() {
+ List uuidList = new ArrayList<>();
+ for (Player player : proxyServer.getAllPlayers()) {
+ uuidList.add(player.getUniqueId());
+ }
+ return uuidList;
+ }
+
+ @Override
+ public void sendMessage(@NotNull UUID uuid, @NotNull String message) {
+
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/velocity/VelocityScheduler.java b/src/main/java/dev/projectg/geyserupdater/velocity/VelocityScheduler.java
new file mode 100644
index 00000000..2e0999ac
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/velocity/VelocityScheduler.java
@@ -0,0 +1,45 @@
+package dev.projectg.geyserupdater.velocity;
+
+import dev.projectg.geyserupdater.common.scheduler.Task;
+import dev.projectg.geyserupdater.common.scheduler.UpdaterScheduler;
+import com.velocitypowered.api.scheduler.ScheduledTask;
+
+import javax.annotation.Nonnull;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+public class VelocityScheduler implements UpdaterScheduler {
+
+ private final VelocityUpdater plugin;
+
+ public VelocityScheduler(@Nonnull VelocityUpdater plugin) {
+ Objects.requireNonNull(plugin);
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Task schedule(@Nonnull Runnable runnable, boolean async, long delay, long repeat, @Nonnull TimeUnit unit) {
+ // https://github.com/VelocityPowered/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java
+
+ Objects.requireNonNull(runnable);
+ Objects.requireNonNull(unit);
+
+ return new VelocityTask(plugin.getProxyServer().getScheduler().buildTask(plugin, runnable)
+ .delay(delay, unit)
+ .repeat(repeat, unit)
+ .schedule());
+ }
+
+ private static class VelocityTask implements Task {
+ private final ScheduledTask task;
+
+ private VelocityTask(ScheduledTask task) {
+ this.task = task;
+ }
+
+ @Override
+ public void cancel() {
+ task.cancel();
+ }
+ }
+}
diff --git a/src/main/java/dev/projectg/geyserupdater/velocity/VelocityUpdater.java b/src/main/java/dev/projectg/geyserupdater/velocity/VelocityUpdater.java
new file mode 100644
index 00000000..0775f770
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserupdater/velocity/VelocityUpdater.java
@@ -0,0 +1,100 @@
+package dev.projectg.geyserupdater.velocity;
+
+import com.google.inject.Inject;
+import dev.projectg.geyserupdater.common.GeyserUpdater;
+import dev.projectg.geyserupdater.common.UpdaterBootstrap;
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
+import dev.projectg.geyserupdater.common.util.ScriptCreator;
+import dev.projectg.geyserupdater.velocity.command.GeyserUpdateCommand;
+import dev.projectg.geyserupdater.velocity.logger.Slf4jUpdaterLogger;
+import dev.projectg.geyserupdater.velocity.bstats.Metrics;
+import com.velocitypowered.api.event.PostOrder;
+import com.velocitypowered.api.event.Subscribe;
+import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
+import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
+import com.velocitypowered.api.plugin.Plugin;
+import com.velocitypowered.api.plugin.annotation.DataDirectory;
+import com.velocitypowered.api.proxy.ProxyServer;
+import org.slf4j.Logger;
+import space.arim.dazzleconf.error.InvalidConfigException;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+@Plugin(id = "geyserupdater", name = "GeyserUpdater", version = VelocityUpdater.VERSION,
+ description = "Automatically or manually downloads new builds of Geyser and applies them on server restart.",
+ authors = {"Jens", "Konicai"})
+public class VelocityUpdater implements UpdaterBootstrap {
+
+ public static final String VERSION = "1.6.0";
+
+ private GeyserUpdater updater;
+ private final ProxyServer server;
+ private final Path dataDirectory;
+ private final Logger logger;
+ private final Metrics.Factory metricsFactory;
+
+ @Inject
+ public VelocityUpdater(ProxyServer server, Logger baseLogger, @DataDirectory final Path dataDirectory, Metrics.Factory metricsFactory) {
+ this.server = server;
+ this.dataDirectory = dataDirectory;
+ this.logger = baseLogger;
+ this.metricsFactory = metricsFactory;
+ }
+
+ @Subscribe
+ public void onProxyInitialization(ProxyInitializeEvent event) throws IOException, InvalidConfigException {
+
+ updater = new GeyserUpdater(
+ dataDirectory,
+ dataDirectory.resolve("BuildUpdate"),
+ dataDirectory.getParent(),
+ this,
+ new Slf4jUpdaterLogger(logger),
+ new VelocityScheduler(this),
+ new VelocityPlayerHandler(server),
+ VERSION,
+ "bootstrap/velocity/target/Geyser-Velocity.jar",
+ "bootstrap/velocity/target/floodgate-velocity.jar"
+ );
+
+ // Register our only command
+ server.getCommandManager().register("geyserupdate", new GeyserUpdateCommand());
+ metricsFactory.make(this, 10673);
+ }
+
+ @Subscribe(order = PostOrder.LAST)
+ public void onShutdown(ProxyShutdownEvent event) {
+ // PostOrder.LAST ensures that we don't modify plugin jars while they are being used.
+ // Hopefully plugins that are being updated don't also use PostOrder.LAST
+ onDisable();
+ }
+
+ @Override
+ public void onDisable() {
+
+ if (updater != null) {
+ try {
+ updater.shutdown();
+ } catch (IOException e) {
+ UpdaterLogger.getLogger().error("Failed to install ALL updates:");
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void createRestartScript() throws IOException {
+ ScriptCreator.createRestartScript(true);
+ }
+
+ public ProxyServer getProxyServer() {
+ return server;
+ }
+}
+
+
+
+
+
+
diff --git a/src/main/java/com/projectg/geyserupdater/velocity/util/bstats/Metrics.java b/src/main/java/dev/projectg/geyserupdater/velocity/bstats/Metrics.java
similarity index 98%
rename from src/main/java/com/projectg/geyserupdater/velocity/util/bstats/Metrics.java
rename to src/main/java/dev/projectg/geyserupdater/velocity/bstats/Metrics.java
index c1956535..4884ae47 100644
--- a/src/main/java/com/projectg/geyserupdater/velocity/util/bstats/Metrics.java
+++ b/src/main/java/dev/projectg/geyserupdater/velocity/bstats/Metrics.java
@@ -1,36 +1,18 @@
-package com.projectg.geyserupdater.velocity.util.bstats;
+package dev.projectg.geyserupdater.velocity.bstats;
+import com.google.inject.Inject;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
-
-import com.google.inject.Inject;
-
import org.slf4j.Logger;
import javax.net.ssl.HttpsURLConnection;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
+import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.UUID;
+import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -955,7 +937,7 @@ private void writeConfig() throws IOException {
"# There is no performance penalty associated with having metrics enabled, and data sent to");
configContent.add("# bStats is fully anonymous.");
configContent.add("enabled=" + defaultEnabled);
- configContent.add("server-uuid=" + UUID.randomUUID().toString());
+ configContent.add("server-uuid=" + UUID.randomUUID());
configContent.add("log-errors=false");
configContent.add("log-sent-data=false");
configContent.add("log-response-status-text=false");
diff --git a/src/main/java/com/projectg/geyserupdater/velocity/command/GeyserUpdateCommand.java b/src/main/java/dev/projectg/geyserupdater/velocity/command/GeyserUpdateCommand.java
similarity index 72%
rename from src/main/java/com/projectg/geyserupdater/velocity/command/GeyserUpdateCommand.java
rename to src/main/java/dev/projectg/geyserupdater/velocity/command/GeyserUpdateCommand.java
index c5ddae66..51b3196b 100644
--- a/src/main/java/com/projectg/geyserupdater/velocity/command/GeyserUpdateCommand.java
+++ b/src/main/java/dev/projectg/geyserupdater/velocity/command/GeyserUpdateCommand.java
@@ -1,20 +1,15 @@
-package com.projectg.geyserupdater.velocity.command;
+package dev.projectg.geyserupdater.velocity.command;
-import com.projectg.geyserupdater.common.Messages;
-import com.projectg.geyserupdater.common.util.GeyserProperties;
-import com.projectg.geyserupdater.velocity.util.GeyserVelocityDownloader;
-
-import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.RawCommand;
-import net.kyori.adventure.text.Component;
-
-import java.io.IOException;
-
public class GeyserUpdateCommand implements RawCommand {
@Override
public void execute(final Invocation invocation) {
+
+ //todo: velocity command
+
+ /*
CommandSource source = invocation.source();
try {
@@ -30,6 +25,7 @@ public void execute(final Invocation invocation) {
source.sendMessage(Component.text(Messages.Command.FAIL_CHECK));
e.printStackTrace();
}
+ */
}
@Override
public boolean hasPermission(final Invocation invocation) {
diff --git a/src/main/java/com/projectg/geyserupdater/velocity/logger/Slf4jUpdaterLogger.java b/src/main/java/dev/projectg/geyserupdater/velocity/logger/Slf4jUpdaterLogger.java
similarity index 91%
rename from src/main/java/com/projectg/geyserupdater/velocity/logger/Slf4jUpdaterLogger.java
rename to src/main/java/dev/projectg/geyserupdater/velocity/logger/Slf4jUpdaterLogger.java
index d8fbb5f5..9bfc2fde 100644
--- a/src/main/java/com/projectg/geyserupdater/velocity/logger/Slf4jUpdaterLogger.java
+++ b/src/main/java/dev/projectg/geyserupdater/velocity/logger/Slf4jUpdaterLogger.java
@@ -1,6 +1,6 @@
-package com.projectg.geyserupdater.velocity.logger;
+package dev.projectg.geyserupdater.velocity.logger;
-import com.projectg.geyserupdater.common.logger.UpdaterLogger;
+import dev.projectg.geyserupdater.common.logger.UpdaterLogger;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;
import org.slf4j.Logger;
diff --git a/src/main/resources/bungee.yml b/src/main/resources/bungee.yml
index b2cdfbdb..a55d0c0e 100644
--- a/src/main/resources/bungee.yml
+++ b/src/main/resources/bungee.yml
@@ -1,6 +1,5 @@
name: ${project.name}
-main: com.projectg.geyserupdater.bungee.BungeeUpdater
+main: dev.projectg.geyserupdater.bungee.BungeeUpdater
version: ${project.version}
description: Automatically or manually downloads new builds of Geyser and applies them on server restart.
-author: Jens
-depends: ["Geyser-BungeeCord"]
+author: Jens, Konicai
\ No newline at end of file
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 78e0f5e8..41436ade 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -4,23 +4,60 @@
# NOTICE: Please read the README on our github page for full information regarding these options!
# https://github.com/ProjectG-Plugins/GeyserUpdater
-# If enabled, GeyserUpdater will check for new Geyser builds on server start, and on the interval specified by Auto-Update-Interval. If a new build exists, it will be downloaded.
-Auto-Update-Geyser: false
# The interval in hours between each auto update check.
-Auto-Update-Interval: 24
+auto-check-interval: 24
+# The maximum amount of seconds that a download is allowed to run for. Increase if you are running into issues on a slow internet connection.
+download-time-limit: 180
+# Delete the downloaded file if the file hash of the downloaded file did not match what the download server provided.
+# Or if the file hash was not checked and the download time limit was reached, or an exception occurred.
+# If the file hash is not correct the downloaded file is likely corrupt or unfinished.
+delete-on-fail: true
# If enabled, GeyserUpdater will attempt to restart the server 10 seconds after a new version of Geyser has been successfully downloaded.
# If you aren't using a hosting provider or a server wrapper, you will need a restart script.
-Auto-Restart-Server: false
+restart-server: false
# When enabled, GeyserUpdater will automatically generate a restart script for you. If you are using CraftBukkit or a proxy
# you will need to use the generated script to start your server! If you are using a hosting provider or a server wrapper you probably don't need this.
-Auto-Script-Generating: false
-
+auto-script-generating: false
# Configure the message that is sent to all online players warning them that the server will be restarting in 10 seconds.
-Restart-Message-Players: '&2This server will be restarting in 10 seconds!'
+restart-message-players: "§2This server will be restarting in 10 seconds!"
# Enable debug logging
-Enable-Debug: false
-
+enable-debug: false
# Please do not change this version value!
-Config-Version: 2
\ No newline at end of file
+config-version: 3
+
+
+modules:
+ GeyserHub:
+ enable: true
+ auto-check: true
+ auto-update: false
+ version:
+ jenkins:
+ # Requires plugin to have a git.properties containing a git.build.number value
+ project: "https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/master"
+ spigot:
+ # Compares version of plugin that the platform provides vs the latest version on SpigotMC
+ resource: 88555
+ github:
+ id:
+
+ download:
+ literal:
+ # Allows specifying a static download location and file
+ file: "https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/master/lastSuccessfulBuild/artifact/bootstrap/spigot/target/Geyser-Spigot.jar"
+ spigot:
+ # Downloads file from a SpigotMC listing. The download link provided on SpigotMC must lead to an immediate file download.
+ resource: 88555
+
+presets:
+ geyser:
+ enable: true
+ auto-check: true
+ auto-update: true
+ custom:
+ # Certain presets may require or allow additional data
+ branch: master
+ compare-hash: true
+
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 6e6c49fe..441c8e9f 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,9 +1,11 @@
name: ${project.name}
-main: com.projectg.geyserupdater.spigot.SpigotUpdater
+main: dev.projectg.geyserupdater.spigot.SpigotUpdater
version: ${project.version}
-depend: [Geyser-Spigot]
-description: Automatically or manually downloads new builds of Geyser and applies them on server restart.
api-version: 1.13
+description: Automatically or manually downloads new builds of Geyser and applies them on server restart.
+authors:
+ - Jens
+ - Konicai
commands:
geyserupdate: