256 lines
6.9 KiB
JavaScript
256 lines
6.9 KiB
JavaScript
|
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)
|
||
|
}
|