| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- 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
- me.filter = typeof props.filter === "function" ? props.filter: null
- // 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"
- , who = me._proxy || me
- if (current) statCb(null, current)
- else fs[stat](me._path, statCb)
- function statCb (er, current) {
- if (me.filter && !me.filter.call(who, who, current)) {
- me._aborted = true
- me.emit("end")
- me.emit("close")
- return
- }
- // 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, made) {
- // console.error("W created", path.dirname(me._path), er)
- if (er) return me.error(er)
- // later on, we have to set the mode and owner for these
- me._madeDir = made
- return me._create()
- })
- }
- function endChmod (me, want, current, path, cb) {
- var wantMode = want.mode
- , chmod = want.follow || me.type !== "SymbolicLink"
- ? "chmod" : "lchmod"
- if (!fs[chmod]) return cb()
- if (typeof wantMode !== "number") return cb()
- var curMode = current.mode & 0777
- wantMode = wantMode & 0777
- if (wantMode === curMode) return cb()
- fs[chmod](path, wantMode, cb)
- }
- function endChown (me, want, current, path, cb) {
- // Don't even try it unless root. Too easy to EPERM.
- if (process.platform === "win32") return cb()
- if (!process.getuid || !process.getuid() === 0) return cb()
- if (typeof want.uid !== "number" &&
- typeof want.gid !== "number" ) return cb()
- if (current.uid === want.uid &&
- current.gid === want.gid) return cb()
- var chown = (me.props.follow || me.type !== "SymbolicLink")
- ? "chown" : "lchown"
- if (!fs[chown]) return cb()
- if (typeof want.uid !== "number") want.uid = current.uid
- if (typeof want.gid !== "number") want.gid = current.gid
- fs[chown](path, want.uid, want.gid, cb)
- }
- function endUtimes (me, want, current, path, cb) {
- if (!fs.utimes || process.platform === "win32") return cb()
- var utimes = (want.follow || me.type !== "SymbolicLink")
- ? "utimes" : "lutimes"
- if (utimes === "lutimes" && !fs[utimes]) {
- utimes = "utimes"
- }
- if (!fs[utimes]) return cb()
- var curA = current.atime
- , curM = current.mtime
- , meA = want.atime
- , meM = want.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()) return cb()
- fs[utimes](path, meA, meM, cb)
- }
- // XXX This function is beastly. Break it up!
- 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) {
- todo += 3
- endChmod(me, me.props, current, me._path, next("chmod"))
- endChown(me, me.props, current, me._path, next("chown"))
- endUtimes(me, me.props, current, me._path, next("utimes"))
- }
- 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
- // we may still need to set the mode/etc. on some parent dirs
- // that were created previously. delay end/close until then.
- if (!me._madeDir) return end()
- else endMadeDir(me, me._path, end)
- function end (er) {
- if (er) {
- er.fstream_finish_call = "setupMadeDir"
- return me.error(er)
- }
- // all the props have been set, so we're completely done.
- me.emit("end")
- me.emit("close")
- }
- }
- }
- }
- function endMadeDir (me, p, cb) {
- var made = me._madeDir
- // everything *between* made and path.dirname(me._path)
- // needs to be set up. Note that this may just be one dir.
- var d = path.dirname(p)
- endMadeDir_(me, d, function (er) {
- if (er) return cb(er)
- if (d === made) {
- return cb()
- }
- endMadeDir(me, d, cb)
- })
- }
- function endMadeDir_ (me, p, cb) {
- var dirProps = {}
- Object.keys(me.props).forEach(function (k) {
- dirProps[k] = me.props[k]
- // only make non-readable dirs if explicitly requested.
- if (k === "mode" && me.type !== "Directory") {
- dirProps[k] = dirProps[k] | 0111
- }
- })
- var todo = 3
- , errState = null
- fs.stat(p, function (er, current) {
- if (er) return cb(errState = er)
- endChmod(me, dirProps, current, p, next)
- endChown(me, dirProps, current, p, next)
- endUtimes(me, dirProps, current, p, next)
- })
- function next (er) {
- if (errState) return
- if (er) return cb(errState = er)
- if (-- todo === 0) return cb()
- }
- }
- 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]';
- }
|