reader.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. module.exports = Reader
  2. var fs = require('graceful-fs')
  3. var Stream = require('stream').Stream
  4. var inherits = require('inherits')
  5. var path = require('path')
  6. var getType = require('./get-type.js')
  7. var hardLinks = Reader.hardLinks = {}
  8. var Abstract = require('./abstract.js')
  9. // Must do this *before* loading the child classes
  10. inherits(Reader, Abstract)
  11. var LinkReader = require('./link-reader.js')
  12. function Reader (props, currentStat) {
  13. var self = this
  14. if (!(self instanceof Reader)) return new Reader(props, currentStat)
  15. if (typeof props === 'string') {
  16. props = { path: props }
  17. }
  18. // polymorphism.
  19. // call fstream.Reader(dir) to get a DirReader object, etc.
  20. // Note that, unlike in the Writer case, ProxyReader is going
  21. // to be the *normal* state of affairs, since we rarely know
  22. // the type of a file prior to reading it.
  23. var type
  24. var ClassType
  25. if (props.type && typeof props.type === 'function') {
  26. type = props.type
  27. ClassType = type
  28. } else {
  29. type = getType(props)
  30. ClassType = Reader
  31. }
  32. if (currentStat && !type) {
  33. type = getType(currentStat)
  34. props[type] = true
  35. props.type = type
  36. }
  37. switch (type) {
  38. case 'Directory':
  39. ClassType = require('./dir-reader.js')
  40. break
  41. case 'Link':
  42. // XXX hard links are just files.
  43. // However, it would be good to keep track of files' dev+inode
  44. // and nlink values, and create a HardLinkReader that emits
  45. // a linkpath value of the original copy, so that the tar
  46. // writer can preserve them.
  47. // ClassType = HardLinkReader
  48. // break
  49. case 'File':
  50. ClassType = require('./file-reader.js')
  51. break
  52. case 'SymbolicLink':
  53. ClassType = LinkReader
  54. break
  55. case 'Socket':
  56. ClassType = require('./socket-reader.js')
  57. break
  58. case null:
  59. ClassType = require('./proxy-reader.js')
  60. break
  61. }
  62. if (!(self instanceof ClassType)) {
  63. return new ClassType(props)
  64. }
  65. Abstract.call(self)
  66. if (!props.path) {
  67. self.error('Must provide a path', null, true)
  68. }
  69. self.readable = true
  70. self.writable = false
  71. self.type = type
  72. self.props = props
  73. self.depth = props.depth = props.depth || 0
  74. self.parent = props.parent || null
  75. self.root = props.root || (props.parent && props.parent.root) || self
  76. self._path = self.path = path.resolve(props.path)
  77. if (process.platform === 'win32') {
  78. self.path = self._path = self.path.replace(/\?/g, '_')
  79. if (self._path.length >= 260) {
  80. // how DOES one create files on the moon?
  81. // if the path has spaces in it, then UNC will fail.
  82. self._swallowErrors = true
  83. // if (self._path.indexOf(" ") === -1) {
  84. self._path = '\\\\?\\' + self.path.replace(/\//g, '\\')
  85. // }
  86. }
  87. }
  88. self.basename = props.basename = path.basename(self.path)
  89. self.dirname = props.dirname = path.dirname(self.path)
  90. // these have served their purpose, and are now just noisy clutter
  91. props.parent = props.root = null
  92. // console.error("\n\n\n%s setting size to", props.path, props.size)
  93. self.size = props.size
  94. self.filter = typeof props.filter === 'function' ? props.filter : null
  95. if (props.sort === 'alpha') props.sort = alphasort
  96. // start the ball rolling.
  97. // this will stat the thing, and then call self._read()
  98. // to start reading whatever it is.
  99. // console.error("calling stat", props.path, currentStat)
  100. self._stat(currentStat)
  101. }
  102. function alphasort (a, b) {
  103. return a === b ? 0
  104. : a.toLowerCase() > b.toLowerCase() ? 1
  105. : a.toLowerCase() < b.toLowerCase() ? -1
  106. : a > b ? 1
  107. : -1
  108. }
  109. Reader.prototype._stat = function (currentStat) {
  110. var self = this
  111. var props = self.props
  112. var stat = props.follow ? 'stat' : 'lstat'
  113. // console.error("Reader._stat", self._path, currentStat)
  114. if (currentStat) process.nextTick(statCb.bind(null, null, currentStat))
  115. else fs[stat](self._path, statCb)
  116. function statCb (er, props_) {
  117. // console.error("Reader._stat, statCb", self._path, props_, props_.nlink)
  118. if (er) return self.error(er)
  119. Object.keys(props_).forEach(function (k) {
  120. props[k] = props_[k]
  121. })
  122. // if it's not the expected size, then abort here.
  123. if (undefined !== self.size && props.size !== self.size) {
  124. return self.error('incorrect size')
  125. }
  126. self.size = props.size
  127. var type = getType(props)
  128. var handleHardlinks = props.hardlinks !== false
  129. // special little thing for handling hardlinks.
  130. if (handleHardlinks && type !== 'Directory' && props.nlink && props.nlink > 1) {
  131. var k = props.dev + ':' + props.ino
  132. // console.error("Reader has nlink", self._path, k)
  133. if (hardLinks[k] === self._path || !hardLinks[k]) {
  134. hardLinks[k] = self._path
  135. } else {
  136. // switch into hardlink mode.
  137. type = self.type = self.props.type = 'Link'
  138. self.Link = self.props.Link = true
  139. self.linkpath = self.props.linkpath = hardLinks[k]
  140. // console.error("Hardlink detected, switching mode", self._path, self.linkpath)
  141. // Setting __proto__ would arguably be the "correct"
  142. // approach here, but that just seems too wrong.
  143. self._stat = self._read = LinkReader.prototype._read
  144. }
  145. }
  146. if (self.type && self.type !== type) {
  147. self.error('Unexpected type: ' + type)
  148. }
  149. // if the filter doesn't pass, then just skip over this one.
  150. // still have to emit end so that dir-walking can move on.
  151. if (self.filter) {
  152. var who = self._proxy || self
  153. // special handling for ProxyReaders
  154. if (!self.filter.call(who, who, props)) {
  155. if (!self._disowned) {
  156. self.abort()
  157. self.emit('end')
  158. self.emit('close')
  159. }
  160. return
  161. }
  162. }
  163. // last chance to abort or disown before the flow starts!
  164. var events = ['_stat', 'stat', 'ready']
  165. var e = 0
  166. ;(function go () {
  167. if (self._aborted) {
  168. self.emit('end')
  169. self.emit('close')
  170. return
  171. }
  172. if (self._paused && self.type !== 'Directory') {
  173. self.once('resume', go)
  174. return
  175. }
  176. var ev = events[e++]
  177. if (!ev) {
  178. return self._read()
  179. }
  180. self.emit(ev, props)
  181. go()
  182. })()
  183. }
  184. }
  185. Reader.prototype.pipe = function (dest) {
  186. var self = this
  187. if (typeof dest.add === 'function') {
  188. // piping to a multi-compatible, and we've got directory entries.
  189. self.on('entry', function (entry) {
  190. var ret = dest.add(entry)
  191. if (ret === false) {
  192. self.pause()
  193. }
  194. })
  195. }
  196. // console.error("R Pipe apply Stream Pipe")
  197. return Stream.prototype.pipe.apply(this, arguments)
  198. }
  199. Reader.prototype.pause = function (who) {
  200. this._paused = true
  201. who = who || this
  202. this.emit('pause', who)
  203. if (this._stream) this._stream.pause(who)
  204. }
  205. Reader.prototype.resume = function (who) {
  206. this._paused = false
  207. who = who || this
  208. this.emit('resume', who)
  209. if (this._stream) this._stream.resume(who)
  210. this._read()
  211. }
  212. Reader.prototype._read = function () {
  213. this.error('Cannot read unknown type: ' + this.type)
  214. }