JUpdater - Client

From LokDoc

Jump to: navigation, search

JUpdater consists of two parts, one part sits in a server and the other in the programs that you want to keep updated. The later part is referred to as the client part and is the focus of this article. The client is written for JRE 1.5 and meant to be easy to implement into existing Java projects.

DKP Log Parser's implementation of the JUpdater client.
Enlarge
DKP Log Parser's implementation of the JUpdater client.

Contents

Installation

The client can be downloaded from http://www.lokorin.com/jupdater/download .

There are four packages that have to be included into the intended project (all found in the client folder).

  • com.lokorin.net: A small net package used by JUpdater.
  • com.lokorin.jupdater: Contains the JUpdater classes.
  • com.twmacinta.util: A fast MD5 implementation used by JUpdater to verify downloaded files. See http://www.twmacinta.com/myjava/fast_md5.php for more information.
  • com.twmacinta.io: See the com.twmacinta.util package.


JUpdater

JUpdater is the central class, everything that has to be accessed to update a program, along with various utility methods, can be found in this class. There are two constructors for JUpdater, here are the signatures.

public JUpdater(URL serverUrl)
public JUpdater(URL serverUrl, JUpdateListener updateListener)

The common parameter serverUrl is the URL to the server's update page (for example http://www.mydomain.com/updatedir/update.php) which is part of JUpdater's server part. The updateListener parameter is the optional listener implementation, which can be used to for instance have a GUI display the progress to the user.


Methods

The following methods can be accessed in the JUpdater class.

updateJar

/**
 * Updates a JAR containing a specified program. The process goes through 
 * many steps and sends the progress to the update listener specified in 
 * the constructor.
 * @param jarToUpdate The local JAR file containing the program.
 * @param programToUpdate The name of the program to update (as it is 
 * 	specified on the server).
 * @return True if the program was patched and should now be restarted, 
 * 	false otherwise.
 */
public boolean updateJar(File jarToUpdate, String programToUpdate)

This is the only method that actually downloads files and should be used when one wants to perform a complete check of the client's version. The method checks each and every file with the server, and then downloads the files that are out of date, patching the old jar file. The progress, and failures, of the method are sent to the JUpdaterListener instance, if one is specified.

The name of the program is the name specified in the server's admin panel for programs. The method starts the update process and any running jars have to be restarted for the changes to take affect, for example by using the restart method.

A jar file's location on the local filesystem can be retrieved by using the getJarFileThatContains method.

serverHasNewVersionByManifest

/**
 * Performs a fast check using the jar's implmentation version to decide 
 * whether or not the server has a new version or not.
 * @param jarToCheck The jar file that should be checked.
 * @param programToCheck The name of the program (as specified by the 
 * 		server) that should be checked.
 * @return True if the server has a version newer than the client's, false 
 * 		otherwise.
 * @throws IllegalArgumentException If the supplied jar file's manifest 
 * 		does not contain the Implementation-Version attribute.
 * @throws IOException If the specified file can not be found or read.
 * @throws ServerFaultException If something goes wrong on the server.
 */
public boolean serverHasNewVersionByManifest(File jarToCheck, String programToCheck) 
		throws IllegalArgumentException, IOException, ServerFaultException

This method can only be used for jar files which specify the Implementation-Version attribute in the jar manifest, see the jar manifest specification for more information about the attribute. The method is meant as a fast way to check for new versions, without having to use the throughout updateJar method method.

The method is particulary suitable if one wants to check for updates when the program starts. An alternative, in that situation, is to use the getChangeLogByManifest method instead, which also provides a change log. That way one can provide the user with information about new versions whenever one is released.

A jar file's location on the local file system can be retrieved by using the getJarFileThatContains method.

getChangeLogByManifest

DKP Log Parser's implementation of the JUpdater client.
Enlarge
DKP Log Parser's implementation of the JUpdater client.
/**
 * Requests the changelog from the server by using the local jar's 
 * implementation version as specified by its manifest if the server has a
 * new version. This method is fast but depends on the implementation 
 * version attribute to function. It can be used to both check whether 
 * there's a new version and get the change log. It should be used rather
 * than first checking if the server has a new version and then getting
 * the changelog (because that creates two sessions, while this only 
 * creates one).
 * @param jarToCheck The jar file that should be checked.
 * @param programToCheck The name of the program (as specified by the 
 * 		server) that should be checked.
 * @return The changelog if the server has a newer version (the changlog 
 * 		may be empty, in which case the server has a newer version, but
 * 		no changelog), or null if the server does not have a newer version.
 * @throws IllegalArgumentException If the supplied jar file's manifest 
 * 		does not contain the Implementation-Version attribute.
 * @throws IOException If the jar file can not be found or read.
 * @throws ServerFaultException If something goes wrong on the server when
 * 		processing the request.
 */
public String getChangeLogByManifest(File jarToCheck, String programToCheck) 
	throws IllegalArgumentException, IOException, ServerFaultException 

This method does two things, it gets a list of all changes made and checks whether or not the server has a new version. So the serverHasNewVersionByManifest method does not have be used with this method. Doing so is not catastrophical, but it will create two sessions with the server, which means twice the work and that two connections are registered in the server's log rather than just one.

A jar file's location on the local file system can be retrieved by using the getJarFileThatContains method.

getJarFileThatContains

/**
 * Attempts to find the currently loaded jar file that contains a specified
 * class. This is useful for locating the jar file that should be updated 
 * in the local filesystem.
 * @param classToFind A class contained in the jar file that should be 
 * 		found.
 * @return An existing jar file that contains the specified class, or null 
 * 		if none could be found.
 */
public static File getJarFileThatContains(Class classToFind)

This method is a utility method which can be handy when calling other methods. Most other methods require a jar file as parameter, but locating a jar file in the user's file system can be hard, especially if the user has changed the jar file's name. The method requires a class that is contained in the jar file that one wants to search for, so one should for instance send Foo.class as parameter if one wants to find the jar file that contains the Foo class.


restart

There are several different utility methods for starting or restarting jar files.

/**
 * Attempts to start the specified jar file. The start is attempted by 
 * having the runtime execute "java -jar", on the specified jar file. This 
 * has a high possibility of failing. If the jar needs to be launched with 
 * a more complex command, e.g. one that sends parameters to the VM, then 
 * please use the startWithCommand() method instead.
 * @param jarFile The jar file that should be started.
 * @return The process that is trying to launch the jar file.
 * @throws IOException If an I/O error occurs when starting the jar.
 */
public static Process start(File jarFile) throws IOException 

/**
 * Attempts to start a program by using the specified command.
 * @param command The command that should be executed by the runtime to
 * 		launch the program.
 * @return The process that is trying to execute the command.
 * @throws IOException If an I/O error occurs when executing the command.
 */
public static Process startWithCommand(String command) throws IOException

/**
 * Attempts to restart the currently running process with the specified jar
 * file. This method basically starts the jar file, then waits a second to 
 * see if the started process died. If not then it assumes that the jar was
 * started successfully and shuts down the running JVM with System.exit(0).
 * There is a risk that the current JVM is shut down without the program
 * having been restarted properly. Any state saving required before 
 * shutting down should be done before calling this method in order to 
 * start the new process with the correct state.
 * @param jarFile The jar file that should be started.
 */
public static void restart(File jarFile)

/**
 * Attempts to restart the currently running process with the specified jar
 * file. This method basically starts the jar file, then waits a specified
 * amount of milliseconds to see if the started process died. If not then 
 * it assumes that the jar was started successfully and shuts down the 
 * running JVM with System.exit(0). There is a risk that the current JVM is
 * shut down without the program having been restarted properly. Any state 
 * saving required before shutting down should be done before calling this 
 * method in order to start the new process with the correct state.
 * @param jarFile The jar file that should be started.
 * @param failureTime The number of milliseconds that the method should 
 * 		wait before checking whether or not the started process died.
 */
public static void restart(File jarFile, int failureTime)

A program that has been patched has to be restarted before the changes take effect, hence the restart methods. There are several methods, but the restart(File) method is probably sufficient for most programs. The other methods are there in case someone wants the launch command to do something more than just java -jar foo.jar, for instance send JVM parameters with the command.

It is important to note that the methods are far from guaranteed to work. In some environments the methods will do nothing, the restart methods might even close the running JVM without having launched anything. The user should therefor always be notified before the program is restarted. If the user is not notified then she or he will probably be quite surprised if the program closes without doing anything, and will probably perceive it as a crash.


JUpdaterListener

The progress from the updaterJar method can be fed into, for instance, a GUI by implementing the following interface into an appropriate class. This way you can let the user know what is going on during the update process. It is possible to run the updater without providing any type of interface for the user, but creating an interface is easy. You only have to implement the com.lokorin.jupdater.JUpdateListener interface in a class of your choice. The interface has seven methods.

/**
 * Sets the progress for the total update to a new value. When this hits 
 * 100 the whole update is finished.
 * @param state The new value [0, 100]. 0 representing no progress and 100 
 * 		representing finished.
 */
public void setTotalProgress(int state);

/**
 * Sets the progress for the current process being executed.
 * param state The new value [0, 100]. 0 representing no progress and 100 
 * 		representing finished.
 */
public void setProcessProgress(int state);

/**
 * Sets the text describing the current process being executed by the 
 * update. For instance "Downloading foo", "Checking integrity for foo" and 
 * so on.
 * param processDescription The bit of text describing what the updater is 
 * 		currently doing.
 */
public void setProcess(String processDescription);

/**
 * Adds text describing the status of the update.
 * param statusDescription The additional status text descibing the 
 * 		current status of the update. E.g. "Establishing connection with 
 * 		the server.", "All files are up to date.".
 */
public void displayStatus(String statusDescription);

/**
 * Displays text describing what is new in the update.
 * param newsText A description of the news in the update. The news is 
 * 		sent on an per change/item basis, so there will be several 
 * 		consecutive calls made to this method. It is hence recommended that
 * 		the text is buffered somehow.
 */
public void displayNews(String newsText);

/**
 * Display that a failure has occured. The failure means that the update
 * can not be completed.
 * @param failureDescription A description of the failure that occured.
 * @param exception The connected exception if there is one, null if there
 * 		was none.
 */
public void displayFailure(String failureDescription, Exception exception);

/**
 * This is used to as the listener if the queued downloads should be 
 * started.
 * @return True if the downloads should be started, false otherwise.
 */
public boolean shouldStartDownloading();

One simple way of implementing them in a GUI would for instance be to connect all the methods starting with display to a textarea, connect the setTotalUpdateProgress method to a progress bars and have the shouldStartDownloading open an dialog asking the user.


Example implementations

This is an example implementation of the JUpdaterListener interface in a Swing GUI. There are some variables not declared in the text.

  • pgbTotal: A progress bar.
  • pgbProgress: A progress bar.
  • lblProcess: A label.
  • txtaStatus: A textarea.

The result is a simple listener which shows what is happening in a textarea and label along with the progress in two progress bars. When an update is found the user is presented with an OptionDialog asking if the updater should patch the user's version or not.

/**
 * @see com.lokorin.jupdater.JUpdateListener#setTotalProgress(int)
 */
public void setTotalProgress(int state) {
	pgbTotal.setValue(state);
}

/**
 * @see com.lokorin.jupdater.JUpdateListener#setProcessProgress(int)
 */
public void setProcessProgress(int state) {
	pgbProcess.setValue(state);
}
 
/**
 * @see com.lokorin.jupdater.JUpdateListener#setProcess(java.lang.String)
 */
public void setProcess(String processDescription) {
	lblProcess.setText("Process: " + processDescription);
}

/**
 * @see com.lokorin.jupdater.JUpdateListener#displayStatus(java.lang.String)
 */
public void displayStatus(String statusDescription) {
	txtaStatus.append(statusDescription + "\n");
}
 
/**
 * @see com.lokorin.jupdater.JUpdateListener#displayNews(java.lang.String)
 */
public void displayNews(String newsText) {
	displayStatus("Change: " + newsText);
}

/**
 * @see com.lokorin.jupdater.JUpdateListener#displayFailure(java.lang.String, java.lang.Exception)
 */
public void displayFailure(String failureDescription, Exception exception) {
	displayStatus("Failure: " + failureDescription);
	if(exception != null) {
		displayStatus("Exception: " + exception);
	}
}

/**
 * @see com.lokorin.jupdater.JUpdateListener#shouldStartDownloading()
 */
public boolean shouldStartDownloading() {
	int n = JOptionPane.showOptionDialog(this,
			"A newer version has been detected,\n" +
			"do you wish to update?",
               "New version",
               JOptionPane.YES_NO_CANCEL_OPTION,
               JOptionPane.QUESTION_MESSAGE,
               null,
               null,
               null);
	return n == JOptionPane.YES_OPTION;
}


Example output

This is an example of what is displayed to the user when updating a jar with the updateJar method. What is actually shown and how it is shown depends on how one implements JUpdateListener, this implementation dumped everything that could be displayed to standard output.

Establishing a connection with the server...
Connection established
Downloading the file list.
Checking local files.
Checking com/twmacinta/util/MD5OutputStream.class
Checking com/twmacinta/util/MD5InputStream.class
Checking com/twmacinta/util/MD5.class
Checking com/twmacinta/io/NullOutputStream.class
Checking com/lokorin/net/ServerPostSender.class
Checking com/lokorin/net/ServerFaultException.class
Checking com/lokorin/jupdater/tests/JUpdateTester.class
Checking com/lokorin/jupdater/tests/JUpdateTester$UpdateThread.class
Checking com/lokorin/jupdater/JUpdater.class
Checking META-INF/MANIFEST.MF
Checking com/lokorin/jupdater/JUpdateListener.class
Checking com/twmacinta/util/MD5State.class
Checking for files that are out of date.
2 files are out of date.
Retrieveing the changes made.
Requesting the change log.

Changes: 
[23rd March 2006]
Changed the test client.

Commencing downloads.
Downloading META-INF/MANIFEST.MF
META-INF/MANIFEST.MF has been successfully downloaded.
Downloading com/lokorin/jupdater/tests/JUpdateTester.class
com/lokorin/jupdater/tests/JUpdateTester.class has been successfully downloaded.
All patches have been downloaded.
Patching files.
Patching.
All updates have been completed.
Personal tools