Resource Async Update Checker for Premium and Regular Plugins

Discussion in 'Spigot Plugin Development' started by Benz56, Jul 9, 2018.

  1. Benz56

    Moderator Supporter

    Single Class Update Checker

    This is a simple update checker which compares the latest version of your plugin on Spigot to the local version defined in the plugin.yml file running on the current server. Whether or not a new version is determined to be available is based on a simple String equality check. There's no fancy number conversions or parsing; if the latest version on Spigot is not the same as the one in the plugin.yml a new version must be available. Simple as that. This works for both premium resources as well as regular resources.

    This is done in a single class in which I've extracted the configurable parts for you to customize (the constants). The class is partially commented though it should be pretty straightforward what is going on.

    If a new update is available it will be logged to the console as well as register a PlayerJoinEvent/Listener in which any player joining based on OP, a permission, and a perm only boolean will get notified of a new update.

    Code (Java):
    public class UpdateChecker {

        private final JavaPlugin javaPlugin;
        private final String localPluginVersion;
        private String spigotPluginVersion;

        //Constants. Customize to your liking.
        private static final int ID = 44876; //The ID of your resource. Can be found in the resource URL.
        private static final String ERR_MSG = "&cUpdate checker failed!";
        private static final String UPDATE_MSG = "&fA new update is available at:&b https://www.spigotmc.org/resources/" + ID + "/updates";
        //PermissionDefault.FALSE == OPs need the permission to be notified.
        //PermissionDefault.TRUE == all OPs are notified regardless of having the permission.
        private static final Permission UPDATE_PERM = new Permission("yourplugin.update", PermissionDefault.FALSE);
        private static final long CHECK_INTERVAL = 12_000; //In ticks.

        public UpdateChecker(final JavaPlugin javaPlugin) {
            this.javaPlugin = javaPlugin;
            this.localPluginVersion = javaPlugin.getDescription().getVersion();
        }

        public void checkForUpdate() {
            new BukkitRunnable() {
                @Override
                public void run() {
                    //The request is executed asynchronously as to not block the main thread.
                    Bukkit.getScheduler().runTaskAsynchronously(javaPlugin, () -> {
                        //Request the current version of your plugin on SpigotMC.
                        try {
                            final HttpsURLConnection connection = (HttpsURLConnection) new URL("https://api.spigotmc.org/legacy/update.php?resource=" + ID).openConnection();
                            connection.setRequestMethod("GET");
                            spigotPluginVersion = new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine();
                        } catch (final IOException e) {
                            Bukkit.getServer().getConsoleSender().sendMessage(ChatColor.translateAlternateColorCodes('&', ERR_MSG));
                            e.printStackTrace();
                            cancel();
                            return;
                        }

                        //Check if the requested version is the same as the one in your plugin.yml.
                        if (localPluginVersion.equals(spigotPluginVersion)) return;

                        Bukkit.getServer().getConsoleSender().sendMessage(ChatColor.translateAlternateColorCodes('&', UPDATE_MSG));

                        //Register the PlayerJoinEvent
                        Bukkit.getScheduler().runTask(javaPlugin, () -> Bukkit.getPluginManager().registerEvents(new Listener() {
                            @EventHandler(priority = EventPriority.MONITOR)
                            public void onPlayerJoin(final PlayerJoinEvent event) {
                                final Player player = event.getPlayer();
                                if (!player.hasPermission(UPDATE_PERM)) return;
                                player.sendMessage(ChatColor.translateAlternateColorCodes('&', UPDATE_MSG));
                            }
                        }, javaPlugin));

                        cancel(); //Cancel the runnable as an update has been found.
                    });
                }
            }.runTaskTimer(javaPlugin, 0, CHECK_INTERVAL);
        }
    }
    GitHub.


    Checking for an update is as simple as including the following in your onEnable:
    Code (Java):
    @Override
    public void onEnable() {
        new UpdateChecker(this).checkForUpdate();
    }

    I've decided to share this code as I'd been looking online for a simple utility class to achieve this, however, there wasn't really an updated utility available that kept things simple and did what I wanted. Most of the ones available include a ton of, what I feel, are unnecessary String parsing and conversions. So I went ahead and created my own. I'd very much appreciate any feedback you might have.

    Feel free to include this in your own project and customize/rip it apart in any way you want. You do not have to credit me as this does not really achieve anything groundbreaking, however, I would appreciate it if you did.

    Leave any constructive criticism below :)
     
    #1 Benz56, Jul 9, 2018
    Last edited: Feb 5, 2019
    • Useful Useful x 16
    • Like Like x 4
    • Agree Agree x 1
  2. FrostedSnowman

    Resource Staff

    declare the constants static
     
  3. Benz56

    Moderator Supporter

    The main reason as to why I've extracted the "constants" from the code is to highlight what is mainly intended to be customized should someone decide to implement this class. I'll change it in a minute.

    Thank you for your feedback :)
     
  4. FrostedSnowman

    Resource Staff

    np. nice job
     
  5. JanTuck

    Supporter

    I like this, thanks. Looks clean and usable.
     
  6. Benz56

    Moderator Supporter

    I've changed the constants to private static final to follow proper conventions; thanks!

    Thank you for the feedback @JanTuck ;)
     
    #6 Benz56, Jul 9, 2018
    Last edited: Jul 9, 2018
  7. Nice resource! Im gonna put this into my "Usefull links" bookmark folder.
     
  8. Benz56

    Moderator Supporter

    Awesome! :)

    Feel free to use it in your resources as you see fit!
     
  9. Finally, a simple resource for this. I will definitely use this. Thanks!
     
    • Like Like x 1
  10. Yay for lambda :D

    Looks neat, gj.
     
    • Agree Agree x 1
    • Creative Creative x 1
  11. Benz56

    Moderator Supporter

    Thank you for the feedback! :)

    Update:
    Slight improvement to the simplicity/clarity of the permission checking.

    The following has been changed:
    Code (Java):
    //Constants:
    private static final Permission UPDATE_PERM = new Permission("yourplugin.update", PermissionDefault.FALSE);
    private static final boolean PERM_ONLY = true; //Whether or not to ignore notifying OPs without the above permission.

    //In the PlayerJoinEvent:
    if (player.hasPermission(UPDATE_PERM) || (player.isOp() && !PERM_ONLY)) {
        player.sendMessage(ChatColor.translateAlternateColorCodes('&', UPDATE_MSG));
    }
    To:
    Code (Java):
    //Constant:
    //PermissionDefault.FALSE == OPs need the permission to be notified.
    //PermissionDefault.TRUE == all OPs are notified regardless of having the permission.
    private static final Permission UPDATE_PERM = new Permission("yourplugin.update", PermissionDefault.FALSE);

    //In the PlayerJoinEvent:
    if (!player.hasPermission(UPDATE_PERM)) return;
    player.sendMessage(ChatColor.translateAlternateColorCodes('&', UPDATE_MSG));
     
    Essentially the PERM_ONLY boolean is now based on the PermissionDefault to avoid the verbosity of having an extra variable which technically already is toggleable elsewhere.

    The changes have been applied to the code in the main post and the Pastebin has been replaced with a GitHub link.
     
    #11 Benz56, Jul 10, 2018
    Last edited: Jul 10, 2018
  12. Bookmarked ;)

    Looks awesome. Nice job!
     
  13. I had a similar code snippet in my plugin a while back. I'm curious if you feel up to adding a repeating task that runs every 2 hours or, x interval essentially. That would provide instant updates. As well, you could iterate through players and push update notifications when an update is available as opposed to only on player join. But nice work.
     
  14. Benz56

    Moderator Supporter

    Thanks!

    I'll leave that up to you. This essentially just showcases how to get the version from Spigot as well as how it could be used in a very simple way.

    Feel free to use, modify, and rip it apart in any way you want to achieve what you mentioned in your post ;)
     
  15. I'd suggest checking that the local version is less than the version on Spigot, as I noticed it doesn't account for that and when I'm working on a new version it says I have an update available. Other than that, it works really well.
     
  16. Benz56

    Moderator Supporter

    That will only really bother yourself. What you're suggesting is exactly what I'm trying to avoid doing; parsing the version to something with a numeric value. People are naming their versions in many different ways some even include Strings such as "DEV", "BUILD" etc.

    Let me just quote myself:
    I usually first increment the plugin.yml version number right before I update; simple solution.

    If you do want this feature and have a Major.Minor.Patch versioning structure simply remove the dots and parse to an Integer.
     
    • Like Like x 1
  17. Amazing! Use it for everything I have but FastAsyncWorldEdit!
     
  18. Hey, this is pretty neat! I switched this up a bit to make it an independent plugin, all you have to do is go through each plugin and add spigotID : [the resource page id] to its config.yml.
    UpdateChecker class:
    Code (Java):

    public class UpdateChecker {

        private static final Pattern tablePattern = Pattern.compile(".*?<table class=\"dataTable resourceHistory\">(.*?)</table>.*?", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        private static final Pattern tableEntrySlotPattern = Pattern.compile("<tr class=\"dataRow\\s+\">(.*?)</tr>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        private static final Pattern versionPattern = Pattern.compile(".*?<td class=\"version\">(.*?)</td>.*?", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        private static final Pattern releaseDatePattern = Pattern.compile(".*?<td class=\"releaseDate\">.*?title=\"(.*?)\".*?</td>.*?", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        private static final Pattern downloadCountPattern = Pattern.compile(".*?<td class=\"downloads\">(.*?)</td>.*?", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        private static final Pattern ratingPattern = Pattern.compile(".*?<td class=\"rating\">(.*?)</td>.*?", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        private static final Pattern ratingAveragePattern = Pattern.compile(".*?<span class=\"Number\"\\s+itemprop=\"average\">(.*?)</span>.*?", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        private static final Pattern rateCountPattern = Pattern.compile(".*?<span class=\"Hint\">(.*?)\\s*ratings?\\s*</span>.*?", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        private static final Pattern downloadLinkPattern = Pattern.compile(".*?<td class=\"dataOptions download\">.*?a\\s*href=\"?(.*?)\".*?", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);

        private final Plugin plugin;
        private final String localPluginVersion;
        private final String spigotID;
        private List<Update> updates;

        public UpdateChecker(final Plugin plugin) {
            this.plugin = plugin;
            this.localPluginVersion = plugin.getDescription().getVersion();
            this.spigotID = plugin.getConfig().getString("spigotID", null);

        }

        public List<Update> getUpdates() {
            return updates;
        }

        public boolean supportsUpdateChecker() {
            return spigotID != null;
        }

        public void checkForUpdate() {
            final UpdateChecker checker = this;
            Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
                try {
                    HttpURLConnection connection = (HttpURLConnection) new URL("http://www.spigotmc.org/resources/" + spigotID + "/history").openConnection();
                    connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB;     rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13 (.NET CLR 3.5.30729)");
                    connection.setInstanceFollowRedirects(false);
                    switch (connection.getResponseCode()) {
                        case 301:
                        case 302:
                        case 307:
                        case 308:
                            connection = (HttpURLConnection) new URL(connection.getHeaderField("Location")).openConnection();
                            connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB;     rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13 (.NET CLR 3.5.30729)");
                            break;
                    }
                    Scanner scanner = new Scanner(connection.getInputStream());
                    scanner.useDelimiter("\\Z");
                    String content = scanner.next();

                    Matcher tableMatcher = tablePattern.matcher(content);
                    if (!tableMatcher.matches()) return;
                    String tableContent = tableMatcher.group(1);
                    Matcher entryMatcher = tableEntrySlotPattern.matcher(tableContent);

                    List<Update> updates = new ArrayList<>();
                    while (entryMatcher.find()) {
                        String entryContent = entryMatcher.group(1);
                        String version = "N/A";
                        String downloadURL = "N/A";
                        String rating = "N/A";
                        String ratingCount = "N/A";
                        String downloadCount = "N/A";
                        String releaseDate = "N/A";

                        Matcher versionMatcher = versionPattern.matcher(entryContent);
                        if (versionMatcher.matches()) {
                            version = versionMatcher.group(1);
                        }
                        Matcher downloadURLMatcher = downloadLinkPattern.matcher(entryContent);
                        if (downloadURLMatcher.matches()) {
                            downloadURL = downloadURLMatcher.group(1);
                        }
                        Matcher ratingMatcher = ratingPattern.matcher(entryContent);
                        if (ratingMatcher.matches()) {
                            String ratingContent = ratingMatcher.group(1);
                            Matcher averageRatingMatcher = ratingAveragePattern.matcher(ratingContent);
                            if (averageRatingMatcher.matches()) {
                                rating = averageRatingMatcher.group(1);
                            }
                            Matcher ratingCountMatcher = rateCountPattern.matcher(ratingContent);
                            if (ratingCountMatcher.matches()) {
                                ratingCount = ratingCountMatcher.group(1);
                            }
                        }
                        Matcher downloadCountMatcher = downloadCountPattern.matcher(entryContent);
                        if (downloadCountMatcher.matches()) {
                            downloadCount = downloadCountMatcher.group(1);
                        }
                        Matcher releaseDateMatcher = releaseDatePattern.matcher(entryContent);
                        if (releaseDateMatcher.matches()) {
                            releaseDate = releaseDateMatcher.group(1);
                        }
                        updates.add(new Update(version, downloadURL, rating, ratingCount, downloadCount, releaseDate));
                    }
                    checker.updates = updates;
                    if (updates.get(0).getVersion().equalsIgnoreCase(checker.localPluginVersion)) {
                        ComponentBuilder updateMessage = new ComponentBuilder("Plugin ").color(ChatColor.RED)
                                .append(checker.plugin.getName()).color(ChatColor.WHITE)
                                .append(" has a new version available! Would you like to see its version history?").color(ChatColor.RED);

                        TextComponent yes = new TextComponent("Yes");
                        yes.setColor(ChatColor.GREEN);
                        yes.setBold(true);
                        yes.setUnderlined(true);
                        yes.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("Opens an interactive inventory")
                                .color(ChatColor.GRAY).create()));
                        yes.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/updates view " + checker.plugin.getName()));

                        TextComponent showDownload = new TextComponent("Just download it");
                        showDownload.setColor(ChatColor.YELLOW);
                        showDownload.setBold(true);
                        showDownload.setUnderlined(true);
                        showDownload.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
                                new ComponentBuilder("Downloads the most recent version of the plugin. Restart the server for it to take effect.")
                                        .color(ChatColor.GRAY).create()));
                        showDownload.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/updates download " + checker.plugin.getName()));

                        ComponentBuilder options = new ComponentBuilder(yes).reset().append("  ").append(showDownload);

                        for (Player player : Bukkit.getOnlinePlayers()) {
                            if (player.hasPermission(checker.plugin.getName() + ".updateMessage")) {
                                player.spigot().sendMessage(ChatMessageType.CHAT, updateMessage.create());
                                player.spigot().sendMessage(ChatMessageType.CHAT, options.create());
                            }
                        }
                    }
                    Bukkit.getLogger().info("Added version support for " + plugin.getName());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }

        public void display(Player player) {
            int inventorySize = this.updates.size() * 9;
            Inventory inventory = Bukkit.createInventory(null, inventorySize, org.bukkit.ChatColor.LIGHT_PURPLE + this.plugin.getName() + " Updates");
            for (int i = 0; i < this.updates.size(); i++) {
                Update update = this.updates.get(i);

                ItemStack version = new ItemStack(Material.ARROW);
                ItemMeta versionMeta = version.getItemMeta();
                versionMeta.setDisplayName(org.bukkit.ChatColor.YELLOW + update.getVersion());
                version.setItemMeta(versionMeta);
                inventory.setItem(9 * i, version);

                ItemStack releaseDate = new ItemStack(Material.WATCH);
                ItemMeta releaseDateMeta = releaseDate.getItemMeta();
                releaseDateMeta.setDisplayName(org.bukkit.ChatColor.YELLOW + update.getReleaseDate());
                releaseDate.setItemMeta(releaseDateMeta);
                inventory.setItem(9 * i + 1, releaseDate);

                ItemStack rating = new ItemStack(Material.NETHER_STAR);
                int amount = Math.round(Float.parseFloat(update.getRating()));
                rating.setAmount(amount > 0 ? amount : 1);
                ItemMeta ratingMeta = rating.getItemMeta();
                ratingMeta.setDisplayName(org.bukkit.ChatColor.YELLOW + update.getRating());
                String lore = org.bukkit.ChatColor.GREEN + update.getRatingCount() + " rating";
                if (Integer.parseInt(update.getRatingCount()) != 1) lore = lore + "s";
                ratingMeta.setLore(Collections.singletonList(lore));
                rating.setItemMeta(ratingMeta);
                inventory.setItem(9 * i + 2, rating);

                ItemStack download = new ItemStack(Material.BOAT);
                ItemMeta downloadMeta = download.getItemMeta();
                downloadMeta.setDisplayName(org.bukkit.ChatColor.YELLOW + "Download");
                downloadMeta.setLore(Arrays.asList(ChatColor.GREEN + update.getDownloadCount() + " downloads",
                        org.bukkit.ChatColor.GREEN + "Click to download. Restart server to load.",
                        org.bukkit.ChatColor.GREEN + "" + org.bukkit.ChatColor.ITALIC + update.getDownloadURL()));
                download.setItemMeta(downloadMeta);
                inventory.setItem(9 * i + 3, download);
            }
            player.openInventory(inventory);
        }
    }
     
    Update class:
    Code (Java):

    public class Update {

        private String version;
        private String downloadURL;
        private String rating;
        private String ratingCount;
        private String downloadCount;
        private String releaseDate;

        public Update(String version, String downloadURL, String rating, String ratingCount, String downloadCount, String releaseDate) {
            this.version = version;
            this.downloadURL = downloadURL;
            this.rating = rating;
            this.ratingCount = ratingCount;
            this.downloadCount = downloadCount;
            this.releaseDate = releaseDate;
        }

        public String getDownloadCount() {
            return downloadCount;
        }

        public String getDownloadURL() {
            return "http://spigotmc.org/" + downloadURL;
        }

        public String getRating() {
            return rating;
        }

        public String getRatingCount() {
            return ratingCount;
        }

        public String getReleaseDate() {
            return releaseDate;
        }

        public String getVersion() {
            return version;
        }

        public void setDownloadCount(String downloadCount) {
            this.downloadCount = downloadCount;
        }

        public void setDownloadURL(String downloadURL) {
            this.downloadURL = downloadURL;
        }

        public void setRating(String rating) {
            this.rating = rating;
        }

        public void setRatingCount(String ratingCount) {
            this.ratingCount = ratingCount;
        }

        public void setReleaseDate(String releaseDate) {
            this.releaseDate = releaseDate;
        }

        public void setVersion(String version) {
            this.version = version;
        }
    }
     
    and finally main class:
    Code (Java):

    public class TestPlugin extends JavaPlugin implements Listener {

        public static final Map<String, UpdateChecker> checkerMap = new HashMap<>();

        private static JavaPlugin plugin;

        public TestPlugin() {
            super();
            plugin = this;
        }

        @Override
        public void onEnable() {
            getServer().getPluginManager().registerEvents(this, this);
            saveDefaultConfig();
            // I use a runnable in order to force it to only fire once all plugins have loaded
            getServer().getScheduler().runTaskLater(this, () -> {
                for (Plugin plugin : getServer().getPluginManager().getPlugins()) {
                    UpdateChecker checker = new UpdateChecker(plugin);
                    if (!checker.supportsUpdateChecker()) continue;
                    checker.checkForUpdate();
                    checkerMap.put(plugin.getName(), checker);
                }
            }, 1);
        }

        @Override
        public void onDisable() {
        }

        @Override
        public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
            if (label.equalsIgnoreCase("updates")) {
                if (args.length == 0) {
                    sender.sendMessage("Available plugins:");
                    sender.sendMessage(checkerMap.keySet().toArray(new String[checkerMap.keySet().size()]));
                    return true;
                } else if (args.length >= 2) {
                    switch (args[0]) {
                        case "view":
                            UpdateChecker checker = checkerMap.get(args[1]);
                            checker.display((Player) sender);
                            break;
                        case "download":
                            UpdateChecker checker1 = checkerMap.get(args[1]);
                            downloadPlugin(checker1.getUpdates().get(0).getDownloadURL());
                            break;
                    }
                    return true;
                }
            }
            return false;
        }

        public static JavaPlugin getPlugin() {
            return plugin;
        }

        @EventHandler
        public void onClick(InventoryClickEvent event) {
            if (event.getInventory().getTitle().toLowerCase().contains("updates")) {
                event.setCancelled(true);
                if (event.getCurrentItem() != null && event.getCurrentItem().getType() == Material.BOAT) {
                    String url = ChatColor.stripColor(event.getCurrentItem().getItemMeta().getLore().get(2));
                    downloadPlugin(url);
                }
            }
        }

        @EventHandler
        public void onDrag(InventoryDragEvent event) {
            if (event.getInventory().getTitle().toLowerCase().contains("updates")) {
                event.setCancelled(true);
            }
        }

        public static void downloadPlugin(String url) {
            Bukkit.getScheduler().runTaskAsynchronously(TestPlugin.getPlugin(), () -> {
                try {
                    URL site = new URL(url);
                    HttpURLConnection httpConn = (HttpURLConnection) site.openConnection();
                    httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB;     rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13 (.NET CLR 3.5.30729)");
                    int responseCode = httpConn.getResponseCode();

                    // always check HTTP response code first
                    httpConn.setInstanceFollowRedirects(false);
                    download(httpConn, url);
                    Bukkit.getLogger().info(String.valueOf(responseCode));

                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }

        public static void download(HttpURLConnection httpConn, String url) {
            String fileName = "";
            try {
                switch (httpConn.getResponseCode()) {
                    case 301:
                    case 302:
                    case 307:
                    case 308:
                        httpConn = (HttpURLConnection) new URL(httpConn.getHeaderField("Location")).openConnection();
                        httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB;     rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13 (.NET CLR 3.5.30729)");
                        download(httpConn, url);
                        return;
                }
                String disposition = httpConn.getHeaderField("Content-Disposition");
                String contentType = httpConn.getContentType();
                int contentLength = httpConn.getContentLength();

                if (disposition != null) {
                    // extracts file name from header field
                    int index = disposition.indexOf("filename=");
                    if (index > 0) {
                        fileName = disposition.substring(index + 10,
                                disposition.length() - 1);
                    }
                } else {
                    // extracts file name from URL
                    fileName = url.substring(url.lastIndexOf("/") + 1,
                            url.length());
                }
                InputStream inputStream = null;
                inputStream = httpConn.getInputStream();
                String saveFilePath = plugin.getDataFolder().getParentFile().getAbsolutePath() + "/" + fileName;

                // opens an output stream to save into file
                FileOutputStream outputStream = new FileOutputStream(saveFilePath);

                int bytesRead = -1;
                byte[] buffer = new byte[15000];
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }

                outputStream.close();
                inputStream.close();

                httpConn.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
            Bukkit.getLogger().info("Finished download of " + fileName);

        }
    }
     
    Obviously, this is imperfect code, its only a rough draft since I'm not really confident with this stuff yet.
    There are a number of issues I can say this code will already have, such as inventories with too many slots (you'll need your own page system), and the auto downloader won't work for any service with DDoS protection (which includes spigot's own website, so resources will only auto download if they use an external download link such as Dropbox)
    I would not recommend using this as is, simply leaving this here in case something gives you an idea for something to include.
    It also probably breaks spigot's terms of service in some way if I had to guess.
    But I hope you're able to use a snippet in here somewhere to improve this resource!
    (and if not, feel free to ignore this post. I'm not attempting to take credit or out-do the project, I just thought it'd be cool if it could show stats for the resource as well).
     
    #18 GreatThane, Jul 11, 2018
    Last edited: Jul 11, 2018
  19. Mas

    Mas

    Just a thought for anyone using this, you'll probably want to end up adding a check in the code stops disables the notification if you're using a build that you have tagged as "beta" or "dev" or whatever, just so the notifications don't annoy you to death :p
     
  20. Benz56

    Moderator Supporter

    Looks very cool at first glance!
    I really appreciate you taking the time to push the limits of something that started as simplistic as the class I'd posted.
    I won't be changing anything in the main post as the purpose of sharing it is to offer people a simple copy/paste class for update checking. Mainly targeting those who don't want to sit down and work it out themselves and hopefully allow them to learn a few things.

    Surely what you've come up with is very cool, thanks! ;)

    Once again I'd recommend changing the version in the plugin.yml right before you update.
    I can see the issue if you are pushing builds to GitHub and not directly to Spigot, however, that can be solved with a simple boolean ;)
     
    • Friendly Friendly x 1