Tuesday, November 5, 2024

Adding Automatic Update Support to your Application

We would like to introduce a simple component which retrieves version information from the website and if there is a newer version available it will download update files via HTTP/HTTPS/FTP and run the update.

Abstract

There are many algorithms of automating the process of receiving web updates.
But most of them have the same simple steps:

Connect to the web server and retrieve information about updates available for downloading.
Check whether the version available on the web is up-to-date comparing to the version of your program.
If the web version is up-to-date then download update file(s).
Process downloaded update file(s) with appropriate method: unpacking, copying, executing etc.

Also there are some additional requirements for web update process:

Updated application should have possibility for updating own exe file which it has been run from.
Downloading process should not interfere with main application process and should allow a user to continue his usual work.
Downloading process can be interrupted by a user at any moment and later continued from the last stop place.

Implementation

To implement the first step of web update we need to place some resource (file for example), which contains information about last update, on the website. In our case we use XML-based resource file webupdate.xml. If you like you can use old fashioned TXT file instead of fancy XML format – this is just a matter of your personal preferences.

The main reason why we use XML format is simplicity of parsing information. Since we need to check for matching between your actual program version and version of update available on the web (step 2) we have to save and store information about last update to the local disk.

There are two methods for loading and storing of update information by using the Microsoft Document Object Model (DOM) library:

procedure TclWebUpdate.GetUpdateInfo(); 
var 
   Dom: IXMLDomDocument; 
begin 
   Dom := CoDOMDocument.Create(); 
   Dom.load(FDownloader.LocalFile); 
   UpdateInfo.FURL := Dom.selectSingleNode('updateinfo/update/@url').text; 
   UpdateInfo.FRunParameters := Dom.selectSingleNode('updateinfo/update/@runparameters').text; 
   UpdateInfo.FVersion := Dom.selectSingleNode('updateinfo/update/@version').text; 
   UpdateInfo.FSize := Dom.selectSingleNode('updateinfo/update/@size').text; 
   UpdateInfo.FProductURL := Dom.selectSingleNode('updateinfo/product/@url').text; 
   UpdateInfo.FProductName := Dom.selectSingleNode('updateinfo/product/@name').text; 
   UpdateInfo.FAuthor := Dom.selectSingleNode('updateinfo/product/@author').text; 
   UpdateInfo.FEmail := Dom.selectSingleNode('updateinfo/product/@email').text; 

   Dom.load(LastUpdateInfoFile); 
   ActualInfo.FSuccess := Dom.selectSingleNode('update/@success').text; 
   ActualInfo.FUpdateDate := Dom.selectSingleNode('update/@updatedate').text; 
   ActualInfo.FVersion := Dom.selectSingleNode('update/@version').text; 
   ActualInfo.FFileName := Dom.selectSingleNode('update/@file').text; 
end; 
procedure TclWebUpdate.StoreLastUpdateInfo(ASuccess: Boolean); 
var 
   Dom: IXMLDomDocument; 
   Node: IXMLDomElement; 
begin 
   Dom := CoDOMDocument.Create(); 
   Node := Dom.createElement('update'); 
   Dom.appendChild(Node); 
   Node.setAttribute('file', ActualInfo.FileName); 
   Node.setAttribute('version', ActualInfo.Version); 
   Node.setAttribute('success', ActualInfo.Success); 
   Node.setAttribute('updatedate', ActualInfo.UpdateDate); 
   Dom.save(LastUpdateInfoFile); 
end; 

Here are the ActualInfo and the UpdateInfo properties which represent structures corresponding to current application version and version of update available on the web. If the downloading process has been completed successfully the update information has been saved to the local file (lastupdate.xml by default). So if you want to run web update one more time using our demo programs (demo1src.zipdemo2src.zip) you may delete this file or modify the version parameter to the previous value (in our case just replace the version attribute with the “1.0” value).

After the whole update info is received we need to compare versions and determine whether should we download and finally apply the update available on the web. If you are using simple enumeration for your updates (like 1, 2, 3 …. ) then the comparing method looks very simple:

function TclWebUpdate.CheckUpdateVersion(const ANewVersion, ActualVersion: string): Boolean; 
begin 
   Result := StrToIntDef(ANewVersion, 0) > StrToIntDef(ActualVersion, 0); 
end;

This function returns True if the web version is up-to-date and needed to be downloaded.
In case of using the standard version format which consists of the following parts: major version.minor version.release.build (2.5.0.33 e.g.) then you need to perform some additional calculations.

This function converts each component version to the corresponding tens place:

function MakeCompleteNumber(S: string): Integer; 
var 
   i, Cnt, Len, OldPos: Integer; 
begin 
   S := Trim(S); 
   Result := 0; 
   Len := Length(S); 
   Cnt := 0; 
   OldPos := Len + 1; 
   for i := Len downto 1 do 
   begin 
      if (S[i] = '.') then 
      begin 
         Result := Result + StrToIntDef('0' + system.Copy(S, i + 1, OldPos - i - 1), 0) * Round(Power(1000, Cnt)); 
         Inc(Cnt); 
         OldPos := i; 
      end else 
      if (i = 1) then 
      begin 
         Result := Result + StrToIntDef('0' + system.Copy(S, i, OldPos - i), 0) * Round(Power(1000, Cnt)); 
      end; 
   end; 
end; 

After all checks completed and web update is up-to-date then the update file downloading process begins. In order to perform the downloading in the background mode without interfering with main application process we use the Clever Downloader component from the Clever Internet Suite library. Following code sample shows how easy downloading process can be implemented:

... 
FDownloader: TclDownloader; 
... 
FDownloader := TclDownloader.Create(nil); 
FDownloader.OnProcessCompleted := DoOnProcessCompleted; 
... 
procedure TclWebUpdate.StartDownloading(); 
begin 
   FDownloader.URL := UpdateInfo.URL; 
   FDownloader.UserName := UserName; 
   FDownloader.Password := Password; 
   FDownloader.Start(True); 
end; 

Since the Start downloader method is called with Async parameter which equal to True the component returns control immediately after calling of this method. So to find out the time when the downloading process is completed we use the OnProcessCompleted downloader event.

To process downloaded update file(s) we implement simple algorithm with using of ShellExecute WinApi function. So if you are about to extract archived update files you can simple call this function as follows:

ShellExecute(Application.Handle, 'open', 'webupdate.zip', nil, nil, SW_SHOWNORMAL); 

This function opens your archive with application which assigned as default one for such a file type. If you have WinZip installed and it is assigned as default application for the ‘*.zip’ files then the webupdate.zip archive will be opened and unpacked. You can specify your own method of handling and applying updates to suit your own needs.

Full component source code is available at component.zip and allows you to customize the executing process with specifying the run application and its run parameters. This can be useful if you want to run a custom application with its specific command line parameters.

It’s well known that to replace running application you have to terminate it before. You can make the update process more comfortable and include the code which closes the application just after ShellExecute call:

... 
var 
CanTerminate: Boolean; 
... 
if NeedTerminate then 
begin 
   CanTerminate := True; 
   DoTerminating(CanTerminate); //

raises an event which user can overwrite in order to 
perform some custom actions on application terminating request or cancel closing

   if CanTerminate then 
   begin 
      Application.Terminate(); 
   end; 
end; 

Requirements

You must have installed the Internet Explorer version 3.0 or higher on the client computer.

This code is constantly improved and your comments and suggestions are always welcome.

Please write us at info@clevercomponents.com

With best regards,
Sergey S
Clever Components team.
www.clevercomponents.com

For your convenience we provide following downloads:

component.zip – an archive with WebUpdate component. Delphi 5, 6, 7 packages are available at this moment.
demo1src.zip – an archive with source code for the first sample.
demo2src.zip – an archive with source code for the second sample.
demo1exe.zip – an archive with compiled executable file for the first sample.
demo2exe.zip – archive with compiled executable file for the second sample.
webupdate.xml – web resource which provides the update information.
Clever Internet Suite downloads – demo version of Clever Internet Suite.

Sergey S. is a member of the Clever Components Team. The history of our site began from an Interbase DataPump and the original idea was to provide Delphi / Borland C++ and Interbase developers with high quality consultations. Nowadays we have many satisfied customers and can offer some excellent tools and utilities. We wish to maintain the highest standards in our service and keep our customers happy. Please feel free to contact us at info@clevercomponents.com

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles