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.
		
		
		
		
			
				
					253 lines
				
				6.3 KiB
			
		
		
			
		
	
	
					253 lines
				
				6.3 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								// A thing that emits "entry" events with Reader objects
							 | 
						||
| 
								 | 
							
								// Pausing it causes it to stop emitting entry events, and also
							 | 
						||
| 
								 | 
							
								// pauses the current entry if there is one.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = DirReader
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var fs = require('graceful-fs')
							 | 
						||
| 
								 | 
							
								var inherits = require('inherits')
							 | 
						||
| 
								 | 
							
								var path = require('path')
							 | 
						||
| 
								 | 
							
								var Reader = require('./reader.js')
							 | 
						||
| 
								 | 
							
								var assert = require('assert').ok
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								inherits(DirReader, Reader)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function DirReader (props) {
							 | 
						||
| 
								 | 
							
								  var self = this
							 | 
						||
| 
								 | 
							
								  if (!(self instanceof DirReader)) {
							 | 
						||
| 
								 | 
							
								    throw new Error('DirReader must be called as constructor.')
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // should already be established as a Directory type
							 | 
						||
| 
								 | 
							
								  if (props.type !== 'Directory' || !props.Directory) {
							 | 
						||
| 
								 | 
							
								    throw new Error('Non-directory type ' + props.type)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  self.entries = null
							 | 
						||
| 
								 | 
							
								  self._index = -1
							 | 
						||
| 
								 | 
							
								  self._paused = false
							 | 
						||
| 
								 | 
							
								  self._length = -1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (props.sort) {
							 | 
						||
| 
								 | 
							
								    this.sort = props.sort
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  Reader.call(this, props)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								DirReader.prototype._getEntries = function () {
							 | 
						||
| 
								 | 
							
								  var self = this
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // race condition.  might pause() before calling _getEntries,
							 | 
						||
| 
								 | 
							
								  // and then resume, and try to get them a second time.
							 | 
						||
| 
								 | 
							
								  if (self._gotEntries) return
							 | 
						||
| 
								 | 
							
								  self._gotEntries = true
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  fs.readdir(self._path, function (er, entries) {
							 | 
						||
| 
								 | 
							
								    if (er) return self.error(er)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    self.entries = entries
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    self.emit('entries', entries)
							 | 
						||
| 
								 | 
							
								    if (self._paused) self.once('resume', processEntries)
							 | 
						||
| 
								 | 
							
								    else processEntries()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    function processEntries () {
							 | 
						||
| 
								 | 
							
								      self._length = self.entries.length
							 | 
						||
| 
								 | 
							
								      if (typeof self.sort === 'function') {
							 | 
						||
| 
								 | 
							
								        self.entries = self.entries.sort(self.sort.bind(self))
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      self._read()
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// start walking the dir, and emit an "entry" event for each one.
							 | 
						||
| 
								 | 
							
								DirReader.prototype._read = function () {
							 | 
						||
| 
								 | 
							
								  var self = this
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (!self.entries) return self._getEntries()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (self._paused || self._currentEntry || self._aborted) {
							 | 
						||
| 
								 | 
							
								    // console.error('DR paused=%j, current=%j, aborted=%j', self._paused, !!self._currentEntry, self._aborted)
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  self._index++
							 | 
						||
| 
								 | 
							
								  if (self._index >= self.entries.length) {
							 | 
						||
| 
								 | 
							
								    if (!self._ended) {
							 | 
						||
| 
								 | 
							
								      self._ended = true
							 | 
						||
| 
								 | 
							
								      self.emit('end')
							 | 
						||
| 
								 | 
							
								      self.emit('close')
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // ok, handle this one, then.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // save creating a proxy, by stat'ing the thing now.
							 | 
						||
| 
								 | 
							
								  var p = path.resolve(self._path, self.entries[self._index])
							 | 
						||
| 
								 | 
							
								  assert(p !== self._path)
							 | 
						||
| 
								 | 
							
								  assert(self.entries[self._index])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // set this to prevent trying to _read() again in the stat time.
							 | 
						||
| 
								 | 
							
								  self._currentEntry = p
							 | 
						||
| 
								 | 
							
								  fs[ self.props.follow ? 'stat' : 'lstat' ](p, function (er, stat) {
							 | 
						||
| 
								 | 
							
								    if (er) return self.error(er)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var who = self._proxy || self
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    stat.path = p
							 | 
						||
| 
								 | 
							
								    stat.basename = path.basename(p)
							 | 
						||
| 
								 | 
							
								    stat.dirname = path.dirname(p)
							 | 
						||
| 
								 | 
							
								    var childProps = self.getChildProps.call(who, stat)
							 | 
						||
| 
								 | 
							
								    childProps.path = p
							 | 
						||
| 
								 | 
							
								    childProps.basename = path.basename(p)
							 | 
						||
| 
								 | 
							
								    childProps.dirname = path.dirname(p)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var entry = Reader(childProps, stat)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // console.error("DR Entry", p, stat.size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    self._currentEntry = entry
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // "entry" events are for direct entries in a specific dir.
							 | 
						||
| 
								 | 
							
								    // "child" events are for any and all children at all levels.
							 | 
						||
| 
								 | 
							
								    // This nomenclature is not completely final.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    entry.on('pause', function (who) {
							 | 
						||
| 
								 | 
							
								      if (!self._paused && !entry._disowned) {
							 | 
						||
| 
								 | 
							
								        self.pause(who)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    entry.on('resume', function (who) {
							 | 
						||
| 
								 | 
							
								      if (self._paused && !entry._disowned) {
							 | 
						||
| 
								 | 
							
								        self.resume(who)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    entry.on('stat', function (props) {
							 | 
						||
| 
								 | 
							
								      self.emit('_entryStat', entry, props)
							 | 
						||
| 
								 | 
							
								      if (entry._aborted) return
							 | 
						||
| 
								 | 
							
								      if (entry._paused) {
							 | 
						||
| 
								 | 
							
								        entry.once('resume', function () {
							 | 
						||
| 
								 | 
							
								          self.emit('entryStat', entry, props)
							 | 
						||
| 
								 | 
							
								        })
							 | 
						||
| 
								 | 
							
								      } else self.emit('entryStat', entry, props)
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    entry.on('ready', function EMITCHILD () {
							 | 
						||
| 
								 | 
							
								      // console.error("DR emit child", entry._path)
							 | 
						||
| 
								 | 
							
								      if (self._paused) {
							 | 
						||
| 
								 | 
							
								        // console.error("  DR emit child - try again later")
							 | 
						||
| 
								 | 
							
								        // pause the child, and emit the "entry" event once we drain.
							 | 
						||
| 
								 | 
							
								        // console.error("DR pausing child entry")
							 | 
						||
| 
								 | 
							
								        entry.pause(self)
							 | 
						||
| 
								 | 
							
								        return self.once('resume', EMITCHILD)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // skip over sockets.  they can't be piped around properly,
							 | 
						||
| 
								 | 
							
								      // so there's really no sense even acknowledging them.
							 | 
						||
| 
								 | 
							
								      // if someone really wants to see them, they can listen to
							 | 
						||
| 
								 | 
							
								      // the "socket" events.
							 | 
						||
| 
								 | 
							
								      if (entry.type === 'Socket') {
							 | 
						||
| 
								 | 
							
								        self.emit('socket', entry)
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        self.emitEntry(entry)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var ended = false
							 | 
						||
| 
								 | 
							
								    entry.on('close', onend)
							 | 
						||
| 
								 | 
							
								    entry.on('disown', onend)
							 | 
						||
| 
								 | 
							
								    function onend () {
							 | 
						||
| 
								 | 
							
								      if (ended) return
							 | 
						||
| 
								 | 
							
								      ended = true
							 | 
						||
| 
								 | 
							
								      self.emit('childEnd', entry)
							 | 
						||
| 
								 | 
							
								      self.emit('entryEnd', entry)
							 | 
						||
| 
								 | 
							
								      self._currentEntry = null
							 | 
						||
| 
								 | 
							
								      if (!self._paused) {
							 | 
						||
| 
								 | 
							
								        self._read()
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // XXX Remove this.  Works in node as of 0.6.2 or so.
							 | 
						||
| 
								 | 
							
								    // Long filenames should not break stuff.
							 | 
						||
| 
								 | 
							
								    entry.on('error', function (er) {
							 | 
						||
| 
								 | 
							
								      if (entry._swallowErrors) {
							 | 
						||
| 
								 | 
							
								        self.warn(er)
							 | 
						||
| 
								 | 
							
								        entry.emit('end')
							 | 
						||
| 
								 | 
							
								        entry.emit('close')
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        self.emit('error', er)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // proxy up some events.
							 | 
						||
| 
								 | 
							
								    ;[
							 | 
						||
| 
								 | 
							
								      'child',
							 | 
						||
| 
								 | 
							
								      'childEnd',
							 | 
						||
| 
								 | 
							
								      'warn'
							 | 
						||
| 
								 | 
							
								    ].forEach(function (ev) {
							 | 
						||
| 
								 | 
							
								      entry.on(ev, self.emit.bind(self, ev))
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								DirReader.prototype.disown = function (entry) {
							 | 
						||
| 
								 | 
							
								  entry.emit('beforeDisown')
							 | 
						||
| 
								 | 
							
								  entry._disowned = true
							 | 
						||
| 
								 | 
							
								  entry.parent = entry.root = null
							 | 
						||
| 
								 | 
							
								  if (entry === this._currentEntry) {
							 | 
						||
| 
								 | 
							
								    this._currentEntry = null
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  entry.emit('disown')
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								DirReader.prototype.getChildProps = function () {
							 | 
						||
| 
								 | 
							
								  return {
							 | 
						||
| 
								 | 
							
								    depth: this.depth + 1,
							 | 
						||
| 
								 | 
							
								    root: this.root || this,
							 | 
						||
| 
								 | 
							
								    parent: this,
							 | 
						||
| 
								 | 
							
								    follow: this.follow,
							 | 
						||
| 
								 | 
							
								    filter: this.filter,
							 | 
						||
| 
								 | 
							
								    sort: this.props.sort,
							 | 
						||
| 
								 | 
							
								    hardlinks: this.props.hardlinks
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								DirReader.prototype.pause = function (who) {
							 | 
						||
| 
								 | 
							
								  var self = this
							 | 
						||
| 
								 | 
							
								  if (self._paused) return
							 | 
						||
| 
								 | 
							
								  who = who || self
							 | 
						||
| 
								 | 
							
								  self._paused = true
							 | 
						||
| 
								 | 
							
								  if (self._currentEntry && self._currentEntry.pause) {
							 | 
						||
| 
								 | 
							
								    self._currentEntry.pause(who)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  self.emit('pause', who)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								DirReader.prototype.resume = function (who) {
							 | 
						||
| 
								 | 
							
								  var self = this
							 | 
						||
| 
								 | 
							
								  if (!self._paused) return
							 | 
						||
| 
								 | 
							
								  who = who || self
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  self._paused = false
							 | 
						||
| 
								 | 
							
								  // console.error('DR Emit Resume', self._path)
							 | 
						||
| 
								 | 
							
								  self.emit('resume', who)
							 | 
						||
| 
								 | 
							
								  if (self._paused) {
							 | 
						||
| 
								 | 
							
								    // console.error('DR Re-paused', self._path)
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (self._currentEntry) {
							 | 
						||
| 
								 | 
							
								    if (self._currentEntry.resume) self._currentEntry.resume(who)
							 | 
						||
| 
								 | 
							
								  } else self._read()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								DirReader.prototype.emitEntry = function (entry) {
							 | 
						||
| 
								 | 
							
								  this.emit('entry', entry)
							 | 
						||
| 
								 | 
							
								  this.emit('child', entry)
							 | 
						||
| 
								 | 
							
								}
							 |