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