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 , 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
16 inherits(DirReader, Reader)
18 function DirReader (props) {
20 if (!(me instanceof DirReader)) throw new Error(
21 "DirReader must be called as constructor.")
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)
34 this.sort = props.sort
37 Reader.call(this, props)
40 DirReader.prototype._getEntries = function () {
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
48 fs.readdir(me._path, function (er, entries) {
49 if (er) return me.error(er)
53 me.emit("entries", entries)
54 if (me._paused) me.once("resume", processEntries)
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))
67 // start walking the dir, and emit an "entry" event for each one.
68 DirReader.prototype._read = function () {
71 if (!me.entries) return me._getEntries()
73 if (me._paused || me._currentEntry || me._aborted) {
74 // console.error("DR paused=%j, current=%j, aborted=%j", me._paused, !!me._currentEntry, me._aborted)
79 if (me._index >= me.entries.length) {
88 // ok, handle this one, then.
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])
95 // set this to prevent trying to _read() again in the stat time.
97 fs[ me.props.follow ? "stat" : "lstat" ](p, function (er, stat) {
98 if (er) return me.error(er)
100 var who = me._proxy || me
103 stat.basename = path.basename(p)
104 stat.dirname = path.dirname(p)
105 var childProps = me.getChildProps.call(who, stat)
107 childProps.basename = path.basename(p)
108 childProps.dirname = path.dirname(p)
110 var entry = Reader(childProps, stat)
112 // console.error("DR Entry", p, stat.size)
114 me._currentEntry = entry
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.
120 entry.on("pause", function (who) {
121 if (!me._paused && !entry._disowned) {
126 entry.on("resume", function (who) {
127 if (me._paused && !entry._disowned) {
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)
138 else me.emit("entryStat", entry, props)
141 entry.on("ready", function EMITCHILD () {
142 // console.error("DR emit child", entry._path)
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")
148 return me.once("resume", EMITCHILD)
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)
163 entry.on("close", onend)
164 entry.on("disown", onend)
168 me.emit("childEnd", entry)
169 me.emit("entryEnd", entry)
170 me._currentEntry = null
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) {
188 // proxy up some events.
192 ].forEach(function (ev) {
193 entry.on(ev, me.emit.bind(me, 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 (stat) {
209 return { depth: this.depth + 1
210 , root: this.root || this
212 , follow: this.follow
213 , filter: this.filter
214 , sort: this.props.sort
218 DirReader.prototype.pause = function (who) {
220 if (me._paused) return
223 if (me._currentEntry && me._currentEntry.pause) {
224 me._currentEntry.pause(who)
226 me.emit("pause", who)
229 DirReader.prototype.resume = function (who) {
231 if (!me._paused) return
235 // console.error("DR Emit Resume", me._path)
236 me.emit("resume", who)
238 // console.error("DR Re-paused", me._path)
242 if (me._currentEntry) {
243 if (me._currentEntry.resume) me._currentEntry.resume(who)
247 DirReader.prototype.emitEntry = function (entry) {
248 this.emit("entry", entry)
249 this.emit("child", entry)