Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

FTP functions library (Object syntax) [DEPRECATED]


  • Please log in to reply
39 replies to this topic
shajul
  • Members
  • 571 posts
  • Last active: Aug 01 2015 03:45 PM
  • Joined: 15 Sep 2006
DEPRECATED LIBRARY
Please use FTP Class instead!

Whats better?
- All the functions wrapped into a class (NO global variables)
- Multiple FTP connections possible with same class
- Asynchronous mode is now fully functional! (Read notes in docs)
- No need to call .Close(), clean-up is done when ftp object is deleted.


================== ORIGINAL POST BELOW ==================
Original FTP Functions by Olfen & Andreone - thread10393, modified by ahklerner - this post

Modified by me for AHK_L,
- added object syntax
- added GetCurrentDirectory()
- corrected FileTime retrieval from Win32_Find_Data structure
- created documentation (thanks fincs for GenDocs and Scite4Autohotkey)

Update 2011-01-17:
- Added FTP_Init(), which needs to be called first to retrieve Object reference
- Error/Extended Error (if any) is retrieved in human readable form [.LastError property]
- FTP connection properties can be set (see documentation)
- Uses only one global variable (an object)

Update 2011-01-21:
- Added .InternetReadFile() and .InternetWriteFile() methods
- Progress indicator for uploads and downloads

Update 2011-02-03:
- Bugfixes (bug) and improvements
- FTP_Init() now has two optional parameters - Proxy and ProxyBypass
- .CloseSocket() function documented

Update 2011-02-20:
- Supports x64 also (thanks fragman)

Update 2011-03-01:
- Asynchronous mode added (Alpha) (see Asynchronous mode notes before using).
- FTP_Init() has new parameter AsyncMode
- .Open() now returns handle, .Close() accepts handle as parameter.


May not work correctly with ANSI build. No support for multiple FTP sessions

Documentation |Download

Example:
; initialize and get reference to FTP object
ftp := FTP_Init()

; connect to FTP server
if !ftp.Open("ftp.autohotkey.net", "myUserName", "myPassword")
  {
  MsgBox % ftp.LastError
  ExitApp
  }

; get current directory
sOrgPath := ftp.GetCurrentDirectory()
if !sOrgPath
  MsgBox % ftp.LastError

;; Error handling omitted from here on for brevity

; upload a file with progress
ftp.InternetWriteFile( A_ScriptDir . "\FTP.zip" )

; download a file with progress
ftp.InternetReadFile( "FTP.zip" , "delete_me.zip")

; delete the file
ftp.DeleteFile("FTP.zip")

; create a new directory 'testing'
if !ftp.CreateDirectory("testing")
  MsgBox % ftp.LastError

;; Error-checking code omitted from here on for brevity

; set the current directory to 'root/testing'
ftp.SetCurrentDirectory("testing")

; upload this script file
ftp.PutFile(A_ScriptFullPath, A_ScriptName)

; rename script to 'mytestscript.ahk'
ftp.RenameFile(A_ScriptName, "MyTestScript.ahk")

; enumerate the file list from the current directory ('root/testing')
item := ftp.FindFirstFile("/testing/*")
MsgBox % "Name : " . item.Name
 . "`nCreationTime : " . item.CreationTime
 . "`nLastAccessTime : " . item.LastAccessTime
 . "`nLastWriteTime : " . item.LastWriteTime
 . "`nSize : " . item.Size
 . "`nAttribs : " . item.Attribs
Loop
{
  if !(item := FTP_FindNextFile())
    break
  MsgBox % "Name : " . item.Name
   . "`nCreationTime : " . item.CreationTime
   . "`nLastAccessTime : " . item.LastAccessTime
   . "`nLastWriteTime : " . item.LastWriteTime
   . "`nSize : " . item.Size
   . "`nAttribs : " . item.Attribs
}

; retrieve the file from the FTP server
ftp.GetFile("MyTestScript.ahk", A_ScriptDir . "\MyTestScript.ahk", 0)

; delete the file from the FTP server
ftp.DeleteFile("MyTestScript.ahk")

; set the current directory back to the root
ftp.SetCurrentDirectory(sOrgPath)

; remove the direcrtory 'testing'
ftp.RemoveDirectory("testing")

; close the FTP connection, free library
ftp.Close()

#Include FTP.ahk

Asynchronous example:
; initialize and get reference to FTP object
ftp := FTP_Init(1)
OnExit, Cleanup
; connect to FTP server
if !ftp.Open("ftp.autohotkey.net", "myUserName", "myPassword")
  {
  MsgBox % ftp.LastError
  ExitApp
  }
SleepWhile()

; create a new directory 'testing'
if !ftp.CreateDirectory("testing")
  MsgBox % ftp.LastError
if !SleepWhile()
  MsgBox CreateDirectory failed
;; Error-checking code omitted from here on for brevity

; set the current directory to 'root/testing'
ftp.SetCurrentDirectory("testing")
if !SleepWhile()
  MsgBox SetCurrentDirectory failed

; upload this script file
ftp.PutFile(A_ScriptFullPath, A_ScriptName)
if !SleepWhile(0,"FTP_Progress")
  MsgBox PutFile failed

; rename script to 'testscript.ahk'
ftp.RenameFile(A_ScriptName, "TestScript.ahk")
if !SleepWhile()
  MsgBox Rename failed

ftp.File.BytesTotal := ftp.GetFileSize("TestScript.ahk") ;no need to wait for .GetFileSize
IfExist, %A_ScriptDir%\TestScript.ahk
  FileDelete, %A_ScriptDir%\TestScript.ahk
; retrieve the file from the FTP server
ftp.GetFile("TestScript.ahk", A_ScriptDir . "\TestScript.ahk", 0)
if !SleepWhile(0,"FTP_Progress")
  MsgBox GetFile failed

; delete the file from the FTP server
ftp.DeleteFile("TestScript.ahk")
if !SleepWhile()
  MsgBox DeleteFile failed

; set the current directory back to the root
ftp.SetCurrentDirectory("/") 
if !SleepWhile()
  MsgBox SetCurrentDirectory failed

; remove the directory 'testing'
ftp.RemoveDirectory("testing")
if !SleepWhile()
  MsgBox RemoveDirectory failed

Cleanup:
; close the FTP connection, free library
ftp.Close()
sleep 1000 ;The request complete will not be triggered, as the last message recieved is 70 = INTERNET_STATUS_HANDLE_CLOSING
exitapp

SleepWhile(WaitSeconds=0,ShowProgress=0) {
  global ftp
  if !WaitSeconds
    WaitSeconds := 20
  WaitIndex := WaitSeconds * 1000 / 20
  if ShowProgress
    {
    if ShowProgress is Integer
      ShowProgress := "FTP_Progress"    
    }
  While !ftp.AsyncRequestComplete
    {
    sleep 20
    if ShowProgress
      {
        %ShowProgress%()
        continue
      }
    if A_Index > %WaitIndex% ;20 seconds by default
      {
        MsgBox, 36, Asynchronous Operation, The asyncrhonous operation is taking too long to complete. Abort script?
        IfMsgBox, Yes
          ExitApp
      }
    }
  return (ftp.AsyncRequestComplete = 1) ? 1 : 0 ; -1 means request complete but failed, only 1 is success
}

#Include FTP.ahk

MSDN Reference: FTP Sessions (Windows)

Uploading - Use PutFile for small files only (otherwise program will be unresponsive till the DllCall is complete). For large files, use InternetWriteFile. If you do not specify a function to handle progress (optional third parameter), upload shows a borderless progress window.
Downloading - Use GetFile for small files only (otherwise program will be unresponsive till the DllCall is complete). For large files, use InternetReadFile. If you do not specify a function to handle progress (optional third parameter), upload shows a borderless progress window.
.CloseSocket() - Call to close only current FTP session. Open a new session with .Open
.Close() - Call when you are done using this FTP library.


This feature is still in alpha stage of development.

The default callback function outputs status related information to console only.

Functions which use data buffers will not work in asynchronous mode.
(Not because it is not possible in AHK, but because it is not desirable* and beyond my skill level)
These functions include:
 - .GetCurrentDirectory()
 - .FindFirstFile / .FindNextFile
 - .InternetReadFile / .InternetWriteFile

* Reference: INFO: Using WinInet APIs Asynchronously Within Visual Basic
To quote : "This makes using WinInet APIs in Visual Basic asynchronously an undesirable option."

Callback function: Note thatyou can specify the function to call (AsyncMode parameter can be the name of the function). Because callbacks are made during processing of the request, the application should spend little time in the callback function to avoid degrading data throughput on the network. For example, displaying a msgbox in a callback function can be such a lengthy operation that the server terminates the request.

Memory/File operations: In your script, please do not write to the memory/file that has set up to use in the callback function. Both the script and wininet callback may try to write to the same memory location/file and corrupt the file/memory or crash the script.

AHK and multithreading: As AHK uses psuedo-multithreading (it is a single thread only), wininet callbacks will pause the currently executing thread. If the current executing thread is critical, the async notifications may be missed.


Anybody can take a shot at improving the asynchronous mode.

Eedis
  • Members
  • 1775 posts
  • Last active: Aug 14 2015 06:33 PM
  • Joined: 12 Jun 2009
Heh, I'm going to have fun with this. Thanks. :)
AutoHotkey state, the forum, Poly, and Drainx1. The short story.
I love my wife, my life, my atomic-match; for giving me the greatest gift a man could ask for, such a perfect and beautiful little girl.
9rjbjc.png

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Great library and documentation.

Thx.
Posted Image

n-l-i-d
  • Guests
  • Last active:
  • Joined: --
Very good stuff :)

It would be useful however to rename the lib and functions to oFTP (to distinguish between object oriented libs and "normal" ones).

And can you try to minimize the amount of global variables, or at least rename them with a oFTP-prefix to show what lib they belong and to avoid naming collisions?

So, my request is to make it a bit more modular (avoid needed rewrites/collisions).

shajul
  • Members
  • 571 posts
  • Last active: Aug 01 2015 03:45 PM
  • Joined: 15 Sep 2006

Very good stuff :)

Great library and documentation.

Thx.


Thanks a lot for your encouragement.

It would be useful however to rename the lib and functions to oFTP (to distinguish between object oriented libs and "normal" ones).


As this is the only library in the forum for Unicode AHK_L, i think this will replace any other, so no collision will occur.

And can you try to minimize the amount of global variables, or at least rename them with a oFTP-prefix to show what lib they belong and to avoid naming collisions?

So, my request is to make it a bit more modular (avoid needed rewrites/collisions).


I have made it to only one global variable, that too an object :)

Library updated - see first post..

Update 2011-01-17:
- Added FTP_Init(), which needs to be called first to retrieve Object reference
- Error/Extended Error (if any) is retrieved in human readable form [.LastError property]
- FTP connection properties can be set (see documentation)
- Uses only one global variable (an object)



n-l-i-d
  • Guests
  • Last active:
  • Joined: --
Great! 8)

n-l-i-d
  • Guests
  • Last active:
  • Joined: --
Hmm. I get these MsgBoxes with your testscript:

Name : .
CreationTime : 1/1/1601 0:0:0
LastAccessTime : 1/1/1601 0:0:0
LastWriteTime : 17/1/2011 13:21:0
Size : 4096
Attribs : D


Name : ..
CreationTime : 17/1/2011 13:20:59
LastAccessTime : 17/1/2011 13:20:59
LastWriteTime : 17/1/2011 13:21:0
Size : 4096
Attribs : D


Name : MyTestScript.ahk
CreationTime : 17/1/2011 13:23:59
LastAccessTime : 17/1/2011 13:23:59
LastWriteTime : 17/1/2011 13:24:0
Size : 1786
Attribs : N


Where the first two seem to be the ".."-folder for going up a level? Weird.

And the 00 in a timestamp gets truncated on all instances.

A remark on your changes: it would be useful if one could feed the FTP_Init function with the wanted values for proxy etcet. instead of setting them afterwards.

8)

HTH

shajul
  • Members
  • 571 posts
  • Last active: Aug 01 2015 03:45 PM
  • Joined: 15 Sep 2006

And the 00 in a timestamp gets truncated on all instances

Thanks for taking your time to test this :)

I have padded the output for minutes and seconds now.. but it is just aesthetically better, data is the same.

Also, the file times return 0 if they are not returned from the server. Now the output looks like this:

Name : .
CreationTime : 0
LastAccessTime : 0
LastWriteTime : 17/1/2011 23:32:00
Size : 4096
Attribs : D


8)

shajul
  • Members
  • 571 posts
  • Last active: Aug 01 2015 03:45 PM
  • Joined: 15 Sep 2006
Updated ( added new functions ).

Update 2011-01-21:
- Added .InternetReadFile() and .InternetWriteFile() methods
- Progress indicator for uploads and downloads

Documentation |Download


If i've seen further it is by standing on the shoulders of giants

my site | ~shajul | WYSIWYG BBCode Editor

olfen
  • Members
  • 115 posts
  • Last active: Dec 25 2012 09:48 AM
  • Joined: 04 Jun 2005
Very slick! I'll be using this. Thanks for the credits also.

shajul
  • Members
  • 571 posts
  • Last active: Aug 01 2015 03:45 PM
  • Joined: 15 Sep 2006

Very slick! I'll be using this. Thanks for the credits also.


So happy you like it..

If I have seen further it is by standing on the shoulders of giants.

- You are one of them :wink:
If i've seen further it is by standing on the shoulders of giants

my site | ~shajul | WYSIWYG BBCode Editor

shajul
  • Members
  • 571 posts
  • Last active: Aug 01 2015 03:45 PM
  • Joined: 15 Sep 2006
Performance Update:

Update 2011-02-03:
- Bugfixes (bug) and improvements
- FTP_Init() now has two optional parameters - Proxy and ProxyBypass
- .CloseSocket() function documented


If you are using InternetReadFile or InternetWriteFile method, please update immediately.

Also, please note, Proxy and ProxyBypass are now optional params for FTP_Init() function as suggested by daonlyfreez.
If i've seen further it is by standing on the shoulders of giants

my site | ~shajul | WYSIWYG BBCode Editor

  • Guests
  • Last active:
  • Joined: --
Works like a charm now !

Cheers,
Bob

Twinsen
  • Members
  • 26 posts
  • Last active: Feb 11 2011 09:40 AM
  • Joined: 25 Jan 2006
Works good so far.
But when one of the functions should return a error, I get a error from AHK.
For example, when I try "if !ftp.SetCurrentDirectory("foo")", if the the folder does not exist i get this:
Error in #include file "C:\Users\Robert\Desktop\FTP.ahk":
     This DllCall requires a prior VarSetCapacity. The program is now unstable and will exit.

724: Return,oFile := FTP_GetFileInfo(@FindData)
725: }
740: {
741: if (errNr = 12003)
742: {
743: VarSetCapacity(ErrorMsg,2)
744: bufferSize := 0
--->	745: DllCall("wininet\InternetGetLastResponseInfo", "UIntP", &ErrorMsg, "UInt", &buffer, "UIntP", &bufferSize)
746: VarSetCapacity(buffer, bufferSize)
747: DllCall("wininet\InternetGetLastResponseInfo", "UIntP", &ErrorMsg, "UInt", &buffer, "UIntP", &bufferSize)
748: Msg := StrGet(&buffer,bufferSize)
749: Return,"Error : " . ErrorMsg . "`n" . Msg
750: }
751: bufferSize = 1024
752: VarSetCapacity(buffer, bufferSize)

I'm using AHK_L Unicode 32bit 1.0.92.02

shajul
  • Members
  • 571 posts
  • Last active: Aug 01 2015 03:45 PM
  • Joined: 15 Sep 2006
Thanks for testing! The error was in retrieving extended error details. I could not test it earlier as i could not create an extended error.
Now that i could create the error, i have corrected the bug.

You can re-download FTP.zip with the bug-fixed version, or just replace the GetModuleErrorText() function with the code below..
GetModuleErrorText(hModule,errNr) ;http://msdn.microsoft.com/en-us/library/ms679351(v=vs.85).aspx
{
	bufferSize = 1024 ; Arbitrary, should be large enough for most uses
	VarSetCapacity(buffer, bufferSize)
    if (errNr = 12003)  ;ERROR_INTERNET_EXTENDED_ERROR
	{
		VarSetCapacity(ErrorMsg,4)
		DllCall("wininet\InternetGetLastResponseInfo", "UIntP", &ErrorMsg, "UInt", &buffer, "UIntP", &bufferSize)
		Msg := StrGet(&buffer,bufferSize)
		Return "Error : " . ErrorMsg . "`n" . Msg
	}
	DllCall("FormatMessage"
	 , "UInt", FORMAT_MESSAGE_FROM_HMODULE := 0x00000800
	 , "UInt", hModule
	 , "UInt", errNr
	 , "UInt", 0 ;0 - looks in following order -> langNuetral->thread->user->system->USEnglish
	 , "Str", buffer
	 , "UInt", bufferSize
	 , "UInt", 0)
	Return "Error : " . errNr . " - " . buffer
}

If i've seen further it is by standing on the shoulders of giants

my site | ~shajul | WYSIWYG BBCode Editor