-
Notifications
You must be signed in to change notification settings - Fork 0
Java Selfupdater
This artifact can enable your app to update itself. Follow this article to see how.
The only (but very challenging) prerequisite is that you publish your app to a public Maven Repository. If you don't know what Maven is, visit this article.
If you don't have your own public maven repository, you might want to have a look at Bintray or Jitpack. They are both free hosters for maven repos as long as your project is open source.
You will need to command maven to put the versioning info into the MANIFEST.MF of your jar file. To do so, you need to configure the maven-assembly-plugin as follows:
<build>
...
<plugins>
...
<plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
<manifestEntries>
<Implementation-Build>${buildNumber}</Implementation-Build>
<Custom-Implementation-Build>${buildNumber}</Custom-Implementation-Build>
</manifestEntries>
</archive>
...
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
</build>You need to tell the UpdateChecker the groupId, artifactId and packaging of your app and the base url of your maven repository. If you use bintray, this would be http://dl.bintray.com//
If you use a custom classifier (e. g. "jar-with-dependencies"), you need to specify that too.
The groupId and artifactId are obviously entirely chosen by you, the packaging is usually jar if you did not specify anything else in your POM.
You can choose any way to save this info in your app but we suggest a Config class which might look like that:
public class Config{
public static final String groupId = "myGroupId";
public static final String artifactId = "myFancyArtifact";
public static final String packaging = "jar";
public static final String classifier = "jar-with-dependencies";
public static final URL getMavenBaseURL(){
URL res = null;
try {
res = new URL("http://dl.bintray.com/vatbub/fokprojectsSnapshots");
} catch (MalformedURLException e) {
log.getLogger().log(Level.SEVERE, "An error occurred", e);
}
return res;
}
}There are two modes of update checking. One, where the user may ignore an update and the other where that setting is overridden. An example:
- The user runs v0.0.1
- You call the UpdateChecker at the beginning of you
main-method. - Version 0.0.2 is published
- The user is prompted to update to v0.0.2 but currently has no time to do the update, so he clicks the ignore button.
- Now, the user will not be prompted anymore at application startup to upgrade to that particular version.
- If you publish another update (let's say v0.0.3), the user will be prompted again, as he only ignored the update to v0.0.2
- Anyway, you might want to place a button in your app that is called Check for updates.
- If the user clicks that button, the
UpdateCheckerwill be called in the second mode and the user will be prompted for an update even if he ignored it.
You usually want to call the UpdateChecker in a secondary thread to prevent your gui from freezing while update info is downloaded from your repo.
Just call isUpdateAvailable(repoBaseURL, mavenGroupID, mavenArtifactID, mavenClassifier) and place your config from earlier on in there. The resulting UpdateInfo-object will tell you everything about the current state:
-
UpdateInfo.mavenRepoBaseURL: The repo url you specified -
UpdateInfo.mavenGroupID: The groupId you specified -
UpdateInfo.mavenArtifactId: The artifactId you specified -
UpdateInfo.mavenClassifier: The classifier you specified -
UpdateInfo.packaging: The packaging you specified or"jar"if you didn't specify anything UpdateInfo.showAlert: Tells you if you should display an update prompt to the user-
UpdateInfo.toVersion: The version of the app installed after updating -
UpdateInfo.fileSizeInMB: The file size of teh update in Megabytes.
Note: If you have a packaging that differs from jar you need to call isUpdateAvailable(repoBaseURL, mavenGroupID, mavenArtifactID, mavenClassifier, packaging)
... you call UpdateChecker.ignoreUpdate(versionToIgnore) and specify UpdateInfo.toVersion as the version to be ignored. This tells the library to ignore that particular version in the future.
The procedure is the very same as for mode 1, except that you call UpdateChecker.isUpdateAvailableCompareAppVersion(...) instead.
If the user decides to apply an update he was prompted for, you just call UpdateChecker.downloadAndInstallUpdate(updateToInstall) with updateToInstall beeing just that UpdateInfo-object you get when calling isUpdateAvailable(...) or isUpdateAvailableCompareAppVersion(...).
While the above call is the simplest one, the user gets stuck with no information about the download- and installation progress. This might be bad especially when the user has a slow internet connection or errors occur. This is why you might want to include a progress gui. To do so, you have two options:
- If you wish to have a completely customized gui and full control over the download options: Create any gui-class that implements the
UpdateProgressDialog-interface and callUpdateChecker.downloadAndInstallUpdate(updateToInstall, guiInstance) - If you are lazy, you can use the predefined gui for that. In that case just call
new UpdateAvailableDialog(updateToInstall)
Beware that the second option handles the entire user interaction so your code should look similar to that (of you use the 2nd option):
Thread updateThread = new Thread() {
@Override
public void run() {
UpdateInfo update = UpdateChecker.isUpdateAvailable(...);
if (update.showAlert) {
Platform.runLater(new Runnable() {
@Override
public void run() {
new UpdateAvailableDialog(update);
}
});
}
}
};
updateThread.start();If you use the secont option, all of the following options will be automatically set to their default values. If you still wish to customize them, you will need to subclass UpdateAvailableDialog and make your changes there.
If you use option one, you are free to change the following options. Just add them to your call of downloadAndInstallUpdate(...):
-
UpdateProgressDialog guiInstance: The instance of your custom gui where the update progress should be shown. -
boolean launchUpdateAfterInstall: Iftrue, the updated app version will be automatically launched after the download. (Default value:true) -
boolean deleteOldVersion: Iftrue, the old app version will be deleted after the update finishes.
Notes concerning deleteOldVersion:
- This option only deletes your main app file (e. g. the old jar file). Any other files created by your app (preferences, ...) will not be touched by this.
- Due to a limitation of Windows, the app can't actually delete itself (due to file locking). This is why we implemented a workaround that unfortunately needs further action on your side:
If the option is set to true, the updated app version will be launched with an additional startup parameter "deleteFile=PathToOldJar". The only thing you need to do is to call
UpdateChecker.completeUpdate(String[] startupArgs)in yourmain-method and it will do the rest.
If you did everything correctly, your code could look like this:
public class Config{
public static final String groupId = "myGroupId";
public static final String artifactId = "myFancyArtifact";
public static final String packaging = "jar";
public static final String classifier = "jar-with-dependencies";
public static final URL getMavenBaseURL(){
URL res = null;
try {
res = new URL("http://dl.bintray.com/vatbub/fokprojectsSnapshots");
} catch (MalformedURLException e) {
log.getLogger().log(Level.SEVERE, "An error occurred", e);
}
return res;
}
}
// File MainClass.java
import common.UpdateChecker;
public class MainClass(){
public static void main(String[] args){
// Complete previous updates
UpdateChecker.completeUpdate(args);
// Check for new updates
Thread updateThread = new Thread() {
@Override
public void run() {
UpdateInfo update = UpdateChecker.isUpdateAvailable(...);
if (update.showAlert) {
Platform.runLater(new Runnable() {
@Override
public void run() {
new UpdateAvailableDialog(update);
}
});
}
}
};
updateThread.start();
// Do your stuff
}
public void checkForUpdatesButtonHandler(){
// Check for new version ignoring ignored updates
Thread updateThread = new Thread() {
@Override
public void run() {
UpdateInfo update = UpdateChecker.isUpdateAvailableCompareAppVersion(...);
Platform.runLater(new Runnable() {
@Override
public void run() {
new UpdateAvailableDialog(update);
}
});
}
};
updateThread.start();
}
}