front-end and back-end brought together (download implemented too)

Change-Id: Ic451459626f5fbbfbf7a04e09204cb8f62c8f340
diff --git a/Prototype/FTPLibrary.js b/Prototype/FTPLibrary.js
index bd09962..8ff244a 100644
--- a/Prototype/FTPLibrary.js
+++ b/Prototype/FTPLibrary.js
@@ -6,7 +6,18 @@
 *     testing purposes or the chrome socket API. 
 */
 function FTPClient(socketType) {
-  var BUFFER_SIZE = 8192;
+  var BUFFER_SIZE = 1024*30;
+  var NOOP_INTERVAL = 18000; /* The interval between two calls to the noop_  
+  function. this is implemented to prevent disconnection from the server when 
+  the client has been idle for some time. No information could be obtained
+  from the server about their timeout time. However 18s seems to work in practice
+  with the servers that we have tested */
+  var MIN_FILE_WRITERS = 5;
+  var MAX_FILE_WRITERS = 20; /*The number of writers concurrently writing to a 
+  file. Once the max number of files is reached, the socket is paused until 
+  there's only min file writers writing, at which point the socket is 
+  unpaused. These numbers were picked based on time measurements of file 
+  download*/
   // For further details on FTP Replies classification see RFC 959 Section 4.2
   var ftpReply = {
     PRELIMINARY: 1, // action initiated, expect another reply before issuing 
@@ -18,6 +29,10 @@
     PERMANENT_ERROR: 5 // requested action did not take place. Client is 
                       // discouraged from repeating the exact request
   };
+  var NET_ERROR = {
+    CONNECTION_CLOSED: -100,
+    SOCKET_NOT_CONNECTED: -15
+  };
   var FTPInfo = {
     socketType: socketType || chrome.sockets.tcp,
     host: "",
@@ -38,6 +53,12 @@
                           // preceded by a 1yz response has been received  
     pendingReads : [], // promises to be resolved upon received server response 
     serverResponses : [], // server responses not yet returned to the user
+    noopId: null,
+    writer: null,
+    fileData: {
+      socketId: null,
+      socketPaused: false,
+    }
   };
 
   FTPInfo.socketType.onReceive.addListener(onReceiveCallback_);
@@ -115,6 +136,14 @@
   UnexpectedReplyError.prototype = new Error();
   UnexpectedReplyError.prototype.constructor = UnexpectedReplyError;
 
+  function NetworkError(message) {
+    this.name = "NetworkError";
+    this.message = message || "Network error";
+  }
+
+  NetworkError.prototype = new Error();
+  NetworkError.prototype.constructor = NetworkError;
+
   function getReplyClass(reply){
     return Math.floor(getReplyCode_(reply)/100);
   }
@@ -138,7 +167,6 @@
   function readReply_(command) {
     return new Promise(function (resolve, reject) {
       if (!FTPInfo.preliminaryState && FTPInfo.serverResponses.length > 0) {
-        print("Inside readReply_ already have response");
         var reply = FTPInfo.serverResponses.shift();
         var result = checkReply_(reply);
         if (result instanceof Error) {
@@ -179,12 +207,12 @@
 
   /** 
   * INTERNAL
-  * organizeData_ - given the data recieved from the server, lines are parsed  
+  * splitLines_ - given the data recieved from the server, lines are parsed  
   * and stored while the remaining raw binary data that does not form a line  
   * is stored into a buffer.
   * @param {Uint8Array} receivedData The binary data received from the server.
   */
-  function organizeData_(receivedData) {
+  function splitLines_(receivedData) {
     print("entering Organize data");
     var dataStrs = [], decoder =  new TextDecoder("utf-8"),
       commandRawData = FTPInfo.commandRawData,
@@ -264,7 +292,7 @@
   */
   function getReply_(receivedData) {
     print("entered getReply_");
-    organizeData_(receivedData);
+    splitLines_(receivedData);
     var commandRawData = FTPInfo.commandRawData, replies = [], response = parseResponse_();
     while (response != null) {
       replies = replies.concat(response);
@@ -334,8 +362,8 @@
   * server adheres to the FTP protocol (send a reply containing a legal 3 digit
   * code, send at most one 1yz reply per command) or whether the reply received 
   * contains an error code.
-  * {string} reply 
-  * {null or Error} if the reply contains a valid 3 digit code and contains no 
+  * @param {string} reply 
+  * @return {null or Error} if the reply contains a valid 3 digit code and contains no 
   * error codes ([45]xx) null is retuned, else the appropriate Error is returned.    
   */
   function checkReply_(reply) {
@@ -401,6 +429,19 @@
     }
   }
 
+  function setPaused (socketId, paused) {
+    var socketType = FTPInfo.socketType;
+    if (socketId == FTPInfo.fileData.socketId) {
+      FTPInfo.fileData.socketPaused = paused;
+    }
+    return new Promise(function (resolve, reject) {
+      function onSetPausedComplete () {
+        resolve();
+      }
+      socketType.setPaused(socketId, paused, onSetPausedComplete);
+    });
+  }
+
   /**
   * INTERNAL
   * onReceiveCallback_ - the callback function called by the global socket 
@@ -409,7 +450,6 @@
   *     the data received.
   */
   function onReceiveCallback_(info) {
-    //console.log("onReceiveCallback_", FTPInfo.commandId, info.socketId);
     var decoder = new TextDecoder("utf-8");
     if (info.socketId == FTPInfo.commandId) {
       var receivedData = new Uint8Array(info.data);
@@ -422,11 +462,9 @@
       resolveReads_();
     }
     else {
-      var receivedData = new Uint8Array(info.data);
       for (var socketId in FTPInfo.dataConnects) {
         if (info.socketId == socketId) {
-          console.log("DATA CONNECTION:", decoder.decode(receivedData));
-          FTPInfo.dataConnects[socketId].onReceiveData(receivedData);
+          FTPInfo.dataConnects[socketId].onReceiveData(info.data);
           break;
         }
       }
@@ -435,25 +473,26 @@
 
   function onErrorCallback_(info) {
     var socketType = FTPInfo.socketType;
-    print(info.resultCode, info.resultCode == -100);
-    if (info.resultCode == -100 || info.resultCode == -15) {
-      print("100 error received: ", info.socketId);
-      print("FTPInfo.dataConnects", FTPInfo.dataConnects);
-      for (var socketId in FTPInfo.dataConnects) {
-        socketId = parseInt(socketId);
-        if (info.socketId == socketId) {
-          var connectionInfo = FTPInfo.dataConnects[socketId];
-          if (connectionInfo.command == "LIST") {
-            connectionInfo.onComplete(FTPInfo.lsOutput + FTPInfo.lsDecoder.decode());
-            FTPInfo.lsOutput = "";
-          }
-          else {
+    print(info.resultCode, info.resultCode == NET_ERROR.CONNECTION_CLOSED);
+    if (info.resultCode == NET_ERROR.CONNECTION_CLOSED ||
+        info.resultCode == NET_ERROR.SOCKET_NOT_CONNECTED) {
+      if (info.socketId == FTPInfo.commandId) {
+        // command connection was closed, reject all the pending reads
+        for (var i = 0; i < FTPInfo.pendingReads.length; i++) {
+          FTPInfo.pendingReads[i].reject(new NetworkError("Command Connection Closed"));
+        }
+      }
+      else {
+        for (var socketId in FTPInfo.dataConnects) {
+          socketId = parseInt(socketId);
+          if (info.socketId == socketId) {
+            var connectionInfo = FTPInfo.dataConnects[socketId];
             connectionInfo.onComplete();
+            delete FTPInfo.dataConnects[socketId];
+            socketType.close(socketId);
+            print("Called close socket");
+            break;
           }
-          delete FTPInfo.dataConnects[socketId];
-          socketType.close(socketId);
-          print("Called close socket");
-          break;
         }
       }
     }
@@ -465,12 +504,16 @@
   * connection alive.
   */
   function noop_() {
+    console.log("calling NOOP")
     if (FTPInfo.pendingReads.length == 0) {
       return sendCommand_("NOOP\r\n").then(function (reply) {
         if (getReplyClass(reply) != ftpReply.OK) {
           // unexpected [13]xx reply
           throw new UnexpectedReplyError();
         }
+        else {
+          return reply;
+        }
       }).catch(function (error) {
         throw error;
       });
@@ -478,23 +521,63 @@
     return null;
   }
 
+  /**
+  * INTERNAL
+  * parsePWDReply_ - extracts the remote working directory. The reply sent
+  * by the server could be of two forms: "<3-digit-code> "<current-dir>"" or
+  * "<3-digit-code> <current-dir>".
+  * @return {string or null} - the full path of the remote working directory is
+  * returned if a valid response was sent by the server. Else null is returned.
+  */
+  function parsePWDReply_(reply) {
+    var dirStart = reply.search(" "), dirEnd, dir;
+    if (dirStart == -1) {
+      return null;
+    }
+    dirStart += 1 // the char following the first space after the 3 digit code
+    if (reply[dirStart] == "\"") { // dirStart points at the quotation mark
+      dirEnd = reply.indexOf("\"", dirStart+1); // find end of quotation
+      if (dirEnd == -1) {
+        return null;
+      }
+      dir = reply.slice(dirStart+1, dirEnd);
+    }
+    else {
+      dirEnd = reply.indexOf("\r", dirStart);
+      if (dirEnd == -1) {
+        dirEnd = reply.indexOf("\n", dirStart);
+        if (dirEnd == -1) {
+          return null;
+        }
+      }
+      dir = reply.slice(dirStart, dirEnd);
+    }
+    if (dir == " " || dir == "") {
+      return null;
+    }
+    if (dir[dir.length-1] != "\/") { // if dir does not end with "/", add it
+      dir += "\/";
+    }
+    return dir;
+  }
+
+  /**
+  * getCurrentDir - get the remote current working directory. The reply sent
+  * by the server could be of two forms: "<3-digit-code> "<current-dir>"" or
+  * "<3-digit-code> <current-dir>".
+  * @return {string} - the full path of the remote working directory
+  */
   function getCurrentDir() {
-    console.log("getCurrentDir called")
     return sendCommand_("PWD\r\n").then (function (reply) {
-      console.log("getCurrentDir reply", reply)
       if (getReplyClass(reply) != ftpReply.OK) {
         throw new UnexpectedReplyError();
       }
       else {
-        var dirStart = reply.search(" ") + 1; // the char following the first 
-                                             // space after the 3 digit code
-        if (reply[dirStart] == "\"") { // dirStart points at the quotation mark
-          var dirEnd = reply.indexOf("\"", dirStart+1); // find end of quotation
-          var dir = reply.slice(dirStart+1, dirEnd);
-          if (dir[dir.length-1] != "\/"){
-            dir += "\/";
-          }
-          console.log("parsed dir", dir);
+        var dir = parsePWDReply_(reply);
+        if (!dir) {
+          throw Error("Invalid PWD response");
+        }
+        else {
           return dir;
         }
       }
@@ -503,6 +586,11 @@
     });
   }
 
+  /**
+  * changeWorkingDir - change the remote working directory to the directory 
+  * specified by the pathname.
+  * @param {string} - the full path of the remote directory to switch into
+  */
   function changeWorkingDir(pathname) {
     return sendCommand_("CWD " + pathname +"\r\n").then(function (reply) {
       if (getReplyClass(reply) != ftpReply.OK) {
@@ -516,6 +604,9 @@
     });
   }
 
+  /**
+  * changeToParentDir - equivalient to the unix command cd .. 
+  */
   function changeToParentDir() {
     return sendCommand_("CDUP\r\n").then(function (reply) {
       if (getReplyClass(reply) != ftpReply.OK) {
@@ -529,27 +620,69 @@
     });
   }
 
-  function download(remoteEntry, localEntry) {
-    var onDownloadComplete, dataId, dataPort;
-    var fileDownloaded = new Promise(function (resolve, reject) {
-      var downloadedCallback = function () {
-        resolve();
-      };
-      onDownloadComplete = downloadedCallback;
+  function download(remoteEntry, localDestination, size) {
+    return FileSystemUtils.constructEntryName(remoteEntry, localDestination).then(function (localVersionName) {
+      return downloadFile(remoteEntry, localVersionName, localDestination, size);
+    }).catch(function (error) {
+      throw error;
     });
+  }
+
+  function downloadFile(originFileName, destinationFileName, destination, size) {
+    var start = new Date().getTime();
+    var onConnectionClosed, dataId, dataPort;
+    var fileReceived = false, onDownloadComplete;
+    var connectionClosed  = new Promise(function (resolve, reject) {
+      var connectionClosedCallback = function () {
+        resolve("Data connection closed");
+      };
+      onConnectionClosed = connectionClosedCallback;
+    });
+
+    var downloadCompleted = new Promise(function (resolve, reject) {
+      var downloadOutcome = function (result) {
+        if (result instanceof Error) {
+          reject(result);
+        }
+        else {
+          resolve(result);
+        }
+      };
+      onDownloadComplete = downloadOutcome;
+    });
+
     var onReceiveData = function (data) {
-      //save the data into the current file;
+      var fileData =FTPInfo.fileData;
+      if (!fileData.socketPaused && FTPInfo.writer.workingWriters > MAX_FILE_WRITERS) {
+        fileData.socketPaused = true;
+        setPaused(fileData.socketId, true);
+      }
+      function onComplete () {
+        var fileData = FTPInfo.fileData;
+        if (fileData.socketPaused && FTPInfo.writer.workingWriters < MIN_FILE_WRITERS) {
+          fileData.socketPaused = false;
+          setPaused(fileData.socketId, false);
+        }
+        if (fileReceived && FTPInfo.writer.workingWriters == 0) {
+          onDownloadComplete("Download completed");
+        }
+      }
+      FTPInfo.writer.write(data, onComplete);
     };
     var confirmDownload = dataConnect_().then(function (info) {
       dataId = info.socketId;
       dataPort = info.port;
       var connectionInfo = {
         "command": "RETR",
-        "onComplete": onListDirComplete,
+        "onComplete": onConnectionClosed,
         "onReceiveData": onReceiveData
       };
       FTPInfo.dataConnects[dataId] = connectionInfo;
-      return sendCommand_("RETR + remoteEntry\r\n");
+      FTPInfo.fileData.socketId = dataId;
+      return FileSystemUtils.createFile(destinationFileName, destination, size);
+    }).then(function (fileEntry) {
+      FTPInfo.writer = new ParallelFileWriter(fileEntry);
+      return sendCommand_("RETR " + originFileName + "\r\n");
     }).then(function (reply) {
       var replyClass = getReplyClass(reply);
       if (replyClass == ftpReply.PRELIMINARY) {
@@ -574,7 +707,23 @@
     }).catch(function (error) {
       throw error;
     });
-    return Promise.all([confirmDownload, fileDownloaded]);
+    Promise.all([confirmDownload, connectionClosed]).then(function (values) {
+        // connection was closed and response was sent to acknowledge file
+        // transfer completion
+        console.log("BOTH PROMISES RESOLVES", values);
+        var fileData = FTPInfo.fileData;
+        fileReceived = true;
+        var end = new Date().getTime();
+        var diff = (end-start)/1000;
+        FTPUI.notifyUI("time " + diff);
+        if (FTPInfo.writer.workingWriters === 0) {
+          // data received and file writers finished writing 
+          onDownloadComplete("Download completed");
+        }
+      }).catch(function (error){
+        onDownloadComplete(error);
+      });
+    return downloadCompleted;
   }
 
   /**
@@ -600,14 +749,16 @@
     // the promise of returning a directory listing if one is received
     var directoryList = new Promise(function (resolve, reject) {
       // resolved once data connection is closed
-      var listDirCallback = function (data) {
-        print("resolved directoryList promise with :", data);
-        resolve(data);
+      var listDirCallback = function () {
+        FTPInfo.lsOutput = FTPInfo.lsOutput + FTPInfo.lsDecoder.decode();
+        resolve(FTPInfo.lsOutput);
+        FTPInfo.lsOutput = "";
       };
       onListDirComplete = listDirCallback;
     });
     // register the callback function for received data
     var onReceiveData = function (data) {
+      data = new Uint8Array(data);
       FTPInfo.lsOutput += FTPInfo.lsDecoder.decode(data, {stream:true});
     };
     // establish data connection and request the directory listing
@@ -658,7 +809,7 @@
   function createSocket_() {
     var socketType = FTPInfo.socketType;
     return new Promise(function (resolve, reject) {
-      var onCreateComplete = function (socketInfo) {
+      function onCreateComplete (socketInfo) {
         print("onCreateComplete", socketInfo.socketId);
         if (socketInfo.socketId >= 0) {
           resolve(socketInfo);
@@ -680,8 +831,8 @@
     print("connect wraper socketId, host, port:", socketId, host, port);
     var socketType = FTPInfo.socketType;
     return new Promise(function (resolve, reject) {
-      var onConnectionComplete = function (result) {
-        print("ConnectWrapper_ result = " + result);
+      function onConnectionComplete (result) {
+        console.log("ConnectWrapper_ result = " + result);
         if (result < 0) {
           reject(new ConnectError("Error code " + result));
         }
@@ -702,13 +853,12 @@
   * @param {number} port The port of the remote machine. 
   */
   function connect(host, port) {
-    print("Calling connect");
     if (port != undefined && port != "") {
       FTPInfo.commandPort = parseInt(port);
     }
     FTPInfo.host = host;
     return createSocket_().then(function (socketInfo) {
-      print("Created socket with socketId:");
+      console.log("Created socket with socketId:", socketInfo.socketId);
       print("Now connecting...");
       FTPInfo.commandId = socketInfo.socketId;
       print("set the commandId", FTPInfo.commandId);
@@ -716,24 +866,18 @@
               FTPInfo.commandPort);
     }).then(function () {
       return readReply_("welcomeMsg");
+
     }).then(function (connectMsg) {
-      print("connectMsg", connectMsg);
       var replyClass = getReplyClass(connectMsg);
       if (replyClass != ftpReply.OK) {
         // unexpected [13]xx reply code 
         throw new UnexpectedReplyError(connectMsg);
       }
       else {
-        return getInfo_(FTPInfo.commandId).then(function (socketInfo) {
-          print("reset host");
-          // reset the host DNS name to the IP address of the server/peer
-          FTPInfo.host = socketInfo.peerAddress;
-          setInterval(noop_, 18000); /* assumed 18 s would be an appropriate 
-                                     * value? */
-          return connectMsg;
-        }).catch(function (error) {
-          throw error;
-        });
+        var timerId = setInterval(noop_, NOOP_INTERVAL); /* assumed 18 s would be an  
+                                                /* appropriate value? */
+        FTPInfo.noopId = timerId;
+        return connectMsg;
       }
     }).catch(function (error) {
       print("error in connect", error);
@@ -741,30 +885,6 @@
     });
   }
 
-  /**
-  * INTERNAL
-  * getInfo_ -  a wrapper function around the chrome.sockets.tcp.getInfo 
-  * function to retreive the state of the given socket synchronously.
-  */
-  function getInfo_(socketId) {
-    var socketType = FTPInfo.socketType;
-    return new Promise(function (resolve, reject) {
-      var onInfoComplete = function (socketInfo) {
-        print("socketInfo", socketInfo);
-        resolve(socketInfo);
-      };
-      socketType.getInfo(socketId, onInfoComplete);
-    });
-  }
-
-  /**
-  * INTERNAL
-  * parsePASVReply_ - extracts the port number that the peer/server will use 
-  * for the data connection. 
-  * @param {string} reply The reply received from the server in reponse to the
-  *    PASV command.
-  * @return {number} The port the peer/server will use for the data connection.
-  */
   function parsePASVReply_(reply) {
     print("entering parsePASV", reply);
     var re = /\d{1,3},\d{1,3},\d{1,3},\d{1,3},(\d{1,3}),(\d{1,3})/;
@@ -782,55 +902,86 @@
 
   /**
   * INTERNAL
+  * parseEPSVReply_ - extracts the port number that the peer/server will use 
+  * for the data connection. The response to an EPSV command must be 
+  * <some text> (<d><d><d><tcp-port><d>), where <d> is a delimiter character. 
+  * @param {string} reply The reply received from the server in reponse to the
+  *    EPSV command.
+  * @return {number} The port the peer/server will use for the data connection.
+  */
+  function parseEPSVReply_(reply) {
+    var portStr = reply.match(/\(\S{3}(\d{5})\S\)/)[1];
+    if (portStr === null) {
+      return null;
+    }
+    return parseInt(portStr);
+  }
+
+  /**
+  * INTERNAL
   * dataConnect_ - establishes the data connection. The port number that the 
   * server will use is extracted from the reply to the PASV command.
   */
   function dataConnect_() {
     var socketType = FTPInfo.socketType, dataPort, dataId;
-    return sendCommand_("PASV\r\n").then(function (reply) {
-      print("dataConnect_", reply);
+    return sendCommand_("EPSV\r\n").then(function (reply) {
       var replyClass = getReplyClass(reply);
       if (replyClass != ftpReply.OK) {
         // unexpected [13]xx reply
         throw UnexpectedReplyError();
       }
       else {
-          dataPort = parsePASVReply_(reply);
+          dataPort = parseEPSVReply_(reply);
           if (!dataPort) {
-            throw new Error("Invalid PASV reply: " + reply);
+            throw new Error("Invalid EPSV reply: " + reply);
+          }
+          else {
+            return createSocket_();
           }
       }
-      return createSocket_();
     }).then(function (socketInfo) {
-      print("Created data socket");
       dataId = socketInfo.socketId;
       return connectWrapper_(dataId, FTPInfo.host,
               dataPort);
     }).then(function (result) {
       return {"socketId": dataId, "port": dataPort};
     }).catch(function (error) {
-      print("Error in dataConnect_", error);
+      console.log("Error in dataConnect_", error);
       throw error;
     });
   }
 
   /**
-  * logOut - close the command and data connections and remove the onReceive 
+  * closeConnections - close the command and data connections and remove the onReceive 
   * and onError listener.
   */
-  function logOut() {
+  function closeConnections() {
     var socketType = FTPInfo.socketType;
     socketType.onReceive.removeListener(onReceiveCallback_);
     socketType.onReceiveError.removeListener(onErrorCallback_);
-    chrome.sockets.tcp.close(FTPInfo.commandId);
+    clearInterval(FTPInfo.noopId);
+    socketType.close(FTPInfo.commandId);
     for (var socketId in FTPInfo.dataConnects) {
       // close any data connection still left open
       socketId = parseInt(socketId);
       delete FTPInfo.dataConnects[socketId];
+      socketType.disconnect(socketId);
       socketType.close(socketId);
     }
   }
 
+  function logOut() {
+    var socketType = FTPInfo.socketType;
+    sendCommand_("QUIT\r\n").then(function (reply) {
+      closeConnections();
+    }).catch(function (error) {
+      // QUIT command was not recognized. Close the existent connections 
+      // anyway. This case should not be reached unless the server is not 
+      // adhering to the FTP protocol.
+      closeConnections();
+    });
+  }
+    
   /**
   * INTERNAL
   * sendCommand_ - a wrapper function around the chrome.sockets.tcp.send function 
@@ -843,18 +994,15 @@
   function sendCommand_(command) {
     var encoder = new TextEncoder("utf-8"), socketType = FTPInfo.socketType;
     var commandBuff = encoder.encode(command).buffer; // get ArrayBuffer
-    print("Entering send");
     var promise = new Promise(function (resolve, reject) {
-      var onCompleteSend = function (sendResult) {
-        print("Send Result = " + sendResult);
-        print("Send Result = " + sendResult.resultCode);
+      function onCompleteSend (sendResult) {
         if (sendResult.resultCode < 0) {
-          reject(new SendError("Send error code " + sendResult.resultCode));
+          reject(new SendError("Error code " + sendResult.resultCode));
         }
         else {
           resolve(sendResult);
         }
-      };
+      }
       socketType.send(FTPInfo.commandId, commandBuff, onCompleteSend);
     });
     return promise.then(function () {
@@ -940,9 +1088,10 @@
   this.test = {
     FTPInfo: FTPInfo,
     readReply_: readReply_,
-    organizeData_: organizeData_,
+    splitLines_: splitLines_,
     parseResponse_: parseResponse_,
     parsePASVReply_ : parsePASVReply_,
+    parseEPSVReply_ : parseEPSVReply_,
     getReply_: getReply_,
     readFinalReply_: readFinalReply_,
     isLegalCode_: isLegalCode_,
@@ -951,7 +1100,9 @@
     createSocket_: createSocket_,
     connectWrapper_: connectWrapper_,
     sendCommand_: sendCommand_,
-    dataConnect_: dataConnect_
+    dataConnect_: dataConnect_,
+    noop_: noop_,
+    parsePWDReply_: parsePWDReply_
   };
 }
 
diff --git a/Prototype/FTPSession.js b/Prototype/FTPSession.js
index 43733fe..5ce87fd 100644
--- a/Prototype/FTPSession.js
+++ b/Prototype/FTPSession.js
@@ -1,8 +1,9 @@
 "use strict"
-
 function FTPSession(host, port) {
   this.host = host;
   this.port = port;
+  this.clientsPool = [];
+  this.pendingDownloads = [];
 }
 
 FTPSession.prototype.login = function(username, password) {
@@ -18,7 +19,6 @@
 };
 
 FTPSession.prototype.changeWorkingDir = function(pathname) {
-  console.log("change directory required");
   return this.generalClient.changeWorkingDir(pathname).catch(function (error) {
     throw error;
   });
@@ -36,19 +36,108 @@
   });
 };
 
-FTPSession.prototype.download = function(remoteEntry, localEntry) {
+
+FTPSession.getRemoteDirEntriesInfo = function(dirPath) {
+  return FTPSession.listDir(dirPath).then(function (directoryListing) {
+     return FTPUtils.parseLsDirectoryListing(directoryListing);
+  });
+};
+
+function flatten(a) {
+  if (!(a instanceof Array)){
+    return [a];
+  }
+  else {
+    var flattenedList = [];
+    for (var i = 0; i < a.length; i++) {
+      var result = flatten(a[i]);
+      flattenedList = flattenedList.concat(result);
+    }
+    return flattenedList;
+  }
+}
+
+FTPSession.prototype.createClient = function () {
   var client = new FTPClient();
   var self = this;
   return client.connect(self.host, self.port).then(function (welcomeMsg) {
     return client.login(self.username, self.password);
   }).then(function (reply) {
-    return client.download(remoteEntry, localEntry);
-  }).then(function (reply) {
-    client.logOut();
+    return client;
   }).catch(function (error) {
-    client.logOut();
-    throw error;
+    throw error; // created the cap number of clients the server allowed 
   });
+
+};
+
+FTPSession.prototype.createFTPClientPool = function(requestedClients, onComplete) {
+  console.log("CREATE FTP CLIENT POOL", requestedClients);
+  var maxClients = 5;
+  var self = this;
+  if (requestedClients > maxClients) {
+    requestedClients = maxClients;
+  }
+  self.createClient().then(function (client) {
+    self.clientsPool.push(client);
+    if (self.clientsPool.length < requestedClients) {
+      self.createFTPClientPool(requestedClients, onComplete);
+    }
+    else {
+      onComplete();
+    }
+  }).catch(function(error) {
+    FTPUI.notifyUI(error.message);
+    onComplete();
+  });
+};
+
+FTPSession.prototype.downloadFiles = function() {
+  var self = this;
+  var client = self.clientsPool.shift(), fileInfo = self.pendingDownloads.shift();
+  console.log("IN DOWNLOAD FILES", fileInfo);
+  var remoteEntry = fileInfo.filePath, localDestination = fileInfo.destination,
+      size = fileInfo.size;
+  return client.download(remoteEntry, localDestination, size).then(function (reply) {
+    console.log("FINISHED DOWNLOAD OF " + remoteEntry, "loging out", reply);
+    FTPUI.notifyUI("COMPLETED");
+    if (self.pendingDownloads.length > 0) {
+      self.clientsPool.push(client);
+      self.downloadFiles();
+    }
+    else {
+      client.logOut();
+    }
+  }).catch(function (error) {
+    console.log("Faild on file", remoteEntry, error);
+    client.logOut();
+    //self.downloadFiles();
+    //throw error;
+  });
+};
+
+FTPSession.prototype.download = function(entryPath, localDestination) {
+  console.log("DOWNLOAD CALLED", entryPath.text(), jQuery.data(entryPath, 'size'));
+  var self = this;
+  if (!FileSystemUtils.isFolder(entryPath)) {
+    self.createFTPClientPool(1, function () {
+      self.pendingDownloads.push({
+        "filePath": jQuery.data(entryPath, 'fullPath'), // a string specifying the full remote path
+        "destination": localDestination, // entry object specifying file destination
+        "size": jQuery.data(entryPath, 'size')
+      });
+      self.downloadFiles();
+    });
+  }
+  else {
+    FTPUtils.createFoldersAndGetFiles(entryPath, localDestination).then(
+      function (filesInfo) {
+        filesInfo = flatten(filesInfo);
+        self.createFTPClientPool(filesInfo.length, function () {
+          self.pendingDownloads = self.pendingDownloads.concat(filesInfo);
+          self.downloadFiles();
+        });
+    });
+  }
 };
 
 FTPSession.prototype.listDir = function(pathname) {
@@ -58,10 +147,9 @@
     return client.login(self.username, self.password);
   }).then(function (reply) {
     return client.listDir(pathname);
-  }).then(function (directoryList) {
+  }).then(function (directoryListing) {
     client.logOut();
-    console.log("FTPSession listDir result:", directoryList[1]);
-    return directoryList[1];
+    return directoryListing[1];
   }).catch(function (error) {
     client.logOut();
     console.log(error);
diff --git a/Prototype/fileSystem.css b/Prototype/FTPUI.css
similarity index 85%
rename from Prototype/fileSystem.css
rename to Prototype/FTPUI.css
index 86953d0..b94d843 100644
--- a/Prototype/fileSystem.css
+++ b/Prototype/FTPUI.css
@@ -66,6 +66,17 @@
   cursor: pointer;
 }
 
+a.listed-dir.selected {
+  background-color: #99CCFF;
+}
+
+a.listed-file.selected {
+  background-color: #99CCFF;
+}
+
+a.link-file.selected {
+  background-color: #99CCFF;
+}
 
 a.listed-dir {
   background: url("dir-icon.png") left top no-repeat;
@@ -80,6 +91,11 @@
   padding-left: 25px;
 }
 
+a.link-file {
+  background: url("file-icon.png") left top no-repeat;
+  white-space: nowrap;
+  padding-left: 25px;
+}
 
 
 #ui-dialog {
@@ -92,7 +108,7 @@
   background: #F7F7F7;
   border: 1px solid #D9D9D9;
   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
-  overflow-x: auto;
+  overflow-y: auto;
 
 }
 
diff --git a/Prototype/FTPUI.js b/Prototype/FTPUI.js
new file mode 100644
index 0000000..8588d8f
--- /dev/null
+++ b/Prototype/FTPUI.js
@@ -0,0 +1,241 @@
+// namespace -> FTPUI
+// invoke the function,pass FTPUI into it, in the case that it doesn't exist, pass an object literal
+var FTPUI = (function (FTPUI) {
+  
+  FTPUI.remoteEntry = null;
+  FTPUI.localEntry = null;
+  FTPUI.curLocalDir = null;
+
+  FTPUI.createRemoteDir = function (session, directoryListing) {
+    console.log("in create remote dir");
+    this.session = session;
+    try {
+      FTPUI.listRemote(directoryListing);
+    }
+    catch (error) {
+      FTPUI.notifyUI("Could not access remote directory. " + error);
+    }
+  };
+
+  FTPUI.listRemote = function (directoryListing) {
+    try {
+      var $serverList = $('#remote-browser');
+      $serverList.prop('textContent', '');
+      var entries = FTPUtils.parseLsDirectoryListing(directoryListing);
+      for (var i = 0; i < entries.length; i++) {
+        var $li = $('<li>');
+        var $link = $('<a>');
+        var entryInfo = entries[i];
+        var entryName = entryInfo.name;
+        var filePerm = entryInfo.permissions;
+
+        var lastModDate = entryInfo.date;
+        var size = entryInfo.size;
+        if (!entryInfo.isFile) {
+          $link.addClass('listed-dir');
+        }
+        else if (entryInfo.isLink === 0) {
+          $link.addClass('link-file');
+        }
+        else {
+          $link.addClass('listed-file');
+        }
+
+        $link.prop('textContent', entryName);
+        $link.appendTo($li);
+        jQuery.data($link, 'size', entryInfo.size);
+        jQuery.data($link, 'fullPath', entryName);
+        $li.appendTo($serverList);
+        FTPUI.handleRemoteEntry($link);
+      }
+    }
+    catch (error) {
+      throw error;
+    }
+  };
+
+  FTPUI.handleRemoteEntry = function ($remoteEntry) {
+    var session = this.session;
+    var self = this;
+    $remoteEntry.dblclick(function () {
+      if ($remoteEntry.hasClass('listed-dir')) {
+        self.handleDirNavigation($remoteEntry);
+      }
+    });
+    $remoteEntry.click(function () {
+      if (FTPUI.remoteEntry) {
+        FTPUI.handleSelect(FTPUI.remoteEntry);
+      }
+      FTPUI.handleSelect($remoteEntry);
+      FTPUI.remoteEntry = $remoteEntry;
+    });
+  };
+
+  FTPUI.handleSelect = function(entry) {
+      entry.toggleClass('selected');
+  };
+
+  FTPUI.handleDirNavigation = function ($remoteEntry) {
+    var session = this.session;
+    console.log("We have clicked on a directory", $remoteEntry.text())
+    console.log($remoteEntry.text());
+    session.getCurrentDir().then(function (currentDir) {
+      session.changeWorkingDir($remoteEntry.text());
+      FTPUI.remoteEntry = null;
+      return currentDir;
+    }).then(function (currentDir) {
+      var dir = currentDir + $remoteEntry.text();
+      return session.listDir(dir);
+    }).then(function (dirListing){
+      console.log(dirListing);
+      FTPUI.listRemote(dirListing);
+    }).catch(function (error) {
+      FTPUI.notifyUI("Could not get directory listing. " + error);
+    });
+  };
+
+  //FTP COMMAND: CDUP -> recieve unixOutput from LIST
+  // listremote(directoryListing)
+  FTPUI.handleRemoteBackButton = function () {
+    var session = this.session;
+    $('#remote-back').click(function() {
+      session.changeToParentDir().then(function () {
+        return session.getCurrentDir();
+      }).then(function (currentDir) {
+        return session.listDir(currentDir);
+      }).then(function (dirListing) {
+        FTPUI.listRemote(dirListing);
+      }).catch(function (error) {
+        FTPUI.notifyUI('Could not move to parent directory. ' + error);
+      });
+    });
+  };
+
+  FTPUI.handleLocalButtons = function() {
+    $('#choose-directory').click(function() {
+      chrome.fileSystem.chooseEntry({type: 'openDirectory'}, function (entry) {
+        if (entry) {
+          // if the user hits cancel, entry will be undefinied!
+          FTPUI.curLocalDir = entry;
+          FTPUI.listLocalDir(entry);
+        }
+        // local back button functionality 
+        $('#local-back').click(function() {
+          try {
+              FTPUI.visitParent(FTPUI.curLocalDir);
+          } catch (error) {
+              console.log(err.message);
+          }
+        });
+      });
+    });
+  };
+
+  FTPUI.handleDownload = function() {
+    var session = this.session;
+    $('#download').click(function() {
+      console.log("handle download")
+      if (!FTPUI.remoteEntry || !FTPUI.curLocalDir) {
+         // local or remote entry not selected
+        return;
+      }
+      else {
+        FTPUI.handleSelect(FTPUI.remoteEntry);
+        if (!FTPUI.localEntry) {
+          //get local dir and download it ther 
+          FTPUI.localEntry = FTPUI.curLocalDir;
+        }
+        if (FTPUI.localEntry.isFile) {
+          //output message
+          FTPUI.notifyUI('Can\'t Download to File');
+          return;
+        }
+        session.getCurrentDir().then(function (currentDir) {
+          var entrySize = jQuery.data(FTPUI.remoteEntry, 'size');
+          var fullPath = currentDir + FTPUI.remoteEntry.text();
+          jQuery.data(FTPUI.remoteEntry, 'fullPath', fullPath);
+          return session.download(FTPUI.remoteEntry, FTPUI.localEntry);
+        }).then(function (value) {
+          FTPUI.remoteEntry = null;
+          FTPUI.localEntry = null;
+          FTPUI.listLocalDir(FTPUI.curLocalDir);
+        });
+      }
+    });
+  };
+
+  FTPUI.getRemoteDirEntriesInfo = function(dirPath) {
+    var session = this.session;
+    return session.listDir(dirPath).then(function (directoryListing) {
+       return FTPUtils.parseLsDirectoryListing(directoryListing);
+    });
+  };
+			
+	FTPUI.visitParent = function(entry) {
+		entry.getDirectory('..', {create: false}, FTPUI.listLocalDir);
+	};
+  
+  FTPUI.listLocalDir = function (entry) {
+    FTPUI.curLocalDir = entry;
+    FileSystemUtils.getLocalDirContents(entry).then(function (dirContent) {
+      FTPUI.displayEntries(dirContent);
+    }).catch(function (error) {
+      throw error;
+      FTPUI.notifyUI("Cannot display local directory" + error);
+    });
+  };
+
+  FTPUI.displayEntries = function (entries) {
+     var $element = $('#local-browser');
+    // clear the current entries
+    $element.prop('textContent', '');
+    // Array.forEach paremeters (value of $element,index of $element, the arrayObject itself)
+    entries.forEach(function (entry, i, entries) {
+
+      var $li = $('<li>');
+      var $link = $('<a>');
+      if (entry.isDirectory) {
+          $link.prop('textContent', entry.name += '/');
+          $link.addClass('listed-dir');
+      } else {
+          $link.prop('textContent', entry.name);
+          $link.addClass('listed-file');
+      }
+
+      //add anchor tag to li $element
+      $link.appendTo($li);
+
+      //add li $element to un-ordered $element list
+      $li.appendTo($element);
+      FTPUI.handleLocalEntry($link, entry);
+    });
+  };
+
+  FTPUI.handleLocalEntry = function ($localEntry, entryObject) {
+    $localEntry.dblclick(function() {
+      if ($localEntry.hasClass('listed-dir')) {
+        FTPUI.listLocalDir(entryObject);
+        FTPUI.localEntry = null;
+      } else {
+        FTPUI.notifyUI('Selected Local File: ' +  $localEntry.text());
+      }
+    });
+    $localEntry.click(function () {
+      FTPUI.handleSelect($localEntry);
+      FTPUI.localEntry = entryObject;
+    });
+  };
+
+    /* Send the User message updates */
+  /* @param Type: String or Error Object*/
+  FTPUI.notifyUI = function (notification) {
+    if(typeof notifcation === Error) notification.toString();
+    var $li = $('<li>');
+    $li.prop('textContent', notification);
+      $('#ui-message').append($li);
+  };
+
+  return FTPUI;
+
+
+}(FTPUI || {}));
diff --git a/Prototype/FTPUtils.js b/Prototype/FTPUtils.js
new file mode 100644
index 0000000..cf79ba3
--- /dev/null
+++ b/Prototype/FTPUtils.js
@@ -0,0 +1,289 @@
+var FTPUtils = (function (FTPUtils) {
+
+  function isInt(s) {
+    return s.match(/^\d+$/) !== null;
+  }
+
+  /**
+  * getEntryDate - if valid arguments passed in, constructs and returns date, 
+  * otherwise null is returned
+  * @param {string} month 3-letters defining the month 
+  * @param {string} day 
+  * @param {string} rest this is either the year of the time in the (H)H:(M)M 
+  *     format
+  * @return {string or null} returns the constructed date or null if valid 
+  *     argument were not passed in.
+  */
+  FTPUtils.getEntryDate = function(month, day, rest) {
+    month = month.toLowerCase();
+    if (month.length < 3 || !isInt(day)) {
+      return null;
+    }
+    else if (month.length > 3) {
+      month = month.slice(0,3);
+    }
+    var months = {
+    "jan": 0, "feb": 1, "mar": 2, "apr": 3, "may": 4, "jun": 5, "jul": 6,
+    "aug": 7, "sep": 8, "oct": 9, "nov": 10, "dec": 11
+    };
+    var monthInt = null;
+    for (var key in months) {
+      if (key == month) {
+        monthInt = months[month];
+        break;
+      }
+    }
+    if (monthInt === null) {
+      return null;
+    }
+    var year, hours, minutes;
+    var currentDate = new Date();
+    if (!isInt(rest)) {
+      // Maybe it's time. Time can be any of theformats "HH:MM", "H:MM", "HH:M",
+      // "H:M"
+      if (rest.length > 5) {
+        return null;
+      }
+      var timeComponents = rest.split(":");
+      if (timeComponents.length != 2) {
+        return null;
+      }
+      hours = timeComponents[0];
+      minutes = timeComponents[1];
+      if (!isInt(hours) || !isInt(minutes)) {
+        return null;
+      }
+      // since the year was not sent, get the current year from user's local date 
+      year = currentDate.getFullYear();
+      if (monthInt > currentDate.getMonth() || monthInt == currentDate.getMonth() &&
+        day > currentDate.getDay()) {
+        year--;
+      }
+    }
+    else {
+      year = rest;
+      hours = currentDate.getHours();
+      minutes = currentDate.getMinutes();
+    }
+    var date = new Date(year, monthInt, day, hours, minutes).toLocaleString();
+    return date;
+  };
+
+  /**
+  * getISO08601date - if valid arguments passed in, constructs and returns date, 
+  * otherwise null is returned
+  * @param {string} date The format of date must be YYYY-MM-DD
+  * @param {string} time The format of time must be (H)H:(M)M 
+  * @return {string or null} returns the constructed date or null if valid 
+  *     argument were not passed in.
+  */
+  FTPUtils.getISO8601date = function(date, time) {
+    var dateComponents = date.split("-");
+    if (dateComponents.length != 3) {
+      return null;
+    }
+    if (!isInt(dateComponents[0]) || !isInt(dateComponents[1]) ||
+        !isInt(dateComponents[2])) {
+      return null;
+    }
+    var timeComponents = time.split(":");
+    if (timeComponents.length != 2) {
+      return null;
+    }
+    if (!isInt(timeComponents[0]) || !isInt(timeComponents[1])) {
+      return null;
+    }
+    var entryDate = new Date(dateComponents[0], dateComponents[1],
+      dateComponents[2], timeComponents[0], timeComponents[1]).toLocaleString();
+    return entryDate;
+  };
+
+  /**
+   * detectDateAndDateOffset - given an array containing the components of a
+   * unix line ls output, detects the array offset of date and then construct
+   * and returns the date
+   * These are the components that a listing line should contain. # indicates
+   * a required field:
+   *  # 1. permission listing
+   *    2. number of links (optional)
+   *  # 3. owner name (may contain spaces)
+   *    4. group name (optional, may contain spaces)
+   *  # 5. size in bytes
+   *  # 6. month
+   *  # 7. day of month
+   *  # 8. year or time <-- dateOffset will be the index of this component
+   *    9. file name (optional, may contain spaces)
+   */
+  FTPUtils.detectDateAndDateOffset = function(entryComponents) {
+    var date, dateOffset;
+    for (var j = 5; j < entryComponents.length; j++) {
+      date = FTPUtils.getEntryDate(entryComponents[j-2], entryComponents[j-1], entryComponents[j]);
+      if (date) {
+        dateOffset = j;
+        break;
+      }
+    }
+    if (!date) {
+      // some FTP servers have swapped the "month" and "day of month" colums
+      // if none of the combination above worked, we try to recognize these 
+      // server
+      for (j = 5; j < entryComponents.length; j++) {
+        date = FTPUtils.getEntryDate(entryComponents[j-1], entryComponents[j-2], entryComponents[j]);
+        if (date) {
+          dateOffset = j;
+          break;
+        }
+      }
+      // still haven't been able to get the date. Some FTP servers use the ISO  
+      // 8601 date format. Try to recognize those servers
+      if (!date) {
+        for (j = 5; j < entryComponents.length; j++) {
+          date = FTPUtils.getISO8601date(entryComponents[j-1], entryComponents[j]);
+          if (date) {
+            dateOffset = j;
+            break;
+          }
+        }
+        if (!date) {
+          return null;
+        }
+      }
+    }
+    return {"date": date, "dateOffset": dateOffset};
+  };
+
+  /**
+  * getEntryName - given an array containing the components of a unix line ls 
+  * output, and the array offset of date, constructs the entry name. In the
+  * array, the entry name component is immediately preceded by date, hence the
+  * date offset is passed in. If the entry name contained spaces, then the array
+  * @param {array} entryComponents The components making up a single line from 
+  *    the ls -l output
+  * @param {string} entry A single line from the ls -l output 
+  * @param {number} dateOffset the offset of date into the entrycomponents array
+  * @return {string} returns the entry Name
+  */
+  FTPUtils.getEntryName = function(entryComponents, entry, dateOffset) {
+    var correctNameComponents = entryComponents.slice(dateOffset+1),
+      firstComponent = entryComponents[dateOffset+1],
+      searchOffset = 0,
+      nameOffset = 0,
+      searchedComponents = [], entryName;
+    while (nameOffset != -1 &&
+           searchedComponents.toString() != correctNameComponents.toString()) {
+      nameOffset = entry.indexOf(firstComponent, searchOffset);
+      entryName = entry.slice(nameOffset);
+      searchedComponents = entryName.split(/\s+/);
+      searchOffset = nameOffset + firstComponent.length;
+    }
+    if (nameOffset == -1) {
+      return "";
+    }
+    if (entryName.search("\r\n") != -1) {
+      entryName = entryName.slice(0, entryName.length-2);
+    }
+    else if (entryName.search("\r") != -1 || entryName.search("\n") != -1) {
+      entryName = entryName.slice(0, entryName.length-1);
+    }
+    return entryName;
+  };
+
+  /** 
+   * parseLsDirectoryListing - given a string that contains unix-formated 
+   * directory listing, for each entry, returns an object containing entry
+   * information such as entry name, last modified date, entry size, persmissions.
+   * Assuming a directory dontains n entries, the format would look as following:
+   * <entry 1 line> <CRLF>
+     <entry 2 line> <CRLF>
+     <entry 3 line> <CRLF>
+     ...
+     <entry n line> <CRLF>, where each of these entry lines is composed of 6 
+     required fields and three optional fields.
+     <entry k line> :: = <permissions> <number of links (opt)> <owner name>
+                         <group name (opt)> <size> <month> <day of month> 
+                         <year or time> <file name (opt)>
+   *@param {string} lsOutput
+   *@return {array} returns an array of objects, where each object k contains 
+   *    information about entry k  
+  */
+  FTPUtils.parseLsDirectoryListing = function(lsOutput) {
+    var unixLines = lsOutput.split(/[\r\n]+/g);
+    if (unixLines[unixLines.length-1] == "") {
+      unixLines.pop();
+    }
+    var entriesInfo = [];
+    for (var i = 0; i < unixLines.length; i++) {
+      var entry = unixLines[i];
+      var entryComponents = entry.split(/\s+/);
+      var result = FTPUtils.detectDateAndDateOffset(entryComponents);
+      if (!result) {
+        return null;
+      }
+      var date = result.date, dateOffset = result.dateOffset;
+      var size = entryComponents[dateOffset-3];
+      var entryName = FTPUtils.getEntryName(entryComponents, entry, dateOffset);
+      var permissions = entryComponents[0], isLink, isFile;
+      if (permissions.search(/^d/) === 0) {
+        entryName += "/";
+        isFile = false;
+        isLink = false;
+      }
+      else if (permissions.search(/^l/) === 0) {
+        isLink = true;
+        isFile = false;
+      }
+      else {
+        isFile = true;
+        isLink = false;
+      }
+      entriesInfo.push({
+        "permissions": permissions,
+        "size": size,
+        "date": date,
+        "name": entryName,
+        "isLink": isLink,
+        "isFile": isFile
+      });
+    }
+    return entriesInfo;
+  };
+
+  /**
+  * getRemoteEntryNames - given the ls -l output, returns all the entry names 
+  * @param {string} directoryListing The output from ls -l output
+  * @return {array} the array containing the netry names
+  */
+  FTPUtils.getRemoteEntryNames = function(directoryListing) {
+    var entries = FTPUtils.parseLsDirectoryListing(directoryListing);
+    entryNames = [];
+    for (var i =0; i < entries.length; i++) {
+      entryNames.push(entries[i].name);
+    }
+    return entryNames;
+  };
+
+  FTPUtils.createFoldersAndGetFiles = function(remotePath, localDestination, size) {
+    if (!FileSystemUtils.isFolder(remotePath)) {
+      return {"filePath": remotePath, "destination": localDestination, "size": size};
+    }
+    else {
+      var folderContent = [];
+      return FileSystemUtils.createFolder(remotePath, localDestination).then(
+        function (folderEntry) {
+          return FTPUI.getRemoteDirEntriesInfo(remotePath).then(function (entriesInfo) {
+            for (var i = 0; i < entriesInfo.length; i++) {
+              var entryPath = remotePath + entriesInfo[i].name;
+              var entrySize = entriesInfo[i].size;
+              folderContent.push(FTPUtils.createFoldersAndGetFiles(entryPath, folderEntry, entrySize));
+            }
+            console.log("FOLDER CONTENT", folderContent);
+            return Promise.all(folderContent);
+          });
+      });
+    }
+  };
+
+  return FTPUtils;
+
+} (FTPUtils || {}));
+
diff --git a/Prototype/FileSystem.js b/Prototype/FileSystem.js
deleted file mode 100644
index 2a1461d..0000000
--- a/Prototype/FileSystem.js
+++ /dev/null
@@ -1,273 +0,0 @@
-
-
-// namespace -> FTPUI
-// invoke the function,pass FTPUI into it, in the case that it doesn't exist, pass an object literal
-var FTPUI = (function (FTPUI) {
-  
-  FTPUI.remoteEntry = null;
-  FTPUI.localEntry = null;
-  
-  FTPUI.createRemoteDir = function (session, unixLsOutput) {
-    console.log("in create remote dir");
-    this.session = session;
-    try {
-      FTPUI.listRemote(unixLsOutput);
-    }
-    catch (error) {
-      FTPUI.notifyUI("Could not acces remote directory. " + error);
-    }
-  };
-
-  FTPUI.listRemote = function (unixLsOutput) {
-    try {
-      var entries = unixLsOutput.split(/[\r\n]+/g);
-      var remoteEntry = [];
-      if (entries[entries.length-1] == "") {
-        entries.pop();
-      }
-
-      var serverList = document.getElementById('remote-browser');
-      serverList.textContent = '';
-      //var $serverList = $('#remote-browser');
-      //$serverList.clear();
-      for (var i = 0; i < entries.length; i++) {
-
-        
-        //console.log(serverList);
-        var $li = $('<li>');
-        var $link = $('<a>');
-
-        var entry = entries[i];
-        var entryParts = entry.split(/\s+/);
-        var lastModDate = entryParts.slice(5,8).join(" ");
-        var filePerm = entryParts[0];
-        var links = entryParts[1];
-        var ownerID = entryParts[2];
-        var groupID = entryParts[3];
-        var size = entryParts[4];
-        var month = entryParts[5];
-        var day = entryParts[6];
-        var time = entryParts[7];
-        //console.log("entry:", entry, entry == "\r", entry == "");
-        var entryName = entry.match(/.{56}(.*)/)[1];
-
-        if (entry.search(/^d/) === 0) {
-          filePerm = filePerm.replace(/^d/,"");
-          //console.log(filePerm);
-          //console.log(entryName);
-          $link.prop('textContent', entryName += '/');
-          $link.addClass('listed-dir');
-          $link.appendTo($li);
-          $li.appendTo($(serverList));
-        }
-        else {
-          filePerm = filePerm.replace(/^./,"");
-          //console.log(filePerm);
-          //console.log(entryName);
-          $link.prop('textContent', entryName);
-          $link.addClass('listed-file');
-          $link.appendTo($li);
-          $li.appendTo($(serverList));
-        }
-        FTPUI.handleElement($link);
-      }
-    }
-    catch (error) {
-      throw error;
-    }
-  };
-
-  FTPUI.handleElement = function ($remoteEntry) {
-    var session = this.session;
-    var self = this;
-    $remoteEntry.dblclick(function () {
-      if ($remoteEntry.hasClass('listed-dir')) {
-        self.handleDirNavigation($remoteEntry);
-      }
-    });
-    $remoteEntry.click(function () {
-      if ($remoteEntry.hasClass('listed-file')) {
-        FTPUI.handleSelect($remoteEntry);
-        FTPUI.remoteEntry = $remoteEntry;
-      }
-    });
-  };
-
-  FTPUI.handleSelect = function(entry) {
-      entry.css({'backgroundColor': '#99CCFF'});
-      FTPUI.notifyUI('Selected Remote File: ' +  entry.text());
-      // change the text of the icon to be white, extend the selection
-  };
-
-  FTPUI.handleDirNavigation = function ($remoteEntry) {
-    var session = this.session;
-    console.log("We have clicked on a directory", $remoteEntry.text())
-    FTPUI.notifyUI('Entering Remote Directory: ' + $remoteEntry.text());
-    console.log($remoteEntry.text());
-    session.getCurrentDir().then(function (currentDir) {
-      session.changeWorkingDir($remoteEntry.text());
-      return currentDir;
-    }).then(function (currentDir) {
-      var dir = currentDir + $remoteEntry.text();
-      console.log("DIRRRRRRRRRRRRRRRRRRRR", dir);
-      return session.listDir(dir);
-    }).then(function (dirListing){
-      console.log("in handle element, got dir:", dirListing);
-      FTPUI.listRemote(dirListing);
-    }).catch(function (error) {
-      FTPUI.notifyUI("Could not get directory listing. " + error);
-    });
-  };
-
-  //FTP COMMAND: CDUP -> recieve unixOutput from LIST
-  // listremote(unixLsOutput)
-  FTPUI.handleRemoteBackButton = function () {
-    var session = this.session;
-    console.log("HANDLE REMOTE BACK BUTTON");
-    $('#remote-back').click(function() {
-      FTPUI.notifyUI('Going To Previous Remote Directory');
-      session.changeToParentDir().then(function () {
-        return session.getCurrentDir();
-      }).then(function (currentDir) {
-        return session.listDir(currentDir);
-      }).then(function (dirListing) {
-        FTPUI.listRemote(dirListing);
-      }).catch(function (error) {
-        FTPUI.notifyUI('Could not move to parent directory. ' + error);
-      });
-    });
-  };
-
-  FTPUI.handleLocalDir = function() {
-    var fileList = document.getElementById('local-browser');
-    $('#choose-directory').click(function() {
-      chrome.fileSystem.chooseEntry({type: 'openDirectory'}, function (entry) {
-        var rootEntry = entry;
-        var localDir = FTPUI.createLocalDir(entry, fileList);
-
-        // local back button functionality 
-        $('#local-back').click(function() {
-          try {
-              localDir.visitParent();
-              FTPUI.notifyUI('Going To Previous Local Directory');
-          } catch (error) {
-              console.log(err.message);
-          }
-        });
-
-        $('#save').click(function (){
-          try {
-              // make function more dynamic, also test with an arrayBuffer
-          saveToFile(rootEntry, 'justAString', function(){
-              localDir.listDirectory(rootEntry);
-          });
-          } catch (err){
-              console.log(err.message);
-          }
-        });
-      });
-    });
-  };
-
-  FTPUI.handleDownload = function() {
-    var session = this.session;
-   $('#download').click(function() {
-    if (!FTPUI.remoteEntry) { // or local entry is not a folder
-      // user has not selected a remote and local entry
-      return;
-    }
-    else {
-      if (!FTPUI.localEntry) {
-        //get local dir and download it there. 
-      }
-      else {
-        session.download(FTPUI.remoteEntry, FTPUI.localEntry);
-      }
-      FTPUI.remoteEntry = null;
-      FTPUI.localEntry = null;
-    }
-    });
-  };
-
-	FTPUI.createLocalDir = function(entry, element) {
-		var localDirectory = {
-			entry : entry,
-			latestEntry : entry,
-			listDirectory : function (entry) {
-				var entries = entry; //for readability
-				var DirectoryLister = entries.createReader(); //creates a Directory Reader object for listing contents of a directory
-				DirectoryLister.readEntries(fetchEntries);
-				var list = [];
-				function fetchEntries (entries) {
-					//once all entries have been returend by readEntries() it produces an empty array
-					//the entries produced by readEntries must not include the directory itself "." or its parent ".."
-					if (entries.length === 0) {
-						localDirectory.displayEntries(list, element);
-					}
-					else {
-						for ( var i = 0; i < entries.length; i++) {
-							list.push(entries[i]);
-						}
-						DirectoryLister.readEntries(fetchEntries); //keep calling readEntries() until we get an empty array
-					}
-				}
-				localDirectory.latestEntry = entry;
-			},
-			displayEntries : function (entries, element) {
-			// clear the current entries
-			element.textContent = '';
-			// Array.forEach paremeters (value of element,index of element, the arrayObject itself)
-			entries.forEach(function (entry, i, entries) {
-
-				var $li = $('<li>');
-				var $link = $('<a>');
-					if (entries[i].isDirectory) {
-							$link.prop('textContent', entry.name += '/');
-							$link.addClass('listed-dir');
-					} else {
-							$link.prop('textContent', entry.name);
-							$link.addClass('listed-file');
-					}
-
-					//add anchor tag to li element
-					$link.appendTo($li);
-
-					//add li element to un-ordered element list
-					$li.appendTo($(element));
-
-					$link.dblclick(function() {
-						if ($link.hasClass('listed-dir')) {
-							localDirectory.listDirectory(entry);
-							FTPUI.notifyUI('Entering Local Directory: ' + $link.text());
-						} else {
-							FTPUI.notifyUI('Selected Local File: ' +  $link.text());
-						}
-					});
-          $link.click(function () {
-            FTPUI.handleSelect($link);
-            FTPUI.localEntry = $link;
-          });
-				});
-			},
-			visitParent : function() {
-				this.latestEntry.getDirectory('..', {create: false}, localDirectory.listDirectory.bind(this));
-			},
-		};
-		//populate the fileBrowser
-		localDirectory.listDirectory(entry);
-		return localDirectory;
-
-	};
-
-  /* Send the User message updates */
-  /* @param Type: String or Error Object*/
-  FTPUI.notifyUI = function (notification) {
-    if(typeof notifcation === Error) notification.toString();
-    var $li = $('<li>');
-    $li.prop('textContent', notification);
-      $('#ui-message').append($li);
-  };
-  
-  return FTPUI;
-
-}(FTPUI || {}));
diff --git a/Prototype/FileSystemUtils.js b/Prototype/FileSystemUtils.js
new file mode 100644
index 0000000..ae024f7
--- /dev/null
+++ b/Prototype/FileSystemUtils.js
@@ -0,0 +1,133 @@
+var FileSystemUtils = (function (FileSystemUtils) {
+
+  FileSystemUtils.createFolder = function(folderPath, destination) {
+    return new Promise(function (resolve, reject) {
+      var onTaskComplete = function (folderEntry) {
+        //console.log("promise resolved with", folderEntry);
+        resolve(folderEntry);
+      };
+      FileSystemUtils.constructEntryName(folderPath, destination).then(
+        function (folderName) {
+          //console.log("constructed unique  folderName:", folderName)
+          destination.getDirectory(folderName, {create: true, exclusive: true},
+            function (folderEntry) {
+              onTaskComplete(folderEntry);
+          });
+      });
+    });
+  };
+
+  FileSystemUtils.createFile = function(destinationFileName, destination, size) {
+    return new Promise(function (resolve, reject) {
+      destination.getFile(destinationFileName, {create : true, exclusive : true},
+        function (fileEntry) {
+          fileEntry.createWriter(function (fileWriter) {
+            fileWriter.seek(fileWriter.length);
+            fileWriter.truncate(size);
+            fileWriter.onwrite = function (event) {
+              resolve(fileEntry);
+            };
+            fileWriter.onerror = reject;
+          }, reject);
+        }, reject);
+    });
+  };
+  
+  FileSystemUtils.isFolder = function (remoteEntry) {
+    if (remoteEntry[remoteEntry.length -1] == "/") {
+      return true;
+    }
+    return false;
+  };
+
+  FileSystemUtils.entryExists = function(entryName, dirContent, isDir) {
+    for (var i = 0; i < dirContent.length; i++) {
+      if(dirContent[i].name == entryName && dirContent[i].isDirectory == isDir) {
+        return true;
+      }
+    }
+    return false;
+  };
+
+  FileSystemUtils.getNameFromPath = function(path) {
+    // the full path of a folder will always end with a backslash
+    if (path[path.length-1] == "/") {
+      path = path.slice(0, path.length-1);
+    }
+    // get the full entry name following the last backslash
+    var entryNameIndex = path.lastIndexOf("/"), fullEntryName;
+    if (entryNameIndex == -1) {
+      fullEntryName = path;
+    }
+    else {
+      fullEntryName = path.slice(entryNameIndex+1);
+    }
+    return fullEntryName;
+  };
+
+  FileSystemUtils.seperateFileExtention = function(fullEntryName) {
+    var fileExtentionIndex = fullEntryName.lastIndexOf("."), entryName,
+        fileExtention = "";
+    if (fileExtentionIndex == -1) {
+      entryName = fullEntryName;
+    }
+    else {
+      fileExtention = fullEntryName.slice(fileExtentionIndex);
+      entryName = fullEntryName.slice(0, fileExtentionIndex);
+    }
+    return {"name": entryName, "extention": fileExtention};
+  };
+
+  FileSystemUtils.constructEntryName = function(entryPath, destination) {
+    var isDir = false, entryName, fileExtention = "", constructedName;
+    // the full path of a folder will always end with a backslash
+    if (entryPath[entryPath.length-1] == "/") {
+      isDir = true;
+      entryPath = entryPath.slice(0, entryPath.length-1);
+    }
+    var fullEntryName = FileSystemUtils.getNameFromPath(entryPath);
+    // if it's a file separate the fileExtention from the fileName
+    if (!isDir) {
+      var nameAndExtention = FileSystemUtils.seperateFileExtention(fullEntryName);
+      entryName = nameAndExtention.name;
+      fileExtention = nameAndExtention.extention;
+    }
+    else {
+      entryName = fullEntryName;
+    }
+    constructedName = fullEntryName;
+    return FileSystemUtils.getLocalDirContents(destination).then(function (dirContent) {
+        var i = 1;
+        while (FileSystemUtils.entryExists(constructedName, dirContent, isDir)) {
+          constructedName = entryName + "(" + i + ")" + fileExtention;
+          i++;
+        }
+        return constructedName;
+    });
+  };
+
+  FileSystemUtils.getLocalDirContents = function(entry) {
+    return new Promise(function (resolve, reject) {
+      var DirectoryLister = entry.createReader(); //creates a Directory Reader object for listing contents of a directory
+      DirectoryLister.readEntries(fetchEntries);
+      var list = [];
+      function fetchEntries (entries) {
+          //once all entries have been returend by readEntries() it produces an empty array
+          //the entries produced by readEntries must not include the directory itself "." or its parent ".."
+          if (entries.length === 0) {
+              resolve(list);
+          }
+          else {
+              for ( var i = 0; i < entries.length; i++) {
+                  list.push(entries[i]);
+              }
+              DirectoryLister.readEntries(fetchEntries); //keep calling readEntries() until we get an empty array
+          }
+      }
+    });
+  };
+  
+  return FileSystemUtils;
+
+}(FileSystemUtils || {}));
+
diff --git a/Prototype/SocketMock.js b/Prototype/SocketMock.js
index a9778b2..3179002 100644
--- a/Prototype/SocketMock.js
+++ b/Prototype/SocketMock.js
@@ -13,282 +13,254 @@
 * including socketId, localAddress, localPort, peerAddress, peerPort, etc.
 * These properties are set within the Socket class.
 */
-"use strict"
+'use strict'
 var socketsCreated = {};
 var socketsTotal = 0;
 var nextlocalPort = 1049;
 var nextServerPort = 6700;
 var dataSocket = null;
-var dataPort = null;
+var dataConnections = {};
 var lastCommand = null;
 var MockSocket = {};
 MockSocket.onReceive = {};
 MockSocket.onReceive.callbacks = [];
 MockSocket.onReceiveError = {};
 MockSocket.onReceiveError.callbacks = [];
+
 // A few error codes as defined for the Chrome socket API
 var errors = {
-	FAILED: -2,
-	UNEXPECTED: -9,
-	SOCKET_NOT_CONNECTED: -15,
-	CONNECTION_REFUSED: -102
+  FAILED: -2,
+  UNEXPECTED: -9,
+  SOCKET_NOT_CONNECTED: -15,
+  CONNECTION_REFUSED: -102
 };
 
 var enableDebug = false;
 
 function print (message) {
-	if (enableDebug) {
-		console.log(message);
-	}
+  if (enableDebug) {
+    console.log(message);
+  }
 }
 
-function timeOutMock() {
-	return;
+function constructServerResponse(reply) {
+  var encoder = new TextEncoder("utf-8");
+  var response = encoder.encode(reply);
+  return response;
 }
 
-function Socket (properties) {
-	if (typeof(properties.name) != "undefined") {
-		this.name = name;
-	}
-	this.connected = false;
-	this.localAddress = "1324:0:1032:1:cd15:c41e:df8e:4f79";
-	this.localPort = nextlocalPort;
-	nextlocalPort++;
-	this.paused = false;
-	this.persistent = properties.persistent || false;
-	socketsTotal++;
-	this.socketId = socketsTotal;
-	this.peerAddress = "";
-	this.peerPort = 0;
-	this.bufferSize = properties.bufferSize || 4096;
-	setTimeout(timeOutMock, 2);
-}
-
-function serverConnectResponse(result) {
-	var encoder = new TextEncoder("utf-8");
-	var response = encoder.encode("220 Service ready for new user\r\n");
-	var responseArray = response.buffer.slice(response.byteOffset, 
-		response.byteOffset + response.byteLength);
-	return responseArray;
-}
-
-Socket.prototype.connect = function (socketId, peerAddress, peerPort, callback) {
-	var result = 0;
-	if (typeof(socketId) != "number" || (socketId < 0) || 
-		typeof(peerPort) != "number" || isNaN(peerPort) || (peerPort < 0)) {
-		print(socketId);
-		print(peerAddress);
-		print(peerPort);
-		result = errors.UNEXPECTED;
-	}
-	else if (typeof(peerAddress) != "string" || peerAddress == "") {
-		result = errors.UNEXPECTED;
-	}
-	else {
-		this.peerAddress = peerAddress;
-		this.peerPort = peerPort;
-		this.connected = true;
-		result = 0;
-	}
-	setTimeout(timeOutMock, 2);
-	if (typeof(callback) == "function"){
-		callback(result);
-	}
-	if (result == 0) {
-		//If connect succeeded, then send the 220 server message to the user
-		var responseArray = serverConnectResponse(result);
-		var connectInfo = {
-			socketId: socketId,
-			data: responseArray 
-		};
-		for (var i = 0; i < MockSocket.onReceive.callbacks.length; i++) {
-			MockSocket.onReceive.callbacks[i](connectInfo);
-		}
-	}
-}
-
-Socket.prototype.setKeepAlive = function (socketId, enable, delay, callback) {
-	var result = 0;
-	if (typeof(socketId) != "number") { 
-		result = errors.SOCKET_NOT_CONNECTED;
-	}
-	if (typeof(callback) == "function"){
-		callback(result);
-	}
-}
-
-MockSocket.onReceive.addListener = function (callback) {
-	//remembering the callback function to notify user when data is "received"
-	//from the server.
-	MockSocket.onReceive.callbacks.push(callback);
-}
-
-MockSocket.onReceiveError.addListener = function (callback) {
-	MockSocket.onReceiveError.callbacks.push(callback);
-}
-
-MockSocket.onReceive.removeListener = function (callback) {
-	var i = MockSocket.onReceive.callbacks.indexOf(callback);
-	if (i != -1) {
-		MockSocket.onReceive.callbacks.splice(i, 1);
-	}
-}
-
-MockSocket.onReceiveError.removeListener = function (callback) {
-	var i = MockSocket.onReceiveError.callbacks.indexOf(callback);
-	if (i != -1) {
-		MockSocket.onReceiveError.callbacks.splice(i, 1);
-	}
-}
-
-MockSocket.create = function (properties, callback) {
-	var socketObj = new Socket(properties);
-	var socketId = socketObj.socketId;
-	socketsCreated[socketId] = socketObj;
-	var createInfo = {"socketId": socketId};
-	if (typeof(callback) == "function") {
-		callback(createInfo);
-	}
+function connect (socketObj, peerAddress, peerPort, callback) {
+  var result = 0;
+  var socketId = socketObj.socketId;
+  if (typeof(socketId) != "number" || (socketId < 0) ||
+    typeof(peerPort) != "number" || isNaN(peerPort) || (peerPort < 0) ||
+  typeof(peerAddress) != "string" || peerAddress === "") {
+    result = errors.UNEXPECTED;
+  } else {
+    socketObj.peerAddress = peerAddress;
+    socketObj.peerPort = peerPort;
+    socketObj.connected = true;
+    result = 0;
+  }
+  if (typeof(callback) == "function"){
+    callback(result);
+  }
+  if (result === 0) {
+    //If connect succeeded, then send the 220 server message to the user
+    var responseArray = constructServerResponse("220 Service ready for new user\r\n");
+    var connectInfo = {
+      socketId: socketId,
+      data: responseArray
+    };
+    for (var i = 0; i < MockSocket.onReceive.callbacks.length; i++) {
+      MockSocket.onReceive.callbacks[i](connectInfo);
+    }
+  }
 }
 
 MockSocket.connect = function (socketId, peerAddress, peerPort, callback) {
-	var socketObj = socketsCreated[socketId];
-	if (socketObj == undefined) { 
-		callback(errors.FAILED) // a generic failure occured 
-	}
-	else {
-		if (peerPort == dataPort) {
-			dataSocket = socketId; // remember data socketId.  
-		}
-		socketObj.connect(socketObj.socketId, peerAddress, peerPort, callback);
-	}
-}
+  var socketObj = socketsCreated[socketId];
+  if (socketObj === undefined) {
+    callback(errors.FAILED); // a generic failure occured
+  } else {
+    for (var commandId in dataConnections) {
+      if (peerPort == dataConnections[commandId].dataPort) {
+        // requested a data connection. Remembering the socketId of the data
+        // connection is neccessary when we send data through the data connection
+        dataConnections[commandId].dataId = socketId;
+      }
+    }
+    connect(socketObj, peerAddress, peerPort, callback);
+  }
+};
 
-MockSocket.setKeepAlive = function (socketId, enable, delay, callback) {
-	var socketObj = socketsCreated[socketId];
-	if (socketObj == undefined) {
-		callback(errors.FAILED); 
-	}
-	else {
-		socketObj.setKeepAlive(socketObj.socketId, enable, delay, callback);
-	}
-}
+MockSocket.onReceive.addListener = function (callback) {
+  //remembering the callback function to notify user when data is "received"
+  //from the server.
+  MockSocket.onReceive.callbacks.push(callback);
+};
+
+MockSocket.onReceiveError.addListener = function (callback) {
+  MockSocket.onReceiveError.callbacks.push(callback);
+};
+
+MockSocket.onReceive.removeListener = function (callback) {
+  var i = MockSocket.onReceive.callbacks.indexOf(callback);
+  if (i != -1) {
+    MockSocket.onReceive.callbacks.splice(i, 1);
+  }
+};
+
+MockSocket.onReceiveError.removeListener = function (callback) {
+  var i = MockSocket.onReceiveError.callbacks.indexOf(callback);
+  if (i != -1) {
+    MockSocket.onReceiveError.callbacks.splice(i, 1);
+  }
+};
+
+MockSocket.create = function (properties, callback) {
+  var name;
+  if (typeof(properties.name) != "undefined") {
+    name = properties.name;
+  }
+  socketsTotal++;
+  var socketObj = {
+    connected: false,
+    localAddress: "1324:0:1032:1:cd15:c41e:df8e:4f79",
+    localPort: nextlocalPort,
+    paused: false,
+    persistent: properties.persistent || false,
+    socketId: socketsTotal,
+    peerAddress: "",
+    peerPort: 0,
+    bufferSize: properties.bufferSize || 4096,
+    name: name || ""
+  };
+  nextlocalPort++;
+  socketsCreated[socketObj.socketId] = socketObj;
+  var createInfo = {"socketId": socketObj.socketId};
+  if (typeof(callback) == "function") {
+    callback(createInfo);
+  }
+};
 
 MockSocket.getInfo = function (socketId, callback) {
-	var socketObj = socketsCreated[socketId];
-	callback(socketObj);
-}
-
+  var socketObj = socketsCreated[socketId];
+  callback(socketObj);
+};
 
 function isCommandCode(commandCode) {
-	var legalCodes = [
-		"USER", "PASS", "LIST", "ACCT", "NLIST", "CWD", "QUIT", "PASV", "PORT",
-		"TYPE", "STRU", "MODE"
-	];
-	if (commandCode.length > 4 || legalCodes.indexOf(commandCode) < 0) {
-		// command codes are 4 or fewer characters
-		return false;
-	}
-	return true;
-} 
+  var legalCodes = [
+    "USER", "PASS", "LIST", "ACCT", "NLIST", "CWD", "QUIT", "PASV", "PORT",
+    "TYPE", "STRU", "MODE", "EPSV", "NOOP", "CDUP", "PWD"
+  ];
+  if (commandCode.length > 4 || legalCodes.indexOf(commandCode) < 0) {
+    // command codes are 4 or fewer characters
+    return false;
+  }
+  return true;
+}
 
-
-function getServerResponse(command) {
-	console.log("entered getServerResponse")
-	var commandCode = /\w*/.exec(command), encoder = new TextEncoder("utf-8");
-	if (commandCode == null || !isCommandCode(commandCode[0])) {
-		// command codes are 4 or fewer characters
-		var response = "500 Unknown command\r\n";
-	}
-	else { 
-		// valid FTP command issued, issue an appropriate response
-		console.log(commandCode[0], commandCode == "PASV");
-		switch (commandCode[0]) {
-			case "USER":
-				var username = /\s\S*\r\n/.exec(command);
-				if (username == null) {
-					var response = "501 Syntax error in parameters or arguments\r\n";
-				}
-				else {
-					username = /\s\S*/.exec(username[0]);
-					if (username == null) {
-						var response = "501 Syntax erro in parameters or arguments\r\n";
-					}
-					else {
-						var response = "331 User name okay, need password\r\n"
-					}
-				}
-				break;
-			case "PASS":
-				console.log(command);
-				var password = /\s\S*\r\n/.exec(command);
-				console.log(password[0]);
-				if (password == null) {
-					var response = "501 Syntax error in parameters or arguments\r\n";
-				}
-				else {
-					password = /\s\S*/.exec(password[0]);
-					console.log("getting password arg", password[0])
-					if (password == null) {
-						var response = "501 Syntax erro in parameters or arguments\r\n";
-					}
-					else if (lastCommand != "USER") {
-						console.log("lastCommand", lastCommand)
-						var response = "530 Not logged in\r\n";
-					}
-					else {
-						var response = "230 User logged in, proceed\r\n";						
-					}
-				}
-				break;
-			case "PASV":
-				var serverPort = nextServerPort;
-				dataPort = serverPort; // remebering the port we sent to the user
-				nextServerPort++;
-				var portHigherBits = (serverPort & 0xFF00)>>8;
-				var portLowerBits = (serverPort & 0xFF)
-				var response = ("227 Entering passive mode " + "(0,0,0,0," + 
-					portHigherBits + "," + portLowerBits +")\r\n");
-				break;
-			default:
-				var response = "500 Unknown command\r\n"; 
-		}
-	}
-	response = encoder.encode(response);
-	var responseArray = response.buffer.slice(response.byteOffset, 
-							response.byteOffset + response.byteLength);
-	lastCommand = commandCode[0]; // some commands are immediately preceded 
-									 // by other commands, hence remember last command 
-	return responseArray;
+function getServerResponse(command, socketId) {
+  var commandCode = /\w*/.exec(command);
+  var response;
+  if (commandCode === null || !isCommandCode(commandCode[0])) {
+    // command codes are 4 or fewer characters
+    response = "500 Unknown command\r\n";
+  } else {
+    // valid FTP command issued, issue an appropriate response
+    switch (commandCode[0]) {
+      case "USER":
+        var username = /\s\S*\r\n/.exec(command);
+        if (username === null) {
+          response = "501 Syntax error in parameters or arguments\r\n";
+        } else {
+          username = /\s\S*/.exec(username[0]);
+          if (username === null) {
+            response = "501 Syntax erro in parameters or arguments\r\n";
+          } else {
+          response = "331 User name okay, need password\r\n";
+          }
+        }
+        break;
+      case "PASS":
+        var password = /\s\S*\r\n/.exec(command);
+        if (password === null) {
+          response = "501 Syntax error in parameters or arguments\r\n";
+        } else {
+          password = /\s\S*/.exec(password[0]);
+          if (password === null) {
+            response = "501 Syntax erro in parameters or arguments\r\n";
+          } else if (lastCommand != "USER") {
+            response = "530 Not logged in\r\n";
+          } else {
+            response = "230 User logged in, proceed\r\n";
+          }
+        }
+        break;
+      case "PASV":
+        var serverPort = nextServerPort;
+        dataConnections[socketId] = {"dataPort": serverPort, dataId: null};
+        nextServerPort++;
+        var portHigherBits = (serverPort & 0xFF00)>>8;
+        var portLowerBits = (serverPort & 0xFF);
+        response = ("227 Entering passive mode " + "(0,0,0,0," +
+        portHigherBits + "," + portLowerBits +")\r\n");
+        break;
+      case "EPSV":
+        var serverPort = nextServerPort;
+        dataConnections[socketId] = {"dataPort": serverPort, dataId: null};
+        nextServerPort++;
+        response = ("227 Entering passive mode " + "(|||" + serverPort +
+            "|)\r\n");
+        break;
+      case "NOOP":
+        if (command != "NOOP\r\n") {
+          response = "501 Syntax erro in parameters or arguments\r\n";
+        } else {
+          response = "220 OK\r\n";
+        }
+        break;
+      case "PWD":
+        response = "257 \"/home/ftp\"\r\n";
+        break;
+      case "CDUP":
+        response = "200 Directory changed\r\n";
+        break;
+      case "CWD":
+        response = "250 Directory changed\r\n";
+        break;
+      default:
+        response = "500 Unknown command\r\n";
+      }
+  }
+  response = constructServerResponse(response);
+  lastCommand = commandCode[0]; // some commands are immediately preceded
+      // by other commands, hence remember last command
+    return response;
 }
 
 
 MockSocket.send = function (socketId, data, callback) {
-	console.log("MockSocket send")
-	var socketObj = socketsCreated[socketId], decoder = new TextDecoder("utf-8");
-	if (socketObj == undefined) {
-		callback({resultCode: errors.FAILED, bytesSent: 0});
-	}
-	else if (!socketObj.connected) {
-		callback({resultCode: errors.SOCKET_NOT_CONNECTED, bytesSent: 0});
-	}
-	else if (socketObj.paused) {
-		callback({resultCode: errors.FAILED, bytesSent: 0});
-	}
-	else {
-		console.log("socket exists, connected")
-		var dataStr = decoder.decode(new Uint8Array(data));
-		console.log(dataStr)
-		var responseArray = getServerResponse(dataStr);
-		var sendInfo = {
-			socketId: socketId,
-			data: responseArray 
-		};
-		// acknowledge receit of data
-		callback({resultCode: 0, bytesSent: data.byteLength});
-		// send server response 
-		for (var i = 0; i < MockSocket.onReceive.callbacks.length; i++) {
-			MockSocket.onReceive.callbacks[i](sendInfo);
-		}
-	}
-}
+  var socketObj = socketsCreated[socketId], decoder = new TextDecoder("utf-8");
+  if (socketObj === undefined) {
+    callback({resultCode: errors.FAILED, bytesSent: 0});
+  } else if (!socketObj.connected) {
+    callback({resultCode: errors.SOCKET_NOT_CONNECTED, bytesSent: 0});
+  } else if (socketObj.paused) {
+    callback({resultCode: errors.FAILED, bytesSent: 0});
+  } else {
+    var dataStr = decoder.decode(new Uint8Array(data));
+    var responseArray = getServerResponse(dataStr, socketId);
+    var sendInfo = {
+      socketId: socketId,
+      data: responseArray
+    };
+    // acknowledge receit of data
+    callback({resultCode: 0, bytesSent: data.byteLength});
+    // send server response
+    for (var i = 0; i < MockSocket.onReceive.callbacks.length; i++) {
+      MockSocket.onReceive.callbacks[i](sendInfo);
+    }
+  }
+};
diff --git a/Prototype/file_writer.js b/Prototype/file_writer.js
new file mode 100644
index 0000000..577ea46
--- /dev/null
+++ b/Prototype/file_writer.js
@@ -0,0 +1,55 @@
+"use-strict"
+function ParallelFileWriter(entry) {
+  this.fileEntry = entry;
+  this.fileReceived = false;
+  //this.onComplete = onComplete;
+  //this.onComplete = oneComplete;
+  this.position = 0;
+  this.workingWriters = 0;
+  this.writers = [];
+
+}
+
+ParallelFileWriter.prototype.getWriter = function() {
+  var self = this;
+  return new Promise(function (resolve, reject) {
+    if (self.writers.length > 0) {
+      self.workingWriters++;
+      resolve(self.writers.shift());
+    }
+    else {
+      self.fileEntry.createWriter(function (fileWriter) {
+        self.workingWriters++;
+        resolve(fileWriter);
+      }, reject);
+    }
+	});
+};
+
+ParallelFileWriter.prototype.restoreWriter = function(writer) {
+  this.writers.push(writer);
+  this.workingWriters--;
+};
+
+ParallelFileWriter.prototype.write = function (data, callback) {
+  var self = this;
+  var curPosition = self.position;
+  self.position += data.byteLength;
+  self.getWriter().then(function (writer) {
+    var blob = new Blob([data]);
+    writer.seek(curPosition);
+    writer.write(blob);
+    writer.onwrite = function() {
+      self.restoreWriter(writer);
+      callback();
+    };
+    writer.onerror = FTPUI.notifyUI;
+  }).catch(function (error) {
+    FTPUI.notifyUI("file saving problem " + error);
+  });
+};
+
+
+
+
+
diff --git a/Prototype/index.js b/Prototype/index.js
index bbe5c7c..24455e3 100644
--- a/Prototype/index.js
+++ b/Prototype/index.js
@@ -1,21 +1,20 @@
 
 $(document).ready(function () {
-    console.log("We are in onload");
-    FTPUI.handleLocalDir();
+    FTPUI.handleLocalButtons();
+    // the page will be updated to take user input (i.e host, port, username, password)
     var client = new FTPSession("ftp.mozilla.org", 21);
-    client.login("", "").then(function (welcomeMsg) {
+      client.login("", "").then(function (welcomeMsg) {
       client.listDir().then(function (listDir) {
-        console.log("got listDirectory");
         FTPUI.createRemoteDir(client, listDir);
         FTPUI.handleRemoteBackButton();
+        FTPUI.handleDownload();
       }).catch(function (error) {
         // listDir error handling
-        console.log("LIST DIR ERRORRRRRRRRRRRRRR", error);
+        console.log(error);
       });
     }).catch(function (error) {
-      console.log("Caught error login");
+      console.log("Caught login error", error);
     });
-
 });
 
 
diff --git a/Prototype/tests.js b/Prototype/tests.js
index b5688c5..642c85f 100644
--- a/Prototype/tests.js
+++ b/Prototype/tests.js
@@ -1,160 +1,163 @@
-BUFFER_SIZE = 8192
+var BUFFER_SIZE = 8192;
 
 QUnit.test("createSocket_", function(assert) {
-	var client = new FTPClient(MockSocket); 
-	var	createSocket_ = client.test.createSocket_; 
-	var	FTPInfo = client.test.FTPInfo;
-	QUnit.stop();
-	createSocket_().then(function(socketInfo){
-		QUnit.ok(socketInfo.socketId >= 0, "socket created");
-	}).catch(function (error) {
-		QUnit.equal(error, "This should have succeeded");
-	});
-	QUnit.start();
+  var client = new FTPClient(MockSocket);
+  var	createSocket_ = client.test.createSocket_, FTPInfo = client.test.FTPInfo;
+  QUnit.stop();
+  createSocket_().then(function(socketInfo){
+    QUnit.ok(socketInfo.socketId >= 0, "socket created");
+  }).catch(function (error) {
+  QUnit.equal(error, "This should have succeeded");
+  });
+  QUnit.start();
 });
 
 QUnit.test("connectWrapper_", function() {
 	QUnit.stop();
-	var client = new FTPClient(MockSocket), 
-		createSocket_ = client.test.createSocket_,
-		connectWrapper_ = client.test.connectWrapper_;
-	createSocket_().then(function (socketInfo) {
-		return connectWrapper_(socketInfo.socketId, "cmu.edu", 22); 
-	}).then(function (result) {
-		QUnit.ok(result >= 0, "connection successful"); 
+  var client = new FTPClient(MockSocket),
+    createSocket_ = client.test.createSocket_,
+    connectWrapper_ = client.test.connectWrapper_;
+	expect(5);
+  createSocket_().then(function (socketInfo) {
+    return connectWrapper_(socketInfo.socketId, "cmu.edu", 22);
+  }).then(function (result) {
+    QUnit.ok(result >= 0, "connection successful");
 	}).catch(function (error) {
 		QUnit.equal(error, "This should have suceeded");
 	});
 	createSocket_().then(function (socketInfo) {
-		return connectWrapper_(socketInfo.socketId, "", 22); 
-	}).then(function (result) {
-		QUnit.equal(result, "connection should not succeed"); 
+		return connectWrapper_(socketInfo.socketId, "", 22);
+  }).then(function (result) {
+    QUnit.equal(result, "connection should not succeed");
 	}).catch(function (error) {
-		QUnit.equal(error.message, "Connect error code -9", 
+		QUnit.ok(error instanceof Error === true, "error received");
+    QUnit.equal(error.message, "Error code -9",
 			"connection fail expected");
 	});
 	createSocket_().then(function (socketInfo) {
-		return connectWrapper_(socketInfo.socketId, "cmu.edu", NaN); 
+		return connectWrapper_(socketInfo.socketId, "cmu.edu", NaN);
 	}).then(function (result) {
-		QUnit.equal(result, "connection should not succeed"); 
+		QUnit.equal(result, "connection should not succeed");
 	}).catch(function (error) {
-		QUnit.equal(error.message, "Connect error code -9", 
-			"connection fail expected");
+		QUnit.ok(error instanceof Error === true, "error received");
+    QUnit.equal(error.message, "Error code -9",
+      "connection fail expected");
 	});
 	QUnit.start();
 });
 
 QUnit.test("connect", function() {
-	expect(3);
+	expect(5);
 	QUnit.stop();
 	var client = new FTPClient(MockSocket);
 	client.connect("google.com", 22).then(function (reply) {
-		QUnit.equal(reply, "220 Service ready for new user\r\n", 
-							"connection successful"); })
-	.catch(function(error){
+    QUnit.equal(reply, "220 Service ready for new user\r\n",
+      "connection successful");
+	}).catch(function(error){
 		QUnit.equal(error, "This should have suceeded");
 	});
 	client = new FTPClient(MockSocket);
 	client.connect("", 22).then(function (reply) {
-		QUnit.equal(reply, "Connection should not succeed"); 
+		QUnit.equal(reply, "Connection should not succeed");
 	}).catch(function (error){
-		QUnit.equal(error.message, "Connect error code -9", 
-					"Connection fail expected");
+    QUnit.ok(error instanceof Error === true, "error received");
+		QUnit.equal(error.message, "Error code -9",
+      "Connection fail expected");
 	});
 	client = new FTPClient(MockSocket);
 	client.connect("cmu.edu","portNaN").then(function (reply) {
-	 	QUnit.equal(reply, "Connection should not succeed"); 
-	 }).catch(function (error){
-		QUnit.equal(error.message, "Connect error code -9", 
-					"Connection fail expected");
+		QUnit.equal(reply, "Connection should not succeed");
+	}).catch(function (error) {
+		QUnit.ok(error instanceof Error === true, "error received");
+		QUnit.equal(error.message, "Error code -9",
+      "Connection fail expected");
 		QUnit.start();
 	});
-}); 
+});
 
-QUnit.test("organizeData_", function() {
+QUnit.test("splitLines_", function() {
 	expect(27);
 	// case I
 	var client = new FTPClient(MockSocket);
-	var FTPInfo = client.test.FTPInfo, organizeData_ = client.test.organizeData_,
+	var FTPInfo = client.test.FTPInfo, splitLines_ = client.test.splitLines_,
 		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
 
 	var receivedData = encoder.encode("rnr\nA\r\n");
-	organizeData_(receivedData);
+	splitLines_(receivedData);
 	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE), "no leftOver");
 	QUnit.equal(commandRawData.buffOffset, 0, "buffOffset 0 - no leftOver");
 	QUnit.deepEqual(commandRawData.parsedLines, ["rnr\n", "A\r\n"], "parsedLines");
 
 	// case II
 	receivedData = encoder.encode("google ");
-	organizeData_(receivedData);
-	QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedData.byteLength), 
-					receivedData, "leftOvers");
+	splitLines_(receivedData);
+	QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedData.byteLength),
+    receivedData, "leftOvers");
 	QUnit.equal(commandRawData.buffOffset, receivedData.byteLength);
 
 	// case III
 	receivedData = encoder.encode("love\r\n");
-	organizeData_(receivedData);
+	splitLines_(receivedData);
 	QUnit.deepEqual(commandRawData.parsedLines, ["rnr\n", "A\r\n", "google love\r\n"],
-					"added new parsed line");
-	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE), 
-					"reset buffer after combining");
+    "added new parsed line");
+	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE),
+    "reset buffer after combining");
 	QUnit.equal(commandRawData.buffOffset, 0, "reset buffOffset after combining");
 
 	// case Iv
 	receivedData = new Uint8Array([230, 176]);
-	organizeData_(receivedData);
-	QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedData.byteLength), 
-					receivedData, "wild character split");
+	splitLines_(receivedData);
+	QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedData.byteLength),
+    receivedData, "wild character split");
 	QUnit.equal(commandRawData.buffOffset, receivedData.byteLength);
 
 	// case V
 	receivedData = new Uint8Array([180, 32, 119, 105, 108, 100, 13, 10, 116]);
-	organizeData_(receivedData);
+	splitLines_(receivedData);
 	QUnit.deepEqual(commandRawData.buffer.subarray(0,1), new Uint8Array([116]));
 	QUnit.deepEqual(commandRawData.parsedLines, ["rnr\n", "A\r\n", "google love\r\n",
-										 "水 wild\r\n"]);
+    "水 wild\r\n"]);
 	QUnit.equal(commandRawData.buffOffset, 1, "combined data, rest buffOffset");
 
 	//case VI
 	receivedData = new Uint8Array([111, 98, 101]);
-	organizeData_(receivedData);
+	splitLines_(receivedData);
 	QUnit.deepEqual(commandRawData.buffer.subarray(0, commandRawData.buffOffset),
-					new Uint8Array([116, 111, 98, 101]), "combined non line data");	
-
-	var client = new FTPClient(MockSocket);
-	var FTPInfo = client.test.FTPInfo, organizeData_ = client.test.organizeData_,
-		commandRawData = FTPInfo.commandRawData;
-
-	var receivedData = encoder.encode("320 google\r\n");
-	organizeData_(receivedData);
-	QUnit.deepEqual(commandRawData.parsedLines, ["320 google\r\n"], "basic test");	
+    new Uint8Array([116, 111, 98, 101]), "combined non line data");
+  client = new FTPClient(MockSocket);
+	FTPInfo = client.test.FTPInfo;
+  splitLines_ = client.test.splitLines_;
+  commandRawData = FTPInfo.commandRawData;
+	receivedData = encoder.encode("320 google\r\n");
+	splitLines_(receivedData);
+	QUnit.deepEqual(commandRawData.parsedLines, ["320 google\r\n"], "basic test");
 	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE), "no left over");
 	QUnit.equal(commandRawData.buffOffset, 0, "buffOffset remained 0");
 
 	var receivedDataI = encoder.encode("320");
-	organizeData_(receivedDataI);
-	QUnit.deepEqual(commandRawData.parsedLines, ["320 google\r\n"], "uncomsumed line");	
-	QUnit.deepEqual(commandRawData.buffer.subarray(0, commandRawData.buffOffset), receivedDataI, 
-		 			"data stored");
-	QUnit.equal(commandRawData.buffOffset, receivedDataI.byteLength, 
-			   "buffOffset reflects dataReceived");
+	splitLines_(receivedDataI);
+	QUnit.deepEqual(commandRawData.parsedLines, ["320 google\r\n"], "uncomsumed line");
+	QUnit.deepEqual(commandRawData.buffer.subarray(0, commandRawData.buffOffset), receivedDataI,
+    "data stored");
+	QUnit.equal(commandRawData.buffOffset, receivedDataI.byteLength,
+    "buffOffset reflects dataReceived");
 
-	var receivedDataII = encoder.encode(" goog"); 
-	organizeData_(receivedDataII);
-	QUnit.deepEqual(commandRawData.parsedLines, ["320 google\r\n"], "unconsumed line");	
-	QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedDataI.byteLength), receivedDataI, 
-		 			"receivedDataI stored");
+	var receivedDataII = encoder.encode(" goog");
+  splitLines_(receivedDataII);
+  QUnit.deepEqual(commandRawData.parsedLines, ["320 google\r\n"], "unconsumed line");
+  QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedDataI.byteLength), receivedDataI,
+    "receivedDataI stored");
 	QUnit.deepEqual(commandRawData.buffer.subarray(receivedDataI.byteLength, commandRawData.buffOffset),
-	 				receivedDataII, "receivedDataII stored");
-	QUnit.equal(commandRawData.buffOffset, receivedDataI.byteLength + receivedDataII.byteLength, 
-			   "buffOffset reflects dataReceived");
+    receivedDataII, "receivedDataII stored");
+	QUnit.equal(commandRawData.buffOffset, receivedDataI.byteLength + receivedDataII.byteLength,
+    "buffOffset reflects dataReceived");
 
 	var receivedDataIII = encoder.encode("le\r\n");
-	organizeData_(receivedDataIII);
-	QUnit.deepEqual(commandRawData.parsedLines, ["320 google\r\n", "320 google\r\n"], 
-					"unconsumed lines");	
-	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE), 
-					"got full line, buffer now empty");
+	splitLines_(receivedDataIII);
+	QUnit.deepEqual(commandRawData.parsedLines, ["320 google\r\n", "320 google\r\n"],
+    "unconsumed lines");
+  QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE),
+    "got full line, buffer now empty");
 	QUnit.equal(commandRawData.buffOffset, 0, "buffOffset reset");
 });
 
@@ -165,52 +168,51 @@
 		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
 	var receivedData = encoder.encode("320-google\r\n");
 	var responses = getReply_(receivedData);
-	console.log("responses", responses)
 	QUnit.deepEqual(commandRawData.parsedLines, ["320-google\r\n"], "basic test");
 	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE), "no leftOver");
 	QUnit.equal(commandRawData.buffOffset, 0, "buffOffset = 0");
-	QUnit.ok(responses == null, "No response received yet");
+  QUnit.ok(responses === null, "No response received yet");
 
 	var receivedDataI = encoder.encode("love ");
 	responses = getReply_(receivedDataI);
 	QUnit.deepEqual(commandRawData.parsedLines, ["320-google\r\n"], "no new line received");
 	QUnit.deepEqual(commandRawData.buffer.subarray(0, commandRawData.buffOffset), receivedDataI,
-	  				"accumulating data");
-	QUnit.equal(commandRawData.buffOffset, receivedDataI.byteLength, 
-	  		   "updated buffOffset");
-	QUnit.ok(responses == null, "No response received yet");
+    "accumulating data");
+	QUnit.equal(commandRawData.buffOffset, receivedDataI.byteLength,
+    "updated buffOffset");
+	QUnit.ok(responses === null, "No response received yet");
 
 	var receivedDataII = encoder.encode("live ");
 	responses = getReply_(receivedDataII);
 	QUnit.deepEqual(commandRawData.parsedLines, ["320-google\r\n"], "again no new line");
-	QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedDataI.byteLength), 
-	 			receivedDataI, "previous data is still in the buff");
-	QUnit.deepEqual(commandRawData.buffer.subarray(receivedDataI.byteLength, 
-	 	receivedDataI.byteLength + receivedDataII.byteLength), receivedDataII,
-	      "accumulated new data");
+	QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedDataI.byteLength),
+    receivedDataI, "previous data is still in the buff");
+	QUnit.deepEqual(commandRawData.buffer.subarray(receivedDataI.byteLength,
+    receivedDataI.byteLength + receivedDataII.byteLength), receivedDataII,
+    "accumulated new data");
 	QUnit.equal(commandRawData.buffOffset, receivedDataI.byteLength+receivedDataII.byteLength,
-	 	"updated buffOffset");
-	QUnit.ok(responses == null, "No response received yet");
+    "updated buffOffset");
+	QUnit.ok(responses === null, "No response received yet");
 
 	var receivedDataIII = encoder.encode("laugh\r\n");
 	responses = getReply_(receivedDataIII);
 	QUnit.deepEqual(commandRawData.parsedLines, ["320-google\r\n", "love live laugh\r\n"],
-	 									"combined replies");
+    "combined replies");
 	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE), "reset buffer");
 	QUnit.equal(commandRawData.buffOffset, 0, "reset buffOffset");
-	QUnit.ok(responses == null, "No response received yet");
+	QUnit.ok(responses === null, "No response received yet");
 
 	var receivedDataIV = encoder.encode("320");
 	responses = getReply_(receivedDataIV);
-	QUnit.ok(responses == null, "No response just yet");
+	QUnit.ok(responses === null, "No response just yet");
 	var receivedDataV = encoder.encode(" \r\n");
 	responses = getReply_(receivedDataV);
-	QUnit.deepEqual(responses, ["320-google\r\nlove live laugh\r\n320 \r\n"], 
-	 			"full response received");
+	QUnit.deepEqual(responses, ["320-google\r\nlove live laugh\r\n320 \r\n"],
+    "full response received");
 	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE),
-	 			"buffer cleared");
-	QUnit.equal(commandRawData.buffOffset, 0, "bufferOffsert reset")
-	QUnit.deepEqual(commandRawData.parsedLines, [], "parsed lines cleared")
+    "buffer cleared");
+	QUnit.equal(commandRawData.buffOffset, 0, "bufferOffsert reset");
+	QUnit.deepEqual(commandRawData.parsedLines, [], "parsed lines cleared");
 });
 
 QUnit.test("getReply_ singlelined", function() {
@@ -222,9 +224,9 @@
 	var responses = getReply_(receivedData);
 	QUnit.deepEqual(commandRawData.parsedLines, [], "basic test");
 	QUnit.deepEqual(commandRawData.buffer.subarray(0, receivedData.byteLength), receivedData,
-					"no new lines");
+    "no new lines");
 	QUnit.equal(commandRawData.buffOffset, receivedData.byteLength, "buffOffset updated");
-	QUnit.ok(responses == null, "No response received yet");
+	QUnit.ok(responses === null, "No response received yet");
 
 	var receivedDataI = encoder.encode("\r\n");
 	responses = getReply_(receivedDataI);
@@ -241,25 +243,25 @@
 		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
 	var receivedData = encoder.encode("320 google");
 	var responses = getReply_(receivedData);
-	QUnit.ok(responses == null, "No response received yet");
+	QUnit.ok(responses === null, "No response received yet");
 	var receivedDataI = encoder.encode("\r\n320-google");
 	responses = getReply_(receivedDataI);
 	var leftOver = encoder.encode("320-google");
-	QUnit.deepEqual(responses, ["320 google\r\n"], 
-					"The singlelined response received");
+	QUnit.deepEqual(responses, ["320 google\r\n"],
+    "The singlelined response received");
 	QUnit.deepEqual(commandRawData.parsedLines, [], "no parsed lines");
 	QUnit.deepEqual(commandRawData.buffer.subarray(0, leftOver.byteLength), leftOver,
-				    "leftOver was stored in buff");
+    "leftOver was stored in buff");
 	QUnit.equal(commandRawData.buffOffset, leftOver.byteLength, "buffOffset updated");
 
 	var receivedDataII = encoder.encode("\r\nnext");
 	responses = getReply_(receivedDataII);
-	QUnit.ok(responses == null, "No new response received");
-	QUnit.deepEqual(commandRawData.parsedLines, ["320-google\r\n"], 
-					"first line of multiline response");
+	QUnit.ok(responses === null, "No new response received");
+	QUnit.deepEqual(commandRawData.parsedLines, ["320-google\r\n"],
+    "first line of multiline response");
 	leftOver = encoder.encode("next");
 	QUnit.deepEqual(commandRawData.buffer.subarray(0, leftOver.byteLength), leftOver,
-				    "leftOver was stored in buff");
+    "leftOver was stored in buff");
 	QUnit.equal(commandRawData.buffOffset, leftOver.byteLength, "buffOffset updated");
 
 	var receivedDataIII = encoder.encode("\r\n320 \r\n");
@@ -272,8 +274,8 @@
 	var receivedDataIV = encoder.encode("320-multi\r\n320 \r\n320 singlelined\r\n");
 	responses = getReply_(receivedDataIV);
 	QUnit.deepEqual(commandRawData.parsedLines, [], "recieved response, no parsed lines");
-	QUnit.deepEqual(responses, ["320-multi\r\n320 \r\n", "320 singlelined\r\n"], 
-					"multiple responses received");
+	QUnit.deepEqual(responses, ["320-multi\r\n320 \r\n", "320 singlelined\r\n"],
+    "multiple responses received");
 	QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE), "buffer cleared");
 	QUnit.equal(commandRawData.buffOffset, 0 , "buffOffset reset");
 });
@@ -309,9 +311,9 @@
 QUnit.test("onReceiveCallback_", function () {
 	expect(17);
 	var client = new FTPClient(MockSocket);
-	var FTPInfo = client.test.FTPInfo, 
-		onReceiveCallback_ = client.test.onReceiveCallback_,
-		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
+	var FTPInfo = client.test.FTPInfo,
+    onReceiveCallback_ = client.test.onReceiveCallback_,
+    commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
 	QUnit.stop();
 	client.connect("cmu.edu", 22).then(function () {
 		QUnit.deepEqual(commandRawData.parsedLines, [], "reply consumed");
@@ -324,43 +326,43 @@
 		onReceiveCallback_(sendInfo);
 
 		var leftOver = encoder.encode("220-multi");
-		QUnit.deepEqual(FTPInfo.serverResponses, ["220 OK\r\n"], 
-		 	            "recorded server response");
-		QUnit.deepEqual(commandRawData.parsedLines, [], 
-		 	            "returned reply, parsedLines empty");
-		QUnit.deepEqual(commandRawData.buffer.subarray(0, leftOver.byteLength), 
-		 		        leftOver, "leftOver stored in buffer");
+		QUnit.deepEqual(FTPInfo.serverResponses, ["220 OK\r\n"],
+      "recorded server response");
+		QUnit.deepEqual(commandRawData.parsedLines, [],
+      "returned reply, parsedLines empty");
+		QUnit.deepEqual(commandRawData.buffer.subarray(0, leftOver.byteLength),
+      leftOver, "leftOver stored in buffer");
 		QUnit.deepEqual(commandRawData.buffOffset, leftOver.byteLength);
 		
 		sendInfo.data = encoder.encode("line\r\n").buffer;
 		onReceiveCallback_(sendInfo);
 		QUnit.deepEqual(FTPInfo.serverResponses, ["220 OK\r\n"],
-		  	            "complete response has not been received yet");
+      "complete response has not been received yet");
 		QUnit.deepEqual(commandRawData.parsedLines, ["220-multiline\r\n"],
-		 				"begining of multiline");
+      "begining of multiline");
 		QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE),
-		 				"new line recieved, buffer cleared");
+      "new line recieved, buffer cleared");
 		QUnit.equal(commandRawData.buffOffset, 0, "buffOffset reset");
 
 		sendInfo.data = encoder.encode("220 end\r\n").buffer;
 		onReceiveCallback_(sendInfo);
-		QUnit.deepEqual(FTPInfo.serverResponses, 
-						["220 OK\r\n", "220-multiline\r\n220 end\r\n"],
-		 	            "new response received");
-		QUnit.deepEqual(commandRawData.parsedLines, [], 
-						"full responses received, no parsed lines");
+		QUnit.deepEqual(FTPInfo.serverResponses,
+      ["220 OK\r\n", "220-multiline\r\n220 end\r\n"],
+      "new response received");
+		QUnit.deepEqual(commandRawData.parsedLines, [],
+      "full responses received, no parsed lines");
 		QUnit.deepEqual(commandRawData.buffer, new Uint8Array(BUFFER_SIZE),
-						"new line recieved, buffer cleared");
+      "new line recieved, buffer cleared");
 		QUnit.equal(commandRawData.buffOffset, 0, "buffOffset reset");
 		
 		var responsePromise = client.test.readReply_();
 		var responsePromiseI = client.test.readReply_();
 		responsePromise.then(function (reply) {
-		 	QUnit.equal(reply, "220 OK\r\n", "read single-lined response");
-		 });
-		responsePromiseI.then(function (reply) {
-			QUnit.equal(reply, "220-multiline\r\n220 end\r\n", 
-		 				"read multilined response");
+      QUnit.equal(reply, "220 OK\r\n", "read single-lined response");
+    });
+    responsePromiseI.then(function (reply) {
+      QUnit.equal(reply, "220-multiline\r\n220 end\r\n",
+        "read multilined response");
 		});
 		QUnit.deepEqual(FTPInfo.serverResponses, [], "serverResponses cleared");
 		QUnit.start();
@@ -374,10 +376,10 @@
 QUnit.test("onReceiveCallback_ preliminary Responses basic", function () {
 	expect(10);
 	var client = new FTPClient(MockSocket);
-	var FTPInfo = client.test.FTPInfo, 
-		onReceiveCallback_ = client.test.onReceiveCallback_, 
-		readReply_ = client.test.readReply_,
-		readFinalReply_ = client.test.readFinalReply_,
+	var FTPInfo = client.test.FTPInfo,
+    onReceiveCallback_ = client.test.onReceiveCallback_,
+    readReply_ = client.test.readReply_,
+    readFinalReply_ = client.test.readFinalReply_,
 		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
 	QUnit.stop();
 	client.connect("cmu.edu", 22).then(function () {
@@ -392,10 +394,10 @@
 		sendInfo.data = encoder.encode("226 Closing data connection\r\n");
 		onReceiveCallback_(sendInfo);
 		QUnit.deepEqual(FTPInfo.serverResponses, ["150 Sending data\r\n",
-						"226 Closing data connection\r\n"], 
-		 	            "got 1yz server response");
-		QUnit.deepEqual(commandRawData.parsedLines, [], 
-		 	            "returned reply, parsedLines empty");
+      "226 Closing data connection\r\n"],
+      "got 1yz server response");
+		QUnit.deepEqual(commandRawData.parsedLines, [],
+      "returned reply, parsedLines empty");
 		var response = readReply_("LIST");
 		response.then(function () {
 			QUnit.equal(FTPInfo.preliminaryState, true, "in preliminary state");
@@ -405,7 +407,7 @@
 			QUnit.equal(FTPInfo.preliminaryState, false);
 			QUnit.deepEqual(FTPInfo.preliminaryRead, null);
 			QUnit.deepEqual(FTPInfo.serverResponses, [], "consumed server responses");
-			QUnit.deepEqual(commandRawData.parsedLines, [], "no parsed lines")
+			QUnit.deepEqual(commandRawData.parsedLines, [], "no parsed lines");
 			QUnit.start();
 		});
 	});
@@ -414,9 +416,9 @@
 QUnit.test("onReceiveCallback_ preliminary responses", function () {
 	expect(9);
 	var client = new FTPClient(MockSocket);
-	var FTPInfo = client.test.FTPInfo, 
-		onReceiveCallback_ = client.test.onReceiveCallback_, 
-		readReply_ = client.test.readReply_,
+	var FTPInfo = client.test.FTPInfo,
+    onReceiveCallback_ = client.test.onReceiveCallback_,
+    readReply_ = client.test.readReply_,
 		readFinalReply_ = client.test.readFinalReply_,
 		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
 	QUnit.stop();
@@ -444,7 +446,6 @@
 			QUnit.equal(FTPInfo.pendingReads.length, 1, "one pending read");
 			return readFinalReply_();
 		}).then(function (reply) {
-			console.log("INSIDE test", reply)
 			QUnit.equal(reply, "226 Closing data connection\r\n");
 			QUnit.deepEqual(FTPInfo.pendingReads, [], "all reads resolved");
 			QUnit.deepEqual(FTPInfo.serverResponses, [], "consumed all responses");
@@ -464,9 +465,9 @@
 QUnit.test("onReceiveCallback_ preliminary responses", function () {
 	//expect(9);
 	var client = new FTPClient(MockSocket);
-	var FTPInfo = client.test.FTPInfo, 
-		onReceiveCallback_ = client.test.onReceiveCallback_, 
-		readReply_ = client.test.readReply_,
+	var FTPInfo = client.test.FTPInfo,
+    onReceiveCallback_ = client.test.onReceiveCallback_,
+    readReply_ = client.test.readReply_,
 		readFinalReply_ = client.test.readFinalReply_,
 		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
 	QUnit.stop();
@@ -485,22 +486,22 @@
 		
 		onReceiveCallback_(sendInfo);
 		QUnit.deepEqual(FTPInfo.pendingReads.length, 0, "pending reads resolved");
-		QUnit.deepEqual(FTPInfo.serverResponses, ["150 Sending data\r\n"])
+		QUnit.deepEqual(FTPInfo.serverResponses, ["150 Sending data\r\n"]);
 		
 		sendInfo.data = encoder.encode("226 Closing data connection\r\n220 OK\r\n");
 		onReceiveCallback_(sendInfo);
-		QUnit.deepEqual(FTPInfo.preliminaryState, false, 
-						"haven't entered preliminary state yet");
-		QUnit.deepEqual(FTPInfo.serverResponses, ["150 Sending data\r\n", 
-						"226 Closing data connection\r\n", "220 OK\r\n"]);
+		QUnit.deepEqual(FTPInfo.preliminaryState, false,
+      "haven't entered preliminary state yet");
+		QUnit.deepEqual(FTPInfo.serverResponses, ["150 Sending data\r\n",
+      "226 Closing data connection\r\n", "220 OK\r\n"]);
 		readReply_();
-		QUnit.equal(FTPInfo.preliminaryState, true, 
-					"preliminary state encountered");		
-		readReply_();
-		QUnit.equal(FTPInfo.pendingReads.length, 1, 
-					"pending reads but preliminary state");
+		QUnit.equal(FTPInfo.preliminaryState, true,
+      "preliminary state encountered");
+    readReply_();
+		QUnit.equal(FTPInfo.pendingReads.length, 1,
+      "pending reads but preliminary state");
 		QUnit.deepEqual(FTPInfo.serverResponses, ["226 Closing data connection\r\n",
-						"220 OK\r\n"]);
+      "220 OK\r\n"]);
 		readFinalReply_();
 		QUnit.deepEqual(FTPInfo.serverResponses, [], "all responses consumed");
 		QUnit.deepEqual(FTPInfo.preliminaryState, false, "preliminary state finished");
@@ -515,9 +516,9 @@
 QUnit.test("onReceiveCallback_ multiple preliminary responses error", function () {
 	expect(6);
 	var client = new FTPClient(MockSocket);
-	var FTPInfo = client.test.FTPInfo, 
-		onReceiveCallback_ = client.test.onReceiveCallback_, 
-		readReply_ = client.test.readReply_,
+	var FTPInfo = client.test.FTPInfo,
+    onReceiveCallback_ = client.test.onReceiveCallback_,
+    readReply_ = client.test.readReply_,
 		readFinalReply_ = client.test.readFinalReply_,
 		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
 	QUnit.stop();
@@ -532,24 +533,24 @@
 		onReceiveCallback_(sendInfo);
 		readReply_();
 
-		QUnit.deepEqual(FTPInfo.serverResponses, ["150 Sending data\r\n"], 
-				"unconsumed server responses");
+		QUnit.deepEqual(FTPInfo.serverResponses, ["150 Sending data\r\n"],
+      "unconsumed server responses");
 		readFinalReply_().catch(function (error) {
-			QUnit.equal(error.message, "FTP protocol violation error", 
-				"multiple 1yz replies caught");
+			QUnit.equal(error.message, "FTP protocol violation error",
+        "multiple 1yz replies caught");
 			QUnit.equal(FTPInfo.preliminaryState, false, "reset preliminary state");
 			QUnit.equal(FTPInfo.preliminaryRead, null, "reset preliminary read");
 			QUnit.start();
-		})	
-	});
+		})
+  });
 });
 
 QUnit.test("onReceiveCallback_ preliminary responses error", function () {
 	expect(6);
 	var client = new FTPClient(MockSocket);
-	var FTPInfo = client.test.FTPInfo, 
-		onReceiveCallback_ = client.test.onReceiveCallback_, 
-		readReply_ = client.test.readReply_,
+	var FTPInfo = client.test.FTPInfo,
+    onReceiveCallback_ = client.test.onReceiveCallback_,
+    readReply_ = client.test.readReply_,
 		readFinalReply_ = client.test.readFinalReply_,
 		commandRawData = FTPInfo.commandRawData, encoder = new TextEncoder("utf-8");
 	QUnit.stop();
@@ -562,16 +563,16 @@
 		var sendInfo = {socketId: commandId, data: sendData};
 		
 		onReceiveCallback_(sendInfo);
-		QUnit.deepEqual(FTPInfo.serverResponses, ["220 OK\r\n", "220 OK\r\n"], 
-				"unconsumed server responses");
+		QUnit.deepEqual(FTPInfo.serverResponses, ["220 OK\r\n", "220 OK\r\n"],
+      "unconsumed server responses");
 		readFinalReply_().catch(function (error) {
-			QUnit.equal(error.message, 
-				"Final read requested in non-preliminary state");
+			QUnit.equal(error.message,
+        "Final read requested in non-preliminary state");
 			QUnit.equal(FTPInfo.preliminaryState, false, "reset preliminary state");
 			QUnit.equal(FTPInfo.preliminaryRead, null, "reset preliminary read");
 			QUnit.start();
-		})	
-	});
+		})
+  });
 });
 
 
@@ -582,13 +583,12 @@
 	var FTPInfo = client.test.FTPInfo,
 		sendCommand_ = client.test.sendCommand_;
 	client.connect("cmu.edu", 21).then(function (reply) {
-		QUnit.deepEqual(reply, "220 Service ready for new user\r\n", 
-					"connection successful");
+		QUnit.deepEqual(reply, "220 Service ready for new user\r\n",
+      "connection successful");
 		return sendCommand_("USER anonymous\r\n");
 	}).then(function (reply) {
-		// console.log("INSIDE TEST sendCommand_ reply", reply)
-		QUnit.equal(reply, "331 User name okay, need password\r\n",
-			"got USER reply");
+    QUnit.equal(reply, "331 User name okay, need password\r\n",
+      "got USER reply");
 		return sendCommand_("USER\r\n");
 	}).catch(function (error) {
 		QUnit.equal(error.message, "501 Syntax error in parameters or arguments\r\n",
@@ -610,13 +610,11 @@
 		QUnit.equal(reply, "230 User logged in, proceed\r\n");
 		return sendCommand_("PASV\r\n");
 	}).then(function (reply) {
-		console.log(reply)
-		console.log("pasv command sent, reply ", typeof(reply))
 		var n = reply.search("227 Entering passive mode");
-		var re = /\d{1,3},\d{1,3},\d{1,3},\d{1,3},(\d{1,3}),(\d{1,3})/
-		var hostPort = re.exec(reply);
+		var re = /\d{1,3},\d{1,3},\d{1,3},\d{1,3},(\d{1,3}),(\d{1,3})/;
+    var hostPort = re.exec(reply);
 		QUnit.equal(n, 0, "passive mode reply returned");
-		QUnit.ok(hostPort != null, "Host and port information sent")
+		QUnit.ok(hostPort != null, "Host and port information sent");
 		QUnit.start();
 	}).catch(function (error) {
 		console.log(error);
@@ -636,8 +634,7 @@
 		QUnit.equal(reply, "This should fail");
 		QUnit.start();
 	}).catch(function (error) {
-		console.log(error)
-		QUnit.equal(error.message, "Send error code -2", "send error caught");
+		QUnit.equal(error.message, "Error code -2", "send error caught");
 		QUnit.start();
 	});
 });
@@ -647,21 +644,56 @@
 	var client = new FTPClient(MockSocket);
 	var FTPInfo = client.test.FTPInfo, parsePASVReply_ = client.test.parsePASVReply_;
 	var port = parsePASVReply_("227 Entering passive mode");
-	QUnit.ok(port == null, "Invalid reply");
+	QUnit.ok(port === null, "Invalid reply");
+	
 	port = parsePASVReply_("227 Entering passive mode 13,15");
-	QUnit.ok(port == null, "invalid reply");
+	QUnit.ok(port === null, "invalid reply");
+	
 	port = parsePASVReply_("227 Entering passive mode 0,0,0,0,15,45");
 	QUnit.equal(port, 15<<8 | 45, "valid port number received");
+	
 	port = parsePASVReply_("227 Entering passive mode (0,0,0,0,15,45)");
 	QUnit.equal(port, 15<<8 | 45, "valid port number received");
+	
 	port = parsePASVReply_("227 Entering passive mode (0,0,0,0,15)");
-	QUnit.ok(port == null, "invalid reply");
+	QUnit.ok(port === null, "invalid reply");
+	
 	port = parsePASVReply_("227 Entering passive mode 000,0,0,0,1500,45");
-	QUnit.ok(port == null, "invalid reply");
+	QUnit.ok(port === null, "invalid reply");
+	
 	port = parsePASVReply_("227 Entering passive mode (0,0,0,0,300,45)");
-	QUnit.ok(port == null, "invalid reply");
+	QUnit.ok(port === null, "invalid reply");
+	
 	port = parsePASVReply_("227 Entering passive mode (0,0,0,0,300,450)");
-	QUnit.ok(port == null, "invalid reply");
+	QUnit.ok(port === null, "invalid reply");
+});
+
+QUnit.test("parseESPV_", function () {
+	var client = new FTPClient(MockSocket);
+	var FTPInfo = client.test.FTPInfo, parseEPSVReply_ = client.test.parseEPSVReply_;
+	var port = parseEPSVReply_("227 Entering passive mode");
+	QUnit.ok(port === null, "Invalid reply");
+	
+	port = parseEPSVReply_("227 Entering passive mode (13,15)");
+	QUnit.ok(port === null, "invalid reply");
+	
+	port = parseEPSVReply_("227 Entering passive mode (||1315)");
+	QUnit.ok(port === null, "invalid reply");
+	
+	port = parseEPSVReply_("227 Entering passive mode (|||1315)");
+	QUnit.ok(port === null, "invalid reply");
+	
+	port = parseEPSVReply_("227 Entering passive mode (|||13152|)");
+	QUnit.ok(port == 13152, "invalid reply");
+	
+	port = parseEPSVReply_("227 Entering passive mode |||13215|");
+	QUnit.ok(port === null, "invalid reply");
+	
+	port = parseEPSVReply_("227 Entering passive mode (13152|)");
+	QUnit.ok(port === null, "invalid reply");
+	
+	port = parseEPSVReply_("227 Entering passive mode 13215");
+	QUnit.ok(port === null, "invalid reply");
 });
 
 QUnit.test("dataConnect", function () {
@@ -679,7 +711,7 @@
 	}).catch(function (error) {
 		QUnit.equal(error, "This test should have succeeded");
 		QUnit.start();
-	})
+  });
 });
 
 QUnit.test("login", function () {
@@ -695,3 +727,107 @@
 		QUnit.start();
 	});
 });
+
+QUnit.test("noop", function (pathname) {
+	QUnit.stop();
+	expect(2);
+	var client = new FTPClient(MockSocket), noop_ = client.test.noop_;
+	client.connect("cmu.edu", 21).then(function (reply) {
+		QUnit.equal(reply, "220 Service ready for new user\r\n");
+		return noop_();
+	}).then(function (reply) {
+		QUnit.equal(reply, "220 OK\r\n");
+		QUnit.start();
+	}).catch(function (error) {
+		QUnit.equal(reply, "This test should have suceeded");
+		QUnit.start();
+	});
+});
+
+QUnit.test("parsePWDReply_", function (pathname) {
+	expect(10);
+	var client = new FTPClient(MockSocket),
+    parsePWDReply_ = client.test.parsePWDReply_;
+	var dir = parsePWDReply_("227 \"/home/ftp/\"\r\n");
+	QUnit.ok(dir == "/home/ftp/", "Parsed dir");
+
+	dir = parsePWDReply_("227 \"/home/ftp/\r\n");
+	QUnit.ok(dir === null, "invalid reply");
+	
+	dir = parsePWDReply_("227 \"/home/ftp\"");
+	QUnit.ok(dir == "/home/ftp/", "parsed dir");
+	
+	dir = parsePWDReply_("227 /home/ftp/\r\n");
+	QUnit.ok(dir == "/home/ftp/" , "parsed dir");
+
+	dir = parsePWDReply_("227 /home/ftp/");
+	QUnit.ok(dir === null, "invalid reply");
+
+	dir = parsePWDReply_("227 \"/\"");
+	QUnit.ok(dir == "/", "parsed dir");
+
+	dir = parsePWDReply_("227 /home/ftp\r");
+	QUnit.ok(dir == "/home/ftp/", "parsed dir");
+	
+	dir = parsePWDReply_("227 /home/ftp\n");
+	QUnit.ok(dir == "/home/ftp/", "parsed dir");
+	
+	dir = parsePWDReply_("227\r\n");
+	QUnit.ok(dir === null, "invalid reply");
+	
+	dir = parsePWDReply_("227 \r\n");
+	QUnit.ok(dir === null, "invalid reply");
+});
+
+QUnit.test("getCurrentDir", function (pathname) {
+	QUnit.stop();
+	expect(2);
+	var client = new FTPClient(MockSocket),
+    getCurrentDir = client.getCurrentDir;
+	client.connect("cmu.edu", 21).then(function (reply) {
+    QUnit.equal(reply, "220 Service ready for new user\r\n");
+		return getCurrentDir();
+	}).then(function (reply) {
+		QUnit.equal(reply, "/home/ftp/");
+		QUnit.start();
+	}).catch(function (error) {
+		QUnit.equal(error, "This test should have suceeded");
+		QUnit.start();
+	});
+});
+
+QUnit.test("changeWorkingDir", function (pathname) {
+	QUnit.stop();
+	expect(2);
+	var client = new FTPClient(MockSocket),
+    changeWorkingDir = client.changeWorkingDir;
+	client.connect("cmu.edu", 21).then(function (reply) {
+		QUnit.equal(reply, "220 Service ready for new user\r\n");
+		return changeWorkingDir("/home/ftpclient/");
+	}).then(function (reply) {
+    QUnit.equal(reply, "250 Directory changed\r\n");
+		QUnit.start();
+	}).catch(function (error) {
+		QUnit.equal(reply, "This test should have suceeded");
+		QUnit.start();
+	});
+});
+
+QUnit.test("changeToParentDir", function (pathname) {
+	QUnit.stop();
+	expect(2);
+  var client = new FTPClient(MockSocket),
+    changeToParentDir = client.changeToParentDir;
+	client.connect("cmu.edu", 21).then(function (reply) {
+		QUnit.equal(reply, "220 Service ready for new user\r\n");
+		return changeToParentDir("/home/ftpclient/");
+	}).then(function (reply) {
+		QUnit.equal(reply, "200 Directory changed\r\n");
+		QUnit.start();
+	}).catch(function (error) {
+		QUnit.equal(reply, "This test should have suceeded");
+		QUnit.start();
+	});
+});
+
+
diff --git a/Prototype/twoPane.html b/Prototype/twoPane.html
index 3a89c34..be55481 100644
--- a/Prototype/twoPane.html
+++ b/Prototype/twoPane.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
   <head>
-    <link rel="stylesheet" type="text/css" href="fileSystem.css">
+    <link rel="stylesheet" type="text/css" href="FTPUI.css">
   </head>
   <body>
       <div id ="local-files">
@@ -25,9 +25,12 @@
       <script src="third_party/jquery.js"></script>
       <script src="third_party/TextEncoder/encoding-indexes.js"></script>
       <script src="third_party/TextEncoder/encoding.js"></script>
+      <script src="file_writer.js"></script>
       <script src="FTPLibrary.js"></script>
       <script src="FTPSession.js"></script>
-      <script src="FileSystem.js" type="text/javascript"></script>
+      <script src="FTPUI.js" type="text/javascript"></script>
+      <script src="FileSystemUtils.js" type="text/javascript"></script>
+      <script src="FTPUtils.js" type="text/javascript"></script>
       <script src="index.js" type="text/javascript"></script>
   </body>
 </html>