Downloading a folder from a FTP server with AS3

In a time like this when semester one of uni is a month away and it’s too hot to go to the gym, then continue working on some coding seems like a nice idea. A week or two ago, thanks to Sathesh, I learnt how to use Socket to download a file from a FTP server. Now a need to download an entire folder has arisen. That or learn ByteArray to decompress a GZip file. I chose to go with the former. May this be an explanation post to myself later on.

I have found that to download a folder, you would need to get a list of file names in the folder, store it into an array or similar and use a download function to download it one-by-one. I also found that Flash can handle downloading multiple files at the same time but I’ve only tried doing that once and with my internet quota capped at the moment. Multiple files downloading may not seem to be a good idea.

Based on the FTP Downloader class from the earlier post, I added a function to get a list of all names. Since the LIST command in FTP returns everything to know about the file: permissions, date, time, file size, etc, I used a regular expression to fix the problem. With http://gskinner.com/RegExr/, I’ve found /(\.|\w)+$/gm works well and able to get only the file names of the files.

This is the ls function to list all the file names of the folder:

public function ls(remoteFolder:String = null, stringToProcess:String = null):void
{
   _filesList = new Array();

   if(remoteFolder != null)
   {
      // start getting a string of the list of files in a folder
      _process = "getDirList";
      _remoteFolder = remoteFolder;
      _control.writeUTFBytes("PASV\n");
      _control.flush();
   }
   else if(stringToProcess != null)
   {
   var str_arr:Array = stringToProcess.match(_nameRegex);
   if(str_arr != null)
   {
      _filesList = str_arr;
      dispatchEvent(new Event(LIST_AVAILABLE));
   }
}

How it works is that firstly, the coder calls ls() and only fills in the remoteFolder parameter, leaving the latter null. The function will then connect to the data port and it will handle listing and returning to the string, which will leave remoteFolder parameter null and fill in stringToProcess. This will applies the regex and dispatch an event to notify the main program that the list is available to grab. _filesList is a private variable with a getter function.

Once the list is grabbed and stored into an array, the easy part comes along. Now using the function downloadFile() from the earlier post, one can download the files individually.

Advertisements

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!

Machinarium The End – The Transcription

Machinarium screenshot - Robot at the bar

Screenshot from the Machinarium game

I was playing Machinarium on my iPad a while ago and I noticed this song when the robot was in the bar. It has a lead guitar, accompanied by percussion and bass, and it has a lovely bossa nova feel to it. After a few fruitless Google search around, I decided to transcribe the piece. Four months in, and it’s fruitful! Here’s a preview of the transcription. I’m a classical guitar player. Bossa Nova rhythm and harmonies aren’t exactly my thing so there will be a few places that sounds ‘off’, but if you just learn the basic chords and wing it, you can certainly get the result you want. Personally, I never stick to the score anyway.

You can get the PDF score here and the MIDI file here. (Recording: http://youtu.be/jY5ztWaLxDw). If anyone knows how to embed Sibelius Scorch onto WordPress, please do tell me. Enjoy!