writer.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. module.exports = Writer
  2. var fs = require('graceful-fs')
  3. var inherits = require('inherits')
  4. var rimraf = require('rimraf')
  5. var mkdir = require('mkdirp')
  6. var path = require('path')
  7. var umask = process.platform === 'win32' ? 0 : process.umask()
  8. var getType = require('./get-type.js')
  9. var Abstract = require('./abstract.js')
  10. // Must do this *before* loading the child classes
  11. inherits(Writer, Abstract)
  12. Writer.dirmode = parseInt('0777', 8) & (~umask)
  13. Writer.filemode = parseInt('0666', 8) & (~umask)
  14. var DirWriter = require('./dir-writer.js')
  15. var LinkWriter = require('./link-writer.js')
  16. var FileWriter = require('./file-writer.js')
  17. var ProxyWriter = require('./proxy-writer.js')
  18. // props is the desired state. current is optionally the current stat,
  19. // provided here so that subclasses can avoid statting the target
  20. // more than necessary.
  21. function Writer (props, current) {
  22. var self = this
  23. if (typeof props === 'string') {
  24. props = { path: props }
  25. }
  26. if (!props.path) self.error('Must provide a path', null, true)
  27. // polymorphism.
  28. // call fstream.Writer(dir) to get a DirWriter object, etc.
  29. var type = getType(props)
  30. var ClassType = Writer
  31. switch (type) {
  32. case 'Directory':
  33. ClassType = DirWriter
  34. break
  35. case 'File':
  36. ClassType = FileWriter
  37. break
  38. case 'Link':
  39. case 'SymbolicLink':
  40. ClassType = LinkWriter
  41. break
  42. case null:
  43. default:
  44. // Don't know yet what type to create, so we wrap in a proxy.
  45. ClassType = ProxyWriter
  46. break
  47. }
  48. if (!(self instanceof ClassType)) return new ClassType(props)
  49. // now get down to business.
  50. Abstract.call(self)
  51. // props is what we want to set.
  52. // set some convenience properties as well.
  53. self.type = props.type
  54. self.props = props
  55. self.depth = props.depth || 0
  56. self.clobber = props.clobber === false ? props.clobber : true
  57. self.parent = props.parent || null
  58. self.root = props.root || (props.parent && props.parent.root) || self
  59. self._path = self.path = path.resolve(props.path)
  60. if (process.platform === 'win32') {
  61. self.path = self._path = self.path.replace(/\?/g, '_')
  62. if (self._path.length >= 260) {
  63. self._swallowErrors = true
  64. self._path = '\\\\?\\' + self.path.replace(/\//g, '\\')
  65. }
  66. }
  67. self.basename = path.basename(props.path)
  68. self.dirname = path.dirname(props.path)
  69. self.linkpath = props.linkpath || null
  70. props.parent = props.root = null
  71. // console.error("\n\n\n%s setting size to", props.path, props.size)
  72. self.size = props.size
  73. if (typeof props.mode === 'string') {
  74. props.mode = parseInt(props.mode, 8)
  75. }
  76. self.readable = false
  77. self.writable = true
  78. // buffer until ready, or while handling another entry
  79. self._buffer = []
  80. self.ready = false
  81. self.filter = typeof props.filter === 'function' ? props.filter : null
  82. // start the ball rolling.
  83. // this checks what's there already, and then calls
  84. // self._create() to call the impl-specific creation stuff.
  85. self._stat(current)
  86. }
  87. // Calling this means that it's something we can't create.
  88. // Just assert that it's already there, otherwise raise a warning.
  89. Writer.prototype._create = function () {
  90. var self = this
  91. fs[self.props.follow ? 'stat' : 'lstat'](self._path, function (er) {
  92. if (er) {
  93. return self.warn('Cannot create ' + self._path + '\n' +
  94. 'Unsupported type: ' + self.type, 'ENOTSUP')
  95. }
  96. self._finish()
  97. })
  98. }
  99. Writer.prototype._stat = function (current) {
  100. var self = this
  101. var props = self.props
  102. var stat = props.follow ? 'stat' : 'lstat'
  103. var who = self._proxy || self
  104. if (current) statCb(null, current)
  105. else fs[stat](self._path, statCb)
  106. function statCb (er, current) {
  107. if (self.filter && !self.filter.call(who, who, current)) {
  108. self._aborted = true
  109. self.emit('end')
  110. self.emit('close')
  111. return
  112. }
  113. // if it's not there, great. We'll just create it.
  114. // if it is there, then we'll need to change whatever differs
  115. if (er || !current) {
  116. return create(self)
  117. }
  118. self._old = current
  119. var currentType = getType(current)
  120. // if it's a type change, then we need to clobber or error.
  121. // if it's not a type change, then let the impl take care of it.
  122. if (currentType !== self.type) {
  123. return rimraf(self._path, function (er) {
  124. if (er) return self.error(er)
  125. self._old = null
  126. create(self)
  127. })
  128. }
  129. // otherwise, just handle in the app-specific way
  130. // this creates a fs.WriteStream, or mkdir's, or whatever
  131. create(self)
  132. }
  133. }
  134. function create (self) {
  135. // console.error("W create", self._path, Writer.dirmode)
  136. // XXX Need to clobber non-dirs that are in the way,
  137. // unless { clobber: false } in the props.
  138. mkdir(path.dirname(self._path), Writer.dirmode, function (er, made) {
  139. // console.error("W created", path.dirname(self._path), er)
  140. if (er) return self.error(er)
  141. // later on, we have to set the mode and owner for these
  142. self._madeDir = made
  143. return self._create()
  144. })
  145. }
  146. function endChmod (self, want, current, path, cb) {
  147. var wantMode = want.mode
  148. var chmod = want.follow || self.type !== 'SymbolicLink'
  149. ? 'chmod' : 'lchmod'
  150. if (!fs[chmod]) return cb()
  151. if (typeof wantMode !== 'number') return cb()
  152. var curMode = current.mode & parseInt('0777', 8)
  153. wantMode = wantMode & parseInt('0777', 8)
  154. if (wantMode === curMode) return cb()
  155. fs[chmod](path, wantMode, cb)
  156. }
  157. function endChown (self, want, current, path, cb) {
  158. // Don't even try it unless root. Too easy to EPERM.
  159. if (process.platform === 'win32') return cb()
  160. if (!process.getuid || process.getuid() !== 0) return cb()
  161. if (typeof want.uid !== 'number' &&
  162. typeof want.gid !== 'number') return cb()
  163. if (current.uid === want.uid &&
  164. current.gid === want.gid) return cb()
  165. var chown = (self.props.follow || self.type !== 'SymbolicLink')
  166. ? 'chown' : 'lchown'
  167. if (!fs[chown]) return cb()
  168. if (typeof want.uid !== 'number') want.uid = current.uid
  169. if (typeof want.gid !== 'number') want.gid = current.gid
  170. fs[chown](path, want.uid, want.gid, cb)
  171. }
  172. function endUtimes (self, want, current, path, cb) {
  173. if (!fs.utimes || process.platform === 'win32') return cb()
  174. var utimes = (want.follow || self.type !== 'SymbolicLink')
  175. ? 'utimes' : 'lutimes'
  176. if (utimes === 'lutimes' && !fs[utimes]) {
  177. utimes = 'utimes'
  178. }
  179. if (!fs[utimes]) return cb()
  180. var curA = current.atime
  181. var curM = current.mtime
  182. var meA = want.atime
  183. var meM = want.mtime
  184. if (meA === undefined) meA = curA
  185. if (meM === undefined) meM = curM
  186. if (!isDate(meA)) meA = new Date(meA)
  187. if (!isDate(meM)) meA = new Date(meM)
  188. if (meA.getTime() === curA.getTime() &&
  189. meM.getTime() === curM.getTime()) return cb()
  190. fs[utimes](path, meA, meM, cb)
  191. }
  192. // XXX This function is beastly. Break it up!
  193. Writer.prototype._finish = function () {
  194. var self = this
  195. if (self._finishing) return
  196. self._finishing = true
  197. // console.error(" W Finish", self._path, self.size)
  198. // set up all the things.
  199. // At this point, we're already done writing whatever we've gotta write,
  200. // adding files to the dir, etc.
  201. var todo = 0
  202. var errState = null
  203. var done = false
  204. if (self._old) {
  205. // the times will almost *certainly* have changed.
  206. // adds the utimes syscall, but remove another stat.
  207. self._old.atime = new Date(0)
  208. self._old.mtime = new Date(0)
  209. // console.error(" W Finish Stale Stat", self._path, self.size)
  210. setProps(self._old)
  211. } else {
  212. var stat = self.props.follow ? 'stat' : 'lstat'
  213. // console.error(" W Finish Stating", self._path, self.size)
  214. fs[stat](self._path, function (er, current) {
  215. // console.error(" W Finish Stated", self._path, self.size, current)
  216. if (er) {
  217. // if we're in the process of writing out a
  218. // directory, it's very possible that the thing we're linking to
  219. // doesn't exist yet (especially if it was intended as a symlink),
  220. // so swallow ENOENT errors here and just soldier on.
  221. if (er.code === 'ENOENT' &&
  222. (self.type === 'Link' || self.type === 'SymbolicLink') &&
  223. process.platform === 'win32') {
  224. self.ready = true
  225. self.emit('ready')
  226. self.emit('end')
  227. self.emit('close')
  228. self.end = self._finish = function () {}
  229. return
  230. } else return self.error(er)
  231. }
  232. setProps(self._old = current)
  233. })
  234. }
  235. return
  236. function setProps (current) {
  237. todo += 3
  238. endChmod(self, self.props, current, self._path, next('chmod'))
  239. endChown(self, self.props, current, self._path, next('chown'))
  240. endUtimes(self, self.props, current, self._path, next('utimes'))
  241. }
  242. function next (what) {
  243. return function (er) {
  244. // console.error(" W Finish", what, todo)
  245. if (errState) return
  246. if (er) {
  247. er.fstream_finish_call = what
  248. return self.error(errState = er)
  249. }
  250. if (--todo > 0) return
  251. if (done) return
  252. done = true
  253. // we may still need to set the mode/etc. on some parent dirs
  254. // that were created previously. delay end/close until then.
  255. if (!self._madeDir) return end()
  256. else endMadeDir(self, self._path, end)
  257. function end (er) {
  258. if (er) {
  259. er.fstream_finish_call = 'setupMadeDir'
  260. return self.error(er)
  261. }
  262. // all the props have been set, so we're completely done.
  263. self.emit('end')
  264. self.emit('close')
  265. }
  266. }
  267. }
  268. }
  269. function endMadeDir (self, p, cb) {
  270. var made = self._madeDir
  271. // everything *between* made and path.dirname(self._path)
  272. // needs to be set up. Note that this may just be one dir.
  273. var d = path.dirname(p)
  274. endMadeDir_(self, d, function (er) {
  275. if (er) return cb(er)
  276. if (d === made) {
  277. return cb()
  278. }
  279. endMadeDir(self, d, cb)
  280. })
  281. }
  282. function endMadeDir_ (self, p, cb) {
  283. var dirProps = {}
  284. Object.keys(self.props).forEach(function (k) {
  285. dirProps[k] = self.props[k]
  286. // only make non-readable dirs if explicitly requested.
  287. if (k === 'mode' && self.type !== 'Directory') {
  288. dirProps[k] = dirProps[k] | parseInt('0111', 8)
  289. }
  290. })
  291. var todo = 3
  292. var errState = null
  293. fs.stat(p, function (er, current) {
  294. if (er) return cb(errState = er)
  295. endChmod(self, dirProps, current, p, next)
  296. endChown(self, dirProps, current, p, next)
  297. endUtimes(self, dirProps, current, p, next)
  298. })
  299. function next (er) {
  300. if (errState) return
  301. if (er) return cb(errState = er)
  302. if (--todo === 0) return cb()
  303. }
  304. }
  305. Writer.prototype.pipe = function () {
  306. this.error("Can't pipe from writable stream")
  307. }
  308. Writer.prototype.add = function () {
  309. this.error("Can't add to non-Directory type")
  310. }
  311. Writer.prototype.write = function () {
  312. return true
  313. }
  314. function objectToString (d) {
  315. return Object.prototype.toString.call(d)
  316. }
  317. function isDate (d) {
  318. return typeof d === 'object' && objectToString(d) === '[object Date]'
  319. }