reader.js 6.9 KB

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