Code for this
article:
http://www.west-wind.com/presentations/wwCodeUpdate/wwCodeUpdate.zip
Applications change - imagine that! Whenever we as developers think an application is complete, along come the customers (or sometimes ourselves) and think up a few more features to add to that 'perfect' application. Other times we find out that the application wasn't so perfect after all and a few bug fixes are required. In this article Rick discusses an efficient way to update your Visual FoxPro based desktop applications via the Internet by presenting a class that handles version checking against an Internet based resource and updating of a running Exe file based application. Along the way he'll also discuss some related issues of dealing with application updates that are just as important as the physical process of updating the code.
Updating an installed application is not something that most of us as developers look forward to. Updating a desktop application, especially one that is distributed in many locations is not a trivial task. If you've ever distributed a vertical application to thousands of users only to find a small but critical bug in the application you probably are familiar with that sinking feeling in your stomach of trying to find and notify all of your customers of the problem and then providing a fix to them.
There are a number of issues involved in updating an application. There's the physical issue of how to get an update of the application to the user. But in addition there's how to check for new versions and how to actually install the update on the client machine. Rarely does an update require only a single Exe file to be updated, but often support files including data files and more importantly data file structures must also be updated.
This article focuses on the physical process of checking for
new versions and updating an application over the Internet and I'll touch
briefly on some of these related issues that I've run into in relatively small
scale desktop applications that have
Before I
At first blush this process seems pretty simple, but there is a fair amount of activity and an external application involved in making this all happen. The typical scenario for an application works something like this: The application starts up and as part of the start up process checks for a version number which is retrieved from the Web via an XML file that contains the version number as well as download information for the update file (URL, size of the download, a message to the user). It matches this retrieved version number with the internal version number of the application and compares the two. If the version available online is newer the user is prompted to download the update. The application then downloads the update file from the Web using the URL retrieved from the XML version resource on the Web into a specified local directory.
At this point the running application must shut down and launch another application that can update the running application and restart it. This is required because the running Exe file is locked and can't be overwritten while running. So, the current application quits and the update application starts. The update application handles updating the Exe file by unzipping the update file into the specified directory and replacing the original application Exe and any other files that need updating. It then restarts the original application, which when re-started is up to date and passes by the version comparison from the Web resource XML file.
If there are additional updates to be performed once the application files have been updated such as structure changes, updating or inserting data or moving files around, those can be handled the first time the application restarts. This requires that the application maintains an updateable version number in a configuration file to compare the applications compiled version and the data version and comparing the two on startup. If they don't match the specified code is fired to perform the update or custom actions followed by updating the data version number.
The wwCodeUpdate class handles most of this update process with a few property settings and simple method calls. The property settings determine where the update is to be performed and what files to access from the Web and where to install them to. In addition it uses an XML file on the server as the version and link resource used to download the update file which makes it possible to have multiple update versions online at the same time (for example for multi-step updates from ancient versions).
Before I
Listing 1 � Using the wwCodeUpdateClass in a client application
* SampleAppForm :: CheckForNewVersion()
LOCAL loUpdate, loAPI, lcURL, lnVersion
*** Retreive the URL to use for version checks
*** Example: http://www.west-wind.com/files/updates/wwReader_update.xml
loAPI = CREATEOBJECT("wwAPI")
lcUrl =
loAPI.GetProfileString(FULLPATH("wwReader.ini"),"
loUpdate=CREATEOBJECT("wwCodeUpdate")
*** Url where to check for new versions (XML file)
loUpdate.cVersionUrl = lcUrl
loUpdate.cVersionType = "N"�� && Numeric Type
*** Name of the application's Exe file to hotswap
loUpdate.cExeFile = "wwReader70.exe"
*** Returns the current available version from the XML file
*** Sets properties of the loUpdate object with the XML
info
lnVersion = loUpdate.GetVersion
�
*** If no version we couldn't get the info from
*** the Web Server
IF lnVersion = 0
�� *** Couldn't retrieve version info � just exit
�� *** You can also display error info: loUpdate.cErrorMsg
�� RETURN
ENDIF
*** Compare the version number against
*** the app's version number - here a property of the form
*** Usually you'd use a .H setting, database field or
Config object
IF lnVersion > THISFORM.nVersion
�� *** Display
message for the user and ask to download
�� IF
MessageBox(loUpdate.cUserMessage + CHR(13) + CHR(13) +;
����������������
"Would you like to download and install it now? (" + ;
����������������
LTRIM(TRANSFORM(loUpdate.nFileSize,"999,999,999")) + ;
����������������
"kb)",32+4,"West Wind Technologies") = 6���������������
����� IF
!loUpdate.DownloadUpdate()
��������
MessageBox(loUpdate.cErrorMsg,48,"Download Error")
����� ELSE
� �������*** Shutdown VFP
�������� CLEAR
EVENTS
�������� RELEASE
THISFORM
��������
�������� *** Run the
generic CodeUpdate Exe and quit application
�������
loUpdate.RunUpdateExe(;
������������������ [WSCRIPT
CodeUpdate.vbs "wwReader70.exe" "] + ;
��������� ���������SYS(5) + CURDIR() +;
������������������
[codeupdate\wwReader_Update.exe /auto ] + ;
������������������ SYS(5) + CURDIR() +
[" ] + ;
������������������
["West Wind Message Reader"])������ ENDIF
�� ENDIF
ENDIF
RETURN
This code starts by creating the wwCodeUpdate class and retrieving the URL to check for updates from an INI file. This value can come from anywhere � a config table, the registry, an XML file etc. � but I like to have this value configurable in some easy way so that I can change the location of the file for different versions or while testing against a local server. This URL points at an XML file on the Web Server that contains the version info for the latest available version as shown in Listing 2.
Listing 2 - XML configuration file that contains update information
<?xml version="1.0"?>
<codeupdate>
��� <version>4.21</version>
��� <fileurl>http://www.west-wind.com/files/updates/wwreader_update.exe</fileurl>
��� <size>372</size>
��
<usermessage>Version 4.21 of the West Wind Message Reader
is now available for download.</usermessage>
</codeupdate>
The GetVersion
|
Configurable Property |
Function |
|
cVersionUrl |
The URL to the XML file that
contains the version info |
|
cVersionType |
The FoxPro type of the
version. Can be either "N" or "C" (default) |
|
cExeFile |
The filename of the Exe
file. Should be just the file name (ex: SampleApp.exe) |
|
cApplicationName |
Used for prompts and dialogs
when updating the application. Defaults to "the application" |
|
cDownloadPath |
The path where to download
and unzip the update file. Typically use a relative path for this (ex: .\codeupdate\ (default)). The path is created if it doesn't
exist |
Only the first three are required settings. After the GetVersion
|
Downloaded Property |
Function |
|
vOnlineVersion |
Character or String value
that holds the version number retrieved. Depends on the setting of the cVersionType property. |
|
cOnlineFileUrl |
The URL to the update file to be downloaded. Typically this is an Exe file
containing zipped content (WinZip Executable) |
|
vOnlineMinVersion |
Minimum version that this
update can be applied to. This can be set and checked to avoid updating
versions that are too old. |
|
nFileSize |
The size of the update file
in kilobytes |
|
cUserMessage |
Message to display to the
user before downloading the update. This message |
The method returns the version number depending on the cVersionType property setting. If the version retrieval
fails - due to a connection problem most likely � the call to GetVersion

Figure 1.1 � Displaying
the download message from the Web Resource's <usermessage>
entry.
If the user decides to download, a call to the DownloadUpdate()
method handles downloading of the update file. The code in the class handles
the HTTP connection, displaying a progress dialog (requires the Common Dialogs
OCX (MSCOMCTL.OCX) and creating the download file and directory if necessary. Figure
1 shows what this dialog looks like. When DownloadUpdate() is complete you should check for errors by checking the
return value of this method, and if .F. checking the cErrorMsg
property.

Figure 1.2 � the progress
dialog that displays while downloading the update file.
After the download has completed you will now have an update file sitting in the directory you specified in the cDownloadPath property. wwCodeUpdate assumes that this file self extracting Zip file that contains the Exe file plus any other files required to update the application. The Exe file should unzip into the application root directory as is and if you include subdirectories make sure they are included as relative directories.
Up to this point all code is called from your main application which makes method calls into the wwCodeUpdate class. Once the update file has been downloaded your application has to exit in order to swap the application files. To do so you can call the RunUpdateExe() method, which runs the specified Exe or Script and then quits the current application. As you can see in Listing 1 you should perform any application cleanup before calling this method, which after running the Exe file asynchronously quits your application. The parameter to the method is the OS command line to the update application, which in this case is Windows Scripting Host VBScript file � CodeUpdate.vbs, which is a generic script that runs the self extracting Zip file and then restarts the application. Codeupdate.vbs is included in download archive for this article.
The script itself takes three parameters � the name of the Exe file, the full command line for the self extractor which includes the unzip path, and the name of the application for display in the update dialogs. These parameters and the full command line for running the script is what you see in the call to RunUpdateExe() above.
The wwCodeUpdate class takes care of all the work required to perform the version checks and the HTTP access to download the XML version file and the actual update file from the Web site.
To get an idea of how the wwCodeUpdate
class works let's look at a few key methods. The GetVersion
Listing 3 � The GetVersion
FUNCTION GetVersion
LOCAL loIP, lcVersionType, lcXML, lnSize, lcVersion
THIS.SetError()
lcVersionType = THIS.cVersionType
IF ISNULL(THIS.oHTTP)
�� loIP = THIS.CreateHTTPClient()
ELSE
�� loIP = THIS.oHTTP��
ENDIF
*** Don't want a dialog on the version check
loIP.lShowDialog = .F.
lcXML = loIP.HTTPGet(This.cVersionUrl)
*** Check for errors
IF loIP.nError # 0
�� THIS.SetError(loIp.cErrorMsg)
�� RETURN IIF(lcVersionType = "C","",0)
ENDIF
IF lcXML <> "<?xml"
�� THIS.SetError("Missing or invalid XML returned from server")
� �RETURN IIF(lcVersionType = "C","",0)
ENDIF
lcVersion = Extract(lcXML,"<version>","</version>")
IF EMPTY(lcVersion)
�� THIS.SetError("No version number found in XML")
�� RETURN IIF(lcVersionType = "C","",0)
ENDIF
THIS.vOnlineVersion = IIF(lcVersionType="C",lcVersion,VAL(lcVersion))
lcVersion = Extract(lcXML,"<minversion>","</minversion>")
IF !EMPTY(lcVersion)
�� THIS.vOnlineMinVersion = IIF(lcVersionType="C",lcVersion,VAL(lcVersion))
ENDIF
THIS.cOnlineFileUrl = Extract(lcXML,"<fileurl>","</fileurl>")
THIS.nFileSize = VAL( Extract(lcXML,"<filesize>","</filesize>") )
THIS.cUserMessage = Extract(lcXML,"<usermessage>","</usermessage>")
*** Save the XML just in case
THIS.cXML = lcXML
IF lcVersionType = "C"
�� RETURN lcVersion
ENDIF
RETURN THIS.vOnlineVersion
This simple code retrieves the version info and deals with
the XML content by using the wwHTTP o
The next method called is DownloadUpdate() which is used to retrieve the Update file from the Web. It too uses wwHTTP, this time with its support to directly download content to a file. Listing 4 shows the code to DownloadUpdate().
Listing 4 � Downloading the update file from the Web site
FUNCTION DownloadUpdate
LPARAMETER llCheckforExistingVersion
LOCAL loIP, lcData, lnSize, loUrl
IF ISNULL(THIS.oHTTP)
�� loIP = THIS.CreateHTTPClient()
ELSE
�� loIP = THIS.oHTTP
ENDIF��
loIP.lShowDialog = THIS.lShowDialog
*** Break down the URL into its components
loUrl = loIP.InternetCrackUrl(THIS.cOnlineFileUrl)
IF ISNULL(loUrl)
�� RETURN .F.
ENDIF
IF loIP.HTTPConnect(loUrl.cServer,"","",;
������������������� IIF(lower(loUrl.cProtocol)="https",.T.,.F.)) # 0
�� THIS.SetError(loIP.cErrorMsg)
�� RETURN .F.
ENDIF��
*** Create a temporary directory if it doesn't exist
IF !ISDIR(THIS.cDownloadPath)
�� MD (THIS.cDownloadPath)
ENDIF��
*** Get the file name to download to
lcTFile = THIS.cDownloadPath + JUSTFNAME(STRTRAN(THIS.cOnlineFileUrl,"/","\"))
lcData = ""
lnSize = 0
*** Actually download the data � fires progress events
IF loIp.HTTPGetEx( TRIM(loUrl.cPath),@lcData,@lnSize,,lcTFile) # 0
�� THIS.SetError(loIP.cErrorMsg)
�� RETURN .F.
ENDIF��
RETURN .T.
DownloadUpdate() uses the lower level wwHTTP
members HttpConnect and HTTPGetEx
to download the file. The last parameter of the HTTPGetEx
call allows specification of a filename which allows wwHTTP
to download directly to file instead of returning data into a string first. This
is important for this application because the download file could potentially
be very large and the file might not fit into memory �
After the download is complete the call to UpdateExe() wraps the Exe command line for running the update Exe. This method runs whatever you pass it using the RUN command then simply QUITs the application.
Listing 5 � The RunUpdate Exe runs the external Exe and quits the app
FUNCTION RunUpdateExe
LPARAMETER lcUpdateExe
*** Run external program to copy in the files
lcParms = [RUN /n1 ] + lcUpdateExe�
&lcParms
*** Required if READ EVENTS IS ACTIVE
*** otherwise Exe won't release
CLEAR EVENTS
ON ERROR *
RELEASE ALL
CLEAR ALL
QUIT
RunUpdate() simply runs an external operating system application or script that is responsible for unpacking the self-extracting Zip file which replaces the running application and then restarting the original application. This external application is required as you cannot update an Exe file that is currently running. The parameter passed into this method is the full command line that runs the swapping application.
This external application can take many different forms. You can use a VFP application and a small stub program is actually provided in the wwCodeUpdate.prg file. To create the swapping EXE simply add wwCodeUpdate.prg to a project and compile it into an Exe file. The mainline program handles swapping exe's by calling the SwapExes method. You can take a look at this code in the provided source code to see how it works � it's only a few lines of code.
Using a VFP application for the swapping application is useful as you can control the user interface and potentially run any other code to update or massage the data copied. But there is one problem with this scenario of using a VFP application for this update: You cannot swap your VFP application if the VFP runtime files also need to be updated as those are required to run the swapping application which itself uses the VFP runtimes. This may not be a problem right now, but in the future as your application revs you might want to have the runtime update available as an option also without having to physically ship or install an update.
To address that particular issue you can use an external application totally unrelated to VFP. For example, one could write a small C++ executable that handles the swapping. Or even simpler � use a Windows Script file or batch file to perform the task. The code shown in Listing 6 shows a VBScript Windows Scripting Host file that performs this task.
Listing 6 � Using a Windows Scripting Host script to swap Exes
* CodeUpdate.vbs
if WScript.Arguments.Length < 3 then
��
WScript.Echo("Invalid number of parameters passed." + vbcrlf +
_
��������������� "Syntax: WSCRIPT CodeUpdate.vbs " + _
��������������� <ExeFile>
<UpdateFile> <ApplicationName>")
�� Wscript.Quit(1)
end if
lcExe = WScript.Arguments(0)
lcUpdateExe = WScript.Arguments(1)
lcAppName = WScript.Arguments(2)
Set oShell = Wscript.CreateO
'*** Wait for 5 seconds for app to shut down
lnResult = oShell.Popup("Waiting for application to shut down...",_
�����������������������
5,lcAppName + "Update",64)
'*** Run the Update Exe
oShell.Exec(lcUpdateExe)
lnResult = oShell.Popup("Updating Exe File... ",_
����������������������� 5,lcAppName +
"Update",64)
'*** Check that the Exe exists
Set oFile = CreateO
if not oFile.FileExists(lcExe) then
�� lnResult =
oShell.Popup("Updating Exe File......�
",_
�������� ������������������5,lcAppName +
"Update",64)
end if
oShell.Exec(lcExe)
To call this script you have to call it with a full commandline to RunUpdateExe():
�loUpdate.RunUpdateExe([WSCRIPT
CodeUpdate.vbs "wwReader70.exe" "] + ;
������������������
SYS(5) + CURDIR() +;
������������������
[codeupdate\wwReader_Update.exe /auto ] + ;
������������������ SYS(5) + CURDIR() +
[" ] + ;
������������������
["West Wind Message Reader"])
CodeUpdate.vbs is fairly generic and you pass it three parameters:
�
A typical OS commandline looks like this (all on one line separated by spaces):
CodeUpdate "SampleApp.exe"
�������������������� "d:\sampleapp\codeupdate\sampleapp_update.exe
/auto d:\sampleapp"
�������������������� "Sample Application"
The script takes these parameters and runs the self extracting zip and then restarts the application. Notice that the script has a few wait states in it using the Popup method with a 5 second delay � it first waits to make sure the application has shut down, then waits after the Exec() method that runs the self-extractor for it to complete. You can tweak the script as needed for longer timeframes if the Zip file should be huge or the application takes much longer to shut down.
This script file (or the VFP Codeupdate.exe) is generic and can handle updates for most applications as is. You can customize either the script or the VFP code, but I think you'll want to leave the swapping application as generic and simple as possible and leave any real data updates up to the main application. This way you can easily reuse the update code.
Most application updates require additional work to be performed after the actual Exe file update. Typically this involves updating structures of data files or copying additional files or massaging existing data into new formats etc. The idea is that you want your Exe to run this update routine only the first time the Exe is started under the new version.
One way to do this is to store an update version number in some data source that is updateable in addition to a version number that is part of the application. In the sample application included with this article (SampleApp.scx) there is a version number hardcoded into the main application form (nVersion) and there's a current data version number stored in a configuration INI file (the same one that houses the version check URL). The INI file looks something like this:
[
Version=1.1
VersionUpdateUrl=http://localhost/files/updates/sampleapp.xml
LastVersionCheck=
On startup the application can read the current data version value from this INI file, then compare that with the version of the application. For example, Listing 7 shows the UpdateData() method of the sample form that checks for a version change read from the INI file.
Listing 7 � Checking for a data version change after the Exe has been updated
*** Retreive the URL to use for version checks
*** This is an XML file
loAPI = CREATEOBJECT("wwAPI")
lnDataVersion = VAL(loAPI.GetProfileString(;
������������������� FULLPATH("SampleApp.ini"),;
������� ������������"Main",;
������������������� "Version") )
�������������������
IF lnDataVersion = THISFORM.nVersion
�� *** Nothing to do
�� RETURN
ENDIF
*** Code to perform data updates
WAIT WINDOW "Data version is not up to date. Updating..." TIMEOUT 5
*** And write out the updated version number
*** but only after this process completes
loAPI.WriteProfileString(;
������������������� FULLPATH("SampleApp.ini"), ;
������������������� "Main", ;
������������������� "Version",;
������������������� LTRIM(TRANSFORM(THISFORM.nVersion,"99.99")))
�������������������
RETURN�������������������
You would put any data update code into the body of this
method or call out to another method or o
One of the most common things my applications do are structure changes for data files. If you're using a database container you can use the ALTER TABLE command, but if you have updates coming from multiple different versions this gets to be messy in many cases. So rather than trying to keep all those ALTER TABLE statements properly versioned I often prefer to simply create a new table with the new structure and import the data into it, then swap the files. An example of an update for a specific data file is shown in Listing 8.
Listing 8 � A simple Table structure update routine
USE wwThreads in 0� && Earlier
SELE wwThreads
*** Save the old file settings
oldfile=SUBSTR(DBF(),1,LEN(DBF())-4)
*** close the file
USE
*** wwthreads.DBF file
CREATE TABLE (tfile) ;
(��� USERID����� C (8),;
�� THREADID��� C (9),;
�� MSGID������ C (9),;
�� ParentID��� C (9),;
�� SUBJECT���� M ,;
�� MESSAGE���� M ,;
�� ATTACHMT��� M ,;
�� ATTACHNM��� M ,;
�� FROMNAME��� C (35),;
�� FROMEMAIL�� M ,;
�� TO��������� M ,;
�� FORUM������ C (40),;
�� TIMESTAMP�� T ,;
�� NOFORMAT��� L ,;
�� READ������� L ,;
�� POST������� L ,;
�� SAVE������� L ,;
�� EMAIL������ L ,;
�� FOLLOWUP��� L ,;
�� EXPANDED��� L )
��������������������
*** Append the old file's records into new structure
APPEND FROM (oldfile)
USE
*** Now rename the files
ERASE (oldfile+".DBF")
ERASE (oldfile+".FPT")
RENAME (tfile+".DBF") TO (oldfile+".dbf")
RENAME (tfile+".fpt") TO (oldfile+".fpt")
ERASE (tfile+".DBF")
ERASE (tfile+".FPT")
USE ("wwthreads")
To help with this process the West Wind Client Tools include a utility to generate the CREATE TABLE commands for me automatically from a table so I don't have to manually keep track of it. The utility is called CRT_DBF and comes with both a programmatic and form interface which returns a string either to the clipboard or as a result value with the table structure command. This routine makes the structure update process easy as I don't have to remember which structure change was associated with which version and instead create a new fully updated structure of the file each time.
You might have to do additional checks based on the current data version such as:
*** Version 1.5 and earlier updates
IF lnDataVersion < 1.5
��� REPLACE To with "All" for empty(To)
ENDIF
Note that you should update the stored data version stamp only after you've completed all of your updates successfully. If for some reason the update process fails the writing to the INI file in the example above should not occur. This way the next time you run the update will occur again. This can be tricky if data updates occur that write data into files, and you might need additional checks to validate the data before updating. But in most cases you can simply re-run the routines.
In a perfect world HTTP downloads are very simple. Unfortunately in today's security environments you'll find that often times you need to customize your HTTP handler to account for proxy and firewalls settings and different HTTP connect modes.
The wwCodeUpdate class uses the West Wind HTTP class (wwHTTP)� to handle the HTTP communication with the Web Server. This class provides full control over retrieving content from a Web server. It is also fully configurable for advanced HTTP operation such as username/passwords, SSL operation, timeout configuration, proxy configuration etc. Full documentation of the wwHTTP class is available at the West Wind Client Tools Web page (http://www.west-wind.com/wwClientTools.asp) which wwHTTP is part of. The choice of using wwHTTP instead of the MSXML's XMLHTTP is done primarily to allow custom configuration of the HTTP process, the need to download binary data and most importantly to provide progress information events as part of the HTTP download of the update file. wwHTTP supports all of these easily with pure VFP code without any COM requirements.
wwCodeUpdate uses the CreateHTTPClient() method to create a default instance of the HTTP client to centralize the configuration of the HTTP client in case you need custom configuration (Listing 9).
�
Listing 9 � Creating an instance of the HTTP client
FUNCTION CreateHTTPClient()
THIS.oHTTP = CREATEOBJECT("CU_wwHTTP")
THIS.oHTTP.nConnectTimeout = THIS.nConnectTimeout && 10 secs
RETURN THIS.oHTTP
This method does nothing more than instantiate a custom
version of the wwHTTP class called CU_wwHTTP. It then sets the connection timeout which is
important since this class is also used to check for the XML file on the Web �
if there's no connection or the Web server is not responding in some way you
want to timeout fairly quickly so that your application doesn't hang too long
waiting to retrieve the version info. Note that if the machine is completely
offline and disconnected from the network the connection will immediately fail
and GetVersion
The point is that once CreateHTTPClient() has been called you can completely customize the oHTTP property reference. For example, if you need to set Proxy information on the client you can use code like this:
loUpdate.CreateHttpClient()� && create .oHTTP reference
loHttp = loUpdate.oHTTP� && Easier reference
loHttp.nHttpConnectType = 3� && Proxy Mode
loHttp.chttpproxyname = "proxy-server.hawaii.rr.com"
The nHttpConnectType property is probably the most important one to set and all of my applications that access Web content tend to provide a configuration setting for the connect mode. Figure 2 shows a configuration dialog for the West Wind Message Reader that provides this configuration option. By default this value is 0 (using Internet Explorer configuration) and can also be 1 (direct connection) or 3 (for Proxy configuration). Generally 0 will work as it tries to read the Internet Explorer network and proxy settings, which works 99% of the time, but in rare cases it won't work with some non-standard proxy servers. You can then use mode 3 or mode 3 with specific proxy server name, username and password provided.

Figure
2 � A typical application
configuration dialog that contains Web Server and connection mode settings. In
this application this value is stored in a configuration o
If you like to allow access to updates only to specific
users you can use a username and password to limit access to the update files
and version resource:
loHttp.cUsername = "rickstrahl"
loHttp.cPassword = "password"
For those of you familiar with wwIPStuff or wwHTTP you probably know that in order to handle download events, the wwHTTP class should be subclassed and the OnHttpBufferUpdate method implemented with a handler that provides the progress information. For this reason the CreateHttpClient() method creates the custom CU_wwHTTP class which implements the OnHttpBufferUpdate method� along with some basic property settings. For example, the default HTTP timeout is set to the value specified on the wwCodeUpdate class.
Listing 10 shows the implementation of the CU_wwHTTP subclass, which adds a few custom properties to the wwHTTP class that relate to the progress dialog display.
Listing 10 � wwHTTP subclass to allow displaying progress information events
*****************************************************
DEFINE CLASS CU_wwHTTP as wwHTTP
**************************************
*** Custom properties dealing with display
*** of download information
lShowDialog = .F.
oProgressForm = .NULL.
cCaption = "Downloading Update"
nContentSize = 0
************************************************************************
* CU_wwHTTP :: OnHTTPBufferUpdate
************************************
***� Function: HTTP
Progress Event Handler
***��� Assume:
Relies on wwDialogs.vcx for Progress Form
************************************************************************
FUNCTION OnHTTPBufferUpdate
LPARAMETERS lnbytes,lnbufferreads,lccurrentchunk
DO CASE
� *** If this is the
0 chunk it's HTTP Header
� CASE lnBufferReads
= 0
������
THIS.nContentSize = VAL( EXTRACT(lcCurrentChunk,CHR(13)+CHR(10) +
"Content-length: ",CHR(13)) )
������ DO CASE
�������� CASE
THIS.nContentSize > 90000
�������������
THIS.nHTTPWorkBufferSize = 16484
�������� CASE
THIS.nContentSize > 40000
�������������
THIS.nHTTPWorkBufferSize = 8182
������ ENDCASE
����� �RETURN
� CASE lnBufferReads
= -1
������ *** Done or cancelled (if lHttpDownloadCancelled=.T.)
������ RETURN
� OTHERWISE
������ DoEvents
ENDCASE
IF THIS.lShowDialog
�� IF
lnBufferReads=1
�����
THIS.oProgressForm = CREATEOBJECT("wwProgressForm")
����� THIS.oProgressForm.SetCaption(THIS.cCaption)
�����
THIS.oProgressForm.Show()
�� ENDIF
�� IF
THIS.oProgressForm.lCancelled
������ *** Cause HTTP Request to cancel on next buffer���������
������
THIS.lHTTPCancelDownload = .T.
�� ENDIF
��
THIS.oProgressForm.SetDescription("Received from " +
THIS.cserver + ":" +CHR(13) +;
�������������������������
LTRIM( TRANSFORM(lnBytes,"999,999,999") ) + " of "
+;
����� ��������������������LTRIM(TRANSFORM(THIS.nContentSize,"999,999,999"))+
" bytes")
��
THIS.oProgressForm.SetProgress(lnBytes/THIS.nContentSize * 100)�������������������������
ENDIF
ENDFUNC
��
ENDDEFINE
OnHttpBufferUpdate() method is called whenever wwHTTP is downloading a chunk of data from the Web server. The value of lnBufferReads determines how many times the method was called. The first call or 0 returns only the HTTP header which is important to retrieve the length of the data to be downloaded. We need this in order to give the user a n bytes of x downloaded progress window. The code figures out the length by extracting the Content-length header line from the complete HTTP header and storing it in the custom nContentSize property of the class. The code then adjusts the nHTTPWorkBufferSize which determines how much data is read in a single chunk at a time. The bigger the chunk the faster the download proceeds, but the less status events fire.
You can capture the end of the request whether it was cancelled by the user or whether it completed successfully with the -1 value for lnBufferReads. In this case the code simply returns � there's nothing to do when the download completes or cancels here.
The otherwise clause handles each one of the buffer reads � data is returned from the Web server. In this case we basically want to update the status display. The wwCodeUpdate class uses the wwProgressForm (contained in wwDialogs.vcx) class which is a progress bar display that displays the bytes downloaded and gives an estimate for how long the download will take. Note that the status display is optional and can be turned off by setting the lShowDialog property to .F. on the oHttp member of wwCodeUpdate, but in most cases you'll want the dialog since the download is extensive.
The wwCodeupdate class is fairly self contained, but there are a couple of dependencies that you need to consider.
An Internet
Connection
Obviously you need an Internet connection to check for
the Web version resource and download the update file. If no connection is
available the checks will simply fail quickly and not affect your application
other than a short delay. Timeouts will determine how long the app waits in the
worst case scenario where there is a network connection, but no Internet connection.
Use nConnectTimeout to set the length of time waited
before timing out.
MS Common Dialogs
Active X control
MSCOMCTL.OCX is required for the progress bar dialog and should be installed
with your application. Most desktop applications use these controls anyway.
Windows Scripting
Host
This software should be installed on all current versions of Windows
starting with Windows 98SE and later and any machine that has IE 5.0 or later
installed. Generally you won't have to worry about this, but to be safe you
might want to require your application to have IE 5.0 installed. You can check
from within your application with whether the Scripting Host is installed by
using IsComO
A Zip Self-Extractor
or the like
The wwCodeUpdate class
assumes that you have a self-extracting ZIP file that contains the update. Specifically
it relies on WinZip's Self-Extractor, but you can change the command line to
fit any other self-extractor in the SwapExe() method of the wwCodeUpdateClass.
You can get WinZip from
www.winzip.com.
The advantage of using a ZIP file is that you can store multiple files and
actual directory structures inside of the file. Figure 11 shows what a typical
application update looks like.

Figure
3 � Files to be updated can be
stored in a WinZip archive with hierarchical directories using the 'Include
Subdirectories' options when zipping. Make sure you don't check 'Save full path
info'.
When you create the Zip file you can include subdirectories as necessary. What I like to do is set up an update directory for my application and copy all the files to update into it, then run WinZip against that to create my Zip file. Check 'Include Subdirectories' and make sure not to check 'Save full path info'.
If your structures include files that live outside of the application root hierarchy, you should copy them into the update structure, and then use Post update code to move these files and directories as necessary using Fox code into these external directories as WinZip can only copy files into a specific directory and copy files into directories below it, not above it.
After you've created the Zip file you will need to create a
self-extracting Zip file and you can use the WinZip Self-Extractor (http://www.winzip.com/winzipse.htm?wzt)
for creating a self-extracting Exe. It's very important that you set up the Exe
in such a way that no prompts occur and so you should use all the default
settings (simply click Next on each step of the Wizard
until you see Finish). Once created the Exe can be extracted
by using the /auto option to run without user interface.
Phew, I've provided a ton of information on updating applications over the Web. With all of this info, realize that your actual work to implement Web updates is pretty small:
The last step is crucially important! I highly suggest you set up a test scenario at a different update URL first and test the update process. For testing purposes I like to build the final update file and deploy it then recompile my local application with a lower version number (I use header file constants) by rolling back the version number. This is nice and easy as you can simply rebuild and then re-run the application and update again.
A self-updating application adds a professional touch to your application. It also gives you the opportunity to remind your customers/users that you are still alive and have their welfare on your mind.
I've presented lots of information on this
what would seem to be a simple topic. I hope that you've
Source Code
http://www.west-wind.com/presentations/wwCodeUpdate/wwCodeUpdate.zip
West Wind Client
Tools
HTTP and general application tools for Visual FoxPro applications
(shareware)
http://www.west-wind.com/wwClientTools.asp
![]() |
Rick Strahl is president of West Wind Technologies on Maui, Hawaii. The company specializes in Web and distributed application development and tools with focus on Visual FoxPro and the .Net Framework for Web and distributed technologies. Rick is author of West Wind Web Connection, a powerful and widely used Web application framework for Visual FoxPro and West Wind HTML Help Builder. He's also a Microsoft Most Valuable Professional, and a frequent contributor to magazines and books. He is co-publisher and co-editor of CoDe magazine, and his book, "Internet Applications with Visual FoxPro 6.0", is published by Hentzenwerke Publishing. For more information please visit: http://www.west-wind.com/. |
|
|
| White Papers Home |  White Papers | Message Board |  Search |  Products |  Purchase | News |   |