comment author = ip comment author info = origin of ip ip addr is jank ( uses php public server for ip get) fuck apache reverse proxy ip change thigy thing comment command started config stuff started planning logging system
390 lines
10 KiB
JavaScript
390 lines
10 KiB
JavaScript
|
|
module.exports = Writer
|
|
|
|
var fs = require("graceful-fs")
|
|
, inherits = require("inherits")
|
|
, rimraf = require("rimraf")
|
|
, mkdir = require("mkdirp")
|
|
, path = require("path")
|
|
, umask = process.platform === "win32" ? 0 : process.umask()
|
|
, getType = require("./get-type.js")
|
|
, Abstract = require("./abstract.js")
|
|
|
|
// Must do this *before* loading the child classes
|
|
inherits(Writer, Abstract)
|
|
|
|
Writer.dirmode = 0777 & (~umask)
|
|
Writer.filemode = 0666 & (~umask)
|
|
|
|
var DirWriter = require("./dir-writer.js")
|
|
, LinkWriter = require("./link-writer.js")
|
|
, FileWriter = require("./file-writer.js")
|
|
, ProxyWriter = require("./proxy-writer.js")
|
|
|
|
// props is the desired state. current is optionally the current stat,
|
|
// provided here so that subclasses can avoid statting the target
|
|
// more than necessary.
|
|
function Writer (props, current) {
|
|
var me = this
|
|
|
|
if (typeof props === "string") {
|
|
props = { path: props }
|
|
}
|
|
|
|
if (!props.path) me.error("Must provide a path", null, true)
|
|
|
|
// polymorphism.
|
|
// call fstream.Writer(dir) to get a DirWriter object, etc.
|
|
var type = getType(props)
|
|
, ClassType = Writer
|
|
|
|
switch (type) {
|
|
case "Directory":
|
|
ClassType = DirWriter
|
|
break
|
|
case "File":
|
|
ClassType = FileWriter
|
|
break
|
|
case "Link":
|
|
case "SymbolicLink":
|
|
ClassType = LinkWriter
|
|
break
|
|
case null:
|
|
// Don't know yet what type to create, so we wrap in a proxy.
|
|
ClassType = ProxyWriter
|
|
break
|
|
}
|
|
|
|
if (!(me instanceof ClassType)) return new ClassType(props)
|
|
|
|
// now get down to business.
|
|
|
|
Abstract.call(me)
|
|
|
|
// props is what we want to set.
|
|
// set some convenience properties as well.
|
|
me.type = props.type
|
|
me.props = props
|
|
me.depth = props.depth || 0
|
|
me.clobber = false === props.clobber ? props.clobber : true
|
|
me.parent = props.parent || null
|
|
me.root = props.root || (props.parent && props.parent.root) || me
|
|
|
|
me._path = me.path = path.resolve(props.path)
|
|
if (process.platform === "win32") {
|
|
me.path = me._path = me.path.replace(/\?/g, "_")
|
|
if (me._path.length >= 260) {
|
|
me._swallowErrors = true
|
|
me._path = "\\\\?\\" + me.path.replace(/\//g, "\\")
|
|
}
|
|
}
|
|
me.basename = path.basename(props.path)
|
|
me.dirname = path.dirname(props.path)
|
|
me.linkpath = props.linkpath || null
|
|
|
|
props.parent = props.root = null
|
|
|
|
// console.error("\n\n\n%s setting size to", props.path, props.size)
|
|
me.size = props.size
|
|
|
|
if (typeof props.mode === "string") {
|
|
props.mode = parseInt(props.mode, 8)
|
|
}
|
|
|
|
me.readable = false
|
|
me.writable = true
|
|
|
|
// buffer until ready, or while handling another entry
|
|
me._buffer = []
|
|
me.ready = false
|
|
|
|
me.filter = typeof props.filter === "function" ? props.filter: null
|
|
|
|
// start the ball rolling.
|
|
// this checks what's there already, and then calls
|
|
// me._create() to call the impl-specific creation stuff.
|
|
me._stat(current)
|
|
}
|
|
|
|
// Calling this means that it's something we can't create.
|
|
// Just assert that it's already there, otherwise raise a warning.
|
|
Writer.prototype._create = function () {
|
|
var me = this
|
|
fs[me.props.follow ? "stat" : "lstat"](me._path, function (er, current) {
|
|
if (er) {
|
|
return me.warn("Cannot create " + me._path + "\n" +
|
|
"Unsupported type: "+me.type, "ENOTSUP")
|
|
}
|
|
me._finish()
|
|
})
|
|
}
|
|
|
|
Writer.prototype._stat = function (current) {
|
|
var me = this
|
|
, props = me.props
|
|
, stat = props.follow ? "stat" : "lstat"
|
|
, who = me._proxy || me
|
|
|
|
if (current) statCb(null, current)
|
|
else fs[stat](me._path, statCb)
|
|
|
|
function statCb (er, current) {
|
|
if (me.filter && !me.filter.call(who, who, current)) {
|
|
me._aborted = true
|
|
me.emit("end")
|
|
me.emit("close")
|
|
return
|
|
}
|
|
|
|
// if it's not there, great. We'll just create it.
|
|
// if it is there, then we'll need to change whatever differs
|
|
if (er || !current) {
|
|
return create(me)
|
|
}
|
|
|
|
me._old = current
|
|
var currentType = getType(current)
|
|
|
|
// if it's a type change, then we need to clobber or error.
|
|
// if it's not a type change, then let the impl take care of it.
|
|
if (currentType !== me.type) {
|
|
return rimraf(me._path, function (er) {
|
|
if (er) return me.error(er)
|
|
me._old = null
|
|
create(me)
|
|
})
|
|
}
|
|
|
|
// otherwise, just handle in the app-specific way
|
|
// this creates a fs.WriteStream, or mkdir's, or whatever
|
|
create(me)
|
|
}
|
|
}
|
|
|
|
function create (me) {
|
|
// console.error("W create", me._path, Writer.dirmode)
|
|
|
|
// XXX Need to clobber non-dirs that are in the way,
|
|
// unless { clobber: false } in the props.
|
|
mkdir(path.dirname(me._path), Writer.dirmode, function (er, made) {
|
|
// console.error("W created", path.dirname(me._path), er)
|
|
if (er) return me.error(er)
|
|
|
|
// later on, we have to set the mode and owner for these
|
|
me._madeDir = made
|
|
return me._create()
|
|
})
|
|
}
|
|
|
|
function endChmod (me, want, current, path, cb) {
|
|
var wantMode = want.mode
|
|
, chmod = want.follow || me.type !== "SymbolicLink"
|
|
? "chmod" : "lchmod"
|
|
|
|
if (!fs[chmod]) return cb()
|
|
if (typeof wantMode !== "number") return cb()
|
|
|
|
var curMode = current.mode & 0777
|
|
wantMode = wantMode & 0777
|
|
if (wantMode === curMode) return cb()
|
|
|
|
fs[chmod](path, wantMode, cb)
|
|
}
|
|
|
|
|
|
function endChown (me, want, current, path, cb) {
|
|
// Don't even try it unless root. Too easy to EPERM.
|
|
if (process.platform === "win32") return cb()
|
|
if (!process.getuid || !process.getuid() === 0) return cb()
|
|
if (typeof want.uid !== "number" &&
|
|
typeof want.gid !== "number" ) return cb()
|
|
|
|
if (current.uid === want.uid &&
|
|
current.gid === want.gid) return cb()
|
|
|
|
var chown = (me.props.follow || me.type !== "SymbolicLink")
|
|
? "chown" : "lchown"
|
|
if (!fs[chown]) return cb()
|
|
|
|
if (typeof want.uid !== "number") want.uid = current.uid
|
|
if (typeof want.gid !== "number") want.gid = current.gid
|
|
|
|
fs[chown](path, want.uid, want.gid, cb)
|
|
}
|
|
|
|
function endUtimes (me, want, current, path, cb) {
|
|
if (!fs.utimes || process.platform === "win32") return cb()
|
|
|
|
var utimes = (want.follow || me.type !== "SymbolicLink")
|
|
? "utimes" : "lutimes"
|
|
|
|
if (utimes === "lutimes" && !fs[utimes]) {
|
|
utimes = "utimes"
|
|
}
|
|
|
|
if (!fs[utimes]) return cb()
|
|
|
|
var curA = current.atime
|
|
, curM = current.mtime
|
|
, meA = want.atime
|
|
, meM = want.mtime
|
|
|
|
if (meA === undefined) meA = curA
|
|
if (meM === undefined) meM = curM
|
|
|
|
if (!isDate(meA)) meA = new Date(meA)
|
|
if (!isDate(meM)) meA = new Date(meM)
|
|
|
|
if (meA.getTime() === curA.getTime() &&
|
|
meM.getTime() === curM.getTime()) return cb()
|
|
|
|
fs[utimes](path, meA, meM, cb)
|
|
}
|
|
|
|
|
|
// XXX This function is beastly. Break it up!
|
|
Writer.prototype._finish = function () {
|
|
var me = this
|
|
|
|
// console.error(" W Finish", me._path, me.size)
|
|
|
|
// set up all the things.
|
|
// At this point, we're already done writing whatever we've gotta write,
|
|
// adding files to the dir, etc.
|
|
var todo = 0
|
|
var errState = null
|
|
var done = false
|
|
|
|
if (me._old) {
|
|
// the times will almost *certainly* have changed.
|
|
// adds the utimes syscall, but remove another stat.
|
|
me._old.atime = new Date(0)
|
|
me._old.mtime = new Date(0)
|
|
// console.error(" W Finish Stale Stat", me._path, me.size)
|
|
setProps(me._old)
|
|
} else {
|
|
var stat = me.props.follow ? "stat" : "lstat"
|
|
// console.error(" W Finish Stating", me._path, me.size)
|
|
fs[stat](me._path, function (er, current) {
|
|
// console.error(" W Finish Stated", me._path, me.size, current)
|
|
if (er) {
|
|
// if we're in the process of writing out a
|
|
// directory, it's very possible that the thing we're linking to
|
|
// doesn't exist yet (especially if it was intended as a symlink),
|
|
// so swallow ENOENT errors here and just soldier on.
|
|
if (er.code === "ENOENT" &&
|
|
(me.type === "Link" || me.type === "SymbolicLink") &&
|
|
process.platform === "win32") {
|
|
me.ready = true
|
|
me.emit("ready")
|
|
me.emit("end")
|
|
me.emit("close")
|
|
me.end = me._finish = function () {}
|
|
return
|
|
} else return me.error(er)
|
|
}
|
|
setProps(me._old = current)
|
|
})
|
|
}
|
|
|
|
return
|
|
|
|
function setProps (current) {
|
|
todo += 3
|
|
endChmod(me, me.props, current, me._path, next("chmod"))
|
|
endChown(me, me.props, current, me._path, next("chown"))
|
|
endUtimes(me, me.props, current, me._path, next("utimes"))
|
|
}
|
|
|
|
function next (what) {
|
|
return function (er) {
|
|
// console.error(" W Finish", what, todo)
|
|
if (errState) return
|
|
if (er) {
|
|
er.fstream_finish_call = what
|
|
return me.error(errState = er)
|
|
}
|
|
if (--todo > 0) return
|
|
if (done) return
|
|
done = true
|
|
|
|
// we may still need to set the mode/etc. on some parent dirs
|
|
// that were created previously. delay end/close until then.
|
|
if (!me._madeDir) return end()
|
|
else endMadeDir(me, me._path, end)
|
|
|
|
function end (er) {
|
|
if (er) {
|
|
er.fstream_finish_call = "setupMadeDir"
|
|
return me.error(er)
|
|
}
|
|
// all the props have been set, so we're completely done.
|
|
me.emit("end")
|
|
me.emit("close")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function endMadeDir (me, p, cb) {
|
|
var made = me._madeDir
|
|
// everything *between* made and path.dirname(me._path)
|
|
// needs to be set up. Note that this may just be one dir.
|
|
var d = path.dirname(p)
|
|
|
|
endMadeDir_(me, d, function (er) {
|
|
if (er) return cb(er)
|
|
if (d === made) {
|
|
return cb()
|
|
}
|
|
endMadeDir(me, d, cb)
|
|
})
|
|
}
|
|
|
|
function endMadeDir_ (me, p, cb) {
|
|
var dirProps = {}
|
|
Object.keys(me.props).forEach(function (k) {
|
|
dirProps[k] = me.props[k]
|
|
|
|
// only make non-readable dirs if explicitly requested.
|
|
if (k === "mode" && me.type !== "Directory") {
|
|
dirProps[k] = dirProps[k] | 0111
|
|
}
|
|
})
|
|
|
|
var todo = 3
|
|
, errState = null
|
|
fs.stat(p, function (er, current) {
|
|
if (er) return cb(errState = er)
|
|
endChmod(me, dirProps, current, p, next)
|
|
endChown(me, dirProps, current, p, next)
|
|
endUtimes(me, dirProps, current, p, next)
|
|
})
|
|
|
|
function next (er) {
|
|
if (errState) return
|
|
if (er) return cb(errState = er)
|
|
if (-- todo === 0) return cb()
|
|
}
|
|
}
|
|
|
|
Writer.prototype.pipe = function () {
|
|
this.error("Can't pipe from writable stream")
|
|
}
|
|
|
|
Writer.prototype.add = function () {
|
|
this.error("Cannot add to non-Directory type")
|
|
}
|
|
|
|
Writer.prototype.write = function () {
|
|
return true
|
|
}
|
|
|
|
function objectToString (d) {
|
|
return Object.prototype.toString.call(d)
|
|
}
|
|
|
|
function isDate(d) {
|
|
return typeof d === 'object' && objectToString(d) === '[object Date]';
|
|
}
|