I've alluded to the patching capabilities of BottomFeeder before, but it seems that I've never actually gone into much detail. I had an email on that this morning, so I figured I should post on it.
The basic capabilities are built into the product - assuming you've left the compiler in your runtime, loading new code in via file-in or parcel loading is easily possible. What I added for my application was this:
- An HTTP based interface, allowing the application to query a server for updates
- an XML based manifest system whereby the client can check what's already loaded versus what's on the server
- An ability to have the updates loaded after download, without the normal development-time dialogs
If you have access to the Public Store Repository, you can check out the package PatchFileDelivery (which should have no dependencies on BottomFeeder - I may have to weed a couple out). The name is something of a misnomer, due to the evolution of the package. When I started, I was delivering small patch parcels, but I moved away from that and on to delivery of new versions of already loaded parcels - it made version control in the runtime a lot simpler. The basic steps look like this:
- Execute an HTTP Query to the server for the XML manifest
- Client compares Manifest to what's loaded
- Client offers any available updates to the user
- Selected updates are downloaded via HTTP
- If requested (and if possible), updates are loaded immediately
So let's take a brief look at those steps. The manifest is a collection of ComponentDefinition objects. That class looks like this:
Smalltalk.Patch defineClass: #ComponentDefinition
superclass: #{Core.Object}
indexedType: #none
private: false
instanceVariableNames: 'parcelName parcelFilename version oldVersion releaseDate vwVersion isPlugin descriptiveName description fileSize allowDynamicLoad '
classInstanceVariableNames: ''
imports: ''
category: 'PatchFileDelivery'
The important pieces of that are the version, parcelName, and allowDynamicLoad. The last one of those is a flag to the client - if it's false, the component in question requires a restart. That comes up when I have an update that does major changes to the UI, for instance. There are ways of dealing with that, but I figured a restart was simpler, and haven't really received complaints. The version is just that - a value derived from Store, the source file repository I use. The name and version are compared to what's loaded to come up with the list for the user.
Once that's figured out, the user's selections are downloaded via HTTP. The only difference between the base HTTP capabilities of VW and what I do is the progress dialog - and that's code that was submitted by Bob, one of our engineers. The change? Additional code in a subclass of the relevant HTTP class to raise notifications of status. Once the code is downloaded, it's either simply saved, or saved and loaded. If it's the latter, the following code gets used to load the new version of the parcel:
actuallyLoadParcelFrom: parcelFile
[[Parcel loadParcelFrom: parcelFile] on: Parcel parcelAlreadyLoadedSignal, CodeStorageError
do: [:ex | ex resume: true]]
on: DuplicateBindingsError
do: [:ex | ex resume]
The first handler catches the "already loaded" signal - which normally raises a dialog. I didn't want that, so I catch it and just have the system resume with true. Which illustrates one of the cool things about Smalltalk exception handling, btw - the ability to rewind back and have the system move along with the correct answer.
The second handler catches transient errors that arise during the load of the new parcel - in some situations, the new version of code can look like a duplicate code binding. That gets resolved as the load continues, so I just catch it and continue. Which again demonstrates the coolness of Smalltalk exception handling.
Once that's done, we have the new code loaded and are ready to run again. What about the next startup? Well, BottomFeeder looks in a known directory for new versions of code, and that's where the upgrade manager saves them. So when the application is started up, it does step (5) from above. And that's it - pretty simple.