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

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

Thread Status:
Not open for further replies.
  1. Offline

    Gravity Retired Staff

    Updater - Version 2.3
    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.

    Get 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.
    -- Get Updater --


    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:
    File titles with proper version numbers (open)

    [​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 the bukkit.yml file where any stored jars will be switched with its currently-in-use counterpart when 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; 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:
    Output (open)

    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.
    

     
    Last edited: May 6, 2016
    FisheyLP, Nathat23, Eathuis and 36 others like this.
  2. Offline

    Gravity Retired Staff

    Updater 2.0 has been released!

    I'm happy to announce a new version of Updater! If you're already using Updater and want to upgrade to 2.0, here's what you need to know.

    • A global configuration has been added! Similar to the PluginMetrics system, Updater 2.0 now includes a per-server configuration, which allows server administrators to configure updating. Notably, this configuration allows admins to globally disable auto-updating, in an effort to ensure plugins comply with the Project Submission Guidelines.
    • The official ServerMods API provided by Curse is now in use! This means that Updater 2.0 has a much cleaner, simpler way to gather information about the newest builds, and is able to provide you, the developer, with access to it.
      • The following new methods have been added:
        • 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
      • Additionally, server administrators have the power to configure an API key to use with querying the site.
      • Projects on the site are now referenced with a static ID, which helps eliminate confusion over the dynamic nature of slugs.
    • A great deal of improvements have been made to the code! This helps make Updater more efficient (and more sane) to use. We continue to welcome your suggestions on the GitHub page.
    How to upgrade to version 2.0:
    1. Download the new Updater class from the repository.
    2. Replace your old Updater.java with the new class.
    3. Follow the wiki article on finding your project's ID.
    4. When initializing the Updater class, replace your project's slug with its ID. For instance:
    Code:
    Updater updater = new Updater(this, "anticheat", this.getFile(), Updater.UpdateType.DEFAULT, false);
    
    Would become:
    Code:
    Updater updater = new Updater(this, 38723, this.getFile(), Updater.UpdateType.DEFAULT, false);
    
    5. Note the change in file info access methods.
    • getLatestVersionString() has been replaced with getLatestName()
    • getFileSize() has been removed
    • getLatestType() has been added
    • getLatestGameVersion() has been added
    • getLatestFileLink() has been added
    • All these new methods have been documented above on the main post and on the JavaDocs.
    6. Note the change in UpdateResult.
    • FAIL_BADSLUG has been replaced with FAIL_BADID
    • DISABLED has been added
    • FAIL_APIKEY has been added
    • All these new values has been documented above on the main post and on the JavaDocs.
    7. Note that although version 2.0 contains a global toggle for updating. It would be unwise to remove an existing configuration toggle, as this would confuse users who have previously set it. Furthermore, the new global toggle may be inconvenient for some server administrators who wish to allow only specific plugins to update.

    Unless mentioned above. There have been no breaking changes to Updater. After you have replaced your old version, you should browse your code for errors, and fix them using the information provided. While there are few functionality changes, you should browse the code, the updated post, and the JavaDocs in order to ensure that you understand what has been changed.
     
  3. Offline

    Ultimate_n00b

    Nice! I like the new json method, instead of parsing the page.
     
    Skyost likes this.
  4. Offline

    Skyost

    Ultimate_n00b BukkitDev reject my files because I have updated the updater...
     
  5. Offline

    Lolmewn Retired Staff

    Please report that file.
     
    Skyost likes this.
  6. Offline

    moose517

    Glad it was an easy update to get this done. However if i can suggest something, would it be possible to make updater API agnostic? My plugins work on both bukkit and spout servers with a single jar file and would like for updating support spout side but can't currently because of the depends on bukkit for the configuration stuff.
     
  7. Offline

    Lolmewn Retired Staff

    Gravity I keep getting 403 Forbidden when using any API key (both my server-mods API key and normal API key don't work and result in 403). Ideas?
     
  8. Offline

    nxtguy

    Gravity Lolmewn How does one specify a API key? Is this something the dev does, or the admin?
     
  9. Offline

    Gravity Retired Staff

    The admin. It's specified in the config file generated at plugins/Updater/config.yml
     
  10. Offline

    nxtguy


    Oh, the config.yml file doesn't get generated. The folder is there, but it's contents are empty. (Any information you need?) I was looking at plugins/update not plugins/Updater :p

    Also, what would be the advantage of the server owner putting their API key in? I haven't done anything and it seems to be working fine.
     
  11. Offline

    Lolmewn Retired Staff

    nxtguy if anonymous access to the api ever gets disabled for some reason, you'll still be able to access it if you specified the api key.
     
    nxtguy likes this.
  12. Offline

    iiHeroo

    Took me a while to figure out how to get the "38723" part :p
    Code:java
    1. Updater updater = new Updater(this, 38723, this.getFile(), Updater.UpdateType.DEFAULT, false
     
  13. Offline

    Gravity Retired Staff

    You mean a while to find the text "If you don't know what this is, follow the instructions on this wiki article."? :D
     
  14. Offline

    iiHeroo


    It confused me a bit, longggg day :p I figured it out myself, I went to my plugin page, went to Curse Link to download, right clicked on "Project Site" and it says a string of numbers at the end for a few seconds, and then I used those #'s. I'm not sure if that's the correct way to do it, but it's easy.
     
  15. Offline

    filoghost

    Did you fixed the bug with plugin versions? (1.8.1 results > 1.9)
     
  16. Offline

    Gravity Retired Staff

    filoghost likes this.
  17. Offline

    lmc

    If the plugin is set to version 6.4 and the version on BukkitDev is 6.3, it will downgrade to 6.3. Just letting you know, is this a bug? Cheers.
     
  18. Offline

    moose517

    i like how the reply right before you post is about that bug....
     
  19. Offline

    Gravity Retired Staff

    Indeed, there's really no other safe way for it to know whether to update other than just comparing "is the latest version different?"

    If people are going to be running pre-release builds, you should specify them as such with a noUpdateTag ("-DEV", "-SNAPSHOT", "-PRE") by default to compensate for this.
     
  20. Offline

    lycano

    Gravity first of all i would like to thank you for providing the Updater class. I believe that many would have spend hours into coding and some simply would have failed.

    As i just had to implement it i discovered some problems when using updater. So i would like to give some feedback.

    • The configuration file is placed in a global Updater plugin directory - Since we will most likely have many devs who will use this class i believe it would be best to point updaterFile to the plugins configuration directory e.g. plugins/MyPlugin or you can only disable updateing plugins globally

    • the configFile name should be configureable. Since i wanted a per plugin configuration i named it updater.yml and placed it under plugins/MyPlugin

    • User-Agent should be configureable via config

    • Initialisation of the class and running the updater thread could be split into init() and run() so we can initialize the Updater class in our plugin and change the behavior on the go through a newly created method setUpdateType(Updater.UpdateType type) and setAnnounce(boolean announce)
      • Updater updater = new Updater(this, 38723, this.getFile(), Updater.UpdateType.NO_DOWNLOAD, false);

      • And if needed setUpdateType to whatever we want. Same with setAnnounce

      • After that calling updater.run() would actually initalize thread variblabe and run the thread

    • The Updater should create its own "workspace" directory in minecraft update directory. If we use the first approach (per plugin config) we could then use those workdirs as "fallback" if we ever want to extend the class to a fully "update and rollback" system.

    • For now i just created a per plugin workfolder so we dont accidently delete zip files that where placed there for a reason. I have used a Stack to recursevily delete the workfolder before downloading a new version

    • Regarding "delete first and download after" or as you have it "download and during that remove the old file" .. everytime i run the thread that will call saveFile i get "java.util.zip.ZipException: error in opening zip file" on my linux box.

      When i disable the delete codeblock i get no error.

      To debug this i inserted a check inside unzip method file.exist() and discovered that the file downloaded is removed during unzip (before new ZipFile could run).

      I believe that this behavior is limited to linux only since deleting that file while having a file pointer active would only be blocked/ignored on windows systems since there is no file lock.
     
  21. Offline

    Gravity Retired Staff

    lycano

    By providing a global configuration, I'm giving you a supplemental, per-server configuration. You're still supposed to have your own configuration with toggle-off options, and you can do whatever you want there. I'm not limiting you at all, just providing a more global configuration IF it's wanted.

    I'm not sure what purpose this would serve. Maybe if you wanted it to be an option in starting the Updater, that would make sense (you can do that by editing the source anyhow), but providing this configuration option to users seems silly in my opinion. Currently the User-Agent doesn't actually do anything except tell Curse what the traffic is coming from, so it's not an important value to expose to the end-user. In any case, if you really want to provide that, the source code is yours to do it from. If you wanted to submit a Pull Request to implement it, we could have a more broad discussion about it there.

    I'm confused as to why that would be necessary; Updater provides NO information before the thread is started, so there's no value in creating the object before it's needed. The behavior is always determined before launching, and if you need to run it again with different parameters, that's another object entirely.

    Updater specifically uses the plugin update folder provided in the bukkit.yml for the user. This means that Bukkit will go through the process of actually replacing files, not Updater. I don't deal with that at all, which is purposeful.

    If you're downloading zip files for a reason, you shouldn't be putting them in the updates folder.


    Please provide me with the whole stack trace so I can solve the issue, unless you're submitting a Pull Request.

    I appreciate your feedback. I'm open to discussion on all these points, but I think some of your suggestions as to what the Updater should do by default are very niche and better implemented on your own; the source code is entirely open and yours to modify.
     
  22. Offline

    lycano

    Gravity

    I agree. You don't limit here anything i still do believe that providing a per-server configuration is usually OK for the user since in general you normally would update all plugins. But from my perspective as a developer i would also like to consider the fact that not all plugins used on a server might be using your class.

    > seperation of init and run part
    Well i guess it might be just me. Im used to "prepare instance by initializing, then modify its behavior when executing a command". Thinking about what you said i do see your point now. Since the class does not provide anything that would be needed all the time it simply does not really make sense to carry around the object all the time.

    > you shouldn't be putting them in the updates folder
    True, but as the zip file will be extracted there could be left-overs from a previous update attempt with the updater class. due to possible IO errors. Personally i prefer to create a workspace folder for each plugin as a additional safety. You could say for sure then "everything thats inside that folder is definitely temporary therefore can be removed safely.

    > Please provide me with the whole stack trace so I can solve the issue, unless you're submitting a Pull Request.
    i will post the stack trace as an issue ticket on your github. I dont have a working solution currently available. A pull request might be following later if i come up with something.

    >but I think some of your suggestions as to what the Updater should do by default are very nice and better implemented on your own
    I agree. As i mentioned in my first statement per server configuration is OK for most devs and users. Its also hard to create a working solution for every single plugin but this might be just it.

    On a side note i was seeing this class i instantly was thinking about "what if this beauty would be taken into bukkit itself?". As the update code is already in there it would be awesome if some might actually configure this via their plugin.yml.

    Im getting off topic here ^^ Anyways thanks again! I might get back to you on another day about the other topics.
     
  23. Offline

    Gravity Retired Staff

    lycano
    If other updaters want to support the global configuration, that's their prerogative. Right now, I'm not aware of another open-source auto-update system such as this one that's up to date, otherwise I would work with the authors to help ensure we have a common global toggle. If you're using a different updater, the global config option doesn't apply, and that notification is given to users in the config's header.
    Such files are automatically removed by the code. I disagree that creating a folder for each plugin is a good idea.
    Yeah, it might be nice, but I think it's best that this stays separate. In my opinion if you haven't sought out auto-updating on your own, you shouldn't be handed it. There are a lot of plugins that needlessly employ auto-updating code as it is, and Bukkit managing that really isn't its job anyhow.
     
  24. Offline

    lycano

    Gravity well not all files where auto removed after zip has finished extracting it. The server was at some point reaching "overloaded state". I will have to take a deeper look later to see why as it was very late yesturday as i got it working to be used and somewhat failsafe.

    > Auto Update included in bukkit
    Well, true but since bukkit plugins should be installed via DBO it would at least make sense to me that this feature would be included.

    But anyhow this could always stay seperate as the goal of bukkitAPI is providing API not implementation. Seeing this I really would like to use this as a base for a per-plugin updater that provides simple registration to it like the EventManager does.

    E.g. updater.registerPlugin(..)

    I will come back to you when i have thought this through i guess ^^
     
  25. Offline

    Ape101

    When i put the one line of code into my main class im getting an error on id
    It cannot be resolved as a variable
    am i missing something? :p
     
  26. Offline

    lycano

    Ape101 for sure. If you just use the source from github and initialize the class like shown in the example there should be no problem.
     
  27. i have an issue thats my file name "UpdateTest v0.1" And i keep getting the error that the filename needs to be "PluginName vVersion" :S
     
  28. Offline

    Gravity Retired Staff

    Link to the BukkitDev page, please?
     
  29. Offline

    PieMan456

    Gravity

    Hey gravity when I put your line of code into my main plugin class it says there is an error with "id". It has the red under it. Do I need to set id to something somewhere, or is it supposed to be like that?
     
  30. Offline

    Gravity Retired Staff

    You're supposed to set the ID to the project's Curse ID. Please reach the full documentation; under that code it is explained in great detail.
     
  31. Offline

    PieMan456

    Gravity
    Where is the full documentation?
     
Thread Status:
Not open for further replies.

Share This Page