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.
		
		
		
		
		
			
		
			
				
					256 lines
				
				6.9 KiB
			
		
		
			
		
	
	
					256 lines
				
				6.9 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								module.exports = Reader
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var fs = require('graceful-fs')
							 | 
						||
| 
								 | 
							
								var Stream = require('stream').Stream
							 | 
						||
| 
								 | 
							
								var inherits = require('inherits')
							 | 
						||
| 
								 | 
							
								var path = require('path')
							 | 
						||
| 
								 | 
							
								var getType = require('./get-type.js')
							 | 
						||
| 
								 | 
							
								var hardLinks = Reader.hardLinks = {}
							 | 
						||
| 
								 | 
							
								var Abstract = require('./abstract.js')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Must do this *before* loading the child classes
							 | 
						||
| 
								 | 
							
								inherits(Reader, Abstract)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var LinkReader = require('./link-reader.js')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function Reader (props, currentStat) {
							 | 
						||
| 
								 | 
							
								  var self = this
							 | 
						||
| 
								 | 
							
								  if (!(self instanceof Reader)) return new Reader(props, currentStat)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (typeof props === 'string') {
							 | 
						||
| 
								 | 
							
								    props = { path: props }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // polymorphism.
							 | 
						||
| 
								 | 
							
								  // call fstream.Reader(dir) to get a DirReader object, etc.
							 | 
						||
| 
								 | 
							
								  // Note that, unlike in the Writer case, ProxyReader is going
							 | 
						||
| 
								 | 
							
								  // to be the *normal* state of affairs, since we rarely know
							 | 
						||
| 
								 | 
							
								  // the type of a file prior to reading it.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var type
							 | 
						||
| 
								 | 
							
								  var ClassType
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (props.type && typeof props.type === 'function') {
							 | 
						||
| 
								 | 
							
								    type = props.type
							 | 
						||
| 
								 | 
							
								    ClassType = type
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    type = getType(props)
							 | 
						||
| 
								 | 
							
								    ClassType = Reader
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (currentStat && !type) {
							 | 
						||
| 
								 | 
							
								    type = getType(currentStat)
							 | 
						||
| 
								 | 
							
								    props[type] = true
							 | 
						||
| 
								 | 
							
								    props.type = type
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  switch (type) {
							 | 
						||
| 
								 | 
							
								    case 'Directory':
							 | 
						||
| 
								 | 
							
								      ClassType = require('./dir-reader.js')
							 | 
						||
| 
								 | 
							
								      break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    case 'Link':
							 | 
						||
| 
								 | 
							
								    // XXX hard links are just files.
							 | 
						||
| 
								 | 
							
								    // However, it would be good to keep track of files' dev+inode
							 | 
						||
| 
								 | 
							
								    // and nlink values, and create a HardLinkReader that emits
							 | 
						||
| 
								 | 
							
								    // a linkpath value of the original copy, so that the tar
							 | 
						||
| 
								 | 
							
								    // writer can preserve them.
							 | 
						||
| 
								 | 
							
								    // ClassType = HardLinkReader
							 | 
						||
| 
								 | 
							
								    // break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    case 'File':
							 | 
						||
| 
								 | 
							
								      ClassType = require('./file-reader.js')
							 | 
						||
| 
								 | 
							
								      break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    case 'SymbolicLink':
							 | 
						||
| 
								 | 
							
								      ClassType = LinkReader
							 | 
						||
| 
								 | 
							
								      break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    case 'Socket':
							 | 
						||
| 
								 | 
							
								      ClassType = require('./socket-reader.js')
							 | 
						||
| 
								 | 
							
								      break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    case null:
							 | 
						||
| 
								 | 
							
								      ClassType = require('./proxy-reader.js')
							 | 
						||
| 
								 | 
							
								      break
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (!(self instanceof ClassType)) {
							 | 
						||
| 
								 | 
							
								    return new ClassType(props)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  Abstract.call(self)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (!props.path) {
							 | 
						||
| 
								 | 
							
								    self.error('Must provide a path', null, true)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  self.readable = true
							 | 
						||
| 
								 | 
							
								  self.writable = false
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  self.type = type
							 | 
						||
| 
								 | 
							
								  self.props = props
							 | 
						||
| 
								 | 
							
								  self.depth = props.depth = props.depth || 0
							 | 
						||
| 
								 | 
							
								  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) {
							 | 
						||
| 
								 | 
							
								      // how DOES one create files on the moon?
							 | 
						||
| 
								 | 
							
								      // if the path has spaces in it, then UNC will fail.
							 | 
						||
| 
								 | 
							
								      self._swallowErrors = true
							 | 
						||
| 
								 | 
							
								      // if (self._path.indexOf(" ") === -1) {
							 | 
						||
| 
								 | 
							
								      self._path = '\\\\?\\' + self.path.replace(/\//g, '\\')
							 | 
						||
| 
								 | 
							
								    // }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  self.basename = props.basename = path.basename(self.path)
							 | 
						||
| 
								 | 
							
								  self.dirname = props.dirname = path.dirname(self.path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // these have served their purpose, and are now just noisy clutter
							 | 
						||
| 
								 | 
							
								  props.parent = props.root = null
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // console.error("\n\n\n%s setting size to", props.path, props.size)
							 | 
						||
| 
								 | 
							
								  self.size = props.size
							 | 
						||
| 
								 | 
							
								  self.filter = typeof props.filter === 'function' ? props.filter : null
							 | 
						||
| 
								 | 
							
								  if (props.sort === 'alpha') props.sort = alphasort
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // start the ball rolling.
							 | 
						||
| 
								 | 
							
								  // this will stat the thing, and then call self._read()
							 | 
						||
| 
								 | 
							
								  // to start reading whatever it is.
							 | 
						||
| 
								 | 
							
								  // console.error("calling stat", props.path, currentStat)
							 | 
						||
| 
								 | 
							
								  self._stat(currentStat)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function alphasort (a, b) {
							 | 
						||
| 
								 | 
							
								  return a === b ? 0
							 | 
						||
| 
								 | 
							
								    : a.toLowerCase() > b.toLowerCase() ? 1
							 | 
						||
| 
								 | 
							
								      : a.toLowerCase() < b.toLowerCase() ? -1
							 | 
						||
| 
								 | 
							
								        : a > b ? 1
							 | 
						||
| 
								 | 
							
								          : -1
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Reader.prototype._stat = function (currentStat) {
							 | 
						||
| 
								 | 
							
								  var self = this
							 | 
						||
| 
								 | 
							
								  var props = self.props
							 | 
						||
| 
								 | 
							
								  var stat = props.follow ? 'stat' : 'lstat'
							 | 
						||
| 
								 | 
							
								  // console.error("Reader._stat", self._path, currentStat)
							 | 
						||
| 
								 | 
							
								  if (currentStat) process.nextTick(statCb.bind(null, null, currentStat))
							 | 
						||
| 
								 | 
							
								  else fs[stat](self._path, statCb)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function statCb (er, props_) {
							 | 
						||
| 
								 | 
							
								    // console.error("Reader._stat, statCb", self._path, props_, props_.nlink)
							 | 
						||
| 
								 | 
							
								    if (er) return self.error(er)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Object.keys(props_).forEach(function (k) {
							 | 
						||
| 
								 | 
							
								      props[k] = props_[k]
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // if it's not the expected size, then abort here.
							 | 
						||
| 
								 | 
							
								    if (undefined !== self.size && props.size !== self.size) {
							 | 
						||
| 
								 | 
							
								      return self.error('incorrect size')
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    self.size = props.size
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var type = getType(props)
							 | 
						||
| 
								 | 
							
								    var handleHardlinks = props.hardlinks !== false
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // special little thing for handling hardlinks.
							 | 
						||
| 
								 | 
							
								    if (handleHardlinks && type !== 'Directory' && props.nlink && props.nlink > 1) {
							 | 
						||
| 
								 | 
							
								      var k = props.dev + ':' + props.ino
							 | 
						||
| 
								 | 
							
								      // console.error("Reader has nlink", self._path, k)
							 | 
						||
| 
								 | 
							
								      if (hardLinks[k] === self._path || !hardLinks[k]) {
							 | 
						||
| 
								 | 
							
								        hardLinks[k] = self._path
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        // switch into hardlink mode.
							 | 
						||
| 
								 | 
							
								        type = self.type = self.props.type = 'Link'
							 | 
						||
| 
								 | 
							
								        self.Link = self.props.Link = true
							 | 
						||
| 
								 | 
							
								        self.linkpath = self.props.linkpath = hardLinks[k]
							 | 
						||
| 
								 | 
							
								        // console.error("Hardlink detected, switching mode", self._path, self.linkpath)
							 | 
						||
| 
								 | 
							
								        // Setting __proto__ would arguably be the "correct"
							 | 
						||
| 
								 | 
							
								        // approach here, but that just seems too wrong.
							 | 
						||
| 
								 | 
							
								        self._stat = self._read = LinkReader.prototype._read
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (self.type && self.type !== type) {
							 | 
						||
| 
								 | 
							
								      self.error('Unexpected type: ' + type)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // if the filter doesn't pass, then just skip over this one.
							 | 
						||
| 
								 | 
							
								    // still have to emit end so that dir-walking can move on.
							 | 
						||
| 
								 | 
							
								    if (self.filter) {
							 | 
						||
| 
								 | 
							
								      var who = self._proxy || self
							 | 
						||
| 
								 | 
							
								      // special handling for ProxyReaders
							 | 
						||
| 
								 | 
							
								      if (!self.filter.call(who, who, props)) {
							 | 
						||
| 
								 | 
							
								        if (!self._disowned) {
							 | 
						||
| 
								 | 
							
								          self.abort()
							 | 
						||
| 
								 | 
							
								          self.emit('end')
							 | 
						||
| 
								 | 
							
								          self.emit('close')
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // last chance to abort or disown before the flow starts!
							 | 
						||
| 
								 | 
							
								    var events = ['_stat', 'stat', 'ready']
							 | 
						||
| 
								 | 
							
								    var e = 0
							 | 
						||
| 
								 | 
							
								    ;(function go () {
							 | 
						||
| 
								 | 
							
								      if (self._aborted) {
							 | 
						||
| 
								 | 
							
								        self.emit('end')
							 | 
						||
| 
								 | 
							
								        self.emit('close')
							 | 
						||
| 
								 | 
							
								        return
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (self._paused && self.type !== 'Directory') {
							 | 
						||
| 
								 | 
							
								        self.once('resume', go)
							 | 
						||
| 
								 | 
							
								        return
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      var ev = events[e++]
							 | 
						||
| 
								 | 
							
								      if (!ev) {
							 | 
						||
| 
								 | 
							
								        return self._read()
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      self.emit(ev, props)
							 | 
						||
| 
								 | 
							
								      go()
							 | 
						||
| 
								 | 
							
								    })()
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Reader.prototype.pipe = function (dest) {
							 | 
						||
| 
								 | 
							
								  var self = this
							 | 
						||
| 
								 | 
							
								  if (typeof dest.add === 'function') {
							 | 
						||
| 
								 | 
							
								    // piping to a multi-compatible, and we've got directory entries.
							 | 
						||
| 
								 | 
							
								    self.on('entry', function (entry) {
							 | 
						||
| 
								 | 
							
								      var ret = dest.add(entry)
							 | 
						||
| 
								 | 
							
								      if (ret === false) {
							 | 
						||
| 
								 | 
							
								        self.pause()
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // console.error("R Pipe apply Stream Pipe")
							 | 
						||
| 
								 | 
							
								  return Stream.prototype.pipe.apply(this, arguments)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Reader.prototype.pause = function (who) {
							 | 
						||
| 
								 | 
							
								  this._paused = true
							 | 
						||
| 
								 | 
							
								  who = who || this
							 | 
						||
| 
								 | 
							
								  this.emit('pause', who)
							 | 
						||
| 
								 | 
							
								  if (this._stream) this._stream.pause(who)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Reader.prototype.resume = function (who) {
							 | 
						||
| 
								 | 
							
								  this._paused = false
							 | 
						||
| 
								 | 
							
								  who = who || this
							 | 
						||
| 
								 | 
							
								  this.emit('resume', who)
							 | 
						||
| 
								 | 
							
								  if (this._stream) this._stream.resume(who)
							 | 
						||
| 
								 | 
							
								  this._read()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Reader.prototype._read = function () {
							 | 
						||
| 
								 | 
							
								  this.error('Cannot read unknown type: ' + this.type)
							 | 
						||
| 
								 | 
							
								}
							 |