SET PROCEDURE TO wwFtp Additive
SET PROCEDURE TO wwUtils ADDITIVE

#INCLUDE wconnect.h

*************************************************************
DEFINE CLASS wwFtp AS Custom
*************************************************************
*: Author: Rick Strahl
*:         (c) West Wind Technologies, 2011
*:Contact: http://www.west-wind.com
*:Created: 08/30/2011
*************************************************************
#IF .F.
*:Help Documentation
*:Topic:
Class wwFtp

*:Description:
Ftp class that uses native WinInet API to handle FTP transfers.
*:ENDHELP
#ENDIF


HelpContextID = 703

*** The size of the FTPGetEx download buffer. For each buffer chunk the OnFTPBufferUpdate event is fired to allow you to cancel and display status information
nftpworkbuffersize = 24576

*** Cancel flag that can be set in OnFTPBufferUpdate to let wwFTP know to stop downloading a file from the Internet.
lcanceldownload = .F.

*** The FTP Session handle used in Internet Access. One of two (hIPSession is the other)
hftpsession = 0

*** Flag to determine whether FTP file sent is ASCII (0) or Binary (1)
nftpbinary = 1

*** The FTP server to connect to.
cftpserver = ""

*** The Source file to upload from the client or download from the server.
cftpsource = ""

*** The file to download to from the server or upload to on the server.
cftptarget = ""

*** Flag that sets whether to use passive FTP.
lpassiveftp = .T.

*** The filesize of the current file being uploaded or downloaded.
ncurrentfilesize = 0

*** Connection timeout 
nConnectTimeout = 25

*** The port to run the FTP connection on
nftpport = 21

*** Internally used port (nFtpPort maps to this)
nhttpport = 21

*** Domain name or IP address for the server
cserver = ""

*** Username for the connection
cUsername = ""

*** Password for the connection
cPassword = ""

*** Connection mode: 0 - Auto configuration 1 - direct 3 - Proxy
nHttpConnectType = 0

cHttpProxyName = ""
cHttpProxyPassword = ""
cHttpProxyUsername = ""
cHttpProxyByPass = ""

*** If non-zero after method call an error occurred
nError = 0

*** Error message set after a method call that fails
cErrorMsg = ""

hIPSession = 0

*** User agent string sent with FTP client request
cUserAgent = ""

*** Optional WinInet FTP Service flags
nServiceFlags = 0


************************************************************************
*  Init
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
PROCEDURE Init(lcPath)

#IF SHAREWARE
WAIT WINDOW NOWAIT ;
  "Welcome to West Wind Client Tools..." + CHR(13) + ;
  "This is a shareware copy of wwFtp." + CHR(13)+ CHR(13) +;
  "Please register your copy."
#ENDIF

DECLARE INTEGER GetLastError IN Win32API 

ENDFUNC


************************************************************************
*  Destroy
****************************************
***  Function: Closes down connection if still open
***    Assume:
***      Pass:
***    Return:
************************************************************************
PROCEDURE Destroy

IF THIS.hIPSession # 0 OR THIS.hFtpSession # 0
   THIS.FtpClose()
ENDIF
ENDFUNC



************************************************************************
*  FtpGetFileEx
****************************************
***  Function: Copies a file from the server to the client
***    Assume: Connection is open already with FtpOpen
***      Pass: lcSourceFile - Server relative path to a file to download
***            lcTargetFile - Full path to a local file to download to
***    Return: 0 on success - error number of failure
************************************************************************
FUNCTION FtpGetFileEx(lcSourceFile, lcTargetFile)
LOCAL lhFileHandle, hFTPFile, tcBuffer, tnSize, tnBufferSize, lnRetVal, lnBytesRead, lnBufferReads

DECLARE INTEGER FtpOpenFile ;
   IN WININET.DLL ;
   INTEGER hIPSession,;
   STRING @lpszFileName,;
   INTEGER dwAcessFlags,;
   INTEGER dwServiceFlags,;
   INTEGER dwContext


DECLARE INTEGER InternetReadFile ;
   IN WININET.DLL ;
   INTEGER hFTPHandle,;
   STRING lcBuffer,;
   INTEGER cbBuffer,;
   INTEGER @cbBuffer

lnMode = IIF(this.nFtpBinary = 1, FTP_TRANSFER_TYPE_BINARY,FTP_TRANSFER_TYPE_ASCII)
hFTPFile = FtpOpenFile(THIS.hFTPSession,TRIM(lcSourceFile),;
                       GENERIC_READ,;
                       INTERNET_FLAG_RELOAD + lnMode,0)

IF hFTPFile = 0
   THIS.nError = GetLastError()
   THIS.cErrorMsg = THIS.GetSystemErrorMsg()
   RETURN THIS.nError
ENDIF

DECLARE INTEGER FtpGetFileSize IN WinINet.dll ;
   INTEGER lhFile, LONG @lnSize

lnUpperSize = 0
THIS.nCurrentFileSize =  FtpGetFileSize(hFTPFile,@lnUpperSize)  

*** Build the buffer dynamically
THIS.lCancelDownload = .f.
tcBuffer = ""
tnSize = 0
tnBufferSize = 0
lnRetVal = 0
lnBytesRead = 1
lnBufferReads = 0
lhFileHandle = FCREATE(TRIM(lcTargetFile))
IF lhFileHandle=-1
      THIS.cErrorMsg = "Output file couldn't be created."
      InternetCloseHandle(hFTPFile)
      RETURN FError()
ENDIF

DO WHILE .t.
     lcReadBuffer = REPL(CHR(0),THIS.nFTPWorkBufferSize)
     lnBytesRead = 0
     lnSize = THIS.nFTPWorkBufferSize

     lnRetval=InternetReadFile(hFTPFile,;
		   @lcReadBuffer,;
		   lnSize,;
		   @lnBytesRead)

		IF lnRetVal = 1 AND lnBytesRead > 0
		   *** Update the input parameters - result buffer and size of buffer
		   *tcBuffer = tcBuffer + LEFT(lcReadBuffer,lnBytesRead)
		   =FWRITE(lhFileHandle,LEFT(lcReadBuffer,lnBytesRead) )
		   tnBufferSize = tnBufferSize + lnBytesRead
		   lnBufferReads = lnBufferReads + 1

         *** Event Call through
		   THIS.OnFTPBufferUpdate(tnBufferSize,lnBufferReads,lcReadBuffer,this.nCurrentFileSize,THIS)
		ENDIF
        IF (lnRetVal = 1 AND lnBytesRead = 0) OR (lnRetVal = 0) 
           *** Done
           EXIT
        ENDIF
        IF THIS.lCancelDownload
           =FCLOSE(lhFileHandle)
           ERASE (lcTargetFile)
           EXIT
        ENDIF
ENDDO

InternetCloseHandle(hFTPFile)

lnBufferSize = tnBufferSize

*** Event Call through 
THIS.OnFTPBufferUpdate(0,-1,"",this.nCurrentfilesize,THIS)
this.nCurrentFileSize = 0

=FCLOSE(lhFileHandle)

RETURN 0

#IF .F.
TEXT
********************************
*   HELP BUILDER COMMENT BLOCK *
********************************
*:Help Documentation
*:Topic:
wwFTP::FTPGetFileEx                                     

*:Description:
Retrieves a file from an FTP site and allows trapping the OnFTPBufferUpdate event. 
This event allows for status information and also cancellation of a download.

*:Parameters:
<<b>>cSourceFile<</b>>
The source file to retrieve from the server.

<<b>>cTargetFile<</b>>
The target file to save it to on the local machine/network.


*:Returns:
numeric - API error code
API Error result codes can be retrieved with wwGetSystemErrorMsg() 

*:Example:
o=create("wwFTP")

o.FTPConnect("ftp.west-wind.com")
o.FTPGetFileEx("/downloads/pkzip.exe","c:\temp\pkzip.exe")


*:Remarks:
Fires OnFTPBufferUpdate events which you can capture to provide progress information on your download.


*:SeeAlso:
Class wwFTP
wwFTP::OnFTPBufferUpdate


*:Keywords:

*:HelpId:
678

*:ENDHELP
ENDTEXT
#ENDIF
ENDFUNC

************************************************************************
*  OnFtpBufferUpdate
****************************************
***  Function: Event that fires whenever the buffer is updated by FTPGetFileEx(). This method is passed the current download total and the number of chunks retrieved already. You can set lCancelDownload to .T. to force FTPGetEx to stop the file download.
***    Assume: This method is just a placeholder
***      Pass:
***    Return:
************************************************************************
FUNCTION OnFtpBufferUpdate(lnbytesdownloaded,lnbufferreads,lccurrentchunk, lnTotalBytes, loFtp)

#IF .F.
TEXT
********************************
*   HELP BUILDER COMMENT BLOCK *
********************************
*:Help Documentation
*:Topic:
wwftp::onftpbufferupdate                                    

*:Description:
Event that fires whenever the buffer is updated by FTPGetFileEx(). This method is passed the current download total and the number of chunks retrieved already. You can set lCancelDownload to .T. to force FTPGetEx to stop the file download.

*:Parameters:
<<b>>lnbytesdownloaded<</b>>
Total bytes that have been downloaded on this request so far.

<<b>>lnbufferreads<</b>>
Total number of buffers that have been read so far.

<<b>>lccurrentchunk<</b>>
Content of the current download chunk read. File information is incremental so it's possible to capture text and append it to a file incrementally for possibly viewing information while it's downloading.


*:Returns:


*:Example:
*** Download with Status Events
DO wwFtp

PUBLIC o
o=CREATE("myFTP")
wait window nowait "Alt-x to abort download..."
ON KEY LABEL ALT-X o.lCancelDownload = .T.

IF o.FTPConnect("ftp.west-wind.com") # 0
   ? o.nError
   ? o.cErrorMsg
   RETURN
ENDIF

IF o.FtpGetFileEx("wconnect.zip","c:\temp\wconnect.zip")  # 0
  ? o.nError
  ? o.cErrorMsg
  RETURN
ENDIF


ON KEY LABEL ALT-X
RETURN

*** Implementation of Message Display and abort code

Define class myFtp as wwFTP

Function OnFTPBufferUpdate
LPARAMETER lnBytesDownloaded,lnBufferReads,lcCurrentChunk

DO CASE
  CASE lnBufferReads > 0
     wait window "Downloaded: " + TRANSFORM(lnBytesDownloaded)+ " bytes (Alt-X to cancel)" nowait
  CASE lnBufferReads = -1
    wait window "Download aborted..." timeout 2
ENDCASE
RETURN
ENDDEFINE


*:Remarks:


*:SeeAlso:
Class wwftp
wwftp::lCancelDownload
wwftp::nFTPWorkBufferSize
wwftp::FTPGetFileEx
wwftp::FTPSendFileEx

*:Keywords:


*:HelpId:
679

*:ENDHELP

ENDTEXT
#ENDIF
ENDFUNC


************************************************************************
*  FtpConnect
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION FtpConnect(lcServer, lcUsername, lcPassword)
LOCAL lhIP, lhHTTP, lnError, lnHTTPPort

lcServer=LOWER(TRIM(IIF(!EMPTY(lcServer),lcServer,THIS.cServer)))
lcUsername=TRIM(IIF(!EMPTY(lcUsername),lcUsername,THIS.cUsername))
lcPassword=TRIM(IIF(!EMPTY(lcPassword),lcPassword,THIS.cPassword))

IF lcServer = "ftp://"
   lcServer = STRTRAN(lcServer,"ftp://","")
ENDIF   

*** Assign Default Ports
IF THIS.nHTTPPort = 0
   lnHTTPPort = INTERNET_DEFAULT_FTP_PORT
ELSE
   lnHTTPPort = THIS.nHTTPPort
ENDIF

IF THIS.lPassiveFTP
   THIS.nServiceFlags = THIS.nServiceFlags + FTP_CONNECT_PASSIVE
ENDIF

THIS.cServer = lcServer
THIS.nError=0
THIS.cErrorMsg=""

DECLARE INTEGER InternetCloseHandle ;
   IN WinInet.DLL ;
   INTEGER

DECLARE INTEGER GetLastError;
   IN WIN32API

DECLARE INTEGER InternetOpen ;
   IN WININET.DLL ;
   STRING,;
   INTEGER,;
   STRING, STRING, INTEGER

hInetConnection=;
   InternetOpen(THIS.cUserAgent,;
   THIS.nhttpconnecttype,;
   THIS.chttpproxyname,THIS.chttpproxybypass,0)

IF hInetConnection = 0
   THIS.nError=GetLastError()
   THIS.cErrorMsg=THIS.GetSystemErrorMsg(THIS.nError)
   RETURN THIS.nError
ENDIF

THIS.hIPSession=hInetConnection
THIS.WinInetSetTimeout()

DECLARE INTEGER InternetConnect ;
   IN WININET.DLL ;
   INTEGER hIPHandle,;
   STRING lpzServer,;
   INTEGER dwPort, ;
   STRING lpzUserName,;
   STRING lpzPassword,;
   INTEGER dwServiceFlags,;
   INTEGER dwReserved,;
   INTEGER dwReserved


lhFTPSession=;
   InternetConnect(hInetConnection,;
   lcServer,;
   lnHTTPPort,;
   lcUsername,;
   lcPassword,;
   INTERNET_SERVICE_FTP,;
   THIS.nServiceFlags,0)

IF (lhFTPSession = 0)
   lnError = 0
   lcErrMsg = SPACE(256)
   lnErrLen = LEN(lcErrMsg)
   
   DECLARE INTEGER InternetGetLastResponseInfo ;
      IN WININET.DLL ;
      INTEGER @dwError,;
      STRING @szBuffer,;
      INTEGER @dwBufferLength
      
   lnResult = InternetGetLastResponseInfo(@lnError,@lcErrMsg,@lnErrLen)

   =InternetCloseHandle(hInetConnection)

   IF lnError = 0
      THIS.cErrorMsg = TRIM(STRTRAN(lcErrMsg,CHR(0),""))
      IF EMPTY(this.cErrorMsg)
         this.cErrorMsg = "Couldn't connect to FTP Server"
      ENDIF
      THIS.nError = 1
   ELSE
      THIS.cErrorMsg = THIS.GetSystemErrorMsg()
      this.nError = lnError
   ENDIF
   RETURN THIS.nError
ENDIF

THIS.hIPSession = hInetConnection
THIS.hFTPSession = lhFTPSession

IF !EMPTY(THIS.cHTtpproxyusername)
   THIS.SetProxyLogin(,,THIS.hFTPSession)
ENDIF

RETURN 0


#IF .F.
TEXT
********************************
*   HELP BUILDER COMMENT BLOCK *
********************************
*:Help Documentation
*:Topic:
wwftp::ftpconnect

*:Description:
Connects to an FTP site and allows passing of a user name and password.

*:Parameters:
<<b>>cServer<</b>>
The server to connect to. Example: ftp.west-wind.com or 127.0.0.1

<<b>>lcUsername<</b>>
<<i>>Optional<</i>> - username for authenticated connections to the server

<<b>>lcPassword<</b>>
<<i>>Optional<</i>> - password for authenticated connections to the server


*:Returns:


*:Example:
oFTP = CREATE("wwFTP")

oFTP.FTPConnect("ftp.west-wind.com")
oFTP.FTPGetFile("pkzip.exe","c:\temp\pkzip.exe")
oFTP.FTPClose()


*:Remarks:


*:SeeAlso:
Class wwftp


*:Keywords:


*:HelpId:
680

*:ENDHELP

ENDTEXT
#ENDIF

ENDFUNC



************************************************************************
*  FtpClose
****************************************
***  Function: Closes an open Ftp Connection
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION FtpClose()

DECLARE INTEGER InternetCloseHandle ;
   IN WININET.DLL ;
   INTEGER hIPSession

=InternetCloseHandle(THIS.hFTPSession)
=InternetCloseHandle(THIS.hIPSession)   

THIS.hFTPSession=0
THIS.hIPSession=0
   
#IF .F.
TEXT
********************************
*   HELP BUILDER COMMENT BLOCK *
********************************
*:Help Documentation
*:Topic:
wwftp::ftpconnect                                           

*:Description:
Connects to an FTP site and allows passing of a user name and password.

*:Parameters:
<<b>>cServer<</b>>
The server to connect to. Example: ftp.west-wind.com or 127.0.0.1

<<b>>lcUsername<</b>>
<<i>>Optional<</i>> - username for authenticated connections to the server

<<b>>lcPassword<</b>>
<<i>>Optional<</i>> - password for authenticated connections to the server


*:Returns:


*:Example:
oFTP = CREATE("wwFTP")

oFTP.FTPConnect("ftp.west-wind.com")
oFTP.FTPGetFile("pkzip.exe","c:\temp\pkzip.exe")
oFTP.FTPClose()


*:Remarks:


*:SeeAlso:
Class wwftp


*:Keywords:


*:HelpId:
680

*:ENDHELP

ENDTEXT
#ENDIF

ENDFUNC


************************************************************************
*  FtpSendFileEx
****************************************
***  Function: Sends a file to the server 
***    Assume: Connection to the server is already open
***      Pass: lcSourceFile  - full path to file to upload
***            lcTargetFile  - Server relative path to file on server
***    Return: 0 or error code
************************************************************************
FUNCTION FtpSendFileEx(lcSourceFile, lcTargetFile)
LOCAL lhFile, lnRetVal, lnBytesRead, lnBufferReads, lcWriteBuffer, hFTPFile, lnMode

this.nError = 0
this.cErrorMsg = ""

DECLARE INTEGER FtpOpenFile ;
   IN WININET.DLL ;
   INTEGER hIPSession,;
   STRING @lpszFileName,;
   INTEGER dwAcessFlags,;
   INTEGER dwServiceFlags,;
   INTEGER dwContext

DECLARE INTEGER InternetWriteFile ;
   IN WININET.DLL ;
   INTEGER hFTPHandle,;
   STRING lcBuffer,;
   INTEGER cbBuffer,;
   INTEGER @cbBuffer
 
lnMode = IIF(this.nFtpBinary = 1, FTP_TRANSFER_TYPE_BINARY,FTP_TRANSFER_TYPE_ASCII)
hFTPFile = FtpOpenFile(THIS.hFTPSession,TRIM(lcTargetFile)+CHR(0),;
                       GENERIC_WRITE,;
                       lnMode,0)
IF hFTPFile = 0
   THIS.nError = GetLastError()
   IF THIS.nError = 0 
      this.cErrorMsg = this.GetSystemErrorMsg(ERROR_INTERNET_EXTENDED_ERROR)
   ELSE
      THIS.cErrorMsg = THIS.GetSystemErrorMsg()
   ENDIF

   IF this.nError = 0 
      IF EMPTY(THIS.cErrorMsg)
         this.cErrorMsg = "Unable to connect to server..."
      ENDIF
      THIS.nError = -1
   ENDIF
   RETURN THIS.nError
ENDIF

*** Read the file from disk
lhFile = FOPEN(TRIM(lcSourceFile))
IF lhFile = -1
   THIS.cErrorMsg = "Source file doesn't exist or is in use..."
   THIS.nError = 1
   RETURN 1
ENDIF   

*** Set the file size to upload
this.nCurrentFileSize = FSEEK(lhFile,0,2)
FSEEK(lhFile,0,0)

THIS.lCancelDownload = .f.
tnBufferSize = 0
lnBufferReads = 0
DO WHILE .T.
   *** Read one chunk at a time
   lcWriteBuffer = FRead(lhFile,THIS.nFTPWorkBufferSize)
   IF LEN(lcWriteBuffer) = 0
      EXIT
   ENDIF

   *** And write out each chunk
   lnSize=LEN(lcWriteBuffer)
   lnBytesRead = 0
   lnRetval=InternetWriteFile(hFTPFile,;
		   lcWriteBuffer,;
		   lnSize,;
		   @lnBytesRead)

      IF (lnRetVal = 0)
         THIS.nError = GetLastError()
         THIS.cErrorMsg = THIS.GetSystemErrorMsg()
         exit
      ENDIF

    IF lnRetVal = 1 AND lnBytesRead > 0
	   *** Update the input parameters - result buffer and size of buffer
	   tnBufferSize = tnBufferSize + lnBytesRead
	   lnBufferReads = lnBufferReads + 1
	   THIS.OnFTPBufferUpdate(tnBufferSize,lnBufferReads,"",this.nCurrentFileSize)
	ENDIF
	IF THIS.lCancelDownload
      THIS.cErrorMsg = "Download canceled by user"
	  EXIT
	ENDIF
    IF (lnRetVal = 1 AND lnBytesRead = 0) OR (lnRetVal = 0) 
       EXIT
    ENDIF
    
    DOEVENTS
ENDDO

InternetCloseHandle(hFTPFile)
lnBufferSize = tnBufferSize

*** Notify Completion
THIS.OnFTPBufferUpdate(0,-1,"",this.nCurrentFileSize)

this.nCurrentFileSize = 0
FCLOSE(lhFile)

RETURN this.nError

#IF .F.
TEXT
********************************
*   HELP BUILDER COMMENT BLOCK *
********************************
*:Help Documentation
*:Topic:
wwftp::FTPSendFileEx                                        

*:Description:
Uploads a file to an FTP site with event progress information and the ability to cancel.

*:Parameters:
<<b>>cSourceFile<</b>>
Local file to send to the server.

<<b>>cTargetFile<</b>>
Filename on the server to copy the file to. 


*:Returns:


*:Example:


*:Remarks:
You must have rights to write to the FTP directory based on the FTP server permissions. Write permissions are not granted to FTP directories by default, so you have to make sure that you can upload files in the first place. You can check this out with your regular FTP client.

*:SeeAlso:
Class wwftp
wwFTP::OnFTPBufferUpdate
wwFTP::lCancelDownload

*:Keywords:


*:HelpId:
682

*:ENDHELP

ENDTEXT
#ENDIF
ENDFUNC


*** Returns a datetime value from a date time structure (binary form)
PROCEDURE parsefiletime
	LPARAMETER lcFTime
	LOCAL lcSysTime, lnYear, lnMonth, lnDay, lnHour, lnMinute, lnSecond, lcDTime

	DECLARE INTEGER FileTimeToSystemTime ;
	   IN WIN32API ;
	   STRING sFileTime, ;
	   STRING @sSysTime
	 
	   
	lcSysTime = SPACE(16)
	FileTimeToSystemTime( lcFTime, @lcSysTime)
	lnYear = CharToBin(LEFT(lcSysTime,2))
	lnMonth = CharToBin(SUBSTR(lcSysTime,3,2))
	lnDay = CharToBin(SUBSTR(lcSysTime,7,2))
	lnHour = CharToBin(SUBSTR(lcSysTime,9,2))
	lnMinute = CharToBin(SUBSTR(lcSysTime,11,2))
	lnSecond = CharToBin(SUBSTR(lcSysTime,13,2))

	lcDTime =  ALLTRIM(STR(lnYear)) + '-' + ALLTRIM(STR(lnMonth)) + '-' + ALLTRIM(STR(lnDay))  + ' ' + ;
	   ALLTRIM(STR(lnHour)) + ':' + ALLTRIM(STR(lnMinute)) + ':' + ALLTRIM(STR(lnSecond))

	RETURN EVALUATE( "{^" + lcDTime + "}" )
ENDFUNC

************************************************************************
* FTPSendFile
*********************************
***  Function: Sends a file from the local machine to an FTP server
***    Assume: Synchronous method - waits for completion without
***                                 status reports
***      Pass: lcFTPServer     -
***            lcSource        -   Source file on the FTP site
***            lcTarget        -   Name to save to
***            lcUsername      -   Username - "" for Anonymous
***            lcPassword      -   Password - "" for Anonymous
***    Return: 0 on success - otherwise Win32 API Error Code
***            12003  -  Permissions error
***            3      -  Path not found
***            5      -  Access denied (local file)
************************************************************************
FUNCTION FtpSendFile(lcFTPServer, lcSource, lcTarget, lcUsername, lcPassword)
LOCAL lnResult

lcFTPServer=TRIM(IIF(type("lcFTPServer")="C",lcFTPServer,THIS.cFTPServer))
lcSource=TRIM(IIF(type("lcSource")="C",lcSource,THIS.cFTPSource))
lcTarget=TRIM(IIF(type("lcTarget")="C",lcTarget,THIS.cFTPTarget))
lcUserName=TRIM(IIF(type("lcUserName")="C",lcUserName,THIS.cUsername))
lcPassword=TRIM(IIF(type("lcPassword")="C",lcPassword,THIS.cPassword))
lnBinary=THIS.nFTPBinary

IF (lnBinary = 1)
   lnBinary = FTP_TRANSFER_TYPE_BINARY
ELSE
   lnBinary = FTP_TRANSFER_TYPE_ASCII
ENDIF
   
lnResult = THIS.FTPConnect(lcFTPServer,lcUserName,lcPassWord)
IF lnResult != 0
   RETURN lnResult
ENDIF

DECLARE Integer FtpPutFile ;
   IN WinInet.dll ;
   Integer hFTPSession,;
   STRING lpzSource,;
   STRING lpzTarget,;
   INTEGER nBinary,;
   INTEGER nReserved
   
lnResult=FtpPutFile(THIS.hFTPSession,;
		  lcSource+CHR(0),;
		  lcTarget+CHR(0),;
		  lnBinary,;
		  0)

IF lnResult = 0
   THIS.nError = GetLastError()
   THIS.cErrorMsg = THIS.GetSystemErrorMsg()
   THIS.FTPClose()
   RETURN THIS.nError
ENDIF

THIS.FTPClose()
   
RETURN 0
ENDFUNC


************************************************************************
*  FtpGetFile
****************************************
***  Function: Self contained FTP file retrieval from server.
***            Use for single file transfers
***    Assume: Does NOT require a connection set up
***      Pass: 
***    Return:
************************************************************************
FUNCTION FtpGetFile(lcFTPServer, lcSource, lcTarget, lnBinary, lcUsername, lcPassword)

lnBinary=IIF(EMPTY(lnBinary),FTP_TRANSFER_TYPE_BINARY,lnBinary)

IF EMPTY(lcFTPServer)
  lcFTPServer = THIS.cFTPServer
ENDIF
IF EMPTY(lcSource)
  lcSource = THIS.cFTPSource
ENDIF
IF EMPTY(lcTarget)
  lcTarget = THIS.cFTPTarget
ENDIF
IF EMPTY(lcUserName)
   lcUserName = THIS.cUserName  
ENDIF
IF EMPTY(lcPassWord)
   lcPassword = THIS.cPassword    
ENDIF

lnResult = THIS.FTPConnect(TRIM(lcFTPServer),TRIM(lcUserName),TRIM(lcPassWord))
IF lnResult # 0 
   RETURN lnResult
ENDIF

DECLARE Integer FtpGetFile ;
   IN WinInet.dll ;
	Integer dwIPSession,;
	String cSource,;
	String cTarget, ;
	Integer bNoOverwrite,;
	INTEGER nAttributes, ;
	Integer nFlags, ;
	Integer nContext

lnResult = FtpGetFile(THIS.hFTPSession,TRIM(lcSource),;
                     TRIM(lcTarget),0,FILE_ATTRIBUTE_NORMAL,;
                     lnBinary + INTERNET_FLAG_RELOAD,0)

IF lnResult = 0
   THIS.nError = GetLastError()
   THIS.cErrorMsg = THIS.GetSystemErrorMsg()
   THIS.FTPClose()
   RETURN THIS.nError
ENDIF

THIS.FTPClose()
   
RETURN 0

#IF .F.
TEXT
********************************
*   HELP BUILDER COMMENT BLOCK *
********************************
*:Help Documentation
*:Topic:
wwftp::FTPGetFile                                           

*:Description:
Simple FTP retrieval method. This method retrieves a file from an FTP server with a single method call. This method is synchronous and does not fire any events.

*:Parameters:
<<b>>cServer<</b>>
The server to connect to. Example: <<i>>ftp.west.wind.com<</i>> or <<i>>123.123.123.213<</i>>

<<b>>cSourceFile<</b>>
Source File on the FTP Server.

<<b>>cTargetFile<</b>>
Target file on the local system. 

<<b>>cUsername<</b>>
<<i>>Optional<</i>> - Username when connecting to a secure directory.

<<b>>cPassword<</b>>
<<i>>Optional<</i>> - Password when connecting to a secure directory


*:Returns:


*:Example:
o=create("wwFTP")

o.FTPGetFile("ftp.west-wind.com","/downloads/pkzip.exe",;
             "c:\temp\pkzip.exe")


*:Remarks:
This method provides no mechanism for reporting progress information.

*:SeeAlso:
Class wwftp
wwFTP::FTPGetFileEx

*:Keywords:


*:HelpId:
704

*:ENDHELP

ENDTEXT
#ENDIF

ENDFUNC


************************************************************************
*  aFtpDir
****************************************
***  Function: Retrieves an FTP directory file listing into an array
***    Assume: connection must be open with FtpOpen() first
***      Pass: @laFiles  -  array to receive files
***            lcFileSpec - file spec (can include wildcards)
***    Return: file count or 0
************************************************************************
FUNCTION aFtpDir(lafiles,lcfilespec)

IF EMPTY(lcFileSpec)
   lcFileSpec = "*.*"
ENDIF

*** W32_FILE_FIND structure
lsFileFind = SPACE(320)
lnFindHandle = 0

*** Offsets into structure
#DEFINE POS_FILENAME  45
#DEFINE POS_FILESIZE  33
#DEFINE POS_FILEATTRIBUTES 1

#DEFINE MAX_PATH 260
#DEFINE DIR_DIMENSIONS 4

THIS.nError = 0

DECLARE INTEGER FtpFindFirstFile ;
   IN WININET.DLL ;
   INTEGER hFTPSession,;
   STRING cFileSpec,;
   STRING @sFileFind,;
   INTEGER nFlags,;
   INTEGER nContext

*** Find the first file and get info into lsFileFind structure
lnFindHandle = ;
   FtpFindFirstFile(THIS.hFTPSession,;
   lcfilespec,;
   @lsFileFind,;
   INTERNET_FLAG_RELOAD,0)

IF lnFindHandle = 0
   THIS.nError = GetLastError()

   *** No files should not be considered an error
   IF THIS.nError = 18
      THIS.nError = 0
      RETURN 0
   ENDIF
   
   THIS.cErrorMsg = THIS.GetSystemErrorMsg()
   RETURN 0
ENDIF

*** Parse out the various fields we're interested in
DIMENSION lafiles[1,DIR_DIMENSIONS]
lcFileName = SUBSTR(lsFileFind,POS_FILENAME,MAX_PATH)
lafiles[1,1] = SUBSTR(lcFileName,1,ATC(CHR(0),lcFileName)-1)

lcAttributes = SUBSTR(lsFileFind,POS_FILEATTRIBUTES,4)
lafiles[1,3] = CharToBin(lcAttributes)

lcSize = SUBSTR(lsFileFind,POS_FILESIZE,4)
lafiles[1,2] = CharToBin(lcSize)

laFiles[1,4] = THIS.ParseFileTime(SUBSTR(lsFileFind, 21, 8))

lnFileCount = 1

DECLARE INTEGER InternetFindNextFile ;
   IN WinInet.DLL ;
   INTEGER hFindHandle,;
   STRING @lsFileFind


*** Loop through the file list
DO WHILE InternetFindNextFile(lnFindHandle,@lsFileFind)  # 0
   lnFileCount = lnFileCount + 1
   
   lcFileName = SUBSTR(lsFileFind,POS_FILENAME,MAX_PATH)
   DIMENSION lafiles[lnFileCount,DIR_DIMENSIONS]
   lafiles[lnFileCount,1] = SUBSTR(lcFileName,1,ATC(CHR(0),lcFileName)-1)

   lcAttributes = SUBSTR(lsFileFind,POS_FILEATTRIBUTES,4)
   lafiles[lnFileCount,3] = CharToBin(lcAttributes)

   lcSize = SUBSTR(lsFileFind,POS_FILESIZE,4)
   lafiles[lnFileCount,2] = CharToBin(lcSize)
 
   lafiles[lnFileCount,4] = THIS.ParseFileTime(SUBSTR(lsFileFind, 21, 8))
ENDDO

InternetCloseHandle(lnFindHandle)

RETURN lnFileCount

************************************************************************
*  FtpListFiles
****************************************
***  Function: Retrieves an FTP directory file listing into a Collection
***    Assume: connection must be open with FtpOpen() first
***      Pass: lcFileSpec - file spec (can include wildcards)
***    Return: Collection or null Collection may be empty 
***            if no files are found
************************************************************************
FUNCTION FtpListFiles(lcfilespec)
LOCAL loCol, loFile, lnCount
LOCAL ARRAY laFiles[1,4]

IF EMPTY(lcFileSpec)
   lcFileSpec = "*.*"
ENDIF

DIMENSION laFiles[1,4]
lnCount = this.aFtpDir(@laFiles,lcFileSpec)
IF lnCount < 0
   RETURN null
ENDIF

loCol = CREATEOBJECT("Collection")

IF lnCount = 0
   RETURN loCol
ENDIF   

FOR x=1 to lnCount
   loFile = CREATEOBJECT("Empty")
   ADDPROPERTY(loFile,"Name",laFiles[x,1])
   ADDPROPERTY(loFile,"FullName",laFiles[x,1])
   ADDPROPERTY(loFile,"Length",laFiles[x,2])
   ADDPROPERTY(loFile,"FileAttribute",laFiles[x,3]) 
   ADDPROPERTY(loFile,"LastWriteTime",laFiles[x,4])
   
   loCol.Add(loFile)
ENDFOR
RETURN loCol
* EOF FtpListFiles

#IF .F.
TEXT
********************************
*   HELP BUILDER COMMENT BLOCK *
********************************
*:Help Documentation
*:Topic:
wwftp::aFTPDir

*:Description:
This method retrieves a directory listing from an FTP directory into an array. The array returns the name, file attributes and size of the file.

*:Parameters:
<<b>>@aFiles<</b>>
Array that contains the list of files. The array has 4 dimensions:

  1 - Filename
  2 - File Size
  3 - File Attribute (numeric)
  4 - Date Time value (not implemented yet)

<<b>>cFileSpec<</b>>
A filespec that specifies which directory to get a listing for. The file spec should include wild card characters like *.* to specify all files or *.txt to show all text files.


*:Returns:
numeric - the number of files returned by the directory.
If this value is 0 you can check the nError and cErrorMsg properties for error conditions, but only if an error occurred.

*:Example:
oFTP = CREATE("wwFTP")

oFTP.FTPConnect("ftp.west-wind.com")

DIMENSION laFiles[1,4]
lnResult = oFTP.aFTPDir(@laFiles,"/*.*")
FOR x=1 to lnResult
   ? laFiles[x,1],laFiles[x,2],laFiles[x,3]
ENDFOR

*:Remarks:
This method requires that you manually call FTPConnect and FTPClose to open and close the FTP session.

*:SeeAlso:
Class wwftp

*:Keywords:


*:HelpId:
703

*:ENDHELP

ENDTEXT
#ENDIF
ENDFUNC


*** Creates a new FTP directory on the server.
PROCEDURE ftpcreatedirectory
	LPARAMETER lcPath

	IF EMPTY(lcPath) 
	   RETURN .F.
	ENDIF
	   
	DECLARE INTEGER FtpCreateDirectory ;
	   IN WININET.DLL ;
	   INTEGER hConnect,;
	   STRING lpszDirectory

	IF FtpCreateDirectory(THIS.hftpsession,lcPath)=0
	   THIS.nError = GetLastError()
	   THIS.cErrorMsg = THIS.GetSystemErrorMsg()
	   RETURN .F.
	ENDIF
	
	RETURN .T.	
ENDFUNC

************************************************************************
*  FtpRemoveDirectory
****************************************
***  Function: Removes a path from the server. Path is server relative
***    Assume: Connection must be open with FtpOpen
***      Pass: lcPath - Server relative path to remove
***    Return: .T. or .F.
************************************************************************
FUNCTION FtpRemoveDirectory(lcPath)

	IF EMPTY(lcPath) 
	   RETURN .F.
	ENDIF
	   
	DECLARE INTEGER FtpRemoveDirectory ;
	   IN WININET.DLL ;
	   INTEGER hConnect,;
	   STRING lpszDirectory

	IF FtpRemoveDirectory(THIS.hftpsession,lcPath)=0
	   THIS.nError = GetLastError()
	   THIS.cErrorMsg = THIS.GetSystemErrorMsg()
	   RETURN .F.
	ENDIF
	
	RETURN .T.
ENDFUNC

************************************************************************
*  FtpDeleteFile
****************************************
***  Function: Deletes a file from the server
***    Assume: Connection must be open
***      Pass: lcFile - server relative file to delete
***    Return:
************************************************************************
FUNCTION FtpDeleteFile(lcFile)
	DECLARE INTEGER FtpDeleteFile ;
	   IN WinInet.dll ;
	   INTEGER hFTPSession,;
	   STRING cFileName
	   
	IF FtpDeleteFile(THIS.hFTPSession,lcFile + CHR(0)) = 0
	   THIS.nError = GetLastError()
	   THIS.cErrorMsg = THIS.GetSystemErrorMsg()
	   RETURN THIS.nError
	ENDIF
	   
	RETURN 0

	#IF .F.
	TEXT
	********************************
	*   HELP BUILDER COMMENT BLOCK *
	********************************
	*:Help Documentation
	*:Topic:
	wwftp::FTPDeleteFile                                        

	*:Description:
	Deletes a file on the FTP server.

	*:Parameters:
	<<b>>cFileName<</b>>
	The filename to delete on the server. This filename can include a FTP server relative path such as "/uploads/somefile.txt". No wildcards are allowed.


	*:Returns:
	numeric - Windows API or WinInet API error code. 0 - on success, other on failure

	*:Example:
	oFTP = CREATE("wwFTP")

	oFTP.FTPConnect("ftp.west-wind.com")

	DIMENSION laFiles[1,4]
	IF  oFTP.DeleteFile("/uploads/somefile.txt") # 0
	   ? oFTP.cErrorMsg
	   RETURN
	ENDIF

	? "File deleted..."

	oFTP.Close()

	*:Remarks:
	This method requires that you manually call FTPConnect and FTPClose to open and close the FTP session. 

	In order to delete files you have to have the appropriate rights on the server.

	*:SeeAlso:
	Class wwftp
	wwFTP::FTPConnect
	wwFTP::FTPClose

	*:Keywords:


	*:HelpId:
	702

	*:ENDHELP

	ENDTEXT
	#ENDIF
ENDFUNC


************************************************************************
*  FtpCommand
****************************************
***  Function: Issue an arbitrary FTP Command 
***    Assume: Connection must exist with FtpOpen
***      Pass:
***    Return:
************************************************************************
FUNCTION FtpCommand(lcCommand, lcBuffer, lnSize)

this.cErrorMsg = ""

DECLARE INTEGER FtpCommand ;
   IN WININET.DLL ;
   INTEGER hConnect,;
   INTEGER fExpectResponse,;
   INTEGER dwFlags,;
   STRING lpzCommand,;
   INTEGER dwContext,;
   INTEGER @phFTPCommandResultHandle

lhResult = 0
lnResult = FtpCommand(THIS.hFTPSession,0,FTP_TRANSFER_TYPE_ASCII,lcCommand,0,@lhResult)
IF lnResult = 0
   THIS.nError = GetLastError()
   THIS.cErrorMsg = THIS.GetSystemErrorMsg()
   RETURN lnResult
ENDIF

IF LEN(lcBuffer) = 0
   lcBuffer = SPACE(4096)
   lnSize = LEN(lcBuffer)
ELSE
   lnSize = LEN(lcBuffer)
ENDIF


DECLARE INTEGER InternetReadFile ;
   IN WININET.DLL ;
   INTEGER hFTPHandle,;
   STRING lcBuffer,;
   INTEGER cbBuffer,;
   INTEGER @cbBuffer
   
lnBytesRead = 0
lnRetval=InternetReadFile(lhResult,;
		   @lcBuffer,;
		   lnSize,;
		   @lnBytesRead)

IF lnRetVal = 1 AND lnBytesRead > 0
   lnSize = lnBytesRead
   lcBuffer = SUBSTR(lcBuffer,1,lnBytesRead)
ELSE
   lcBuffer = ""
   lnSize = 0
ENDIF

InternetCloseHandle(lhResult)

RETURN 0
ENDFUNC

************************************************************************
*  FtpRenameFile
****************************************
***  Function: Renames a file on the FTP Server
***    Assume: 
***      Pass:
***    Return:
************************************************************************
FUNCTION FtpRenameFile(lcOldName, lcNewName)

Declare integer FtpRenameFile ;
   in wininet.dll ;
   INTEGER HCONNECT, ;
   STRING lpszExisting, ;
   STRING lpszNew

lnResult = FtpRenameFile(THIS.hftpsession, lcOldName + CHR(0), lcNewName + CHR(0))
IF lnResult = 0
   THIS.nError = GetLastError()
   THIS.cErrorMsg = THIS.GetSystemErrorMsg()
   RETURN THIS.nError
ENDIF
   
RETURN 0
ENDFUNC


************************************************************************
*  FtpSetDirectory
****************************************
***  Function: Sets the current directory on the FTP server
***    Assume:
***      Pass: lcPath  -  Server relative path to change to
***    Return: .T. or .F.
************************************************************************
FUNCTION FtpSetDirectory(lcPath)

IF EMPTY(lcPath) 
   RETURN .F.
ENDIF
   
DECLARE INTEGER FtpSetCurrentDirectory ;
   IN WININET.DLL ;
   INTEGER hConnect,;
   STRING lpszDirectory

RETURN IIF(FtpSetCurrentDirectory(THIS.hftpsession,lcPath)=0,.F.,.T.)

ENDFUNC


************************************************************************
*  FtpSendFileEx2
****************************************
***  Function: Sends a file to an FTP server. Uses simpler PutFile
***            operation. Try this if other mechanisms of sending
***            fail.
***    Assume: Doesn't send progress info to OnFtpBufferUpdate
***            as it makes a single call to FtpFtpPutFile.
***      Pass:
***    Return:
************************************************************************
FUNCTION FtpSendFileEx2(lcSource, lcTarget)

DECLARE Integer FtpPutFile ;
   IN WinInet.dll ;
   Integer hFTPSession,;
   STRING lpzSource,;
   STRING lpzTarget,;
   INTEGER nBinary,;
   INTEGER nReserved

lnMode = IIF(this.nFtpBinary = 1, FTP_TRANSFER_TYPE_BINARY,FTP_TRANSFER_TYPE_ASCII)
lnResult=FtpPutFile(THIS.hFTPSession,;
        lcSource+CHR(0),;
        lcTarget+CHR(0),;
        lnMode,;
        0)

IF lnResult = 0
   THIS.nError = GetLastError()
   THIS.cErrorMsg = THIS.GetSystemErrorMsg()
   WAIT WINDOW TIMEOUT 1 this.cErrorMsg
   THIS.FTPClose()
   RETURN THIS.nError
ENDIF

RETURN 0
ENDFUNC


PROCEDURE nftpport_assign
LPARAMETERS vNewVal
*To do: Modify this routine for the Assign method
THIS.nHttpPort = m.vNewVal
this.nFtpPort = m.vNewVal
ENDFUNC



************************************************************************
*  SetProxyLogin
****************************************
***  Function: Allows you to specify proxy information for a request
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION SetProxyLogin()
LPARAMETERS tcUserName, tcPassword, hIPHandle
LOCAL lcUsername, lcPassword, lpBuffer, lpdwBufferLength, nSuccess

lcUserName = IIF(!EMPTY(tcUserName),tcUserName,THIS.cHTTPProxyUserName)
lcPassword = IIF(!EMPTY(tcPassword),tcPassword,THIS.cHTTPProxyPassword)
hIPHandle = IIF(EMPTY(hIPHandle),THIS.hFtpSession,hIPHandle)

IF EMPTY(lcUsername)
   RETURN
ENDIF   

DECLARE INTEGER InternetSetOption IN WinInet.dll ;
    INTEGER hInternet, ;
    INTEGER dwOption, ;
    STRING @lpBuffer, ;
    LONG lpdwBufferLength

INTERNET_OPTION_PROXY_USERNAME = 43
INTERNET_OPTION_PROXY_PASSWORD = 44

IF !EMPTY(lcUserName)
    lpBuffer = lcUserName 
    dwBufferLength = LEN(lpBuffer) 
    dwOption = INTERNET_OPTION_PROXY_USERNAME
    nSuccess = InternetSetOption(hIPHandle, dwOption, @lpBuffer, dwBufferLength)
    IF nSuccess <> 1
         THIS.nError = GetLastError()
         THIS.cErrorMsg = THIS.GetSystemErrorMsg(THIS.nError)
         RETURN .F.
     ENDIF
ENDIF

IF !EMPTY(lcPassword)
   lpBuffer = lcpassword
   dwBufferLength = LEN(lpBuffer) 
   dwOption = INTERNET_OPTION_PROXY_PASSWORD
   nSuccess = InternetSetOption(hIPHandle, dwOption, @lpBuffer, dwBufferLength)
   IF nSuccess <> 1
      THIS.nError = GetLastError()
      THIS.cErrorMsg = THIS.GetSystemErrorMsg(THIS.nError)
      RETURN .F.
   ENDIF
ENDIF

RETURN .T.
ENDFUNC



************************************************************************
* GetLastInternetError
*********************************
***  Function: Retrieves the last WinInet error using WinInet's error 
***            responses.
***      Pass: lnError   -  Error Code to resolve(Optional)
***    Return: Error Message or "" 
************************************************************************
FUNCTION GetLastInternetError
LPARAMETERS lnError

lnError=IIF(type("lnError")="N",lnError,THIS.nError)

DECLARE INTEGER InternetGetLastResponseInfo ;
    IN WININET.DLL ;
    INTEGER @lpdwError,;
    STRING @lpszBuffer,;
    INTEGER @lpcbSize 

lcErrorMsg=SPACE(1024)
lnSize=LEN(lcErrorMsg)

=InterNetGetLastResponseInfo(@lnError,@lcErrorMsg,@lnSize)

IF lnSize < 2
   RETURN ""
ENDIF

RETURN SUBSTR(lcErrorMsg,1,lnSize)
ENDFUNC

********************************************************
* GetSystemErrorMsg
*********************************
***  Function: Returns an Error Message for the last 
***            error value set in nError property.
***    Assume: nError was set by last operation
***    Return: Error String or ""
********************************************************
FUNCTION GetSystemErrorMsg
LPARAMETERS lnErrorNo, llAPI
LOCAL szMsgBuffer,lnSize

lnErrorNo=IIF(type("lnErrorNo")="N",lnErrorNo,THIS.nError)

IF lnErrorNo = ERROR_INTERNET_EXTENDED_ERROR
  RETURN THIS.GetLastInternetError()
ENDIF

szMsgBuffer=SPACE(500)
DECLARE INTEGER FormatMessage ;
     IN WIN32API ;
     INTEGER dwFlags ,;
     INTEGER lpvSource,;
     INTEGER dwMsgId,;
     INTEGER dwLangId,;
     STRING @lpBuffer,;
     INTEGER nSize,;
     INTEGER  Arguments

DECLARE INTEGER GetModuleHandle ;
   IN WIN32API ;
   STRING

lnModule=GetModuleHandle("wininet.dll")
IF lnModule # 0 AND !llAPI
    lnSize=FormatMessage(FORMAT_MESSAGE_FROM_HMODULE,lnModule,lnErrorNo,;
                         0,@szMsgBuffer,LEN(szMsgBuffer),0)
ELSE
  lnSize=0                     
ENDIF                     

IF lnSize > 2
  szMsgBuffer=SUBSTR(szMsgBuffer,1, lnSize -2  )
ELSE
  *** REtry with 12000 less - WinInet return Windows API file error codes
  lnSize=FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,0,lnErrorNo,;
                     0,@szMsgBuffer,LEN(szMsgBuffer),0)

   IF lnSize > 2
      szMsgBuffer="Win32 API: " + SUBSTR(szMsgBuffer,1, lnSize-2 )
   ELSE
      szMsgBuffer=""  
   ENDIF
ENDIF
               
RETURN szMsgBuffer
ENDFUNC


********************************************************
* WinInetSetTimeout
*********************************
***  Function: Sets various timeout for use with a 
***            WinInet Connection
***      Pass: dwTimeoutSecs - Secs to wait for timeout
********************************************************
FUNCTION WinInetSetTimeout
LPARAMETERS dwTimeoutSecs, lnHandle

dwTimeoutSecs=IIF(VARTYPE(dwTimeoutSecs)="N",;
   dwTimeoutSecs,THIS.nconnecttimeout)
   
IF dwTimeoutSecs = 0
   *** Just use the default
   RETURN
ENDIF   

IF EMPTY(lnHandle)
   lnHandle = THIS.hIPSession
ENDIF      

DECLARE INTEGER InternetSetOption ;
   IN WININET.DLL ;
   INTEGER HINTERNET,;
   INTEGER dwFlags,;
   INTEGER @dwValue,;
   INTEGER 

dwTimeoutSecs=dwTimeoutSecs * 1000   && to milliseconds
llRetVal=InternetSetOption(lnHandle,;
   INTERNET_OPTION_CONNECT_TIMEOUT,;
   @dwTimeoutSecs,4)

llRetVal=InternetSetOption(lnHandle,;
   INTERNET_OPTION_RECEIVE_TIMEOUT,;
   @dwTimeoutSecs,4)

llRetVal=InternetSetOption(lnHandle,;
   INTERNET_OPTION_SEND_TIMEOUT,;
   @dwTimeoutSecs,4)

*   dwTimeoutSecs=1  &&// Retry only 1 time
*   llRetVal=InternetSetOption(lnHandle,;
*      INTERNET_OPTION_CONNECT_RETRIES,;
*      @dwTimeoutSecs,4)
ENDFUNC

ENDDEFINE
*
*** EndDefine: wwftp
**************************************************
