desirae.js/lib/node-adapters/fsapi.js

374 lines
9.2 KiB
JavaScript

'use strict';
var PromiseA = require('bluebird').Promise
, fs = PromiseA.promisifyAll(require('fs'))
, forEachAsync = require('foreachasync').forEachAsync
, path = require('path')
, walk = require('walk')
, escapeRegExp = require('escape-string-regexp')
, safeResolve = require('../utils').safeResolve
, sha1sum = function (str) { return require('secret-utils').hashsum('sha1', str); }
, mkdirp = PromiseA.promisify(require('mkdirp'))
, fsExtra = PromiseA.promisifyAll(require('fs.extra'))
//, tmpdir = require('os').tmpdir()
;
function strip(prefix, pathname) {
return pathname.substr(prefix.length + 1);
}
function walkDir(parent, sub, opts) {
opts = opts || {};
if (false !== opts.sha1sum) {
opts.sha1sum = true;
}
var prefix = path.resolve(parent)
, trueRoot = path.resolve(prefix, sub)
, files = []
;
function filter(name) {
if (!name) {
return false;
}
if (!opts.dotfiles && ('.' === name[0])) {
return false;
}
if (opts.extensions && opts.extensions.length) {
if (!opts.extensions.some(function (ext) {
return new RegExp('\\.' + escapeRegExp(ext) + '$').test(name);
})) {
return false;
}
}
return true;
}
return new PromiseA(function (resolve) {
var walker = walk.walk(trueRoot)
;
walker.on('nodeError', function (filepath, stat, next) {
//stats.forEach(function (stat) {
if (!filter(stat.name)) {
return;
}
stat.error.path = path.join(strip(prefix, filepath), stat.name);
files.push({
name: stat.name
, relativePath: strip(prefix, filepath)
, path: path.join(strip(prefix, filepath), stat.name)
, type: undefined
, error: stat.error
});
//});
next();
});
walker.on('files', function (root, stats, next) {
var dirname = strip(prefix, root)
;
function eachFile(stat) {
var file
;
if (!filter(stat.name)) {
return PromiseA.resolve();
}
file = {
name: stat.name
, relativePath: dirname
, path: path.join(dirname, stat.name)
, createdDate: (stat.birthtime||stat.ctime).toISOString()
, lastModifiedDate: stat.mtime.toISOString()
, size: stat.size
, type: undefined // TODO include mimetype
};
files.push(file);
if (!(opts.sha1sum || opts.content)) {
return PromiseA.resolve();
}
// TODO stream sha1 (for assets)
return fs.readFileAsync(path.join(root, stat.name), null).then(function (buffer) {
var contents = buffer.toString('utf8')
;
file.sha1 = sha1sum(contents);
file.type = undefined;
if (opts.contents) {
file.contents = contents;
}
});
}
if (!opts.contents) {
stats.forEach(eachFile);
next();
} else {
forEachAsync(stats, eachFile).then(function () {
next();
});
}
});
walker.on('end', function () {
resolve(files);
});
});
}
function walkDirs(parent, subs, opts) {
opts = opts || {};
var collections = {}
;
return forEachAsync(subs, function (sub) {
return walkDir(parent, sub, opts).then(function (results) {
collections[sub] = results;
});
}).then(function () {
return collections;
});
}
function getfs(blogdir, filepaths) {
var files = []
;
return forEachAsync(filepaths, function (filepath) {
var pathname = safeResolve(blogdir, filepath)
;
return fs.lstatAsync(pathname).then(function (stat) {
return fs.readFileAsync(pathname, null).then(function (buffer) {
files.push({
name: path.basename(pathname)
, relativePath: path.dirname(filepath)
, path: filepath
, createdDate: (stat.birthtime||stat.ctime).toISOString()
, lastModifiedDate: stat.mtime.toISOString()
, contents: buffer.toString('utf8')
, size: buffer.length
, sha1: sha1sum(buffer)
, type: undefined
});
});
}).catch(function (e) {
files.push({ path: filepath, error: e.message });
});
}).then(function () {
return files;
});
}
function makeAllDirs(dirpaths) {
var errors = []
;
return forEachAsync(dirpaths, function (pathname) {
return mkdirp(pathname).catch(function (e) {
// TODO exclude attempting to write files to this dir?
errors.push({
type: 'directory'
, directory: pathname
, message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
});
});
}).then(function () {
return errors;
});
}
function copyfs(blogdir, files) {
// TODO switch format to { source: ..., dest: ..., opts: ... } ?
var results = { errors: [] }
, dirpaths = {}
, sources = Object.keys(files)
;
return forEachAsync(sources, function (source) {
/*
var nsource = safeResolve(blogdir, source)
;
*/
var dest = safeResolve(blogdir, files[source])
, pathname = path.dirname(dest)
//, filename = path.basename(dest)
;
dirpaths[pathname] = true;
return PromiseA.resolve();
}).then(function () {
// TODO is it better to do this lazy-like or as a batch?
// I figure as batch when there may be hundreds of files,
// likely within 2 or 3 directories
return makeAllDirs(Object.keys(dirpaths)).then(function (errors) {
errors.forEach(function (e) {
results.errors.push(e);
});
});
}).then(function () {
// TODO allow delete?
return forEachAsync(sources, function (source) {
return fsExtra.copyAsync(
safeResolve(blogdir, source)
, safeResolve(blogdir, files[source])
, { replace: true }
).catch(function (e) {
results.errors.push({
type: 'file'
, source: source
, destination: files[source]
, message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
});
});
});
}).catch(function (e) {
results.error = {
message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
};
}).then(function () {
return results;
});
}
function putfs(blogdir, files, options) {
options = options || {};
var putfsResults = { errors: [] }
, dirpaths = {}
;
return forEachAsync(files, function (file) {
var filepath = safeResolve(blogdir, file.path || path.join(file.relativePath, file.name))
, pathname = path.dirname(filepath)
, filename = file.name || path.basename(filepath)
;
file.realPath = filepath;
file.name = filename;
dirpaths[pathname] = true;
return PromiseA.resolve();
}).then(function () {
// TODO is it better to do this lazy-like or as a batch?
// I figure as batch when there may be hundreds of files,
// likely within 2 or 3 directories
return forEachAsync(Object.keys(dirpaths), function (pathname) {
return mkdirp(pathname).catch(function (e) {
// TODO exclude attempting to write files to this dir?
putfsResults.errors.push({
type: 'directory'
, directory: pathname
, message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
});
});
});
}).then(function () {
// TODO sort deletes last
return forEachAsync(files, function (file) {
// TODO use lastModifiedDate as per client request?
// TODO compare sha1 sums for integrity
// NOTE existsAsync is backwards
return fs.existsAsync(file.realPath).then(function () {
return fs.writeFileAsync(file.realPath, file.contents, 'utf8');
}).catch(function (/*exists*/) {
if (file.delete || !file.contents) {
return fs.unlinkAsync(file.realPath);
}
if (false === options.replace || false === options.overwrite) {
throw new Error('EEXIST: the file already exists');
}
return fs.writeFileAsync(file.realPath, file.contents, 'utf8');
}).catch(function (e) {
putfsResults.errors.push({
type: 'file'
, file: file.realPath
, delete: !file.contents
, path: file.path
, relativePath: file.relativePath
, name: file.name
, message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
});
});
});
}).catch(function (e) {
putfsResults.error = {
message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
};
}).then(function () {
return putfsResults;
});
}
/*
walkDirs('blog', ['posts'], { contents: false }).then(function (stats) {
console.log(JSON.stringify(stats, null, ' '));
});
*/
module.exports.walk = { walkDirs: walkDirs, walkDir: walkDir };
module.exports.copyfs = copyfs;
module.exports.getfs = getfs;
module.exports.putfs = putfs;
module.exports.walkDir = walkDir;
module.exports.walkDirs = walkDirs;
module.exports.fsapi = module.exports;