Advertisement
Guest User

CFTPclient.cls

a guest
Dec 29th, 2017
361
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. Option Explicit
  2.  
  3. ' =========================================
  4. ' CFTPclient: wininet ftp functions wrapper
  5. '             needs IE version 4 or above
  6. ' =========================================
  7.  
  8. ' FTP file transfer modes
  9. Public Enum enFtpFileMode
  10.   ffm_Ascii = 1&
  11.   ffm_Binary = 2&
  12. End Enum
  13.  
  14. ' Public events
  15. Public Event Connected(ByVal sServer As String, ByVal lPort As Long)
  16. Public Event Message(ByVal sMsg As String)
  17. Public Event DirLine(ByVal vAttr As VbFileAttribute, ByVal dtDateTime As Date, ByVal dSize As Double, ByVal sName As String)
  18. Public Event Progress(ByVal lCurrent As Long, ByVal lTotal As Long, bCancel As Boolean)
  19. Public Event Failure(ByVal lCode As Long, ByVal sMessage As String)
  20. Public Event Disconnected(ByVal sServer As String, ByVal lPort As Long)
  21.  
  22. ' ftp defaults (used in constructor)
  23. Private Const FTP_DEFAULT_SITE = "ftp.cisco.com"
  24. Private Const FTP_DEFAULT_USER = "Anonymous"
  25. Private Const FTP_DEFAULT_PASS = "guest@example.com"
  26. Private Const FTP_DEFAULT_PASV = True
  27.  
  28. ' Private FTP session data
  29. Private Type tag_FtpSession
  30.   sServer         As String   ' server name/ip
  31.  nPort           As Long     ' server port
  32.  sUser           As String   ' username
  33.  sPassword       As String   ' password
  34.  bPassive        As Boolean  ' true=passive mode
  35.  hInternet       As Long     ' internet session handle
  36.  hConnect        As Long     ' ftp session handle
  37.  hCtx            As Long     ' context (not used)
  38.  dwLastError     As Long     ' last error #
  39.  szLastError     As String   ' last error msg
  40. End Type
  41.  
  42. ' private storage area
  43. Private mFTP        As tag_FtpSession     ' current session
  44. Private msAppName   As String             ' application name w/o ".exe"
  45. Private msAgent     As String             ' user agent string
  46. Private mbDebug     As Boolean            ' true=enable debug/trace
  47.  
  48. ' Type of internet connection
  49. Private Enum INTERNET_OPEN_TYPE
  50.   INTERNET_OPEN_TYPE_PRECONFIG = 0                        ' use system (IE) configuration
  51.  INTERNET_OPEN_TYPE_DIRECT = 1                           ' direct to net
  52.  INTERNET_OPEN_TYPE_PROXY = 3                            ' via named proxy
  53.  INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY = 4      ' preconfig, no proxy autodiscovery
  54. End Enum
  55.  
  56. ' Misc constants
  57. Private Const INTERNET_INVALID_PORT_NUMBER = 0            ' invalid port
  58. Private Const INTERNET_DEFAULT_FTP_PORT = 21              ' default for FTP servers
  59. Private Const INTERNET_SERVICE_FTP = 1                    ' FTP service type selector
  60. Private Const INTERNET_FLAG_PASSIVE = &H8000000           ' passive FTP connections
  61. Private Const INTERNET_FLAG_RELOAD = &H80000000           ' avoid data caching
  62.  
  63. Private Const MAX_PATH = 260                              ' max path/file size
  64. Private Const ERROR_NO_MORE_FILES = 18                    ' end of dir listing
  65.  
  66. ' FileTime (see below)
  67. Private Type FILETIME
  68.   dwLowDateTime       As Long
  69.   dwHighDateTime      As Long
  70. End Type
  71.  
  72. ' Struct used for dir enumeration
  73. Private Type WIN32_FIND_DATA
  74.   dwFileAttributes    As Long
  75.   ftCreationTime      As FILETIME
  76.   ftLastAccessTime    As FILETIME
  77.   ftLastWriteTime     As FILETIME
  78.   nFileSizeHigh       As Long
  79.   nFileSizeLow        As Long
  80.   dwReserved0         As Long
  81.   dwReserved1         As Long
  82.   cFileName           As String * MAX_PATH
  83.   cAlternate          As String * 14
  84. End Type
  85.  
  86. ' Used to convert filetime to vb time
  87. Private Type SYSTEMTIME
  88.   wYear               As Integer
  89.   wMonth              As Integer
  90.   wDayOfWeek          As Integer
  91.   wDay                As Integer
  92.   wHour               As Integer
  93.   wMinute             As Integer
  94.   wSecond             As Integer
  95.   wMilliseconds       As Integer
  96. End Type
  97.  
  98. ' Directory data in VB format
  99. Private Type VB_FIND_DATA
  100.   vFileAttributes     As VbFileAttribute
  101.   dtCreationTime      As Date
  102.   dtLastAccessTime    As Date
  103.   dtLastWriteTime     As Date
  104.   dFileSize           As Double
  105.   sFileName           As String
  106.   sAlternate          As String
  107. End Type
  108.  
  109. ' Date/Time and memory manipulation
  110. Private Declare Function FileTimeToLocalFileTime Lib "kernel32" (lpFILETIME As FILETIME, lpLocalFileTime As FILETIME) As Long
  111. Private Declare Function FileTimeToSystemTime Lib "kernel32" (lpFILETIME As FILETIME, lpSystemTime As SYSTEMTIME) As Long
  112. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, source As Any, ByVal bytes As Long)
  113.  
  114. ' error constants
  115. Private Const ERROR_INTERNET_EXTENDED_ERROR = &H2EE3
  116. Private Const FORMAT_MESSAGE_FROM_HMODULE = &H800
  117.  
  118. ' Error messages decoding
  119. Private Declare Function FormatMessage Lib "kernel32" Alias "FormatMessageA" (ByVal dwFlags As Long, ByVal lpSource As Long, ByVal dwMessageId As Long, ByVal dwLanguageId As Long, ByVal lpBuffer As String, ByVal nSize As Long, dwArguments As Long) As Long
  120. Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpLibFileName As String) As Long
  121.  
  122. ' FTP transfer modes (private)
  123. Private Enum FTP_FILE_OPEN_MODE
  124.   GENERIC_READ = &H80000000                           ' file read mode (download)
  125.  GENERIC_WRITE = &H40000000                          ' file write mode (upload)
  126. End Enum
  127.  
  128. ' FTP transfer types (private)
  129. Private Enum FTP_TRANSFER_TYPE
  130.   FTP_TRANSFER_TYPE_UNKNOWN = &H0&                    ' unknown (binary)
  131.  FTP_TRANSFER_TYPE_ASCII = &H1&                      ' ASCII
  132.  FTP_TRANSFER_TYPE_BINARY = &H2&                     ' binary
  133.  FTP_TRANSFER_NO_CACHE = &H80000000                  ' no caching
  134. End Enum
  135.  
  136.  
  137. ' open/close an internet session
  138. Private Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" (ByVal lpszAgent As String, ByVal dwAccessType As INTERNET_OPEN_TYPE, ByVal lpszProxyName As String, ByVal lpszProxyBypass As String, ByVal dwFlags As Long) As Long
  139. Private Declare Function InternetClose Lib "wininet.dll" Alias "InternetCloseHandle" (ByVal hInternet As Long) As Long
  140.  
  141. ' last error code/message
  142. Private Declare Function InternetError Lib "wininet.dll" Alias "InternetGetLastResponseInfoA" (lpdwError As Long, ByVal lpszBuffer As String, lpdwBufferLength As Long) As Long
  143.  
  144. ' connect/disconnect an ftp server
  145. Private Declare Function FtpConnect Lib "wininet.dll" Alias "InternetConnectA" (ByVal hInternet As Long, ByVal lpszServerName As String, ByVal nServerPort As Long, ByVal lpszUserName As String, ByVal lpszPassword As String, ByVal dwService As Long, ByVal dwFlags As Long, dwContext As Long) As Long
  146. Private Declare Function FtpDisconnect Lib "wininet.dll" Alias "InternetCloseHandle" (ByVal hInternet As Long) As Long
  147.  
  148. ' directory operations
  149. Private Declare Function FtpQueryDir Lib "wininet.dll" Alias "FtpGetCurrentDirectoryA" (ByVal hConnect As Long, ByVal lpszCurrentDirectory As String, lpdwCurrentDirectory As Long) As Long
  150. Private Declare Function FtpChangeDir Lib "wininet.dll" Alias "FtpSetCurrentDirectoryA" (ByVal hConnect As Long, ByVal lpszDirectory As String) As Long
  151. Private Declare Function FtpCreateDir Lib "wininet.dll" Alias "FtpCreateDirectoryA" (ByVal hConnect As Long, ByVal lpszDirectory As String) As Long
  152. Private Declare Function FtpRemoveDir Lib "wininet.dll" Alias "FtpRemoveDirectoryA" (ByVal hConnect As Long, ByVal lpszDirectory As String) As Long
  153. Private Declare Function FtpFindFirstFile Lib "wininet.dll" Alias "FtpFindFirstFileA" (ByVal hConnect As Long, ByVal lpszSearchFile As String, lpFindFileData As WIN32_FIND_DATA, ByVal dwFlags As Long, dwContext As Long) As Long
  154. Private Declare Function FtpFindNextFile Lib "wininet.dll" Alias "InternetFindNextFileA" (ByVal hFind As Long, lpFindFileData As WIN32_FIND_DATA) As Long
  155. Private Declare Function FtpFindClose Lib "wininet.dll" Alias "InternetCloseHandle" (ByVal hInternet As Long) As Long
  156.  
  157. ' file operations
  158. Private Declare Function FtpOpenFile Lib "wininet.dll" Alias "FtpOpenFileA" (ByVal hConnect As Long, ByVal lpszFileName As String, ByVal dwAccess As FTP_FILE_OPEN_MODE, ByVal dwFlags As FTP_TRANSFER_TYPE, dwContext As Long) As Long
  159. Private Declare Function FtpFileSize Lib "wininet.dll" Alias "FtpGetFileSize" (ByVal hFile As Long, lpdwFileSizeHigh As Long) As Long
  160. Private Declare Function FtpBuffSize Lib "wininet.dll" Alias "InternetQueryDataAvailable" (ByVal hFile As Long, lpdwNumberOfBytesAvailable As Long, ByVal dwFlags As Long, ByVal dwContext As Long) As Long
  161. Private Declare Function FtpReadFile Lib "wininet.dll" Alias "InternetReadFile" (ByVal hFile As Long, ByRef lpBuffer As Byte, ByVal dwNumberOfBytesToRead As Long, lpdwNumberOfBytesRead As Long) As Long
  162. Private Declare Function FtpWriteFile Lib "wininet.dll" Alias "InternetWriteFile" (ByVal hFile As Long, ByRef lpBuffer As Byte, ByVal dwNumberOfBytesToWrite As Long, lpdwNumberOfBytesWritten As Long) As Long
  163. Private Declare Function FtpCloseFile Lib "wininet.dll" Alias "InternetCloseHandle" (ByVal hInternet As Long) As Long
  164. Private Declare Function FtpDeleteFile Lib "wininet.dll" Alias "FtpDeleteFileA" (ByVal hConnect As Long, ByVal lpszFileName As String) As Long
  165. Private Declare Function FtpRenameFile Lib "wininet.dll" Alias "FtpRenameFileA" (ByVal hConnect As Long, ByVal lpszExisting As String, ByVal lpszNew As String) As Long
  166.  
  167. ' debugging
  168. Private Declare Sub OutputDebugString Lib "kernel32" Alias "OutputDebugStringA" (ByVal lpOutputString As String)
  169.  
  170. ' .:::: Init instance
  171. Private Sub Class_Initialize()
  172.   ' setup some reasonable defaults so that
  173.  ' just instancing the class and calling
  174.  ' the connect method, it will work
  175.  With mFTP
  176.     .sServer = FTP_DEFAULT_SITE
  177.     .nPort = INTERNET_DEFAULT_FTP_PORT
  178.     .sUser = FTP_DEFAULT_USER
  179.     .sPassword = FTP_DEFAULT_PASS
  180.     .bPassive = FTP_DEFAULT_PASV
  181.     .hInternet = 0
  182.     .hConnect = 0
  183.     .hCtx = 0
  184.     .dwLastError = 0
  185.     .szLastError = ""
  186.   End With
  187.   msAppName = GetAppName()
  188.   msAgent = msAppName & "/" & App.Major & "." & App.Minor & " (build=" & App.Revision & ")"
  189.   mbDebug = False
  190. End Sub
  191.  
  192. ' .:::: Release instance
  193. Private Sub Class_Terminate()
  194.   On Local Error Resume Next
  195.   Disconnect
  196. End Sub
  197.  
  198. ' .:::: Server name or IP address
  199. Public Property Let Server(ByVal sServer As String)
  200.   mFTP.sServer = sServer
  201. End Property
  202.  
  203. Public Property Get Server() As String
  204.   Server = mFTP.sServer
  205. End Property
  206.  
  207. ' .:::: server port #
  208. Public Property Let Port(ByVal nPort As Long)
  209.   mFTP.nPort = nPort
  210. End Property
  211.  
  212. Public Property Get Port() As Long
  213.   Port = mFTP.nPort
  214. End Property
  215.  
  216. ' .:::: logon user name
  217. Public Property Let User(ByVal sUser As String)
  218.   mFTP.sUser = sUser
  219. End Property
  220.  
  221. Public Property Get User() As String
  222.   User = mFTP.sUser
  223. End Property
  224.  
  225. ' .:::: logon password
  226. Public Property Let Password(ByVal sPassword As String)
  227.   mFTP.sPassword = sPassword
  228. End Property
  229.  
  230. Public Property Get Password() As String
  231.   Password = mFTP.sPassword
  232. End Property
  233.  
  234. ' .:::: passive mode
  235. Public Property Let Passive(ByVal bYesNo As Boolean)
  236.   mFTP.bPassive = bYesNo
  237. End Property
  238.  
  239. Public Property Get Passive() As Boolean
  240.   Passive = mFTP.bPassive
  241. End Property
  242.  
  243. ' .:::: user agent string
  244. Public Property Let UserAgent(ByVal sAgent As String)
  245.   msAgent = sAgent
  246. End Property
  247.  
  248. Public Property Get UserAgent() As String
  249.   UserAgent = msAgent
  250. End Property
  251.  
  252. ' .:::: debug/trace mode
  253. Public Property Let DebugMode(ByVal bYesNo As Boolean)
  254.   mbDebug = bYesNo
  255. End Property
  256.  
  257. Public Property Get DebugMode() As Boolean
  258.   DebugMode = mbDebug
  259. End Property
  260.  
  261. ' .:::: last error number/message
  262. Public Property Get LastErrorNum() As Long
  263.   LastErrorNum = mFTP.dwLastError
  264. End Property
  265.  
  266. Public Property Get LastErrorMsg() As String
  267.   LastErrorMsg = mFTP.szLastError
  268. End Property
  269.  
  270. ' .:::: Connect and logon to ftp server
  271. Public Function Connect() As Boolean
  272.   Dim lErrNo As Long, lFlags As Long
  273.   Dim bRet As Boolean
  274.  
  275.   On Local Error Resume Next
  276.   bRet = False
  277.   Connect = bRet
  278.  
  279.   DbgTrace "Connect " & mFTP.sServer & ":" & mFTP.nPort & " " & msAgent
  280.  
  281.   ' acquire an internet handle
  282.  Err.Clear
  283.   mFTP.hInternet = InternetOpen(msAgent, INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0&)
  284.   If (mFTP.hInternet = 0) Then
  285.     DecodeError
  286.     Disconnect
  287.     Exit Function
  288.   End If
  289.  
  290.   ' connect to the FTP server
  291.  lFlags = IIf(mFTP.bPassive, INTERNET_FLAG_PASSIVE, 0&)
  292.   Err.Clear
  293.   mFTP.hConnect = FtpConnect(mFTP.hInternet, mFTP.sServer, mFTP.nPort, mFTP.sUser, mFTP.sPassword, INTERNET_SERVICE_FTP, lFlags, mFTP.hCtx)
  294.   If (mFTP.hConnect = 0) Then
  295.     DecodeError
  296.     Disconnect
  297.     Exit Function
  298.   End If
  299.  
  300.   RaiseEvent Connected(mFTP.sServer, mFTP.nPort)
  301.   FtpResponse
  302.   bRet = True
  303.  
  304.   Connect = bRet
  305. End Function
  306.  
  307. ' .:::: Disconnect
  308. Public Sub Disconnect()
  309.   On Local Error Resume Next
  310.   If (mFTP.hConnect <> 0) Or (mFTP.hInternet <> 0) Then
  311.     RaiseEvent Disconnected(mFTP.sServer, mFTP.nPort)
  312.     DbgTrace "Disconnect"
  313.   End If
  314.   If mFTP.hConnect <> 0 Then
  315.     Call FtpDisconnect(mFTP.hConnect)
  316.     FtpResponse
  317.     mFTP.hConnect = 0
  318.   End If
  319.   If mFTP.hInternet <> 0 Then
  320.     Call InternetClose(mFTP.hInternet)
  321.     mFTP.hInternet = 0
  322.   End If
  323. End Sub
  324.  
  325. ' .:::: read current dir path
  326. Public Function GetCurrentDirectory() As String
  327.   Dim sDir As String, lSize As Long, bRet As Boolean
  328.  
  329.   On Local Error Resume Next
  330.   lSize = 2048
  331.   sDir = String(lSize, vbNullChar)
  332.   bRet = FtpQueryDir(mFTP.hConnect, sDir, lSize)
  333.   If bRet = False Then
  334.     DecodeError
  335.     sDir = vbNullString
  336.   Else
  337.     FtpResponse
  338.   End If
  339.   sDir = GetSZ(sDir)
  340.   DbgTrace "GetCurrentDirectory = " & sDir
  341.   GetCurrentDirectory = sDir
  342. End Function
  343.  
  344. ' .:::: set current dir path
  345. Public Function SetCurrentDirectory(ByVal sDirectory As String) As Boolean
  346.   Dim bRet As Boolean
  347.  
  348.   On Local Error Resume Next
  349.   DbgTrace "SetCurrentDirectory " & sDirectory
  350.   bRet = FtpChangeDir(mFTP.hConnect, sDirectory)
  351.   If bRet = False Then
  352.     DecodeError
  353.   Else
  354.     FtpResponse
  355.   End If
  356.   SetCurrentDirectory = bRet
  357. End Function
  358.  
  359. ' .:::: create a directory
  360. Public Function CreateDirectory(ByVal sDirectory As String) As Boolean
  361.   Dim bRet As Boolean
  362.  
  363.   On Local Error Resume Next
  364.   DbgTrace "CreateDirectory " & sDirectory
  365.   bRet = FtpCreateDir(mFTP.hConnect, sDirectory)
  366.   If bRet = False Then
  367.     DecodeError
  368.   Else
  369.     FtpResponse
  370.   End If
  371.   CreateDirectory = bRet
  372. End Function
  373.  
  374. ' .:::: remove a directory (directory must be empty)
  375. Public Function RemoveDirectory(ByVal sDirectory As String) As Boolean
  376.   Dim bRet As Boolean
  377.  
  378.   On Local Error Resume Next
  379.   DbgTrace "RemoveDirectory " & sDirectory
  380.   bRet = FtpRemoveDir(mFTP.hConnect, sDirectory)
  381.   If bRet = False Then
  382.     DecodeError
  383.   Else
  384.     FtpResponse
  385.   End If
  386.   RemoveDirectory = bRet
  387. End Function
  388.  
  389. ' .:::: read directory listing
  390. Public Function ReadDirList(Optional ByVal sFileMask As String = vbNullString) As Long
  391.   Dim hFind As Long, tFIND As WIN32_FIND_DATA
  392.   Dim bFind As Boolean, lFiles As Long
  393.  
  394.   On Local Error Resume Next
  395.   lFiles = -1
  396.   ReadDirList = lFiles
  397.   DbgTrace "ReadDirList " & sFileMask
  398.  
  399.   ' start the listing
  400.  Err.Clear
  401.   hFind = FtpFindFirstFile(mFTP.hConnect, sFileMask, tFIND, INTERNET_FLAG_RELOAD, mFTP.hCtx)
  402.   If (hFind = 0) Then
  403.     DecodeError
  404.     Exit Function
  405.   End If
  406.   FtpResponse
  407.  
  408.   ' loop over FTP folder items
  409.  lFiles = 0
  410.   Do
  411.     lFiles = lFiles + 1
  412.     DecodeDirEntry tFIND
  413.     bFind = FtpFindNextFile(hFind, tFIND)
  414.     If bFind = False Then
  415.       If Err.LastDllError <> ERROR_NO_MORE_FILES Then
  416.         DecodeError
  417.         lFiles = -1
  418.       End If
  419.     End If
  420.   Loop While bFind <> False
  421.  
  422.   ' cleanup and return
  423.  Call FtpFindClose(hFind)
  424.   If lFiles <> -1 Then
  425.     FtpResponse
  426.   End If
  427.   ReadDirList = lFiles
  428. End Function
  429.  
  430. ' .:::: delete a file
  431. Public Function DeleteFile(ByVal sFileName As String) As Boolean
  432.   Dim bRet As Boolean
  433.  
  434.   On Local Error Resume Next
  435.   DbgTrace "DeleteFile " & sFileName
  436.   bRet = FtpDeleteFile(mFTP.hConnect, sFileName)
  437.   If bRet = False Then
  438.     DecodeError
  439.   Else
  440.     FtpResponse
  441.   End If
  442.   DeleteFile = bRet
  443. End Function
  444.  
  445. ' .:::: rename a file
  446. Public Function RenameFile(ByVal sFileName As String, ByVal sNewName As String) As Boolean
  447.   Dim bRet As Boolean
  448.  
  449.   On Local Error Resume Next
  450.   DbgTrace "RenameFile " & sFileName & " -> " & sNewName
  451.   bRet = FtpRenameFile(mFTP.hConnect, sFileName, sNewName)
  452.   If bRet = False Then
  453.     DecodeError
  454.   Else
  455.     FtpResponse
  456.   End If
  457.   RenameFile = bRet
  458. End Function
  459.  
  460. ' .:::: download (receive) a file from ftp server
  461. Public Function DownloadFile(ByVal sRemoteFile As String, ByVal sLocalFile As String, Optional ByVal nMode As enFtpFileMode = ffm_Binary) As Boolean
  462.   Dim lMode As FTP_TRANSFER_TYPE
  463.   Dim hLocal As Long, hRemote As Long
  464.   Dim lSizeLow As Long, lSizeHigh As Long
  465.   Dim lSize As Long, lCurrent As Long
  466.   Dim bRet As Boolean, bRead As Boolean
  467.   Dim cbBuff() As Byte, lBuff As Long, lBytes As Long
  468.   Dim bCancel As Boolean
  469.  
  470.   On Local Error Resume Next
  471.   bRet = False
  472.   lCurrent = 0
  473.   DownloadFile = bRet
  474.   DbgTrace "DownloadFile " & sRemoteFile & " -> " & sLocalFile
  475.  
  476.   ' open local file for writing (delete existing)
  477.  hLocal = OpenFile(sLocalFile, True)
  478.   If hLocal = 0 Then
  479.     Exit Function
  480.   End If
  481.  
  482.   ' open remote file for reading
  483.  If nMode = ffm_Ascii Then
  484.     lMode = FTP_TRANSFER_TYPE_ASCII
  485.   Else
  486.     lMode = FTP_TRANSFER_TYPE_BINARY
  487.   End If
  488.   lMode = lMode + FTP_TRANSFER_NO_CACHE
  489.   Err.Clear
  490.   hRemote = FtpOpenFile(mFTP.hConnect, sRemoteFile, GENERIC_READ, lMode, mFTP.hCtx)
  491.   If (hRemote = 0) Then
  492.     DecodeError
  493.     Close #hLocal
  494.     Exit Function
  495.   End If
  496.   FtpResponse
  497.  
  498.   ' read the remote file size
  499.  lSizeLow = FtpFileSize(hRemote, lSizeHigh)
  500.   lSize = DecodeSize(lSizeLow, lSizeHigh)
  501.   RaiseEvent Progress(lCurrent, lSize, bCancel)
  502.  
  503.   ' loop: read from ftp and write to file
  504.  If Not bCancel Then
  505.     bRet = False
  506.     Do
  507.       ' find out the current read buffer size
  508.      bRead = FtpBuffSize(hRemote, lBuff, 0&, mFTP.hCtx)
  509.       If lBuff < 1 Then
  510.         lBuff = 1024
  511.       End If
  512.       ReDim cbBuff(lBuff + 1)
  513.       lBytes = 0
  514.       ' read a chunk from remote file
  515.      bRead = FtpReadFile(hRemote, cbBuff(0), lBuff, lBytes)
  516.       If (bRead <> False) Then
  517.         If (lBytes > 0) Then
  518.           ' write local file
  519.          ReDim Preserve cbBuff(lBytes - 1)
  520.           Err.Clear
  521.           Put #hLocal, , cbBuff
  522.           If Err.Number <> 0 Then
  523.             RaiseEvent Failure(Err.Number, Err.Description)
  524.             DbgTrace "Error: " & Err.Number & " " & Err.Description
  525.             bRead = False
  526.             bRet = False
  527.           Else
  528.             lCurrent = lCurrent + lBytes
  529.             RaiseEvent Progress(lCurrent, lSize, bCancel)
  530.             If bCancel Then
  531.               bRead = False
  532.               bRet = False
  533.             End If
  534.           End If
  535.         Else
  536.           bRead = False
  537.           bRet = True
  538.         End If
  539.       Else
  540.         DecodeError
  541.         bRead = False
  542.         bRet = False
  543.       End If
  544.     Loop While (bRead <> False)
  545.   End If
  546.  
  547.   If bRet = True Then
  548.     RaiseEvent Progress(lCurrent, lSize, bCancel)
  549.   End If
  550.   Call FtpCloseFile(hRemote)
  551.   If bRet = True Then
  552.     FtpResponse
  553.   End If
  554.   Close #hLocal
  555.  
  556.   DownloadFile = bRet
  557. End Function
  558.  
  559. ' .:::: upload (send) a file to ftp server
  560. Public Function UploadFile(ByVal sLocalFile As String, ByVal sRemoteFile As String, Optional ByVal nMode As enFtpFileMode = ffm_Binary) As Boolean
  561.   Dim lMode As FTP_TRANSFER_TYPE
  562.   Dim hLocal As Long, hRemote As Long
  563.   Dim lSizeLow As Long, lSizeHigh As Long
  564.   Dim lSize As Long, lCurrent As Long, lRemainder As Long
  565.   Dim bRet As Boolean, bWrite As Boolean
  566.   Dim cbBuff() As Byte, lBuff As Long, lBytes As Long
  567.   Dim bCancel As Boolean
  568.  
  569.   On Local Error Resume Next
  570.   bRet = False
  571.   lCurrent = 0
  572.   UploadFile = bRet
  573.   DbgTrace "UploadFile " & sLocalFile & " -> " & sRemoteFile
  574.  
  575.   ' open local file for reading
  576.  hLocal = OpenFile(sLocalFile, False)
  577.   If hLocal = 0 Then
  578.     Exit Function
  579.   End If
  580.  
  581.   ' open remote file for writing
  582.  If nMode = ffm_Ascii Then
  583.     lMode = FTP_TRANSFER_TYPE_ASCII
  584.   Else
  585.     lMode = FTP_TRANSFER_TYPE_BINARY
  586.   End If
  587.   lMode = lMode + FTP_TRANSFER_NO_CACHE
  588.   Err.Clear
  589.   hRemote = FtpOpenFile(mFTP.hConnect, sRemoteFile, GENERIC_WRITE, lMode, mFTP.hCtx)
  590.   If (hRemote = 0) Then
  591.     DecodeError
  592.     Close #hLocal
  593.     Exit Function
  594.   End If
  595.   FtpResponse
  596.  
  597.   lSize = FileLen(sLocalFile)
  598.   RaiseEvent Progress(lCurrent, lSize, bCancel)
  599.  
  600.   ' loop: read from file and write to ftp
  601.  If Not bCancel Then
  602.     bRet = False
  603.     lRemainder = lSize
  604.     Do
  605.       ' read a chunk from local file
  606.      If lRemainder < 1024 Then
  607.         lBuff = lRemainder
  608.       Else
  609.         lBuff = 1024
  610.       End If
  611.       ReDim cbBuff(lBuff - 1)
  612.       lRemainder = lRemainder - lBuff
  613.       Err.Clear
  614.       Get #hLocal, , cbBuff
  615.       If Err.Number <> 0 Then
  616.         RaiseEvent Failure(Err.Number, Err.Description)
  617.         DbgTrace "Error: " & Err.Number & " " & Err.Description
  618.         bWrite = False
  619.         bRet = False
  620.       Else
  621.         ' write a chunk to remote file
  622.        lBytes = 0
  623.         bWrite = FtpWriteFile(hRemote, cbBuff(0), lBuff, lBytes)
  624.         If (bWrite <> False) Then
  625.           If (lBytes > 0) Then
  626.             lCurrent = lCurrent + lBytes
  627.             RaiseEvent Progress(lCurrent, lSize, bCancel)
  628.             If bCancel Then
  629.               bWrite = False
  630.               bRet = False
  631.             End If
  632.           Else
  633.             bWrite = False
  634.             bRet = True
  635.           End If
  636.           If lRemainder < 1 Then
  637.             bWrite = False
  638.             bRet = True
  639.           End If
  640.         Else
  641.           DecodeError
  642.           bWrite = False
  643.           bRet = False
  644.         End If
  645.       End If
  646.     Loop While (bWrite <> False)
  647.   End If
  648.   If bRet = True Then
  649.     RaiseEvent Progress(lCurrent, lSize, bCancel)
  650.   End If
  651.   Call FtpCloseFile(hRemote)
  652.   If bRet = True Then
  653.     FtpResponse
  654.   End If
  655.   Close #hLocal
  656.  
  657.   UploadFile = bRet
  658. End Function
  659.  
  660. ' :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  661. ' :: Internal service functions - not visible from outer program
  662. ' :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  663.  
  664. ' ==== decode error and signal it
  665. Private Sub DecodeError()
  666.   Dim lSize As Long, lErrNo As Long, lRet As Long
  667.   Dim hMod As Long
  668.  
  669.   lErrNo = Err.LastDllError
  670.   lSize = 2048
  671.   mFTP.dwLastError = 0
  672.   If lErrNo = ERROR_INTERNET_EXTENDED_ERROR Then
  673.     ' got an FTP error message, try retrieving it
  674.    mFTP.szLastError = String(lSize, vbNullChar)
  675.     lRet = InternetError(mFTP.dwLastError, mFTP.szLastError, lSize)
  676.   End If
  677.   If (lRet <> 0) And (mFTP.dwLastError <> 0) Then
  678.     ' got the message, go on
  679.    mFTP.szLastError = GetSZ(mFTP.szLastError)
  680.   Else
  681.     ' retrieve the message from system
  682.    mFTP.dwLastError = lErrNo
  683.     mFTP.szLastError = ""
  684.     hMod = GetModuleHandle("wininet.dll")
  685.     If hMod <> 0 Then
  686.       lSize = 2048
  687.       mFTP.szLastError = String(lSize, vbNullChar)
  688.       lRet = FormatMessage(FORMAT_MESSAGE_FROM_HMODULE, hMod, lErrNo, 0, mFTP.szLastError, lSize, 0)
  689.       mFTP.dwLastError = lErrNo
  690.       mFTP.szLastError = GetSZ(mFTP.szLastError)
  691.     End If
  692.     If Len(mFTP.szLastError) < 1 Then
  693.       ' unable to retrieve the error message
  694.      mFTP.szLastError = "Unexpected error."
  695.     End If
  696.   End If
  697.   RaiseEvent Failure(mFTP.dwLastError, mFTP.szLastError)
  698.   DbgTrace "Failure: " & mFTP.dwLastError & " " & mFTP.szLastError
  699. End Sub
  700.  
  701. ' ==== FTP responses
  702. Private Sub FtpResponse()
  703.   Dim lSize As Long, lErrNo As Long
  704.   Dim lCode As Long, sData As String
  705.   Dim vaData As Variant, iData As Long
  706.   Dim sMsg As String
  707.  
  708.   On Local Error Resume Next
  709.   lSize = 2048
  710.   lCode = 0
  711.   sData = String(lSize, vbNullChar)
  712.   ' retrieve the FTP response
  713.  If InternetError(lCode, sData, lSize) Then
  714.     ' response may be multiline, split it
  715.    sData = GetSZ(sData)
  716.     vaData = Split(sData, vbCrLf)
  717.     ' loop over message lines
  718.    For iData = LBound(vaData) To UBound(vaData)
  719.       sMsg = Trim(Replace(vaData(iData), vbCr, " "))
  720.       sMsg = Trim(Replace(sMsg, vbLf, " "))
  721.       If Len(sMsg) > 0 Then
  722.         RaiseEvent Message(sMsg)
  723.         DbgTrace "Message " & sMsg
  724.       End If
  725.     Next iData
  726.   End If
  727. End Sub
  728.  
  729. ' ==== cut an AsciiZ to its length
  730. Private Function GetSZ(ByVal sBuff As String)
  731.   On Local Error Resume Next
  732.   GetSZ = Mid(sBuff, 1, InStr(sBuff & vbNullChar, vbNullChar) - 1)
  733. End Function
  734.  
  735. ' ==== decode a directory entry
  736. Private Sub DecodeDirEntry(tFIND As WIN32_FIND_DATA)
  737.   Dim tDATA As VB_FIND_DATA
  738.   Dim cTemp As Currency
  739.   Dim st As SYSTEMTIME
  740.  
  741.   On Local Error Resume Next
  742.   With tDATA
  743.     ' if it's a regular file, calculate size
  744.    If (tFIND.dwFileAttributes And vbDirectory) = 0 Then
  745.       CopyMemory cTemp, tFIND.nFileSizeLow, 4
  746.       CopyMemory ByVal VarPtr(cTemp) + 4, tFIND.nFileSizeHigh, 4
  747.       .dFileSize = CDbl(cTemp) * 10000#
  748.     End If
  749.     ' convert file time and set other infos
  750.    FileTimeToSystemTime tFIND.ftLastWriteTime, st
  751.     .dtLastWriteTime = DateSerial(st.wYear, st.wMonth, st.wDay) + TimeSerial(st.wHour, st.wMinute, st.wSecond) + (st.wMilliseconds / 86400000)
  752.     .vFileAttributes = tFIND.dwFileAttributes
  753.     .sFileName = GetSZ(tFIND.cFileName)
  754.     .sAlternate = GetSZ(tFIND.cAlternate) ' <-- not used
  755.    RaiseEvent DirLine(.vFileAttributes, .dtLastWriteTime, .dFileSize, .sFileName)
  756.     DbgTrace "DirLine " & Format(.dtLastWriteTime, "YYYY-MM-DD HH:NN:SS") & " " & Format(.dFileSize, "#,###,###,###,##0") & " " & .sFileName
  757.   End With
  758. End Sub
  759.  
  760. ' ==== decode a qword quantity
  761. Private Function DecodeSize(ByVal lSizeLow As Long, ByVal lSizeHigh As Long) As Double
  762.   Dim cTemp As Currency
  763.  
  764.   On Local Error Resume Next
  765.   CopyMemory cTemp, lSizeLow, 4
  766.   CopyMemory ByVal VarPtr(cTemp) + 4, lSizeHigh, 4
  767.   DecodeSize = CDbl(cTemp) * 10000#
  768. End Function
  769.  
  770. ' ==== Open a file for read or write
  771. Private Function OpenFile(ByVal sFileName As String, Optional bWrite As Boolean = False) As Long
  772.   Dim hFile As Long, bRet As Boolean
  773.  
  774.   On Local Error GoTo Catch
  775.   bRet = False
  776.   hFile = FreeFile
  777.   If bWrite Then
  778.     ' write mode, delete local file
  779.    On Local Error Resume Next
  780.     SetAttr sFileName, vbNormal
  781.     Kill sFileName
  782.     ' open file in binary write mode with exclusive access
  783.    On Local Error GoTo Catch
  784.     Open sFileName For Binary Access Write Lock Read Write As #hFile
  785.   Else
  786.     ' open file in binary read mode with exclusive access
  787.    Open sFileName For Binary Access Read Lock Read Write As #hFile
  788.   End If
  789.   bRet = True
  790.  
  791. BailOut:
  792.   ' common exit point
  793.  On Local Error Resume Next
  794.   If bRet = False Then
  795.     ' got an error
  796.    Close #hFile
  797.     hFile = 0
  798.     If bWrite Then
  799.       Kill sFileName
  800.     End If
  801.   End If
  802.   OpenFile = hFile
  803.   Exit Function
  804.  
  805. Catch:
  806.   ' error handler
  807.  RaiseEvent Failure(Err.Number, Err.Description)
  808.   DbgTrace "Error: " & Err.Number & " " & Err.Description
  809.   bRet = False
  810.   Resume BailOut
  811. End Function
  812.  
  813. ' ==== writes a debug message
  814. Private Function DbgTrace(ByVal sMsg As String)
  815.   Dim sTxt As String
  816.  
  817.   On Local Error Resume Next
  818.   If mbDebug = True Then
  819.     ' compose and emit the trace message
  820.    Debug.Print sMsg
  821.     sTxt = "[" & msAppName & "][FTP] " & sMsg & vbCrLf
  822.     Call OutputDebugString(sTxt)
  823.   End If
  824. End Function
  825.  
  826. ' ==== gets the application name
  827. Private Function GetAppName() As String
  828.   Dim nPos As Integer
  829.   Dim sName As String
  830.  
  831.   sName = App.EXEName
  832.   nPos = InStr(sName, ".")
  833.   If nPos > 0 Then
  834.     sName = Mid(sName, 1, nPos - 1)
  835.   End If
  836.   GetAppName = sName
  837. End Function
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement