From 236cfed61631352e9394632d80409cac62f31cc3 Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Thu, 16 Feb 2012 22:31:40 +0000 Subject: [PATCH] Added a built-in update checker. See http://wiki.bukkit.org/Bukkit.yml#auto-updater for new bukkit.yml options. --- pom.xml | 5 + .../server/ServerConfigurationManager.java | 1 + .../org/bukkit/craftbukkit/CraftServer.java | 19 ++++ .../craftbukkit/updater/ArtifactDetails.java | 81 +++++++++++++ .../craftbukkit/updater/AutoUpdater.java | 107 ++++++++++++++++++ .../updater/BukkitDLUpdaterService.java | 68 +++++++++++ src/main/resources/configurations/bukkit.yml | 6 + .../updater/BukkitDLUpdaterServiceTest.java | 30 +++++ 8 files changed, 317 insertions(+) create mode 100644 src/main/java/org/bukkit/craftbukkit/updater/ArtifactDetails.java create mode 100644 src/main/java/org/bukkit/craftbukkit/updater/AutoUpdater.java create mode 100644 src/main/java/org/bukkit/craftbukkit/updater/BukkitDLUpdaterService.java create mode 100644 src/test/java/org/bukkit/craftbukkit/updater/BukkitDLUpdaterServiceTest.java diff --git a/pom.xml b/pom.xml index afe078e975..b5de605b46 100644 --- a/pom.xml +++ b/pom.xml @@ -130,6 +130,11 @@ 1.2.1 test + + com.google.code.gson + gson + 2.1 + diff --git a/src/main/java/net/minecraft/server/ServerConfigurationManager.java b/src/main/java/net/minecraft/server/ServerConfigurationManager.java index 3cea43f1e2..1ee34a70bb 100644 --- a/src/main/java/net/minecraft/server/ServerConfigurationManager.java +++ b/src/main/java/net/minecraft/server/ServerConfigurationManager.java @@ -135,6 +135,7 @@ public class ServerConfigurationManager { if ((joinMessage != null) && (joinMessage.length() > 0)) { this.server.serverConfigurationManager.sendAll(new Packet3Chat(joinMessage)); } + this.cserver.onPlayerJoin(playerJoinEvent.getPlayer()); // CraftBukkit end worldserver.addEntity(entityplayer); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index ac018e97de..763d26aaa0 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -78,6 +78,8 @@ import org.bukkit.craftbukkit.map.CraftMapView; import org.bukkit.craftbukkit.potion.CraftPotionBrewer; import org.bukkit.scheduler.BukkitWorker; import org.bukkit.craftbukkit.scheduler.CraftScheduler; +import org.bukkit.craftbukkit.updater.AutoUpdater; +import org.bukkit.craftbukkit.updater.BukkitDLUpdaterService; import org.bukkit.craftbukkit.util.DatFileFilter; import org.bukkit.craftbukkit.util.Versioning; import org.bukkit.util.permissions.DefaultPermissions; @@ -106,6 +108,7 @@ public final class CraftServer implements Server { private YamlConfiguration configuration; private final Yaml yaml = new Yaml(new SafeConstructor()); private final Map offlinePlayers = new MapMaker().softValues().makeMap(); + private AutoUpdater updater; static { ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); @@ -137,6 +140,12 @@ public final class CraftServer implements Server { saveConfig(); ((SimplePluginManager) pluginManager).useTimings(configuration.getBoolean("settings.plugin-profiling", false)); + updater = new AutoUpdater(new BukkitDLUpdaterService(configuration.getString("auto-updater.host")), getLogger(), configuration.getString("auto-updater.preferred-channel")); + updater.setEnabled(configuration.getBoolean("auto-updater.enabled")); + updater.getOnBroken().addAll(configuration.getStringList("auto-updater.on-broken")); + updater.getOnUpdate().addAll(configuration.getStringList("auto-updater.on-update")); + updater.check(serverVersion); + loadPlugins(); enablePlugins(PluginLoadOrder.STARTUP); @@ -1013,4 +1022,14 @@ public final class CraftServer implements Server { return result; } + + public void onPlayerJoin(Player player) { + if ((updater.isEnabled()) && (player.hasPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE))) { + if ((updater.getCurrent().isBroken()) && (updater.getOnBroken().contains(updater.WARN_OPERATORS))) { + player.sendMessage(ChatColor.DARK_RED + "The version of CraftBukkit that this server is running is known to be broken. Please consider updating to the latest version at dl.bukkit.org."); + } else if ((updater.isUpdateAvailable()) && (updater.getOnUpdate().contains(updater.WARN_OPERATORS))) { + player.sendMessage(ChatColor.DARK_PURPLE + "The version of CraftBukkit that this server is running is out of date. Please consider updating to the latest version at dl.bukkit.org."); + } + } + } } diff --git a/src/main/java/org/bukkit/craftbukkit/updater/ArtifactDetails.java b/src/main/java/org/bukkit/craftbukkit/updater/ArtifactDetails.java new file mode 100644 index 0000000000..b9af02d8ba --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/updater/ArtifactDetails.java @@ -0,0 +1,81 @@ +package org.bukkit.craftbukkit.updater; + +import java.util.Date; + +public class ArtifactDetails { + private String brokenReason; + private boolean isBroken; + private int buildNumber; + private String htmlUrl; + private String version; + private Date created; + private FileDetails file; + + public FileDetails getFile() { + return file; + } + + public void setFile(FileDetails file) { + this.file = file; + } + + public String getBrokenReason() { + return brokenReason; + } + + public void setBrokenReason(String brokenReason) { + this.brokenReason = brokenReason; + } + + public int getBuildNumber() { + return buildNumber; + } + + public void setBuildNumber(int buildNumber) { + this.buildNumber = buildNumber; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public String getHtmlUrl() { + return htmlUrl; + } + + public void setHtmlUrl(String htmlUrl) { + this.htmlUrl = htmlUrl; + } + + public boolean isBroken() { + return isBroken; + } + + public void setBroken(boolean isBroken) { + this.isBroken = isBroken; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public static class FileDetails { + private String url; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/updater/AutoUpdater.java b/src/main/java/org/bukkit/craftbukkit/updater/AutoUpdater.java new file mode 100644 index 0000000000..ede432e610 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/updater/AutoUpdater.java @@ -0,0 +1,107 @@ +package org.bukkit.craftbukkit.updater; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +public class AutoUpdater { + public static final String WARN_CONSOLE = "warn-console"; + public static final String WARN_OPERATORS = "warn-ops"; + + private final BukkitDLUpdaterService service; + private final List onUpdate = new ArrayList(); + private final List onBroken = new ArrayList(); + private final Logger log; + private final String channel; + private boolean enabled; + private ArtifactDetails current = null; + private ArtifactDetails latest = null; + + public AutoUpdater(BukkitDLUpdaterService service, Logger log, String channel) { + this.service = service; + this.log = log; + this.channel = channel; + } + + public String getChannel() { + return channel; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean isEnabled) { + this.enabled = isEnabled; + } + + public List getOnBroken() { + return onBroken; + } + + public List getOnUpdate() { + return onUpdate; + } + + public boolean isUpdateAvailable() { + if ((latest == null) || (current == null) || (!isEnabled())) { + return false; + } else { + return latest.getCreated().after(current.getCreated()); + } + } + + public ArtifactDetails getCurrent() { + return current; + } + + public ArtifactDetails getLatest() { + return latest; + } + + public void check(final String currentSlug) { + if (!isEnabled()) return; + + new Thread() { + @Override + public void run() { + current = service.getArtifact(currentSlug); + latest = service.getArtifact("latest-" + channel); + + if (isUpdateAvailable()) { + if ((current.isBroken()) && (onBroken.contains(WARN_CONSOLE))) { + log.severe("----- Bukkit Auto Updater -----"); + log.severe("Your version of CraftBukkit is known to be broken. It is strongly advised that you update to a more recent version ASAP."); + log.severe("Known issues with your version:"); + + for (String line : current.getBrokenReason().split("\n")) { + log.severe("> " + line); + } + + log.severe("Newer version " + latest.getVersion() + " (build #" + latest.getBuildNumber() + ") was released on " + latest.getCreated() + "."); + log.severe("Details: " + latest.getHtmlUrl()); + log.severe("Download: " + latest.getFile().getUrl()); + log.severe("----- ------------------- -----"); + } else if (onUpdate.contains(WARN_CONSOLE)) { + log.warning("----- Bukkit Auto Updater -----"); + log.warning("Your version of CraftBukkit is out of date. Version " + latest.getVersion() + " (build #" + latest.getBuildNumber() + ") was released on " + latest.getCreated() + "."); + log.warning("Details: " + latest.getHtmlUrl()); + log.warning("Download: " + latest.getFile().getUrl()); + log.warning("----- ------------------- -----"); + } + } else if ((current != null) && (current.isBroken()) && (onBroken.contains(WARN_CONSOLE))) { + log.severe("----- Bukkit Auto Updater -----"); + log.severe("Your version of CraftBukkit is known to be broken. It is strongly advised that you update to a more recent version ASAP."); + log.severe("Known issues with your version:"); + + for (String line : current.getBrokenReason().split("\n")) { + log.severe("> " + line); + } + + log.severe("Unfortunately, there is not yet a newer version suitable for your server. We would advise you wait an hour or two, or try out a dev build."); + log.severe("----- ------------------- -----"); + } + } + }.start(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/updater/BukkitDLUpdaterService.java b/src/main/java/org/bukkit/craftbukkit/updater/BukkitDLUpdaterService.java new file mode 100644 index 0000000000..cd5984986b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/updater/BukkitDLUpdaterService.java @@ -0,0 +1,68 @@ +package org.bukkit.craftbukkit.updater; + +import com.google.gson.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class BukkitDLUpdaterService { + private static final String API_PREFIX = "/api/1.0/downloads/projects/craftbukkit/view/"; + private static final DateDeserializer dateDeserializer = new DateDeserializer(); + private final String host; + + public BukkitDLUpdaterService(String host) { + this.host = host; + } + + public ArtifactDetails getArtifact(String slug) { + try { + return fetchArtifact(slug); + } catch (UnsupportedEncodingException ex) { + Logger.getLogger(BukkitDLUpdaterService.class.getName()).log(Level.WARNING, "Could not get Artifact details for the auto-updater", ex); + } catch (IOException ex) { + Logger.getLogger(BukkitDLUpdaterService.class.getName()).log(Level.WARNING, "Could not get Artifact details for the auto-updater", ex); + } + + return null; + } + + public ArtifactDetails fetchArtifact(String slug) throws UnsupportedEncodingException, IOException { + URL url = new URL("http", host, API_PREFIX + slug); + InputStreamReader reader = null; + + try { + reader = new InputStreamReader(url.openStream()); + Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, dateDeserializer).setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + ArtifactDetails fromJson = gson.fromJson(reader, ArtifactDetails.class); + + return fromJson; + } finally { + if (reader != null) { + reader.close(); + } + } + } + + static class DateDeserializer implements JsonDeserializer { + private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + public Date deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { + try { + return format.parse(je.getAsString()); + } catch (ParseException ex) { + throw new JsonParseException("Date is not formatted correctly", ex); + } + } + } +} diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml index 875ca16b8f..55c9407de3 100644 --- a/src/main/resources/configurations/bukkit.yml +++ b/src/main/resources/configurations/bukkit.yml @@ -24,6 +24,12 @@ settings: ticks-per: animal-spawns: 400 monster-spawns: 1 +auto-updater: + enabled: true + on-broken: [warn-console, warn-ops] + on-update: [warn-console, warn-ops] + preferred-channel: rb + host: dl.bukkit.org aliases: icanhasbukkit: - version diff --git a/src/test/java/org/bukkit/craftbukkit/updater/BukkitDLUpdaterServiceTest.java b/src/test/java/org/bukkit/craftbukkit/updater/BukkitDLUpdaterServiceTest.java new file mode 100644 index 0000000000..701de3a3c5 --- /dev/null +++ b/src/test/java/org/bukkit/craftbukkit/updater/BukkitDLUpdaterServiceTest.java @@ -0,0 +1,30 @@ +package org.bukkit.craftbukkit.updater; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import static org.junit.Assert.assertNotNull; +import org.junit.Test; + +public class BukkitDLUpdaterServiceTest { + @Test(expected=IOException.class) + public void testHostNotFound() throws UnsupportedEncodingException, IOException { + BukkitDLUpdaterService service = new BukkitDLUpdaterService("404.example.org"); + + service.fetchArtifact("rb"); + } + + @Test(expected=FileNotFoundException.class) + public void testArtifactNotFound() throws UnsupportedEncodingException, IOException { + BukkitDLUpdaterService service = new BukkitDLUpdaterService("dl.bukkit.org"); + + service.fetchArtifact("meep"); + } + + @Test + public void testArtifactExists() throws UnsupportedEncodingException, IOException { + BukkitDLUpdaterService service = new BukkitDLUpdaterService("dl.bukkit.org"); + + assertNotNull(service.fetchArtifact("latest-dev")); + } +}