dir-writer.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. // It is expected that, when .add() returns false, the consumer
  2. // of the DirWriter will pause until a "drain" event occurs. Note
  3. // that this is *almost always going to be the case*, unless the
  4. // thing being written is some sort of unsupported type, and thus
  5. // skipped over.
  6. module.exports = DirWriter
  7. var fs = require("graceful-fs")
  8. , fstream = require("../fstream.js")
  9. , Writer = require("./writer.js")
  10. , inherits = require("inherits")
  11. , mkdir = require("mkdirp")
  12. , path = require("path")
  13. , collect = require("./collect.js")
  14. inherits(DirWriter, Writer)
  15. function DirWriter (props) {
  16. var me = this
  17. if (!(me instanceof DirWriter)) me.error(
  18. "DirWriter must be called as constructor.", null, true)
  19. // should already be established as a Directory type
  20. if (props.type !== "Directory" || !props.Directory) {
  21. me.error("Non-directory type "+ props.type + " " +
  22. JSON.stringify(props), null, true)
  23. }
  24. Writer.call(this, props)
  25. }
  26. DirWriter.prototype._create = function () {
  27. var me = this
  28. mkdir(me._path, Writer.dirmode, function (er) {
  29. if (er) return me.error(er)
  30. // ready to start getting entries!
  31. me.ready = true
  32. me.emit("ready")
  33. me._process()
  34. })
  35. }
  36. // a DirWriter has an add(entry) method, but its .write() doesn't
  37. // do anything. Why a no-op rather than a throw? Because this
  38. // leaves open the door for writing directory metadata for
  39. // gnu/solaris style dumpdirs.
  40. DirWriter.prototype.write = function () {
  41. return true
  42. }
  43. DirWriter.prototype.end = function () {
  44. this._ended = true
  45. this._process()
  46. }
  47. DirWriter.prototype.add = function (entry) {
  48. var me = this
  49. // console.error("\tadd", entry._path, "->", me._path)
  50. collect(entry)
  51. if (!me.ready || me._currentEntry) {
  52. me._buffer.push(entry)
  53. return false
  54. }
  55. // create a new writer, and pipe the incoming entry into it.
  56. if (me._ended) {
  57. return me.error("add after end")
  58. }
  59. me._buffer.push(entry)
  60. me._process()
  61. return 0 === this._buffer.length
  62. }
  63. DirWriter.prototype._process = function () {
  64. var me = this
  65. // console.error("DW Process p=%j", me._processing, me.basename)
  66. if (me._processing) return
  67. var entry = me._buffer.shift()
  68. if (!entry) {
  69. // console.error("DW Drain")
  70. me.emit("drain")
  71. if (me._ended) me._finish()
  72. return
  73. }
  74. me._processing = true
  75. // console.error("DW Entry", entry._path)
  76. me.emit("entry", entry)
  77. // ok, add this entry
  78. //
  79. // don't allow recursive copying
  80. var p = entry
  81. do {
  82. var pp = p._path || p.path
  83. if (pp === me.root._path || pp === me._path ||
  84. (pp && pp.indexOf(me._path) === 0)) {
  85. // console.error("DW Exit (recursive)", entry.basename, me._path)
  86. me._processing = false
  87. if (entry._collected) entry.pipe()
  88. return me._process()
  89. }
  90. } while (p = p.parent)
  91. // console.error("DW not recursive")
  92. // chop off the entry's root dir, replace with ours
  93. var props = { parent: me
  94. , root: me.root || me
  95. , type: entry.type
  96. , depth: me.depth + 1 }
  97. var p = entry._path || entry.path || entry.props.path
  98. if (entry.parent) {
  99. p = p.substr(entry.parent._path.length + 1)
  100. }
  101. // get rid of any ../../ shenanigans
  102. props.path = path.join(me.path, path.join("/", p))
  103. // if i have a filter, the child should inherit it.
  104. props.filter = me.filter
  105. // all the rest of the stuff, copy over from the source.
  106. Object.keys(entry.props).forEach(function (k) {
  107. if (!props.hasOwnProperty(k)) {
  108. props[k] = entry.props[k]
  109. }
  110. })
  111. // not sure at this point what kind of writer this is.
  112. var child = me._currentChild = new Writer(props)
  113. child.on("ready", function () {
  114. // console.error("DW Child Ready", child.type, child._path)
  115. // console.error(" resuming", entry._path)
  116. entry.pipe(child)
  117. entry.resume()
  118. })
  119. // XXX Make this work in node.
  120. // Long filenames should not break stuff.
  121. child.on("error", function (er) {
  122. if (child._swallowErrors) {
  123. me.warn(er)
  124. child.emit("end")
  125. child.emit("close")
  126. } else {
  127. me.emit("error", er)
  128. }
  129. })
  130. // we fire _end internally *after* end, so that we don't move on
  131. // until any "end" listeners have had their chance to do stuff.
  132. child.on("close", onend)
  133. var ended = false
  134. function onend () {
  135. if (ended) return
  136. ended = true
  137. // console.error("* DW Child end", child.basename)
  138. me._currentChild = null
  139. me._processing = false
  140. me._process()
  141. }
  142. }