module.exports = Writer var fs = require('graceful-fs') var inherits = require('inherits') var rimraf = require('rimraf') var mkdir = require('mkdirp') var path = require('path') var umask = process.platform === 'win32' ? 0 : process.umask() var getType = require('./get-type.js') var Abstract = require('./abstract.js') // Must do this *before* loading the child classes inherits(Writer, Abstract) Writer.dirmode = parseInt('0777', 8) & (~umask) Writer.filemode = parseInt('0666', 8) & (~umask) var DirWriter = require('./dir-writer.js') var LinkWriter = require('./link-writer.js') var FileWriter = require('./file-writer.js') var 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 self = this if (typeof props === 'string') { props = { path: props } } // polymorphism. // call fstream.Writer(dir) to get a DirWriter object, etc. var type = getType(props) var ClassType = Writer switch (type) { case 'Directory': ClassType = DirWriter break case 'File': ClassType = FileWriter break case 'Link': case 'SymbolicLink': ClassType = LinkWriter break case null: default: // Don't know yet what type to create, so we wrap in a proxy. ClassType = ProxyWriter break } if (!(self instanceof ClassType)) return new ClassType(props) // now get down to business. Abstract.call(self) if (!props.path) self.error('Must provide a path', null, true) // props is what we want to set. // set some convenience properties as well. self.type = props.type self.props = props self.depth = props.depth || 0 self.clobber = props.clobber === false ? props.clobber : true self.parent = props.parent || null self.root = props.root || (props.parent && props.parent.root) || self self._path = self.path = path.resolve(props.path) if (process.platform === 'win32') { self.path = self._path = self.path.replace(/\?/g, '_') if (self._path.length >= 260) { self._swallowErrors = true self._path = '\\\\?\\' + self.path.replace(/\//g, '\\') } } self.basename = path.basename(props.path) self.dirname = path.dirname(props.path) self.linkpath = props.linkpath || null props.parent = props.root = null // console.error("\n\n\n%s setting size to", props.path, props.size) self.size = props.size if (typeof props.mode === 'string') { props.mode = parseInt(props.mode, 8) } self.readable = false self.writable = true // buffer until ready, or while handling another entry self._buffer = [] self.ready = false self.filter = typeof props.filter === 'function' ? props.filter : null // start the ball rolling. // this checks what's there already, and then calls // self._create() to call the impl-specific creation stuff. self._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 self = this fs[self.props.follow ? 'stat' : 'lstat'](self._path, function (er) { if (er) { return self.warn('Cannot create ' + self._path + '\n' + 'Unsupported type: ' + self.type, 'ENOTSUP') } self._finish() }) } Writer.prototype._stat = function (current) { var self = this var props = self.props var stat = props.follow ? 'stat' : 'lstat' var who = self._proxy || self if (current) statCb(null, current) else fs[stat](self._path, statCb) function statCb (er, current) { if (self.filter && !self.filter.call(who, who, current)) { self._aborted = true self.emit('end') self.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(self) } self._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 !== self.type || self.type === 'File' && current.nlink > 1) { return rimraf(self._path, function (er) { if (er) return self.error(er) self._old = null create(self) }) } // otherwise, just handle in the app-specific way // this creates a fs.WriteStream, or mkdir's, or whatever create(self) } } function create (self) { // console.error("W create", self._path, Writer.dirmode) // XXX Need to clobber non-dirs that are in the way, // unless { clobber: false } in the props. mkdir(path.dirname(self._path), Writer.dirmode, function (er, made) { // console.error("W created", path.dirname(self._path), er) if (er) return self.error(er) // later on, we have to set the mode and owner for these self._madeDir = made return self._create() }) } function endChmod (self, want, current, path, cb) { var wantMode = want.mode var chmod = want.follow || self.type !== 'SymbolicLink' ? 'chmod' : 'lchmod' if (!fs[chmod]) return cb() if (typeof wantMode !== 'number') return cb() var curMode = current.mode & parseInt('0777', 8) wantMode = wantMode & parseInt('0777', 8) if (wantMode === curMode) return cb() fs[chmod](path, wantMode, cb) } function endChown (self, 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 = (self.props.follow || self.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 (self, want, current, path, cb) { if (!fs.utimes || process.platform === 'win32') return cb() var utimes = (want.follow || self.type !== 'SymbolicLink') ? 'utimes' : 'lutimes' if (utimes === 'lutimes' && !fs[utimes]) { utimes = 'utimes' } if (!fs[utimes]) return cb() var curA = current.atime var curM = current.mtime var meA = want.atime var 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 self = this if (self._finishing) return self._finishing = true // console.error(" W Finish", self._path, self.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 (self._old) { // the times will almost *certainly* have changed. // adds the utimes syscall, but remove another stat. self._old.atime = new Date(0) self._old.mtime = new Date(0) // console.error(" W Finish Stale Stat", self._path, self.size) setProps(self._old) } else { var stat = self.props.follow ? 'stat' : 'lstat' // console.error(" W Finish Stating", self._path, self.size) fs[stat](self._path, function (er, current) { // console.error(" W Finish Stated", self._path, self.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' && (self.type === 'Link' || self.type === 'SymbolicLink') && process.platform === 'win32') { self.ready = true self.emit('ready') self.emit('end') self.emit('close') self.end = self._finish = function () {} return } else return self.error(er) } setProps(self._old = current) }) } return function setProps (current) { todo += 3 endChmod(self, self.props, current, self._path, next('chmod')) endChown(self, self.props, current, self._path, next('chown')) endUtimes(self, self.props, current, self._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 self.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 (!self._madeDir) return end() else endMadeDir(self, self._path, end) function end (er) { if (er) { er.fstream_finish_call = 'setupMadeDir' return self.error(er) } // all the props have been set, so we're completely done. self.emit('end') self.emit('close') } } } } function endMadeDir (self, p, cb) { var made = self._madeDir // everything *between* made and path.dirname(self._path) // needs to be set up. Note that this may just be one dir. var d = path.dirname(p) endMadeDir_(self, d, function (er) { if (er) return cb(er) if (d === made) { return cb() } endMadeDir(self, d, cb) }) } function endMadeDir_ (self, p, cb) { var dirProps = {} Object.keys(self.props).forEach(function (k) { dirProps[k] = self.props[k] // only make non-readable dirs if explicitly requested. if (k === 'mode' && self.type !== 'Directory') { dirProps[k] = dirProps[k] | parseInt('0111', 8) } }) var todo = 3 var errState = null fs.stat(p, function (er, current) { if (er) return cb(errState = er) endChmod(self, dirProps, current, p, next) endChown(self, dirProps, current, p, next) endUtimes(self, 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("Can't 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]' }