Updater 2.2 - Easy, Safe, and Policy-Compliant auto-updating for your plugins [NEW!]

Discussion in 'Resources' started by Gravity, Aug 28, 2012.

     
  1. Offline

    Gravity BukkitDev Team Lead Moderator BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Updater - Version 2.2


    NEW: As of 10/11/13, Updater 2.0 has been released! All documentation has been updated - see this post for information about the new version.


    Updater is an easy-to-use but robust and fully policy-compliant plugin updating system. It provides plugin developers with the ability to both check for and download new updates straight from BukkitDev, and requires no web server setup to function.

    Download and Source:
    Updater is a single class file that you need to add to your plugin. Simply create a new class somewhere in your plugin named "Updater", and populate it with Updater's source code, which you can find by clicking the "Get Updater" link below. Then, go to the "How to use it" section to learn how adding one line of code to your plugin will implement Updater.


    Features:

    • No more hassle! Never worry about configuring your Dropbox text files to the latest build's url, or forgetting to update external files again. Upload once to BukkitDev, and as soon as your file is approved clients will start downloading it, even if the approval comes at 4am and you're fast asleep.
    • Setup is as easy as copying a class and giving it your BukkitDev project slug. Updater will do the rest.
    • Ability to tag certain builds as non-update builds. For instance, on my Jenkins system every build is tagged with -DEV, so that people who are using it do not get switched to the official latest build, and can stay testing the pre-release. Simply edit the "noUpdateTag" array in the class to define what kind of builds should be left alone.
    • Don't hassle your users anymore. Server admins have enough on their hands, don't concern them with updates, because they just will /not/ update. From personal experience, I know that the only time I cared about a plugin update was when something broke. It's far too difficult to worry about a new file every day, but if you let Updater automatically install updates, your users will rejoice!
    • Be safe. EVERY file that Updater downloads has been approved by BukkitDev staff. Real humans go line-by-line through the code of each plugin that is approved on dev.bukkit.org, to verify it is free of any malicious code. Your user's shouldn't have to blindly accept your trust, you can instead show and prove to them that by using updater, you are keeping them secure.
    • Works with both .jar file and .zip file updates.


    How it works:

    - First, Updater connects to BukkitDev API and requests information about your project.

    - It then searches the information for the latest file, and obtains information about it like its name and version number.

    - Optionally, Updater will run a version check, comparing the newest file with the plugin's current version. NOTE: For this to work, your file titles must be named in this format: 'PluginName vVersionNumber', such as 'AntiCheat v1.0' (or simply 'v1.0', the name is not needed, but suggested). Here's a screenshot of how this should look, if done properly:

    [IMG]

    - Assuming that an update is needed, Updater will download the file from dev.bukkit.org and store it in the update folder. This is a folder defined in bukkit.yml in which any file that is contained in it will be switched with it's currently-in-use non-updated counterpart whenever the system is reloaded or restarted. This means that the user does not need to worry about replacing the downloaded file with the current file, or anything like that. It's all done in the background.

    How to use it:

    If you are using Maven to manage your project you can use my Maven repository to get the dependency. To do this, edit your pom.xml to add the following repository:
    Code:
        <repositories>
            <repository>
                <id>gravity-repo</id>
                <url>http://repo.gravitydevelopment.net</url>
            </repository>
        </repositories>
    
    Then, add the following dependency:
    Code:
        <dependencies>
            <dependency>
                <groupId>net.gravitydevelopment.updater</groupId>
                <artifactId>updater</artifactId>
                <version>2.1</version>
            </dependency>
        </dependencies>
    
    Otherwise, you can use the traditional way and download the source code for Updater. Simply place this somewhere within your plugin's packages, and then switch over to your main class to get to work.

    As with most of my projects, I boast the fact that you only need one line of code added to your main class (the one that extends JavaPlugin) to make this work (along with my Updater class, of course), so here's what it is:

    Code:
    Updater updater = new Updater(this, id, this.getFile(), Updater.UpdateType.DEFAULT, false);
    That's it! This single line of code will literally keep the user updated for the rest of their life. Here's a breakdown of what all these values are:

    1) "this" - The plugin instance. I suggest using this in your onEnable() method, so that you can properly issue the 'this' keyword. Other methods that are called before onEnable() will not work (but anything after it, or that is called BY onEnable() does work).

    2) "id" - This is how Updater finds your project on BukkitDev. If you don't know what this is, follow the instructions on this wiki article.

    3) "this.getFile()" - The plugin's file, this is so that Updater can properly replace your plugin with the update when it is downloaded. Note that this is a protected value, and so it can only be accessed within your plugin's main class

    4) "Updater.UpdateType.DEFAULT" - This allows you to choose which type of update you would like to take place. Currently there are 3 options:
    - DEFAULT - Typically what you would want. Do an update check, and then if it's out of date download and install the latest update.
    - NO_VERSION_CHECK - In case you know you need (or want) to update, skip version checking and just download the latest file, regardless of any it's details.
    - NO_DOWNLOAD - In case you just want to do a version check. No files will be downloaded, but you still get information about the newest build on DBO, like it's version number and size.

    5) "false" - This is a value declaring whether you want Updater to announce the progress of the download, as it takes place. This is similar to what this output (to the console) will look like:

    2012-08-29 16:30:56 [INFO] [AntiCheat] Enabling AntiCheat v1.3.6-DEV
    2012-08-29 16:30:57 [INFO] About to download a new update: AntiCheat v1.3.5
    2012-08-29 16:30:57 [INFO] Downloading update: 10% of 93738 bytes.
    2012-08-29 16:30:57 [INFO] Downloading update: 20% of 93738 bytes.
    2012-08-29 16:30:57 [INFO] Downloading update: 30% of 93738 bytes.
    2012-08-29 16:30:57 [INFO] Downloading update: 50% of 93738 bytes.
    2012-08-29 16:30:57 [INFO] Downloading update: 70% of 93738 bytes.
    2012-08-29 16:30:57 [INFO] Downloading update: 80% of 93738 bytes.
    2012-08-29 16:30:57 [INFO] Downloading update: 90% of 93738 bytes.
    2012-08-29 16:30:57 [INFO] Downloading update: 100% of 93738 bytes.
    2012-08-29 16:30:57 [INFO] Finished updating.

    If this option is true, and there is no update, there will be no output to the console.

    You can also see these values documented in JavaDocs here: http://gravitydevelopment.net/docs/updater/

    ------------------------------------------------------------------------------------------------------------------------------------
    NOTICE: As of Updater 2.0, a configuration file is created to allow server administrators to globally toggle updating for any plugin using this class. While this option does provide a convenient method for server admins to disable all Updater instances, Bukkit project submission guidelines still require that you make your plugin's Updater instance specifically toggleable with its own configuration option. This gives server administrators the opportunity to only disable the updating capabilities of one plugin in particular, should they choose to do so. You may read more about compliance with this policy here.
    ------------------------------------------------------------------------------------------------------------------------------------


    Expanding updater:

    Note: The following contains more advanced user information on controlling Updater. While Updater is very simple to use, it also gives a great deal of feedback and control to the developer if they want to use it. If you are just starting to develop plugins, it is recommended that you stop here and just use Updater as you have learned to use it so far. If you are an advanced user, you may continue on, but know that all of the following info is optional, and only necessary if you want to customize your experience.

    Now, of course you may want to know what the outcome of the process was, so you can inform the user or update some values in your plugin to reflect that it is now updated. This result can easily be obtained by using the "getResult()" call. This returns an UpdateResult that reflects what happened.

    Code:
            Updater.UpdateResult result = updater.getResult();
            switch(result)
            {
                case SUCCESS:
                    // Success: The updater found an update, and has readied it to be loaded the next time the server restarts/reloads
                    break;
                case NO_UPDATE:
                    // No Update: The updater did not find an update, and nothing was downloaded.
                    break;
                case DISABLED:
                    // Won't Update: The updater was disabled in its configuration file.
                    break;
                case FAIL_DOWNLOAD:
                    // Download Failed: The updater found an update, but was unable to download it.
                    break;
                case FAIL_DBO:
                    // dev.bukkit.org Failed: For some reason, the updater was unable to contact DBO to download the file.
                    break;
                case FAIL_NOVERSION:
                    // No version found: When running the version check, the file on DBO did not contain the a version in the format 'vVersion' such as 'v1.0'.
                    break;
                case FAIL_BADID:
                    // Bad id: The id provided by the plugin running the updater was invalid and doesn't exist on DBO.
                    break;
                case FAIL_APIKEY:
                    // Bad API key: The user provided an invalid API key for the updater to use.
                    break;
                case UPDATE_AVAILABLE:
                  // There was an update found, but because you had the UpdateType set to NO_DOWNLOAD, it was not downloaded.
            }
    All these values, of course, are documented in easy-to-read HTML here: http://gravitydevelopment.net/docs/updater/

    You also may want to know information about the newest update. Some people prefer to have Updater run a version check ONLY (using UpdateType.NO_DOWNLOAD), then, if there is an update available, start notifying admins as they log in that there is a new version ready, with information like file size and version. An admin would then issue a command, and the developer would run Updater again but this time with UpdateType set to NO_VERSION_CHECK, thus downloading the newest build at the admin's request.

    We have a few methods available for you to use for this information. We already know that we can determine the outcome of the version check by calling getResult(), but here are some more methods you can call to get information about the newest file:

    - getLatestName() - Returns the name of the latest file you have uploaded to BukkitDev (Ex: "AntiCheat v1.5.9")
    - getLatestType() - Returns the type of the latest file you have uploaded to BukkitDev (Alpha, Beta, Release)
    - getLatestGameVersion() - Returns the compatible Game Version of the latest file you have uploaded to BukkitDev (Ex: "CB 1.6.2-R1.0")
    - getLatestFileLink() - Returns the link to the latest file you have uploaded.

    The scenario mentioned about would look something like this (pseudocode):

    Code:
    // In main class
     
    public static boolean update = false;
    public static String name = "";
    public static ReleaseType type = null;
    public static String version = "";
    public static String link = "";
    // You would want to make getter methods in your class, this is just for simplicity.
     
    public void onEnable()
    {
      Updater updater = new Updater(this, YOUR_ID_HERE, this.getFile(), Updater.UpdateType.NO_DOWNLOAD, false); // Start Updater but just do a version check
      update = updater.getResult() == Updater.UpdateResult.UPDATE_AVAILABLE; // Determine if there is an update ready for us
      name = updater.getLatestName(); // Get the latest name
      version = updater.getLatestGameVersion(); // Get the latest game version
      type = updater.getLatestType(); // Get the latest file's type
      link = updater.getLatestFileLink(); // Get the latest link
    }
     
    // In a listener class:
     
    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event)
    {
      Player player = event.getPlayer();
      if(player.hasPermission("foo.bar") && Main.update)
      {
        player.sendMessage("An update is available: " + Main.name + ", a " + Main.type + " for " + Main.version + " available at " + Main.link);
        // Will look like - An update is available: AntiCheat v1.5.9, a release for CB 1.6.2-R0.1 available at http://media.curseforge.com/XYZ
        player.sendMessage("Type /update if you would like to automatically update.");
      }
    }
     
    // And then later in a CommandExecutor class, when they type /update:
     
    Updater updater = new Updater(this, YOUR_ID_HERE, this.getFile(), Updater.UpdateType.NO_VERSION_CHECK, true); // Go straight to downloading, and announce progress to console.
    


    This post has been edited 31 times. It was last edited by Gravity Jul 18, 2014 at 7:30 PM.
  2.  
  3. Offline

    Hidendra

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Might help to expose the result enum and not make it private -- magic numbers are evil :p
  4. Offline

    Gravity BukkitDev Team Lead Moderator BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Silly me! I had it private! Changing that now, thanks.
  5. Online

    iKeirNez

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    This looks awesome, I'll try this instead of V10lators version and see how it goes.

    Do you think it would be possible to unzip the jar from a zip file because we upload CommandsEX in zip format for the config and slap libraries.
  6. Offline

    Gravity BukkitDev Team Lead Moderator BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    I hadn't even thought about ZIP files, I'll include support for that too.
  7. Online

    iKeirNez

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Woo that would be awesome, maybe if you made an HashMap/ArrayList of directories to copy from the zip and where to copy them to.

    E.g.

    hm.put("CommandsEX/CommandsEX.jar", "/");

    That would tell it to put CommandsEX.jar into / (plugins folder) I don't know if there would be a better way to do this.
  8. Offline

    Gravity BukkitDev Team Lead Moderator BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Bukkit already does that with jars itself, that's what the update folder is for. All I have to do is unzip the contents into the update folder, and then move any directories into the plugins folder myself, as Bukkit wont do it for you.
  9. Online

    iKeirNez

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Ok that's awesome, if you added that then I would definitely be moving from v10lators version.

    I wrote some code for v10lators updated that could tell the difference between Alpha, Beta and Release and Minor Versions like 1.80a or 1.80c etc. It's pretty simple code and I know it couldve been simpler but I could try adding that to your updated.
  10. Offline

    Gravity BukkitDev Team Lead Moderator BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    When I have a second, I'm going to do something like that, something better than what it's currently doing which is just checking if the versions are equal.
  11. Offline

    Gravity BukkitDev Team Lead Moderator BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Alright, that took longer than I thought it would but I think I have some fairly nice zip support now.
    Basically, when a zip file is downloaded it will extract everything, check through the extracted files to find the jar for the plugin, which it will place in the update folder so that Bukkit can update it as usual.

    Then, it checks to see if you have a folder for the plugin (Where you store configs and all that), and if you do it will copy over any files that do not already exist. This way, you can provide an update that, say, adds a new dependency jar into the folder (or any new file that was not in previous versions, not just a jar, although it's still recommended that if it's just a text file you generate it from within the plugin) , and people will get that new file, but not have their configs and other files overwritten.

    @iKeirNez , I tested the update specifically with CommandsEx to make sure a live example of a zipped plugin update would work, and it seems to be working perfectly.

    This post has been edited 2 times. It was last edited by Gravity May 9, 2013.
  12. Offline

    confuserr

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    This looks great. Is it possible though to simply do the version check in onPlayerJoin and send an admin a message saying a new version is available rather than auto updating? As a server admin myself, I like to see the changes between versions before updating.
  13. Offline

    Lolmewn BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Thank you very much! Will try this when I get home.
  14. Offline

    Theodossis

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Perfect! I will try it when i go on my pc :D
  15. Offline

    Lolmewn BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Oh, I upload .zips to BukkitDev. In there, I made some folders, plugins/ and web/, does Updater support file extraction from .zips or do I have to do it myself? :p
  16. Online

    iKeirNez

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    That sounds epic, one thing though. We use the smack and smackx libaries for XMPP, what if we updated smack in the zip file, will it overwrite the servers smack and smackx jars?
  17. Offline

    V10lator

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    @h31ix Isn't the parsing of dev.bukkit.org against the ToS of Curse? Also you shouldn't do network I/O in minecrafts main thread:
    (Source: http://wiki.bukkit.org/Scheduler_Programming#Asynchronous_Tasks )
    It has a reason my updater has a complicated sync/async logic. Maybe you want to help developing AutoUpdate instead of recreating the wheel? :)
    What's wrong with mine? :(
  18. Offline

    Gravity BukkitDev Team Lead Moderator BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    There's not really an easy way to do that. The reason being, what if you included a config.yml in your zip file too, then everyone's stuff would get overwritten every time it updates. I'm going to have to try and think of a way to do that where it knows what to replace and what not to, maybe some way to tag the actual files or something.
  19. Offline

    Gravity BukkitDev Team Lead Moderator BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Maybe I can add an option that runs the version check but doesn't download anything, and add a new UpdateResult.UPDATE_AVALIABLE, so that you can do all the notifying you want. Then, you can just run Updater again and use that version check toggle to turn off version checking the next time, since you know there is an update.

    Does that sound good?

    You're welcome and thank you!

    It does indeed, scroll up and see my comment on that.
  20. Offline

    Gravity BukkitDev Team Lead Moderator BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Just added stuff like the UpdateType enum, allowing you to customize how Updater runs, a new constructor variable that allows you to announce download progress to the console, and ability to obtain latest file information, like it's name and size. OP and javadocs have been updated to reflect this.
  21. Online

    iKeirNez

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    I think your one is great but the reason I want to switch to this one is because it can read zip files.
  22. Offline

    Gravity BukkitDev Team Lead Moderator BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    No. The Curse ToS restricts you from creating a "bot" that harvests information way faster and more frequently than any human could do, thus creating much, much more traffic for them than is necessary.

    I am considering threading it, but at this point I'm not convinced it's a big deal. The main reason I have for wanting to thread it is so that people can get information about how the download is coming along as it is processed.

    I don't really think I'm reinventing the wheel at this point, Updater works in a different way than yours does (which I had no idea existed), mostly because I don't think it's a fantastic idea to rely on Bukget (not that I don't think it's a good service; I use it all the time, but just because it may not always be there for us), and we can easily see that having more than one solution to a problem generates innovation. Plus, being a BukkitDev staff member, I feel that I know the ins and outs of our guidelines more than anyone else, and I read over thousands of lines of real user's code per day, and so I get a feel for what people need, what they will use, and how they will use it, which directly influences my code and helps me improve things.

    There's no reason to feel like I'm saying your solution isn't good enough, but I do prefer to do things my own way, thus allowing me to find new ways to simplify and improve my solutions. As this project becomes bigger and simpler, so too will yours, and that's better for everyone.
  23. Offline

    confuserr

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Sounds great! :)
  24. Offline

    V10lator

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    But aren't you not only creating a bot but a botnet? Think about it that way:
    10 plugins use this. Each of this plugins is used by 10 people. So you create 10x10 (so 100x) more traffic than a human. And I think your goal is to get this into more than 10 plugins which aren't used by many servers.
    You know that I have to cache bukget results cause they fear the traffic auto updaters create? And bukget is designed as an API, the RSS feed is not. But anyway, right now they have another problem with my solution so maybe it will die soon (3 plugins using it where rejected cause of it, clearing the situation may take some time...). :(
    Well, if dev.bukkit.org is down for some reason your method will freeze the game for 30 seconds (I/O timeout), so you should really implement it asap.
    I don't feel like that, don't worry. And I hope you don't feel like I'm saying that about yours, that also not true (except the async part above ;)).
    Hopefully you're right. :)
  25. Online

    ferrybig

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    is it a problem if the manes are like

    v 1.1
    v 1.0

    because that was the first format, but people placed the plugin names also in it

    EDIT, can you set an option that it wont notice you of files marked whit beta and alpha, only release

    EDIT 2: can I help you making it download on an ascry thread, and fire an event on the main thread when the results are there?

    This post has been edited 2 times. It was last edited by ferrybig Sep 7, 2012.
  26. Online

    iKeirNez

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Would it be possible to make it so that if the update type is NO_DOWNLOAD it would alert the console and players that an update is available, the reason for this is that most users use the Builder for CommandsEX to make a customized copy.
  27. Offline

    Gravity BukkitDev Team Lead Moderator BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    You can do that yourself, it's all up to the plugin author to get the result and do what they please with it.
  28. Online

    iKeirNez

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Okay, thanks!
  29. Offline

    Unscrewed

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Edit: Putting it in onEnable() fixed it. ;-)

    I got a NullPointerException? I just copy-pasted the Updater class into my own, changed the package path to my own and edited the 1 line to this:
    Code:java
    1. Updater updater = new Updater(this, "iBlock", this.getFile(), Updater.UpdateType.DEFAULT, false);

    This post has been edited 2 times. It was last edited by Unscrewed Sep 12, 2012.
  30. Offline

    Gravity BukkitDev Team Lead Moderator BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    Yeah. Just so others know who might see this, using the "this" keyword isn't possible before onEnable() has been called, because the plugin hasn't been initialized yet.
  31. Offline

    zack6849

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Minecraft account:
    MCUSERNAME
    H3lix, i love you for making this and then posting it for us to use.

    This post has been edited 1 time. It was last edited by zack6849 Sep 16, 2012.
    h31ix likes this.

Share This Page