| module.exports = Reader |
| |
| var fs = require('graceful-fs') |
| var Stream = require('stream').Stream |
| var inherits = require('inherits') |
| var path = require('path') |
| var getType = require('./get-type.js') |
| var hardLinks = Reader.hardLinks = {} |
| var Abstract = require('./abstract.js') |
| |
| // Must do this *before* loading the child classes |
| inherits(Reader, Abstract) |
| |
| var LinkReader = require('./link-reader.js') |
| |
| function Reader (props, currentStat) { |
| var self = this |
| if (!(self instanceof Reader)) return new Reader(props, currentStat) |
| |
| if (typeof props === 'string') { |
| props = { path: props } |
| } |
| |
| if (!props.path) { |
| self.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 |
| var 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 = require('./dir-reader.js') |
| 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 = require('./file-reader.js') |
| break |
| |
| case 'SymbolicLink': |
| ClassType = LinkReader |
| break |
| |
| case 'Socket': |
| ClassType = require('./socket-reader.js') |
| break |
| |
| case null: |
| ClassType = require('./proxy-reader.js') |
| break |
| } |
| |
| if (!(self instanceof ClassType)) { |
| return new ClassType(props) |
| } |
| |
| Abstract.call(self) |
| |
| self.readable = true |
| self.writable = false |
| |
| self.type = type |
| self.props = props |
| self.depth = props.depth = props.depth || 0 |
| self.parent = props.parent || null |
| self.root = props.root || (props.parent && props.parent.root) || self |
| |
| self._path = self.path = path.resolve(props.path) |
| if (process.platform === 'win32') { |
| self.path = self._path = self.path.replace(/\?/g, '_') |
| if (self._path.length >= 260) { |
| // how DOES one create files on the moon? |
| // if the path has spaces in it, then UNC will fail. |
| self._swallowErrors = true |
| // if (self._path.indexOf(" ") === -1) { |
| self._path = '\\\\?\\' + self.path.replace(/\//g, '\\') |
| // } |
| } |
| } |
| self.basename = props.basename = path.basename(self.path) |
| self.dirname = props.dirname = path.dirname(self.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) |
| self.size = props.size |
| self.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 self._read() |
| // to start reading whatever it is. |
| // console.error("calling stat", props.path, currentStat) |
| self._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 self = this |
| var props = self.props |
| var stat = props.follow ? 'stat' : 'lstat' |
| // console.error("Reader._stat", self._path, currentStat) |
| if (currentStat) process.nextTick(statCb.bind(null, null, currentStat)) |
| else fs[stat](self._path, statCb) |
| |
| function statCb (er, props_) { |
| // console.error("Reader._stat, statCb", self._path, props_, props_.nlink) |
| if (er) return self.error(er) |
| |
| Object.keys(props_).forEach(function (k) { |
| props[k] = props_[k] |
| }) |
| |
| // if it's not the expected size, then abort here. |
| if (undefined !== self.size && props.size !== self.size) { |
| return self.error('incorrect size') |
| } |
| self.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", self._path, k) |
| if (hardLinks[k] === self._path || !hardLinks[k]) { |
| hardLinks[k] = self._path |
| } else { |
| // switch into hardlink mode. |
| type = self.type = self.props.type = 'Link' |
| self.Link = self.props.Link = true |
| self.linkpath = self.props.linkpath = hardLinks[k] |
| // console.error("Hardlink detected, switching mode", self._path, self.linkpath) |
| // Setting __proto__ would arguably be the "correct" |
| // approach here, but that just seems too wrong. |
| self._stat = self._read = LinkReader.prototype._read |
| } |
| } |
| |
| if (self.type && self.type !== type) { |
| self.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 (self.filter) { |
| var who = self._proxy || self |
| // special handling for ProxyReaders |
| if (!self.filter.call(who, who, props)) { |
| if (!self._disowned) { |
| self.abort() |
| self.emit('end') |
| self.emit('close') |
| } |
| return |
| } |
| } |
| |
| // last chance to abort or disown before the flow starts! |
| var events = ['_stat', 'stat', 'ready'] |
| var e = 0 |
| ;(function go () { |
| if (self._aborted) { |
| self.emit('end') |
| self.emit('close') |
| return |
| } |
| |
| if (self._paused && self.type !== 'Directory') { |
| self.once('resume', go) |
| return |
| } |
| |
| var ev = events[e++] |
| if (!ev) { |
| return self._read() |
| } |
| self.emit(ev, props) |
| go() |
| })() |
| } |
| } |
| |
| Reader.prototype.pipe = function (dest) { |
| var self = this |
| if (typeof dest.add === 'function') { |
| // piping to a multi-compatible, and we've got directory entries. |
| self.on('entry', function (entry) { |
| var ret = dest.add(entry) |
| if (ret === false) { |
| self.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) |
| } |