You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

316 lines
8.9 KiB

module.exports = Writer
var fs = require("graceful-fs")
, inherits = require("inherits")
, rimraf = require("rimraf")
, mkdir = require("mkdirp")
, path = require("path")
, umask = process.platform === "win32" ? 0 : process.umask()
, getType = require("./get-type.js")
, Abstract = require("./abstract.js")
// Must do this *before* loading the child classes
inherits(Writer, Abstract)
Writer.dirmode = 0777 & (~umask)
Writer.filemode = 0666 & (~umask)
var DirWriter = require("./dir-writer.js")
, LinkWriter = require("./link-writer.js")
, FileWriter = require("./file-writer.js")
, ProxyWriter = require("./proxy-writer.js")
// props is the desired state. current is optionally the current stat,
// provided here so that subclasses can avoid statting the target
// more than necessary.
function Writer (props, current) {
var me = this
if (typeof props === "string") {
props = { path: props }
}
if (!props.path) me.error("Must provide a path", null, true)
// polymorphism.
// call fstream.Writer(dir) to get a DirWriter object, etc.
var type = getType(props)
, ClassType = Writer
switch (type) {
case "Directory":
ClassType = DirWriter
break
case "File":
ClassType = FileWriter
break
case "Link":
case "SymbolicLink":
ClassType = LinkWriter
break
case null:
// Don't know yet what type to create, so we wrap in a proxy.
ClassType = ProxyWriter
break
}
if (!(me instanceof ClassType)) return new ClassType(props)
// now get down to business.
Abstract.call(me)
// props is what we want to set.
// set some convenience properties as well.
me.type = props.type
me.props = props
me.depth = props.depth || 0
me.clobber = false === props.clobber ? props.clobber : true
me.parent = props.parent || null
me.root = props.root || (props.parent && props.parent.root) || me
me._path = me.path = path.resolve(props.path)
if (process.platform === "win32") {
me.path = me._path = me.path.replace(/\?/g, "_")
if (me._path.length >= 260) {
me._swallowErrors = true
me._path = "\\\\?\\" + me.path.replace(/\//g, "\\")
}
}
me.basename = path.basename(props.path)
me.dirname = path.dirname(props.path)
me.linkpath = props.linkpath || null
props.parent = props.root = null
// console.error("\n\n\n%s setting size to", props.path, props.size)
me.size = props.size
if (typeof props.mode === "string") {
props.mode = parseInt(props.mode, 8)
}
me.readable = false
me.writable = true
// buffer until ready, or while handling another entry
me._buffer = []
me.ready = false
// start the ball rolling.
// this checks what's there already, and then calls
// me._create() to call the impl-specific creation stuff.
me._stat(current)
}
// Calling this means that it's something we can't create.
// Just assert that it's already there, otherwise raise a warning.
Writer.prototype._create = function () {
var me = this
fs[me.props.follow ? "stat" : "lstat"](me._path, function (er, current) {
if (er) {
return me.warn("Cannot create " + me._path + "\n" +
"Unsupported type: "+me.type, "ENOTSUP")
}
me._finish()
})
}
Writer.prototype._stat = function (current) {
var me = this
, props = me.props
, stat = props.follow ? "stat" : "lstat"
if (current) statCb(null, current)
else fs[stat](me._path, statCb)
function statCb (er, current) {
// if it's not there, great. We'll just create it.
// if it is there, then we'll need to change whatever differs
if (er || !current) {
return create(me)
}
me._old = current
var currentType = getType(current)
// if it's a type change, then we need to clobber or error.
// if it's not a type change, then let the impl take care of it.
if (currentType !== me.type) {
return rimraf(me._path, function (er) {
if (er) return me.error(er)
me._old = null
create(me)
})
}
// otherwise, just handle in the app-specific way
// this creates a fs.WriteStream, or mkdir's, or whatever
create(me)
}
}
function create (me) {
// console.error("W create", me._path, Writer.dirmode)
// XXX Need to clobber non-dirs that are in the way,
// unless { clobber: false } in the props.
mkdir(path.dirname(me._path), Writer.dirmode, function (er) {
// console.error("W created", path.dirname(me._path), er)
if (er) return me.error(er)
me._create()
})
}
Writer.prototype._finish = function () {
var me = this
// console.error(" W Finish", me._path, me.size)
// set up all the things.
// At this point, we're already done writing whatever we've gotta write,
// adding files to the dir, etc.
var todo = 0
var errState = null
var done = false
if (me._old) {
// the times will almost *certainly* have changed.
// adds the utimes syscall, but remove another stat.
me._old.atime = new Date(0)
me._old.mtime = new Date(0)
// console.error(" W Finish Stale Stat", me._path, me.size)
setProps(me._old)
} else {
var stat = me.props.follow ? "stat" : "lstat"
// console.error(" W Finish Stating", me._path, me.size)
fs[stat](me._path, function (er, current) {
// console.error(" W Finish Stated", me._path, me.size, current)
if (er) {
// if we're in the process of writing out a
// directory, it's very possible that the thing we're linking to
// doesn't exist yet (especially if it was intended as a symlink),
// so swallow ENOENT errors here and just soldier on.
if (er.code === "ENOENT" &&
(me.type === "Link" || me.type === "SymbolicLink") &&
process.platform === "win32") {
me.ready = true
me.emit("ready")
me.emit("end")
me.emit("close")
me.end = me._finish = function () {}
return
} else return me.error(er)
}
setProps(me._old = current)
})
}
return
function setProps (current) {
// console.error(" W setprops", me._path)
// mode
var wantMode = me.props.mode
, chmod = me.props.follow || me.type !== "SymbolicLink"
? "chmod" : "lchmod"
if (fs[chmod] && typeof wantMode === "number") {
wantMode = wantMode & 0777
todo ++
// console.error(" W chmod", wantMode.toString(8), me.basename, "\r")
fs[chmod](me._path, wantMode, next(chmod))
}
// uid, gid
// Don't even try it unless root. Too easy to EPERM.
if (process.platform !== "win32" &&
process.getuid && process.getuid() === 0 &&
( typeof me.props.uid === "number" ||
typeof me.props.gid === "number" )) {
var chown = (me.props.follow || me.type !== "SymbolicLink")
? "chown" : "lchown"
if (fs[chown]) {
if (typeof me.props.uid !== "number") me.props.uid = current.uid
if (typeof me.props.gid !== "number") me.props.gid = current.gid
if (me.props.uid !== current.uid || me.props.gid !== current.gid) {
todo ++
// console.error(" W chown", me.props.uid, me.props.gid, me.basename)
fs[chown](me._path, me.props.uid, me.props.gid, next("chown"))
}
}
}
// atime, mtime.
if (fs.utimes && process.platform !== "win32") {
var utimes = (me.props.follow || me.type !== "SymbolicLink")
? "utimes" : "lutimes"
if (utimes === "lutimes" && !fs[utimes]) {
utimes = "utimes"
}
var curA = current.atime
, curM = current.mtime
, meA = me.props.atime
, meM = me.props.mtime
if (meA === undefined) meA = curA
if (meM === undefined) meM = curM
if (!isDate(meA)) meA = new Date(meA)
if (!isDate(meM)) meA = new Date(meM)
if (meA.getTime() !== curA.getTime() ||
meM.getTime() !== curM.getTime()) {
todo ++
// console.error(" W utimes", meA, meM, me.basename)
fs[utimes](me._path, meA, meM, next("utimes"))
}
}
// finally, handle the case if there was nothing to do.
if (todo === 0) {
// console.error(" W nothing to do", me.basename)
next("nothing to do")()
}
}
function next (what) { return function (er) {
// console.error(" W Finish", what, todo)
if (errState) return
if (er) {
er.fstream_finish_call = what
return me.error(errState = er)
}
if (--todo > 0) return
if (done) return
done = true
// all the props have been set, so we're completely done.
me.emit("end")
me.emit("close")
}}
}
Writer.prototype.pipe = function () {
this.error("Can't pipe from writable stream")
}
Writer.prototype.add = function () {
this.error("Cannot add to non-Directory type")
}
Writer.prototype.write = function () {
return true
}
function objectToString (d) {
return Object.prototype.toString.call(d)
}
function isDate(d) {
return typeof d === 'object' && objectToString(d) === '[object Date]';
}