369 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			369 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
"use strict";
 | 
						|
 | 
						|
var fs = require("fs").promises;
 | 
						|
var path = require("path");
 | 
						|
 | 
						|
var forEachAsync = require("foreachasync").forEachAsync;
 | 
						|
var walk = require("walk");
 | 
						|
var escapeRegExp = require("escape-string-regexp");
 | 
						|
var safeResolve = require("../utils").safeResolve;
 | 
						|
var sha1sum = function (str) {
 | 
						|
  return require("secret-utils").hashsum("sha1", str);
 | 
						|
};
 | 
						|
var 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);
 | 
						|
  var trueRoot = path.resolve(prefix, sub);
 | 
						|
  var 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.readFile(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 fs.mkdir(pathname, { recursive: true }).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 fs.mkdir(pathname, { recursive: true }).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.writeFile(file.realPath, file.contents, "utf8");
 | 
						|
          })
 | 
						|
          .catch(function (/*exists*/) {
 | 
						|
            if (file.delete || !file.contents) {
 | 
						|
              return fs.unlink(file.realPath);
 | 
						|
            }
 | 
						|
 | 
						|
            if (false === options.replace || false === options.overwrite) {
 | 
						|
              throw new Error("EEXIST: the file already exists");
 | 
						|
            }
 | 
						|
 | 
						|
            return fs.writeFile(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;
 |