Archive for the 'Java' Category

How to Write an Auto-Updater System in Java

Occasionally I do things where I spend a large portion of time looking up tidbits of code from various sources to string together something really cool. Usually I wish someone had documented how to do them so I wouldn’t have to jump around from site to site looking for things. This is one such time. I wanted to write an auto-updater for a java program, distributed in a jar file. Although this may sound somewhat complicated, it’s actually fairly simple, especially when your entire program is just one jar file. More programs should implement something like this, but I digress.

Here are the steps necessary for a complete autoupdating system. I will elaborate sometime in the near future, with code examples.

Step 0: Automate your build process

This is optional, but makes things easier. Basically, you can set up a build script using Ant that you can just double click to compile, assign a revision number, jar, and then upload your program to a webserver. Ant is an XML based build system, and since I’m not really familiar with build scripts I was surprised at how powerful and easy to use it is. I won’t go into depth about Ant, but this is a good link for getting started. Basically, you use XML to describe a set of tasks you want it to do. These groups of tasks are called targets. Of course, Eclipse, my editor of choice, has excellent Ant support, so editing is really easy, properties are autocompleted, and running scripts is just a double click on the desired target. There are a couple things that are of specific use for an auto-updater, such as finding the current revision of your code (in my case, from SVN, although you could use anything really).

<target name="find_revision" description="Sets property 'svn.revision' to the head svn revision"> <taskdef resource="org/tigris/subversion/svnant/svnantlib.xml" /> <svn> <status path="src" revisionProperty="svn.revision" /> </svn> <echo>Revision: ${svn.revision}</echo> <echo file="revision.txt" append="false">${svn.revision}</echo> </target>

This code grabs the revision number from SVN, using the SVNAnt plugin, and then stores it in a file called revision.txt, as well as the variable svn.revision.

 

<target name="create_run_jar" depends="build,find_revision"> <jar destfile="foo.jar" filesetmanifest="mergewithoutmain"> <manifest> <attribute name="Built-By" value="${user.name}" /> <attribute name="Main-Class" value="package.MainClass" /> <attribute name="Class-Path" value="." /> <attribute name="Implementation-Vendor" value="The Team" /> <attribute name="Implementation-Title" value="The Program" /> <attribute name="Implementation-Version" value="${version.num}r${svn.revision}" /> </manifest> <fileset dir="classes" /> <fileset file="revision.txt" /> </jar> </target>

This bit packages the code into an executable jar file. It also grabs revision.txt, so the software can figure out what revision it is. I had a similar jar for my updater, since if your program is running you can’t overwrite it, at least on Windows, so you need to use a separate executable to actually download the update.

<target name="copy_to_server" depends="find_revision,create_run_jar,create_updater_jar"> <input message="Please enter username:" addproperty="scpuser" /> <input message="Please enter password:" addproperty="scppw" /> <scp file="foo.jar" trust="yes" remoteToFile="${scpuser}:${scppw}@my.server.com:~/public_html/stuff/foor${svn.revision}.jar"> </scp> <scp file="updater.jar" trust="yes" remoteToFile="${scpuser}:${scppw}@my.server.com:~/public_html/stuff/updater.jar"> </scp> <sshexec host="my.server.com" username="${scpuser}" password="${scppw}" trust="yes" command="chmod 644 ~/public_html/stuff/foor${svn.revision}.jar" /> <sshexec host="my.server.com" username="${scpuser}" password="${scppw}" trust="yes" command="ln -s -f ~/public_html/stuff/foor${svn.revision}.jar ~/public_html/stuff/foo.jar" /> <scp file="revision.txt" trust="yes" todir="${scpuser}:${scppw}@my.server.com:~/public_html/stuff"> </scp> </target>

This part copies the files to the server, using scp. SCP is the secure file copy client, part of SSH. There’s probably a plugin to use FTP too, if that’s your thing. The thing about SCP is that it gives files default permissions of 0600, meaning they aren’t world readable, no matter what your umask is, so you need to follow it up with an ssh command to chmod the file to the proper permissions, probably 644. Just for the sake of keeping old revisions on the server, instead of uploading something to foo.jar I upload to foor${svn.revision} and then create a symlink from the latest file to foo.jar.

Step 1: Store your local version somewhere

In my case, I stored it inside the jar file in a text file called revision.txt using the Ant script. Later, you can get at it by using this little bit of code.

public int currentRevision(){ BufferedReader is; try { is = new BufferedReader( new InputStreamReader(ClassLoader.getSystemResource("revision.txt").openStream())); int rev = Integer.valueOf(is.readLine()); return rev; } catch(NullPointerException e){ }catch (Exception e) { e.printStackTrace(); } return 1<<31-1; }

This checks inside the jar file for a revision.txt, and then parses it to get the current revision. If no file is found, it returns the largest possible value for an int, to avoid problems with trying to update needlessly.

Step 2: Host updates on a server, along with a latest version number

I used a remote revision.txt to check against the local one. You can check the latest revision using this snippet of code:

public int latestRevision(){ URL url; try { url = new URL("http://my.server.com/~me/stuff/revision.txt"); HttpURLConnection hConnection = (HttpURLConnection) url .openConnection(); HttpURLConnection.setFollowRedirects(true); if (HttpURLConnection.HTTP_OK == hConnection.getResponseCode()) { BufferedReader is = new BufferedReader(new InputStreamReader(hConnection.getInputStream())); int rev = Integer.valueOf(is.readLine()); return rev; } }catch(IOException e){ e.printStackTrace(); } return -1; }

This simply downloads a file from the server and returns the int value of the file. All uploading tasks are taken care of by the Ant script, which you would run every time you want to publish a new revision. You would want to compare the latest revision with your revision at some time that makes sense for your program, probably at startup. If you find that there’s a new version, notify the user that there is an update available, and then start the updater and exit yourself (System.exit(0);).

Step 3: Write a downloader stub that downloads the new version

Since you can’t write to an open file (at least on Windows) you need to download the new version in a separate process and close the current one. I used a specialized class just for downloading the file. I also included a general purpose method for downloading files in case other parts need to download anything. This is used in another class to download the updater.jar, in case it doesn’t exist. You might find that you need to update the updater, in which case you can just skip the check for an existing updater, or go through the trouble of versioning the updater too. It’s not that difficult, but probably not really worth it when the updater.jar is around 3kb.

package updater; import java.awt.Dimension; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import javax.swing.JFrame; import javax.swing.JProgressBar; public class Updater extends JFrame{ String updateurl; JProgressBar progress; public static void main(String[] args){ Updater up = new Updater("http://my.server.com/~me/stuff/foo.jar"); up.downloadLatestVersion(); try { Process foo = Runtime.getRuntime().exec("java -jar foo.jar"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.exit(0); } public Updater(String url){ updateurl = url; this.setPreferredSize(new Dimension(300, 80)); this.setSize(new Dimension(300, 80)); this.setTitle("Updater"); progress = new JProgressBar(0,100); progress.setValue(0); progress.setStringPainted(true); this.add(progress); this.setLocationRelativeTo(null); this.setVisible(true); this.requestFocus(true); } void downloadLatestVersion(){ URL url; try { url = new URL(updateurl); HttpURLConnection hConnection = (HttpURLConnection) url .openConnection(); HttpURLConnection.setFollowRedirects(true); if (HttpURLConnection.HTTP_OK == hConnection.getResponseCode()) { InputStream in = hConnection.getInputStream(); BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream("foo.jar")); int filesize = hConnection.getContentLength(); progress.setMaximum(filesize); byte[] buffer = new byte[4096]; int numRead; long numWritten = 0; while ((numRead = in.read(buffer)) != -1) { out.write(buffer, 0, numRead); numWritten += numRead; System.out.println((double)numWritten/(double)filesize); progress.setValue((int) numWritten); } if(filesize!=numWritten) System.out.println("Wrote "+numWritten+" bytes, should have been "+filesize); else System.out.println("Downloaded successfully."); out.close(); in.close(); } }catch(IOException e){ e.printStackTrace(); } } public static void downloadFile(String sourceurl, String dest){ URL url; try { url = new URL(sourceurl); HttpURLConnection hConnection = (HttpURLConnection) url .openConnection(); HttpURLConnection.setFollowRedirects(true); if (HttpURLConnection.HTTP_OK == hConnection.getResponseCode()) { InputStream in = hConnection.getInputStream(); BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream(dest)); int filesize = hConnection.getContentLength(); byte[] buffer = new byte[4096]; int numRead; long numWritten = 0; while ((numRead = in.read(buffer)) != -1) { out.write(buffer, 0, numRead); numWritten += numRead; System.out.println((double)numWritten/(double)filesize); } if(filesize!=numWritten) System.out.println("Wrote "+numWritten+" bytes, should have been "+filesize); else System.out.println("Downloaded successfully."); out.close(); in.close(); } }catch(IOException e){ e.printStackTrace(); } } }

This code is pretty easy to follow, and half of it is essentially the same method duplicated for reuse in other places. In hindsight, I probably could have statically downloaded the update file, but then I wouldn’t be able to use the nice progress bar.

Step 4: Restart with the new version

Once you’ve downloaded the new version, the updater will simply start the new version and quit. And then, you’ve got an automatic updater system that is pretty simple and easy to use. Thanks to jar files, you won’t even have to worry about grabbing a large number of files, although if you really wanted to save bandwidth you could do something where you split up your code and resources between jar files. In my case it wouldn’t really matter since my entire program, including resources, is under 500kb, and if it was just the code by itself it would download before the progress bar even showed up. As for my project, maybe I’ll reveal it in another post once I clear up some issues.

Text Invaders 0.6

After a bit of work, I’ve released another version of Text Invaders in Java, this time a bit more featureful. The full changelog is on Google Code here, but in summary, I added a HUD, money/score, menus, a rudimentary shop, and waves. The release is available in executable jar format again here. The next release should add a lot of polish and turn what are mostly a bunch of placeholders in this version into actual counterparts of the VB version.

Text Invaders Java 0.5

I’ve resumed working on my Java port of Text Invaders, and so far things are going fairly well. I’ve got an alpha quality release up on Google Code now, available at http://code.google.com/p/textinvaders/ . Right now it’s just basic collision detection and graphics working. I’ve got a list of bugs in the issue tracker for each milestone, and I hope to get 1.0 out by the end of the summer.

The game is packaged in an executable JAR format, which should mean it’s cross-platform as I only use built-in nonnative libraries (Graphics2D FTW) but I haven’t tried it on anything other than Windows yet.

Text Invaders Plans

So I’ve got a handful of free time this summer, between robotics, getting a driver’s license, and trying to get back in shape, and I’ve decided to start working on a Java port of Text Invaders. Hopefully I’ll be able to finish the game this time. Unfortunately with the original VB6 version it languished for a while, and when I was actually interested in completing it I had learned Java and Visual Basic seemed too primitive. VB6 isn’t even fully object-oriented. For example there are classes and inheritance, but it’s only interface inheritance and not implementation inheritance, so any subclasses have to rewrite inherited methods, which kind of defeats the purpose of inheritance in my opinion. And my code for Text Invaders was so sloppy, and mostly in one file, that I just couldn’t bear to edit it anymore.

Now, I’ve taken the basic drawing code I used for my APCS final project, named LANeroids now, and modified it to use text instead of polygons. By moving to Java I gain a lot of nice things like true object orientation and instant cross-platform capabilities, but sadly I also give up some nice things like DirectX support. I think it might be possible to add DirectX to a Java app, but that would make it cease being cross-platform, so I don’t know if that’s worth it. OpenGL is a nice alternative I guess, but I’d also need to use OpenAL for sound most likely. And I was so proud of my 3D positional effects, too. Once I’ve gotten a definitive Java version done, complete with actual levels with perhaps multiple waves per level, bosses, online high score boards, automatic updates, and such, I’m considering doing a Wii or DS homebrew port. A post-1.0 version might also add in co-op multiplayer. Anyway, this is all speculation right now. Back to coding!

LANeroids now online, will be worked on soon

Yay, finally got around to putting the source for LANeroids (my catchy name for my APCS final project) on my google code svn repository, and I plan to work on it further. I have some ideas about optimizing performance to make it not suck, such as ripping out all the serialization junk and replacing it with sending strings over sockets instead. Much less dense and less bandwidth required, and I have a feeling serialization takes a fair bit of time in Java. I was pulling out just the graphics code for a Robotics thing and I found that the graphics/keyboard input portion without the networking runs 10x faster on my computer, so the serialized network code is definitely to blame.