From 0b96b9cb42475481cf1f2e6a9a7248b810b7755a Mon Sep 17 00:00:00 2001 From: Thijs Wiefferink Date: Mon, 12 Jun 2017 19:56:49 +0200 Subject: [PATCH] Github update checker, Java 8 - Add Github update checker, closes #201, it is implemented to allow reuse by other plugins, it will probably move to a separate repository later - Switch to Java 8, if people did not update already they probably never will - Add used Bukkit version as property, use that in all pom files - Add Task class with methods to easily use BukkitRunnable (not all code is migrated to this pattern yet) --- AreaShop/pom.xml | 2 +- .../java/me/wiefferink/areashop/AreaShop.java | 204 +++-- .../me/wiefferink/areashop/lib/Updater.java | 733 ------------------ .../listeners/PlayerLoginLogoutListener.java | 11 +- .../areashop/managers/FileManager.java | 23 +- .../areashop/tools/GithubUpdateCheck.java | 263 +++++++ .../areashop/tools/GithubUpdateChecker.java | 4 - .../me/wiefferink/areashop/tools/Task.java | 163 ++++ AreaShop/src/main/resources/lang/EN.yml | 7 +- Interfaces/pom.xml | 2 +- WorldEdit 5/pom.xml | 2 +- WorldEdit 6/pom.xml | 2 +- WorldGuard 5/pom.xml | 2 +- WorldGuard 6/pom.xml | 2 +- WorldGuard 6_1_3/pom.xml | 2 +- pom.xml | 5 +- 16 files changed, 551 insertions(+), 876 deletions(-) delete mode 100644 AreaShop/src/main/java/me/wiefferink/areashop/lib/Updater.java create mode 100644 AreaShop/src/main/java/me/wiefferink/areashop/tools/GithubUpdateCheck.java delete mode 100644 AreaShop/src/main/java/me/wiefferink/areashop/tools/GithubUpdateChecker.java create mode 100644 AreaShop/src/main/java/me/wiefferink/areashop/tools/Task.java diff --git a/AreaShop/pom.xml b/AreaShop/pom.xml index 4b06d53..525e0c3 100644 --- a/AreaShop/pom.xml +++ b/AreaShop/pom.xml @@ -17,7 +17,7 @@ org.bukkit bukkit - 1.8-R0.1-SNAPSHOT + ${bukkit.version} jar provided true diff --git a/AreaShop/src/main/java/me/wiefferink/areashop/AreaShop.java b/AreaShop/src/main/java/me/wiefferink/areashop/AreaShop.java index fe28851..a80be86 100644 --- a/AreaShop/src/main/java/me/wiefferink/areashop/AreaShop.java +++ b/AreaShop/src/main/java/me/wiefferink/areashop/AreaShop.java @@ -5,9 +5,6 @@ import com.sk89q.worldguard.bukkit.WorldGuardPlugin; import me.wiefferink.areashop.interfaces.AreaShopInterface; import me.wiefferink.areashop.interfaces.WorldEditInterface; import me.wiefferink.areashop.interfaces.WorldGuardInterface; -import me.wiefferink.areashop.lib.Updater; -import me.wiefferink.areashop.lib.Updater.UpdateResult; -import me.wiefferink.areashop.lib.Updater.UpdateType; import me.wiefferink.areashop.listeners.PlayerLoginLogoutListener; import me.wiefferink.areashop.listeners.SignBreakListener; import me.wiefferink.areashop.listeners.SignChangeListener; @@ -18,6 +15,8 @@ import me.wiefferink.areashop.managers.FileManager; import me.wiefferink.areashop.managers.Manager; import me.wiefferink.areashop.managers.SignLinkerManager; import me.wiefferink.areashop.tools.Analytics; +import me.wiefferink.areashop.tools.GithubUpdateCheck; +import me.wiefferink.areashop.tools.Task; import me.wiefferink.areashop.tools.Utils; import me.wiefferink.interactivemessenger.processing.Message; import me.wiefferink.interactivemessenger.source.LanguageManager; @@ -34,7 +33,6 @@ import org.bukkit.permissions.Permission; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; import java.util.HashSet; import java.util.List; @@ -63,9 +61,8 @@ public final class AreaShop extends JavaPlugin implements AreaShopInterface { private Set managers = null; private boolean debug = false; private List chatprefix = null; - private Updater updater = null; - private boolean updateAvailable = false; private boolean ready = false; + private GithubUpdateCheck githubUpdateCheck = null; // Folders and file names public static final String languageFolder = "lang"; @@ -157,7 +154,7 @@ public final class AreaShop extends JavaPlugin implements AreaShopInterface { int fixes = 0; Integer build = null; Plugin plugin = getServer().getPluginManager().getPlugin("WorldGuard"); - if(plugin == null || !(plugin instanceof WorldGuardPlugin)) { + if(plugin == null || !(plugin instanceof WorldGuardPlugin) || !plugin.isEnabled()) { error("WorldGuard plugin is not present or has not loaded correctly"); error = true; } else { @@ -234,7 +231,7 @@ public final class AreaShop extends JavaPlugin implements AreaShopInterface { // Check if WorldEdit is present String weVersion = null; plugin = getServer().getPluginManager().getPlugin("WorldEdit"); - if(plugin == null || !(plugin instanceof WorldEditPlugin)) { + if(plugin == null || !(plugin instanceof WorldEditPlugin) || !plugin.isEnabled()) { error("WorldEdit plugin is not present or has not loaded correctly"); error = true; } else { @@ -311,37 +308,58 @@ public final class AreaShop extends JavaPlugin implements AreaShopInterface { // Don't initialize the updatechecker if disabled in the config if(getConfig().getBoolean("checkForUpdates")) { - new BukkitRunnable() { - @Override - public void run() { - try { - updater = new Updater(AreaShop.getInstance(), 76518, null, UpdateType.NO_DOWNLOAD, false); - AreaShop.debug("Result=" + updater.getResult().toString() + ", Latest=" + updater.getLatestName() + ", Type=" + updater.getLatestType()); - updateAvailable = updater.getResult() == UpdateResult.UPDATE_AVAILABLE; - if(updateAvailable) { - AreaShop.info("Update from AreaShop V" + AreaShop.getInstance().getDescription().getVersion() + " to " + updater.getLatestName() + " available, get the latest version at http://dev.bukkit.org/bukkit-plugins/regionbuyandrent/"); - new BukkitRunnable() { - @Override - public void run() { - for(Player player : Utils.getOnlinePlayers()) { - if(player.hasPermission("areashop.notifyupdate")) { - AreaShop.getInstance().message(player, "update-playerNotify", AreaShop.getInstance().getDescription().getVersion(), AreaShop.getInstance().getUpdater().getLatestName()); - } - } - } - }.runTask(AreaShop.getInstance()); - } - } catch(Exception e) { - AreaShop.debug("Something went wrong with the Updater:"); - AreaShop.debug(e.getMessage()); - updateAvailable = false; - } + githubUpdateCheck = new GithubUpdateCheck( + AreaShop.getInstance(), + "NLThijs48", + "AreaShop" + ).withVersionComparator((latestVersion, currentVersion) -> + !cleanVersion(latestVersion).equals(cleanVersion(currentVersion)) + ).checkUpdate((result) -> { + AreaShop.debug("Update check result:", result); + if(!result.hasUpdate()) { + return; } - }.runTaskAsynchronously(this); + + AreaShop.info("Update from AreaShop V" + cleanVersion(result.getCurrentVersion()) + " to AreaShop V" + cleanVersion(result.getLatestVersion()) + " available, get the latest version at https://www.spigotmc.org/resources/areashop.2991/"); + for(Player player : Utils.getOnlinePlayers()) { + notifyUpdate(player); + } + }); } } } + /** + * Notify a player about an update if he wants notifications about it and an update is available. + * @param sender CommandSender to notify + */ + public void notifyUpdate(CommandSender sender) { + if(githubUpdateCheck != null && githubUpdateCheck.hasUpdate() && sender.hasPermission("areashop.notifyupdate")) { + AreaShop.getInstance().message(sender, "update-playerNotify", cleanVersion(githubUpdateCheck.getCurrentVersion()), cleanVersion(githubUpdateCheck.getLatestVersion())); + } + } + + /** + * Cleanup a version number. + * @param version Version to clean + * @return Cleaned up version (removed prefixes and suffixes) + */ + private String cleanVersion(String version) { + version = version.toLowerCase(); + + // Strip 'v' as used on Github tags + if(version.startsWith("v")) { + version = version.substring(1); + } + + // Strip build number as used by Jenkins + if(version.contains("#")) { + version = version.substring(0, version.indexOf("#")); + } + + return version; + } + /** * Called on shutdown or reload of the server. */ @@ -370,7 +388,6 @@ public final class AreaShop extends JavaPlugin implements AreaShopInterface { chatprefix = null; debug = false; ready = false; - updater = null; HandlerList.unregisterAll(this); } @@ -507,22 +524,6 @@ public final class AreaShop extends JavaPlugin implements AreaShopInterface { return fileManager; } - /** - * Get the updater (check the update result). - * @return The updater - */ - public Updater getUpdater() { - return updater; - } - - /** - * Check if an update for AreaShop is available. - * @return true if an update is available, otherwise false - */ - public boolean updateAvailable() { - return updateAvailable; - } - /** * Register dynamic permissions controlled by config settings. */ @@ -553,87 +554,74 @@ public final class AreaShop extends JavaPlugin implements AreaShopInterface { long expirationCheck = Utils.millisToTicks(Utils.getDurationFromSecondsOrString("expiration.delay")); final AreaShop finalPlugin = this; if(expirationCheck > 0) { - new BukkitRunnable() { - @Override - public void run() { - if(isReady()) { - finalPlugin.getFileManager().checkRents(); - AreaShop.debugTask("Checking rent expirations..."); - } else { - AreaShop.debugTask("Skipped checking rent expirations, plugin not ready"); - } + Task.syncTimer(expirationCheck, () -> { + if(isReady()) { + finalPlugin.getFileManager().checkRents(); + AreaShop.debugTask("Checking rent expirations..."); + } else { + AreaShop.debugTask("Skipped checking rent expirations, plugin not ready"); } - }.runTaskTimer(this, 1, expirationCheck); + }); } + // Inactive unrenting/selling timer long inactiveCheck = Utils.millisToTicks(Utils.getDurationFromMinutesOrString("inactive.delay")); if(inactiveCheck > 0) { - new BukkitRunnable() { - @Override - public void run() { - if(isReady()) { - finalPlugin.getFileManager().checkForInactiveRegions(); - AreaShop.debugTask("Checking for regions with players that are inactive too long..."); - } else { - AreaShop.debugTask("Skipped checking for regions of inactive players, plugin not ready"); - } + Task.syncTimer(inactiveCheck, () -> { + if(isReady()) { + finalPlugin.getFileManager().checkForInactiveRegions(); + AreaShop.debugTask("Checking for regions with players that are inactive too long..."); + } else { + AreaShop.debugTask("Skipped checking for regions of inactive players, plugin not ready"); } - }.runTaskTimer(this, inactiveCheck, inactiveCheck); + }); } + // Periodic updating of signs for timeleft tags long periodicUpdate = Utils.millisToTicks(Utils.getDurationFromSecondsOrString("signs.delay")); if(periodicUpdate > 0) { - new BukkitRunnable() { - @Override - public void run() { - if(isReady()) { - finalPlugin.getFileManager().performPeriodicSignUpdate(); - AreaShop.debugTask("Performing periodic sign update..."); - } else { - AreaShop.debugTask("Skipped performing periodic sign update, plugin not ready"); - } + Task.syncTimer(periodicUpdate, () -> { + if(isReady()) { + finalPlugin.getFileManager().performPeriodicSignUpdate(); + AreaShop.debugTask("Performing periodic sign update..."); + } else { + AreaShop.debugTask("Skipped performing periodic sign update, plugin not ready"); } - }.runTaskTimer(this, periodicUpdate, periodicUpdate); + }); } + // Saving regions and group settings long saveFiles = Utils.millisToTicks(Utils.getDurationFromMinutesOrString("saving.delay")); if(saveFiles > 0) { - new BukkitRunnable() { - @Override - public void run() { - if(isReady()) { - finalPlugin.getFileManager().saveRequiredFiles(); - AreaShop.debugTask("Saving required files..."); - } else { - AreaShop.debugTask("Skipped saving required files, plugin not ready"); - } + Task.syncTimer(saveFiles, () -> { + if(isReady()) { + finalPlugin.getFileManager().saveRequiredFiles(); + AreaShop.debugTask("Saving required files..."); + } else { + AreaShop.debugTask("Skipped saving required files, plugin not ready"); } - }.runTaskTimer(this, saveFiles, saveFiles); + }); } + // Sending warnings about rent regions to online players long expireWarning = Utils.millisToTicks(Utils.getDurationFromMinutesOrString("expireWarning.delay")); if(expireWarning > 0) { - new BukkitRunnable() { - @Override - public void run() { - if(isReady()) { - finalPlugin.getFileManager().sendRentExpireWarnings(); - AreaShop.debugTask("Sending rent expire warnings..."); - } else { - AreaShop.debugTask("Skipped sending rent expire warnings, plugin not ready"); - } + Task.syncTimer(expireWarning, () -> { + if(isReady()) { + finalPlugin.getFileManager().sendRentExpireWarnings(); + AreaShop.debugTask("Sending rent expire warnings..."); + } else { + AreaShop.debugTask("Skipped sending rent expire warnings, plugin not ready"); } - }.runTaskTimer(this, expireWarning, expireWarning); + }); } + // Update all regions on startup if(getConfig().getBoolean("updateRegionsOnStartup")) { - new BukkitRunnable() { - @Override - public void run() { - finalPlugin.getFileManager().updateAllRegions(); - AreaShop.debugTask("Updating all regions at startup..."); - } - }.runTaskLater(this, 20L); + Task.syncLater(20, () -> { + finalPlugin.getFileManager().updateAllRegions(); + AreaShop.debugTask("Updating all regions at startup..."); + }); } } diff --git a/AreaShop/src/main/java/me/wiefferink/areashop/lib/Updater.java b/AreaShop/src/main/java/me/wiefferink/areashop/lib/Updater.java deleted file mode 100644 index a6d6f76..0000000 --- a/AreaShop/src/main/java/me/wiefferink/areashop/lib/Updater.java +++ /dev/null @@ -1,733 +0,0 @@ -package me.wiefferink.areashop.lib; - -import me.wiefferink.areashop.AreaShop; -import org.apache.commons.lang.exception.ExceptionUtils; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; -import java.util.Enumeration; -import java.util.logging.Level; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -/** - * Check for updates on BukkitDev for a given plugin, and download the updates if needed. - *

- * VERY, VERY IMPORTANT: Because there are no standards for adding auto-update toggles in your plugin's config, this system provides NO CHECK WITH YOUR CONFIG to make sure the user has allowed auto-updating. - *
- * It is a BUKKIT POLICY that you include a boolean value in your config that prevents the auto-updater from running AT ALL. - *
- * If you fail to include this option in your config, your plugin will be REJECTED when you attempt to submit it to dev.bukkit.org. - *

- * An example of a good configuration option would be something similar to 'auto-update: true' - if this value is set to false you may NOT run the auto-updater. - *
- * If you are unsure about these rules, please read the plugin submission guidelines: http://goo.gl/8iU5l - * @author Gravity - * @version 2.3 - */ - -public class Updater { - - // Remote file's title - private static final String TITLE_VALUE = "name"; - // Remote file's download link - private static final String LINK_VALUE = "downloadUrl"; - // Remote file's release type - private static final String TYPE_VALUE = "releaseType"; - // Remote file's build version - private static final String VERSION_VALUE = "gameVersion"; - // Path to GET - private static final String QUERY = "/servermods/files?projectIds="; - // Slugs will be appended to this to get to the project's RSS feed - private static final String HOST = "https://api.curseforge.com"; - // User-agent when querying Curse - private static final String USER_AGENT = "Updater (by Gravity)"; - // Used for locating version numbers in file names - private static final String DELIMETER = "^V|[\\s_-]V"; - // If the version number contains one of these, don't update. - private static final String[] NO_UPDATE_TAG = {"-DEV", "-PRE", "-SNAPSHOT"}; - // Used for downloading files - private static final int BYTE_SIZE = 1024; - // Config key for api key - private static final String API_KEY_CONFIG_KEY = "api-key"; - // Config key for disabling Updater - private static final String DISABLE_CONFIG_KEY = "disable"; - // Default api key value in config - private static final String API_KEY_DEFAULT = "PUT_API_KEY_HERE"; - // Default disable value in config - private static final boolean DISABLE_DEFAULT = false; - - // Plugin running Updater - private final Plugin plugin; - // Type of update check to run - private final UpdateType type; - // Whether to announce file downloads - private final boolean announce; - // The plugin file (jar) - private final File file; - // The folder that downloads will be placed in - private final File updateFolder; - // The provided callback (if any) - private final UpdateCallback callback; - // Project's Curse ID - private int id = -1; - // BukkitDev ServerMods API key - private String apiKey = null; - - private String versionName; - private String versionLink; - private String versionType; - private String versionGameVersion; - - // Connection to RSS - private URL url; - // Updater thread - private Thread thread; - // Used for determining the outcome of the update process - private Updater.UpdateResult result = Updater.UpdateResult.SUCCESS; - - /** - * Gives the developer the result of the update process. Can be obtained by called {@link #getResult()} - */ - public enum UpdateResult { - /** - * The updater found an update, and has readied it to be loaded the next time the server restarts/reloads. - */ - SUCCESS, - /** - * The updater did not find an update, and nothing was downloaded. - */ - NO_UPDATE, - /** - * The server administrator has disabled the updating system. - */ - DISABLED, - /** - * The updater found an update, but was unable to download it. - */ - FAIL_DOWNLOAD, - /** - * For some reason, the updater was unable to contact dev.bukkit.org to download the file. - */ - FAIL_DBO, - /** - * When running the version check, the file on DBO did not contain a recognizable version. - */ - FAIL_NOVERSION, - /** - * The id provided by the plugin running the updater was invalid and doesn't exist on DBO. - */ - FAIL_BADID, - /** - * The server administrator has improperly configured their API key in the configuration. - */ - FAIL_APIKEY, - /** - * The updater found an update, but because of the UpdateType being set to NO_DOWNLOAD, it wasn't downloaded. - */ - UPDATE_AVAILABLE - } - - /** - * Allows the developer to specify the type of update that will be run. - */ - public enum UpdateType { - /** - * Run a version check, and then if the file is out of date, download the newest version. - */ - DEFAULT, - /** - * Don't run a version check, just find the latest update and download it. - */ - NO_VERSION_CHECK, - /** - * Get information about the version and the download size, but don't actually download anything. - */ - NO_DOWNLOAD - } - - /** - * Represents the various release types of a file on BukkitDev. - */ - public enum ReleaseType { - /** - * An "alpha" file. - */ - ALPHA, - /** - * A "beta" file. - */ - BETA, - /** - * A "release" file. - */ - RELEASE - } - - /** - * Initialize the updater. - * @param plugin The plugin that is checking for an update. - * @param id The dev.bukkit.org id of the project. - * @param file The file that the plugin is running from, get this by doing this.getFile() from within your main class. - * @param type Specify the type of update this will be. See {@link UpdateType} - * @param announce True if the program should announce the progress of new updates in console. - */ - public Updater(Plugin plugin, int id, File file, UpdateType type, boolean announce) { - this(plugin, id, file, type, null, announce); - } - - /** - * Initialize the updater with the provided callback. - * @param plugin The plugin that is checking for an update. - * @param id The dev.bukkit.org id of the project. - * @param file The file that the plugin is running from, get this by doing this.getFile() from within your main class. - * @param type Specify the type of update this will be. See {@link UpdateType} - * @param callback The callback instance to notify when the Updater has finished - */ - public Updater(Plugin plugin, int id, File file, UpdateType type, UpdateCallback callback) { - this(plugin, id, file, type, callback, false); - } - - /** - * Initialize the updater with the provided callback. - * @param plugin The plugin that is checking for an update. - * @param id The dev.bukkit.org id of the project. - * @param file The file that the plugin is running from, get this by doing this.getFile() from within your main class. - * @param type Specify the type of update this will be. See {@link UpdateType} - * @param callback The callback instance to notify when the Updater has finished - * @param announce True if the program should announce the progress of new updates in console. - */ - public Updater(Plugin plugin, int id, File file, UpdateType type, UpdateCallback callback, boolean announce) { - this.plugin = plugin; - this.type = type; - this.announce = announce; - this.file = file; - this.id = id; - this.updateFolder = this.plugin.getServer().getUpdateFolderFile(); - this.callback = callback; - - final File pluginFile = this.plugin.getDataFolder().getParentFile(); - final File updaterFile = new File(pluginFile, "Updater"); - final File updaterConfigFile = new File(updaterFile, "config.yml"); - - YamlConfiguration config = new YamlConfiguration(); - config.options().header("This configuration file affects all plugins using the Updater system (version 2+ - http://forums.bukkit.org/threads/96681/ )" + '\n' - + "If you wish to use your API key, read http://wiki.bukkit.org/ServerMods_API and place it below." + '\n' - + "Some updating systems will not adhere to the disabled value, but these may be turned off in their plugin's configuration."); - config.addDefault(API_KEY_CONFIG_KEY, API_KEY_DEFAULT); - config.addDefault(DISABLE_CONFIG_KEY, DISABLE_DEFAULT); - - if(!updaterFile.exists()) { - this.fileIoOrError(updaterFile, updaterFile.mkdir(), true); - } - - boolean createFile = !updaterConfigFile.exists(); - try { - if(createFile) { - this.fileIoOrError(updaterConfigFile, updaterConfigFile.createNewFile(), true); - config.options().copyDefaults(true); - config.save(updaterConfigFile); - } else { - config.load(updaterConfigFile); - } - } catch(final Exception e) { - final String message; - if(createFile) { - message = "The updater could not create configuration at " + updaterFile.getAbsolutePath(); - } else { - message = "The updater could not load configuration at " + updaterFile.getAbsolutePath(); - } - this.plugin.getLogger().log(Level.SEVERE, message, e); - } - - if(config.getBoolean(DISABLE_CONFIG_KEY)) { - this.result = UpdateResult.DISABLED; - return; - } - - String key = config.getString(API_KEY_CONFIG_KEY); - if(API_KEY_DEFAULT.equalsIgnoreCase(key) || "".equals(key)) { - key = null; - } - - this.apiKey = key; - - try { - this.url = new URL(Updater.HOST + Updater.QUERY + this.id); - } catch(final MalformedURLException e) { - this.plugin.getLogger().log(Level.SEVERE, "The project ID provided for updating, " + this.id + " is invalid.", e); - this.result = UpdateResult.FAIL_BADID; - } - - if(this.result != UpdateResult.FAIL_BADID) { - this.thread = new Thread(new UpdateRunnable()); - this.thread.start(); - } else { - runUpdater(); - } - } - - /** - * Get the result of the update process. - * @return result of the update process. - * @see UpdateResult - */ - public Updater.UpdateResult getResult() { - this.waitForThread(); - return this.result; - } - - /** - * Get the latest version's release type. - * @return latest version's release type. - * @see ReleaseType - */ - public ReleaseType getLatestType() { - this.waitForThread(); - if(this.versionType != null) { - for(ReleaseType type : ReleaseType.values()) { - if(this.versionType.equalsIgnoreCase(type.name())) { - return type; - } - } - } - return null; - } - - /** - * Get the latest version's game version (such as "CB 1.2.5-R1.0"). - * @return latest version's game version. - */ - public String getLatestGameVersion() { - this.waitForThread(); - return this.versionGameVersion; - } - - /** - * Get the latest version's name (such as "Project v1.0"). - * @return latest version's name. - */ - public String getLatestName() { - this.waitForThread(); - return this.versionName; - } - - /** - * Get the latest version's direct file link. - * @return latest version's file link. - */ - public String getLatestFileLink() { - this.waitForThread(); - return this.versionLink; - } - - /** - * As the result of Updater output depends on the thread's completion, it is necessary to wait for the thread to finish - * before allowing anyone to check the result. - */ - private void waitForThread() { - if((this.thread != null) && this.thread.isAlive()) { - try { - this.thread.join(); - } catch(final InterruptedException e) { - this.plugin.getLogger().log(Level.SEVERE, null, e); - } - } - } - - /** - * Save an update from dev.bukkit.org into the server's update folder. - * @param file the name of the file to save it as. - */ - private void saveFile(String file) { - final File folder = this.updateFolder; - - deleteOldFiles(); - if(!folder.exists()) { - this.fileIoOrError(folder, folder.mkdir(), true); - } - downloadFile(); - - // Check to see if it's a zip file, if it is, unzip it. - final File dFile = new File(folder.getAbsolutePath(), file); - if(dFile.getName().endsWith(".zip")) { - // Unzip - this.unzip(dFile.getAbsolutePath()); - } - if(this.announce) { - this.plugin.getLogger().info("Finished updating."); - } - } - - /** - * Download a file and save it to the specified folder. - */ - private void downloadFile() { - BufferedInputStream in = null; - FileOutputStream fout = null; - try { - URL fileUrl = new URL(this.versionLink); - final int fileLength = fileUrl.openConnection().getContentLength(); - in = new BufferedInputStream(fileUrl.openStream()); - fout = new FileOutputStream(new File(this.updateFolder, file.getName())); - - final byte[] data = new byte[Updater.BYTE_SIZE]; - int count; - if(this.announce) { - this.plugin.getLogger().info("About to download a new update: " + this.versionName); - } - long downloaded = 0; - while((count = in.read(data, 0, Updater.BYTE_SIZE)) != -1) { - downloaded += count; - fout.write(data, 0, count); - final int percent = (int)((downloaded * 100) / fileLength); - if(this.announce && ((percent % 10) == 0)) { - this.plugin.getLogger().info("Downloading update: " + percent + "% of " + fileLength + " bytes."); - } - } - } catch(Exception ex) { - this.plugin.getLogger().log(Level.WARNING, "The auto-updater tried to download a new update, but was unsuccessful.", ex); - this.result = Updater.UpdateResult.FAIL_DOWNLOAD; - } finally { - try { - if(in != null) { - in.close(); - } - } catch(final IOException ex) { - this.plugin.getLogger().log(Level.SEVERE, null, ex); - } - try { - if(fout != null) { - fout.close(); - } - } catch(final IOException ex) { - this.plugin.getLogger().log(Level.SEVERE, null, ex); - } - } - } - - /** - * Remove possibly leftover files from the update folder. - */ - private void deleteOldFiles() { - //Just a quick check to make sure we didn't leave any files from last time... - File[] list = listFilesOrError(this.updateFolder); - for(final File xFile : list) { - if(xFile.getName().endsWith(".zip")) { - this.fileIoOrError(xFile, xFile.mkdir(), true); - } - } - } - - /** - * Part of Zip-File-Extractor, modified by Gravity for use with Updater. - * @param file the location of the file to extract. - */ - private void unzip(String file) { - final File fSourceZip = new File(file); - try { - final String zipPath = file.substring(0, file.length() - 4); - ZipFile zipFile = new ZipFile(fSourceZip); - Enumeration e = zipFile.entries(); - while(e.hasMoreElements()) { - ZipEntry entry = e.nextElement(); - File destinationFilePath = new File(zipPath, entry.getName()); - this.fileIoOrError(destinationFilePath.getParentFile(), destinationFilePath.getParentFile().mkdirs(), true); - if(!entry.isDirectory()) { - final BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entry)); - int b; - final byte[] buffer = new byte[Updater.BYTE_SIZE]; - final FileOutputStream fos = new FileOutputStream(destinationFilePath); - final BufferedOutputStream bos = new BufferedOutputStream(fos, Updater.BYTE_SIZE); - while((b = bis.read(buffer, 0, Updater.BYTE_SIZE)) != -1) { - bos.write(buffer, 0, b); - } - bos.flush(); - bos.close(); - bis.close(); - final String name = destinationFilePath.getName(); - if(name.endsWith(".jar") && this.pluginExists(name)) { - File output = new File(this.updateFolder, name); - this.fileIoOrError(output, destinationFilePath.renameTo(output), true); - } - } - } - zipFile.close(); - - // Move any plugin data folders that were included to the right place, Bukkit won't do this for us. - moveNewZipFiles(zipPath); - - } catch(final IOException e) { - this.plugin.getLogger().log(Level.SEVERE, "The auto-updater tried to unzip a new update file, but was unsuccessful.", e); - this.result = Updater.UpdateResult.FAIL_DOWNLOAD; - } finally { - this.fileIoOrError(fSourceZip, fSourceZip.delete(), false); - } - } - - /** - * Find any new files extracted from an update into the plugin's data directory. - * @param zipPath path of extracted files. - */ - private void moveNewZipFiles(String zipPath) { - File[] list = listFilesOrError(new File(zipPath)); - for(final File ddFile : list) { - if(ddFile.isDirectory() && this.pluginExists(ddFile.getName())) { - // Current dir - final File oFile = new File(this.plugin.getDataFolder().getParent(), ddFile.getName()); - // List of existing files in the new dir - final File[] dList = listFilesOrError(ddFile); - // List of existing files in the current dir - final File[] oList = listFilesOrError(oFile); - for(File ccFile : dList) { - // Loop through all the files in the new dir - boolean found = false; - for(final File xxFile : oList) { - // Loop through all the contents in the current dir to see if it exists - if(xxFile.getName().equals(ccFile.getName())) { - found = true; - break; - } - } - if(!found) { - // Move the new file into the current dir - File output = new File(oFile, ccFile.getName()); - this.fileIoOrError(output, ccFile.renameTo(output), true); - } else { - // This file already exists, so we don't need it anymore. - this.fileIoOrError(ccFile, ccFile.delete(), false); - } - } - } - this.fileIoOrError(ddFile, ddFile.delete(), false); - } - File zip = new File(zipPath); - this.fileIoOrError(zip, zip.delete(), false); - } - - /** - * Check if the name of a jar is one of the plugins currently installed, used for extracting the correct files out of a zip. - * @param name a name to check for inside the plugins folder. - * @return true if a file inside the plugins folder is named this. - */ - private boolean pluginExists(String name) { - File[] plugins = listFilesOrError(new File("plugins")); - for(final File file : plugins) { - if(file.getName().equals(name)) { - return true; - } - } - return false; - } - - /** - * Check to see if the program should continue by evaluating whether the plugin is already updated, or shouldn't be updated. - * @return true if the version was located and is not the same as the remote's newest. - */ - private boolean versionCheck() { - final String title = this.versionName; - if(this.type != UpdateType.NO_VERSION_CHECK) { - String localVersion = this.plugin.getDescription().getVersion(); - if(localVersion.contains("#")) { // Strip build number - localVersion = localVersion.substring(0, localVersion.indexOf("#")); - } - if(title.split(DELIMETER).length == 2) { - // Get the newest file's version number - final String remoteVersion = title.split(DELIMETER)[1].split(" ")[0]; - - if(this.hasTag(localVersion) || !this.shouldUpdate(localVersion, remoteVersion)) { - // We already have the latest version, or this build is tagged for no-update - this.result = Updater.UpdateResult.NO_UPDATE; - return false; - } - } else { - // The file's name did not contain the string 'vVersion' - final String authorInfo = this.plugin.getDescription().getAuthors().isEmpty() ? "" : " (" + this.plugin.getDescription().getAuthors().get(0) + ")"; - this.plugin.getLogger().warning("The author of this plugin" + authorInfo + " has misconfigured their Auto Update system"); - this.plugin.getLogger().warning("File versions should follow the format 'PluginName vVERSION'"); - this.plugin.getLogger().warning("Please notify the author of this error."); - this.result = Updater.UpdateResult.FAIL_NOVERSION; - return false; - } - } - return true; - } - - /** - * If you wish to run mathematical versioning checks, edit this method. - *

- * With default behavior, Updater will NOT verify that a remote version available on BukkitDev - * which is not this version is indeed an "update". - * If a version is present on BukkitDev that is not the version that is currently running, - * Updater will assume that it is a newer version. - * This is because there is no standard versioning scheme, and creating a calculation that can - * determine whether a new update is actually an update is sometimes extremely complicated. - *

- *

- * Updater will call this method from {@link #versionCheck()} before deciding whether - * the remote version is actually an update. - * If you have a specific versioning scheme with which a mathematical determination can - * be reliably made to decide whether one version is higher than another, you may - * revise this method, using the local and remote version parameters, to execute the - * appropriate check. - *

- *

- * Returning a value of false will tell the update process that this is NOT a new version. - * Without revision, this method will always consider a remote version at all different from - * that of the local version a new update. - *

- * @param localVersion the current version - * @param remoteVersion the remote version - * @return true if Updater should consider the remote version an update, false if not. - */ - public boolean shouldUpdate(String localVersion, String remoteVersion) { - return !localVersion.equalsIgnoreCase(remoteVersion); - } - - /** - * Evaluate whether the version number is marked showing that it should not be updated by this program. - * @param version a version number to check for tags in. - * @return true if updating should be disabled. - */ - private boolean hasTag(String version) { - for(final String string : Updater.NO_UPDATE_TAG) { - if(version.contains(string)) { - return true; - } - } - return false; - } - - /** - * Make a connection to the BukkitDev API and request the newest file's details. - * @return true if successful. - */ - private boolean read() { - try { - final URLConnection conn = this.url.openConnection(); - conn.setConnectTimeout(5000); - - if(this.apiKey != null) { - conn.addRequestProperty("X-API-Key", this.apiKey); - } - conn.addRequestProperty("User-Agent", Updater.USER_AGENT); - - conn.setDoOutput(true); - - final BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); - final String response = reader.readLine(); - - final JSONArray array = (JSONArray)JSONValue.parse(response); - - if(array.isEmpty()) { - this.plugin.getLogger().warning("The updater could not find any files for the project id " + this.id); - this.result = UpdateResult.FAIL_BADID; - return false; - } - - JSONObject latestUpdate = (JSONObject)array.get(array.size() - 1); - this.versionName = (String)latestUpdate.get(Updater.TITLE_VALUE); - this.versionLink = (String)latestUpdate.get(Updater.LINK_VALUE); - this.versionType = (String)latestUpdate.get(Updater.TYPE_VALUE); - this.versionGameVersion = (String)latestUpdate.get(Updater.VERSION_VALUE); - - return true; - } catch(final IOException e) { - if(e.getMessage() != null && e.getMessage().contains("HTTP response code: 403")) { - this.plugin.getLogger().severe("dev.bukkit.org rejected the API key provided in plugins/Updater/config.yml"); - this.plugin.getLogger().severe("Please double-check your configuration to ensure it is correct."); - this.result = UpdateResult.FAIL_APIKEY; - } else { - this.plugin.getLogger().severe("The updater could not contact dev.bukkit.org for updating."); - this.plugin.getLogger().severe("If you have not recently modified your configuration and this is the first time you are seeing this message, the site may be experiencing temporary downtime."); - this.result = UpdateResult.FAIL_DBO; - } - AreaShop.debug(ExceptionUtils.getStackTrace(e)); - return false; - } - } - - /** - * Perform a file operation and log any errors if it fails. - * @param file file operation is performed on. - * @param result result of file operation. - * @param create true if a file is being created, false if deleted. - */ - private void fileIoOrError(File file, boolean result, boolean create) { - if(!result) { - this.plugin.getLogger().severe("The updater could not " + (create ? "create" : "delete") + " file at: " + file.getAbsolutePath()); - } - } - - private File[] listFilesOrError(File folder) { - File[] contents = folder.listFiles(); - if(contents == null) { - this.plugin.getLogger().severe("The updater could not access files at: " + this.updateFolder.getAbsolutePath()); - return new File[0]; - } else { - return contents; - } - } - - /** - * Called on main thread when the Updater has finished working, regardless - * of result. - */ - public interface UpdateCallback { - /** - * Called when the updater has finished working. - * @param updater The updater instance - */ - void onFinish(Updater updater); - } - - private class UpdateRunnable implements Runnable { - @Override - public void run() { - runUpdater(); - } - } - - private void runUpdater() { - if(this.url != null && (this.read() && this.versionCheck())) { - // Obtain the results of the project's file feed - if((this.versionLink != null) && (this.type != UpdateType.NO_DOWNLOAD)) { - String name = this.file.getName(); - // If it's a zip file, it shouldn't be downloaded as the plugin's name - if(this.versionLink.endsWith(".zip")) { - name = this.versionLink.substring(this.versionLink.lastIndexOf("/") + 1); - } - this.saveFile(name); - } else { - this.result = UpdateResult.UPDATE_AVAILABLE; - } - } - - if(this.callback != null) { - new BukkitRunnable() { - @Override - public void run() { - runCallback(); - } - }.runTask(this.plugin); - } - } - - private void runCallback() { - this.callback.onFinish(this); - } -} \ No newline at end of file diff --git a/AreaShop/src/main/java/me/wiefferink/areashop/listeners/PlayerLoginLogoutListener.java b/AreaShop/src/main/java/me/wiefferink/areashop/listeners/PlayerLoginLogoutListener.java index eb079fa..15b219e 100644 --- a/AreaShop/src/main/java/me/wiefferink/areashop/listeners/PlayerLoginLogoutListener.java +++ b/AreaShop/src/main/java/me/wiefferink/areashop/listeners/PlayerLoginLogoutListener.java @@ -42,10 +42,7 @@ public final class PlayerLoginLogoutListener implements Listener { return; } final Player player = event.getPlayer(); - // Notify admins for plugin updates - if(plugin.updateAvailable() && player.hasPermission("areashop.notifyupdate")) { - AreaShop.getInstance().message(player, "update-playerNotify", AreaShop.getInstance().getDescription().getVersion(), AreaShop.getInstance().getUpdater().getLatestName()); - } + // Schedule task to check for notifications, prevents a lag spike at login new BukkitRunnable() { @Override @@ -72,9 +69,14 @@ public final class PlayerLoginLogoutListener implements Listener { } } } + + // Notify admins for plugin updates + AreaShop.getInstance().notifyUpdate(player); + this.cancel(); } }.runTaskTimer(plugin, 25, 25); + // Check if the player has regions that use an old name of him and update them final List regions = new ArrayList<>(plugin.getFileManager().getRegions()); new BukkitRunnable() { @@ -86,6 +88,7 @@ public final class PlayerLoginLogoutListener implements Listener { if(!plugin.isReady()) { return; } + // Check all regions for(int i = 0; i < plugin.getConfig().getInt("nameupdate.regionsPerTick"); i++) { if(current < regions.size()) { diff --git a/AreaShop/src/main/java/me/wiefferink/areashop/managers/FileManager.java b/AreaShop/src/main/java/me/wiefferink/areashop/managers/FileManager.java index 878d496..8f23138 100644 --- a/AreaShop/src/main/java/me/wiefferink/areashop/managers/FileManager.java +++ b/AreaShop/src/main/java/me/wiefferink/areashop/managers/FileManager.java @@ -13,6 +13,7 @@ import me.wiefferink.areashop.regions.GeneralRegion.RegionEvent; import me.wiefferink.areashop.regions.GeneralRegion.RegionType; import me.wiefferink.areashop.regions.RegionGroup; import me.wiefferink.areashop.regions.RentRegion; +import me.wiefferink.areashop.tools.Task; import me.wiefferink.areashop.tools.Utils; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -735,23 +736,11 @@ public class FileManager extends Manager { * Unrent regions that have no time left, regions to check per tick is in the config. */ public void checkRents() { - final List regions = new ArrayList<>(getRents()); - new BukkitRunnable() { - private int current = 0; - - @Override - public void run() { - for(int i = 0; i < plugin.getConfig().getInt("expiration.regionsPerTick"); i++) { - if(current < regions.size()) { - regions.get(current).checkExpiration(); - current++; - } - } - if(current >= regions.size()) { - this.cancel(); - } - } - }.runTaskTimer(plugin, 1, 1); + Task.doForAll( + plugin.getConfig().getInt("expiration.regionsPerTick"), + getRents(), + RentRegion::checkExpiration + ); } /** diff --git a/AreaShop/src/main/java/me/wiefferink/areashop/tools/GithubUpdateCheck.java b/AreaShop/src/main/java/me/wiefferink/areashop/tools/GithubUpdateCheck.java new file mode 100644 index 0000000..a253744 --- /dev/null +++ b/AreaShop/src/main/java/me/wiefferink/areashop/tools/GithubUpdateCheck.java @@ -0,0 +1,263 @@ +package me.wiefferink.areashop.tools; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; +import java.util.logging.Logger; + +public class GithubUpdateCheck { + + public static String API_HOST = "https://api.github.com/repos"; + public static String API_LATEST_RELEASE = "releases/latest"; + public static String USER_AGENT = "GithubUpdateCheck by NLThijs48"; + public static boolean DEBUG = false; + + private String author; + private String repository; + private Plugin plugin; + private Logger logger; + private URL url; + private VersionComparator versionComparator; + + // Status + private boolean checking; + private boolean error; + private boolean hasUpdate; + private String latestVersion; + private String currentVersion; + + /** + * Create a new GithubUpdateCheck with the required information. + * @param plugin The plugin to create it for (used for logging and checking version) + * @param author The author of the plugin as used on Github + * @param repository The repository name of the plugin on Github + */ + public GithubUpdateCheck(Plugin plugin, String author, String repository) { + this.plugin = plugin; + this.logger = plugin.getLogger(); + this.author = author; + this.repository = repository; + this.currentVersion = plugin.getDescription().getVersion(); + this.versionComparator = (latestVersion, currentVersion) -> + !latestVersion.equalsIgnoreCase(currentVersion); + + this.checking = false; + this.error = false; + this.hasUpdate = false; + } + + /** + * Change the version comparator. + * @param versionComparator VersionComparator to use for checking if one version is newer than the other + * @return this + */ + public GithubUpdateCheck withVersionComparator(VersionComparator versionComparator) { + this.versionComparator = versionComparator; + return this; + } + + /** + * Check if an update is available. + */ + public void checkUpdate() { + checkUpdate(null); + } + + /** + * Check if an update is available. + * @param callback Callback to execute when the update check is done + */ + public GithubUpdateCheck checkUpdate(UpdateCallback callback) { + checking = true; + final GithubUpdateCheck self = this; + // Check for update on asyn thread + new BukkitRunnable() { + @Override + public void run() { + try { + try { + String rawUrl = API_HOST + "/" + author + "/" + repository + "/" + API_LATEST_RELEASE; + url = new URL(rawUrl); + } catch(MalformedURLException e) { + logger.severe("Invalid url: '" + url + "', are the author '" + author + "' and repository '" + repository + "' correct?"); + error = true; + return; + } + + try { + URLConnection conn = url.openConnection(); + // Give up after 15 seconds + conn.setConnectTimeout(15000); + // Identify ourselves + conn.addRequestProperty("User-Agent", USER_AGENT); + // Make sure we access the correct api version + conn.addRequestProperty("Accept", "application/vnd.github.v3+json"); + // We want to read the result + conn.setDoOutput(true); + // Open connection + try(BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { + String response = reader.readLine(); + debug("Response:", response); + + JSONObject latestRelease = (JSONObject)JSONValue.parse(response); + + if(latestRelease.isEmpty()) { + logger.warning("Failed to get api response from " + url); + error = true; + return; + } + debug("json: " + latestRelease.toJSONString()); + + // Latest version + latestVersion = (String)latestRelease.get("tag_name"); + debug("Tag name:", latestVersion); + + // Current version + debug("Plugin version:", currentVersion); + + // Compare version + hasUpdate = versionComparator.isNewer(latestVersion, currentVersion); + } + } catch(IOException e) { + logger.severe("Failed to get latest release:" + ExceptionUtils.getStackTrace(e)); + error = true; + } catch(ClassCastException e) { + logger.info("Unexpected structure of the result, failed to parse it"); + error = true; + } + } finally { + checking = false; + debug("result:", self); + if(callback != null) { + // Switch back to main thread and call the callback + new BukkitRunnable() { + @Override + public void run() { + callback.run(self); + } + }.runTask(plugin); + } + } + } + }.runTaskAsynchronously(plugin); + return this; + } + + /** + * Check if an update check is running. + * @return true if an update check is running + */ + public boolean isChecking() { + return checking; + } + + /** + * Check if the update check failed. + * @return true if the update check failed (an error message has been logged) + */ + public boolean hasFailed() { + return error; + } + + /** + * Check if an update has been found. + * @return true if an update has been found + */ + public boolean hasUpdate() { + return hasUpdate; + } + + /** + * Get the repository that this update checker is checking. + * @return Used repository + */ + public String getRepository() { + return repository; + } + + /** + * Get the author that this update checker is checking. + * @return Used author + */ + public String getAuthor() { + return author; + } + + /** + * Get the current version. + * @return Current version of the plugin + */ + public String getCurrentVersion() { + return currentVersion; + } + + /** + * Get the latest version. + * @return Latest version of the plugin (if checking is complete) + */ + public String getLatestVersion() { + return latestVersion; + } + + public interface VersionComparator { + /** + * Check if a version should be considered 'newer' than another. + * @param latestVersion Version that is available on Github + * @param currentVersion Version of the current plugin + * @return true if the latestVersion is newer than the localVersion, otherwise false + */ + boolean isNewer(String latestVersion, String currentVersion); + } + + public interface UpdateCallback { + void run(GithubUpdateCheck result); + } + + public class UpdateCheckResult { + public boolean error; + public boolean hasUpdate; + + /** + * Constructor, create empty result. + */ + public UpdateCheckResult() { + error = false; + hasUpdate = false; + } + } + + /** + * Print a debug message if DEBUG is enabled. + * @param message Message to print + */ + private void debug(Object... message) { + if(DEBUG) { + logger.info("[" + this.getClass().getSimpleName() + "] [DEBUG] " + StringUtils.join(message, " ")); + } + } + + @Override + public String toString() { + return "GithubUpdateCheck(" + StringUtils.join(Arrays.asList( + "author=" + author, + "repository=" + repository, + "plugin=" + plugin.getName(), + "checking=" + checking, + "hasUpdate=" + hasUpdate, + "error=" + error, + "currentVersion=" + currentVersion, + "latestVersion=" + latestVersion + ), ", ") + ")"; + } +} diff --git a/AreaShop/src/main/java/me/wiefferink/areashop/tools/GithubUpdateChecker.java b/AreaShop/src/main/java/me/wiefferink/areashop/tools/GithubUpdateChecker.java deleted file mode 100644 index 1f4e825..0000000 --- a/AreaShop/src/main/java/me/wiefferink/areashop/tools/GithubUpdateChecker.java +++ /dev/null @@ -1,4 +0,0 @@ -package me.wiefferink.areashop.tools; - -public class GithubUpdateChecker { -} diff --git a/AreaShop/src/main/java/me/wiefferink/areashop/tools/Task.java b/AreaShop/src/main/java/me/wiefferink/areashop/tools/Task.java new file mode 100644 index 0000000..25f077e --- /dev/null +++ b/AreaShop/src/main/java/me/wiefferink/areashop/tools/Task.java @@ -0,0 +1,163 @@ +package me.wiefferink.areashop.tools; + +import me.wiefferink.areashop.AreaShop; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.ArrayList; +import java.util.Collection; + +public class Task { + + // No access + private Task() {} + + /** + * Run a task on the main server thread. + * @param runnable The BukkitRunnable to run + */ + public static void sync(Run runnable) { + new BukkitRunnable() { + @Override + public void run() { + runnable.run(); + } + }.runTask(AreaShop.getInstance()); + } + + /** + * Run a task on the main server thread. + * @param runnable The BukkitRunnable to run + * @param delay Ticks to wait before running the task + */ + public static void syncLater(long delay, Run runnable) { + new BukkitRunnable() { + @Override + public void run() { + runnable.run(); + } + }.runTaskLater(AreaShop.getInstance(), delay); + } + + /** + * Run a task on an asynchronous thread. + * @param runnable The BukkitRunnable to run + */ + public static void async(Run runnable) { + new BukkitRunnable() { + @Override + public void run() { + runnable.run(); + } + }.runTaskAsynchronously(AreaShop.getInstance()); + } + + /** + * Run a task on an asynchronous thread. + * @param runnable The BukkitRunnable to run + * @param delay Ticks to wait before running the task + */ + public static void asyncLater(long delay, Run runnable) { + new BukkitRunnable() { + @Override + public void run() { + runnable.run(); + } + }.runTaskLaterAsynchronously(AreaShop.getInstance(), delay); + } + + /** + * Run a timer task on the main server thread. + * @param runnable The BukkitRunnable to run + */ + public static void syncTimer(long period, Run runnable) { + new BukkitRunnable() { + @Override + public void run() { + runnable.run(); + } + }.runTaskTimer(AreaShop.getInstance(), 0, period); + } + + /** + * Run a timer task on the main server thread. + * @param runnable The BukkitRunnable to run + */ + public static void syncTimer(long period, RunResult runnable) { + new BukkitRunnable() { + @Override + public void run() { + if(!runnable.run()) { + this.cancel(); + } + } + }.runTaskTimer(AreaShop.getInstance(), 0, period); + } + + /** + * Run a timer task on an asynchronous thread. + * @param runnable The BukkitRunnable to run + */ + public static void asyncTimer(long period, Run runnable) { + new BukkitRunnable() { + @Override + public void run() { + runnable.run(); + } + }.runTaskTimerAsynchronously(AreaShop.getInstance(), 0, period); + } + + /** + * Run a timer task on an asynchronous thread. + * @param runnable The BukkitRunnable to run + */ + public static void asyncTimer(long period, RunResult runnable) { + new BukkitRunnable() { + @Override + public void run() { + if(!runnable.run()) { + this.cancel(); + } + } + }.runTaskTimerAsynchronously(AreaShop.getInstance(), 0, period); + } + + /** + * Perform an action for each given object. + * @param perTick Number of objects to process per tick + * @param objects Objects to process + * @param runArgument Function to execute for each object + * @param Type of object to process + */ + public static void doForAll(int perTick, Collection objects, RunArgument runArgument) { + final ArrayList finalObjects = new ArrayList<>(objects); + new BukkitRunnable() { + private int current = 0; + + @Override + public void run() { + for(int i = 0; i < perTick; i++) { + if(current < finalObjects.size()) { + runArgument.run(finalObjects.get(current)); + current++; + } + } + if(current >= finalObjects.size()) { + this.cancel(); + } + } + }.runTaskTimer(AreaShop.getInstance(), 1, 1); + } + + + public interface Run { + void run(); + } + + public interface RunResult { + T run(); + } + + public interface RunArgument { + void run(T argument); + } +} diff --git a/AreaShop/src/main/resources/lang/EN.yml b/AreaShop/src/main/resources/lang/EN.yml index d8156ac..fd8532d 100644 --- a/AreaShop/src/main/resources/lang/EN.yml +++ b/AreaShop/src/main/resources/lang/EN.yml @@ -579,4 +579,9 @@ greeting-forsale: "%region% can be bought for %price%." greeting-bought: "%region% is bought by %player%." greeting-resale: "%region% can be bought for %resellprice% from %player%." -update-playerNotify: "[darkgreen]Update from AreaShop V%0% to %1% available, get the latest version at https://github.com/NLthijs48/AreaShop/releases." +update-playerNotify: + - "[darkgreen]Update from AreaShop V%0% to AreaShop V%1% available, get the latest version at " + - "[bold]Spigot[/bold]" + - " hover: %lang:action|Go to the Spigot AreaShop page|%" + - " link: https://www.spigotmc.org/resources/areashop.2991/" + - " (and leave a review)." diff --git a/Interfaces/pom.xml b/Interfaces/pom.xml index 2a59ccf..f6568e5 100644 --- a/Interfaces/pom.xml +++ b/Interfaces/pom.xml @@ -16,7 +16,7 @@ org.bukkit bukkit - LATEST + ${bukkit.version} jar diff --git a/WorldEdit 5/pom.xml b/WorldEdit 5/pom.xml index 9ba727f..6154eb4 100644 --- a/WorldEdit 5/pom.xml +++ b/WorldEdit 5/pom.xml @@ -16,7 +16,7 @@ org.bukkit bukkit - 1.8-R0.1-SNAPSHOT + ${bukkit.version} jar true diff --git a/WorldEdit 6/pom.xml b/WorldEdit 6/pom.xml index 8ce77ba..45bf07e 100644 --- a/WorldEdit 6/pom.xml +++ b/WorldEdit 6/pom.xml @@ -16,7 +16,7 @@ org.bukkit bukkit - 1.8-R0.1-SNAPSHOT + ${bukkit.version} jar true diff --git a/WorldGuard 5/pom.xml b/WorldGuard 5/pom.xml index f98f318..050e5ca 100644 --- a/WorldGuard 5/pom.xml +++ b/WorldGuard 5/pom.xml @@ -16,7 +16,7 @@ org.bukkit bukkit - 1.8-R0.1-SNAPSHOT + ${bukkit.version} jar true diff --git a/WorldGuard 6/pom.xml b/WorldGuard 6/pom.xml index a1639f8..91f7293 100644 --- a/WorldGuard 6/pom.xml +++ b/WorldGuard 6/pom.xml @@ -16,7 +16,7 @@ org.bukkit bukkit - 1.8-R0.1-SNAPSHOT + ${bukkit.version} jar true diff --git a/WorldGuard 6_1_3/pom.xml b/WorldGuard 6_1_3/pom.xml index 45430ba..098210f 100644 --- a/WorldGuard 6_1_3/pom.xml +++ b/WorldGuard 6_1_3/pom.xml @@ -16,7 +16,7 @@ org.bukkit bukkit - 1.8-R0.1-SNAPSHOT + ${bukkit.version} jar true diff --git a/pom.xml b/pom.xml index a357024..b82471e 100644 --- a/pom.xml +++ b/pom.xml @@ -33,9 +33,10 @@ UTF-8 - 1.7 - 1.7 + 1.8 + 1.8 #CUSTOM + 1.8-R0.1-SNAPSHOT