diff --git a/pom.xml b/pom.xml
index ab2450f..dbf8fed 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
ru.simsonic
rscPermissions
- 0.9.20b
+ 0.9.22b
jar
rscPermissions
diff --git a/src/main/java/ru/simsonic/rscPermissions/API/Settings.java b/src/main/java/ru/simsonic/rscPermissions/API/Settings.java
index 78fe44c..e9c24fa 100644
--- a/src/main/java/ru/simsonic/rscPermissions/API/Settings.java
+++ b/src/main/java/ru/simsonic/rscPermissions/API/Settings.java
@@ -5,6 +5,7 @@ import ru.simsonic.rscCommonsLibrary.ConnectionMySQL.ConnectionParams;
public interface Settings
{
+ public static final String updaterURL = "http://simsonic.github.io/rscPermissions/latest.json";
public static final String chatPrefix = "{GOLD}[rscp] {_LS}";
public static final String separator = ".";
public static final String separatorRegExp = "\\.";
diff --git a/src/main/java/ru/simsonic/rscPermissions/Bukkit/BukkitEventListener.java b/src/main/java/ru/simsonic/rscPermissions/Bukkit/BukkitEventListener.java
index b86954e..b049e29 100644
--- a/src/main/java/ru/simsonic/rscPermissions/Bukkit/BukkitEventListener.java
+++ b/src/main/java/ru/simsonic/rscPermissions/Bukkit/BukkitEventListener.java
@@ -9,6 +9,7 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.event.player.PlayerExpChangeEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerKickEvent;
import org.bukkit.event.player.PlayerLevelChangeEvent;
import org.bukkit.event.player.PlayerLoginEvent;
@@ -60,6 +61,14 @@ public class BukkitEventListener implements Listener
{
rscp.permissionManager.recalculatePlayer(event.getPlayer());
}
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onPlayerJoin(PlayerJoinEvent event)
+ {
+ final Player player = event.getPlayer();
+ // Inform admins about updates
+ if(player.hasPermission("rscp.admin"))
+ rscp.updating.onAdminJoin(player, true);
+ }
@EventHandler
public void onPlayerExp(PlayerLevelChangeEvent event)
{
diff --git a/src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/BukkitCommands.java b/src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/BukkitCommands.java
index f2f95e8..f9d8799 100644
--- a/src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/BukkitCommands.java
+++ b/src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/BukkitCommands.java
@@ -23,6 +23,7 @@ public class BukkitCommands
private final CommandFetch cmdFetch;
private final CommandDebug cmdDebug;
private final CommandReload cmdReload;
+ private final CommandUpdate cmdUpdate;
public final BukkitDatabaseFetcher threadFetchDatabaseContents;
public BukkitCommands(final BukkitPluginMain plugin)
{
@@ -31,6 +32,7 @@ public class BukkitCommands
cmdFetch = new CommandFetch(rscp);
cmdDebug = new CommandDebug(rscp);
cmdReload = new CommandReload(rscp);
+ cmdUpdate = new CommandUpdate(rscp);
threadFetchDatabaseContents = new BukkitDatabaseFetcher(rscp);
}
public Thread threadMigrateFromPExSQL(final CommandSender sender)
@@ -167,6 +169,9 @@ public class BukkitCommands
/* rscp reload */
cmdReload.execute(sender);
return;
+ case "update":
+ cmdUpdate.execute(sender, args);
+ return;
case "help":
default:
break;
diff --git a/src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/CommandUpdate.java b/src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/CommandUpdate.java
new file mode 100644
index 0000000..357122d
--- /dev/null
+++ b/src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/CommandUpdate.java
@@ -0,0 +1,27 @@
+package ru.simsonic.rscPermissions.Bukkit.Commands;
+
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import ru.simsonic.rscMinecraftLibrary.Bukkit.CommandAnswerException;
+import ru.simsonic.rscPermissions.BukkitPluginMain;
+
+public class CommandUpdate
+{
+ private final BukkitPluginMain rscp;
+ CommandUpdate(BukkitPluginMain plugin)
+ {
+ this.rscp = plugin;
+ }
+ public void execute(CommandSender sender, String args[]) throws CommandAnswerException
+ {
+ if(sender.hasPermission("rscp.admin"))
+ {
+ if(args.length > 0 && "do".equals(args[0]))
+ {
+ rscp.updating.doUpdate(sender instanceof Player ? (Player)sender : null);
+ } else {
+ rscp.updating.checkUpdate(sender instanceof Player ? (Player)sender : null);
+ }
+ }
+ }
+}
diff --git a/src/main/java/ru/simsonic/rscPermissions/BukkitPluginMain.java b/src/main/java/ru/simsonic/rscPermissions/BukkitPluginMain.java
index 50614f9..f6bddcd 100644
--- a/src/main/java/ru/simsonic/rscPermissions/BukkitPluginMain.java
+++ b/src/main/java/ru/simsonic/rscPermissions/BukkitPluginMain.java
@@ -6,11 +6,13 @@ import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler;
import org.mcstats.MetricsLite;
import ru.simsonic.rscMinecraftLibrary.Bukkit.CommandAnswerException;
import ru.simsonic.rscMinecraftLibrary.Bukkit.GenericChatCodes;
+import ru.simsonic.rscMinecraftLibrary.Bukkit.Tools;
import ru.simsonic.rscPermissions.API.Settings;
import ru.simsonic.rscPermissions.Bukkit.BukkitEventListener;
import ru.simsonic.rscPermissions.Bukkit.BukkitPermissionManager;
@@ -23,20 +25,22 @@ import ru.simsonic.rscPermissions.Engine.Backends.BackendJson;
import ru.simsonic.rscPermissions.Engine.Backends.DatabaseContents;
import ru.simsonic.rscPermissions.Engine.InternalCache;
import ru.simsonic.rscPermissions.Engine.Phrases;
+import ru.simsonic.rscPermissions.Updater.BukkitUpdater;
public final class BukkitPluginMain extends JavaPlugin
{
public static final Logger consoleLog = Bukkit.getLogger();
- public final Settings settings = new BukkitPluginConfiguration(this);
- public final BridgeForBukkitAPI bridgeForBukkit = new BridgeForBukkitAPI(this);
- public final BukkitEventListener bukkitListener = new BukkitEventListener(this);
- public final BackendJson localStorage = new BackendJson(getDataFolder());
- public final BackendDatabase connection = new BackendDatabase(consoleLog);
- public final InternalCache internalCache = new InternalCache();
- public final BukkitPermissionManager permissionManager = new BukkitPermissionManager(this);
- public final BukkitRegionProviders regionListProvider = new BukkitRegionProviders(this);
- private final RegionUpdateObserver regionUpdateObserver = new RegionUpdateObserver(this);
- public final BukkitCommands commandHelper = new BukkitCommands(this);
+ public final Settings settings = new BukkitPluginConfiguration(this);
+ public final BukkitUpdater updating = new BukkitUpdater(this, Settings.updaterURL, Settings.chatPrefix);
+ public final BackendJson localStorage = new BackendJson(getDataFolder());
+ public final BackendDatabase connection = new BackendDatabase(consoleLog);
+ public final InternalCache internalCache = new InternalCache();
+ public final BukkitCommands commandHelper = new BukkitCommands(this);
+ public final BridgeForBukkitAPI bridgeForBukkit = new BridgeForBukkitAPI(this);
+ public final BukkitEventListener bukkitListener = new BukkitEventListener(this);
+ public final BukkitPermissionManager permissionManager = new BukkitPermissionManager(this);
+ public final BukkitRegionProviders regionListProvider = new BukkitRegionProviders(this);
+ private final RegionUpdateObserver regionUpdateObserver = new RegionUpdateObserver(this);
private MetricsLite metrics;
@Override
public void onLoad()
@@ -51,6 +55,7 @@ public final class BukkitPluginMain extends JavaPlugin
{
// Read settings and setup components
settings.readSettings();
+ updating.onEnable();
bukkitListener.onEnable();
internalCache.setDefaultGroup(
settings.getDefaultGroup(),
@@ -99,6 +104,9 @@ public final class BukkitPluginMain extends JavaPlugin
if(settings.getAutoReloadDelayTicks() > 0)
commandHelper.threadFetchDatabaseContents.startDeamon();
// Done
+ for(Player online : Tools.getOnlinePlayers())
+ if(online.hasPermission("rscm.admin"))
+ updating.onAdminJoin(online, false);
consoleLog.info(Phrases.PLUGIN_ENABLED.toString());
}
@Override
diff --git a/src/main/java/ru/simsonic/rscPermissions/Updater/BukkitUpdater.java b/src/main/java/ru/simsonic/rscPermissions/Updater/BukkitUpdater.java
new file mode 100644
index 0000000..30d185e
--- /dev/null
+++ b/src/main/java/ru/simsonic/rscPermissions/Updater/BukkitUpdater.java
@@ -0,0 +1,247 @@
+package ru.simsonic.rscPermissions.Updater;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonParseException;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerKickEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.plugin.java.JavaPlugin;
+import ru.simsonic.rscCommonsLibrary.RestartableThread;
+import ru.simsonic.rscMinecraftLibrary.Bukkit.GenericChatCodes;
+
+public final class BukkitUpdater implements Listener
+{
+ private final JavaPlugin plugin;
+ private final String latestURL;
+ private final HashSet staff = new HashSet<>();
+ private final String chatPrefix;
+ public BukkitUpdater(JavaPlugin plugin, String latestURL, String chatPrefix)
+ {
+ this.plugin = plugin;
+ this.latestURL = latestURL;
+ this.chatPrefix = chatPrefix;
+ }
+ public void onEnable()
+ {
+ plugin.getServer().getPluginManager().registerEvents(BukkitUpdater.this, plugin);
+ checkUpdate(null);
+ }
+ public void checkUpdate(Player sender)
+ {
+ if(sender != null)
+ staff.add(sender);
+ threadCheck.start();
+ }
+ public void doUpdate(Player sender)
+ {
+ if(sender != null)
+ staff.add(sender);
+ threadUpdate.start();
+ }
+ private final RestartableThread threadCheck = new RestartableThread()
+ {
+ @Override
+ public void run()
+ {
+ checkForUpdate();
+ final ArrayList lines = latestToLines();
+ if(lines != null)
+ runLines(lines.toArray(new String[lines.size()]));
+ else
+ runLine("You are using the latest version.");
+ }
+ };
+ private final RestartableThread threadUpdate = new RestartableThread()
+ {
+ @Override
+ public void run()
+ {
+ runLine("Downloading update...");
+ if(downloadUpdate())
+ {
+ // SUCCESS
+ runLine("Installing update...");
+ installUpdate();
+ runLines(new String[]
+ {
+ "{_LG}Installation complete!",
+ "Please restart your server to avoid errors.",
+ });
+ } else {
+ // FAILED
+ runLines(new String[] {
+ "{_LR}Downloading error!",
+ "Cannot download update file. Please try later.",
+ });
+ }
+ }
+ };
+ private Latest latest = new Latest();
+ private void checkForUpdate()
+ {
+ try
+ {
+ this.latest = new Gson().fromJson(downloadJson(latestURL), Latest.class);
+ } catch(IOException ex) {
+ this.latest = new Latest();
+ }
+ if(latest.note == null)
+ latest.note = "New version: " + latest.version;
+ if(latest.notes == null)
+ latest.notes = new String[] { latest.note };
+ if(latest.version == null)
+ latest.version = plugin.getDescription().getVersion();
+ }
+ private void runLine(final String line)
+ {
+ runLines(new String[] { line });
+ }
+ private void runLines(final String[] lines)
+ {
+ final Runnable syncTask = new Runnable()
+ {
+ @Override
+ public synchronized void run()
+ {
+ // CONSOLE
+ final ConsoleCommandSender console = plugin.getServer().getConsoleSender();
+ for(String line : lines)
+ if(line != null)
+ console.sendMessage(GenericChatCodes.processStringStatic(chatPrefix + line));
+ // PLAYERS
+ for(Player online : staff)
+ for(String line : lines)
+ if(line != null)
+ online.sendMessage(GenericChatCodes.processStringStatic(chatPrefix + line));
+ notify();
+ }
+ };
+ try
+ {
+ synchronized(syncTask)
+ {
+ plugin.getServer().getScheduler().runTask(plugin, syncTask);
+ syncTask.wait();
+ }
+ } catch(InterruptedException ex) {
+ }
+ }
+ private ArrayList latestToLines()
+ {
+ // THERE IS NO UPDATE
+ if(plugin.getDescription().getVersion().equals(latest.version))
+ return null;
+ // THERE IS AN UPDATE
+ final ArrayList result = new ArrayList<>();
+ result.add("New "
+ + (latest.snapshot ? "{_DS}snapshot {_LS}" : "{_WH}release {_LS}")
+ + "version {_LG}" + latest.version + "{_LS} is available!");
+ result.addAll(Arrays.asList(latest.notes));
+ result.add("Apply this update with command {GOLD}/rscfjd update do");
+ return result;
+ }
+ public void onAdminJoin(Player player, boolean fromEvent)
+ {
+ staff.add(player);
+ if(fromEvent)
+ {
+ final ArrayList lines = latestToLines();
+ if(lines != null)
+ for(String line : lines)
+ if(line != null)
+ player.sendMessage(GenericChatCodes.processStringStatic(chatPrefix + line));
+ }
+ }
+ @EventHandler
+ protected void onPlayerQuit(PlayerQuitEvent event)
+ {
+ staff.add(event.getPlayer());
+ }
+ @EventHandler
+ protected void onPlayerKick(PlayerKickEvent event)
+ {
+ staff.add(event.getPlayer());
+ }
+ private static String downloadJson(String url) throws IOException
+ {
+ try
+ {
+ final HttpURLConnection connection = (HttpURLConnection)new URL(url).openConnection();
+ connection.setDoOutput(true);
+ connection.setDoInput(true);
+ connection.setConnectTimeout(5000);
+ connection.setReadTimeout(5000);
+ connection.setUseCaches(false);
+ final int responseCode = connection.getResponseCode();
+ if(responseCode == HttpURLConnection.HTTP_OK)
+ return readUnicodeStream(connection.getInputStream());
+ throw new IOException(new StringBuilder()
+ .append(Integer.toString(responseCode))
+ .append("Erroneous result of executing web-method: ")
+ .append(connection.getResponseMessage())
+ .append("\r\n")
+ .append(readUnicodeStream(connection.getErrorStream()))
+ .toString());
+ } catch(JsonParseException | MalformedURLException ex) {
+ throw new IOException(ex);
+ } catch(IOException ex) {
+ throw ex;
+ }
+ }
+ private static String readUnicodeStream(InputStream is) throws IOException
+ {
+ try(ByteArrayOutputStream baos = new ByteArrayOutputStream())
+ {
+ final byte[] buffer = new byte[1024];
+ for(int length = 0; length != -1; length = is.read(buffer))
+ baos.write(buffer, 0, length);
+ return new String(baos.toByteArray(), "UTF-8");
+ }
+ }
+ private boolean downloadUpdate()
+ {
+ final File folder = plugin.getDataFolder().getParentFile();
+ final File target = new File(folder, plugin.getName() + "_v" + latest.version + ".jar");
+ try(FileOutputStream fos = new FileOutputStream(target))
+ {
+ final ReadableByteChannel rbc = Channels.newChannel(new URL(latest.url).openStream());
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.flush();
+ return true;
+ } catch(MalformedURLException ex) {
+ System.err.println(ex);
+ } catch(IOException ex) {
+ System.err.println(ex);
+ }
+ return false;
+ }
+ private void installUpdate()
+ {
+ // RENAME OLD VERSION
+ try
+ {
+ final String outdatedJarPath = plugin.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
+ final File outdatedJarSrc = new File(outdatedJarPath);
+ final File outdatedJarDst = new File(outdatedJarPath + "-outdated");
+ outdatedJarSrc.renameTo(outdatedJarDst);
+ } catch(URISyntaxException ex) {
+ }
+ }
+}
diff --git a/src/main/java/ru/simsonic/rscPermissions/Updater/Latest.java b/src/main/java/ru/simsonic/rscPermissions/Updater/Latest.java
new file mode 100644
index 0000000..ae3c725
--- /dev/null
+++ b/src/main/java/ru/simsonic/rscPermissions/Updater/Latest.java
@@ -0,0 +1,10 @@
+package ru.simsonic.rscPermissions.Updater;
+
+public class Latest
+{
+ public String version;
+ public String note;
+ public String[] notes;
+ public String url;
+ public boolean snapshot;
+}