Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stat files before serveIndex, also output date, size, and type #74

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 87 additions & 50 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/

var accepts = require('accepts');
var bytes = require('bytes')
var createError = require('http-errors');
var debug = require('debug')('serve-index');
var escapeHtml = require('escape-html');
Expand Down Expand Up @@ -144,6 +145,13 @@ function serveIndex(root, options) {

if (!stat.isDirectory()) return next();

// content-negotiation
var accept = accepts(req);
var type = accept.type(mediaTypes);

// not acceptable
if (!type) return next(createError(406));

// fetch files
debug('readdir "%s"', path);
fs.readdir(path, function(err, files){
Expand All @@ -154,13 +162,44 @@ function serveIndex(root, options) {
});
files.sort();

// content-negotiation
var accept = accepts(req);
var type = accept.type(mediaTypes);
// add parent directory as first
if (showUp) {
files.unshift('..');
}

// not acceptable
if (!type) return next(createError(406));
serveIndex[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path, view, template, stylesheet);
// stat all files
fstat(path, files, function (err, stats) {
if (err) return next(err);

// combine the stats into the file list
var fileList = files.map(function (file, i) {
return { name: file, stat: stats[i] };
});

// sort file list
fileList.sort(fileSort);

// make similar to W3 FileAPI Object
var directory = {
name: originalDir,
type: 'inode/directory',
size: stat.size,
lastModified: stat.mtime
}

var nodes = fileList.map(function (file) {
var ext = extname(file.name)
var mimetype = mime.lookup(ext)
return {
name: file.name,
type: file.stat.isDirectory() ? 'inode/directory' : mimetype,
size: file.stat.size,
lastModified: file.stat.mtime
}
})

serveIndex[mediaType[type]](req, res, directory, nodes, next, showUp, icons, path, view, template, stylesheet);
});
});
});
};
Expand All @@ -170,46 +209,29 @@ function serveIndex(root, options) {
* Respond with text/html.
*/

serveIndex.html = function _html(req, res, files, next, dir, showUp, icons, path, view, template, stylesheet) {
serveIndex.html = function _html(req, res, dir, files, next, showUp, icons, path, view, template, stylesheet) {
var render = typeof template !== 'function'
? createHtmlRender(template)
: template

if (showUp) {
files.unshift('..');
}

// stat all files
stat(path, files, function (err, stats) {
// read stylesheet
fs.readFile(stylesheet, 'utf8', function (err, style) {
if (err) return next(err);

// combine the stats into the file list
var fileList = files.map(function (file, i) {
return { name: file, stat: stats[i] };
});

// sort file list
fileList.sort(fileSort);
// create locals for rendering
var locals = {
directory: dir.name,
displayIcons: Boolean(icons),
fileList: files,
path: path,
style: style,
viewName: view
};

// read stylesheet
fs.readFile(stylesheet, 'utf8', function (err, style) {
// render html
render(locals, function (err, body) {
if (err) return next(err);

// create locals for rendering
var locals = {
directory: dir,
displayIcons: Boolean(icons),
fileList: fileList,
path: path,
style: style,
viewName: view
};

// render html
render(locals, function (err, body) {
if (err) return next(err);
send(res, 'text/html', body)
});
send(res, 'text/html', body)
});
});
};
Expand All @@ -218,24 +240,39 @@ serveIndex.html = function _html(req, res, files, next, dir, showUp, icons, path
* Respond with application/json.
*/

serveIndex.json = function _json(req, res, files) {
send(res, 'application/json', JSON.stringify(files))
serveIndex.json = function _json(req, res, directory, nodes) {
send(res, 'application/json', JSON.stringify({ directory: directory, nodes: nodes }))
};

/**
* Respond with text/plain.
*/

serveIndex.plain = function _plain(req, res, files) {
send(res, 'text/plain', (files.join('\n') + '\n'))
serveIndex.plain = function _plain(req, res, dir, files) {
var directory = {
name: dir.name.replace(/\/?$/, '/')
};

// include size and date
var nodes = files.map(function (file) {
// human readable
var size = bytes(file.size)

return [
file.lastModified.toISOString(),
size,
file.name + ('inode/directory' === file.type ? '/' : '')
].join('\t')
})
send(res, 'text/plain', (directory.name + '\n' + nodes.join('\n') + '\n'))
};

/**
* Map html `files`, returning an html unordered list.
* @private
*/

function createHtmlFileList(files, dir, useIcons, view) {
function createHtmlFileList(files, dirname, useIcons, view) {
var html = '<ul id="files" class="view-' + escapeHtml(view) + '">'
+ (view === 'details' ? (
'<li class="header">'
Expand All @@ -246,8 +283,8 @@ function createHtmlFileList(files, dir, useIcons, view) {

html += files.map(function (file) {
var classes = [];
var isDir = file.stat && file.stat.isDirectory();
var path = dir.split('/').map(function (c) { return encodeURIComponent(c); });
var isDir = 'inode/directory' === file.type
var path = dirname.split('/').map(function (c) { return encodeURIComponent(c); });

if (useIcons) {
classes.push('icon');
Expand All @@ -269,11 +306,11 @@ function createHtmlFileList(files, dir, useIcons, view) {

path.push(encodeURIComponent(file.name));

var date = file.stat && file.name !== '..'
? file.stat.mtime.toLocaleDateString() + ' ' + file.stat.mtime.toLocaleTimeString()
var date = file.lastModified && file.name !== '..'
? file.lastModified.toLocaleDateString() + ' ' + file.lastModified.toLocaleTimeString()
: '';
var size = file.stat && !isDir
? file.stat.size
? file.size
: '';

return '<li><a href="'
Expand Down Expand Up @@ -421,7 +458,7 @@ function iconStyle(files, useIcons) {
for (i = 0; i < files.length; i++) {
var file = files[i];

var isDir = file.stat && file.stat.isDirectory();
var isDir = 'inode/directory' === file.type
var icon = isDir
? { className: 'icon-directory', fileName: icons.folder }
: iconLookup(file.name);
Expand Down Expand Up @@ -511,7 +548,7 @@ function send (res, type, body) {
* in same order.
*/

function stat(dir, files, cb) {
function fstat(dir, files, cb) {
var batch = new Batch();

batch.concurrency(10);
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"dependencies": {
"accepts": "~1.3.4",
"batch": "0.6.1",
"bytes": "^3.0.0",
"debug": "2.6.9",
"escape-html": "~1.0.3",
"http-errors": "~1.6.2",
Expand Down
20 changes: 11 additions & 9 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,15 +436,17 @@ describe('serveIndex(root)', function () {
it('should provide "fileList" local', function (done) {
var server = createServer(fixtures, {'template': function (locals, callback) {
callback(null, JSON.stringify(locals.fileList.map(function (file) {
file.stat = file.stat instanceof fs.Stats;
file.type = -1 !== file.type.indexOf('/')
file.size = file.size >= 0
file.lastModified = file.lastModified instanceof Date
return file;
})));
}});

request(server)
.get('/users/')
.set('Accept', 'text/html')
.expect('[{"name":"..","stat":true},{"name":"#dir","stat":true},{"name":"index.html","stat":true},{"name":"tobi.txt","stat":true}]')
.expect('[{"name":"..","type":true,"size":true,"lastModified":true},{"name":"#dir","type":true,"size":true,"lastModified":true},{"name":"index.html","type":true,"size":true,"lastModified":true},{"name":"tobi.txt","type":true,"size":true,"lastModified":true}]')
.expect(200, done);
});

Expand Down Expand Up @@ -504,9 +506,9 @@ describe('serveIndex(root)', function () {
it('should get file list', function (done) {
var server = createServer()

serveIndex.html = function (req, res, files) {
serveIndex.html = function (req, res, dir, files) {
var text = files
.filter(function (f) { return /\.txt$/.test(f) })
.filter(function (f) { return /\.txt$/.test(f.name) })
.sort()
res.setHeader('Content-Type', 'text/html')
res.end('<b>' + text.length + ' text files</b>')
Expand All @@ -521,9 +523,9 @@ describe('serveIndex(root)', function () {
it('should get dir name', function (done) {
var server = createServer()

serveIndex.html = function (req, res, files, next, dir) {
serveIndex.html = function (req, res, dir, files, next) {
res.setHeader('Content-Type', 'text/html')
res.end('<b>' + dir + '</b>')
res.end('<b>' + dir.name + '</b>')
}

request(server)
Expand All @@ -535,7 +537,7 @@ describe('serveIndex(root)', function () {
it('should get template path', function (done) {
var server = createServer()

serveIndex.html = function (req, res, files, next, dir, showUp, icons, path, view, template) {
serveIndex.html = function (req, res, dir, files, next, showUp, icons, path, view, template) {
res.setHeader('Content-Type', 'text/html')
res.end(String(fs.existsSync(template)))
}
Expand All @@ -549,7 +551,7 @@ describe('serveIndex(root)', function () {
it('should get template with tokens', function (done) {
var server = createServer()

serveIndex.html = function (req, res, files, next, dir, showUp, icons, path, view, template) {
serveIndex.html = function (req, res, dir, files, next, showUp, icons, path, view, template) {
res.setHeader('Content-Type', 'text/html')
res.end(fs.readFileSync(template, 'utf8'))
}
Expand All @@ -567,7 +569,7 @@ describe('serveIndex(root)', function () {
it('should get stylesheet path', function (done) {
var server = createServer()

serveIndex.html = function (req, res, files, next, dir, showUp, icons, path, view, template, stylesheet) {
serveIndex.html = function (req, res, dir, files, next, showUp, icons, path, view, template, stylesheet) {
res.setHeader('Content-Type', 'text/html')
res.end(String(fs.existsSync(stylesheet)))
}
Expand Down