diff --git a/Plan/src/main/java/com/djrapitops/plan/command/PlanBungeeCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/PlanBungeeCommand.java index 2636189cf..8472f44da 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/PlanBungeeCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/PlanBungeeCommand.java @@ -4,6 +4,7 @@ import com.djrapitops.plan.PlanBungee; import com.djrapitops.plan.command.commands.*; import com.djrapitops.plan.command.commands.manage.ManageConDebugCommand; import com.djrapitops.plan.system.settings.Permissions; +import com.djrapitops.plan.system.settings.Settings; import com.djrapitops.plan.system.settings.locale.Locale; import com.djrapitops.plan.system.settings.locale.Msg; import com.djrapitops.plugin.command.CommandNode; @@ -49,6 +50,7 @@ public class PlanBungeeCommand extends TreeCmdNode { new BungeeSetupToggleCommand(), new ReloadCommand(plugin), new StatusCommand<>(plugin, Permissions.MANAGE.getPermission(), plugin.getColorScheme()), + (Settings.ALLOW_UPDATE.isTrue() ? new UpdateCommand() : null) } ); } diff --git a/Plan/src/main/java/com/djrapitops/plan/command/PlanCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/PlanCommand.java index 68ce5d31e..6b2ca3f1c 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/PlanCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/PlanCommand.java @@ -46,7 +46,8 @@ public class PlanCommand extends TreeCmdNode { new ReloadCommand(plugin), new ManageCommand(plugin, this), new StatusCommand<>(plugin, Permissions.MANAGE.getPermission(), plugin.getColorScheme()), - (Settings.DEV_MODE.isTrue() ? new DevCommand() : null) + (Settings.DEV_MODE.isTrue() ? new DevCommand() : null), + (Settings.ALLOW_UPDATE.isTrue() ? new UpdateCommand() : null) } ); } diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/UpdateCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/UpdateCommand.java new file mode 100644 index 000000000..ad5f473ad --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/UpdateCommand.java @@ -0,0 +1,210 @@ +package com.djrapitops.plan.command.commands; + +import com.djrapitops.plan.api.exceptions.connection.*; +import com.djrapitops.plan.api.exceptions.database.DBException; +import com.djrapitops.plan.command.commands.manage.ManageConDebugCommand; +import com.djrapitops.plan.system.database.databases.Database; +import com.djrapitops.plan.system.database.databases.operation.FetchOperations; +import com.djrapitops.plan.system.info.InfoSystem; +import com.djrapitops.plan.system.info.request.CheckConnectionRequest; +import com.djrapitops.plan.system.info.request.UpdateCancelRequest; +import com.djrapitops.plan.system.info.server.Server; +import com.djrapitops.plan.system.settings.Permissions; +import com.djrapitops.plan.system.update.VersionCheckSystem; +import com.djrapitops.plan.system.update.VersionInfo; +import com.djrapitops.plan.system.webserver.WebServerSystem; +import com.djrapitops.plugin.api.utility.log.Log; +import com.djrapitops.plugin.command.CommandNode; +import com.djrapitops.plugin.command.CommandType; +import com.djrapitops.plugin.command.ISender; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** + * Command that updates all servers in the network + * + * @author Rsl1122 + */ +public class UpdateCommand extends CommandNode { + + public UpdateCommand() { + super("update", Permissions.MANAGE.getPermission(), CommandType.ALL); + setArguments("[-update]/[cancel]"); + setShortHelp("Get change log link or update plugin."); + setInDepthHelp( + "/plan update", + " Used to update the plugin on the next shutdown\n", + " /plan update - get change log link", + " /plan update -update - Schedule update to happen on all network servers that are online next time they reboot.", + " /plan update cancel - Cancel scheduled update on servers that haven't rebooted yet." + ); + } + + @Override + public void onCommand(ISender sender, String commandLabel, String[] args) { + if (!VersionCheckSystem.isNewVersionAvailable()) { + sender.sendMessage("§aYou're running the latest version of Plan."); + return; + } + + VersionInfo available = VersionCheckSystem.getInstance().getNewVersionAvailable(); + String downloadUrl = available.getDownloadUrl(); + + if (!available.isTrusted()) { + sender.sendMessage("§cVersion download url did not start with " + + "https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/ " + + "and might not be trusted. You can download this version manually here (Direct download):"); + sender.sendLink(downloadUrl, downloadUrl); + return; + } + + if (args.length == 0) { + sender.sendLink("Change Log v" + available.getVersion().toString() + ": ", "Click me", available.getChangeLogUrl()); + return; + } + + String firstArgument = args[0]; + if ("-update".equals(firstArgument)) { + handleUpdate(sender, args); + } else if ("cancel".equals(firstArgument)) { + cancel(sender); + } else { + throw new IllegalArgumentException("Unknown argument, use '-update' or 'cancel'"); + } + } + + private void cancel(ISender sender) { + try { + cancel(sender, Database.getActive().fetch().getServers()); + sender.sendMessage("§aUpdate has been cancelled."); + } catch (DBException e) { + sender.sendMessage("§cDatabase error occurred, cancel could not be performed."); + Log.toLog(this.getClass().getName(), e); + } + } + + private void handleUpdate(ISender sender, String[] args) { + sender.sendMessage("§aYou can cancel the update on servers that haven't rebooted yet with /plan update cancel."); + sender.sendMessage("Checking that all servers are online.."); + if (!checkNetworkStatus(sender)) { + sender.sendMessage("§cNot all servers were online or accessible, you can still update available servers using -force as a 2nd argument."); + if (args.length <= 1 || !"-force".equals(args[1])) { + return; + } + } + try { + List servers = Database.getActive().fetch().getServers(); + update(sender, servers); + } catch (DBException e) { + Log.toLog(this.getClass().getName(), e); + } + } + + private void update(ISender sender, List servers) { + for (Server server : servers) { + if (update(sender, server)) { + sender.sendMessage("§a" + server.getName() + " scheduled for update."); + } else { + sender.sendMessage("§cUpdate failed on a server, cancelling update on all servers.."); + cancel(sender, servers); + sender.sendMessage("§cUpdate cancelled."); + break; + } + } + } + + private void cancel(ISender sender, List servers) { + for (Server server : servers) { + cancel(sender, server); + } + + } + + private void cancel(ISender sender, Server server) { + try { + InfoSystem.getInstance().getConnectionSystem().sendInfoRequest(new UpdateCancelRequest(), server); + } catch (ForbiddenException | GatewayException | InternalErrorException e) { + sender.sendMessage("§cCancel failed on " + server.getName() + ": Odd Exception: " + e.getClass().getSimpleName()); + } catch (UnauthorizedServerException e) { + sender.sendMessage("§cCancel failed on " + server.getName() + ": Unauthorized. " + server.getName() + " might be using different database."); + } catch (ConnectionFailException e) { + sender.sendMessage("§cCancel failed on " + server.getName() + ": " + e.getCause().getClass().getSimpleName() + " " + e.getCause().getMessage()); + String address = server.getWebAddress(); + boolean local = address.contains("localhost") + || address.startsWith("https://:") // IP empty = Localhost + || address.startsWith("http://:") // IP empty = Localhost + || address.contains("127.0.0.1"); + if (!local) { + sender.sendMessage("§cNon-local address, check that port is open"); + } + } catch (NotFoundException e) { + /* Ignored, older version */ + } catch (WebException e) { + sender.sendMessage("§cCancel failed on " + server.getName() + ": Odd Exception:" + e.getClass().getSimpleName()); + } + } + + private boolean update(ISender sender, Server server) { + try { + InfoSystem.getInstance().getConnectionSystem().sendInfoRequest(new CheckConnectionRequest(), server); + return true; + } catch (BadRequestException e) { + sender.sendMessage("§c" + server.getName() + " has Allow-Update set to false, aborting update."); + return false; + } catch (ForbiddenException | GatewayException | InternalErrorException e) { + sender.sendMessage("§c" + server.getName() + ": Odd Exception: " + e.getClass().getSimpleName()); + return false; + } catch (UnauthorizedServerException e) { + sender.sendMessage("§cFail reason: Unauthorized. " + server.getName() + " might be using different database."); + return false; + } catch (ConnectionFailException e) { + sender.sendMessage("§cFail reason: " + e.getCause().getClass().getSimpleName() + " " + e.getCause().getMessage()); + String address = server.getWebAddress(); + boolean local = address.contains("localhost") + || address.startsWith("https://:") // IP empty = Localhost + || address.startsWith("http://:") // IP empty = Localhost + || address.contains("127.0.0.1"); + if (!local) { + sender.sendMessage("§cNon-local address, check that port is open"); + } + return false; + } catch (NotFoundException e) { + sender.sendMessage("§e" + server.getName() + " is using older version and can not be scheduled for update. " + + "You can update it manually, update will proceed."); + return true; + } catch (WebException e) { + sender.sendMessage("§eOdd Exception: " + e.getClass().getSimpleName()); + return false; + } + } + + private boolean checkNetworkStatus(ISender sender) { + try { + FetchOperations fetch = Database.getActive().fetch(); + Optional bungeeInformation = fetch.getBungeeInformation(); + if (!bungeeInformation.isPresent()) { + return true; + } + Map bukkitServers = fetch.getBukkitServers(); + String accessAddress = WebServerSystem.getInstance().getWebServer().getAccessAddress(); + boolean success = true; + for (Server server : bukkitServers.values()) { + if (!ManageConDebugCommand.testServer(sender, accessAddress, server)) { + success = false; + } + } + Server bungee = bungeeInformation.get(); + if (!ManageConDebugCommand.testServer(sender, accessAddress, bungee)) { + success = false; + } + return success; + } catch (DBException e) { + sender.sendMessage("§cDatabase error occurred, update has been cancelled."); + Log.toLog(this.getClass().getName(), e); + return false; + } + } +} \ No newline at end of file diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageConDebugCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageConDebugCommand.java index 9a8f8055e..7414538e8 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageConDebugCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageConDebugCommand.java @@ -28,12 +28,9 @@ import java.util.UUID; */ public class ManageConDebugCommand extends CommandNode { - private final ColorScheme cs; - public ManageConDebugCommand() { super("con", Permissions.MANAGE.getPermission(), CommandType.ALL); setShortHelp("Debug Bukkit-Bungee Connections"); - cs = PlanPlugin.getInstance().getColorScheme(); } @Override @@ -68,18 +65,19 @@ public class ManageConDebugCommand extends CommandNode { } } - private void testServer(ISender sender, String accessAddress, Server server) { + public static boolean testServer(ISender sender, String accessAddress, Server server) { String address = server.getWebAddress().toLowerCase(); boolean usingHttps = address.startsWith("https"); boolean local = address.contains("localhost") - || address.startsWith("https://:") - || address.startsWith("http://:") + || address.startsWith("https://:") // IP empty = Localhost + || address.startsWith("http://:") // IP empty = Localhost || address.contains("127.0.0.1"); try { InfoSystem.getInstance().getConnectionSystem().sendInfoRequest(new CheckConnectionRequest(accessAddress), server); sender.sendMessage(getMsgFor(address, usingHttps, local, true, true)); + return true; } catch (ForbiddenException | BadRequestException | InternalErrorException e) { sender.sendMessage(getMsgFor(address, usingHttps, local, false, false)); @@ -102,9 +100,11 @@ public class ManageConDebugCommand extends CommandNode { sender.sendMessage(getMsgFor(address, usingHttps, local, false, false)); sender.sendMessage("§eOdd Exception: " + e.getClass().getSimpleName()); } + return false; } - private String getMsgFor(String address, boolean usingHttps, boolean local, boolean successTo, boolean successFrom) { + private static String getMsgFor(String address, boolean usingHttps, boolean local, boolean successTo, boolean successFrom) { + ColorScheme cs = PlanPlugin.getInstance().getColorScheme(); String tCol = cs.getTertiaryColor(); String sCol = cs.getSecondaryColor(); return tCol + address + sCol + ": " diff --git a/Plan/src/main/java/com/djrapitops/plan/system/info/connection/ConnectionSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/info/connection/ConnectionSystem.java index 546c8a884..b53777ecc 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/info/connection/ConnectionSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/info/connection/ConnectionSystem.java @@ -125,6 +125,9 @@ public abstract class ConnectionSystem implements SubSystem { putRequest(requests, SaveDBSettingsRequest.createHandler()); putRequest(requests, SendDBSettingsRequest.createHandler()); putRequest(requests, CheckConnectionRequest.createHandler()); + + putRequest(requests, UpdateRequest.createHandler()); + putRequest(requests, UpdateCancelRequest.createHandler()); return requests; } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/info/request/UpdateCancelRequest.java b/Plan/src/main/java/com/djrapitops/plan/system/info/request/UpdateCancelRequest.java new file mode 100644 index 000000000..e7aeebc55 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/info/request/UpdateCancelRequest.java @@ -0,0 +1,37 @@ +/* + * Licence is provided in the jar as license.yml also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml + */ +package com.djrapitops.plan.system.info.request; + +import com.djrapitops.plan.system.update.ShutdownUpdateHook; +import com.djrapitops.plan.system.webserver.response.DefaultResponses; +import com.djrapitops.plan.system.webserver.response.Response; + +import java.util.Map; + +/** + * InfoRequest used for Updating the plugin on a network. + * + * @author Rsl1122 + */ +public class UpdateCancelRequest implements InfoRequest { + + public UpdateCancelRequest() { + } + + public static UpdateCancelRequest createHandler() { + return new UpdateCancelRequest(); + } + + @Override + public void runLocally() { + ShutdownUpdateHook.deActivate(); + } + + @Override + public Response handleRequest(Map variables) { + ShutdownUpdateHook.deActivate(); + return DefaultResponses.SUCCESS.get(); + } +} \ No newline at end of file diff --git a/Plan/src/main/java/com/djrapitops/plan/system/info/request/UpdateRequest.java b/Plan/src/main/java/com/djrapitops/plan/system/info/request/UpdateRequest.java new file mode 100644 index 000000000..c724d1abb --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/info/request/UpdateRequest.java @@ -0,0 +1,43 @@ +/* + * Licence is provided in the jar as license.yml also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml + */ +package com.djrapitops.plan.system.info.request; + +import com.djrapitops.plan.system.settings.Settings; +import com.djrapitops.plan.system.update.ShutdownUpdateHook; +import com.djrapitops.plan.system.webserver.response.DefaultResponses; +import com.djrapitops.plan.system.webserver.response.Response; +import com.djrapitops.plan.system.webserver.response.api.BadRequestResponse; + +import java.util.Map; + +/** + * InfoRequest used for Updating the plugin on a network. + * + * @author Rsl1122 + */ +public class UpdateRequest implements InfoRequest { + + public UpdateRequest() { + } + + public static UpdateRequest createHandler() { + return new UpdateRequest(); + } + + @Override + public void runLocally() { + new ShutdownUpdateHook().register(); + } + + @Override + public Response handleRequest(Map variables) { + if (Settings.ALLOW_UPDATE.isTrue()) { + new ShutdownUpdateHook().register(); + return DefaultResponses.SUCCESS.get(); + } else { + return new BadRequestResponse("Update not allowed on this server"); + } + } +} \ No newline at end of file diff --git a/Plan/src/main/java/com/djrapitops/plan/system/settings/Settings.java b/Plan/src/main/java/com/djrapitops/plan/system/settings/Settings.java index 63a8f01d7..a4fc77b0b 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/settings/Settings.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/settings/Settings.java @@ -37,6 +37,8 @@ public enum Settings { DISPLAY_PLAYER_IPS("Customization.Display.PlayerIPs"), DISPLAY_GAPS_IN_GRAPH_DATA("Customization.Display.GapsInGraphData"), DATA_GEOLOCATIONS("Data.Geolocations"), + ALLOW_UPDATE("Plugin.Allow-Update-Command"), + NOTIFY_ABOUT_DEV_RELEASES("Plugin.Notify-About-DEV-Releases"), // Integer WEBSERVER_PORT("WebServer.Port"), diff --git a/Plan/src/main/java/com/djrapitops/plan/system/update/ShutdownUpdateHook.java b/Plan/src/main/java/com/djrapitops/plan/system/update/ShutdownUpdateHook.java new file mode 100644 index 000000000..99f5b1ee6 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/update/ShutdownUpdateHook.java @@ -0,0 +1,104 @@ +package com.djrapitops.plan.system.update; + +import com.djrapitops.plan.PlanPlugin; +import com.djrapitops.plugin.api.Check; +import com.djrapitops.plugin.api.utility.Version; +import com.djrapitops.plugin.api.utility.log.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; + +/** + * Shutdown hook that updates the plugin on server shutdown. + *

+ * Does not perform update on force close. + * + * @author Rsl1122 + */ +public class ShutdownUpdateHook extends Thread { + + private static boolean activated = false; + + private static boolean isActivated() { + return activated; + } + + private static void activate(ShutdownUpdateHook hook) { + activated = true; + Runtime.getRuntime().addShutdownHook(hook); + } + + public static void deActivate() { + activated = false; + Log.infoColor("§aUpdate has been cancelled."); + } + + public void register() { + if (isActivated()) { + return; + } + Log.infoColor("§aUpdate has been scheduled, The new jar will be downloaded on server shutdown."); + activate(this); + } + + @Override + public void run() { + if (!activated) { + return; + } + activated = false; + VersionInfo available = VersionCheckSystem.getInstance().getNewVersionAvailable(); + + if (!Version.isNewVersionAvailable(new Version(VersionCheckSystem.getCurrentVersion()), available.getVersion())) { + return; + } + + File dataFolder = PlanPlugin.getInstance().getDataFolder(); + File pluginsFolder = Check.isSpongeAvailable() + ? dataFolder.getParentFile() + : new File(dataFolder.getParentFile().getParentFile(), "mods"); + if (pluginsFolder == null || !pluginsFolder.isDirectory()) { + System.out.println("Could not get plugin folder for Plan."); + return; + } + File newFileLocation = new File(pluginsFolder, "Plan-" + available.getVersion() + ".jar"); + + try { + downloadNewJar(available, newFileLocation); + deleteOldJar(pluginsFolder, newFileLocation); + } catch (IOException e) { + Log.toLog(this.getClass().getName(), e); + } + } + + private void deleteOldJar(File pluginsFolder, File newFileLocation) { + File[] files = pluginsFolder.listFiles(); + if (files == null) { + System.out.println("Could not delete old jar."); + return; + } + for (File file : files) { + String fileName = file.getName(); + boolean isPlanJar = (fileName.startsWith("Plan-") + && fileName.endsWith(".jar")) + || fileName.equals("Plan.jar"); + boolean isNewJar = fileName.equals(newFileLocation.getName()); + if (isPlanJar && !isNewJar) { + file.deleteOnExit(); + } + } + } + + private void downloadNewJar(VersionInfo available, File newFileLocation) throws IOException { + URL downloadFrom = new URL(available.getDownloadUrl()); + + ReadableByteChannel rbc = Channels.newChannel(downloadFrom.openStream()); + FileOutputStream fos = new FileOutputStream(newFileLocation); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + } + +} \ No newline at end of file diff --git a/Plan/src/main/java/com/djrapitops/plan/system/update/VersionCheckSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/update/VersionCheckSystem.java index 5051d2441..fe1cd2e8d 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/update/VersionCheckSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/update/VersionCheckSystem.java @@ -6,6 +6,7 @@ package com.djrapitops.plan.system.update; import com.djrapitops.plan.system.PlanSystem; import com.djrapitops.plan.system.SubSystem; +import com.djrapitops.plan.system.settings.Settings; import com.djrapitops.plugin.api.Priority; import com.djrapitops.plugin.api.systems.NotificationCenter; import com.djrapitops.plugin.api.utility.Version; @@ -13,6 +14,8 @@ import com.djrapitops.plugin.api.utility.log.Log; import com.djrapitops.plugin.utilities.Verify; import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; /** * System for checking if new Version is available when the System initializes. @@ -22,7 +25,7 @@ import java.io.IOException; public class VersionCheckSystem implements SubSystem { private final String currentVersion; - private boolean newVersionAvailable = false; + private VersionInfo newVersionAvailable; public VersionCheckSystem(String currentVersion) { this.currentVersion = currentVersion; @@ -35,7 +38,7 @@ public class VersionCheckSystem implements SubSystem { } public static boolean isNewVersionAvailable() { - return getInstance().newVersionAvailable; + return getInstance().newVersionAvailable != null; } public static String getCurrentVersion() { @@ -44,14 +47,35 @@ public class VersionCheckSystem implements SubSystem { @Override public void enable() { - checkForNewVersion(); + if (Settings.ALLOW_UPDATE.isTrue()) { + try { + List versions = VersionInfoLoader.load(); + if (Settings.NOTIFY_ABOUT_DEV_RELEASES.isFalse()) { + versions = versions.stream().filter(VersionInfo::isRelease).collect(Collectors.toList()); + } + VersionInfo newestVersion = versions.get(0); + if (Version.isNewVersionAvailable(new Version(currentVersion), newestVersion.getVersion())) { + String notification = + "New Release (" + newestVersion.getVersion().toString() + ") is available and can be updated " + + "to using update subcommand." + (newestVersion.isRelease() ? "" : " This is a DEV release."); + Log.info(notification); + NotificationCenter.addNotification(newestVersion.isRelease() ? Priority.HIGH : Priority.MEDIUM, notification); + } else { + Log.info("You're using the latest version."); + } + } catch (IOException e) { + Log.error("Version information could not be loaded from Github/versions.txt"); + } + } else { + checkForNewVersion(); + } } private void checkForNewVersion() { String githubVersionUrl = "https://raw.githubusercontent.com/Rsl1122/Plan-PlayerAnalytics/master/Plan/src/main/resources/plugin.yml"; String spigotUrl = "https://www.spigotmc.org/resources/plan-player-analytics.32536/"; try { - newVersionAvailable = Version.checkVersion(currentVersion, githubVersionUrl); + boolean newVersionAvailable = Version.checkVersion(currentVersion, githubVersionUrl); if (!newVersionAvailable) { try { newVersionAvailable = Version.checkVersion(currentVersion, spigotUrl); @@ -77,4 +101,8 @@ public class VersionCheckSystem implements SubSystem { public void disable() { /* Does not need to be closed */ } + + public VersionInfo getNewVersionAvailable() { + return newVersionAvailable; + } } \ No newline at end of file diff --git a/Plan/src/main/java/com/djrapitops/plan/system/update/VersionInfo.java b/Plan/src/main/java/com/djrapitops/plan/system/update/VersionInfo.java new file mode 100644 index 000000000..c294a5803 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/update/VersionInfo.java @@ -0,0 +1,63 @@ +package com.djrapitops.plan.system.update; + +import com.djrapitops.plugin.api.utility.Version; +import com.google.common.base.Objects; + +/** + * Data class for reading version.txt in https://github.com/Rsl1122/Plan-PlayerAnalytics. + * + * @author Rsl1122 + */ +public class VersionInfo implements Comparable { + + private final boolean release; + private final Version version; + private final String downloadUrl; + private final String changeLogUrl; + + public VersionInfo(boolean release, Version version, String downloadUrl, String changeLogUrl) { + this.release = release; + this.version = version; + this.downloadUrl = downloadUrl; + this.changeLogUrl = changeLogUrl; + } + + public boolean isRelease() { + return release; + } + + public Version getVersion() { + return version; + } + + public String getDownloadUrl() { + return downloadUrl; + } + + public String getChangeLogUrl() { + return changeLogUrl; + } + + public boolean isTrusted() { + return downloadUrl.startsWith("https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/"); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + VersionInfo that = (VersionInfo) o; + return release == that.release && + Objects.equal(version, that.version); + } + + @Override + public int hashCode() { + return Objects.hashCode(release, version); + } + + @Override + public int compareTo(VersionInfo o) { + return -this.version.compareTo(o.version); + } +} \ No newline at end of file diff --git a/Plan/src/main/java/com/djrapitops/plan/system/update/VersionInfoLoader.java b/Plan/src/main/java/com/djrapitops/plan/system/update/VersionInfoLoader.java new file mode 100644 index 000000000..4ff8281bf --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/update/VersionInfoLoader.java @@ -0,0 +1,57 @@ +package com.djrapitops.plan.system.update; + +import com.djrapitops.plugin.api.utility.Version; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Scanner; + +/** + * Utility for loading version information from github. + * + * @author Rsl1122 + */ +public class VersionInfoLoader { + + private static final String VERSION_TXT_URL = + "https://raw.githubusercontent.com/Rsl1122/Plan-PlayerAnalytics/master/versions.txt"; + + /** + * Loads version information from github. + * + * @return List of VersionInfo, newest version first. + * @throws IOException If site can not be accessed. + * @throws java.net.MalformedURLException If VERSION_TXT_URL is not valid. + */ + public static List load() throws IOException { + URL url = new URL(VERSION_TXT_URL); + + List versionInfo = new ArrayList<>(); + + try (Scanner websiteScanner = new Scanner(url.openStream())) { + while (websiteScanner.hasNextLine()) { + String line = websiteScanner.nextLine(); + if (!line.startsWith("REL") && !line.startsWith("DEV")) { + continue; + } + String[] parts = line.split("\\|"); + if (parts.length < 4) { + continue; + } + boolean release = parts[0].equals("REL"); + Version version = new Version(parts[1]); + String downloadUrl = parts[2]; + String changeLogUrl = parts[3]; + + versionInfo.add(new VersionInfo(release, version, downloadUrl, changeLogUrl)); + } + } + + Collections.sort(versionInfo); + return versionInfo; + } + +} \ No newline at end of file diff --git a/Plan/src/main/resources/bungee.yml b/Plan/src/main/resources/bungee.yml index ff3950d61..50e65696b 100644 --- a/Plan/src/main/resources/bungee.yml +++ b/Plan/src/main/resources/bungee.yml @@ -1,4 +1,4 @@ name: Plan author: Rsl1122 main: com.djrapitops.plan.PlanBungee -version: 4.2.0 \ No newline at end of file +version: 4.2.0-b1 \ No newline at end of file diff --git a/Plan/src/main/resources/bungeeconfig.yml b/Plan/src/main/resources/bungeeconfig.yml index 439e5f76b..459f24239 100644 --- a/Plan/src/main/resources/bungeeconfig.yml +++ b/Plan/src/main/resources/bungeeconfig.yml @@ -12,6 +12,8 @@ Network: Plugin: Debug: 'false' Locale: default + Allow-Update-Command: true + Notify-About-DEV-Releases: false # ----------------------------------------------------- # More information about SSL Certificate Settings: diff --git a/Plan/src/main/resources/config.yml b/Plan/src/main/resources/config.yml index 65cbe2836..4e102d8f5 100644 --- a/Plan/src/main/resources/config.yml +++ b/Plan/src/main/resources/config.yml @@ -17,6 +17,8 @@ Plugin: Bungee-Override: StandaloneMode: false CopyBungeeConfig: true + Allow-Update-Command: true + Notify-About-DEV-Releases: false # ----------------------------------------------------- # More information about SSL Certificate Settings: diff --git a/Plan/src/main/resources/plugin.yml b/Plan/src/main/resources/plugin.yml index ae52c2950..d7d70ff21 100644 --- a/Plan/src/main/resources/plugin.yml +++ b/Plan/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: Plan author: Rsl1122 main: com.djrapitops.plan.Plan -version: 4.2.0 +version: 4.2.0-b1 softdepend: - EssentialsX - Towny diff --git a/Plan/src/test/java/com/djrapitops/plan/system/update/VersionInfoLoaderTest.java b/Plan/src/test/java/com/djrapitops/plan/system/update/VersionInfoLoaderTest.java new file mode 100644 index 000000000..60734e2a0 --- /dev/null +++ b/Plan/src/test/java/com/djrapitops/plan/system/update/VersionInfoLoaderTest.java @@ -0,0 +1,24 @@ +package com.djrapitops.plan.system.update; + +import com.djrapitops.plugin.api.utility.Version; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class VersionInfoLoaderTest { + + @Test + public void versionLoaderTest() throws IOException { + List versions = VersionInfoLoader.load(); + + VersionInfo oldest = versions.get(versions.size() - 1); + assertEquals(new Version("4.1.7"), oldest.getVersion()); + assertTrue(oldest.isRelease()); + assertTrue(oldest.isTrusted()); + } + +} \ No newline at end of file