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();
- };
- >
+ 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");
+ };
+ >
+ });
+
+ var downloadCompleted = new Promise(function (resolve, reject) {
+ var downloadOutcome = function (result) {
+ if (result instanceof Error) {
+ reject(result);
+ }
+ else {
+ resolve(result);
+ }
+ };
+ >
+ });
+
var (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 = "";
};
>
});
// register the callback function for received data
var (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 (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 (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 (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 (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 (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. (event) {
+ resolve(fileEntry);
+ };
+ fileWriter.>
+ }, 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.>
MockSocket.onReceive.callbacks = [];
MockSocket.>
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.>
+ //this.>
+ 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. {
+ self.restoreWriter(writer);
+ callback();
+ };
+ writer.>
+ }).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>