| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- module.exports = Reader
- var fs = require("graceful-fs")
- , Stream = require("stream").Stream
- , inherits = require("inherits")
- , path = require("path")
- , getType = require("./get-type.js")
- , hardLinks = Reader.hardLinks = {}
- , Abstract = require("./abstract.js")
- // Must do this *before* loading the child classes
- inherits(Reader, Abstract)
- var DirReader = require("./dir-reader.js")
- , FileReader = require("./file-reader.js")
- , LinkReader = require("./link-reader.js")
- , SocketReader = require("./socket-reader.js")
- , ProxyReader = require("./proxy-reader.js")
- function Reader (props, currentStat) {
- var me = this
- if (!(me instanceof Reader)) return new Reader(props, currentStat)
- if (typeof props === "string") {
- props = { path: props }
- }
- if (!props.path) {
- me.error("Must provide a path", null, true)
- }
- // polymorphism.
- // call fstream.Reader(dir) to get a DirReader object, etc.
- // Note that, unlike in the Writer case, ProxyReader is going
- // to be the *normal* state of affairs, since we rarely know
- // the type of a file prior to reading it.
- var type
- , ClassType
- if (props.type && typeof props.type === "function") {
- type = props.type
- ClassType = type
- } else {
- type = getType(props)
- ClassType = Reader
- }
- if (currentStat && !type) {
- type = getType(currentStat)
- props[type] = true
- props.type = type
- }
- switch (type) {
- case "Directory":
- ClassType = DirReader
- break
- case "Link":
- // XXX hard links are just files.
- // However, it would be good to keep track of files' dev+inode
- // and nlink values, and create a HardLinkReader that emits
- // a linkpath value of the original copy, so that the tar
- // writer can preserve them.
- // ClassType = HardLinkReader
- // break
- case "File":
- ClassType = FileReader
- break
- case "SymbolicLink":
- ClassType = LinkReader
- break
- case "Socket":
- ClassType = SocketReader
- break
- case null:
- ClassType = ProxyReader
- break
- }
- if (!(me instanceof ClassType)) {
- return new ClassType(props)
- }
- Abstract.call(me)
- me.readable = true
- me.writable = false
- me.type = type
- me.props = props
- me.depth = props.depth = props.depth || 0
- me.parent = props.parent || null
- me.root = props.root || (props.parent && props.parent.root) || me
- me._path = me.path = path.resolve(props.path)
- if (process.platform === "win32") {
- me.path = me._path = me.path.replace(/\?/g, "_")
- if (me._path.length >= 260) {
- // how DOES one create files on the moon?
- // if the path has spaces in it, then UNC will fail.
- me._swallowErrors = true
- //if (me._path.indexOf(" ") === -1) {
- me._path = "\\\\?\\" + me.path.replace(/\//g, "\\")
- //}
- }
- }
- me.basename = props.basename = path.basename(me.path)
- me.dirname = props.dirname = path.dirname(me.path)
- // these have served their purpose, and are now just noisy clutter
- props.parent = props.root = null
- // console.error("\n\n\n%s setting size to", props.path, props.size)
- me.size = props.size
- me.filter = typeof props.filter === "function" ? props.filter : null
- if (props.sort === "alpha") props.sort = alphasort
- // start the ball rolling.
- // this will stat the thing, and then call me._read()
- // to start reading whatever it is.
- // console.error("calling stat", props.path, currentStat)
- me._stat(currentStat)
- }
- function alphasort (a, b) {
- return a === b ? 0
- : a.toLowerCase() > b.toLowerCase() ? 1
- : a.toLowerCase() < b.toLowerCase() ? -1
- : a > b ? 1
- : -1
- }
- Reader.prototype._stat = function (currentStat) {
- var me = this
- , props = me.props
- , stat = props.follow ? "stat" : "lstat"
- // console.error("Reader._stat", me._path, currentStat)
- if (currentStat) process.nextTick(statCb.bind(null, null, currentStat))
- else fs[stat](me._path, statCb)
- function statCb (er, props_) {
- // console.error("Reader._stat, statCb", me._path, props_, props_.nlink)
- if (er) return me.error(er)
- Object.keys(props_).forEach(function (k) {
- props[k] = props_[k]
- })
- // if it's not the expected size, then abort here.
- if (undefined !== me.size && props.size !== me.size) {
- return me.error("incorrect size")
- }
- me.size = props.size
- var type = getType(props)
- var handleHardlinks = props.hardlinks !== false
-
- // special little thing for handling hardlinks.
- if (handleHardlinks && type !== "Directory" && props.nlink && props.nlink > 1) {
- var k = props.dev + ":" + props.ino
- // console.error("Reader has nlink", me._path, k)
- if (hardLinks[k] === me._path || !hardLinks[k]) hardLinks[k] = me._path
- else {
- // switch into hardlink mode.
- type = me.type = me.props.type = "Link"
- me.Link = me.props.Link = true
- me.linkpath = me.props.linkpath = hardLinks[k]
- // console.error("Hardlink detected, switching mode", me._path, me.linkpath)
- // Setting __proto__ would arguably be the "correct"
- // approach here, but that just seems too wrong.
- me._stat = me._read = LinkReader.prototype._read
- }
- }
- if (me.type && me.type !== type) {
- me.error("Unexpected type: " + type)
- }
- // if the filter doesn't pass, then just skip over this one.
- // still have to emit end so that dir-walking can move on.
- if (me.filter) {
- var who = me._proxy || me
- // special handling for ProxyReaders
- if (!me.filter.call(who, who, props)) {
- if (!me._disowned) {
- me.abort()
- me.emit("end")
- me.emit("close")
- }
- return
- }
- }
- // last chance to abort or disown before the flow starts!
- var events = ["_stat", "stat", "ready"]
- var e = 0
- ;(function go () {
- if (me._aborted) {
- me.emit("end")
- me.emit("close")
- return
- }
- if (me._paused && me.type !== "Directory") {
- me.once("resume", go)
- return
- }
- var ev = events[e ++]
- if (!ev) {
- return me._read()
- }
- me.emit(ev, props)
- go()
- })()
- }
- }
- Reader.prototype.pipe = function (dest, opts) {
- var me = this
- if (typeof dest.add === "function") {
- // piping to a multi-compatible, and we've got directory entries.
- me.on("entry", function (entry) {
- var ret = dest.add(entry)
- if (false === ret) {
- me.pause()
- }
- })
- }
- // console.error("R Pipe apply Stream Pipe")
- return Stream.prototype.pipe.apply(this, arguments)
- }
- Reader.prototype.pause = function (who) {
- this._paused = true
- who = who || this
- this.emit("pause", who)
- if (this._stream) this._stream.pause(who)
- }
- Reader.prototype.resume = function (who) {
- this._paused = false
- who = who || this
- this.emit("resume", who)
- if (this._stream) this._stream.resume(who)
- this._read()
- }
- Reader.prototype._read = function () {
- this.error("Cannot read unknown type: "+this.type)
- }
|