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.
		
		
		
		
		
			
		
			
				
					256 lines
				
				6.9 KiB
			
		
		
			
		
	
	
					256 lines
				
				6.9 KiB
			| 
											2 years ago
										 | 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) | ||
|  | } |