Jump to content

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

FTP Class + Library [FTP as easy as 1-2-3]


  • Please log in to reply
40 replies to this topic
shajul
  • Members
  • 571 posts
  • Last active: Aug 01 2015 03:45 PM
  • Joined: 15 Sep 2006
FTP Class + Library
FTP as easy as 1-2-3

Documentation | Download

Version 2: (2011-06-26)
- All the functions wrapped into a class
  ie, NO global variables, except the Class (FTP) itself
- Multiple FTP connections possible with same class
- Asynchronous mode is now fully functional! (Read notes below also)
- No need to call .Close(), clean-up is done when ftp object is deleted.

Using as a class: You'll have to include this file in your script.
#Include FTP.ahk
oFTP := new FTP()
Using as a library: If this library is in any of your lib folders, no need to #Include, you can initialize the FTP object like so
oFTP := FTPv2()

History:
Original FTP Functions by Olfen & Andreone - thread 10393, modified by ahklerner - post
Modified by me for AHK_L - thread 67370 (many modifications/functions added, see thread for details)

Example:
;; FTP Class Example - http://www.autohotkey.com/forum/viewtopic.php?t=73544
;; Synchronous mode example

;;== USER SETTINGS ===============
Server     := "ftp.autohotkey.net"
UserName   := "my_username"
Password   := "my_password"
UploadFile := "D:\Temp\Test.zip"
;;== END USER SETTINGS ===========


#Include FTP.ahk

ftp1 := new FTP()
ftp1 ? TTip("InternetOpen Success") : Quit("Could not load module/InternetOpen")

; connect to FTP server
ftp1.Open(Server, UserName, Password) ? TTip("Connected to FTP") : Quit(ftp1.LastError)

; get current directory
sOrgPath := ftp1.GetCurrentDirectory()
sOrgPath ? TTip("GetCurrentDirectory : " sOrgPath) : Msg(ftp1.LastError)
  
; create a new directory 'testing'
ftp1.CreateDirectory("testing") ? TTip("Created Directory ""testing""") : Msg(ftp1.LastError)

; set the current directory to 'root/testing'
ftp1.SetCurrentDirectory("testing") ? TTip("SetCurrentDirectory ""testing""") : Msg(ftp1.LastError)

; upload this script file
ftp1.PutFile(A_ScriptFullPath, A_ScriptName) ? TTip("PutFile success!") : Msg(ftp1.LastError)

; rename script to 'mytestscript.ahk'
ftp1.RenameFile(A_ScriptName, "MyTestScript.ahk") ? TTip("RenameFile success!") : Msg(ftp1.LastError)

; enumerate the file list from the current directory ('root/testing')
TTip("Enumerating files in directory ""/testing/""")
item := ftp1.FindFirstFile("/testing/*")
MsgBox % "Name : " . item.Name
 . "`nCreationTime : " . item.CreationTime
 . "`nLastAccessTime : " . item.LastAccessTime
 . "`nLastWriteTime : " . item.LastWriteTime
 . "`nSize : " . item.Size
 . "`nAttribs : " . item.Attribs
Loop
{
  if !(item := ftp1.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
ftp1.GetFile("MyTestScript.ahk", A_ScriptDir . "\MyTestScript.ahk", 0) ? TTip("GetFile success!") : Msg(ftp1.LastError)

; delete the file from the FTP server
ftp1.DeleteFile("MyTestScript.ahk") ? TTip("DeleteFile success!") : Msg(ftp1.LastError)

; upload a file with progress
ftp1.InternetWriteFile( UploadFile ) ? TTip("InternetWriteFile success!") : Msg(ftp1.LastError)

; download a file with progress
SplitPath,UploadFile,fName,,fExt
ftp1.InternetReadFile( fName , "delete_me." fExt) ? TTip("InternetReadFile success!") : Msg(ftp1.LastError)

; delete the file
ftp1.DeleteFile( fName )  ? TTip("DeleteFile success!") : Msg(ftp1.LastError)

; set the current directory back to the root
ftp1.SetCurrentDirectory(sOrgPath) ? TTip("SetCurrentDirectory to original path: success!") : Msg(ftp1.LastError)

; remove the direcrtory 'testing'
ftp1.RemoveDirectory("testing") ? TTip("RemoveDirectory ""\testing"" success!") : Msg(ftp1.LastError)

; close the FTP connection, free library
ftp1 := ""    ;__Delete called
MsgBox, 64, Success, Tests successfully completed!, 3
ExitApp


Quit(Message="") {
	if Message
		MsgBox, 16, Error!, %Message%, 5
	ExitApp
}

Msg(Message="") {
	MsgBox, 64, , %Message%, 5
}

TTip(Message="") {
	ToolTip %Message%
}

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


Asynchronous mode example:
;; FTP Class Example - http://www.autohotkey.com/forum/viewtopic.php?t=73544
;; Asynchronous Mode example

;; PLEASE NOTE: 
;; 1. All output is logged to the stdout, can be only seen if debugger attached
;;    or you run it with Scite4Autohotkey
;; 2. The script can do any other work while waiting for AsyncRequestComplete notification

;;== USER SETTINGS ===============
Server     := "ftp.autohotkey.net"
UserName   := "my_username"
Password   := "my_password"
UploadFile := "D:\Temp\Test.zip"
;;== END USER SETTINGS ===========

; initialize and get reference to FTP object
ftp1 := new FTP(1) ; 1 = Async mode, use default callback function
ftp1 ? TTip("InternetOpen Success") : Quit("Could not load module/InternetOpen")

OnExit, Cleanup


; connect to FTP server
ftp1.Open(Server, UserName, Password) ? TTip("Connected to FTP") : Quit(ftp1.LastError)

; create a new directory 'testing'
ftp1.CreateDirectory("testing")
SleepWhile() ? TTip("Created Directory ""testing""") : Msg("Create Directory Failed")

; set the current directory to 'root/testing'
ftp1.SetCurrentDirectory("testing")
SleepWhile() ? TTip("SetCurrentDirectory ""testing""") : Msg("SetCurrentDirectory failed!")

; upload this script file
SplitPath,UploadFile,RemoteFile,,fExt
ftp1.PutFile(UploadFile, RemoteFile)
SleepWhile() ? TTip("PutFile success!") : Msg("PutFile failed!")

; rename script to 'testscript.ahk'
ftp1.RenameFile(RemoteFile, (NewLocalFile := "Delete_Me." . fExt))
SleepWhile() ? TTip("RenameFile success!") : Msg("RenameFile failed!")

IfExist, % NewLocalFile
  FileDelete, % NewLocalFile

; retrieve the file from the FTP server
ftp1.GetFile(NewLocalFile,A_ScriptDir . "" . NewLocalFile, 0)
SleepWhile() ? TTip("GetFile success!") : Msg("GetFile failed!")

; delete the file from the FTP server
ftp1.DeleteFile(NewLocalFile)
SleepWhile() ? TTip("DeleteFile success!") : Msg("DeleteFile failed!")

; set the current directory back to the root
ftp1.SetCurrentDirectory("/") 
SleepWhile() ? TTip("SetCurrentDirectory to original path: success!") : Msg("SetCurrentDirectory failed")

; remove the directory 'testing'
ftp1.RemoveDirectory("testing")
SleepWhile() ? TTip("RemoveDirectory ""\testing"" success!") : Msg("RemoveDirectory failed")
ExitApp

Cleanup:
; close the FTP connection, free library
ftp1 := ""  ; __Delete Called

sleep 1000 ;The request complete will not be triggered, as the last message recieved is 70 = INTERNET_STATUS_HANDLE_CLOSING
MsgBox done!
exitapp


SleepWhile() {
  global FTP
  While !FTP.AsyncRequestComplete
    sleep 50
  return (FTP.AsyncRequestComplete = 1) ? 1 : 0 ; -1 means request complete but failed, only 1 is success
}

Quit(Message="") {
	if Message
		MsgBox, 16, Error!, %Message%, 5
	ExitApp
}

Msg(Message="") {
	MsgBox, 64, , %Message%, 5
}

TTip(Message="") {
	ToolTip %Message%
}

#Include FTP.ahk

1. You can only make one asynchronous mode connection at a time at present.

2. 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

Ref: INFO: Using WinInet APIs Asynchronously Within Visual Basic
To quote the last line of the said article: "This makes using WinInet APIs in Visual Basic asynchronously an undesirable option."

3. Default Async callback function logs all output to stdout
The default async callback function logs all output to stdout, so it can be only seen if debugger attached or you run it with Scite4Autohotkey.

4. The script can continue doing other tasks while waiting for async request complete notification.

5. Callback function:
Note that you 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.

6. 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.

7. 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.


MSDN Reference: FTP Sessions (Windows)

Note: FTP Class means that it is for Autohotkey_L/Autohotkey v2 only!
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
Reserved for future use.
If i've seen further it is by standing on the shoulders of giants

my site | ~shajul | WYSIWYG BBCode Editor

sumon
  • Moderators
  • 1317 posts
  • Last active: Dec 05 2016 10:14 PM
  • Joined: 18 May 2010
Woah, well documented, well done, thanks for sharing. I will try this at once, seems very nice.

Edit: Tested, easy as 1-2-3 indeed! Many thanks for this, it will become very useful for me and I hope many others. Is there any AHK.net drag'n'drop uploader or similar that people are using to upload to AHK.net? If not, I am inspired to make one...

nimda
  • Members
  • 4368 posts
  • Last active: Aug 09 2015 02:36 AM
  • Joined: 26 Dec 2010
Very nice :)
I was actually doing it manually with stuff I picked off of MSDN :?
This will clean that up a bit :p

Tuncay
  • Members
  • 1945 posts
  • Last active: Feb 08 2015 03:49 PM
  • Joined: 07 Nov 2006
I have not tested it yet, just looked in the code. You prove for (NO global variables), but I can see a variable defined as a global, namely FTP. Why this?

No signature.


nimda
  • Members
  • 4368 posts
  • Last active: Aug 09 2015 02:36 AM
  • Joined: 26 Dec 2010
That's the object

shajul
  • Members
  • 571 posts
  • Last active: Aug 01 2015 03:45 PM
  • Joined: 15 Sep 2006
that is the class object as nimda said. If you define any class it becomes a global, that is how classes are in AHK!
If i've seen further it is by standing on the shoulders of giants

my site | ~shajul | WYSIWYG BBCode Editor

Twinsen
  • Members
  • 26 posts
  • Last active: Feb 11 2011 09:40 AM
  • Joined: 25 Jan 2006
It seems no matter what I try, when I try to use GetFile, InternetReadFile, PutFile. I get this:

Error : 12003
200 Switching to Binary mode.
500 Illegal PORT command.
500 Unknown command.

This happens when using your Example.ahk too.
I tried ftp.InternetConnectFlags := 0x00000000, I tried ASCII mode, no effect.

When using a FTP client(or a browser) everything is ok...

Any ideas?

shajul
  • Members
  • 571 posts
  • Last active: Aug 01 2015 03:45 PM
  • Joined: 15 Sep 2006
thanks for reporting.
are you getting this error with autohotkey.net ftp or with other site?
also, could you give more details, eg Operating System, language, x64 or x86 system etc.
If i've seen further it is by standing on the shoulders of giants

my site | ~shajul | WYSIWYG BBCode Editor

Twinsen
  • Members
  • 26 posts
  • Last active: Feb 11 2011 09:40 AM
  • Joined: 25 Jan 2006
It's my own server.

I tried now with autohotkey.net and i get this:

Error : 12003
200 TYPE is now 8-bit binary (or TYPE is now ASCII)
500 I won't open a connection to <my internal IP> (only to <my external IP>)
500 Unknown command

I'm guessing that solves it?

AutoHotkey_L 1.1.00.00, Windows 7 x64

sumon
  • Moderators
  • 1317 posts
  • Last active: Dec 05 2016 10:14 PM
  • Joined: 18 May 2010
Thanks again Shajul for this great library. On Frankie's request I made a simple upload app for FTP!

Below is a demo of a simple "drag-n-drop/select file" upload for uploading selected file to your AHK ftp (I recommend using a subfolder, such as "/uploads"), and returning the URL to your clipboard. It's "Appifyer compatible", of course.

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

#Include %A_ScriptDir%\FTP.ahk ; You need FTP by Shajul for this to work. Get it at http://www.autohotkey.com/forum/viewtopic.php?t=73544


Server     := "ftp.autohotkey.net"
UserName   := "sumon" ; INSERT USERNAME HERE
Password   := "bajs" ; INSERT PASSWORD HERE (no, my password is not that)
Directory := "uploads"

; DETERMINE FILE TO UPLOAD
Input = %1%

GST := Gst()
If ((GST > 1) AND (InStr(GST, "\")) AND (InStr(GST, ".")))
	UploadFile := GST
else if ((Clipboard > 1) AND (InStr(Clipboard, "\")) AND (InStr(Clipboard, ".")))
{
   MsgBox, 68, Use clipboard file?, Your clipboard contains the filepath %Clipboard%. Upload this file?
      IfMsgBox, Yes
         UploadFile := Clipboard
 }
else if (IsURL(Clipboard))
{
	MsgBox %A_ScriptName% does not support URLs yet
	ExitApp
}
If (Input)
	Loop, %Input%
		UploadFile := A_LoopFileName
If (!UploadFile)
{
	;~ MsgBox, 32, No file selected, No file selected for upload. Select a file:
	FileSelectFile, UploadFile,,, No file selected for upload. Select a file.
	If ErrorLevel
		ExitApp
	;~ ExitApp
}

TTip("Uploading file " . UploadFile)

; PROCEED WITH UPLOADING


oFTP := new FTP()

oFTP ? TTip("Connecting...") : Quit("Could not load module/InternetOpen")
oFTP.Open(Server, UserName, Password) ? TTip("... connected to FTP") : Quit(oFTP.LastError) ; connect to FTP server
  oFTP.SetCurrentDirectory(Directory) ? TTip("Ready") : (oFTP.CreateDirectory(Directory) ? oFTP.SetCurrentDirectory(Directory) : Msg(oFTP.LastError)) ; Change dir (create if necessary)
oFTP.InternetWriteFile( UploadFile ) ? TTip("Done!") : Msg(oFTP.LastError) ; Upload a file with progress
oFTP := ""    ; Close library

; DONE

TTip("Done!")
SplitPath, UploadFile, FileName
If (Server = "ftp.autohotkey.net")
   Clipboard := "https://ahknet.autohotkey.com/~" . UserName .  "/" . Directory . "/" . FileName
else
   Clipboard := "ftp://" . Server . "/" . Directory . "/" . FileName
ExitApp

; FUNCTIONS

Quit(Message="") {
   if Message
      MsgBox, 16, Error!, %Message%, 5
   ExitApp
}

Msg(Message="") {
   MsgBox, 64, , %Message%, 5
}

TTip(Message="") {
   ToolTip %Message%
}

gst() {   ; GetSelectedText by Learning one 
   IsClipEmpty := (Clipboard = "") ? 1 : 0
   if !IsClipEmpty {
      ClipboardBackup := ClipboardAll
      While !(Clipboard = "") {
         Clipboard =
         Sleep, 10
      }
   }
   Send, ^c
   ClipWait, 0.1
   ToReturn := Clipboard, Clipboard := ClipboardBackup
   if !IsClipEmpty
   ClipWait, 0.5, 1
   Return ToReturn
}

IsUrl(string) ; By Lazslo
{
   If string contains »
      Return 0
   string = »%string%
   if string contains »http://,»ftp://,»www. ; If string starts with any of these
      Return 1
   Return 0
}

As you can see, all the FTP calls are handled with just a few lines of code, so the library was very useful. I will post this script as separate topic after it's been slightly refined.

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

Password   := "bajs" ; INSERT PASSWORD HERE (no, my password is not that)

LOL.

As you can see, all the FTP calls are handled with just a few lines of code, so the library was very useful. I will post this script as separate topic after it's been slightly refined.

Glad. Good to see that the library is used efficiently!
If i've seen further it is by standing on the shoulders of giants

my site | ~shajul | WYSIWYG BBCode Editor

nimda
  • Members
  • 4368 posts
  • Last active: Aug 09 2015 02:36 AM
  • Joined: 26 Dec 2010

Glad. Good to see that the library is used efficiently!

I have a hotkey...

#IfWinActive ahk_class CabinetWClass
!u::
   server   := "ftp.autohotkey.net", Username := "crazyfirex", password := "bajs"
   file := clip()
   SplitPath, file, uploadFile
   ftp1 := new ftp()
   ftp1.Open(server, username, password)
   ftp1.putFile(file, uploadFile)
   Clipboard := "https://ahknet.autohotkey.com/~crazyfirex/" . uploadFile
   run % clipboard
   ftp1 := ""
return
#IfWinActive
Which is 1% my code, 99% copied from you :D
It took no effort, but helps me tremendously

dylan904
  • Members
  • 706 posts
  • Last active: Nov 14 2016 06:17 PM
  • Joined: 18 Jan 2012
i really appreciate the library, it is ideal for my current script. im mainly interested tho because of the prospect that maybe there could chose from an array of protocols. this would be awesome. there are several different protocols out there, all more secure than the original FTP. I.E. (FTPS, FTPES and SFTP). personally im interested it FTPES, as my hosted ftp client recommends it. now under the server var, i can just swap out FTP in exchange for FTPES, and it works as it should, but im not convinced. is there any way to confirm that i am operating under a secure connection?

also, is there anyway to implement wildcards into here, on the command line ftp, you can specify "prompt" and "mput" rather than "put", along with a "*" before the end of the file name, I.E, (*.txt) to send all text files in the directory.

much thanks, dylan904

dylan904
  • Members
  • 706 posts
  • Last active: Nov 14 2016 06:17 PM
  • Joined: 18 Jan 2012
*Edit*

Well, as far as the wildcards go, i just used

Loop, *.ini{
   ftp1.PutFile(A_LoopFileFullPath) ? : Msg(ftp1.LastError)
}
:oops:

My questions still stand regarding the FTP security aspect, feedback would be praised.