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.
		
		
		
		
		
			
		
			
				
					
					
						
							252 lines
						
					
					
						
							6.3 KiB
						
					
					
				
			
		
		
	
	
							252 lines
						
					
					
						
							6.3 KiB
						
					
					
				// 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) | 
						|
}
 | 
						|
 |