Hello,
I have a pretty basic update mechanism in my applications which gets the job done - but it has it's limitations. It would be nice to have "dev" and "stable" channels, messages to be shown to the users (e.g. if there's a yet unfixed bug with a workaround), going back to a previous version, delta updates only, etc.
So I started to rework the thing as it is but I am simply stuck on the design stage... I can not agree with myself on how it should be done properly. My base theory is one file in one update archive, the application determines what it needs to update and download only those archives to minimize network traffic.
- Put a static JSON with all version information, this gets refreshed each time a version is deployed? This is how the current system works: easy to implement but...
- Just put the ready archive with the new version in the folder and have a service explore the changes and rebuild the static JSON accordingly? This option sounds the best to avoid any lingering files / entries (e.g. archive is placed but changed were not placed in the DB or vice versa) but where the changelog is coming from in this case?
- Store everything in a database and use a PHP script to query and assemble the reply JSON? My PHP knowledge is really limited, so I'd prefer not to have this option. Although, a dynamic list is crucial to minimize traffic (why to download version information for different products, or changelogs for versions below the current one?)
So my question is... are there any readily available update platforms for Delphi (server and client side too) which I can simply implement in my applications and forget about this matter?
I'm also open to suggestions on how the thing should work, on all possible levels:
- Backend. Is a database really needed or overkill?
- Static vs dynamic update definitions. Dynamic is better from many perspectives but does it really worth the extra effort?
- Protocol. Should I really stick to HTTP, or is there a better / create my own?
- How the information is translated and sent, including how the application should know if a new file is added to the distribution?
Cheers!
So I made a basic sketch of my updating mechanism, freely available for anyone to check: https://github.com/aehimself/AEFramework
The only things you'll need are:
- AE.Updater.Updater
- AE.Misc.FileUtils
- AE.Updater.UpdateFile
- AE.Application.Settings
- AE.Misc.ByteUtils
At the moment it is using the System.Zip2 unit but can be reverted easily to Delphi's built in one by changing it to System.Zip in AE.Updater.Updater. It was built and tested on Delphi 11, relies heavily on generics and other built-in components. File versioning is strictly Windows-only... for the time being I found no better way to determine file data than in AE.Misc.FileUtils. That could use a refactor, but as it works for the time being I didn't bother.
To test, have any type of web server ready and decide where you want to put your repository. Let's say our repository will be https://dev.lan/updates, locally available at D:\WWW_root\devlan\updates. I'll make the references accordingly.
- Create a TAEUpdateFile instance and add a test product:
updatefile := TAEUpdateFile.Create;
Try
var fname = ParamStr(0);
updatefile.Product[FileProduct(fname)].URL := 'myproduct';
var pfile := updatefile.Product[FileProduct(fname)].ProjectFile[ExtractFileName(fname)];
pfile.LocalFileName := fname;
var ver = FileVersion(fname).VersionNumber;
var fver = pfile.Version[ver];
fver.ArchiveFileName := ChangeFileExt(ExtractFileName(fname), Format('_%s.zip', [FileVersionToString(ver)]));
fver.Changelog := 'Improved some stuff' + sLineBreak +
'Broke lots of things I don''t yet know about';
fver.DeploymentDate := 1; // Use your favorite UNIX timestamping method, just don't leave it on 0. 0 means undeployed and will not be considered when checking for updates
var ms := TMemoryStream.Create;
Try
updatefile.SaveToStream(ms);
ms.SaveToFile('D:\WWW_root\devlan\updates\update.dat');
Finally
ms.Free;
End;
Finally
updatefile.Free;
End;
Deploying the actual update file is manual for the time being, just zip your .exe, rename it to "Project1_1.0.0.0.zip" (or whatever the original .EXE name and version number is) and copy it to D:\WWW_root\devlan\updates\myproduct. Basically right next to the update file there will be a bunch of folders (one for each product) and inside this folder there will be tons of .zip files, one for each version of each file. Later on this can be used to downgrade as long as the .zip is still available.
Updating is a lot easier:
Var
upd: TAEUpdater;
s, fname: String;
ver: UInt64;
Begin
upd := TAEUpdater.Create(nil);
Try
upd.UpdateFileURL := 'https://dev.lan/updates/updates.dat';
upd.UpdateFileEtag := _etag; // string var on form to minimize web traffic
upd.CheckForUpdates;
_etag := upd.UpdateFileEtag;
s := '';
For fname In upd.UpdateableFiles Do
Begin
s := s + fname + sLineBreak;
For ver In upd.UpdateableFileVersions[fname] Do
s := s + FileVersionToString(ver) + sLineBreak + upd.FileVersionChangelog[fname, ver] + sLineBreak + sLineBreak;
upd.Update(fname);
End;
If Not s.IsEmpty Then ShowMessage(s);
Finally
FreeAndNil(upd);
End;
At the start of your application call TAEUpdater.Cleanup to remove the old version of files - if any.
Todo: Error checking and handling... empty product url will probably result in 404 (unless https://dev.lan/updates//file.zip is a valid URL - didn't check). Files in subfolders aren't yet supported, all will be placed right next to the executable. Files without version information are not yet supported. Hash checking to be implemented, messages to be added, plus a basic demo app to manipulate the update file... in the long run I might replace generics and allow a custom way to download the files so instead of TNetHTTPClient ICS or Indy can be used according to the users taste. Yeah, this is only a skeleton for the time being but it seems to work.
Any suggestion is greatly appreciated!