blob: 820cdc85a8e9c4ae4b6aa64250df9dbf829c0f54 [file] [log] [blame]
// 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)
}