Transferring Files with the Internet Transfer Control

by Brad Wist

Thanks to the Internet's ever-increasing prominence in our world, we developers are constantly finding new and better ways to take advantage of its capabilities. Frequently, that means finding new ways to perform tasks on the Internet-pushing the limits to do something that hasn't been done before. At other times, we must find alternate paths to take advantage of functionality that's existed for years, such as file transfers using the File Transfer Protocol (FTP).

FTP gives us the ability to send or receive all sorts of files across the Internet. Web browsers use underlying FTP functionality when downloading files. We can employ that same functionality in our Visual Basic applications to transfer files across the Internet or intranet.

To begin, we could pull up the specifications for FTP (ds.internic.net/rfc/rfc959.txt) and start writing massive amounts of code to accomplish our task. Or we could take advantage of the Internet Transfer Control that ships with VB 5. Let's take the second option.

In this article, we'll build a simple application that will allow us to connect to an FTP site and transfer files back and forth. The application we'll design, shown in Figure A, lets the user specify a URL, user name, and password. Drive, directory, and file list boxes provide access to local files, while a separate list box displays remote files.

Figure A: Our sample application looks like this at design time.

We'll also use the status bar control that ships with VB to display status information about the connection. To add the status-bar control to your application, choose the Project | Components… menu item, then select Microsoft Windows Common Controls 5.0 and click OK.

The sample application lets users transfer files by dragging them from one list box to the other. Although this feature is optional, it's very user-friendly. The sample code found in Listing A shows you the steps to implement this drag-and-drop feature-pay particular attention to the MouseDown, MouseUp, and DragDrop events. We'll examine how those transfers are performed throughout the rest of this article.

Listing A: Code for core FTP functionality

Private Const ftpDIR As Integer = 0
Private Const ftpPUT As Integer = 1
Private Const ftpGET As Integer = 2
Private Const ftpDEL As Integer = 3
Private iLastFTP As Integer
Private Sub cmdConnect_Click()
    On Error GoTo ConnectError
    Inet1.URL = txtURL
    Inet1.UserName = txtUserName
    Inet1.Password = txtPassword
    Inet1.Protocol = icFTP
    iLastFTP = ftpDIR
    Inet1.Execute Inet1.URL, "DIR"
End Sub

Private Sub Inet1_StateChanged(ByVal _
    State As Integer)
    Select Case State
        Case icNone
         sbFTP.Panels("status").Text = ""
        Case icResolvingHost
         sbFTP.Panels("status").Text
           = "Resolving Host"
        Case icHostResolved
         sbFTP.Panels("status").Text _
           = "Host Resolved"
        Case icConnecting
         sbFTP.Panels("status").Text _
           = "Connecting..."
        Case icConnected
         sbFTP.Panels("status").Text _
           = "Connected!"
        Case icRequesting
         sbFTP.Panels("status").Text _
           = "Requesting..."
        Case icRequestSent
         sbFTP.Panels("status").Text _
           = "Request Sent"
        Case icReceivingResponse
         sbFTP.Panels("status").Text _
           = "Receiving Response..."
        Case icResponseReceived
         sbFTP.Panels("status").Text _
           = "Response Received!"
        Case icDisconnecting
         sbFTP.Panels("status").Text _
           = "Disconnecting..."
        Case icDisconnected
         sbFTP.Panels("status").Text _
           = "Disconnected"
        Case icError
         sbFTP.Panels("status").Text _
           = "Error! " & Trim(CStr( _
           Inet1.ResponseCode)) & _
           ": " & Inet1.ResponseInfo
        Case icResponseCompleted
         sbFTP.Panels("status").Text _
           = "Response Completed!"
            ReactToResponse iLastFTP
    End Select
End Sub

Public Function _
    ReactToResponse(ByVal _
    iLastCommand As Integer) As Long
    Select Case iLastCommand
        Case ftpDIR
            ShowRemoteFileList
        Case ftpPUT
            MsgBox "File Sent from " & CurDir()
        Case ftpGET
            MsgBox "File Received "& "in " & CurDir()
        Case ftpDEL
    End Select
End Function

Public Function ShowRemoteFileList() As Long
    Dim sFileList As String
    Dim sTemp As String
    Dim p As Integer
    sTemp = Inet1.GetChunk(1024)
    Do While Len(sTemp) > 0
        DoEvents
        sFileList = sFileList & sTemp
        sTemp = Inet1.GetChunk(1024)
    Loop
    lstRemoteFiles.Clear
    Do While sFileList > ""
        DoEvents
        p = InStr(sFileList, vbCrLf)
        If p > 0 Then
            lstRemoteFiles.AddItem 
                Left(sFileList, p - 1)
            If Len(sFileList) > (p + 2) Then
                sFileList = Mid(sFileList, p + 2)
            Else
                sFileList = ""
            End If
        Else
            lstRemoteFiles.AddItem sFileList
            sFileList = ""
        End If
    Loop
End Function

Public Function GetFiles(sFileList As String) As Long
    Dim sFile As String
    Dim sTemp As String
    Dim p As Integer
    iLastFTP = ftpGET
    sTemp = sFileList
    Do While sTemp > ""
        DoEvents
        p = InStr(sTemp, "|")
        If p Then
            sFile = Left(sTemp, p - 1)
            sTemp = Mid(sTemp, p + 1)
        Else
            sFile = sTemp
            sTemp = ""
        End If
        Inet1.Execute Inet1.URL, "GET " & sFile & _
            " " & sFile
    'wait until this execution is done 
    'before going to next file
        Do
            DoEvents
        Loop Until Not _
            Inet1.StillExecuting
    Loop
    iLastFTP = ftpDIR
    Inet1.Execute Inet1.URL, "DIR"
End Function
Public Function PutFiles(sFileList As String) As Long
    Dim sFile As String
    Dim sTemp As String
    Dim p As Integer
    iLastFTP = ftpPUT
    sTemp = sFileList
    Do While sTemp > ""
        DoEvents
        p = InStr(sTemp, "|")
        If p Then
            sFile = Left(sTemp, p - 1)
            sTemp = Mid(sTemp, p + 1)
        Else
            sFile = sTemp
            sTemp = ""
        End If
        Inet1.Execute Inet1.URL, "PUT" & sFile & _
            " " & sFile
    'wait until this execution is done 
    'before going to next file
        Do
            DoEvents
        Loop Until Not Inet1.StillExecuting
    Loop
    iLastFTP = ftpDIR
    Inet1.Execute Inet1.URL, "DIR"
End Function

Private Sub dirLocal_Change()
    filLocal.Path = dirLocal.Path
End Sub

Private Sub drvLocal_Change()
    dirLocal.Path = drvLocal.Drive
End Sub

Private Sub filLocal_DragDrop(Source _
        As Control, X As Single, Y As Single)
    'receiving files from FTP site.
    Dim I As Integer
    Dim sFileList As String
    If TypeOf Source Is ListBox Then
        For i = 0 _
            To Source.ListCount - 1
            If Source.Selected(i) Then
                sFileList = _
                    sFileList & _
                    Source.List(i) & "|"
            End If
        Next
    End If
    If Len(sFileList) > 0 Then
        'strip off the last pipe
        sFileList = Left(sFileList, _
            Len(sFileList) - 1)
        GetFiles sFileList
    End If
End Sub

Private Sub _
    filLocal_MouseDown(Button As _
    Integer, Shift As Integer, X As _
    Single, Y As Single)
    filLocal.Drag vbBeginDrag
End Sub

Private Sub filLocal_MouseUp(Button _
    As Integer, Shift As Integer, _
    X As Single, Y As Single)
    filLocal.Drag vbEndDrag
End Sub

Private Sub _
    lstRemoteFiles_DragDrop(Source _
    As Control, X As Single, Y As Single)
    Dim I As Integer
    Dim sFileList As String
    If TypeOf Source Is FileListBox Then
        For i = 0 To Source.ListCount - 1
            If Source.Selected(i) Then
                sFileList = sFileList & _
                  Source.List(i) & "|"
            End If
        Next
    End If
    If Len(sFileList) > 0 Then
        'strip off the last pipe
        sFileList = Left(sFileList, _
            Len(sFileList) - 1)
        PutFiles sFileList
    End If
End Sub

Private Sub _
    lstRemoteFiles_KeyDown(KeyCode _
    As Integer, Shift As Integer)
    If KeyCode = vbKeyDelete Then
        Inet1.Execute Inet1.URL, "DEL " & _
            lstRemoteFiles.List( _
            lstRemoteFiles.ListIndex)
        Do
            DoEvents
        Loop While Inet1.StillExecuting
    End If
    iLastFTP = ftpDIR
    Inet1.Execute Inet1.URL, "DIR"
End Sub

Private Sub _
    lstRemoteFiles_MouseDown(Button _
    As Integer, Shift As Integer, )
    X As Single, Y As Single)
    lstRemoteFiles.Drag vbBeginDrag
End Sub

Private Sub lstRemoteFiles_MouseUp(Button As _
    Integer, Shift As Integer, _
    X As Single, Y As Single)
    lstRemoteFiles.Drag vbEndDrag
End Sub
Internet Transfer Control

The Internet Transfer Control provides fairly extensive capabilities for transferring data across the Internet, in the form of either Web pages or files. For our purposes, we'll concentrate on file transfers and leave the rest for another article.

The control resides in the MSINET.OCX file. To load the control into your VB toolbox, choose Project | Components…. Next, find the Microsoft Internet Transfer Control 5.0 control, select it by placing an X beside it, then click OK. Now, add the control to your project form. Note that the control will appear as a button and won't be visible at runtime.

You can open the Object Browser (by pressing [b]) to examine all the properties, methods, events, and built-in constants available through this code. In addition to the control's help file, this information makes an excellent reference. For this article, we'll focus on the small set of available properties and methods listed in Table A.

Table A: Selected properties, methods, and events

Properties Description
Password The password you use when connecting with the FTP server.
StillExecuting Specifies whether a command is still being processed.
URL The URL of the FTP server.
Username User name to use to log into the FTP server.

MethodsDescription
Execute Initiates an asynchronous command/connection.
GetChunk Reads data from the buffer.
OpenURL Initiates a synchronous command/connection.

EventsDescription

StateChangedFires whenever the control state has changed, for example, when a response is received from the FTP server.

Using the control

To perform FTP transfers, you must follow a few basic steps. First, you define the FTP server you want to attach to. You can specify the FTP site in two ways: using the RemoteHost and RemotePort properties or via the URL property. For simplicity's sake, we'll use the URL property:

Inet1.URL = txtURL

You also must specify the user name and password you'll provide. Many FTP sites allow anonymous connections. In those cases, the user name anonymous will work with any password you like, although most FTP sites ask you to provide your E-mail address, as well. Here's the syntax:

Inet1.UserName = txtUsername
Inet1.Password = txtPassword

Setting the URL property will clear the Username and Password properties. So, be sure to set the URL first, then specify the user name and password.

Since we're going to be dealing strictly with FTP connections, we'll set the Protocol property accordingly, as follows:

Inet1.Protocol = icFTP

We'll want to execute these commands when we make our first connection to the FTP server. We use the cmdConnect command button to establish this connection, so the code will go to the server. At the same time, when we make this first FTP connection, we'll also retrieve the list of files available on the FTP server. We'll see how to do this next.

Execute gets things done

You'll use the Execute method to send all commands to the FTP site through the control. The syntax of the Execute method is

Inet1.Execute URL, Operation, Data, _
    RequestHeaders

However, when performing FTP commands, we'll only use the URL and Operation parameters. The others have no meaning for us-they're used in other processes.

You send all FTP commands in the Operation parameter; they take the syntax command [file1 [file2]]. The help file for the Internet Transfer Control includes a list of valid FTP commands under the Execute page. We'll focus on a few of these commands in the rest of this article.

Asynchronous processing

When you're using the Execute method, keep in mind that all its operations are asynchronous. This means that when you tell the control to perform an operation, it starts the operation but returns control back to the application. The control will handle all communications back and forth, based on the properties and commands you've given it. When the operation is completed, the control will notify the application.

If you use the OpenURL method, the control makes a synchronous connection and executes the command. However, control doesn't return until the command finishes executing. This more straightforward approach is somewhat simpler to program. Since the asynchronous approach is more flexible-and therefore preferable-we'll use it exclusively here.

Our discussion of the asynchronous approach would be incomplete without mentioning the StillExecuting property. This property identifies when the control is in the middle of performing some operation. If you need to perform an operation that requires several commands, you'll start the first command, loop until the control has stopped processing the command (i.e., StillExecuting is False), then move on to the next operation, as follows:

Inet1.Execute txtURL, "get MyFile.txt"
Do 
    DoEvents
Loop While Inet1.StillExecuting

In event-driven programming, we want to be able to react when the operation is complete. We'll use the StateChanged event to provide this functionality. Specifically, we'll look for the new State of the control to be icResponseCompleted. It may be useful to set a variable, such as iLastFTP, to store a value signifying which FTP command executed last. Then you can test that variable in the StateChanged event to determine what command completion you're reacting to, with the lines:

Sub Inet1.StateChanged(ByVal State As Integer)
Select Case State
    Case icResponseCompleted
        'put your code here
End Select
End Sub

Of course, the State parameter can hold a number of other values as well. We show these in the full code listing, found in Listing A. You can also check the help file for all these values. Now, let's build our FTP application.

Creating the sample project

The first step is to begin a new EXE project in VB5. Build a form similar to that shown in Figure A.

As you can see, the form should include TextBox controls for the target URL, user name, and password. You'll also need to provide a way to display both local and remote files. Our example uses the DirListBox, DriveListBox, and FileListBox controls for the local files, and a standard ListBox control to display the remote files. Finally, you must add a CommandButton to establish the initial connection. After that, our work with the list boxes will be complete. Table B shows the controls to add to the form, as well as some key properties. (You can also download our sample files from www.zdjournals.com/ivb. Click on the Source Code hyperlink.)

Table B: Controls to add to the form

Control Property Setting
Form Caption File Transfer
TextBox Name txtURL
TextBox Name txtUserName
TextBox Name txtPassword
DriveListBox Name drvLocal
DirListBox Name dirLocal
FileListBox Name filLocal
ListBox Name lstRemoteFiles
CommandButton Name Caption cmdConnect Connect

As the name implies, the Connect button will connect to the designated FTP site and retrieve a list of files. To accomplish this, put the following code in the cmdConnect_Click event:

Public Sub cmdConnect_Click()
    Inet1.URL = txtURL
    Inet1.UserName = txtUserName
    Inet1.Password = txtPassword
    Inet1.Protocol = icFTP
    'Use constant to identify that
    'we're getting a directory listing.
    'We'll use it in the
    'Inet1_StateChanged Event.
    iLastFTP = ftpDIR
    Inet1.Execute Inet1.URL, "DIR"
End Sub

The first time we use the Execute method, we establish a connection between the user machine and the FTP site.

Executing the Dir command will place a list of files in the control's buffer. To retrieve them from the buffer, we'll use the GetChunk method. GetChunk requires one parameter that specifies the maximum amount of data (in bytes) that we'll retrieve. We specify an amount and keep looping until we've emptied out the buffer. The result is a string of filenames, separated by a carriage return and line feed (vbCrLf). We can then display the list of files however we want. We wrote the function ShowRemoteFileList() in Listing A to load the file list into a list box.

Once we have the list of files, we can let the user upload files to (or download files from) the FTP site. Since downloading files is more common, let's consider this example first. We download files by executing the FTP command Get. The syntax of the command is Get file1 file2, where file1 is the name of the file on the FTP site and file2 is the name you want the file to have locally. File2 can include path information as well. The GetFiles() function in Listing A demonstrates how to issue the command and retrieve the file.

Similarly, if you want to upload a file to the FTP site (assuming you have write privileges at the site), you use the FTP command Put. The syntax of the command is Put file1 file2, where file1 is the local filename (which can include the path) and file2 is the name the file will have on the FTP site. The PutFiles() function in Listing A demonstrates this process.

Please note that you'll have a problem to work around. The FTP Command Line doesn't allow spaces in the filename or path. To solve this problem, you can take one of the following steps:

  1.  Use relative paths when specifying local files (which is the option we used in the sample program).

  2. Place quotation marks (Chr(34)) around the full path and filename (such as C:\My FTP Files\TestFile.txt) in the ftp command.

  3. Use the 8.3-character directory name

  4. Don't allow spaces in directory names.

With a little work, our application can allow the user to select multiple files to transfer in one operation. Of course, the application will need to issue an Execute command for each transfer. Then, we must test the StillExecuting property to determine whether the control has finished executing that command. Once it's complete, we can loop back and send the command again for the second file. We can continue this process for as many files as necessary.

Known bugs and issues

You should be aware of several issues that exist with the current versions of the control. These issues vary depending on which version you're using.

In the version that ships with VB 5 (version 5.00.3714), the control sends all filenames as uppercase when you're sending or receiving files. If you're hitting an Internet Information Server (IIS) using NT/DOS file settings, case doesn't matter, since the filenames aren't case-sensitive. However, if you're hitting a UNIX server, it's extremely important, since UNIX filenames are case-sensitive. The result is that any files you send will be named in all uppercase, and you won't be able to retrieve files that have lowercase letters in their names.

Fortunately, Microsoft is aware of this conflict (see the Microsoft Knowledge Base article support.microsoft.com/support/kb/articles/Q168/7/66.asp for more information) and has corrected it in Service Pack 2 for Visual Studio. However, the SP2 control (version 5.01.4319) introduces an even worse problem.

In the SP2 version of the control, you can't log in to any server, other than a strictly anonymous server (such as ftp://ftp.microsoft.com). User names and passwords are sent incorrectly to the FTP server. (See the Microsoft Knowledge Base article support.microsoft.com/support/kb/articles/Q173/2/65.asp for more details.)

Finally, Microsoft released Service Pack 3 (http://www.microsoft.com/vstudio/sp) in early December 1997, correcting these problems. So, be sure to upgrade to SP3 before developing your own projects.

Conclusion

As the Internet's importance grows in our daily lives, we must make our applications more Internet-aware. Actually, the Internet offers several solutions to some potential problems-the challenge is to take advantage of the existing capabilities to meet those challenges. If you need to transfer files between two Internet sites, the Internet Transfer Control offers a quick solution. In this article, we've shown you how to use the control in your applications. We've also pointed out a couple of bugs to work around.

Copyright © 1998, ZD Inc. All rights reserved. ZD Journals and the ZD Journals logo are trademarks of ZD Inc. Reproduction in whole or in part in any form or medium without express written permission of ZD Inc. is prohibited. All other product names and logos are trademarks or registered trademarks of their respective owners.