#INCLUDE wconnect.h
DO wwFTP
DO wwDotnetBridge
SET PROCEDURE TO wwSftp ADDITIVE

*************************************************************
DEFINE CLASS wwSFTP AS wwftp  OF wwFtp.prg
*************************************************************
*: Author: Rick Strahl
*:         (c) West Wind Technologies, 2016
*:Contact: http://www.west-wind.com
*:Created: 12/30/2016
*************************************************************
#IF .F.
*:Help Documentation
*:Topic:
Class wwSFTP

*:Description:

*:Example:

*:Remarks:

*:SeeAlso:


*:ENDHELP
#ENDIF

oBridge = null

*** Custom Properties

*** Instance of the SSH.NET SftpClient 
*** from here additional connection settings can be made
oSFTP = null

*oSFTPClient = null

*** Default SFTP Port
nFtpPort = 23

*** Optional PrivateKeyFile to connect instead of Password
cPrivateKeyFile = ""
cPrivateKeyFilePassword = ""

*** Stock Properties
************************************************************************
*  Init
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Init()

this.Load()

ENDFUNC
*   Init

************************************************************************
*  Load
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Load()

IF !ISNULL(this.oSFTP)
   RETURN
ENDIF   

IF ISNULL(this.oBridge)
	this.oBridge = GetwwDotnetBridge()
ENDIF

THIS.oSFTP = this.oBridge.CreateInstance("Westwind.WebConnection.SftpFtpClient")

IF ISNULL(THIS.oSFTP )
   ERROR "Unable to load SFTP Client. Make sure Renci.SshNet.dll is available." + ;
         CRLF + this.oBridge.cErrorMsg
ENDIF

ENDFUNC
*   Load


************************************************************************
*  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, lcFtpSource, lcLocalTarget, lnBinary, lcUsername, lcPassword)

IF ISNULL(this.oSftp)
   this.Load()
ENDIF   

llResult = this.oSFtp.DownloadFileSimple(lcFtpSource,FULLPATH(lcLocalTarget),;
				lcFtpServer,;
				INT(this.nFtpPort),;
				lcUsername, lcPassword)

IF (!llResult)
   this.cErrorMsg = this.oSFTP.ErrorMessage
   RETURN -1
ENDIF

RETURN 0
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)

IF ISNULL(this.oSftp)
   this.Load()
ENDIF   

llResult = this.oSFtp.UploadFileSimple(FULLPATH(lcSource),lcTarget,;
				lcFtpServer,;
				INT(this.nFtpPort),;
				lcUsername, lcPassword)

IF (!llResult)
   this.cErrorMsg = this.oSFTP.ErrorMessage
   RETURN -1
ENDIF

RETURN 0
ENDFUNC



************************************************************************
*  FtpConnect
****************************************
***  Function: Sets up connection to the server. As most Internet protocols
***            this doesn't actually connect, but just checks for valid
***            names and DNS entries.
***    Assume:
***      Pass: 
***    Return: SftpClient or null on failure
************************************************************************
FUNCTION FtpConnect(lcServer, lcUsername, lcPassword)
LOCAL loSftpClient, loSftp

THIS.nError = 0

IF EMPTY(lcServer)
   lcServer = this.cFtpServer
ENDIF

IF ISNULLOREMPTY(lcUserName)
   lcUsername = this.cUsername
ENDIF
IF ISNULLOREMPTY(lcPassword)
   lcPassword = this.cPassword
ENDIF


IF ISNULLOREMPTY(lcUserName)
   lcUsername = ""
ENDIF
IF ISNULLOREMPTY(lcPassword)
   lcPassword = ""
ENDIF   

loSftp = this.oSftp

loSftp.Timeout = this.nConnectTimeout

*** Messing with this buffer causes file corruption
loSftp.BufferSize = this.nFtpWorkBuffersize

loSftp.ProxyName = this.cHttpProxyName
loSftp.ProxyUsername = this.cHttpProxyUsername
loSftp.ProxyPassword = this.cHttpProxyPassword

IF !ISNULLOREMPTY(this.cPrivateKeyFile)
   loSftp.PrivateKeyFile = this.cPrivateKeyFile
   loSftp.PrivateKeyFilePassword = this.cPrivateKeyFilePassword
ENDIF

loSftp.wwSftp = this

loSftpClient = loSftp.Connect(lcServer, INT(this.nFtpPort),lcUsername,lcPassword)

IF ISNULL(loSftpClient)
   THIS.cErrorMsg = "Unable to connect to SFTP server: " + this.oSftp.ErrorMessage
   THIS.nError = 1
ENDIF

RETURN THIS.nError
ENDFUNC

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

IF !ISNULL(this.oSFTP)
	THIS.oSFtp.Close()	
ENDIF

ENDFUNC

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

THIS.FtpClose()
THIS.oSFTP = null
THIS.oBridge = null

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 - -1 on failure 
************************************************************************
FUNCTION FtpGetFileEx(lcFtpSourceFile, lcLocalTargetFile)

llResult = this.oSFtp.DownloadFile(lcFtpSourceFile,FULLPATH(lcLocalTargetFile))


IF (!llResult)
   this.cErrorMsg = this.oSFtp.ErrorMessage
   RETURN -1
ENDIF   

RETURN 0


************************************************************************
*  FtpSendFileEx
****************************************
***  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 - -1 on failure 
************************************************************************
FUNCTION FtpSendFileEx(lcLocalSourceFile, lcFtpTargetFile)
LOCAL lcFile, llResult

lcFile = LOWER(FULLPATH(lcLocalSourceFile))
IF !FILE(lcFile)
   this.cErrorMsg = "Invalid file passed for uploading."
   RETURN -1
ENDIF

   
llResult = this.oSFtp.UploadFile(lcFile,lcFtpTargetFile)
IF (!llResult)
   this.cErrorMsg = this.oSFtp.ErrorMessage
   RETURN -1
ENDIF   

RETURN 0

************************************************************************
*  aFtpDir
****************************************
***  Function: retrieves a listing of files for a given folder
***    Assume: Obsolete - use FtpListFiles instead
***      Pass: @laFiles - Array to receive files
***             lcPath  - Server Path for which to list files
***                       Note: no wildcards or file specs
***    Return: count of files retrieved
************************************************************************
FUNCTION aFtpDir(laFiles,lcPath)
LOCAL loFiles, lnCount, lnX, loFile

loFiles = THIS.FtpListFiles(lcPath)
IF ISNULL(loFiles)
   RETURN -1
ENDIF

lnCount = loFiles.Count
IF lnCount = 0
   RETURN 0
ENDIF
   
DIMENSION laFiles[lnCount,4]   

FOR lnX = 1 TO lnCount
   loFile = loFiles.Item(lnX)
   laFiles[lnX,1] = loFile.Name
   laFiles[lnX,2] = loFile.Length
   laFiles[lnX,3] = loFile.FileAttribute
   laFiles[lnX,4] = loFile.LastWriteTime
ENDFOR 

RETURN lnCount
ENDFUNC
*   aFtpDir

************************************************************************
*  FtpListFiles
****************************************
***  Function: Returns a list of file
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION FtpListFiles(lcFileSpec)
LOCAL lnCount, loFileList, loFiles, lnX

IF EMPTY(lcFileSpec)
   lcFileSpec = ""
ENDIF 

loFiles = this.oBridge.InvokeMethod(this.oSftp,"ListFiles",lcFileSpec)

IF ISNULL(loFiles) 
   this.cErrorMsg = this.oSftp.ErrorMessage
   RETURN null
ENDIF

loFileList = CREATEOBJECT("Collection")

lnCount = loFiles.Count
FOR lnX = 0 TO lnCount  -1
   loFileList.Add(loFiles.Item(lnX))
ENDFOR
 
RETURN loFileList
ENDFUNC
*   FtpListFiles


************************************************************************
*  FtpDeleteFile
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION FtpDeleteFile(lcFile)

IF EMPTY(lcFile)
  this.cErrorMsg = "No file specified to delete on server."
  RETURN .f.
ENDIF  

llResult = this.oSFtp.DeleteFile(lcFile)

IF !llResult
   this.cErrorMsg = this.oSftp.ErrorMessage
   RETURN -1
ENDIF

RETURN 0
ENDFUNC
*   FtpDeleteFile

************************************************************************
*  FtpRenameFile
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION FtpRenameFile(lcOldname, lcNewName)

llResult = this.oSFtp.RenameFile(lcOldname, lcNewName)

IF !llResult
   this.cErrorMsg = this.oSftp.ErrorMessage
ENDIF

RETURN llResult
ENDFUNC
*   FtpRenameFile

************************************************************************
*  FtpChangeDirectory
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION FtpSetDirectory(lcNewPath)

llResult = this.oSFtp.ChangeDirectory(lcNewPath)

IF !llResult
   this.cErrorMsg = this.oSftp.ErrorMessage
ENDIF

RETURN llResult
ENDFUNC
*   FtpChangeDirectory

************************************************************************
*  FtpCreateDirectory
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION FtpCreateDirectory(lcPath)

llResult = this.oSFtp.CreateDirectory(lcPath)

IF !llResult
   this.cErrorMsg = this.oSftp.ErrorMessage
ENDIF

RETURN llResult
ENDFUNC
*   FtpCreateDirectory


************************************************************************
*  FtpRemoveDirectory
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION FtpRemoveDirectory(lcPath)

llResult = this.oSFtp.RemoveDirectory(lcPath)

IF !llResult
   this.cErrorMsg = this.oSftp.ErrorMessage
ENDIF

RETURN llResult
ENDFUNC
*   FtpRemoveDirectory

ENDDEFINE
*EOC wwSFTP 