1 // A thing that emits "entry" events with Reader objects
2 // Pausing it causes it to stop emitting entry events, and also
3 // pauses the current entry if there is one.
5 module.exports = DirReader
7 var fs = require('graceful-fs')
8 var inherits = require('inherits')
9 var path = require('path')
10 var Reader = require('./reader.js')
11 var assert = require('assert').ok
13 inherits(DirReader, Reader)
15 function DirReader (props) {
17 if (!(self instanceof DirReader)) {
18 throw new Error('DirReader must be called as constructor.')
21 // should already be established as a Directory type
22 if (props.type !== 'Directory' || !props.Directory) {
23 throw new Error('Non-directory type ' + props.type)
32 this.sort = props.sort
35 Reader.call(this, props)
38 DirReader.prototype._getEntries = function () {
41 // race condition. might pause() before calling _getEntries,
42 // and then resume, and try to get them a second time.
43 if (self._gotEntries) return
44 self._gotEntries = true
46 fs.readdir(self._path, function (er, entries) {
47 if (er) return self.error(er)
49 self.entries = entries
51 self.emit('entries', entries)
52 if (self._paused) self.once('resume', processEntries)
55 function processEntries () {
56 self._length = self.entries.length
57 if (typeof self.sort === 'function') {
58 self.entries = self.entries.sort(self.sort.bind(self))
65 // start walking the dir, and emit an "entry" event for each one.
66 DirReader.prototype._read = function () {
69 if (!self.entries) return self._getEntries()
71 if (self._paused || self._currentEntry || self._aborted) {
72 // console.error('DR paused=%j, current=%j, aborted=%j', self._paused, !!self._currentEntry, self._aborted)
77 if (self._index >= self.entries.length) {
86 // ok, handle this one, then.
88 // save creating a proxy, by stat'ing the thing now.
89 var p = path.resolve(self._path, self.entries[self._index])
90 assert(p !== self._path)
91 assert(self.entries[self._index])
93 // set this to prevent trying to _read() again in the stat time.
94 self._currentEntry = p
95 fs[ self.props.follow ? 'stat' : 'lstat' ](p, function (er, stat) {
96 if (er) return self.error(er)
98 var who = self._proxy || self
101 stat.basename = path.basename(p)
102 stat.dirname = path.dirname(p)
103 var childProps = self.getChildProps.call(who, stat)
105 childProps.basename = path.basename(p)
106 childProps.dirname = path.dirname(p)
108 var entry = Reader(childProps, stat)
110 // console.error("DR Entry", p, stat.size)
112 self._currentEntry = entry
114 // "entry" events are for direct entries in a specific dir.
115 // "child" events are for any and all children at all levels.
116 // This nomenclature is not completely final.
118 entry.on('pause', function (who) {
119 if (!self._paused && !entry._disowned) {
124 entry.on('resume', function (who) {
125 if (self._paused && !entry._disowned) {
130 entry.on('stat', function (props) {
131 self.emit('_entryStat', entry, props)
132 if (entry._aborted) return
134 entry.once('resume', function () {
135 self.emit('entryStat', entry, props)
137 } else self.emit('entryStat', entry, props)
140 entry.on('ready', function EMITCHILD () {
141 // console.error("DR emit child", entry._path)
143 // console.error(" DR emit child - try again later")
144 // pause the child, and emit the "entry" event once we drain.
145 // console.error("DR pausing child entry")
147 return self.once('resume', EMITCHILD)
150 // skip over sockets. they can't be piped around properly,
151 // so there's really no sense even acknowledging them.
152 // if someone really wants to see them, they can listen to
153 // the "socket" events.
154 if (entry.type === 'Socket') {
155 self.emit('socket', entry)
157 self.emitEntry(entry)
162 entry.on('close', onend)
163 entry.on('disown', onend)
167 self.emit('childEnd', entry)
168 self.emit('entryEnd', entry)
169 self._currentEntry = null
175 // XXX Remove this. Works in node as of 0.6.2 or so.
176 // Long filenames should not break stuff.
177 entry.on('error', function (er) {
178 if (entry._swallowErrors) {
183 self.emit('error', er)
187 // proxy up some events.
192 ].forEach(function (ev) {
193 entry.on(ev, self.emit.bind(self, ev))
198 DirReader.prototype.disown = function (entry) {
199 entry.emit('beforeDisown')
200 entry._disowned = true
201 entry.parent = entry.root = null
202 if (entry === this._currentEntry) {
203 this._currentEntry = null
208 DirReader.prototype.getChildProps = function () {
210 depth: this.depth + 1,
211 root: this.root || this,
215 sort: this.props.sort,
216 hardlinks: this.props.hardlinks
220 DirReader.prototype.pause = function (who) {
222 if (self._paused) return
225 if (self._currentEntry && self._currentEntry.pause) {
226 self._currentEntry.pause(who)
228 self.emit('pause', who)
231 DirReader.prototype.resume = function (who) {
233 if (!self._paused) return
237 // console.error('DR Emit Resume', self._path)
238 self.emit('resume', who)
240 // console.error('DR Re-paused', self._path)
244 if (self._currentEntry) {
245 if (self._currentEntry.resume) self._currentEntry.resume(who)
249 DirReader.prototype.emitEntry = function (entry) {
250 this.emit('entry', entry)
251 this.emit('child', entry)