Programming with the Internet Transfer Control
I wrote a program I call the Internet Updater, which demonstrates the use of the Internet Transfer control. The Internet Updater is used to update an application by examining each file in the application and choosing those files that have newer versions. Files with newer versions than those residing on the user’s system are then downloaded.
The program begins by loading into memory a local file that contains the status of the local files. Then it will download a similar file that contains the information about the current version of the application. Then it will recommend which files should be updated, as shown in the example in Figure 11.1. The users can choose to accept the recommendation or adjust it as they see fit. Finally, they can download the files onto their system. When the download is complete, the local list will be updated and saved.
Figure 11.1: The Internet Updater program
Creating the Local and Remote Information Files
In order to determine which files should be updated, you need a list of files available for download and a list of files on your system. In addition to the file lists, you also need two more pieces of information—the current version of the file and where to locate the file. All of this information is contained in two files, one on the local machine and one on the remote machine.
Remote File Information
As you can see in the example below, the format of the remote file is very simple. Each file in the list occupies a single line of text, with the individual fields separated by commas. The fields are File Name, Creation Date, Version, File Size, and URL to retrieve the file.
app.exe, 1/1/98, 1.1.121, 98700, http://jillion/app/app.exe app.dll, 1/1/98, 1.1.121, 87600, http://jillion/app/app.dll app.dat, 1/1/97, 1.1.120, 76500, http://jillion/app/app.dat
Local File Information
The format of the local file is similar to the remote file, but it contains a little more information in the beginning. The local file has the following information:
The first line of the file contains the URL to retrieve the remote file.
The user name and password to access the system are on the second and third lines.
The fourth line contains the root directory where the application files are stored.
Starting with the fifth line is a list of files similar to the files found on the remote system, except that the last field tracks when the file was installed rather than a URL.
Here is an example of a local information file, before updates:
http://jillion/app/app.ver anonymous wfreeze d:\app app.exe, 1/1/97, 1.1.120, 98700, Installed 6/1/97 00:00:00 AM app.dat, 1/1/97, 1.1.120, 76500, Installed 6/1/97 00:00:00 AM
When you compare the remote and local information files, notice that the remote file includes a new file that is not currently on the local system (app.dll). You should also notice that the app.exe file has been updated; it now has a date of 1/1/98 and a version level of 1.1.121. The local file after it has been updated will look like this:
http://jillion/app/app.ver anonymous wfreeze d:\app app.dat, 1/1/97, 1.1.120, 76500, Installed 6/1/97 00:00:00 AM app.dll, 1/1/98, 1.1.121, 87600, Installed 6/7/98 10:54:42 PM app.exe, 1/1/98, 1.1.121, 98700, Installed 6/7/98 10:54:42 PM
Retrieving the Remote File
As you saw in Figure 11.1, I track all of the information from the files in a ListView control (I talked about the ListView control in Chapter 7). Loading the local information into the ListView is very straightforward, especially when using the Split function, so I won’t cover it here. Loading the remote file is a bit more interesting, however.
In Figure 11.2, you see the Properties window for the Internet Updater program. It contains the same four pieces of information found at the beginning of the local file: the URL of the remote file, the user name and password to access the remote file, and the directory where you can find the application on the local system.
Figure 11.2: The Internet Updater Properties window
Loading Information from the Remote System
The LoadRemoteInfo subroutine in Listing 11.1 uses three of these four fields to establish a connection to the remote system. It works as follows:
Begin by initializing the Internet Transfer control (Inet1).
Assign the URL of the remote file to the URL property of Inet1.
Assign values for the UserName and Password properties.
Use the OpenURL method to retrieve a string containing the remote file.
Use the Split function to break the string apart into its separate lines.
Call the AnalyzeInfo function to take the information I just retrieved and update the ListView control.
You may have noticed something interesting here. Nowhere did I mention any code that was specific for the HTTP or FTP protocol. I used the Internet Transfer control’s ability to load information from either type of server and left the ultimate decision on what protocol to use up to the implementers.
Listing 11.1: LoadRemoteInfo Routine in Internet Updater
Private Sub LoadRemoteInfo() Dim d As String Dim x() As String Inet1.URL = Text1.Text Inet1.UserName = Text2.Text Inet1.Password = Text3.Text d = Inet1.OpenURL(, icString) x = Split(d, vbCrLf) AnalyzeInfo x End Sub
Analyzing the Information
The AnalyzeInfo routine, shown in Listing 11.2, looks like one big mess. It probably is, too. This routine grew out of a much smaller routine, but I wanted to add a few extra functions.
Listing 11.2: AnalyzeInfo Routine in Internet Updater
Private Sub AnalyzeInfo(x() As String) Dim b As Boolean Dim c As Integer Dim i As Integer Dim j As Integer Dim l As ListItem Dim y() As String On Error Resume Next ‘ Make sure that nothing is marked as selected For Each l In ListView1.ListItems l.Selected = False Next l ‘ Process the remote file one line at a time For i = 0 To UBound(x) y = Split(x(i), “,”) For j = 0 To 4 y(j) = Trim(y(j)) Next j b = False b = Len(ListView1.ListItems(y(0)).Text) > 0 If b Then ListView1.ListItems(y(0)).SubItems(4) = y(1) ListView1.ListItems(y(0)).SubItems(5) = y(2) ListView1.ListItems(y(0)).SubItems(6) = y(3) ListView1.ListItems(y(0)).SubItems(7) = y(4) If ListView1.ListItems(y(0)).SubItems(1) < y(1) Then ListView1.ListItems(y(0)).Selected = True ListView1.ListItems(y(0)).SubItems(8) = “Newer date” ElseIf ListView1.ListItems(y(0)).SubItems(2) < y(2) Then ListView1.ListItems(y(0)).Selected = True ListView1.ListItems(y(0)).SubItems(8) = “Newer version” End If Else Set l = ListView1.ListItems.Add(, y(0), y(0)) l.Selected = True l.SubItems(1) = “” l.SubItems(2) = “” l.SubItems(3) = “” l.SubItems(4) = y(1) l.SubItems(5) = y(2) l.SubItems(6) = y(3) l.SubItems(7) = y(4) l.SubItems(8) = “New file” End If Next i c = 0 For Each l In ListView1.ListItems If l.Selected Then c = c + 1 End If Next l If c = 0 Then MsgBox “No updates are needed.” ElseIf c = 1 Then MsgBox “One file should be updated.” Else MsgBox FormatNumber(c, 0) & “ files should be updated.” End If End Sub
I start by going through each item in the ListView control and making sure it is not selected. This is important, since I’ve set MultiSelect to True and I want the user to be able to override any of the recommendations I make in this routine.
After I clear any selections, I begin to process each line of the remote file. The first thing I do is to separate the line into a string array using the Split function with a comma as a delimiter. Then I remove any leading and trailing spaces from each member of the array by using the Trim function.
Next, I get a little tricky. I need to know if the filename stored in y(0) is in the ListView. To do this, I rely on the On Error Resume Next statement to prevent my program from dying if I use an invalid key value. First, I set a temporary variable (b) to False. Then I check to see if the Text property of the ListItem with a key value of y(0) is greater than zero and assign b a value of True. In other words, if there is an entry in the ListView with that filename, b will be set to True. If there isn’t, the statement will fail and not change the previous value of b. I think this approach is much better than building a loop to search the entire ListView for a particular value.
Once I know that the value is in the ListView, I merely fill in the values for the New Date, New Version, New Size, and URL. Then I can compare the dates. If the new date is newer than the current date, I indicate this in the Comment column and select the row for processing later. If the dates are compatible, I then check the version information.
If the filename didn’t exist in the ListView, I create a new row using the filename as the Key and Text values. Then I assign empty strings to the current values (since there aren’t any current values), and I assign the new values to the new columns. Finally, I set the Comments field to indicate that this is a new file.
At the end of this routine, I decide to get cute and count the number of files I need to download by counting the number of selected rows in the ListView control. Then I display the appropriate message box letting the users know that the update analysis has been finished and tell them how many files need to be downloaded.
In Listing 11.3, I perform the actual update process. I scan through the ListView looking for selected rows. If a row is selected, I call the GetFile routine to download the file using the URL stored in the URL column (SubItems(7)), and the application directory (Text4.Text) and the filename (ListItems(i).Text). Then I copy the value from the new columns to the current columns since this is now the current data, and I set the Comments column to reflect the date and time the new files were installed.
Listing 11.3: UpdateInfo Routine in Internet Updater
Private Sub UpdateInfo() Dim i As Integer For i = 1 To ListView1.ListItems.Count If ListView1.ListItems(i).Selected Then GetFile ListView1.ListItems(i).SubItems(7), _ Text4.Text & “\” & ListView1.ListItems(i).Text ListView1.ListItems(i).SubItems(1) = _ ListView1.ListItems(i).SubItems(4) ListView1.ListItems(i).SubItems(2) = _ ListView1.ListItems(i).SubItems(5) ListView1.ListItems(i).SubItems(3) = _ ListView1.ListItems(i).SubItems(6) ListView1.ListItems(i).SubItems(8) = _ “Installed “ & FormatDateTime(Now, vbGeneralDate) ListView1.ListItems(i).Selected = False End If Next i SaveInfo MsgBox “Update completed.” End Sub
Retrieving Files from the Remote Server
I use the GetFile routine, shown in Listing 11.4, to retrieve a file from the remote server. The parameters s and d contain the URL and local path name for the file, respectively. I use the OpenURL method to retrieve the file on the remote system, similar to how I used it with the LoadRemoteInfo subroutine. The only difference is that this time I return the contents as a byte array. This ensures that no binary information is lost, which is particularly important when you are dealing with .EXE and .DLL files. After I receive the data, I open the local file for binary access, write the contents of the byte array, and then close it.
Listing 11.4: GetFile Routine in Internet Updater
Private Sub GetFile(s As String, d As String) Debug.Print s, d Dim x() As Byte Inet1.URL = s Inet1.UserName = Text2.Text Inet1.Password = Text3.Text x = Inet1.OpenURL(, icByteArray) Open d For Binary Access Write As #1 Put #1, , x() Close #1 End Sub
Watching State Information
I find it interesting to watch the state of the Internet Transfer control. It provides some insight into what is happening when. This is especially true when you are trying out different features of the control. In Listing 11.5, I handle the StateChanged event. I simply convert State into a descriptive string and display it in the status bar.
Listing 11.5: Inet1_ StateChanged Routine in Internet Updater
Private Sub Inet1_StateChanged(ByVal State As Integer) StatusBar1.Panels(1).Text = GetState(State) End Sub
Translating the State value into a string is just a matter of using one big Select Case statement, as you can see in Listing 11.6.
Listing 11.6: GetState Function in Internet Updater
Private Function GetState(s As Integer) As String Select Case s Case 0 GetState = “No state information is available.” Case 1 GetState = “Looking up the IP address for the remote server.” Case 2 GetState = “Found the IP address for the remote server.” Case 3 GetState = “Connecting to the remote server.” Case 4 GetState = “Connected to the remote server.” Case 5 GetState = “Requesting information from the remote server.” Case 6 GetState = “The request was sent successfully to the remote server.” Case 7 GetState = “Receiving a response from the remote server.” Case 8 GetState = “The response was received successfully from the “ & _ “remote server.” Case 9 GetState = “Disconnecting from the remote server.” Case 10 GetState = “Disconnected from the remote server.” Case 11 GetState = “An error has occurred while communicating with the “ & _ “remote server.” Case 12 GetState = “The request was completed, all data has been received.” Case Else GetState = “Unknown state: “ & FormatNumber(State, 0) End Select End Function