Dear John, How Do I... Create an FTP Browser?

The Visual Basic online help for the Internet Transfer control demonstrates the pieces you would use to create an FTP browser, but it doesn't assemble those pieces into a working application. This is a problem because the Internet Transfer control is asynchronous—how the events and error handling interact is the most difficult aspect of using the control.

Figure 7-6 below shows a simple FTP browser I've created using two text boxes and an Internet Transfer control. You enter the URL of an FTP server in the Address text box, and then select a file or a directory from the contents text box. If the selection is a directory, the application displays that directory. If the selection is a file, the browser saves the file in the Windows Temp directory.

When the user presses Enter, the Address text box executes requests by setting the Internet Transfer control's URL property and calling the Execute method. The OpenURL method performs the same action when requesting a specific file. However, when you use the OpenURL method to return the contents of a directory, HTML source code indicating the directory contents typically is returned. Therefore, in this example, I have steered clear of the OpenURL method. The code following Figure 7-6 shows the KeyPress event procedure for the txtAddress text box.

Figure 7-6. A simple FTP browser created using the Internet Transfer control.

Private Sub txtAddress_KeyPress(KeyAscii As Integer)
    If KeyAscii = Asc(vbCr) Then
        `Eat keystroke
        KeyAscii = 0
        `Select text
        txtAddress.SelStart = 0
        txtAddress.SelLength = Len(txtAddress)
        On Error GoTo errOpenURL
        `Set FTP address to view
        inetBrowse.URL = txtAddress
        `Get directory
        inetBrowse.Execute , "Dir "
        txtAddress = inetBrowse.URL
    End If
    Exit Sub

errOpenURL:
    Select Case Err.Number
        Case icBadUrl
            MsgBox "Bad address. Please reenter."
        Case icConnectFailed, icConnectionAborted, _
                icCannotConnect
            MsgBox "Unable to connect to network."
        Case icInetTimeout
            MsgBox "Connection timed out."
        Case icExecuting
            `Cancel previous request
            inetBrowse.Cancel
            `Check whether cancel worked
            If inetBrowse.StillExecuting Then
                Caption = "Couldn't cancel request."
            `Resubmit current request
            Else
                Resume
            End If
        Case Else
            Debug.Print Err.Number, Err.Description
    End Select
End Sub

Trapping Errors

It's important to trap any errors that occur when you submit a request to the Internet Transfer control. The icExecuting error is particularly important. The Internet Transfer control processes all requests asynchronously; however, it can process only one request at a time. If you cancel a pending request, be sure to check the StillExecuting property before resuming, as shown in the previous code. Some requests can't be canceled and using only a Resume statement will result in an infinite loop!

The following code shows the DblClick event procedure for the txtContents text box, in which directory listings are displayed. This code builds the URL string and executes a Dir command if the selection is a subdirectory, or a Get command if the selection is a file.

Private Sub txtContents_DblClick()
    `Browse selected directory
    If txtContents.SelLength Then
        `If selection is a directoryDear John, How Do I... 
        If Right(txtContents.SelText, 1) = "/" Then

            `Add selected item to address
            txtAddress = txtAddress & "/" & _
                Left(txtContents.SelText, _
                txtContents.SelLength - 1)
            `Trap errors (important!)
            On Error GoTo errBrowse
            `Show directory
            mstrDir = Right(txtAddress, Len(txtAddress) _
                - Len(inetBrowse.URL))
            inetBrowse.Execute , "Dir " & mstrDir & "/*"
        `Otherwise, it's a file, so retrieve it
        Else
            Dim strFilename
            `Build pathname of file
            mstrDir = Right(txtAddress, Len(txtAddress) _
                - Len(inetBrowse.URL)) & "/" & _
                txtContents.SelText
            mstrDir = Right(mstrDir, Len(mstrDir) - 1)
            strFilename = mstrDir
            Do
                strFilename = Right(strFilename, _
                    Len(strFilename) - InStr(strFilename, "/"))
            Loop Until InStr(strFilename, "/") = 0 `Retrieve file
            inetBrowse.Execute , "Get " & mstrDir & _
                " " & mstrTempDir & strFilename
        End If
    End If
Exit Sub
errBrowse:
    If Err = icExecuting Then
        `Cancel previous request
        inetBrowse.Cancel
        `Check whether cancel worked
        If inetBrowse.StillExecuting Then
            Caption = "Couldn't cancel request."
        `Resubmit current request
        Else
            Resume
        End If
    Else
        `Display error
        Debug.Print Err & " " & Err.Description
    End If
End Sub

The Execute and OpenURL methods trigger the StateChanged event. It's important to remember that both methods do this, because OpenURL appears to be a synchronous method; however, StateChanged events still occur and can cause problems with reentrancy.

The following code updates the form's caption to keep you abreast of the request's progress and then uses the GetChunk method to retrieve a directory listing if the command executed was Dir.

Private Sub inetBrowse_StateChanged(ByVal State As Integer)
    Select Case State
        Case icError
            Debug.Print inetBrowse.ResponseCode & " " & _
                inetBrowse.ResponseInfo
        Case icResolvingHost, icRequesting, icRequestSent
            Caption = "SearchingDear John, How Do I... "
        Case icHostResolved
            Caption = "Found."
        Case icReceivingResponse, icResponseReceived
            Caption = "Receiving data."
        Case icResponseCompleted
            Dim strBuffer As String
            `Get data
            strBuffer = inetBrowse.GetChunk(1024)
            `If data is a directory, display it
            If strBuffer <> "" Then
                Caption = "Completed."
                txtContents = strBuffer
            Else
                Caption = "File saved in " & _
                    mstrTempDir & "."
            End If
        Case icConnecting, icConnected
            Caption = "Connecting."
        Case icDisconnecting
        Case icDisconnected
        Case Else
            Debug.Print State
    End Select
End Sub

NOTE

The current documentation suggests that GetChunk will work with the Execute method's GET command, but that does not seem to be the case. The GET command's syntax specifies a source file and a destination file; therefore, GetChunk is not needed when you are copying a file from a server.

The complete code for the FTP browser, including the GetTempPath API function declaration and the Form_Load event procedure, can be found on the companion CD-ROM.