e4e1b482cf6a313c3bcb6ece22675a3ea6e5a12b
[platform/upstream/nodejs.git] / deps / npm / node_modules / fstream / lib / reader.js
1
2 module.exports = Reader
3
4 var fs = require("graceful-fs")
5   , Stream = require("stream").Stream
6   , inherits = require("inherits")
7   , path = require("path")
8   , getType = require("./get-type.js")
9   , hardLinks = Reader.hardLinks = {}
10   , Abstract = require("./abstract.js")
11
12 // Must do this *before* loading the child classes
13 inherits(Reader, Abstract)
14
15 var DirReader = require("./dir-reader.js")
16   , FileReader = require("./file-reader.js")
17   , LinkReader = require("./link-reader.js")
18   , SocketReader = require("./socket-reader.js")
19   , ProxyReader = require("./proxy-reader.js")
20
21 function Reader (props, currentStat) {
22   var me = this
23   if (!(me instanceof Reader)) return new Reader(props, currentStat)
24
25   if (typeof props === "string") {
26     props = { path: props }
27   }
28
29   if (!props.path) {
30     me.error("Must provide a path", null, true)
31   }
32
33   // polymorphism.
34   // call fstream.Reader(dir) to get a DirReader object, etc.
35   // Note that, unlike in the Writer case, ProxyReader is going
36   // to be the *normal* state of affairs, since we rarely know
37   // the type of a file prior to reading it.
38
39
40   var type
41     , ClassType
42
43   if (props.type && typeof props.type === "function") {
44     type = props.type
45     ClassType = type
46   } else {
47     type = getType(props)
48     ClassType = Reader
49   }
50
51   if (currentStat && !type) {
52     type = getType(currentStat)
53     props[type] = true
54     props.type = type
55   }
56
57   switch (type) {
58     case "Directory":
59       ClassType = DirReader
60       break
61
62     case "Link":
63       // XXX hard links are just files.
64       // However, it would be good to keep track of files' dev+inode
65       // and nlink values, and create a HardLinkReader that emits
66       // a linkpath value of the original copy, so that the tar
67       // writer can preserve them.
68       // ClassType = HardLinkReader
69       // break
70
71     case "File":
72       ClassType = FileReader
73       break
74
75     case "SymbolicLink":
76       ClassType = LinkReader
77       break
78
79     case "Socket":
80       ClassType = SocketReader
81       break
82
83     case null:
84       ClassType = ProxyReader
85       break
86   }
87
88   if (!(me instanceof ClassType)) {
89     return new ClassType(props)
90   }
91
92   Abstract.call(me)
93
94   me.readable = true
95   me.writable = false
96
97   me.type = type
98   me.props = props
99   me.depth = props.depth = props.depth || 0
100   me.parent = props.parent || null
101   me.root = props.root || (props.parent && props.parent.root) || me
102
103   me._path = me.path = path.resolve(props.path)
104   if (process.platform === "win32") {
105     me.path = me._path = me.path.replace(/\?/g, "_")
106     if (me._path.length >= 260) {
107       // how DOES one create files on the moon?
108       // if the path has spaces in it, then UNC will fail.
109       me._swallowErrors = true
110       //if (me._path.indexOf(" ") === -1) {
111         me._path = "\\\\?\\" + me.path.replace(/\//g, "\\")
112       //}
113     }
114   }
115   me.basename = props.basename = path.basename(me.path)
116   me.dirname = props.dirname = path.dirname(me.path)
117
118   // these have served their purpose, and are now just noisy clutter
119   props.parent = props.root = null
120
121   // console.error("\n\n\n%s setting size to", props.path, props.size)
122   me.size = props.size
123   me.filter = typeof props.filter === "function" ? props.filter : null
124   if (props.sort === "alpha") props.sort = alphasort
125
126   // start the ball rolling.
127   // this will stat the thing, and then call me._read()
128   // to start reading whatever it is.
129   // console.error("calling stat", props.path, currentStat)
130   me._stat(currentStat)
131 }
132
133 function alphasort (a, b) {
134   return a === b ? 0
135        : a.toLowerCase() > b.toLowerCase() ? 1
136        : a.toLowerCase() < b.toLowerCase() ? -1
137        : a > b ? 1
138        : -1
139 }
140
141 Reader.prototype._stat = function (currentStat) {
142   var me = this
143     , props = me.props
144     , stat = props.follow ? "stat" : "lstat"
145
146   // console.error("Reader._stat", me._path, currentStat)
147   if (currentStat) process.nextTick(statCb.bind(null, null, currentStat))
148   else fs[stat](me._path, statCb)
149
150
151   function statCb (er, props_) {
152     // console.error("Reader._stat, statCb", me._path, props_, props_.nlink)
153     if (er) return me.error(er)
154
155     Object.keys(props_).forEach(function (k) {
156       props[k] = props_[k]
157     })
158
159     // if it's not the expected size, then abort here.
160     if (undefined !== me.size && props.size !== me.size) {
161       return me.error("incorrect size")
162     }
163     me.size = props.size
164
165     var type = getType(props)
166     // special little thing for handling hardlinks.
167     if (type !== "Directory" && props.nlink && props.nlink > 1) {
168       var k = props.dev + ":" + props.ino
169       // console.error("Reader has nlink", me._path, k)
170       if (hardLinks[k] === me._path || !hardLinks[k]) hardLinks[k] = me._path
171       else {
172         // switch into hardlink mode.
173         type = me.type = me.props.type = "Link"
174         me.Link = me.props.Link = true
175         me.linkpath = me.props.linkpath = hardLinks[k]
176         // console.error("Hardlink detected, switching mode", me._path, me.linkpath)
177         // Setting __proto__ would arguably be the "correct"
178         // approach here, but that just seems too wrong.
179         me._stat = me._read = LinkReader.prototype._read
180       }
181     }
182
183     if (me.type && me.type !== type) {
184       me.error("Unexpected type: " + type)
185     }
186
187     // if the filter doesn't pass, then just skip over this one.
188     // still have to emit end so that dir-walking can move on.
189     if (me.filter) {
190       var who = me._proxy || me
191       // special handling for ProxyReaders
192       if (!me.filter.call(who, who, props)) {
193         if (!me._disowned) {
194           me.abort()
195           me.emit("end")
196           me.emit("close")
197         }
198         return
199       }
200     }
201
202     // last chance to abort or disown before the flow starts!
203     var events = ["_stat", "stat", "ready"]
204     var e = 0
205     ;(function go () {
206       if (me._aborted) {
207         me.emit("end")
208         me.emit("close")
209         return
210       }
211
212       if (me._paused) {
213         me.once("resume", go)
214         return
215       }
216
217       var ev = events[e ++]
218       if (!ev) return me._read()
219       me.emit(ev, props)
220       go()
221     })()
222   }
223 }
224
225 Reader.prototype.pipe = function (dest, opts) {
226   var me = this
227   if (typeof dest.add === "function") {
228     // piping to a multi-compatible, and we've got directory entries.
229     me.on("entry", function (entry) {
230       var ret = dest.add(entry)
231       if (false === ret) {
232         me.pause()
233       }
234     })
235   }
236
237   // console.error("R Pipe apply Stream Pipe")
238   return Stream.prototype.pipe.apply(this, arguments)
239 }
240
241 Reader.prototype.pause = function (who) {
242   this._paused = true
243   who = who || this
244   this.emit("pause", who)
245   if (this._stream) this._stream.pause(who)
246 }
247
248 Reader.prototype.resume = function (who) {
249   this._paused = false
250   who = who || this
251   this.emit("resume", who)
252   if (this._stream) this._stream.resume(who)
253   this._read()
254 }
255
256 Reader.prototype._read = function () {
257   this.error("Cannot read unknown type: "+this.type)
258 }
259