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"));
+ }
+}