Do you really need a control?
So, last week, I became tired of having to deal with that. So, I decided to turn to another approach. I could have used the Microsoft Internet Transfer control. That would have helped me as this is not a commercial product and it would have been easier to maintain it locally. However, the problem of deployment would have remained. That control also doesn't support to ability to establish a progress bar.
So, I decided to look for something else. Someone on the Universal Thread has helped me a lot by showing me a few declares to WININET which allows me to do some specific FTP functionalities. I decided to try it. To my surprise, everything went smooth and worked in the first place.
What are the objectives?
The objectives were pretty clear. I needed to connect to an FTP server, update a file as well as the ability to rename or remove a file. WINNET allows the use of FTP functionalities with a set of several API calls. The following declares initializes the API call we need to do to connect, change to a specific directory, rename a file and remove a file.
DECLARE INTEGER InternetOpen in WinINet.dll ; STRING cAgent, INTEGER nType, STRING cProxy, STRING cBypass, INTEGER nFlags DECLARE INTEGER InternetCloseHandle in WinINet.dll ; INTEGER nInternetHandle DECLARE INTEGER InternetConnect in WinINet.dll ; INTEGER nInternetSession, STRING cServer, INTEGER nPort, ; STRING cUserName, STRING cPassword, INTEGER nService, INTEGER nFlags, INTEGER nContext DECLARE INTEGER FtpSetCurrentDirectory IN WinINet.dll ; INTEGER nFtpSession, STRING cDirectory DECLARE INTEGER FtpOpenFile IN WinINet.dll ; INTEGER nFtpSession, STRING lpszFileName, INTEGER nAccess, INTEGER NFlags, ; INTEGER nContext DECLARE INTEGER InternetWriteFile IN WinINet.dll ; INTEGER nFile, STRING cBuffer, ; INTEGER nToWrite, INTEGER @ nWritten DECLARE INTEGER FtpRenameFile IN WinINet.dll ; INTEGER nFile, STRING lpszExisting, STRING lpszNew DECLARE INTEGER FtpDeleteFile IN WinINet.dll ; INTEGER nFile, STRING lpszFileName
Building the object
In order to benefit of those functionalities elsewhere, I decided to build an object. The first method I defined was a method to connect to the FTP server. The method name is Connect(). Here is the code:
This.nSession = InternetOpen("Wininet",0,.NULL.,.NULL.,0) IF This.nSession=0 RETURN .F. ENDIF This.nFTPSession = InternetConnect(This.nSession,ALLTRIM(This.cServer),21,; ALLTRIM(This.cUserName),ALLTRIM(This.cPassword),1,0x08000000,0) IF This.nFTPSession=0 InternetCloseHandle(This.nSession) RETURN .F. ENDIF
The most interesting method is certainly the Upload() method. This is making use of a progress bar to show the progress of the upload. The method includes calls to the Thermo object which initializes the progress bar and increase its status. The method is making use of the cRemoteDir property which includes the full path of the directory where we are uploading. Assuming you are connecting to your drive D: of the server and that you need to upload to D:/Test/MyDir/MyFile.exe, you would then put Test/MyDir in cRemoteDir. We also need to initialzie the remote file. That is done using the cRemoteFile property. In this case, it would be equal to MyFile.exe. Next is the property which holds the local file. Basically, that is the full path of your local file such as d:\MyFile.exe.
IF FtpSetCurrentDirectory(This.nFTPSession,'/'+This.cRemoteDir)=0 RETURN .F. ENDIF * Open the file for upload (both local and remote) lnRemote=FtpOpenFile(This.nFTPSession,This.cRemoteFile,0x40000000,2,0) lnLocal=FOPEN(This.cLocalFile,0) IF lnRemote!=0 AND lnLocal!=-1 lnBufferSize=1024 * We got valid handles, so we may start to send the file lnFileLength=FSEEK(lnLocal,0,2) FSEEK(lnLocal,0,0) lnUploaded=0 Thermo(0,'Transfer in progress') * While there is something to upload DO WHILE lnUploaded<lnFileLength lcBuffer=FREAD(lnLocal,lnBufferSize) lnWritten=0 InternetWriteFile(lnRemote,lcBuffer,LEN(lcBuffer),@lnWritten) lnUploaded=lnUploaded+LEN(lcBuffer) * Makes use of the Thermo object to show the progress Thermo(,,lnUploaded*100/lnFileLength) ENDDO * Terminate upload InternetCloseHandle(lnRemote) FCLOSE(lnLocal) ENDIF
The rename method is pretty simple. It is named Rename() and we pass the actual name and the new name as the parameters. The method is making use of the cRemoteDir property. So, if you want to rename d:/MyDir/MyFile.exe to d:/MyDir/MyFile2.exe, you could call this method with FTP.Rename('MyFile.exe','MyFile2.exe') and initialize FTP.cRemoteDir with 'MyDir' prior to making the call. This assumes the object as been created under the name of FTP.
PARAMETERS tcFile,tcFile2 lnResult=FtpRenameFile(This.nFTPSession,'/'+This.cRemoteDir+'/'+tcFile,; '/'+This.cRemoteDir+'/'+tcFile2) IF lnResult=0 RETURN .F. ENDIF
The delete method makes use of the cRemoteDir and cRemoteFile properties. It is called Delete(). Once those properties are initialized, you just call the method.
lnResult=FtpDeleteFile(This.nFTPSession,'/'+This.cRemoteDir+'/'+This.cRemoteFile) IF lnResult=0 RETURN .F. ENDIF
Putting all together
Here is a little schema of putting it all together. Lets assume we connect to 120.120.120.120 with MyUsername as the username and MyPassword as the password. We would then upload a file from our D: drive named ToUpload.exe in the FTP directory Test/Today. We will keep the same name. Then, we will rename the file to ToUpload2.exe and we will remove it.
We will assume the control has been initialized in a form under the name FTP. Then, a specific method of the form could do the following:
ThisForm.Ftp.cServer='120.120.120.120' ThisForm.Ftp.cUsername='MyUsername' ThisForm.Ftp.cPassword='MyPassword' ThisForm.Ftp.cRemoteDir='Test/Today' ThisForm.Ftp.cRemoteFile='ToUpload.exe' ThisForm.Ftp.cLocalFile='d:\ToUpload.exe' ThisForm.Ftp.Connect() ThisForm.Ftp.Upload() ThisForm.Ftp.Rename('ToUpload.exe','ToUpload2.exe') ThisForm.Ftp.cRemoteFile='ToUpload2.exe' ThisForm.Ftp.Delete() ThisForm.Ftp.Close()
A few notes
This code is only a basic start. If you use it or part of it, you will probably check for the method returns to see if the process succeeded or not. Each method returns .T. or .F. to let you know if it worked. Assuming it does not, you will then be able to take action.
This code doesn't include the FTP object. However, in a few minutes, by copying the methods code, you should be able to do so. Just create an object, the required properties and methods and paste the methods code in the required methods.
This code has been done within a few minutes. A lot of enhancements can be applied to it if you wish to use it as a start. Basically, you would probably want to use only properties and avoid having to pass parameters such as in the Rename() method. Having had more time, I would have probably done it myself.
An important method missing is the Download() method. As this is an important need, by following the same approach I use for the Upload() method, you can easily do it pretty fast. The Microsoft Web site MSDN section includes all the API function descriptions for the FTP commands.
Properties and Methods
The following properties and methods can be used:
From now on
I hope this has given you a good start to benefit of the use of those Windows API functions to do some FTP. If you never did it before, this might help you to get it running. Such an object can be expanded to include the ability to list the files from a specific directory and execute other tasks.
You can also do some searches on the Universal Thread for specific content in regards to FTP. There have been several threads in regards to that as well as the presence of some good utilities in the Downloads section.