From 16d0b5c228dd8d981a0d79944c70248ea813d63b Mon Sep 17 00:00:00 2001 From: ementalo Date: Wed, 27 Jun 2012 13:35:39 +0100 Subject: package name change to net.ess3 --- .../src/net/ess3/update/AbstractWorkListener.java | 39 ++++ .../src/net/ess3/update/EssentialsHelp.java | 171 +++++++++++++++++ .../src/net/ess3/update/EssentialsUpdate.java | 59 ++++++ EssentialsUpdate/src/net/ess3/update/GetFile.java | 113 ++++++++++++ .../src/net/ess3/update/ModuleInfo.java | 35 ++++ .../src/net/ess3/update/PastieUpload.java | 40 ++++ .../src/net/ess3/update/PostToUrl.java | 66 +++++++ .../src/net/ess3/update/UpdateCheck.java | 202 ++++++++++++++++++++ .../src/net/ess3/update/UpdateFile.java | 204 +++++++++++++++++++++ .../src/net/ess3/update/UpdateProcess.java | 199 ++++++++++++++++++++ EssentialsUpdate/src/net/ess3/update/Version.java | 173 +++++++++++++++++ .../src/net/ess3/update/VersionInfo.java | 49 +++++ .../net/ess3/update/chat/AbstractFileCommand.java | 75 ++++++++ .../src/net/ess3/update/chat/Command.java | 9 + .../src/net/ess3/update/chat/ConfigCommand.java | 65 +++++++ .../src/net/ess3/update/chat/ErrorsCommand.java | 71 +++++++ .../src/net/ess3/update/chat/HelpCommand.java | 18 ++ .../src/net/ess3/update/chat/IrcBot.java | 197 ++++++++++++++++++++ .../src/net/ess3/update/chat/ListCommand.java | 32 ++++ .../src/net/ess3/update/chat/StartupCommand.java | 77 ++++++++ .../src/net/ess3/update/chat/UsernameUtil.java | 124 +++++++++++++ .../src/net/ess3/update/states/AbstractState.java | 115 ++++++++++++ .../net/ess3/update/states/AbstractYesNoState.java | 57 ++++++ .../src/net/ess3/update/states/AdvancedMode.java | 20 ++ .../src/net/ess3/update/states/Changelog.java | 91 +++++++++ .../src/net/ess3/update/states/EssentialsChat.java | 47 +++++ .../ess3/update/states/EssentialsChatSettings.java | 29 +++ .../net/ess3/update/states/EssentialsGeoIP.java | 48 +++++ .../net/ess3/update/states/EssentialsProtect.java | 48 +++++ .../update/states/InstallationFinishedEvent.java | 21 +++ .../src/net/ess3/update/states/StateMachine.java | 185 +++++++++++++++++++ .../src/net/ess3/update/states/StateMap.java | 12 ++ .../ess3/update/states/UpdateOrInstallation.java | 61 ++++++ .../src/net/ess3/update/tasks/InstallModule.java | 65 +++++++ .../src/net/ess3/update/tasks/SelfUpdate.java | 56 ++++++ .../src/net/ess3/update/tasks/Task.java | 7 + 36 files changed, 2880 insertions(+) create mode 100644 EssentialsUpdate/src/net/ess3/update/AbstractWorkListener.java create mode 100644 EssentialsUpdate/src/net/ess3/update/EssentialsHelp.java create mode 100644 EssentialsUpdate/src/net/ess3/update/EssentialsUpdate.java create mode 100644 EssentialsUpdate/src/net/ess3/update/GetFile.java create mode 100644 EssentialsUpdate/src/net/ess3/update/ModuleInfo.java create mode 100644 EssentialsUpdate/src/net/ess3/update/PastieUpload.java create mode 100644 EssentialsUpdate/src/net/ess3/update/PostToUrl.java create mode 100644 EssentialsUpdate/src/net/ess3/update/UpdateCheck.java create mode 100644 EssentialsUpdate/src/net/ess3/update/UpdateFile.java create mode 100644 EssentialsUpdate/src/net/ess3/update/UpdateProcess.java create mode 100644 EssentialsUpdate/src/net/ess3/update/Version.java create mode 100644 EssentialsUpdate/src/net/ess3/update/VersionInfo.java create mode 100644 EssentialsUpdate/src/net/ess3/update/chat/AbstractFileCommand.java create mode 100644 EssentialsUpdate/src/net/ess3/update/chat/Command.java create mode 100644 EssentialsUpdate/src/net/ess3/update/chat/ConfigCommand.java create mode 100644 EssentialsUpdate/src/net/ess3/update/chat/ErrorsCommand.java create mode 100644 EssentialsUpdate/src/net/ess3/update/chat/HelpCommand.java create mode 100644 EssentialsUpdate/src/net/ess3/update/chat/IrcBot.java create mode 100644 EssentialsUpdate/src/net/ess3/update/chat/ListCommand.java create mode 100644 EssentialsUpdate/src/net/ess3/update/chat/StartupCommand.java create mode 100644 EssentialsUpdate/src/net/ess3/update/chat/UsernameUtil.java create mode 100644 EssentialsUpdate/src/net/ess3/update/states/AbstractState.java create mode 100644 EssentialsUpdate/src/net/ess3/update/states/AbstractYesNoState.java create mode 100644 EssentialsUpdate/src/net/ess3/update/states/AdvancedMode.java create mode 100644 EssentialsUpdate/src/net/ess3/update/states/Changelog.java create mode 100644 EssentialsUpdate/src/net/ess3/update/states/EssentialsChat.java create mode 100644 EssentialsUpdate/src/net/ess3/update/states/EssentialsChatSettings.java create mode 100644 EssentialsUpdate/src/net/ess3/update/states/EssentialsGeoIP.java create mode 100644 EssentialsUpdate/src/net/ess3/update/states/EssentialsProtect.java create mode 100644 EssentialsUpdate/src/net/ess3/update/states/InstallationFinishedEvent.java create mode 100644 EssentialsUpdate/src/net/ess3/update/states/StateMachine.java create mode 100644 EssentialsUpdate/src/net/ess3/update/states/StateMap.java create mode 100644 EssentialsUpdate/src/net/ess3/update/states/UpdateOrInstallation.java create mode 100644 EssentialsUpdate/src/net/ess3/update/tasks/InstallModule.java create mode 100644 EssentialsUpdate/src/net/ess3/update/tasks/SelfUpdate.java create mode 100644 EssentialsUpdate/src/net/ess3/update/tasks/Task.java (limited to 'EssentialsUpdate/src/net/ess3/update') 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 commands = new HashMap(); + + 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 map = new HashMap(); + 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 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 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 versions = updateFile.getVersions(); + final int bukkitVersion = getBukkitVersion(); + Version higher = null; + Version found = null; + Version lower = null; + int bukkitHigher = 0; + int bukkitLower = 0; + for (Entry 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 versions = new TreeMap(); + + 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 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 +{ + 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 changelog; + private final transient int minBukkit; + private final transient int maxBukkit; + private final transient Map 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(); + final String modulesPath = path + ".modules"; + for (String module : updateConfig.getKeys(false)) + { + modules.put(module, new ModuleInfo(updateConfig, modulesPath + module)); + } + } + + public List getChangelog() + { + return Collections.unmodifiableList(changelog); + } + + public int getMinBukkit() + { + return minBukkit; + } + + public int getMaxBukkit() + { + return maxBukkit; + } + + public Map 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 getState(final Class 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 yesState; + private final transient Class noState; + + public AbstractYesNoState(final StateMap states, final Class nextState) + { + this(states, nextState, nextState); + } + + public AbstractYesNoState(final StateMap states, final Class yesState, final Class 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 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 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 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, 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(); +} -- cgit v1.2.3