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
			| 
											2 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) | ||
|  | } |