FTPDownloader Class – Download folders and files with AS3

Unless I’m trotting this down, I’m going to forget about the changes I made to the source code, and I did. I fixed a few issues with the original FTPDownloader class posted here sometime ago, and I forgot what I changed. I think it had something to do with the file downloading initialization. As of right now, I can only remember vaguely how to use this class so I’m going to trot it down as much as I can.

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

    public class FTPDownloader
    {
        public static const LOG_IN_SUCCESSFUL:String = "log in successfully";
        public static const DATA_RECEIVED:String = "data received";
        public static const DOWNLOAD_CAN_START:String = "download can start";
        public static const LIST_AVAILABLE:String = "list available";
        public static const COMMAND_EXECUTED:String = "226";

        private var _dispatcher:EventDispatcher = new EventDispatcher();        
        private var _control:Socket;
        private var _data:Socket;

        private var _host:String;
        private var _port:int;
        private var _username:String;
        private var _password:String;
        private var _process:String;
        private var _remoteFolder:String;
        private var _remoteFile:String;    
        private var _fileData:ByteArray;
        private var _fileStream:FileStream;
        private var _downloadCanStart:Boolean = false;
        private var _localFile:File;
        private var _filesList:Array;
        private var _nameRegex:RegExp;

        public function FTPDownloader(host:String, port:int, userName:String, password:String)
        {
            this._host = host;
            this._port = port;
            this._username = userName;
            this._password = password;

            _nameRegex = /(\.|\w)+$/gm;

            _control = new Socket(host, port);
            _control.addEventListener(Event.CONNECT, onControlConnection);
            _control.addEventListener(ProgressEvent.SOCKET_DATA, onControlDataReceived);
            _control.addEventListener(IOErrorEvent.IO_ERROR, onError);

            _data = new Socket();
            _data.addEventListener(Event.CONNECT, onDataConnection);
            _data.addEventListener(ProgressEvent.SOCKET_DATA, onServerData);
            _data.addEventListener(IOErrorEvent.IO_ERROR, onError);
        }

        public function get filesList():Array
        {
            return _filesList;
        }

        // initDownload() - initialize downloading
        public function downloadFile(remoteFilePath:String):void
        {
            var rf:Array = remoteFilePath.split("/");
            this._remoteFile = rf.pop();
            this._remoteFolder = rf.join("/");

            _localFile = File.applicationStorageDirectory.resolvePath(this._remoteFile);
            _fileStream = new FileStream();
            _fileStream.open(_localFile, FileMode.WRITE);
            _process = "download";
            _control.writeUTFBytes("TYPE I\n");
            _control.writeUTFBytes("PASV\n");
            _control.flush();
        }

        // onPasvConnection() - connected to data port
        protected function onDataConnection(event:Event):void
        {
            //trace("process: " + _process);
            if(_process == "download")
            {

                // start download a file by using the RETR command
                _control.writeUTFBytes("RETR " + this._remoteFolder + "/" + this._remoteFile + "\n");
            }
            else if(_process == "getDirList")
            {
                _control.writeUTFBytes("LIST " + this._remoteFolder + "\n");
            }
            _control.flush();
        }

        // data received from server through data port
        protected function onServerData(event:ProgressEvent):void
        {
            if(_process == "download")
            {
                // temp object to store incoming data
                _fileData = new ByteArray();
                // write data into temp bytearray
                _data.readBytes(_fileData, 0, _data.bytesAvailable);
                // write byte array into file stream
                _fileStream.writeBytes(_fileData, 0, _fileData.bytesAvailable);
            }
            else if(_process == "getDirList")
            {
                //trace(1);
                var filesList:String = _data.readUTFBytes(_data.bytesAvailable);
                ls(null, filesList);
            }
        }

        protected function onError(event:IOErrorEvent):void
        {
            trace(event.errorID + ":" + event.text);
        }

        // FTP returns server codes whenever the 'control' connection sends commands over
        protected function onControlDataReceived(event:ProgressEvent):void
        {
            var d:String = _control.readUTFBytes(_control.bytesAvailable);
            //trace(d);

            if(d.indexOf("220") != -1)
            {
                // connected to server
                _control.writeUTFBytes("USER " + this._username + "\n");
                _control.flush();
            }

            if(d.indexOf("331") != -1)
            {
                // user accepted, input password
                _control.writeUTFBytes("PASS " + this._password + "\n");
                _control.flush();
            }

            if(d.indexOf("230") != -1)
            {
                // login successful
                dispatchEvent(new Event(LOG_IN_SUCCESSFUL));
            }

            var a:int = d.indexOf('227');
            if (a != -1)
            {
                // entered passive mode
                var st:int = d.indexOf("(",a);
                var en:int = d.indexOf(")",a);
                var str:String;
                str = d.substring(st + 1,en);
                var a2:Array = str.split(",");
                var p1:int= a2.pop();
                var p2:int = a2.pop();
                var ip:String = a2.join(".");
                var port:int=(p2*256)+(p1*1);
                _data.connect(ip, port);
            }

            if(d.indexOf("226") != -1)
            {
                dispatchEvent(new Event(COMMAND_EXECUTED));

                if(_process == "download")
                {
                    // download completed
                    dispatchEvent(new Event(DATA_RECEIVED));
                    //_process = "null";
                }
            }

            if(d.indexOf("221") != -1)
            {
                // logged out from server
            }

            if(d.indexOf("150") != -1)
            {
                // data connection is established after PASV
            }
        }

        protected function onControlConnection(event:Event):void
        {
            // connected to FTP server
        }

        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);
        }

        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));
                }
            }
        }    
    }
}

Usage: To download a folder,

  1. Create connections to that host by filling in the parameters to FTPDownloader().
  2. Create an event listener to listen to LOG_IN_SUCCESSFUL. In the event handler, use the ls function to list the file names of a folder.
  3. Create an event listener to listen to LIST_AVAILABLE. In the background, ls is going to dispatch this event once a list of file names are retrieved. The files’ names are stored in an array that can be accessed through the getter filesList. In the event handler for LIST_AVAILABLE, copy the list to a local variable.
  4. Everytime a command is executed, an event is dispatched as COMMAND_EXECUTED. Listen to this event.
  5. In the command executed event, only when the list of file names are assigned, start downloading by using for example if(_filesList.length > 0). Inside this conditional block, create a path to the remote file using the a path to the folder + the last element in the local filesList array, eg: var path:String = “tz/data/” + _filesList[_filesList.length-1];
  6. Commence downloading that file by using downloadFile(pathToFile)
  7. Create an event listener to listen to DATA_RECEIVED. This is dispatched whenever a single file  is downloaded. In the handler, pop the last element in the files list array. In the background a command is always executed (successfully or else an error code will pop up), once a file is downloaded, if the local variable containing the files list is not empty, it will continue to download until the list of files is empty.
  8. In the command executed conditional block, place an else block and trace yourself a message that all the files has finished downloading (since the list of files is empty)

Anyhow, if I ever need to know how to use this class, may I understand what I wrote. Enjoy!

Advertisements