Add even more FTP functionality to your VB apps

Learn some new and more advanced FTP tricks. Web Editor Lamont Adams introduces you to his NotSoSimpleFTP class library.

In my previous article, "Put basic FTP functionality in your VB applications," I introduced the WinInet functions and provided a class library, called SimpleFTP, that you can use to include basic FTP Get and Put functionality in a VB6 application. In this article, I'll extend SimpleFTP with a bit more functionality—so much more, in fact, that I've renamed the library NotSoSimpleFTP. But more on that later. You can download the entire VB6 project here.

Error codes
As with all WinInet functions, the ones I'll discuss here return one (1) for success and zero (0) for failure. If a function fails, you can check err.LastDLLError for a numeric error code.

Deleting a file from an FTP server
Using the FtpDeleteFile function, deleting a remote file is about as simple as uploading one. The VB6 Declare statement for FtpDeleteFile appears below:
Public Declare Function FtpDeleteFile Lib "wininet.dll" _
Alias "FtpDeleteFileA" (ByVal hFtpSession As Long, _
ByVal lpszFileName As String) As Boolean

You need to supply a handle to an open FTP connection (hFtpSession) and the relative path and name of the file you want to delete (lpszFileName) when calling this function. Check out the DeleteFile method I've put in Listing A to see FtpDeleteFile in action. You'll notice there's more stuff going on in there than just deleting the file, and I'll get to that in a moment.

Getting a directory listing
Retrieving a list of files in a particular directory on a server requires several steps, which appear below. I've placed a code snippet in Listing B that illustrates this process in action.
  1. 1.      Change to the appropriate directory using FtpSetCurrentDirectory.
  2. 2.      Call FtpFindFirstFile to get information about the first file in that directory, which is returned wrapped in a WIN32_FIND_DATA structure. Note here that which file is considered to be "first" is somewhat arbitrary, so don't count on files being returned in a particular order.
  3. 3.      Call InternetFindNextFile repeatedly to enumerate through the files contained in the current directory and receive information about each in a WIN32_FIND_DATA structure. You'll know you are finished when InternetFindNextFile returns False and err.LastDLLError is ERROR_NO_MORE_FILES (18).

As always, there's a catch, and you'll hit it the first time you attempt to enumerate through a directory more than once using the same connection handle. In that situation, FtpFindFirstFile will fail with a 12110 error code, which you'll discover, with a little research, equates to ERROR_FTP_TRANSFER_IN_PROGRESS. Of course, you don't have any other transfers in progress, so what's going on here?

As it turns out, the FTP protocol itself is the limiting factor. It specifically allows for only a single enumeration of a given directory per session, which means that you'll either have to disconnect and reconnect to the server before listing the contents of a directory a second time or cache the results of the listing somehow so you can refer to them later.

Caching saves the day
I took a second approach, which increased the complexity of SimpleFTP considerably and was my primary motivation for renaming the project NotSoSimpleFTP, but it seemed more efficient than closing and reopening server connections each time a directory listing was requested. NotSoSimpleFTP makes use of two custom collections, colFiles and colDirectories, to keep track of which directories have been previously enumerated and what the contents of each directory were at that time.

The GetMaskedDirectoryListing method in Listing C shows how this caching is done. You can see that when a listing is requested the first time, a new colFiles instance is created and populated with the names of the files found in the remote directory. This instance is stored in a class-level colDirectories collection keyed by directory name and is returned in response to future requests for a listing of the same directory. So there's no question about whether the listing is current; colFiles includes a Cached property that's set to true when the instance is saved to the colDirectories collection.

Status updates
Anyone who has ever tried downloading a file from a busy server or over a 56K modem line knows that FTP access sometimes doesn't set any land-speed records. So, it's usually a good idea to include some kind of status updates in an app that uses FTP for any purpose. That way, the user knows what's going on while the app is communicating with a server. The WinInet library includes support for status function callbacks, which you set up by passing the address of a function with a particular declaration to InternetSetStatusCallback.

Unfortunately, despite much experimentation, I could not get this system to work as advertised, even when using the FTP access functions in asynchronous mode. That left me in a bit of a conundrum, since I did promise to show you how to implement status notification in my previous literary exploration of FTP using WinInet.

I was able to make good on my promise, even though it's not quite what I had in mind. NotSoSimpleFTP exposes a single event, StatusUpdate, which provides information about the progress of an operation in both string and numeric format. It's not incredibly flashy, but it does provide some feedback to the class's client and breaks up what could be relatively long periods of inactivity by briefly returning control to the client.

Possible uses?
There are a variety of potential uses for including FTP functionality in a VB application, most of which center around retrieving or sharing data files. However, it would also be possible to construct a self-upgrading application (like the banking application I discussed at the beginning of my previous article) that uses FTP to check for and retrieve updated components. If there's sufficient interest in doing so, I will devote a future article to that very topic.

How do you use the Internet?
Applications don't have to be browser-based or consume Web services to make use of the Internet. Tell us how you've leveraged Internet access in your applications.





Editor's Picks