writer.js 10 KB

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