You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
255 lines
6.9 KiB
255 lines
6.9 KiB
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 } |
|
} |
|
|
|
// 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) |
|
|
|
if (!props.path) { |
|
self.error('Must provide a path', null, true) |
|
} |
|
|
|
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) |
|
}
|
|
|