Autoupdate Explained

Whatever type of application you write, it's most likely that you will need to update it during the course of its life. This might be even more frequent with plugin-based systems written with frameworks like Hydra, as they don't require complete re-deployment.

Hydra introduces a set of components that allow you to enhance your applications with remote auto-update features. This article will explain how they work.

Auto-update at a glance

Hydra auto-updating works in a very simple manner: when your application starts, a specialized component (THYAutoUpdateClient) will issue a small request to a server by sending an UpdateID string. This UpdateID will identify the last update the client received and the server will use it to see if there's anything newer that needs to be sent back to the client. This part of the update process is required to ensure that your application can start with as little overhead as possible. Although it is possible to skip this and always do a complete check, most applications will probably benefit from the option.

If the server responds saying there's a new update, the client will send a list of files and the server will respond with a second list (a delta), containing information about the files that need to be downloaded and updated.

The client download process will then start and, once complete, the new files will overwrite the old ones.

Autoupdate in more detail

When the client downloads its updates, it will generate some temporary files in the directories where these files are meant to go.

For instance, if your main executable file is called TestApp.exe and a new version is being downloaded, the Hydra auto-update module will create a $$TestApp.exe file in the same directory and will keep fetching data into it until the download is complete. The way these files are named is under your control, so you could decide to replace "$$" by any prefix to your liking.

This approach offers several benefits: first of all, you can immediately see what files are being downloaded by just looking at the directories, instead of having to look into hidden or temporary directories that might or might not resemble the application's directory structure. Removing temporary files also becomes an easy task and you could even decide to remove just a part of the download. There's no dependencies on registry entries or temporary *.ini files either.

But probably the most important feature is that the Hydra auto-update framework allows for resumable downloads. This means that you can programmatically interrupt a download and restore it whenever it's most convenient, without affecting any of the existing files.

The THYAutoUpdateClient component

The THYAutoUpdateClient is the focused component in Hydra's component tab:

THYAutoUpdateClient inherits from the standard TRORemoteService component and extends it by adding a few extra properties, which allow you to control where the updates are downloaded from and how to send information about your current files to the server.

The following screenshot shows the TAutoUpdateClient's properties:

As you can see, it is connected to a standard RemObjects SDK channel and message component and has its ServiceName property set. Let's take a look at the other properties and events in more detail:

Property Description
DownloadBufferSize (integer) Each file download is incremental and this property determines the size of each downloaded chunk. Events such as OnDownloadProgress can be used to display the download status and update progress bars.
TemporaryFileNamePrefix (string) Allows you to control how to prefix the names of your temporary files, as shown in the section Auto-update in more detail.
ServiceName As with any TRORemoteService components, this property refers to the name of the remote service. It's important to notice that these services need to inherit from a specialized type of service called THYAutoUpdateService (more about this later), in order to function properly.
FileSearchInfo This property determines how to build the list of files to be sent to the server. As this property is also critical server side, its description is postponed until we look at that.

The THYAutoUpdateClient component offers a wide set of events that you can use to update your application's user interface to show progress status and more. You have events that allow you to track when an update download starts, when the download finishes as well as events that inform you of resumes or download progress:

Now let's take a look at how simple it is to use this component. This is a screenshot of the "AutoUpdate" client sample that ships with Hydra:

The following code is triggered when the "Check for Updates" button is clicked:

function TAutoUpdateClientData.CheckForUpdates : boolean;
var currupdateid, userdata : AnsiString;
begin
  result := FALSE;
  fCanResume := FALSE;

  FreeAndNIL(fUpdates);
  try
    // Checks to see if there are any updates. If the download is forced, it ignores this step and continues to
    // get the actual list of updates required
    if not ForceDownload and (AutoUpdateClient.VerifyUpdateStatus(fLastUpdateID, currupdateid, userdata)=usNoUpdates)
      then Exit;

    fLastUpdateID := currupdateid;
    
    // Retrieves the list of updates from the server and stores it in the
    // private field "fUpdates" (THYFileUpdateInfoArray) by using the property
    // Updates. Updates has a property setter called SetUpdates which updates the
    // listview in the application's mainform
    Updates := AutoUpdateClient.GetUpdatesInfo(fLastUpdateID, fCanResume);
  finally
    result := (Updates<>NIL) and (Updates.Count>0);
  end;
end;

As you can see, only a few lines are required.

After this method is executed, and assuming that updates are actually present on the server, the application's screen will change to something similar to this:

We can now click the "Download Updates" button which is associated to this code:

function TAutoUpdateClientData.DownloadUpdates : boolean;
begin
  with AutoUpdateClient do begin
    result := DownloadUpdates(Updates);
    if result then begin
      result := FinalizeUpdate(Updates);

      // Finally updates the LastUpdateID in the ini file
      UpdateLastUpdateID;
    end;
  end;
end;

This is pretty much it! This code (and the events the component fires) allows us to update progress bars and everything else with minimal effort.

Auto-updates, server side

If the code required for the client side looks easy, then the server side code is even easier as there is no need for any, unless you want to customize your server to handle complex update scenario.

As with Data Abstract-based services, the starting point for inheriting a lot of pre-built functionality is to reference an existing RODL file, in this case HydraAutoUpdate.RODL, which ships with Hydra.

This RODL defines the several types needed to exchange file information and to define a base service, THYAutoUpdateService, with the following interface:

{ IHYAutoUpdateService }
IHYAutoUpdateService = interface
  ['{39330BB5-9B50-41B5-AC92-854FC48DD748}']
  function VerifyUpdateStatus(const LastUpdateID: AnsiString; out CurrentUpdateID: AnsiString;
  out UserData: AnsiString): THYUpdateStatus;
  function GetUpdatesInfo(const UpdateID: AnsiString; 
  const ClientFiles: THYFileUpdateInfoArray): THYFileUpdateInfoArray;
  function GetFileData(const UpdateInfo: THYFileUpdateInfo; const Offset: Integer; 
  const Count: Integer; out Data: Binary): Integer;
end;

Once you reference the HydraAutoUpdate.RODL, you'll be able to create auto-update services by just inheriting them from the base service as shown in this screenshot:

That's pretty much it. After compiling your server project, you will have a new service built which inherits from THYAutoUpdateService and exposes all the properties and events you will need to handle auto-updates and send files to your clients.

You can see these properties and events in the following screenshot:

Additional types included in the HydraAutoUpdate.RODL are:

  { THYFileUpdateInfo }
  THYFileUpdateInfo = class(TROComplexType)
  [..]
  published
    property Operation:THYUpdateOperation read fOperation write fOperation;
    property FileName:AnsiString read fFileName write fFileName;
    property Size:Integer read fSize write fSize;
    property TimeStamp:DateTime read fTimeStamp write fTimeStamp;
    property Version:AnsiString read fVersion write fVersion;
    property DownloadProgress:Integer read fDownloadProgress write fDownloadProgress;
    property UserData:AnsiString read fUserData write fUserData;
    property Status:THYFileUpdateStatus read fStatus write fStatus;
  end;


  { THYFileSearchInfo }
  THYFileSearchInfo = class(TROComplexType)
  [..]
  published
    property SearchMask:AnsiString read fSearchMask write fSearchMask;
    property Recursive:Boolean read fRecursive write fRecursive;
    property TargetClientDirectory:AnsiString read fTargetClientDirectory write fTargetClientDirectory;
  end;

We'll now have a look at the property FileSearchInfo, which is a key property for both server and clients and is a collection of the THYFileSearchInfo declared above.

FileSearchInfo

The following screenshot shows an example of a typical setting for the FileSearchInfo property:

This property stores the list of directories that are used by servers and clients to generate their file lists. In this specific case, these settings will tell the server to look into the directory $APPLICATION\Update.1\ApplicationDir\*.* when comparing the list of files the client contains under the $APPLICATION directory (see the TargetClientDirectory property).

It will also match the files contained in $APPLICATION\Update.1\WindowsDirUpdates\*.* when comparing the files the client claims to have in its $WINDOWS directory.

To make this a little easier to understand, consider what we said in the section "Auto-update at a glance". When the client sends its list of files, the server will receive a list of THYFileUpdateInfo elements with filenames like $APPLICATION\TestApp.exe and $WINDOWS\MyCustomDLL.DLL. Because of the association (SearchMask) of $APPLICATION\Update.1\ApplicationDir\*.* with (TargetClientDirectory)$APPLICATION, the server knows that a new TestApp.exe might be contained in $APPLICATION\Update.1\ApplicationDir\. For the same reason, an update for MyCustomDLL.DLL will be stored under $APPLICATION\Update.1\WindowsDirUpdates.

Notice the use of the macro $APPLICATION, which points to the application's .exe file directory. Other macros are $SYSTEM and $WINDOWS, which point to the relative system directories.

What about .Net

Currently we don't provide separate components for .Net, but since the auto-update server is a standard Remoting SDK server, you still can gain access to this service.

In order to do this, you need to create a "Windows Forms Application" and add two RODL files to your project. Click on the RemObjects SDK|Import RemObjects SDK Service:

The first RODL is the HydraAutoUpdate.RODL. This file holds the definition for the auto-update service and can be found in the source directory of the Hydra for Delphi library. The second file is the AutoUpdateServerTestLib.RODL, which can be found in the AutoUpdate sample folder. This file holds the definition of our sample auto-update service.

Add the IpHttpClientChannel named "clientChannel" and a BinMessage named "message" to the main form and set the TargetUrl channel to your service location, i.e. http://127.0.0.1:8099/bin.

Add two buttons, named "buttonCheck" and "buttonDownload" and a list view "listUpdates". The screenshot below shows how your application should look like:

Now add the code that will perform the check for updates:

    private THYFileUpdateInfo[] ServerFiles = null;
    private IAutoUpdateTestService fAutoUpdateService = null;

    public IAutoUpdateTestService AutoUpdateService
    {
      get
      {
        if (fAutoUpdateService == null)
          fAutoUpdateService = CoAutoUpdateTestService.Create(message, clientChannel);

        return fAutoUpdateService;
      }
    }

    private THYFileUpdateInfo[] SearchForClientFiles()
    {
      DirectoryInfo Dir = new DirectoryInfo(Application.StartupPath);
      FileInfo[] Files = Dir.GetFiles("*.*", SearchOption.AllDirectories);
      THYFileUpdateInfo[] ClientFiles = new THYFileUpdateInfo[Files.Length];

      for (int i = 0; i < Files.Length; i++)
      {
        ClientFiles[i] = new THYFileUpdateInfo();
        ClientFiles[i].FileName = Files[i].FullName.Replace(Application.StartupPath, "$APPLICATION");
        ClientFiles[i].Size = Convert.ToInt32(Files[i].Length);
        ClientFiles[i].TimeStamp = Files[i].CreationTime;
        ClientFiles[i].Operation = THYUpdateOperation.uoClient;
      }

      return ClientFiles;
    }

    private int CheckForUpdates()
    {
      string CurrentUpdateId = "";
      string UserData = "";

      if (AutoUpdateService.VerifyUpdateStatus("", out CurrentUpdateId, out UserData) == THYUpdateStatus.usNoUpdates)
        return 0;

      ServerFiles = AutoUpdateService.GetUpdatesInfo(CurrentUpdateId, SearchForClientFiles());
      return ServerFiles.Length;
    }

    private void ShowServerFiles()
    {
      listUpdates.Items.Clear();

      foreach (THYFileUpdateInfo file in ServerFiles)
      {
        ListViewItem Item = listUpdates.Items.Add(Path.GetFileName(file.FileName));
        Item.SubItems.Add(file.Size.ToString());
        Item.SubItems.Add(file.TimeStamp.ToString());
      }
    }

    private void buttonCheck_Click(object sender, EventArgs e)
    {
      if (CheckForUpdates() == 0)
        MessageBox.Show("There are no updates available on the server");
      else
      {
        ShowServerFiles();
        buttonCheck.Enabled = false;
        buttonDownload.Enabled = true;
      }
    }

Please note that this code will only handle files that are placed in an application directory. Now, you can add the code that downloads files from the update server:

    private string ExpandFilePath(THYFileUpdateInfo file)
    {
      string path = file.FileName.Replace("$APPLICATION", Application.StartupPath);
      if (!Directory.Exists(Path.GetDirectoryName(path)))
        Directory.CreateDirectory(Path.GetDirectoryName(path));

      return path;
    }

    private void DownloadFile(THYFileUpdateInfo file)
    {
      if (!file.FileName.Contains("$APPLICATION"))
        return;

      Binary Data = new Binary();

      while (Data.Length != file.Size)
      {
        Binary Buffer;
        AutoUpdateService.GetFileData(file, Convert.ToInt32(Data.Position), 1024, out Buffer);
        Data.Write(Buffer.ToArray(), Convert.ToInt32(Buffer.Length));
      }

      using (FileStream Stream = new FileStream(ExpandFilePath(file), FileMode.OpenOrCreate))
      {
        Data.SaveToStream(Stream);        
      }
    }

    private void buttonDownload_Click(object sender, EventArgs e)
    {
      foreach (THYFileUpdateInfo file in ServerFiles)
      {
        DownloadFile(file);
      }
      MessageBox.Show("The updates have been installed sucessfully");
      buttonDownload.Enabled = false;
    }

And now it's done, you can compile and run the project.

Conclusion

Hydra's auto-update feature provides the means to update parts (e.g. plugins) of an application without a full deploy. Little or no coding is required even for support of resumable downloads.