diff --git a/src/main/java/net/frankheijden/serverutils/ServerUtils.java b/src/main/java/net/frankheijden/serverutils/ServerUtils.java index f8233f5..5d7ce8c 100644 --- a/src/main/java/net/frankheijden/serverutils/ServerUtils.java +++ b/src/main/java/net/frankheijden/serverutils/ServerUtils.java @@ -3,8 +3,12 @@ package net.frankheijden.serverutils; import co.aikar.commands.PaperCommandManager; import net.frankheijden.serverutils.commands.CommandPlugins; import net.frankheijden.serverutils.commands.CommandServerUtils; +import net.frankheijden.serverutils.config.Config; import net.frankheijden.serverutils.config.Messenger; +import net.frankheijden.serverutils.listeners.MainListener; +import net.frankheijden.serverutils.managers.VersionManager; import net.frankheijden.serverutils.reflection.*; +import net.frankheijden.serverutils.tasks.UpdateCheckerTask; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.command.*; @@ -49,6 +53,11 @@ public class ServerUtils extends JavaPlugin implements CommandExecutor { .collect(Collectors.toList())); commandManager.getCommandCompletions().registerAsyncCompletion("supportedConfigs", context -> CommandServerUtils.getSupportedConfigs()); reload(); + + Bukkit.getPluginManager().registerEvents(new MainListener(), this); + + new VersionManager(); + checkForUpdates(); } @Override @@ -81,10 +90,10 @@ public class ServerUtils extends JavaPlugin implements CommandExecutor { restoreBukkitPluginCommand(); } + new Config(copyResourceIfNotExists("config.yml")); new Messenger(copyResourceIfNotExists("messages.yml")); - YamlConfiguration config = YamlConfiguration.loadConfiguration(copyResourceIfNotExists("config.yml")); - if (!config.getBoolean("settings.disable-plugins-command", false)) { + if (!Config.getInstance().getBoolean("settings.disable-plugins-command")) { this.removeCommands("pl", "plugins"); this.commandPlugins = new CommandPlugins(); commandManager.registerCommand(commandPlugins); @@ -117,4 +126,10 @@ public class ServerUtils extends JavaPlugin implements CommandExecutor { } return file; } + + private void checkForUpdates() { + if (Config.getInstance().getBoolean("settings.check-updates")) { + UpdateCheckerTask.start(Bukkit.getConsoleSender(), true); + } + } } diff --git a/src/main/java/net/frankheijden/serverutils/config/Config.java b/src/main/java/net/frankheijden/serverutils/config/Config.java new file mode 100644 index 0000000..b4adf84 --- /dev/null +++ b/src/main/java/net/frankheijden/serverutils/config/Config.java @@ -0,0 +1,39 @@ +package net.frankheijden.serverutils.config; + +import net.frankheijden.serverutils.ServerUtils; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; + +public class Config { + + private static Defaults DEFAULT_CONFIG = Defaults.of( + "settings", Defaults.of( + "disable-plugins-command", false, + "check-updates", true, + "download-updates", false, + "download-at-startup-and-update", false + ) + ); + + private static final ServerUtils plugin = ServerUtils.getInstance(); + private static Config instance; + private final YamlConfiguration config; + + public Config(File file) { + instance = this; + config = Defaults.init(file, DEFAULT_CONFIG); + } + + public static Config getInstance() { + return instance; + } + + public YamlConfiguration getConfig() { + return config; + } + + public boolean getBoolean(String path) { + return config.getBoolean(path, (boolean) DEFAULT_CONFIG.get(path)); + } +} diff --git a/src/main/java/net/frankheijden/serverutils/config/Defaults.java b/src/main/java/net/frankheijden/serverutils/config/Defaults.java index a7cfe76..b640d67 100644 --- a/src/main/java/net/frankheijden/serverutils/config/Defaults.java +++ b/src/main/java/net/frankheijden/serverutils/config/Defaults.java @@ -2,6 +2,8 @@ package net.frankheijden.serverutils.config; import org.bukkit.configuration.file.YamlConfiguration; +import java.io.File; +import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; @@ -35,4 +37,33 @@ public class Defaults { } } } + + public Object get(String path) { + return get(this, path); + } + + private Object get(Defaults defaults, String path) { + String[] split = path.split("\\."); + if (split.length > 1) { + return get((Defaults) defaults.rootMap.get(split[0]), path.substring(split[0].length() + 1)); + } + return defaults.rootMap.get(split[0]); + } + + public static YamlConfiguration init(File file, Defaults defaults) { + YamlConfiguration yml = YamlConfiguration.loadConfiguration(file); + Defaults.addDefaults(defaults, yml); + + try { + // Idk somehow the order messes up + // of the messages if we don't do this + file.delete(); + file.createNewFile(); + + yml.save(file); + } catch (IOException ex) { + ex.printStackTrace(); + } + return yml; + } } diff --git a/src/main/java/net/frankheijden/serverutils/config/Messenger.java b/src/main/java/net/frankheijden/serverutils/config/Messenger.java index 61fe579..1c46fb2 100644 --- a/src/main/java/net/frankheijden/serverutils/config/Messenger.java +++ b/src/main/java/net/frankheijden/serverutils/config/Messenger.java @@ -6,45 +6,56 @@ import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.YamlConfiguration; import java.io.File; -import java.io.IOException; public class Messenger { - private static final Defaults DEFAULT_MESSAGES; - static { - DEFAULT_MESSAGES = Defaults.of( - "serverutils", Defaults.of( - "success", "&3Successfully %action%ed &b%what%&3!", - "warning", "&3Successfully %action%ed &b%what%&3, but with warnings.", - "error", "&cAn error occurred while %action%ing &4%what%&c, please check the console!", - "not_exists", "&cAn error occurred while %action%ing &4%what%&c, plugin does not exist!", - "not_enabled", "&cAn error occurred while %action%ing &4%what%&c, plugin is not enabled!", - "already_enabled", "&cAn error occurred while %action%ing &4%what%&c, plugin is already enabled!", - "file_changed", "&cAccessing the jar file while %action%ing &4%what%&c went wrong, please load the plugin manually!", - "help", Defaults.of( - "header", "&8&m-------------=&r&8[ &b&lServerUtils Help&r &8]&m=---------------", - "format", "&8/&3%command%&b%subcommand% &f(&7%help%&f)", - "footer", "&8&m-------------------------------------------------" - ), - "plugins", Defaults.of( - "header", "&8&m------------=&r&8[ &b&lServerUtils Plugins&r &8]&m=-------------", - "format", "&3%plugin%", - "seperator", "&b, ", - "last_seperator", " &band ", - "version", " &8(&a%version%&8)", - "footer", "&8&m-------------------------------------------------" - ), - "plugininfo", Defaults.of( - "header", "&8&m-----------=&r&8[ &b&lServerUtils PluginInfo&r &8]&m=-----------", - "format", " &3%key%&8: &b%value%", - "list_format", "&b%value%", - "seperator", "&8, ", - "last_seperator", " &8and ", - "footer", "&8&m-------------------------------------------------" - ) - ) - ); - } + private static final Defaults DEFAULT_MESSAGES = Defaults.of( + "serverutils", Defaults.of( + "success", "&3Successfully %action%ed &b%what%&3!", + "warning", "&3Successfully %action%ed &b%what%&3, but with warnings.", + "error", "&cAn error occurred while %action%ing &4%what%&c, please check the console!", + "not_exists", "&cAn error occurred while %action%ing &4%what%&c, plugin does not exist!", + "not_enabled", "&cAn error occurred while %action%ing &4%what%&c, plugin is not enabled!", + "already_enabled", "&cAn error occurred while %action%ing &4%what%&c, plugin is already enabled!", + "file_changed", "&cAccessing the jar file while %action%ing &4%what%&c went wrong, please load the plugin manually!", + "update", Defaults.of( + "available", "&8&m------------=&r&8[ &b&lServerUtils Update&r &8]&m=--------------\n" + + " &3Current version: &b%old%\n" + + " &3New version: &b%new%\n" + + " &3Release info: &b%info%\n" + + "&8&m-------------------------------------------------", + "downloading", "&8&m------------=&r&8[ &b&lServerUtils Update&r &8]&m=--------------\n" + + " &3A new version of ServerUtils will be downloaded and installed after a restart!\n" + + " &3Current version: &b%old%\n" + + " &3New version: &b%new%\n" + + " &3Release info: &b%info%\n" + + "&8&m-------------------------------------------------", + "download_failed", "&cFailed to download version %new% of ServerUtils. Please update manually.", + "download_success", "&3ServerUtils has been downloaded and will be installed on the next restart." + ), + "help", Defaults.of( + "header", "&8&m-------------=&r&8[ &b&lServerUtils Help&r &8]&m=---------------", + "format", "&8/&3%command%&b%subcommand% &f(&7%help%&f)", + "footer", "&8&m-------------------------------------------------" + ), + "plugins", Defaults.of( + "header", "&8&m------------=&r&8[ &b&lServerUtils Plugins&r &8]&m=-------------", + "format", "&3%plugin%", + "seperator", "&b, ", + "last_seperator", " &band ", + "version", " &8(&a%version%&8)", + "footer", "&8&m-------------------------------------------------" + ), + "plugininfo", Defaults.of( + "header", "&8&m-----------=&r&8[ &b&lServerUtils PluginInfo&r &8]&m=-----------", + "format", " &3%key%&8: &b%value%", + "list_format", "&b%value%", + "seperator", "&8, ", + "last_seperator", " &8and ", + "footer", "&8&m-------------------------------------------------" + ) + ) + ); private static final ServerUtils plugin = ServerUtils.getInstance(); private static Messenger instance; @@ -52,19 +63,7 @@ public class Messenger { public Messenger(File file) { instance = this; - messages = YamlConfiguration.loadConfiguration(file); - Defaults.addDefaults(DEFAULT_MESSAGES, messages); - - try { - // Idk somehow the order messes up - // of the messages if we don't do this - file.delete(); - file.createNewFile(); - - messages.save(file); - } catch (IOException ex) { - ex.printStackTrace(); - } + messages = Defaults.init(file, DEFAULT_MESSAGES); } public static String getMessage(String path, String... replacements) { diff --git a/src/main/java/net/frankheijden/serverutils/listeners/MainListener.java b/src/main/java/net/frankheijden/serverutils/listeners/MainListener.java new file mode 100644 index 0000000..799a4e6 --- /dev/null +++ b/src/main/java/net/frankheijden/serverutils/listeners/MainListener.java @@ -0,0 +1,17 @@ +package net.frankheijden.serverutils.listeners; + +import net.frankheijden.serverutils.tasks.UpdateCheckerTask; +import org.bukkit.entity.Player; +import org.bukkit.event.*; +import org.bukkit.event.player.PlayerJoinEvent; + +public class MainListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + if (player.hasPermission("serverutils.notification.update")) { + UpdateCheckerTask.start(player); + } + } +} diff --git a/src/main/java/net/frankheijden/serverutils/managers/VersionManager.java b/src/main/java/net/frankheijden/serverutils/managers/VersionManager.java new file mode 100644 index 0000000..d8d844f --- /dev/null +++ b/src/main/java/net/frankheijden/serverutils/managers/VersionManager.java @@ -0,0 +1,41 @@ +package net.frankheijden.serverutils.managers; + +import net.frankheijden.serverutils.ServerUtils; + +public class VersionManager { + + private static VersionManager instance; + private final ServerUtils plugin = ServerUtils.getInstance(); + private final String currentVersion; + private String downloadedVersion; + + public VersionManager() { + instance = this; + this.currentVersion = plugin.getDescription().getVersion(); + this.downloadedVersion = currentVersion; + } + + public static VersionManager getInstance() { + return instance; + } + + public String getCurrentVersion() { + return currentVersion; + } + + public String getDownloadedVersion() { + return downloadedVersion; + } + + public boolean hasDownloaded() { + return !downloadedVersion.equals(currentVersion); + } + + public boolean isDownloadedVersion(String version) { + return downloadedVersion.equals(version); + } + + public void setDownloadedVersion(String downloadedVersion) { + this.downloadedVersion = downloadedVersion; + } +} diff --git a/src/main/java/net/frankheijden/serverutils/tasks/UpdateCheckerTask.java b/src/main/java/net/frankheijden/serverutils/tasks/UpdateCheckerTask.java new file mode 100644 index 0000000..91da8c8 --- /dev/null +++ b/src/main/java/net/frankheijden/serverutils/tasks/UpdateCheckerTask.java @@ -0,0 +1,195 @@ +package net.frankheijden.serverutils.tasks; + +import com.google.gson.*; +import net.frankheijden.serverutils.ServerUtils; +import net.frankheijden.serverutils.config.Config; +import net.frankheijden.serverutils.config.Messenger; +import net.frankheijden.serverutils.managers.*; +import net.frankheijden.serverutils.utils.VersionUtils; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.InvalidDescriptionException; +import org.bukkit.plugin.InvalidPluginException; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.*; +import java.lang.reflect.Method; +import java.net.*; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.charset.StandardCharsets; +import java.util.logging.Level; + +public class UpdateCheckerTask implements Runnable { + + private static final ServerUtils plugin = ServerUtils.getInstance(); + private static final VersionManager versionManager = VersionManager.getInstance(); + private final CommandSender sender; + private final String currentVersion; + private final boolean startup; + + private static final String GITHUB_LINK = "https://api.github.com/repos/FrankHeijden/ServerUtils/releases/latest"; + + private UpdateCheckerTask(CommandSender sender, boolean startup) { + this.sender = sender; + this.currentVersion = plugin.getDescription().getVersion(); + this.startup = startup; + } + + public static void start(CommandSender sender) { + start(sender, false); + } + + public static void start(CommandSender sender, boolean startup) { + UpdateCheckerTask task = new UpdateCheckerTask(sender, startup); + Bukkit.getScheduler().runTaskAsynchronously(plugin, task); + } + + public boolean isStartupCheck() { + return this.startup; + } + + @Override + public void run() { + if (isStartupCheck()) { + plugin.getLogger().info("Checking for updates..."); + } + + JsonObject jsonObject; + try { + jsonObject = readJsonFromURL(GITHUB_LINK).getAsJsonObject(); + } catch (ConnectException | UnknownHostException ex) { + plugin.getLogger().severe(String.format("Error fetching new version of ServerUtils: (%s) %s (maybe check your connection?)", + ex.getClass().getSimpleName(), ex.getMessage())); + return; + } catch (FileNotFoundException ex) { + plugin.getLogger().severe(String.format("Error fetching new version of ServerUtils: (%s) %s (no update available)", + ex.getClass().getSimpleName(), ex.getMessage())); + return; + } catch (IOException ex) { + plugin.getLogger().log(Level.SEVERE, ex, () -> "Error fetching new version of ServerUtils"); + return; + } + String githubVersion = jsonObject.getAsJsonPrimitive("tag_name").getAsString(); + githubVersion = githubVersion.replace("v", ""); + String body = jsonObject.getAsJsonPrimitive("body").getAsString(); + + JsonArray assets = jsonObject.getAsJsonArray("assets"); + String downloadLink = null; + if (assets != null && assets.size() > 0) { + downloadLink = assets.get(0) + .getAsJsonObject() + .getAsJsonPrimitive("browser_download_url") + .getAsString(); + } + if (VersionUtils.isNewVersion(currentVersion, githubVersion)) { + if (isStartupCheck()) { + plugin.getLogger().info(String.format("ServerUtils %s is available!", githubVersion)); + plugin.getLogger().info("Release info: " + body); + } + if (canDownloadPlugin()) { + if (isStartupCheck()) { + plugin.getLogger().info("Started downloading from \"" + downloadLink + "\"..."); + } else { + Messenger.sendMessage(sender, "serverutils.update.downloading", + "%old%", currentVersion, + "%new%", githubVersion, + "%info%", body); + } + downloadPlugin(githubVersion, downloadLink); + } else if (!isStartupCheck()) { + Messenger.sendMessage(sender, "serverutils.update.available", + "%old%", currentVersion, + "%new%", githubVersion, + "%info%", body); + } + } else if (versionManager.hasDownloaded()) { + Messenger.sendMessage(sender, "serverutils.update.success", + "%new%", versionManager.getDownloadedVersion()); + } else if (isStartupCheck()) { + plugin.getLogger().info("We are up-to-date!"); + } + } + + private boolean canDownloadPlugin() { + if (isStartupCheck()) return Config.getInstance().getBoolean("settings.download-at-startup-and-update"); + return Config.getInstance().getBoolean("settings.download-updates"); + } + + private void downloadPlugin(String githubVersion, String downloadLink) { + if (versionManager.isDownloadedVersion(githubVersion)) { + broadcastDownloadStatus(githubVersion, false); + return; + } + + if (downloadLink == null) { + broadcastDownloadStatus(githubVersion, true); + return; + } + + try { + download(downloadLink, getPluginFile()); + } catch (IOException ex) { + broadcastDownloadStatus(githubVersion, true); + throw new RuntimeException("Error downloading a new version of ServerUtils", ex); + } + + if (isStartupCheck()) { + plugin.getLogger().info(String.format("Downloaded ServerUtils version v%s. Restarting plugin now...", githubVersion)); + Bukkit.getPluginManager().disablePlugin(plugin); + try { + Bukkit.getPluginManager().enablePlugin(Bukkit.getPluginManager().loadPlugin(getPluginFile())); + } catch (InvalidPluginException | InvalidDescriptionException ex) { + ex.printStackTrace(); + return; + } + plugin.getLogger().info(String.format("Successfully upgraded ServerUtils to v%s!", githubVersion)); + } else { + versionManager.setDownloadedVersion(githubVersion); + broadcastDownloadStatus(githubVersion, false); + } + } + + private void broadcastDownloadStatus(String githubVersion, boolean isError) { + final String path = "serverutils.update." + (isError ? "failed" : "success"); + Bukkit.getOnlinePlayers().forEach((p) -> { + if (p.hasPermission("serverutils.notification.update")) { + Messenger.sendMessage(sender, path, "%new%", githubVersion); + } + }); + } + + private File getPluginFile() { + try { + Method method = JavaPlugin.class.getDeclaredMethod("getFile"); + method.setAccessible(true); + return (File) method.invoke(plugin); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Error retrieving current plugin file", ex); + } + } + + private void download(String urlString, File target) throws IOException { + URL url = new URL(urlString); + ReadableByteChannel rbc = Channels.newChannel(url.openStream()); + FileOutputStream fos = new FileOutputStream(target); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + } + + private String readAll(BufferedReader reader) throws IOException { + StringBuilder sb = new StringBuilder(); + int cp; + while ((cp = reader.read()) != -1) { + sb.append((char) cp); + } + return sb.toString(); + } + + private JsonElement readJsonFromURL(String url) throws IOException { + try (InputStream is = new URL(url).openStream()) { + BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + String jsonText = readAll(reader); + return new JsonParser().parse(jsonText); + } + } +} diff --git a/src/main/java/net/frankheijden/serverutils/utils/VersionUtils.java b/src/main/java/net/frankheijden/serverutils/utils/VersionUtils.java new file mode 100644 index 0000000..a44849b --- /dev/null +++ b/src/main/java/net/frankheijden/serverutils/utils/VersionUtils.java @@ -0,0 +1,21 @@ +package net.frankheijden.serverutils.utils; + +public class VersionUtils { + + public static boolean isNewVersion(String oldVersion, String newVersion) { + String[] oldVersionSplit = oldVersion.split("\\."); + String[] newVersionSplit = newVersion.split("\\."); + + int i = 0; + while (i < oldVersionSplit.length && i < newVersionSplit.length) { + int o = Integer.parseInt(oldVersionSplit[i]); + int n = Integer.parseInt(newVersionSplit[i]); + if (i != oldVersionSplit.length - 1 && i != newVersionSplit.length - 1) { + if (n < o) return false; + } + if (n > o) return true; + i++; + } + return false; + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index ba5ce43..9e26dfe 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,2 +1 @@ -settings: - disable-plugins-command: false \ No newline at end of file +{} \ No newline at end of file