dir-reader.js 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. // A thing that emits "entry" events with Reader objects
  2. // Pausing it causes it to stop emitting entry events, and also
  3. // pauses the current entry if there is one.
  4. module.exports = DirReader
  5. var fs = require('graceful-fs')
  6. var inherits = require('inherits')
  7. var path = require('path')
  8. var Reader = require('./reader.js')
  9. var assert = require('assert').ok
  10. inherits(DirReader, Reader)
  11. function DirReader (props) {
  12. var self = this
  13. if (!(self instanceof DirReader)) {
  14. throw new Error('DirReader must be called as constructor.')
  15. }
  16. // should already be established as a Directory type
  17. if (props.type !== 'Directory' || !props.Directory) {
  18. throw new Error('Non-directory type ' + props.type)
  19. }
  20. self.entries = null
  21. self._index = -1
  22. self._paused = false
  23. self._length = -1
  24. if (props.sort) {
  25. this.sort = props.sort
  26. }
  27. Reader.call(this, props)
  28. }
  29. DirReader.prototype._getEntries = function () {
  30. var self = this
  31. // race condition. might pause() before calling _getEntries,
  32. // and then resume, and try to get them a second time.
  33. if (self._gotEntries) return
  34. self._gotEntries = true
  35. fs.readdir(self._path, function (er, entries) {
  36. if (er) return self.error(er)
  37. self.entries = entries
  38. self.emit('entries', entries)
  39. if (self._paused) self.once('resume', processEntries)
  40. else processEntries()
  41. function processEntries () {
  42. self._length = self.entries.length
  43. if (typeof self.sort === 'function') {
  44. self.entries = self.entries.sort(self.sort.bind(self))
  45. }
  46. self._read()
  47. }
  48. })
  49. }
  50. // start walking the dir, and emit an "entry" event for each one.
  51. DirReader.prototype._read = function () {
  52. var self = this
  53. if (!self.entries) return self._getEntries()
  54. if (self._paused || self._currentEntry || self._aborted) {
  55. // console.error('DR paused=%j, current=%j, aborted=%j', self._paused, !!self._currentEntry, self._aborted)
  56. return
  57. }
  58. self._index++
  59. if (self._index >= self.entries.length) {
  60. if (!self._ended) {
  61. self._ended = true
  62. self.emit('end')
  63. self.emit('close')
  64. }
  65. return
  66. }
  67. // ok, handle this one, then.
  68. // save creating a proxy, by stat'ing the thing now.
  69. var p = path.resolve(self._path, self.entries[self._index])
  70. assert(p !== self._path)
  71. assert(self.entries[self._index])
  72. // set this to prevent trying to _read() again in the stat time.
  73. self._currentEntry = p
  74. fs[ self.props.follow ? 'stat' : 'lstat' ](p, function (er, stat) {
  75. if (er) return self.error(er)
  76. var who = self._proxy || self
  77. stat.path = p
  78. stat.basename = path.basename(p)
  79. stat.dirname = path.dirname(p)
  80. var childProps = self.getChildProps.call(who, stat)
  81. childProps.path = p
  82. childProps.basename = path.basename(p)
  83. childProps.dirname = path.dirname(p)
  84. var entry = Reader(childProps, stat)
  85. // console.error("DR Entry", p, stat.size)
  86. self._currentEntry = entry
  87. // "entry" events are for direct entries in a specific dir.
  88. // "child" events are for any and all children at all levels.
  89. // This nomenclature is not completely final.
  90. entry.on('pause', function (who) {
  91. if (!self._paused && !entry._disowned) {
  92. self.pause(who)
  93. }
  94. })
  95. entry.on('resume', function (who) {
  96. if (self._paused && !entry._disowned) {
  97. self.resume(who)
  98. }
  99. })
  100. entry.on('stat', function (props) {
  101. self.emit('_entryStat', entry, props)
  102. if (entry._aborted) return
  103. if (entry._paused) {
  104. entry.once('resume', function () {
  105. self.emit('entryStat', entry, props)
  106. })
  107. } else self.emit('entryStat', entry, props)
  108. })
  109. entry.on('ready', function EMITCHILD () {
  110. // console.error("DR emit child", entry._path)
  111. if (self._paused) {
  112. // console.error(" DR emit child - try again later")
  113. // pause the child, and emit the "entry" event once we drain.
  114. // console.error("DR pausing child entry")
  115. entry.pause(self)
  116. return self.once('resume', EMITCHILD)
  117. }
  118. // skip over sockets. they can't be piped around properly,
  119. // so there's really no sense even acknowledging them.
  120. // if someone really wants to see them, they can listen to
  121. // the "socket" events.
  122. if (entry.type === 'Socket') {
  123. self.emit('socket', entry)
  124. } else {
  125. self.emitEntry(entry)
  126. }
  127. })
  128. var ended = false
  129. entry.on('close', onend)
  130. entry.on('disown', onend)
  131. function onend () {
  132. if (ended) return
  133. ended = true
  134. self.emit('childEnd', entry)
  135. self.emit('entryEnd', entry)
  136. self._currentEntry = null
  137. if (!self._paused) {
  138. self._read()
  139. }
  140. }
  141. // XXX Remove this. Works in node as of 0.6.2 or so.
  142. // Long filenames should not break stuff.
  143. entry.on('error', function (er) {
  144. if (entry._swallowErrors) {
  145. self.warn(er)
  146. entry.emit('end')
  147. entry.emit('close')
  148. } else {
  149. self.emit('error', er)
  150. }
  151. })
  152. // proxy up some events.
  153. ;[
  154. 'child',
  155. 'childEnd',
  156. 'warn'
  157. ].forEach(function (ev) {
  158. entry.on(ev, self.emit.bind(self, ev))
  159. })
  160. })
  161. }
  162. DirReader.prototype.disown = function (entry) {
  163. entry.emit('beforeDisown')
  164. entry._disowned = true
  165. entry.parent = entry.root = null
  166. if (entry === this._currentEntry) {
  167. this._currentEntry = null
  168. }
  169. entry.emit('disown')
  170. }
  171. DirReader.prototype.getChildProps = function () {
  172. return {
  173. depth: this.depth + 1,
  174. root: this.root || this,
  175. parent: this,
  176. follow: this.follow,
  177. filter: this.filter,
  178. sort: this.props.sort,
  179. hardlinks: this.props.hardlinks
  180. }
  181. }
  182. DirReader.prototype.pause = function (who) {
  183. var self = this
  184. if (self._paused) return
  185. who = who || self
  186. self._paused = true
  187. if (self._currentEntry && self._currentEntry.pause) {
  188. self._currentEntry.pause(who)
  189. }
  190. self.emit('pause', who)
  191. }
  192. DirReader.prototype.resume = function (who) {
  193. var self = this
  194. if (!self._paused) return
  195. who = who || self
  196. self._paused = false
  197. // console.error('DR Emit Resume', self._path)
  198. self.emit('resume', who)
  199. if (self._paused) {
  200. // console.error('DR Re-paused', self._path)
  201. return
  202. }
  203. if (self._currentEntry) {
  204. if (self._currentEntry.resume) self._currentEntry.resume(who)
  205. } else self._read()
  206. }
  207. DirReader.prototype.emitEntry = function (entry) {
  208. this.emit('entry', entry)
  209. this.emit('child', entry)
  210. }