Downloading a file from a FTP server with AS3 (AIR)

Recently, I ran into a project that involves downloading a file from a FTP server. It was daunting that the HTTPService class in Flex didn’t support the FTP scheme neither does the Actionscript 3 framework has a built-in class to connect to a FTP server. Luckily, AS3 has been around long enough that there are tutorials! One of the most helpful tutorials I have found is the source code of a FTP client by Sathesh (http://suzhiyam.wordpress.com/2011/04/23/as3-ftp-client-part-ii-uploaddownload/). There are also some other references that I use, including:

From following Sathesh’s code, I discovered that it is pretty messy so I decided to write a helper class. How it works is that firstly, a socket ‘s’ is created to connect to the server and this socket ‘s’ is used only to send commands and retrieve server feedback, then another socket ‘r’ is created to handle all incoming data from server, data, not return codes. Once logged in, the client enters passive mode and is ready to download files. It will then make an empty file in the application storage directory, fetch the file using the RETR command and start the writing-to-file process. The returned data is handled through an event listener of the ‘r’ socket. It opens a file stream and began writing to the local file created earlier. Once download is completed, FTP server will send the 226 return code. Although the file is completely downloaded, AIR is not closing the file automatically so we need to check for when the data socket is properly disconnected and close the file stream. Timers come in handy and this eliminate “Error #3013: File or directory is in use”. It should be noted that once server returns code 226, the data connection is closed so you would need to make a new one.

Since I only need to download 1 file from the server, I wrote the class to tailor my needs. Modifications are simple enough once you briefly understood how it works.

package
{
 import flash.events.Event;
 import flash.events.EventDispatcher;
 import flash.events.ProgressEvent;
 import flash.events.TimerEvent;
 import flash.filesystem.File;
 import flash.filesystem.FileMode;
 import flash.filesystem.FileStream;
 import flash.net.Socket;
 import flash.utils.ByteArray;
 import flash.utils.Timer;

 public class DownloadFTPFile
 {
 private var s:Socket;
 private var r:Socket;
 private var fileData:ByteArray;
 private var fileStream:FileStream;
 private var file:File;

 private var ip:String;
 private var port:int;

 private var dTimer:Timer;

 private var filesToDownload:Array;
 private var currentIndex:int;

 private static var dispatcher:EventDispatcher = new EventDispatcher();

 public static var FILE_DOWNLOADED:String = "fileDownloaded";

 private var ftp_host:String;
 private var ftp_port:int;
 private var localFileName:String;
 private var remoteFilePath:String;
 private var ftp_user:String;
 private var ftp_pass:String;

 public function DownloadFTPFile(ftp_host:String, ftp_port:int, ftp_userName:String, ftp_password:String, localFileName:String, remoteFilePath:String)
 {
 this.ftp_host = ftp_host;
 this.ftp_port = ftp_port;
 this.localFileName = localFileName;
 this.remoteFilePath = remoteFilePath;
 this.ftp_user = ftp_userName;
 this.ftp_pass = ftp_password;

 s = new Socket(this.ftp_host, this.ftp_port);
 s.addEventListener(ProgressEvent.SOCKET_DATA, sData);

 r = new Socket();
 r.addEventListener(ProgressEvent.SOCKET_DATA, rData);
 r.addEventListener(Event.CONNECT, onPassiveConnection);

 dTimer = new Timer(10, 1);
 dTimer.addEventListener(TimerEvent.TIMER_COMPLETE, checkStream);
 }

 // connect to server data
 protected function sData(event:ProgressEvent):void
 {
 var d:String = s.readUTFBytes(s.bytesAvailable);
 trace(d);
 if(d.indexOf("220") != -1) // service ready for new user
 {
 s.writeUTFBytes("USER " + this.ftp_user + "\n");
 s.writeUTFBytes("PASS " + this.ftp_pass + "\n");
 s.flush();
 }
 if(d.indexOf("230") != -1) // login successful
 {
 s.writeUTFBytes("PASV\n");
 s.flush();
 }
 var a:int = d.indexOf('227');
 if (a != -1)
 {
 var st:int = d.indexOf("(",a);
 var en:int = d.indexOf(")",a);
 var str:String = d.substring(st + 1,en);
 var a2:Array = str.split(",");
 var p1:int = a2.pop();
 var p2:int = a2.pop();
 ip = a2.join(".");
 port =(p2*256)+(p1*1);
 r.connect(ip, port); // onPassiveConnection() is called once connected
 }

 if(d.indexOf("226") != -1) // file send ok
 {
 dTimer.start();
 }
 }

 protected function onPassiveConnection(event:Event):void
 {
 trace("Connected to data port - downloading commenced");

 file = File.applicationStorageDirectory.resolvePath(localFileName);
 fileStream = new FileStream();
 fileStream.open(file, FileMode.WRITE);
 s.writeUTFBytes("RETR " + remoteFilePath + "\n");
 s.flush(); 
 // now data is going to be sent from server, so continue onto rData()
 }

 // returned server data
 protected function rData(event:ProgressEvent):void
 {
 fileData = new ByteArray();
 r.readBytes(fileData, 0, r.bytesAvailable);
 fileStream.writeBytes(fileData, 0, fileData.bytesAvailable);
 }

 protected function checkStream(event:TimerEvent):void
 {
 if(!r.connected && fileStream != null)
 {
 fileStream.close();

 s.writeUTFBytes("QUIT");
 s.flush();

 dispatchEvent(new Event(FILE_DOWNLOADED));

 }
 else
 {
 dTimer.reset();
 dTimer.start();
 }
 }

 public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
 dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
 }
 public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void {
 dispatcher.removeEventListener(type, listener, useCapture);
 }
 public function dispatchEvent(event:Event):Boolean {
 return dispatcher.dispatchEvent(event);
 }
 public function hasEventListener(type:String):Boolean {
 return dispatcher.hasEventListener(type);
 }
 }
}

How to use:

  1. Create an instance of the class DownloadFTPFile, enter the parameters correctly.
  2. Make the instance listen to the event DownloadFTPFile.FILE_DOWNLOADED. That’s when the file is downloaded!

Enjoy!

Advertisements

3 thoughts on “Downloading a file from a FTP server with AS3 (AIR)

  1. Hello, I stumbled upon this article while looking for a FTP solution and it’s been working great except for a little detail…

    I’m downloading a .zip file, and somehow I get it heavier than it should be (I get 1Mo of data that is not in the original archive) and it results in an invalid archive. I am not sure what the problem could be. I did not modify your code, aside from saving the data in a ByteArray as well as on the applicationStorageDirectory like in your original code.

    I’ll keep looking into it, but due to the lack of time left on the project, I figured I’d ask if you had ever encountered a problem like this.

    Thank you.

    1. No, I’ve never gotten a problem like that before. The last time I used it was to download a bunch of text files, zip files which all worked well for me. Maybe try it on a different network. If you can e-mail me a link to the FTP site you’re working on, I maybe able to provide further assistance.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s