Initial commit
[yaffs-website] / node_modules / fstream / lib / dir-reader.js
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.
4
5 module.exports = DirReader
6
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
12
13 inherits(DirReader, Reader)
14
15 function DirReader (props) {
16   var self = this
17   if (!(self instanceof DirReader)) {
18     throw new Error('DirReader must be called as constructor.')
19   }
20
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)
24   }
25
26   self.entries = null
27   self._index = -1
28   self._paused = false
29   self._length = -1
30
31   if (props.sort) {
32     this.sort = props.sort
33   }
34
35   Reader.call(this, props)
36 }
37
38 DirReader.prototype._getEntries = function () {
39   var self = this
40
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
45
46   fs.readdir(self._path, function (er, entries) {
47     if (er) return self.error(er)
48
49     self.entries = entries
50
51     self.emit('entries', entries)
52     if (self._paused) self.once('resume', processEntries)
53     else processEntries()
54
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))
59       }
60       self._read()
61     }
62   })
63 }
64
65 // start walking the dir, and emit an "entry" event for each one.
66 DirReader.prototype._read = function () {
67   var self = this
68
69   if (!self.entries) return self._getEntries()
70
71   if (self._paused || self._currentEntry || self._aborted) {
72     // console.error('DR paused=%j, current=%j, aborted=%j', self._paused, !!self._currentEntry, self._aborted)
73     return
74   }
75
76   self._index++
77   if (self._index >= self.entries.length) {
78     if (!self._ended) {
79       self._ended = true
80       self.emit('end')
81       self.emit('close')
82     }
83     return
84   }
85
86   // ok, handle this one, then.
87
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])
92
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)
97
98     var who = self._proxy || self
99
100     stat.path = p
101     stat.basename = path.basename(p)
102     stat.dirname = path.dirname(p)
103     var childProps = self.getChildProps.call(who, stat)
104     childProps.path = p
105     childProps.basename = path.basename(p)
106     childProps.dirname = path.dirname(p)
107
108     var entry = Reader(childProps, stat)
109
110     // console.error("DR Entry", p, stat.size)
111
112     self._currentEntry = entry
113
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.
117
118     entry.on('pause', function (who) {
119       if (!self._paused && !entry._disowned) {
120         self.pause(who)
121       }
122     })
123
124     entry.on('resume', function (who) {
125       if (self._paused && !entry._disowned) {
126         self.resume(who)
127       }
128     })
129
130     entry.on('stat', function (props) {
131       self.emit('_entryStat', entry, props)
132       if (entry._aborted) return
133       if (entry._paused) {
134         entry.once('resume', function () {
135           self.emit('entryStat', entry, props)
136         })
137       } else self.emit('entryStat', entry, props)
138     })
139
140     entry.on('ready', function EMITCHILD () {
141       // console.error("DR emit child", entry._path)
142       if (self._paused) {
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")
146         entry.pause(self)
147         return self.once('resume', EMITCHILD)
148       }
149
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)
156       } else {
157         self.emitEntry(entry)
158       }
159     })
160
161     var ended = false
162     entry.on('close', onend)
163     entry.on('disown', onend)
164     function onend () {
165       if (ended) return
166       ended = true
167       self.emit('childEnd', entry)
168       self.emit('entryEnd', entry)
169       self._currentEntry = null
170       if (!self._paused) {
171         self._read()
172       }
173     }
174
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) {
179         self.warn(er)
180         entry.emit('end')
181         entry.emit('close')
182       } else {
183         self.emit('error', er)
184       }
185     })
186
187     // proxy up some events.
188     ;[
189       'child',
190       'childEnd',
191       'warn'
192     ].forEach(function (ev) {
193       entry.on(ev, self.emit.bind(self, ev))
194     })
195   })
196 }
197
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
204   }
205   entry.emit('disown')
206 }
207
208 DirReader.prototype.getChildProps = function () {
209   return {
210     depth: this.depth + 1,
211     root: this.root || this,
212     parent: this,
213     follow: this.follow,
214     filter: this.filter,
215     sort: this.props.sort,
216     hardlinks: this.props.hardlinks
217   }
218 }
219
220 DirReader.prototype.pause = function (who) {
221   var self = this
222   if (self._paused) return
223   who = who || self
224   self._paused = true
225   if (self._currentEntry && self._currentEntry.pause) {
226     self._currentEntry.pause(who)
227   }
228   self.emit('pause', who)
229 }
230
231 DirReader.prototype.resume = function (who) {
232   var self = this
233   if (!self._paused) return
234   who = who || self
235
236   self._paused = false
237   // console.error('DR Emit Resume', self._path)
238   self.emit('resume', who)
239   if (self._paused) {
240     // console.error('DR Re-paused', self._path)
241     return
242   }
243
244   if (self._currentEntry) {
245     if (self._currentEntry.resume) self._currentEntry.resume(who)
246   } else self._read()
247 }
248
249 DirReader.prototype.emitEntry = function (entry) {
250   this.emit('entry', entry)
251   this.emit('child', entry)
252 }