Thursday, January 28, 2010

RCP Update: No more "jammern" with InstallJammer

Wow, I got a lot of feedback because of my last P2 bashing blog post. A guy from compeople even called me the other day, telling me they had the same problem and that they can provide a ready to use update mechanism. Not for free unfortunately. But it might be worth looking into it at some point. He also told me, that they are contributing to the eclipse Riena project which looks promising, have to look into this when I find some time.
I understand, that the situation is frustrating for the P2 team and that I better should help resolving the issues instead of complaining. But please also understand, that our customer doesn't care and I had to work through the weekend to write my own update mechanism.
Well the good news is that the InstallJammer approach works so far and even better: Comerge agreed to open source the update code under a new project called Refresh. In this blog I want to explain how Refresh works.

How to update an RCP with Refresh

Refresh connects to a set of updatesites and collects all available updates. An updatsite is an xml document hosted on a web server. The site contains a set of updates. An update is described by a product id, a version and a location of an executable. The updater can then download the executable and execute it. The executable is expected to update the client and then restart the client. We use InstallJammer to generate this executable. In the first part I describe how to program the updater in the second part I explain how to generate the executable.

Programming the updater

First you need to collect the updatesites:

HttpUpdateSitesReader reader= new HttpUpdateSitesReader(new File("updatesites.txt"));
List<UpdateSite> sites= reader.getSites();

The updatesites.txt can contain as many updatsites as you want:

http://foobar.com/maintenance/updatesite.xml
http://dev.foobar.com/milestones/updatesite.xml

I recommend to place this file next to the executable of your RCP. The user can then easily add new sites.

The updater will then connect to all the sites and collect all updates for a given product:
UpdateSearcher searcher= new UpdateSearcher("foo.bar.product", sites.toArray(new UpdateSite[sites.size()]));
List<Update> allUpdates= searcher.searchUpdates(new NullProgressMonitor());
The updatesite is a simple xml file like this:
<?xml version="1.0" encoding="UTF-8"?>
<updatesite>
   <update>
      <product>foo.bar.product</product>
      <version>1.0.1</version>
      <resource>http://foobar.com/update101.exe</resource>
   </update>
   <update>
      <product>foo.bar.product</product>
      <version>0.2.0</version>
      <resource>http://dev.foobar.com/update020.exe</resource>
   </update>
</updatesite>
You then need to determine if there is an update available:
List<Update> updates= EclipseVersionSchemaHelpers.getNewerVersions("0.1.0", allUpdates);
if (updates.size() == 0)
 //no update available
 return;

Update update= EclipseVersionSchemaHelpers.getNewestVersion(updates);
The code assumes, that you use the standard eclipse versioning schema but using your own is straight forward.

If there is an update available all what's left to do is to download the update and restart the workbench:
if (update.install(".", new NullProgressMonitor())) {
 PlatformUI.getWorkbench().close();
}
Of course you should use a ProgressDialog here and there and provide some useful error reporting. But you get the idea. The harder part is to generate the executable to update your client.

Generate InstallJammer updater

You can use the InstallJammer configuration GUI to define your updater. Defining an updater is quite simple, I only found two things which are not that obvious:
1. The default update type must be set to silence, otherwise the user will see the installer UI and has to press finish at the end. I was not yet able to show a progress bar only when executing the installer.



2. The folders which the installer is reading must be marked as active. Otherwise the content of the folders will be fixed and new files will not be picked up when generating the installer.



Once you have the installer defined you can use a simple bash script to generate the installer. My hudson build executes this script after the PDE build has finished:
mkdir hudson-jobs/foobar/workspace/pluginbuilder/installerworkspace
cd hudson-jobs/foobar/workspace/pluginbuilder/installerworkspace
cp ../results/foobar-*.zip foobar.zip
unzip foobar.zip
./installjammer -DAppName foobarupdate --build-for-release --output-dir ../results/ --build ../../rcp/foo.bar.installer/FooBarUpdate.mpi
mv ../results/foobarupdate--Setup.exe ../results/foobar-update.exe
That's it. I use the same script to generate an installer to install the RCP initially.

Things to do

All this is very simple, a bit too simple at the moment. There are certain must haves:
1. Show a progress while the update installer is running
2. Allow the installer to authenticate. Otherwise updates are available for everyone.
3. Make it work on other platforms

If you want to contribute or use the code go to the project page. The code is licensed under EPL.

Thanks

No comments:

Post a Comment

Followers