npm: Upgrade to 1.3.17
[platform/upstream/nodejs.git] / deps / npm / 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   , fstream = require("../fstream.js")
9   , Reader = fstream.Reader
10   , inherits = require("inherits")
11   , mkdir = require("mkdirp")
12   , path = require("path")
13   , Reader = require("./reader.js")
14   , assert = require("assert").ok
15
16 inherits(DirReader, Reader)
17
18 function DirReader (props) {
19   var me = this
20   if (!(me instanceof DirReader)) throw new Error(
21     "DirReader must be called as constructor.")
22
23   // should already be established as a Directory type
24   if (props.type !== "Directory" || !props.Directory) {
25     throw new Error("Non-directory type "+ props.type)
26   }
27
28   me.entries = null
29   me._index = -1
30   me._paused = false
31   me._length = -1
32
33   if (props.sort) {
34     this.sort = props.sort
35   }
36
37   Reader.call(this, props)
38 }
39
40 DirReader.prototype._getEntries = function () {
41   var me = this
42
43   // race condition.  might pause() before calling _getEntries,
44   // and then resume, and try to get them a second time.
45   if (me._gotEntries) return
46   me._gotEntries = true
47
48   fs.readdir(me._path, function (er, entries) {
49     if (er) return me.error(er)
50
51     me.entries = entries
52
53     me.emit("entries", entries)
54     if (me._paused) me.once("resume", processEntries)
55     else processEntries()
56
57     function processEntries () {
58       me._length = me.entries.length
59       if (typeof me.sort === "function") {
60         me.entries = me.entries.sort(me.sort.bind(me))
61       }
62       me._read()
63     }
64   })
65 }
66
67 // start walking the dir, and emit an "entry" event for each one.
68 DirReader.prototype._read = function () {
69   var me = this
70
71   if (!me.entries) return me._getEntries()
72
73   if (me._paused || me._currentEntry || me._aborted) {
74     // console.error("DR paused=%j, current=%j, aborted=%j", me._paused, !!me._currentEntry, me._aborted)
75     return
76   }
77
78   me._index ++
79   if (me._index >= me.entries.length) {
80     if (!me._ended) {
81       me._ended = true
82       me.emit("end")
83       me.emit("close")
84     }
85     return
86   }
87
88   // ok, handle this one, then.
89
90   // save creating a proxy, by stat'ing the thing now.
91   var p = path.resolve(me._path, me.entries[me._index])
92   assert(p !== me._path)
93   assert(me.entries[me._index])
94
95   // set this to prevent trying to _read() again in the stat time.
96   me._currentEntry = p
97   fs[ me.props.follow ? "stat" : "lstat" ](p, function (er, stat) {
98     if (er) return me.error(er)
99
100     var who = me._proxy || me
101
102     stat.path = p
103     stat.basename = path.basename(p)
104     stat.dirname = path.dirname(p)
105     var childProps = me.getChildProps.call(who, stat)
106     childProps.path = p
107     childProps.basename = path.basename(p)
108     childProps.dirname = path.dirname(p)
109
110     var entry = Reader(childProps, stat)
111
112     // console.error("DR Entry", p, stat.size)
113
114     me._currentEntry = entry
115
116     // "entry" events are for direct entries in a specific dir.
117     // "child" events are for any and all children at all levels.
118     // This nomenclature is not completely final.
119
120     entry.on("pause", function (who) {
121       if (!me._paused && !entry._disowned) {
122         me.pause(who)
123       }
124     })
125
126     entry.on("resume", function (who) {
127       if (me._paused && !entry._disowned) {
128         me.resume(who)
129       }
130     })
131
132     entry.on("stat", function (props) {
133       me.emit("_entryStat", entry, props)
134       if (entry._aborted) return
135       if (entry._paused) entry.once("resume", function () {
136         me.emit("entryStat", entry, props)
137       })
138       else me.emit("entryStat", entry, props)
139     })
140
141     entry.on("ready", function EMITCHILD () {
142       // console.error("DR emit child", entry._path)
143       if (me._paused) {
144         // console.error("  DR emit child - try again later")
145         // pause the child, and emit the "entry" event once we drain.
146         // console.error("DR pausing child entry")
147         entry.pause(me)
148         return me.once("resume", EMITCHILD)
149       }
150
151       // skip over sockets.  they can't be piped around properly,
152       // so there's really no sense even acknowledging them.
153       // if someone really wants to see them, they can listen to
154       // the "socket" events.
155       if (entry.type === "Socket") {
156         me.emit("socket", entry)
157       } else {
158         me.emitEntry(entry)
159       }
160     })
161
162     var ended = false
163     entry.on("close", onend)
164     entry.on("disown", onend)
165     function onend () {
166       if (ended) return
167       ended = true
168       me.emit("childEnd", entry)
169       me.emit("entryEnd", entry)
170       me._currentEntry = null
171       if (!me._paused) {
172         me._read()
173       }
174     }
175
176     // XXX Remove this.  Works in node as of 0.6.2 or so.
177     // Long filenames should not break stuff.
178     entry.on("error", function (er) {
179       if (entry._swallowErrors) {
180         me.warn(er)
181         entry.emit("end")
182         entry.emit("close")
183       } else {
184         me.emit("error", er)
185       }
186     })
187
188     // proxy up some events.
189     ; [ "child"
190       , "childEnd"
191       , "warn"
192       ].forEach(function (ev) {
193         entry.on(ev, me.emit.bind(me, 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 (stat) {
209   return { depth: this.depth + 1
210          , root: this.root || this
211          , parent: this
212          , follow: this.follow
213          , filter: this.filter
214          , sort: this.props.sort
215          , hardlinks: this.props.hardlinks
216          }
217 }
218
219 DirReader.prototype.pause = function (who) {
220   var me = this
221   if (me._paused) return
222   who = who || me
223   me._paused = true
224   if (me._currentEntry && me._currentEntry.pause) {
225     me._currentEntry.pause(who)
226   }
227   me.emit("pause", who)
228 }
229
230 DirReader.prototype.resume = function (who) {
231   var me = this
232   if (!me._paused) return
233   who = who || me
234
235   me._paused = false
236   // console.error("DR Emit Resume", me._path)
237   me.emit("resume", who)
238   if (me._paused) {
239     // console.error("DR Re-paused", me._path)
240     return
241   }
242
243   if (me._currentEntry) {
244     if (me._currentEntry.resume) me._currentEntry.resume(who)
245   } else me._read()
246 }
247
248 DirReader.prototype.emitEntry = function (entry) {
249   this.emit("entry", entry)
250   this.emit("child", entry)
251 }