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.
		
		
		
		
		
			
		
			
				
					
					
						
							390 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
	
	
							390 lines
						
					
					
						
							11 KiB
						
					
					
				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]' | 
						|
}
 | 
						|
 |