I'm a fairly experienced programmer and I've just recently discovered node.js. I love JavaScript because that's where I started (Web Development) so being able to write server-side code with its is amazing.
Currently, I'm working on a simple exercise, a WebSocket/HTTP server, and I began to add a directory list function when I ran into a slight annoyance: when I list directories and files in a certain directory they're not in any order. I would like them to be listed with directories first, files second than alphabetically (like the 'ls' command). I have a feeling its because its asynchronus but I'm not totally positive. Any help would be appreciated.
BTW, here's my code:
var sys = require("sys");
var ws = require('websocket-server');
var fs = require("fs");
var path = require("path");
var url = require("url");
function log(data){
sys.log("\033[0;32m"+data+"\033[0m");
}
var server = ws.createServer();
server.listen(3400);
log("Listening on 3400 for HTTP and WS");
server.addListener("request", function(request, response){
log("HTTP: Connected: " + request.connection.remoteAddress);
var uri = url.parse(request.url).pathname;
var filename = path.join("/home/brandon", uri);
log("HTTP: " + request.connection.remoteAddress + " Requested: " + filename);
path.exists(filename, function(exists) {
if(!exists) {
response.writeHead(404, {"Content-Type": "text/plain"});
response.write("404 Not Found\n");
log("HTTP: " + filename + " Does Not Exist. 404 returned to " + request.connection.remoteAddress);
response.end();
log("HTTP: Disconnected: " + request.connection.remoteAddress);
return;
}
fs.readFile(filename, "binary", function(err, file) {
if(err) {
if(err.errno === 21){
fs.readdir(filename, function(err1, files){
if(err1){
response.writeHead(500, {"Content-Type": "text/plain"});
response.write("Error when reading directory: " + err1 + "\n");
log("HTTP: " + filename + " Could Not Be Read. 500 returned to " + request.connection.remoteAddress);
response.end();
log("HTTP: Disconnected: " + request.connection.remoteAddress);
return;
} else {
response.writeHead(200);
response.write("<HTML><HEAD><title>Directory Listing for " + uri + "</title></HEAD><BODY><h1>Directory Listing for " + uri + "</h1>");
response.write("<ul>");
function printBr(element, index, array) {
response.write("<li>" + element + "</li>");
}
/*for( i in files ){
response.write("<li>" + files[i] + "</li>");
}*/
files.forEach(printBr);
response.write("</ul>");
response.write("</BODY></HTML>");
log("HTTP: Directory listing for " + filename + " sent to " + request.connection.remoteAddress);
response.end();
log("HTTP: Disconnected: " + request.connection.remoteAddress);
return;
}
});
return;
}
response.writeHead(500, {"Content-Type": "text/plain"});
response.write("Error when reading file: " + err + "\n");
log("HTTP: " + filename + " Could Not Be Read. 500 returned to " + request.connection.remoteAddress);
response.end();
log("HTTP: Disconnected: " + request.connection.remoteAddress);
return;
}
response.writeHead(200);
response.write(file, "binary");
log("HTTP: " + filename + " Read and Sent to " + request.connection.remoteAddress);
response.end();
log("HTTP: Disconnected: " + request.connection.remoteAddress);
});
});
});
server.addListener("connection", function(conn){
log(conn.id + ": new connection");
server.broadcast("New Connection: "+conn.id);
conn.addListener("readyStateChange", function(readyState){
log("stateChanged: "+readyState);
});
conn.addListener("close", function(){
var c = this;
log(c.id + ": Connection Closed");
server.broadcast("Connection Closed: "+c.id);
});
conn.addListener("message", function(message){
log(conn.id + ": "+JSON.stringify(message));
server.broadcast(conn.id + ": "+message);
});
});
And here's the output in the browser:
Thanks to @Samir, I found out how to do exactly what I wanted to do. I iterated through the array of the directory contents, checked if an item was a directory or file, separated them into two arrays ('dirs_in' for dirs and 'files_in' for files), sorted the two arrays alphabetically, and finally wrote them out.
Code (Lines 42-70):
response.writeHead(200);
response.write("<HTML><HEAD><title>Directory Listing for " + uri + "</title></HEAD><BODY><h1>Directory Listing for " + uri + "</h1>");
response.write("<ul>");
function printBr(element, index, array) {
response.write("<li>" + element);
if( fs.statSync( path.join(filename + element) ).isDirectory() ){
response.write(" is a <b>dir</b>");
} else {
response.write(" is a <b>file</b>");
}
response.write("</li>");
}
var dirs_in = [];
var files_in = [];
function sep(element, index, array) {
if( fs.statSync( path.join(filename + element) ).isDirectory() ){
dirs_in.push(element);
} else {
files_in.push(element);
}
}
files.forEach(sep);
dirs_in.sort().forEach(printBr);
files_in.sort().forEach(printBr);
response.write("</ul>");
response.write("</BODY></HTML>");
log("HTTP: Directory listing for " + filename + " sent to " + request.connection.remoteAddress);
response.end();
log("HTTP: Disconnected: " + request.connection.remoteAddress);
Browser Output: P.S. I'll remove the 'is a dir' and 'is a file'. They were just for testing.
If you want them ordered by name, you can just call sort()
on the array first.
files.sort().forEach(printBr);
If, for example, you'd like to sort directories first, then you need to get more information. A naive implementation would be to query the stats of each file in the sort comparison function:
files.sort(function(a, b) {
var aIsDir = fs.statSync(dir + "/" + a).isDirectory(),
bIsDir = fs.statSync(dir + "/" + b).isDirectory();
if (aIsDir && !bIsDir) {
return -1;
}
if (!aIsDir && bIsDir) {
return 1;
}
return a.localeCompare(b);
}).forEach(printBr);
The localeCompare
method is what the sort
method uses by default as the comparison function, so we delegate to that if they're both on "equal" terms. You can expand upon this as necessary. I'd also recommend you store the result of isDirectory
in a map or something at the very least. In addition, statSync
is good for demonstration purposes, but not in production code. Use stat
instead. This does lead to slightly more complex code, but the benefits of asynchronous behaviour are worth it.