1 module.exports = Writer
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')
12 // Must do this *before* loading the child classes
13 inherits(Writer, Abstract)
15 Writer.dirmode = parseInt('0777', 8) & (~umask)
16 Writer.filemode = parseInt('0666', 8) & (~umask)
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')
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) {
29 if (typeof props === 'string') {
30 props = { path: props }
33 if (!props.path) self.error('Must provide a path', null, true)
36 // call fstream.Writer(dir) to get a DirWriter object, etc.
37 var type = getType(props)
38 var ClassType = Writer
45 ClassType = FileWriter
49 ClassType = LinkWriter
53 // Don't know yet what type to create, so we wrap in a proxy.
54 ClassType = ProxyWriter
58 if (!(self instanceof ClassType)) return new ClassType(props)
60 // now get down to business.
64 // props is what we want to set.
65 // set some convenience properties as well.
66 self.type = props.type
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
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, '\\')
81 self.basename = path.basename(props.path)
82 self.dirname = path.dirname(props.path)
83 self.linkpath = props.linkpath || null
85 props.parent = props.root = null
87 // console.error("\n\n\n%s setting size to", props.path, props.size)
88 self.size = props.size
90 if (typeof props.mode === 'string') {
91 props.mode = parseInt(props.mode, 8)
97 // buffer until ready, or while handling another entry
101 self.filter = typeof props.filter === 'function' ? props.filter : null
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.
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 () {
113 fs[self.props.follow ? 'stat' : 'lstat'](self._path, function (er) {
115 return self.warn('Cannot create ' + self._path + '\n' +
116 'Unsupported type: ' + self.type, 'ENOTSUP')
122 Writer.prototype._stat = function (current) {
124 var props = self.props
125 var stat = props.follow ? 'stat' : 'lstat'
126 var who = self._proxy || self
128 if (current) statCb(null, current)
129 else fs[stat](self._path, statCb)
131 function statCb (er, current) {
132 if (self.filter && !self.filter.call(who, who, current)) {
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) {
146 var currentType = getType(current)
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)
158 // otherwise, just handle in the app-specific way
159 // this creates a fs.WriteStream, or mkdir's, or whatever
164 function create (self) {
165 // console.error("W create", self._path, Writer.dirmode)
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)
173 // later on, we have to set the mode and owner for these
175 return self._create()
179 function endChmod (self, want, current, path, cb) {
180 var wantMode = want.mode
181 var chmod = want.follow || self.type !== 'SymbolicLink'
184 if (!fs[chmod]) return cb()
185 if (typeof wantMode !== 'number') return cb()
187 var curMode = current.mode & parseInt('0777', 8)
188 wantMode = wantMode & parseInt('0777', 8)
189 if (wantMode === curMode) return cb()
191 fs[chmod](path, wantMode, cb)
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()
201 if (current.uid === want.uid &&
202 current.gid === want.gid) return cb()
204 var chown = (self.props.follow || self.type !== 'SymbolicLink')
206 if (!fs[chown]) return cb()
208 if (typeof want.uid !== 'number') want.uid = current.uid
209 if (typeof want.gid !== 'number') want.gid = current.gid
211 fs[chown](path, want.uid, want.gid, cb)
214 function endUtimes (self, want, current, path, cb) {
215 if (!fs.utimes || process.platform === 'win32') return cb()
217 var utimes = (want.follow || self.type !== 'SymbolicLink')
218 ? 'utimes' : 'lutimes'
220 if (utimes === 'lutimes' && !fs[utimes]) {
224 if (!fs[utimes]) return cb()
226 var curA = current.atime
227 var curM = current.mtime
231 if (meA === undefined) meA = curA
232 if (meM === undefined) meM = curM
234 if (!isDate(meA)) meA = new Date(meA)
235 if (!isDate(meM)) meA = new Date(meM)
237 if (meA.getTime() === curA.getTime() &&
238 meM.getTime() === curM.getTime()) return cb()
240 fs[utimes](path, meA, meM, cb)
243 // XXX This function is beastly. Break it up!
244 Writer.prototype._finish = function () {
247 if (self._finishing) return
248 self._finishing = true
250 // console.error(" W Finish", self._path, self.size)
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.
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)
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)
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') {
283 self.end = self._finish = function () {}
285 } else return self.error(er)
287 setProps(self._old = current)
293 function setProps (current) {
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'))
300 function next (what) {
301 return function (er) {
302 // console.error(" W Finish", what, todo)
305 er.fstream_finish_call = what
306 return self.error(errState = er)
308 if (--todo > 0) return
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)
319 er.fstream_finish_call = 'setupMadeDir'
320 return self.error(er)
322 // all the props have been set, so we're completely done.
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)
336 endMadeDir_(self, d, function (er) {
337 if (er) return cb(er)
341 endMadeDir(self, d, cb)
345 function endMadeDir_ (self, p, cb) {
347 Object.keys(self.props).forEach(function (k) {
348 dirProps[k] = self.props[k]
350 // only make non-readable dirs if explicitly requested.
351 if (k === 'mode' && self.type !== 'Directory') {
352 dirProps[k] = dirProps[k] | parseInt('0111', 8)
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)
367 if (er) return cb(errState = er)
368 if (--todo === 0) return cb()
372 Writer.prototype.pipe = function () {
373 this.error("Can't pipe from writable stream")
376 Writer.prototype.add = function () {
377 this.error("Can't add to non-Directory type")
380 Writer.prototype.write = function () {
384 function objectToString (d) {
385 return Object.prototype.toString.call(d)
388 function isDate (d) {
389 return typeof d === 'object' && objectToString(d) === '[object Date]'