summaryrefslogtreecommitdiffstats
path: root/EssentialsUpdate/src/net/ess3/update
diff options
context:
space:
mode:
authorementalo <ementalodev@gmx.co.uk>2012-06-27 13:35:39 +0100
committerementalo <ementalodev@gmx.co.uk>2012-06-27 13:35:39 +0100
commit16d0b5c228dd8d981a0d79944c70248ea813d63b (patch)
treeb76e6966bfc2b4b9b668b89b13e30307e0b81988 /EssentialsUpdate/src/net/ess3/update
parentc1230bd6ae4e17539e1f6f2c82691fa7a8395e8c (diff)
downloadEssentials-16d0b5c228dd8d981a0d79944c70248ea813d63b.tar
Essentials-16d0b5c228dd8d981a0d79944c70248ea813d63b.tar.gz
Essentials-16d0b5c228dd8d981a0d79944c70248ea813d63b.tar.lz
Essentials-16d0b5c228dd8d981a0d79944c70248ea813d63b.tar.xz
Essentials-16d0b5c228dd8d981a0d79944c70248ea813d63b.zip
package name change to net.ess3
Diffstat (limited to 'EssentialsUpdate/src/net/ess3/update')
-rw-r--r--EssentialsUpdate/src/net/ess3/update/AbstractWorkListener.java39
-rw-r--r--EssentialsUpdate/src/net/ess3/update/EssentialsHelp.java171
-rw-r--r--EssentialsUpdate/src/net/ess3/update/EssentialsUpdate.java59
-rw-r--r--EssentialsUpdate/src/net/ess3/update/GetFile.java113
-rw-r--r--EssentialsUpdate/src/net/ess3/update/ModuleInfo.java35
-rw-r--r--EssentialsUpdate/src/net/ess3/update/PastieUpload.java40
-rw-r--r--EssentialsUpdate/src/net/ess3/update/PostToUrl.java66
-rw-r--r--EssentialsUpdate/src/net/ess3/update/UpdateCheck.java202
-rw-r--r--EssentialsUpdate/src/net/ess3/update/UpdateFile.java204
-rw-r--r--EssentialsUpdate/src/net/ess3/update/UpdateProcess.java199
-rw-r--r--EssentialsUpdate/src/net/ess3/update/Version.java173
-rw-r--r--EssentialsUpdate/src/net/ess3/update/VersionInfo.java49
-rw-r--r--EssentialsUpdate/src/net/ess3/update/chat/AbstractFileCommand.java75
-rw-r--r--EssentialsUpdate/src/net/ess3/update/chat/Command.java9
-rw-r--r--EssentialsUpdate/src/net/ess3/update/chat/ConfigCommand.java65
-rw-r--r--EssentialsUpdate/src/net/ess3/update/chat/ErrorsCommand.java71
-rw-r--r--EssentialsUpdate/src/net/ess3/update/chat/HelpCommand.java18
-rw-r--r--EssentialsUpdate/src/net/ess3/update/chat/IrcBot.java197
-rw-r--r--EssentialsUpdate/src/net/ess3/update/chat/ListCommand.java32
-rw-r--r--EssentialsUpdate/src/net/ess3/update/chat/StartupCommand.java77
-rw-r--r--EssentialsUpdate/src/net/ess3/update/chat/UsernameUtil.java124
-rw-r--r--EssentialsUpdate/src/net/ess3/update/states/AbstractState.java115
-rw-r--r--EssentialsUpdate/src/net/ess3/update/states/AbstractYesNoState.java57
-rw-r--r--EssentialsUpdate/src/net/ess3/update/states/AdvancedMode.java20
-rw-r--r--EssentialsUpdate/src/net/ess3/update/states/Changelog.java91
-rw-r--r--EssentialsUpdate/src/net/ess3/update/states/EssentialsChat.java47
-rw-r--r--EssentialsUpdate/src/net/ess3/update/states/EssentialsChatSettings.java29
-rw-r--r--EssentialsUpdate/src/net/ess3/update/states/EssentialsGeoIP.java48
-rw-r--r--EssentialsUpdate/src/net/ess3/update/states/EssentialsProtect.java48
-rw-r--r--EssentialsUpdate/src/net/ess3/update/states/InstallationFinishedEvent.java21
-rw-r--r--EssentialsUpdate/src/net/ess3/update/states/StateMachine.java185
-rw-r--r--EssentialsUpdate/src/net/ess3/update/states/StateMap.java12
-rw-r--r--EssentialsUpdate/src/net/ess3/update/states/UpdateOrInstallation.java61
-rw-r--r--EssentialsUpdate/src/net/ess3/update/tasks/InstallModule.java65
-rw-r--r--EssentialsUpdate/src/net/ess3/update/tasks/SelfUpdate.java56
-rw-r--r--EssentialsUpdate/src/net/ess3/update/tasks/Task.java7
36 files changed, 2880 insertions, 0 deletions
diff --git a/EssentialsUpdate/src/net/ess3/update/AbstractWorkListener.java b/EssentialsUpdate/src/net/ess3/update/AbstractWorkListener.java
new file mode 100644
index 000000000..7a927d4db
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/AbstractWorkListener.java
@@ -0,0 +1,39 @@
+package net.ess3.update;
+
+import org.bukkit.plugin.Plugin;
+
+
+public abstract class AbstractWorkListener
+{
+ public AbstractWorkListener(final Plugin plugin, final VersionInfo newVersionInfo)
+ {
+ this.plugin = plugin;
+ this.newVersionInfo = newVersionInfo;
+ }
+ private final transient Plugin plugin;
+ private final transient VersionInfo newVersionInfo;
+
+ public final void onWorkAbort()
+ {
+ onWorkAbort(null);
+ }
+
+ public abstract void onWorkAbort(String message);
+
+ public final void onWorkDone()
+ {
+ onWorkDone(null);
+ }
+
+ public abstract void onWorkDone(String message);
+
+ public VersionInfo getNewVersionInfo()
+ {
+ return newVersionInfo;
+ }
+
+ public Plugin getPlugin()
+ {
+ return plugin;
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/EssentialsHelp.java b/EssentialsUpdate/src/net/ess3/update/EssentialsHelp.java
new file mode 100644
index 000000000..0d433ecc0
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/EssentialsHelp.java
@@ -0,0 +1,171 @@
+package net.ess3.update;
+
+import net.ess3.update.chat.*;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import org.bukkit.Server;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerChatEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.PluginManager;
+
+
+public class EssentialsHelp implements Listener
+{
+ private transient Player chatUser;
+ private final transient Server server;
+ private final transient Plugin plugin;
+ private transient IrcBot ircBot;
+ private final transient Map<String, Command> commands = new HashMap<String, Command>();
+
+ public EssentialsHelp(final Plugin plugin)
+ {
+ super();
+ this.plugin = plugin;
+ this.server = plugin.getServer();
+ commands.put("!help", new HelpCommand());
+ commands.put("!list", new ListCommand());
+ commands.put("!startup", new StartupCommand(plugin));
+ commands.put("!errors", new ErrorsCommand(plugin));
+ commands.put("!config", new ConfigCommand(plugin));
+ }
+
+ public void registerEvents()
+ {
+ final PluginManager pluginManager = server.getPluginManager();
+ pluginManager.registerEvents(this, plugin);
+ }
+
+ public void onCommand(final CommandSender sender)
+ {
+ if (sender instanceof Player && sender.hasPermission("essentials.helpchat"))
+ {
+ if (chatUser == null)
+ {
+ chatUser = (Player)sender;
+ ircBot = null;
+ sender.sendMessage("You will be connected to the Essentials Help Chat.");
+ sender.sendMessage("All your chat messages will be forwarded to the channel. You can't chat with other players on your server while in help chat, but you can use commands.");
+ sender.sendMessage("Please be patient, if noone is available, check back later.");
+ sender.sendMessage("Type !help to get a list of all commands.");
+ sender.sendMessage("Type !quit to leave the channel.");
+ sender.sendMessage("Do you want to join the channel now? (yes/no)");
+ }
+ if (!chatUser.equals(sender))
+ {
+ sender.sendMessage("The player " + chatUser.getDisplayName() + " is already using the essentialshelp.");
+ }
+ }
+ else
+ {
+ sender.sendMessage("Please run the command as op from in game.");
+ }
+ }
+
+ public void onDisable()
+ {
+ closeConnection();
+ }
+
+ private boolean sendChatMessage(final Player player, final String message)
+ {
+ final String messageCleaned = message.trim();
+ if (messageCleaned.isEmpty())
+ {
+ return false;
+ }
+ if (ircBot == null)
+ {
+ return handleAnswer(messageCleaned, player);
+ }
+ else
+ {
+ if (ircBot.isKicked())
+ {
+ closeConnection();
+ return false;
+ }
+ final String lowMessage = messageCleaned.toLowerCase(Locale.ENGLISH);
+ if (lowMessage.startsWith("!quit"))
+ {
+ closeConnection();
+ player.sendMessage("Connection closed.");
+ return true;
+ }
+ if (!ircBot.isConnected() || ircBot.getChannels().length == 0)
+ {
+ return false;
+ }
+ if (handleCommands(lowMessage, player))
+ {
+ return true;
+ }
+ ircBot.sendMessage(messageCleaned);
+ chatUser.sendMessage("§6" + ircBot.getNick() + ": §7" + messageCleaned);
+ return true;
+ }
+ }
+
+ private void closeConnection()
+ {
+ chatUser = null;
+ if (ircBot != null)
+ {
+ ircBot.quit();
+ ircBot = null;
+ }
+ }
+
+ private boolean handleAnswer(final String message, final Player player)
+ {
+ if (message.equalsIgnoreCase("yes"))
+ {
+ player.sendMessage("Connecting...");
+ connectToIRC(player);
+ return true;
+ }
+ if (message.equalsIgnoreCase("no") || message.equalsIgnoreCase("!quit"))
+ {
+ chatUser = null;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean handleCommands(final String lowMessage, final Player player)
+ {
+ final String[] parts = lowMessage.split(" ");
+ if (commands.containsKey(parts[0]))
+ {
+ commands.get(parts[0]).run(ircBot, player);
+ return true;
+ }
+ return false;
+ }
+
+ private void connectToIRC(final Player player)
+ {
+ ircBot = new IrcBot(player, "Ess_" + player.getName(), UsernameUtil.createUsername(player));
+ }
+
+ @EventHandler
+ public void onPlayerChat(final PlayerChatEvent event)
+ {
+ if (event.getPlayer() == chatUser)
+ {
+ final boolean success = sendChatMessage(event.getPlayer(), event.getMessage());
+ event.setCancelled(success);
+ }
+ }
+
+ @EventHandler
+ public void onPlayerQuit(final PlayerQuitEvent event)
+ {
+ closeConnection();
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/EssentialsUpdate.java b/EssentialsUpdate/src/net/ess3/update/EssentialsUpdate.java
new file mode 100644
index 000000000..33875423a
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/EssentialsUpdate.java
@@ -0,0 +1,59 @@
+package net.ess3.update;
+
+import java.util.logging.Level;
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.plugin.java.JavaPlugin;
+
+
+public class EssentialsUpdate extends JavaPlugin
+{
+ private transient EssentialsHelp essentialsHelp;
+ private transient UpdateProcess updateProcess;
+
+ @Override
+ public void onEnable()
+ {
+ if (!getDataFolder().exists() && !getDataFolder().mkdirs())
+ {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not create data folder: {0}", getDataFolder().getPath());
+ }
+ essentialsHelp = new EssentialsHelp(this);
+ essentialsHelp.registerEvents();
+
+ final UpdateCheck updateCheck = new UpdateCheck(this);
+ updateCheck.checkForUpdates();
+ updateProcess = new UpdateProcess(this, updateCheck);
+ updateProcess.registerEvents();
+
+ if (updateCheck.isEssentialsInstalled())
+ {
+ updateCheck.scheduleUpdateTask();
+ }
+ else
+ {
+ Bukkit.getLogger().info("Essentials is ready for installation. Join the game and follow the instructions.");
+ }
+ }
+
+ @Override
+ public void onDisable()
+ {
+ essentialsHelp.onDisable();
+ }
+
+ @Override
+ public boolean onCommand(final CommandSender sender, final Command command, final String label, final String[] args)
+ {
+ if (command.getName().equalsIgnoreCase("essentialsupdate"))
+ {
+ updateProcess.onCommand(sender);
+ }
+ if (command.getName().equalsIgnoreCase("essentialshelp"))
+ {
+ essentialsHelp.onCommand(sender);
+ }
+ return true;
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/GetFile.java b/EssentialsUpdate/src/net/ess3/update/GetFile.java
new file mode 100644
index 000000000..9636e3bd1
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/GetFile.java
@@ -0,0 +1,113 @@
+package net.ess3.update;
+
+import java.io.*;
+import java.math.BigInteger;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.logging.Level;
+import org.bukkit.Bukkit;
+
+
+public class GetFile
+{
+ private transient URLConnection connection;
+ private transient MessageDigest digest;
+
+ public GetFile(final String urlString) throws MalformedURLException, IOException
+ {
+ this(new URL(urlString));
+ }
+
+ public GetFile(final URL url) throws IOException
+ {
+ this.connection = url.openConnection();
+ this.connection.setConnectTimeout(1000);
+ this.connection.setReadTimeout(5000);
+ this.connection.setUseCaches(false);
+ this.connection.connect();
+ final int respCode = ((HttpURLConnection)this.connection).getResponseCode();
+ if (respCode >= 300 && respCode < 400 && this.connection.getHeaderField("Location") != null)
+ {
+ connection.getInputStream().close();
+ final URL redirect = new URL(this.connection.getHeaderField("Location"));
+ this.connection = redirect.openConnection();
+ this.connection.setConnectTimeout(1000);
+ this.connection.setReadTimeout(5000);
+ this.connection.setUseCaches(false);
+ this.connection.connect();
+ }
+ }
+
+ public void saveTo(final File file) throws IOException
+ {
+ try
+ {
+ saveTo(file, null);
+ }
+ catch (NoSuchAlgorithmException ex)
+ {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public void saveTo(final File file, final String key) throws IOException, NoSuchAlgorithmException
+ {
+ if (key != null)
+ {
+ digest = MessageDigest.getInstance("SHA256");
+ }
+ final byte[] buffer = new byte[1024 * 8];
+ boolean brokenFile = false;
+ final BufferedInputStream input = new BufferedInputStream(connection.getInputStream());
+ try
+ {
+ final BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(file));
+ try
+ {
+ int length;
+ do
+ {
+ length = input.read(buffer);
+ if (length >= 0)
+ {
+ if (key != null)
+ {
+ digest.update(buffer, 0, length);
+ }
+ output.write(buffer, 0, length);
+ }
+ }
+ while (length >= 0);
+ if (key != null)
+ {
+ final byte[] checksum = digest.digest();
+ final String checksumString = new BigInteger(checksum).toString(36);
+ if (!checksumString.equals(key))
+ {
+ brokenFile = true;
+ }
+ }
+ }
+ finally
+ {
+ output.close();
+ }
+ if (brokenFile && !file.delete())
+ {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not delete file {0}", file.getPath());
+ }
+ }
+ finally
+ {
+ input.close();
+ }
+ if (brokenFile)
+ {
+ throw new IOException("Checksum check failed.");
+ }
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/ModuleInfo.java b/EssentialsUpdate/src/net/ess3/update/ModuleInfo.java
new file mode 100644
index 000000000..993575cbf
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/ModuleInfo.java
@@ -0,0 +1,35 @@
+package net.ess3.update;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import org.bukkit.configuration.Configuration;
+
+
+public class ModuleInfo
+{
+ private final transient String url;
+ private final transient String version;
+ private final transient String hash;
+
+ public ModuleInfo(final Configuration updateConfig, final String path)
+ {
+ url = updateConfig.getString(path + ".url", null);
+ version = updateConfig.getString(path + ".version", null);
+ hash = updateConfig.getString(path + ".hash", null);
+ }
+
+ public URL getUrl() throws MalformedURLException
+ {
+ return new URL(url);
+ }
+
+ public String getVersion()
+ {
+ return version;
+ }
+
+ public String getHash()
+ {
+ return hash;
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/PastieUpload.java b/EssentialsUpdate/src/net/ess3/update/PastieUpload.java
new file mode 100644
index 000000000..9fba2351d
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/PastieUpload.java
@@ -0,0 +1,40 @@
+package net.ess3.update;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public class PastieUpload
+{
+ private final transient PostToUrl connection;
+
+ public PastieUpload() throws MalformedURLException
+ {
+ connection = new PostToUrl(new URL("http://pastie.org/pastes"));
+ }
+
+ public String send(final String data) throws IOException
+ {
+ final Map<String, Object> map = new HashMap<String, Object>();
+ map.put("paste[parser_id]", "19");
+ map.put("paste[authorization]", "burger");
+ map.put("paste[body]", data);
+ map.put("paste[restricted]", "1");
+ final String html = connection.send(map);
+ final Matcher matcher = Pattern.compile("(?s).*\\?key=([a-z0-9]+).*").matcher(html);
+ if (matcher.matches())
+ {
+ final String key = matcher.group(1);
+ return "http://pastie.org/private/" + key;
+ }
+ else
+ {
+ throw new IOException("Failed to upload to pastie.org");
+ }
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/PostToUrl.java b/EssentialsUpdate/src/net/ess3/update/PostToUrl.java
new file mode 100644
index 000000000..28fc13c55
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/PostToUrl.java
@@ -0,0 +1,66 @@
+package net.ess3.update;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.Charset;
+import java.util.Map;
+import java.util.Random;
+
+
+public class PostToUrl
+{
+ private final transient URL url;
+ private final transient String boundary;
+ private final transient Random random = new Random();
+ private final static String CRLF = "\r\n";
+ private final static Charset UTF8 = Charset.forName("utf-8");
+
+ public PostToUrl(final URL url)
+ {
+ this.url = url;
+ final byte[] bytes = new byte[32];
+ random.nextBytes(bytes);
+ this.boundary = "----------" + new BigInteger(bytes).toString(Character.MAX_RADIX) + "_$";
+ }
+
+ public String send(final Map<String, Object> data) throws IOException
+ {
+ final URLConnection connection = url.openConnection();
+ connection.setRequestProperty("content-type", "multipart/form-data; boundary=" + boundary);
+ final StringBuilder dataBuilder = new StringBuilder();
+ for (Map.Entry<String, Object> entry : data.entrySet())
+ {
+ if (entry.getValue() instanceof String)
+ {
+ dataBuilder.append("--").append(boundary).append(CRLF);
+ dataBuilder.append("Content-Disposition: form-data; name=\"").append(entry.getKey()).append('"').append(CRLF);
+ dataBuilder.append(CRLF);
+ dataBuilder.append(entry.getValue()).append(CRLF);
+ }
+ // TODO: Add support for file upload
+ }
+ dataBuilder.append("--").append(boundary).append("--").append(CRLF);
+ dataBuilder.append(CRLF);
+ connection.setDoOutput(true);
+ final byte[] message = dataBuilder.toString().getBytes(UTF8);
+ connection.setRequestProperty("content-length", Integer.toString(message.length));
+ connection.connect();
+ final OutputStream stream = connection.getOutputStream();
+ stream.write(message);
+ stream.close();
+ final BufferedReader page = new BufferedReader(new InputStreamReader(connection.getInputStream(), UTF8));
+ final StringBuilder input = new StringBuilder();
+ String line;
+ while ((line = page.readLine()) != null)
+ {
+ input.append(line).append("\n");
+ }
+ page.close();
+ return input.toString();
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/UpdateCheck.java b/EssentialsUpdate/src/net/ess3/update/UpdateCheck.java
new file mode 100644
index 000000000..98a9983e8
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/UpdateCheck.java
@@ -0,0 +1,202 @@
+package net.ess3.update;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.bukkit.Bukkit;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.PluginManager;
+
+
+public class UpdateCheck
+{
+ private transient CheckResult result = CheckResult.UNKNOWN;
+ private transient Version currentVersion;
+ private transient Version newVersion = null;
+ private transient int bukkitResult = 0;
+ private transient UpdateFile updateFile;
+ private final static int CHECK_INTERVAL = 20 * 60 * 60 * 6;
+ private final transient Plugin plugin;
+ private transient boolean essentialsInstalled;
+
+ public UpdateCheck(final Plugin plugin)
+ {
+ this.plugin = plugin;
+ updateFile = new UpdateFile(plugin);
+ checkForEssentials();
+ }
+
+ private void checkForEssentials()
+ {
+ final PluginManager pluginManager = plugin.getServer().getPluginManager();
+ final Plugin essentials = pluginManager.getPlugin("Essentials-3");
+ essentialsInstalled = essentials != null;
+ if (essentialsInstalled)
+ {
+ currentVersion = new Version(essentials.getDescription().getVersion());
+ }
+ else
+ {
+ if (new File(plugin.getDataFolder().getParentFile(), "Essentials.jar").exists())
+ {
+ Bukkit.getLogger().severe("Essentials.jar found, but not recognized by Bukkit. Broken download?");
+ }
+ }
+ }
+
+ public void scheduleUpdateTask()
+ {
+ plugin.getServer().getScheduler().scheduleAsyncRepeatingTask(plugin, new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ updateFile = new UpdateFile(plugin);
+ checkForUpdates();
+ }
+ }, CHECK_INTERVAL, CHECK_INTERVAL);
+ }
+
+ public boolean isEssentialsInstalled()
+ {
+ return essentialsInstalled;
+ }
+
+ public CheckResult getResult()
+ {
+ return result;
+ }
+
+ public int getNewBukkitVersion()
+ {
+ return bukkitResult;
+ }
+
+ public VersionInfo getNewVersionInfo()
+ {
+ return updateFile.getVersions().get(newVersion);
+ }
+
+
+ public enum CheckResult
+ {
+ NEW_ESS, NEW_ESS_BUKKIT, NEW_BUKKIT, OK, UNKNOWN
+ }
+
+ public void checkForUpdates()
+ {
+ if (currentVersion == null)
+ {
+ return;
+ }
+ final Map<Version, VersionInfo> versions = updateFile.getVersions();
+ final int bukkitVersion = getBukkitVersion();
+ Version higher = null;
+ Version found = null;
+ Version lower = null;
+ int bukkitHigher = 0;
+ int bukkitLower = 0;
+ for (Entry<Version, VersionInfo> entry : versions.entrySet())
+ {
+ final int minBukkit = entry.getValue().getMinBukkit();
+ final int maxBukkit = entry.getValue().getMaxBukkit();
+ if (minBukkit == 0 || maxBukkit == 0)
+ {
+ continue;
+ }
+ if (bukkitVersion <= maxBukkit)
+ {
+ if (bukkitVersion < minBukkit)
+ {
+ if (higher == null || higher.compareTo(entry.getKey()) < 0)
+ {
+
+ higher = entry.getKey();
+ bukkitHigher = minBukkit;
+ }
+ }
+ else
+ {
+ if (found == null || found.compareTo(entry.getKey()) < 0)
+ {
+ found = entry.getKey();
+ }
+ }
+ }
+ else
+ {
+ if (lower == null || lower.compareTo(entry.getKey()) < 0)
+ {
+ lower = entry.getKey();
+ bukkitLower = minBukkit;
+ }
+ }
+ }
+ if (found != null)
+ {
+ if (found.compareTo(currentVersion) > 0)
+ {
+ result = CheckResult.NEW_ESS;
+ newVersion = found;
+ }
+ else
+ {
+ result = CheckResult.OK;
+ }
+ }
+ else if (higher != null)
+ {
+ if (higher.compareTo(currentVersion) > 0)
+ {
+ newVersion = higher;
+ result = CheckResult.NEW_ESS_BUKKIT;
+ bukkitResult = bukkitHigher;
+ }
+ else if (higher.compareTo(currentVersion) < 0)
+ {
+ result = CheckResult.UNKNOWN;
+ }
+ else
+ {
+ result = CheckResult.NEW_BUKKIT;
+ bukkitResult = bukkitHigher;
+ }
+ }
+ else if (lower != null)
+ {
+ if (lower.compareTo(currentVersion) > 0)
+ {
+ result = CheckResult.NEW_ESS_BUKKIT;
+ newVersion = lower;
+ bukkitResult = bukkitLower;
+ }
+ else if (lower.compareTo(currentVersion) < 0)
+ {
+ result = CheckResult.UNKNOWN;
+ }
+ else
+ {
+ result = CheckResult.NEW_BUKKIT;
+ bukkitResult = bukkitLower;
+ }
+ }
+
+ }
+
+ private int getBukkitVersion()
+ {
+ final Matcher versionMatch = Pattern.compile("git-Bukkit-(?:(?:[0-9]+)\\.)+[0-9]+-R[\\.0-9]+-(?:[0-9]+-g[0-9a-f]+-)?b([0-9]+)jnks.*").matcher(plugin.getServer().getVersion());
+ if (versionMatch.matches())
+ {
+ return Integer.parseInt(versionMatch.group(1));
+ }
+ throw new NumberFormatException("Bukkit Version changed!");
+ }
+
+ public Version getNewVersion()
+ {
+ return newVersion;
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/UpdateFile.java b/EssentialsUpdate/src/net/ess3/update/UpdateFile.java
new file mode 100644
index 000000000..cdab65600
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/UpdateFile.java
@@ -0,0 +1,204 @@
+package net.ess3.update;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.plugin.Plugin;
+
+
+public class UpdateFile
+{
+ private final static Logger LOGGER = Bukkit.getLogger();
+ private final static String UPDATE_URL = "http://goo.gl/67jev";
+ private final static BigInteger PUBLIC_KEY = new BigInteger("5ha6a2d4qdy17ttkg8evh74sl5a87djojwenu12k1lvy8ui6003e6l06rntczpoh99mhc3txj8mqlxw111oyy9yl7s7qpyluyzix3j1odxrxx4u52gxvyu6qiteapczkzvi7rxgeqsozz7b19rdx73a7quo9ybwpz1cr82r7x5k0pg2a73pjjsv2j1awr13azo7klrcxp9y5xxwf5qv1s3tw4zqftli18u0ek5qkbzfbgk1v5n2f11pkwwk6p0mibrn26wnjbv11vyiqgu95o7busmt6vf5q7grpcenl637w83mbin56s3asj1131b2mscj9xep3cbj7la9tgsxl5bj87vzy8sk2d34kzwqdqgh9nry43nqqus12l1stmiv184r8r3jcy8w43e8h1u1mzklldb5eytkuhayqik8l3ns04hwt8sgacvw534be8sx26qrn5s1", 36);
+ private final transient File file;
+ private final transient Plugin plugin;
+ private final transient TreeMap<Version, VersionInfo> versions = new TreeMap<Version, VersionInfo>();
+
+ public UpdateFile(final Plugin plugin)
+ {
+ this.plugin = plugin;
+ final long lastUpdate = Long.parseLong(plugin.getConfig().getString("lastupdate", "0"));
+ file = new File(plugin.getDataFolder(), "update.yml");
+ if (lastUpdate < System.currentTimeMillis() - 1000 * 60 * 60 * 6 || !file.exists())
+ {
+ if (file.exists() && !file.delete())
+ {
+ LOGGER.log(Level.SEVERE, "Could not delete file update.yml!");
+ return;
+ }
+ if (!downloadFile() || !checkFile())
+ {
+ LOGGER.log(Level.SEVERE, "Could not download and verify file update.yml!");
+ return;
+ }
+ }
+ try
+ {
+ readVersions();
+ }
+ catch (Exception ex)
+ {
+ LOGGER.log(Level.SEVERE, "Could not load update.yml!");
+ return;
+ }
+ }
+
+ private boolean downloadFile()
+ {
+ GetFile getFile;
+ try
+ {
+ getFile = new GetFile(UPDATE_URL);
+ getFile.saveTo(file);
+ plugin.getConfig().set("lastupdate", System.currentTimeMillis());
+ plugin.getConfig().save(new File(plugin.getDataFolder(), "config.yml"));
+ return true;
+ }
+ catch (IOException ex)
+ {
+ LOGGER.log(Level.SEVERE, "Error while downloading update.yml", ex);
+ return false;
+ }
+ }
+
+ private boolean checkFile()
+ {
+ BufferedInputStream bis = null;
+ try
+ {
+ bis = new BufferedInputStream(new FileInputStream(file));
+ if (bis.read() != '#')
+ {
+ throw new IOException("File has to start with #");
+ }
+ final StringBuilder length = new StringBuilder();
+ final StringBuilder signature = new StringBuilder();
+ boolean isSignature = false;
+ do
+ {
+ final int cur = bis.read();
+ if (cur == -1)
+ {
+ break;
+ }
+ if (cur == ':')
+ {
+ isSignature = true;
+ }
+ else if (cur == '\n')
+ {
+ break;
+ }
+ else if ((cur >= '0' && cur <= '9')
+ || (cur >= 'a' && cur <= 'z'))
+ {
+ if (isSignature)
+ {
+ signature.append((char)cur);
+ }
+ else
+ {
+ length.append((char)cur);
+ }
+ }
+ else
+ {
+ throw new IOException("Illegal character in signature!");
+ }
+ }
+ while (true);
+ if (length.length() == 0 || signature.length() == 0)
+ {
+ throw new IOException("Broken signature!");
+ }
+ final int sigLength = new BigInteger(length.toString(), 36).intValue();
+ if (sigLength < 0 || sigLength > 2048)
+ {
+ throw new IOException("Invalid signature length!");
+ }
+ final byte[] sigBytes = new BigInteger(signature.toString(), 36).toByteArray();
+ if (sigLength < sigBytes.length)
+ {
+ throw new IOException("Length is less then available bytes.");
+ }
+ byte[] realBytes;
+ if (sigLength == sigBytes.length)
+ {
+ realBytes = sigBytes;
+ }
+ else
+ {
+ realBytes = new byte[sigLength];
+ System.arraycopy(sigBytes, 0, realBytes, sigLength - sigBytes.length, sigBytes.length);
+ }
+ final X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(PUBLIC_KEY.toByteArray());
+ final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ final PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
+ final Signature rsa = Signature.getInstance("SHA256withRSA");
+ rsa.initVerify(pubKey);
+ final byte[] buffer = new byte[2048];
+ int readLength;
+ do
+ {
+ readLength = bis.read(buffer);
+ if (readLength >= 0)
+ {
+ rsa.update(buffer, 0, readLength);
+ }
+ }
+ while (readLength >= 0);
+ return rsa.verify(realBytes);
+ }
+ catch (Exception ex)
+ {
+ LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
+ }
+ finally
+ {
+ try
+ {
+ if (bis != null)
+ {
+ bis.close();
+ }
+ }
+ catch (IOException ex)
+ {
+ LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
+ }
+ }
+ return false;
+ }
+
+ private void readVersions() throws FileNotFoundException, InvalidConfigurationException, IOException
+ {
+ final YamlConfiguration updateConfig = new YamlConfiguration();
+ updateConfig.load(file);
+ versions.clear();
+ for (String versionString : updateConfig.getKeys(false))
+ {
+ versions.put(new Version(versionString), new VersionInfo(updateConfig, versionString));
+ }
+ }
+
+ public Map<Version, VersionInfo> getVersions()
+ {
+ return Collections.unmodifiableMap(versions.descendingMap());
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/UpdateProcess.java b/EssentialsUpdate/src/net/ess3/update/UpdateProcess.java
new file mode 100644
index 000000000..f96cdd18f
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/UpdateProcess.java
@@ -0,0 +1,199 @@
+package net.ess3.update;
+
+import net.ess3.update.states.InstallationFinishedEvent;
+import net.ess3.update.states.StateMachine;
+import net.ess3.update.tasks.SelfUpdate;
+import java.util.logging.Level;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerChatEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.plugin.Plugin;
+
+// TODO: This whole thing should make use of the conversations api
+public class UpdateProcess implements Listener
+{
+ private transient Player currentPlayer;
+ private final transient Plugin plugin;
+ private final transient UpdateCheck updateCheck;
+ private transient StateMachine stateMachine;
+
+ public UpdateProcess(final Plugin plugin, final UpdateCheck updateCheck)
+ {
+ super();
+ this.plugin = plugin;
+ this.updateCheck = updateCheck;
+ }
+
+ public void registerEvents()
+ {
+ plugin.getServer().getPluginManager().registerEvents(this, plugin);
+ }
+
+ public boolean selfUpdate()
+ {
+ if (new Version(plugin.getDescription().getVersion()).compareTo(updateCheck.getNewVersion()) < 0)
+ {
+ if (currentPlayer != null)
+ {
+ currentPlayer.sendMessage("A newer version of EssentialsUpdate is found. Downloading new file and reloading server.");
+ }
+ Bukkit.getLogger().log(Level.INFO, "A newer version of EssentialsUpdate is found. Downloading new file and reloading server.");
+ new SelfUpdate(new AbstractWorkListener(plugin, updateCheck.getNewVersionInfo())
+ {
+ @Override
+ public void onWorkAbort(final String message)
+ {
+ if (message != null && !message.isEmpty()
+ && UpdateProcess.this.currentPlayer != null
+ && UpdateProcess.this.currentPlayer.isOnline())
+ {
+ UpdateProcess.this.currentPlayer.sendMessage(message);
+ }
+ if (message != null && !message.isEmpty())
+ {
+ Bukkit.getLogger().log(Level.SEVERE, message);
+ }
+ UpdateProcess.this.currentPlayer = null;
+ }
+
+ @Override
+ public void onWorkDone(final String message)
+ {
+ if (message != null && !message.isEmpty()
+ && UpdateProcess.this.currentPlayer != null
+ && UpdateProcess.this.currentPlayer.isOnline())
+ {
+ UpdateProcess.this.currentPlayer.sendMessage(message);
+ }
+ if (message != null && !message.isEmpty())
+ {
+ Bukkit.getLogger().log(Level.INFO, message);
+ }
+ UpdateProcess.this.currentPlayer = null;
+ }
+ }).start();
+ return true;
+ }
+ if (updateCheck.getResult() == UpdateCheck.CheckResult.NEW_ESS_BUKKIT)
+ {
+ final String message = "Please update bukkit to version " + updateCheck.getNewBukkitVersion() + " before updating Essentials.";
+ if (currentPlayer != null)
+ {
+ currentPlayer.sendMessage(message);
+ }
+ Bukkit.getLogger().log(Level.INFO, message);
+ currentPlayer = null;
+ return true;
+ }
+ return false;
+ }
+
+ @EventHandler
+ public void onInstallationFinished(final InstallationFinishedEvent event)
+ {
+ UpdateProcess.this.currentPlayer = null;
+ }
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onPlayerChat(final PlayerChatEvent event)
+ {
+ if (event.getPlayer() == currentPlayer)
+ {
+ final StateMachine.MachineResult result = stateMachine.reactOnMessage(event.getMessage());
+ if (result == StateMachine.MachineResult.ABORT)
+ {
+ currentPlayer.sendMessage("Installation wizard aborted. You can restart it using /essentialsupdate.");
+ currentPlayer = null;
+ }
+ if (result == StateMachine.MachineResult.DONE)
+ {
+ startWork();
+ }
+ event.setCancelled(true);
+ return;
+ }
+ }
+
+ @EventHandler
+ public void onPlayerJoin(final PlayerJoinEvent event)
+ {
+ final Player player = event.getPlayer();
+ if (currentPlayer != null && currentPlayer.getName().equals(player.getName()))
+ {
+ currentPlayer = player;
+ player.sendMessage("You quit the game, while the installation wizard was running.");
+ player.sendMessage("The installation wizard will now resume.");
+ player.sendMessage("You can exit the wizard by typing quit into the chat.");
+ stateMachine.resumeInstallation(player);
+ }
+ if (player.hasPermission("essentials.update") && !updateCheck.isEssentialsInstalled())
+ {
+ player.sendMessage("Hello " + player.getDisplayName());
+ player.sendMessage("Please type /essentialsupdate into the chat to start the installation of Essentials.");
+ }
+ if (player.hasPermission("essentials.update"))
+ {
+ final UpdateCheck.CheckResult result = updateCheck.getResult();
+ switch (result)
+ {
+ case NEW_ESS:
+ player.sendMessage("The new version " + updateCheck.getNewVersion().toString() + " for Essentials is available. Please type /essentialsupdate to update.");
+ break;
+ case NEW_BUKKIT:
+ player.sendMessage("Your bukkit version is not the recommended build for Essentials, please update to version " + updateCheck.getNewBukkitVersion() + ".");
+ break;
+ case NEW_ESS_BUKKIT:
+ player.sendMessage("There is a new version " + updateCheck.getNewVersion().toString() + " of Essentials for Bukkit " + updateCheck.getNewBukkitVersion());
+ break;
+ default:
+ }
+ }
+ }
+
+ public void onCommand(final CommandSender sender)
+ {
+ if (sender instanceof Player && sender.hasPermission("essentials.update"))
+ {
+ if (currentPlayer == null)
+ {
+ currentPlayer = (Player)sender;
+ if (selfUpdate())
+ {
+ return;
+ }
+ stateMachine = new StateMachine(plugin, currentPlayer, updateCheck);
+ final StateMachine.MachineResult result = stateMachine.askQuestion();
+ if (result == StateMachine.MachineResult.DONE)
+ {
+ startWork();
+ }
+ }
+ if (!currentPlayer.equals(sender))
+ {
+ sender.sendMessage("The player " + currentPlayer.getDisplayName() + " is already using the wizard.");
+ }
+ }
+ else
+ {
+ sender.sendMessage("Please run the command as op from in game.");
+ }
+ }
+
+ private void startWork()
+ {
+ currentPlayer.sendMessage("Installation wizard done. Starting installation.");
+ Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ stateMachine.startWork();
+ }
+ });
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/Version.java b/EssentialsUpdate/src/net/ess3/update/Version.java
new file mode 100644
index 000000000..84f62b6c8
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/Version.java
@@ -0,0 +1,173 @@
+package net.ess3.update;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public class Version implements Comparable<Version>
+{
+ public enum Type
+ {
+ STABLE, PREVIEW, DEVELOPER
+ }
+
+ public int getMajor()
+ {
+ return major;
+ }
+
+ public int getMinor()
+ {
+ return minor;
+ }
+
+ public int getBuild()
+ {
+ return build;
+ }
+
+ public Type getType()
+ {
+ return type;
+ }
+ private final transient int major;
+ private final transient int minor;
+ private final transient int build;
+ private final transient Type type;
+
+ public Version(final String versionString)
+ {
+ final Matcher matcher = Pattern.compile("(Pre|Dev)?([0-9]+)[_\\.]([0-9]+)[_\\.]([0-9]+).*").matcher(versionString);
+ if (!matcher.matches() || matcher.groupCount() < 4)
+ {
+ type = Type.DEVELOPER;
+ major = 99;
+ minor = build = 0;
+ return;
+ }
+ if (versionString.startsWith("Pre"))
+ {
+ type = Type.PREVIEW;
+ }
+ else if (versionString.startsWith("Dev"))
+ {
+ type = Type.DEVELOPER;
+ }
+ else
+ {
+ type = Type.STABLE;
+ }
+ major = Integer.parseInt(matcher.group(2));
+ minor = Integer.parseInt(matcher.group(3));
+ build = Integer.parseInt(matcher.group(4));
+ }
+
+ @Override
+ public int compareTo(final Version other)
+ {
+ int ret = 0;
+ if (other.getType() == Type.DEVELOPER && getType() != Type.DEVELOPER)
+ {
+ ret = -1;
+ }
+ else if (getType() == Type.DEVELOPER && other.getType() != Type.DEVELOPER)
+ {
+ ret = 1;
+ }
+ else if (other.getMajor() > getMajor())
+ {
+ ret = -1;
+ }
+ else if (getMajor() > other.getMajor())
+ {
+ ret = 1;
+ }
+ else if (other.getMinor() > getMinor())
+ {
+ ret = -1;
+ }
+ else if (getMinor() > other.getMinor())
+ {
+ ret = 1;
+ }
+ else if (other.getBuild() > getBuild())
+ {
+ ret = -1;
+ }
+ else if (getBuild() > other.getBuild())
+ {
+ ret = 1;
+ }
+ else if (other.getType() == Type.STABLE && getType() == Type.PREVIEW)
+ {
+ ret = -1;
+ }
+ else if (getType() == Type.STABLE && other.getType() == Type.PREVIEW)
+ {
+ ret = 1;
+ }
+ return ret;
+ }
+
+ @Override
+ public boolean equals(final Object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ final Version other = (Version)obj;
+ if (this.major != other.major)
+ {
+ return false;
+ }
+ if (this.minor != other.minor)
+ {
+ return false;
+ }
+ if (this.build != other.build)
+ {
+ return false;
+ }
+ if (this.type != other.type)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 5;
+ hash = 71 * hash + this.major;
+ hash = 71 * hash + this.minor;
+ hash = 71 * hash + this.build;
+ hash = 71 * hash + (this.type == null ? 0 : this.type.hashCode());
+ return hash;
+ }
+
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ if (type == Type.DEVELOPER)
+ {
+ builder.append("Dev");
+ }
+ if (type == Type.PREVIEW)
+ {
+ builder.append("Pre");
+ }
+ builder.append(major);
+ builder.append('.');
+ builder.append(minor);
+ builder.append('.');
+ builder.append(build);
+ return builder.toString();
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/VersionInfo.java b/EssentialsUpdate/src/net/ess3/update/VersionInfo.java
new file mode 100644
index 000000000..afbe87de8
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/VersionInfo.java
@@ -0,0 +1,49 @@
+package net.ess3.update;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.bukkit.configuration.Configuration;
+
+
+public class VersionInfo
+{
+ private final transient List<String> changelog;
+ private final transient int minBukkit;
+ private final transient int maxBukkit;
+ private final transient Map<String, ModuleInfo> modules;
+
+ public VersionInfo(final Configuration updateConfig, final String path)
+ {
+ changelog = updateConfig.getStringList(path + ".changelog");
+ minBukkit = updateConfig.getInt(path + ".min-bukkit", 0);
+ maxBukkit = updateConfig.getInt(path + ".max-bukkit", 0);
+ modules = new HashMap<String, ModuleInfo>();
+ final String modulesPath = path + ".modules";
+ for (String module : updateConfig.getKeys(false))
+ {
+ modules.put(module, new ModuleInfo(updateConfig, modulesPath + module));
+ }
+ }
+
+ public List<String> getChangelog()
+ {
+ return Collections.unmodifiableList(changelog);
+ }
+
+ public int getMinBukkit()
+ {
+ return minBukkit;
+ }
+
+ public int getMaxBukkit()
+ {
+ return maxBukkit;
+ }
+
+ public Map<String, ModuleInfo> getModules()
+ {
+ return Collections.unmodifiableMap(modules);
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/chat/AbstractFileCommand.java b/EssentialsUpdate/src/net/ess3/update/chat/AbstractFileCommand.java
new file mode 100644
index 000000000..2e6d44f20
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/chat/AbstractFileCommand.java
@@ -0,0 +1,75 @@
+package net.ess3.update.chat;
+
+import net.ess3.update.PastieUpload;
+import java.io.*;
+import java.nio.charset.Charset;
+
+import net.ess3.update.PastieUpload;
+import org.bukkit.plugin.Plugin;
+
+
+public abstract class AbstractFileCommand implements Command
+{
+ private final transient Plugin plugin;
+ private final static Charset UTF8 = Charset.forName("utf-8");
+
+ public AbstractFileCommand(final Plugin plugin)
+ {
+ this.plugin = plugin;
+ }
+
+ protected BufferedReader getServerLogReader() throws IOException
+ {
+ final File bukkitFolder = plugin.getDataFolder().getAbsoluteFile().getParentFile().getParentFile();
+ if (bukkitFolder == null || !bukkitFolder.exists())
+ {
+ throw new IOException("Bukkit folder not found.");
+ }
+ final File logFile = new File(bukkitFolder, "server.log");
+ if (!logFile.exists())
+ {
+ throw new IOException("Server log not found.");
+ }
+ final FileInputStream fis = new FileInputStream(logFile);
+ try
+ {
+ if (logFile.length() > 1000000)
+ {
+ fis.skip(logFile.length() - 1000000);
+ }
+ return new BufferedReader(new InputStreamReader(fis));
+ }
+ catch (IOException ex)
+ {
+ fis.close();
+ throw ex;
+ }
+ }
+
+ protected BufferedReader getPluginConfig(final String pluginName, final String fileName) throws IOException
+ {
+ final File configFolder = new File(plugin.getDataFolder().getAbsoluteFile().getParentFile(), pluginName);
+ if (!configFolder.exists())
+ {
+ throw new IOException(pluginName + " plugin folder not found.");
+ }
+ final File configFile = new File(configFolder, fileName);
+ if (!configFile.exists())
+ {
+ throw new IOException(pluginName + " plugin file " + fileName + " not found.");
+ }
+ return new BufferedReader(new InputStreamReader(new FileInputStream(configFile), UTF8));
+
+ }
+
+ protected String uploadToPastie(final StringBuilder input) throws IOException
+ {
+ if (input.length() > 15000)
+ {
+ input.delete(0, input.length() - 15000);
+ input.append("## Cropped after 15000 bytes");
+ }
+ final PastieUpload pastie = new PastieUpload();
+ return pastie.send(input.toString());
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/chat/Command.java b/EssentialsUpdate/src/net/ess3/update/chat/Command.java
new file mode 100644
index 000000000..1e7e94e1c
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/chat/Command.java
@@ -0,0 +1,9 @@
+package net.ess3.update.chat;
+
+import org.bukkit.entity.Player;
+
+
+public interface Command
+{
+ void run(final IrcBot ircBot, final Player player);
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/chat/ConfigCommand.java b/EssentialsUpdate/src/net/ess3/update/chat/ConfigCommand.java
new file mode 100644
index 000000000..913294b01
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/chat/ConfigCommand.java
@@ -0,0 +1,65 @@
+package net.ess3.update.chat;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.logging.Level;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+
+
+public class ConfigCommand extends AbstractFileCommand implements Command
+{
+ public ConfigCommand(final Plugin plugin)
+ {
+ super(plugin);
+ }
+
+ @Override
+ public void run(final IrcBot ircBot, final Player player)
+ {
+ BufferedReader page = null;
+ try
+ {
+ page = getPluginConfig("Essentials-3", "config.yml");
+ final StringBuilder input = new StringBuilder();
+ do
+ {
+ final String line = page.readLine();
+ if (line == null)
+ {
+ break;
+ }
+ else
+ {
+ input.append(line).append("\n");
+ }
+ }
+ while (true);
+ page.close();
+ final String message = "Essentials config.yml: " + uploadToPastie(input);
+ player.sendMessage("§6" + ircBot.getNick() + ": §7" + message);
+ ircBot.sendMessage(message);
+ }
+ catch (IOException ex)
+ {
+ Bukkit.getLogger().log(Level.SEVERE, null, ex);
+ player.sendMessage(ex.getMessage());
+ }
+ finally
+ {
+ try
+ {
+ if (page != null)
+ {
+ page.close();
+ }
+ }
+ catch (IOException ex)
+ {
+ Bukkit.getLogger().log(Level.SEVERE, null, ex);
+ player.sendMessage(ex.getMessage());
+ }
+ }
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/chat/ErrorsCommand.java b/EssentialsUpdate/src/net/ess3/update/chat/ErrorsCommand.java
new file mode 100644
index 000000000..6e3c29924
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/chat/ErrorsCommand.java
@@ -0,0 +1,71 @@
+package net.ess3.update.chat;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.regex.Pattern;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+
+
+public class ErrorsCommand extends AbstractFileCommand implements Command
+{
+ private final transient Pattern pattern = Pattern.compile("^[0-9 :-]+\\[INFO\\].*");
+
+ public ErrorsCommand(final Plugin plugin)
+ {
+ super(plugin);
+ }
+
+ @Override
+ public void run(final IrcBot ircBot, final Player player)
+ {
+ BufferedReader page = null;
+ try
+ {
+ page = getServerLogReader();
+ final StringBuilder input = new StringBuilder();
+ do
+ {
+ final String line = page.readLine();
+ if (line == null)
+ {
+ break;
+ }
+ else
+ {
+ if (!pattern.matcher(line).matches())
+ {
+ input.append(line).append("\n");
+ }
+ }
+ }
+ while (true);
+ page.close();
+ final String message = "Errors: " + uploadToPastie(input);
+ player.sendMessage("§6" + ircBot.getNick() + ": §7" + message);
+ ircBot.sendMessage(message);
+ }
+ catch (IOException ex)
+ {
+ Bukkit.getLogger().log(Level.SEVERE, null, ex);
+ player.sendMessage(ex.getMessage());
+ }
+ finally
+ {
+ try
+ {
+ if (page != null)
+ {
+ page.close();
+ }
+ }
+ catch (IOException ex)
+ {
+ Bukkit.getLogger().log(Level.SEVERE, null, ex);
+ player.sendMessage(ex.getMessage());
+ }
+ }
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/chat/HelpCommand.java b/EssentialsUpdate/src/net/ess3/update/chat/HelpCommand.java
new file mode 100644
index 000000000..1e82953f0
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/chat/HelpCommand.java
@@ -0,0 +1,18 @@
+package net.ess3.update.chat;
+
+import org.bukkit.entity.Player;
+
+
+public class HelpCommand implements Command
+{
+ @Override
+ public void run(final IrcBot ircBot, final Player player)
+ {
+ player.sendMessage("Commands: (Note: Files send to the chat will be public viewable.)");
+ player.sendMessage("!errors - Send the last server errors to the chat.");
+ player.sendMessage("!startup - Send the last startup messages to the chat.");
+ player.sendMessage("!config - Sends your Essentials config to the chat.");
+ player.sendMessage("!list - List all players in chat.");
+ player.sendMessage("!quit - Leave chat.");
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/chat/IrcBot.java b/EssentialsUpdate/src/net/ess3/update/chat/IrcBot.java
new file mode 100644
index 000000000..10ce9ad8a
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/chat/IrcBot.java
@@ -0,0 +1,197 @@
+package net.ess3.update.chat;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.jibble.pircbot.Colors;
+import org.jibble.pircbot.IrcException;
+import org.jibble.pircbot.PircBot;
+import org.jibble.pircbot.User;
+
+
+public class IrcBot extends PircBot
+{
+ private static final String CHANNEL = "#essentials";
+ private static final int PORT = 6667;
+ private static final String SERVER = "irc.esper.net";
+ private transient boolean reconnect = true;
+ private final transient Player player;
+ private transient boolean kicked = false;
+
+ public IrcBot(final Player player, final String nickName, final String versionString)
+ {
+ super();
+ this.player = player;
+ setName(nickName);
+ setLogin("esshelp");
+ setVersion(versionString);
+ connect();
+ joinChannel(CHANNEL);
+ }
+
+ private void connect()
+ {
+ try
+ {
+ connect(SERVER, PORT);
+ }
+ catch (IOException ex)
+ {
+ Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex);
+ }
+ catch (IrcException ex)
+ {
+ Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex);
+ }
+ }
+
+ public void quit()
+ {
+ reconnect = false;
+ disconnect();
+ }
+
+ @Override
+ protected void onConnect()
+ {
+ reconnect = true;
+ }
+
+ @Override
+ protected void onDisconnect()
+ {
+ if (reconnect)
+ {
+ int tries = 10;
+ while (!isConnected())
+ {
+ try
+ {
+ tries--;
+ reconnect();
+ }
+ catch (Exception e)
+ {
+ Bukkit.getLogger().log(Level.WARNING, e.getMessage(), e);
+ try
+ {
+ Thread.sleep(10000);
+ }
+ catch (InterruptedException ex)
+ {
+ Bukkit.getLogger().log(Level.WARNING, e.getMessage(), e);
+ }
+ }
+ if (tries <= 0)
+ {
+ player.sendMessage("Connection lost to server.");
+ kicked = true;
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onKick(final String channel, final String kickerNick,
+ final String kickerLogin, final String kickerHostname,
+ final String recipientNick, final String reason)
+ {
+ if (recipientNick.equals(getNick()))
+ {
+ player.sendMessage("You have been kicked from the channel: " + reason);
+ quit();
+ kicked = true;
+ }
+ }
+
+ public boolean isKicked()
+ {
+ return kicked;
+ }
+
+ @Override
+ protected void onMessage(final String channel, final String sender,
+ final String login, final String hostname,
+ final String message)
+ {
+ player.sendMessage(formatChatMessage(sender, message, false));
+ }
+
+ @Override
+ protected void onAction(final String sender, final String login,
+ final String hostname, final String target,
+ final String action)
+ {
+ player.sendMessage(formatChatMessage(sender, action, true));
+ }
+
+ @Override
+ protected void onNotice(final String sourceNick, final String sourceLogin,
+ final String sourceHostname, final String target,
+ final String notice)
+ {
+ player.sendMessage(formatChatMessage(sourceNick, notice, false));
+ }
+
+ @Override
+ protected void onTopic(final String channel, final String topic,
+ final String setBy, final long date,
+ final boolean changed)
+ {
+ player.sendMessage(formatChatMessage(channel, topic, false));
+ }
+
+ public String formatChatMessage(final String nick, final String message, final boolean action)
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("§6");
+ if (action)
+ {
+ builder.append('*');
+ }
+ builder.append(nick);
+ if (!action)
+ {
+ builder.append(':');
+ }
+ builder.append(" §7");
+ builder.append(replaceColors(message));
+ return builder.toString();
+ }
+
+ private String replaceColors(final String message)
+ {
+ String m = Colors.removeFormatting(message);
+ m = m.replaceAll("\u000310(,(0?[0-9]|1[0-5]))?", "§b");
+ m = m.replaceAll("\u000311(,(0?[0-9]|1[0-5]))?", "§f");
+ m = m.replaceAll("\u000312(,(0?[0-9]|1[0-5]))?", "§9");
+ m = m.replaceAll("\u000313(,(0?[0-9]|1[0-5]))?", "§d");
+ m = m.replaceAll("\u000314(,(0?[0-9]|1[0-5]))?", "§8");
+ m = m.replaceAll("\u000315(,(0?[0-9]|1[0-5]))?", "§7");
+ m = m.replaceAll("\u00030?1(,(0?[0-9]|1[0-5]))?", "§0");
+ m = m.replaceAll("\u00030?2(,(0?[0-9]|1[0-5]))?", "§1");
+ m = m.replaceAll("\u00030?3(,(0?[0-9]|1[0-5]))?", "§2");
+ m = m.replaceAll("\u00030?4(,(0?[0-9]|1[0-5]))?", "§c");
+ m = m.replaceAll("\u00030?5(,(0?[0-9]|1[0-5]))?", "§4");
+ m = m.replaceAll("\u00030?6(,(0?[0-9]|1[0-5]))?", "§5");
+ m = m.replaceAll("\u00030?7(,(0?[0-9]|1[0-5]))?", "§6");
+ m = m.replaceAll("\u00030?8(,(0?[0-9]|1[0-5]))?", "§e");
+ m = m.replaceAll("\u00030?9(,(0?[0-9]|1[0-5]))?", "§a");
+ m = m.replaceAll("\u00030?0(,(0?[0-9]|1[0-5]))?", "§f");
+ m = m.replace("\u000f", "§7");
+ m = Colors.removeColors(m);
+ return m;
+ }
+
+ public void sendMessage(final String message)
+ {
+ sendMessage(CHANNEL, message);
+ }
+
+ public User[] getUsers()
+ {
+ return getUsers(CHANNEL);
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/chat/ListCommand.java b/EssentialsUpdate/src/net/ess3/update/chat/ListCommand.java
new file mode 100644
index 000000000..218b6631b
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/chat/ListCommand.java
@@ -0,0 +1,32 @@
+package net.ess3.update.chat;
+
+import org.bukkit.entity.Player;
+import org.jibble.pircbot.User;
+
+
+public class ListCommand implements Command
+{
+ @Override
+ public void run(final IrcBot ircBot, final Player player)
+ {
+ final User[] members = ircBot.getUsers();
+ final StringBuilder message = new StringBuilder();
+ for (User user : members)
+ {
+ if (message.length() > 0)
+ {
+ message.append("§f, ");
+ }
+ if (user.isOp() || user.hasVoice())
+ {
+ message.append("§6");
+ }
+ else
+ {
+ message.append("§7");
+ }
+ message.append(user.getPrefix()).append(user.getNick());
+ }
+ player.sendMessage(message.toString());
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/chat/StartupCommand.java b/EssentialsUpdate/src/net/ess3/update/chat/StartupCommand.java
new file mode 100644
index 000000000..978da60c2
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/chat/StartupCommand.java
@@ -0,0 +1,77 @@
+package net.ess3.update.chat;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.regex.Pattern;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+
+
+public class StartupCommand extends AbstractFileCommand implements Command
+{
+ private final transient Pattern patternStart = Pattern.compile("^[0-9 :-]+\\[INFO\\] Starting minecraft server version.*");
+ private final transient Pattern patternEnd = Pattern.compile("^[0-9 :-]+\\[INFO\\] Done \\([0-9.,]+s\\)! For help, type \"help\".*");
+
+ public StartupCommand(final Plugin plugin)
+ {
+ super(plugin);
+ }
+
+ @Override
+ public void run(final IrcBot ircBot, final Player player)
+ {
+ BufferedReader page = null;
+ try
+ {
+ page = getServerLogReader();
+ final StringBuilder input = new StringBuilder();
+ String line;
+ boolean log = false;
+ while ((line = page.readLine()) != null)
+ {
+ if (patternStart.matcher(line).matches())
+ {
+ if (input.length() > 0)
+ {
+ input.delete(0, input.length());
+ }
+ log = true;
+ }
+ if (log)
+ {
+ input.append(line).append("\n");
+ }
+ if (patternEnd.matcher(line).matches())
+ {
+ log = false;
+ }
+ }
+ page.close();
+ final String message = "Startup: " + uploadToPastie(input);
+ player.sendMessage("§6" + ircBot.getNick() + ": §7" + message);
+ ircBot.sendMessage(message);
+ }
+ catch (IOException ex)
+ {
+ Bukkit.getLogger().log(Level.SEVERE, null, ex);
+ player.sendMessage(ex.getMessage());
+ }
+ finally
+ {
+ try
+ {
+ if (page != null)
+ {
+ page.close();
+ }
+ }
+ catch (IOException ex)
+ {
+ Bukkit.getLogger().log(Level.SEVERE, null, ex);
+ player.sendMessage(ex.getMessage());
+ }
+ }
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/chat/UsernameUtil.java b/EssentialsUpdate/src/net/ess3/update/chat/UsernameUtil.java
new file mode 100644
index 000000000..e9dfdf728
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/chat/UsernameUtil.java
@@ -0,0 +1,124 @@
+package net.ess3.update.chat;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.bukkit.Bukkit;
+import org.bukkit.Server;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+
+
+public final class UsernameUtil
+{
+ private static final Pattern CB_PATTERN = Pattern.compile("git-Bukkit-([0-9]+).([0-9]+).([0-9]+)-[0-9]+-[0-9a-z]+-b([0-9]+)jnks.*");
+
+ private UsernameUtil()
+ {
+ }
+
+ public static String createUsername(final Player player)
+ {
+ final StringBuilder nameBuilder = new StringBuilder();
+ final Server server = Bukkit.getServer();
+ nameBuilder.append(player.getName());
+
+ addCraftBukkitVersion(server, nameBuilder);
+ addEssentialsVersion(server, nameBuilder);
+ addGroupManagerVersion(server, nameBuilder);
+ addPermissionsExVersion(server, nameBuilder);
+ addPermissionsBukkitVersion(server, nameBuilder);
+ addBPermissionsVersion(server, nameBuilder);
+ addPermissionsVersion(server, nameBuilder);
+
+ return nameBuilder.toString();
+ }
+
+ private static void addPermissionsVersion(final Server server, final StringBuilder nameBuilder)
+ {
+ final Plugin perm = server.getPluginManager().getPlugin("Permissions");
+ if (perm != null)
+ {
+ nameBuilder.append(" P");
+ if (!perm.isEnabled())
+ {
+ nameBuilder.append('!');
+ }
+ nameBuilder.append(perm.getDescription().getVersion());
+ }
+ }
+
+ private static void addBPermissionsVersion(final Server server, final StringBuilder nameBuilder)
+ {
+ final Plugin bperm = server.getPluginManager().getPlugin("bPermissions");
+ if (bperm != null)
+ {
+ nameBuilder.append(" BP");
+ if (!bperm.isEnabled())
+ {
+ nameBuilder.append('!');
+ }
+ nameBuilder.append(bperm.getDescription().getVersion());
+ }
+ }
+
+ private static void addPermissionsBukkitVersion(final Server server, final StringBuilder nameBuilder)
+ {
+ final Plugin permb = server.getPluginManager().getPlugin("PermissionsBukkit");
+ if (permb != null)
+ {
+ nameBuilder.append(" PB");
+ if (!permb.isEnabled())
+ {
+ nameBuilder.append('!');
+ }
+ nameBuilder.append(permb.getDescription().getVersion());
+ }
+ }
+
+ private static void addPermissionsExVersion(final Server server, final StringBuilder nameBuilder)
+ {
+ final Plugin pex = server.getPluginManager().getPlugin("PermissionsEx");
+ if (pex != null)
+ {
+ nameBuilder.append(" PEX");
+ if (!pex.isEnabled())
+ {
+ nameBuilder.append('!');
+ }
+ nameBuilder.append(pex.getDescription().getVersion());
+ }
+ }
+
+ private static void addGroupManagerVersion(final Server server, final StringBuilder nameBuilder)
+ {
+ final Plugin groupManager = server.getPluginManager().getPlugin("GroupManager");
+ if (groupManager != null)
+ {
+ nameBuilder.append(" GM");
+ if (!groupManager.isEnabled())
+ {
+ nameBuilder.append('!');
+ }
+ }
+ }
+
+ private static void addEssentialsVersion(final Server server, final StringBuilder nameBuilder)
+ {
+ final Plugin essentials = server.getPluginManager().getPlugin("Essentials-3");
+ if (essentials != null)
+ {
+ nameBuilder.append(" ESS");
+ nameBuilder.append(essentials.getDescription().getVersion());
+ }
+ }
+
+ private static void addCraftBukkitVersion(final Server server, final StringBuilder nameBuilder)
+ {
+ final Matcher versionMatch = CB_PATTERN.matcher(server.getVersion());
+ if (versionMatch.matches())
+ {
+ nameBuilder.append(" CB");
+ nameBuilder.append(versionMatch.group(4));
+ }
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/states/AbstractState.java b/EssentialsUpdate/src/net/ess3/update/states/AbstractState.java
new file mode 100644
index 000000000..5d4e03a2c
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/states/AbstractState.java
@@ -0,0 +1,115 @@
+package net.ess3.update.states;
+
+import net.ess3.update.AbstractWorkListener;
+import net.ess3.update.AbstractWorkListener;
+import org.bukkit.entity.Player;
+
+
+public abstract class AbstractState
+{
+ private transient boolean abortion = false;
+ private final transient StateMap stateMap;
+
+ public AbstractState(final StateMap stateMap)
+ {
+ this.stateMap = stateMap;
+ }
+
+ public <T extends AbstractState> T getState(final Class<? extends T> stateClass)
+ {
+ if (!stateMap.containsKey(stateClass))
+ {
+ try
+ {
+ final AbstractState state = stateClass.getConstructor(StateMap.class).newInstance(stateMap);
+ stateMap.put(stateClass, state);
+ }
+ catch (Exception ex)
+ {
+ /*
+ * This should never happen. All states, that are added to the map automatically, have to have a
+ * Constructor that accepts the StateMap.
+ */
+ throw new RuntimeException(ex);
+ }
+ }
+ return (T)stateMap.get(stateClass);
+ }
+
+ public abstract AbstractState getNextState();
+
+ /**
+ * Check if we already know the answer, so the user does not have to answer the question.
+ *
+ * @return true, if the answer could be guessed.
+ */
+ public boolean guessAnswer()
+ {
+ return false;
+ }
+
+ /**
+ * Ask the user the question.
+ *
+ * @param sender
+ */
+ public abstract void askQuestion(Player sender);
+
+ /**
+ * React on the answer and set internal variables
+ *
+ * @param answer
+ * @return true, if the answer could be recognized as a valid answer
+ */
+ public abstract boolean reactOnAnswer(String answer);
+
+ public final AbstractState reactOnAnswer(final Player sender, final String answer)
+ {
+ final String trimmedAnswer = answer.trim();
+ if (trimmedAnswer.equalsIgnoreCase("quit")
+ || trimmedAnswer.equalsIgnoreCase("bye")
+ || trimmedAnswer.equalsIgnoreCase("abort")
+ || trimmedAnswer.equalsIgnoreCase("cancel")
+ || trimmedAnswer.equalsIgnoreCase("exit"))
+ {
+ abort();
+ return null;
+ }
+ try
+ {
+ final boolean found = reactOnAnswer(trimmedAnswer);
+ if (found)
+ {
+ return getNextState();
+ }
+ else
+ {
+ sender.sendMessage("Answer not recognized.");
+ return this;
+ }
+ }
+ catch (RuntimeException ex)
+ {
+ sender.sendMessage(ex.toString());
+ return this;
+ }
+ }
+
+ /**
+ * Do something based on the answer, that the user gave.
+ */
+ public void doWork(final AbstractWorkListener listener)
+ {
+ listener.onWorkDone();
+ }
+
+ public boolean isAbortion()
+ {
+ return abortion;
+ }
+
+ protected void abort()
+ {
+ abortion = true;
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/states/AbstractYesNoState.java b/EssentialsUpdate/src/net/ess3/update/states/AbstractYesNoState.java
new file mode 100644
index 000000000..3ee7d0ee2
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/states/AbstractYesNoState.java
@@ -0,0 +1,57 @@
+package net.ess3.update.states;
+
+
+public abstract class AbstractYesNoState extends AbstractState
+{
+ private boolean answer = false;
+ private final transient Class<? extends AbstractState> yesState;
+ private final transient Class<? extends AbstractState> noState;
+
+ public AbstractYesNoState(final StateMap states, final Class<? extends AbstractState> nextState)
+ {
+ this(states, nextState, nextState);
+ }
+
+ public AbstractYesNoState(final StateMap states, final Class<? extends AbstractState> yesState, final Class<? extends AbstractState> noState)
+ {
+ super(states);
+ this.yesState = yesState;
+ this.noState = noState;
+ }
+
+ @Override
+ public AbstractState getNextState()
+ {
+ return answer
+ ? (yesState == null ? null : getState(yesState))
+ : (noState == null ? null : getState(noState));
+ }
+
+ @Override
+ public boolean reactOnAnswer(final String answer)
+ {
+ if (answer.equalsIgnoreCase("yes")
+ || answer.equalsIgnoreCase("y"))
+ {
+ this.answer = true;
+ return true;
+ }
+ if (answer.equalsIgnoreCase("no")
+ || answer.equalsIgnoreCase("n"))
+ {
+ this.answer = false;
+ return true;
+ }
+ return false;
+ }
+
+ public boolean getAnswer()
+ {
+ return answer;
+ }
+
+ protected void setAnswer(final boolean answer)
+ {
+ this.answer = answer;
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/states/AdvancedMode.java b/EssentialsUpdate/src/net/ess3/update/states/AdvancedMode.java
new file mode 100644
index 000000000..8ddf06eeb
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/states/AdvancedMode.java
@@ -0,0 +1,20 @@
+package net.ess3.update.states;
+
+import org.bukkit.entity.Player;
+
+
+public class AdvancedMode extends AbstractYesNoState
+{
+ public AdvancedMode(final StateMap states)
+ {
+ super(states, EssentialsChat.class);
+ }
+
+ @Override
+ public void askQuestion(final Player sender)
+ {
+ sender.sendMessage("This installation mode has a lot of options.");
+ sender.sendMessage("Do you want use the advanced mode to see all questions?");
+ sender.sendMessage("Otherwise the default values will be used.");
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/states/Changelog.java b/EssentialsUpdate/src/net/ess3/update/states/Changelog.java
new file mode 100644
index 000000000..42fdc8ca2
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/states/Changelog.java
@@ -0,0 +1,91 @@
+package net.ess3.update.states;
+
+import net.ess3.update.UpdateCheck;
+import net.ess3.update.VersionInfo;
+import java.util.List;
+import org.bukkit.entity.Player;
+
+
+public class Changelog extends AbstractState
+{
+ private static final int CHANGES_PER_PAGE = 5;
+ private transient int page = 0;
+ private transient boolean confirmed = false;
+ private transient final List<String> changes;
+ private transient final int pages;
+
+ public Changelog(final StateMap stateMap)
+ {
+ super(stateMap);
+ changes = getChanges();
+ pages = changes.size() / CHANGES_PER_PAGE + (changes.size() % CHANGES_PER_PAGE > 0 ? 1 : 0);
+ }
+
+ @Override
+ public AbstractState getNextState()
+ {
+ return confirmed ? getState(EssentialsChat.class) : this;
+ }
+
+ @Override
+ public boolean guessAnswer()
+ {
+ if (pages == 0)
+ {
+ confirmed = true;
+ }
+ return confirmed;
+ }
+
+ private List<String> getChanges()
+ {
+ final UpdateCheck updateCheck = getState(UpdateOrInstallation.class).getUpdateCheck();
+ final VersionInfo versionInfo = updateCheck.getNewVersionInfo();
+ return versionInfo.getChangelog();
+ }
+
+ @Override
+ public void askQuestion(final Player sender)
+ {
+ if (pages > 1)
+ {
+ sender.sendMessage("Changelog, page " + page + " of " + pages + ":");
+ }
+ else
+ {
+ sender.sendMessage("Changelog:");
+ }
+ for (int i = page * CHANGES_PER_PAGE; i < Math.min(page * CHANGES_PER_PAGE + CHANGES_PER_PAGE, changes.size()); i++)
+ {
+ sender.sendMessage(changes.get(i));
+ }
+ if (pages > 1)
+ {
+ sender.sendMessage("Select a page by typing the numbers 1 to " + pages + " to view all changes and then type confirm or abort.");
+ }
+ else
+ {
+ sender.sendMessage("Type confirm to update Essentials or abort to cancel the update.");
+ }
+ }
+
+ @Override
+ public boolean reactOnAnswer(final String answer)
+ {
+ if (answer.equalsIgnoreCase("confirm"))
+ {
+ confirmed = true;
+ return true;
+ }
+ if (answer.matches("[0-9]+"))
+ {
+ final int page = Integer.parseInt(answer);
+ if (page <= pages && page > 0)
+ {
+ this.page = page - 1;
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/states/EssentialsChat.java b/EssentialsUpdate/src/net/ess3/update/states/EssentialsChat.java
new file mode 100644
index 000000000..a62735f5e
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/states/EssentialsChat.java
@@ -0,0 +1,47 @@
+package net.ess3.update.states;
+
+import net.ess3.update.AbstractWorkListener;
+import net.ess3.update.tasks.InstallModule;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+
+
+public class EssentialsChat extends AbstractYesNoState
+{
+ public EssentialsChat(final StateMap states)
+ {
+ super(states, EssentialsChatSettings.class);
+ }
+
+ @Override
+ public boolean guessAnswer()
+ {
+ final Plugin plugin = Bukkit.getPluginManager().getPlugin("EssentialsChat");
+ if (plugin != null)
+ {
+ setAnswer(true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void askQuestion(final Player sender)
+ {
+ sender.sendMessage("Do you want to install EssentialsChat? (yes/no)");
+ sender.sendMessage("EssentialsChat is a simple chat formatting plugin");
+ sender.sendMessage("It allows you to make user prefixes and coloured text.");
+ }
+
+ @Override
+ public void doWork(final AbstractWorkListener listener)
+ {
+ if (getAnswer())
+ {
+ new InstallModule(listener, "EssentialsChat").start();
+ return;
+ }
+ listener.onWorkDone();
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/states/EssentialsChatSettings.java b/EssentialsUpdate/src/net/ess3/update/states/EssentialsChatSettings.java
new file mode 100644
index 000000000..db3202ab3
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/states/EssentialsChatSettings.java
@@ -0,0 +1,29 @@
+package net.ess3.update.states;
+
+import org.bukkit.entity.Player;
+
+
+public class EssentialsChatSettings extends AbstractYesNoState
+{
+ public EssentialsChatSettings(final StateMap states)
+ {
+ super(states, null);
+ }
+
+ @Override
+ public boolean guessAnswer()
+ {
+ if (getState(AdvancedMode.class).getAnswer())
+ {
+ setAnswer(false);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void askQuestion(final Player sender)
+ {
+ sender.sendMessage("Would you like to configure EssentialsChat to prefix ingame messages with their group?");
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/states/EssentialsGeoIP.java b/EssentialsUpdate/src/net/ess3/update/states/EssentialsGeoIP.java
new file mode 100644
index 000000000..cd1865b42
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/states/EssentialsGeoIP.java
@@ -0,0 +1,48 @@
+package net.ess3.update.states;
+
+import net.ess3.update.AbstractWorkListener;
+import net.ess3.update.tasks.InstallModule;
+import net.ess3.update.AbstractWorkListener;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+
+
+public class EssentialsGeoIP extends AbstractYesNoState
+{
+ public EssentialsGeoIP(final StateMap states)
+ {
+ super(states, null);
+ }
+
+ @Override
+ public boolean guessAnswer()
+ {
+ final Plugin plugin = Bukkit.getPluginManager().getPlugin("EssentialsGeoIP");
+ if (plugin != null)
+ {
+ setAnswer(true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void askQuestion(final Player sender)
+ {
+ sender.sendMessage("Do you want to install EssentialsGeoIP? (yes/no)");
+ sender.sendMessage("EssentialsGeoIP performs a IP lookup on joining players");
+ sender.sendMessage("It allows you get a rough idea of where a player is from.");
+ }
+
+ @Override
+ public void doWork(final AbstractWorkListener listener)
+ {
+ if (getAnswer())
+ {
+ new InstallModule(listener, "EssentialsGeoIP").start();
+ return;
+ }
+ listener.onWorkDone();
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/states/EssentialsProtect.java b/EssentialsUpdate/src/net/ess3/update/states/EssentialsProtect.java
new file mode 100644
index 000000000..bb3a841a7
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/states/EssentialsProtect.java
@@ -0,0 +1,48 @@
+package net.ess3.update.states;
+
+import net.ess3.update.AbstractWorkListener;
+import net.ess3.update.tasks.InstallModule;
+import net.ess3.update.AbstractWorkListener;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+
+
+public class EssentialsProtect extends AbstractYesNoState
+{
+ public EssentialsProtect(final StateMap states)
+ {
+ super(states, null);
+ }
+
+ @Override
+ public boolean guessAnswer()
+ {
+ final Plugin plugin = Bukkit.getPluginManager().getPlugin("EssentialsProtect");
+ if (plugin != null)
+ {
+ setAnswer(true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void askQuestion(final Player sender)
+ {
+ sender.sendMessage("Do you want to install EssentialsProtect? (yes/no)");
+ sender.sendMessage("EssentialsProtect is a basic world protection system");
+ sender.sendMessage("It allows you to set server wide rules, such as disabling creeper explosions, and preventing fire spread.");
+ }
+
+ @Override
+ public void doWork(final AbstractWorkListener listener)
+ {
+ if (getAnswer())
+ {
+ new InstallModule(listener, "EssentialsProtect").start();
+ return;
+ }
+ listener.onWorkDone();
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/states/InstallationFinishedEvent.java b/EssentialsUpdate/src/net/ess3/update/states/InstallationFinishedEvent.java
new file mode 100644
index 000000000..5c38db7ee
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/states/InstallationFinishedEvent.java
@@ -0,0 +1,21 @@
+package net.ess3.update.states;
+
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+
+public class InstallationFinishedEvent extends Event
+{
+ private static final HandlerList handlers = new HandlerList();
+
+ @Override
+ public HandlerList getHandlers()
+ {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList()
+ {
+ return handlers;
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/states/StateMachine.java b/EssentialsUpdate/src/net/ess3/update/states/StateMachine.java
new file mode 100644
index 000000000..b66ca4c58
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/states/StateMachine.java
@@ -0,0 +1,185 @@
+package net.ess3.update.states;
+
+import net.ess3.update.AbstractWorkListener;
+import net.ess3.update.UpdateCheck;
+import java.util.Iterator;
+
+import net.ess3.update.UpdateCheck;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+
+
+public class StateMachine extends AbstractWorkListener implements Runnable
+{
+ public enum MachineResult
+ {
+ ABORT, WAIT, DONE, NONE
+ }
+ private final transient StateMap states = new StateMap();
+ private transient AbstractState current;
+ private transient Player player;
+ private transient MachineResult result = MachineResult.NONE;
+
+ public StateMachine(final Plugin plugin, final Player player, final UpdateCheck updateCheck)
+ {
+ super(plugin, updateCheck.getNewVersionInfo());
+ this.player = player;
+ states.clear();
+ final UpdateOrInstallation state = new UpdateOrInstallation(states, updateCheck);
+ current = states.put(UpdateOrInstallation.class, state);
+ }
+
+ public MachineResult askQuestion()
+ {
+ try
+ {
+ while (current.guessAnswer())
+ {
+ current = current.getNextState();
+ if (current == null)
+ {
+ result = MachineResult.DONE;
+ break;
+ }
+ }
+ if (current != null)
+ {
+ if (player.isOnline())
+ {
+ current.askQuestion(player);
+ }
+ result = MachineResult.WAIT;
+ }
+ }
+ catch (RuntimeException ex)
+ {
+ player.sendMessage(ex.getMessage());
+ finish();
+ result = MachineResult.ABORT;
+ }
+ return result;
+ }
+
+ public MachineResult reactOnMessage(final String message)
+ {
+ result = MachineResult.NONE;
+ final AbstractState next = current.reactOnAnswer(player, message);
+ if (next == null)
+ {
+ if (current.isAbortion())
+ {
+ finish();
+ result = MachineResult.ABORT;
+ }
+ else
+ {
+ result = MachineResult.DONE;
+ }
+ }
+ else
+ {
+ current = next;
+ askQuestion();
+ }
+ return result;
+ }
+ private transient Iterator<AbstractState> iterator;
+
+ public void startWork()
+ {
+ iterator = states.values().iterator();
+ Bukkit.getScheduler().scheduleAsyncDelayedTask(getPlugin(), this);
+ }
+
+ @Override
+ public void run()
+ {
+ if (!iterator.hasNext())
+ {
+ Bukkit.getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ if (StateMachine.this.player.isOnline())
+ {
+ StateMachine.this.player.sendMessage("Installation done. Reloading server.");
+ }
+ finish();
+ Bukkit.getServer().reload();
+ }
+ });
+ return;
+ }
+ final AbstractState state = iterator.next();
+ state.doWork(this);
+ }
+
+ @Override
+ public void onWorkAbort(final String message)
+ {
+ finish();
+ Bukkit.getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ if (message != null && !message.isEmpty() && StateMachine.this.player.isOnline())
+ {
+ StateMachine.this.player.sendMessage(message);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onWorkDone(final String message)
+ {
+ Bukkit.getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ if (message != null && !message.isEmpty() && StateMachine.this.player.isOnline())
+ {
+ StateMachine.this.player.sendMessage(message);
+ }
+ Bukkit.getScheduler().scheduleAsyncDelayedTask(getPlugin(), StateMachine.this);
+ }
+ });
+ }
+
+ private void finish()
+ {
+ current = null;
+ iterator = null;
+ states.clear();
+ getPlugin().getServer().getPluginManager().callEvent(new InstallationFinishedEvent());
+ }
+
+ public void resumeInstallation(final Player player)
+ {
+ this.player = player;
+ if (result == MachineResult.WAIT)
+ {
+ if (current == null)
+ {
+ throw new RuntimeException("State is WAIT, but current state is null!");
+ }
+ current.askQuestion(player);
+ }
+ if (result == MachineResult.DONE && iterator != null)
+ {
+ player.sendMessage("Installation is still running.");
+ }
+ if (result == MachineResult.ABORT)
+ {
+ throw new RuntimeException("Player should not be able to resume an aborted installation.");
+ }
+ if (result == MachineResult.NONE)
+ {
+ throw new RuntimeException("State machine in an undefined state.");
+ }
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/states/StateMap.java b/EssentialsUpdate/src/net/ess3/update/states/StateMap.java
new file mode 100644
index 000000000..397ef7c81
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/states/StateMap.java
@@ -0,0 +1,12 @@
+package net.ess3.update.states;
+
+import java.util.LinkedHashMap;
+
+
+public class StateMap extends LinkedHashMap<Class<? extends AbstractState>, AbstractState>
+{
+ public StateMap()
+ {
+ super(50);
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/states/UpdateOrInstallation.java b/EssentialsUpdate/src/net/ess3/update/states/UpdateOrInstallation.java
new file mode 100644
index 000000000..c7bd5127a
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/states/UpdateOrInstallation.java
@@ -0,0 +1,61 @@
+package net.ess3.update.states;
+
+import net.ess3.update.UpdateCheck;
+import net.ess3.update.UpdateCheck;
+import org.bukkit.entity.Player;
+
+
+public class UpdateOrInstallation extends AbstractState
+{
+ private final transient UpdateCheck updateCheck;
+ private transient boolean update = false;
+
+ public UpdateOrInstallation(final StateMap stateMap, final UpdateCheck updateCheck)
+ {
+ super(stateMap);
+ this.updateCheck = updateCheck;
+ }
+
+ @Override
+ public boolean guessAnswer()
+ {
+ if (getUpdateCheck().isEssentialsInstalled())
+ {
+ update = true;
+ }
+ return update;
+ }
+
+ @Override
+ public AbstractState getNextState()
+ {
+ return update ? getState(Changelog.class) : getState(EssentialsChat.class);
+ }
+
+ @Override
+ public void askQuestion(final Player sender)
+ {
+ sender.sendMessage("Thank you for choosing Essentials.");
+ sender.sendMessage("The following installation wizard will guide you through the installation of Essentials.");
+ sender.sendMessage("Your answers will be saved for a later update.");
+ sender.sendMessage("Please answer the messages with yes or no, if not otherwise stated.");
+ sender.sendMessage("Write bye/exit/quit if you want to exit the wizard at anytime.");
+ sender.sendMessage("Type ok to continue...");
+ }
+
+ @Override
+ public boolean reactOnAnswer(final String answer)
+ {
+ return answer.equalsIgnoreCase("ok") || answer.equalsIgnoreCase("k") || answer.equalsIgnoreCase("continue");
+ }
+
+ public UpdateCheck getUpdateCheck()
+ {
+ return updateCheck;
+ }
+
+ public boolean isUpdate()
+ {
+ return update;
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/tasks/InstallModule.java b/EssentialsUpdate/src/net/ess3/update/tasks/InstallModule.java
new file mode 100644
index 000000000..1f7742026
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/tasks/InstallModule.java
@@ -0,0 +1,65 @@
+package net.ess3.update.tasks;
+
+import net.ess3.update.AbstractWorkListener;
+import net.ess3.update.GetFile;
+import net.ess3.update.AbstractWorkListener;
+import net.ess3.update.GetFile;
+import net.ess3.update.ModuleInfo;
+import net.ess3.update.VersionInfo;
+import java.io.File;
+import java.net.URL;
+import java.util.logging.Level;
+
+import net.ess3.update.VersionInfo;
+import org.bukkit.Bukkit;
+
+
+public class InstallModule implements Runnable, Task
+{
+ protected final transient AbstractWorkListener listener;
+ private final transient String moduleName;
+ private final transient String fileName;
+
+ public InstallModule(final AbstractWorkListener listener, final String moduleName)
+ {
+ this(listener, moduleName, moduleName + ".jar");
+ }
+
+ public InstallModule(final AbstractWorkListener listener, final String moduleName, final String fileName)
+ {
+ this.listener = listener;
+ this.moduleName = moduleName;
+ this.fileName = fileName;
+ }
+
+ @Override
+ public void start()
+ {
+ Bukkit.getScheduler().scheduleAsyncDelayedTask(listener.getPlugin(), this);
+ }
+
+ @Override
+ public void run()
+ {
+ final VersionInfo info = listener.getNewVersionInfo();
+ final ModuleInfo module = info.getModules().get(moduleName);
+ if (module == null)
+ {
+ listener.onWorkAbort("Module " + moduleName + " not found in VersionInfo.");
+ return;
+ }
+ try
+ {
+ final URL downloadUrl = module.getUrl();
+ final GetFile getFile = new GetFile(downloadUrl);
+ getFile.saveTo(new File(listener.getPlugin().getServer().getUpdateFolderFile(), fileName), module.getHash());
+ listener.onWorkDone("Module " + moduleName + " downloaded.");
+ }
+ catch (Exception ex)
+ {
+ Bukkit.getLogger().log(Level.SEVERE, "Failed to download module " + moduleName + " to " + fileName, ex);
+ listener.onWorkAbort("An error occured, please check your server log.");
+ return;
+ }
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/tasks/SelfUpdate.java b/EssentialsUpdate/src/net/ess3/update/tasks/SelfUpdate.java
new file mode 100644
index 000000000..bc0d65b5e
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/tasks/SelfUpdate.java
@@ -0,0 +1,56 @@
+package net.ess3.update.tasks;
+
+import net.ess3.update.AbstractWorkListener;
+import net.ess3.update.AbstractWorkListener;
+import org.bukkit.Bukkit;
+
+
+public class SelfUpdate extends AbstractWorkListener implements Task, Runnable
+{
+ private final transient AbstractWorkListener listener;
+
+ public SelfUpdate(final AbstractWorkListener listener)
+ {
+ super(listener.getPlugin(), listener.getNewVersionInfo());
+ this.listener = listener;
+ }
+
+ @Override
+ public void onWorkAbort(final String message)
+ {
+ listener.onWorkAbort(message);
+ }
+
+ @Override
+ public void onWorkDone(final String message)
+ {
+ listener.onWorkDone(message);
+ Bukkit.getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ Bukkit.getServer().reload();
+ }
+ });
+ }
+
+ @Override
+ public void start()
+ {
+ Bukkit.getScheduler().scheduleSyncDelayedTask(getPlugin(), this);
+ }
+
+ @Override
+ public void run()
+ {
+ Bukkit.getScheduler().scheduleAsyncDelayedTask(getPlugin(), new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ new InstallModule(SelfUpdate.this, "EssentialsUpdate").start();
+ }
+ });
+ }
+}
diff --git a/EssentialsUpdate/src/net/ess3/update/tasks/Task.java b/EssentialsUpdate/src/net/ess3/update/tasks/Task.java
new file mode 100644
index 000000000..0dc421446
--- /dev/null
+++ b/EssentialsUpdate/src/net/ess3/update/tasks/Task.java
@@ -0,0 +1,7 @@
+package net.ess3.update.tasks;
+
+
+public interface Task
+{
+ void start();
+}