Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Take 2! So much stupid commenting, but working pretty completely at t…

…his point
  • Loading branch information...
commit c930b54fea9d78f73490445e5142c7c0f8affaba 1 parent ab3f7ae
@isaacs isaacs authored
View
2  examples/pipe.js
@@ -1,6 +1,6 @@
var fstream = require("../fstream.js")
var path = require("path")
-
+debugger
var r = fstream.Reader({ path: path.dirname(__dirname)
, filter: function () {
return !this.basename.match(/^\./) &&
View
13 examples/reader.js
@@ -7,12 +7,21 @@ var r = fstream.Reader({ path: path.dirname(__dirname)
}
})
+console.error(r instanceof fstream.Reader)
+console.error(r instanceof require("stream").Stream)
+console.error(r instanceof require("events").EventEmitter)
+console.error(r.on)
+
+r.on("stat", function () {
+ console.error("a %s !!!\t", r.type, r.path)
+})
+
r.on("entries", function (entries) {
- console.error("the entries", entries)
+ console.error("\t" + entries.join("\n\t"))
})
r.on("entry", function (entry) {
- console.error("a %s appears!", entry.type, entry.path)
+ console.error("a %s !!!\t", entry.type, entry.path)
})
r.on("end", function () {
View
31 lib/collect.js
@@ -1,6 +1,11 @@
module.exports = collect
function collect (stream) {
+ if (stream._collected) return
+
+ // console.error("collect", stream.path, new Error("trace").stack)
+
+ stream._collected = true
stream.pause()
stream.on("data", save)
@@ -19,9 +24,26 @@ function collect (stream) {
entryBuffer.push(e)
}
+ stream.on("proxy", proxyPause)
+ function proxyPause (p) {
+ p.pause()
+ }
+
+
// replace the pipe method with a new version that will
// unlock the buffered stuff.
stream.pipe = (function (orig) { return function (dest) {
+ if (!dest) {
+ // console.error(" <->-<->- no dest, just unblock")
+ stream.pipe = orig
+ stream.removeListener("entry", saveEntry)
+ stream.removeListener("data", save)
+ stream.removeListener("end", save)
+ stream.resume()
+ return
+ }
+
+ // console.error(" restore pipe", stream.path, "->", dest.path)
// let the entries flow through one at a time.
// Once they're all done, then we can resume completely.
@@ -29,20 +51,23 @@ function collect (stream) {
;(function unblockEntry () {
var entry = entryBuffer[e++]
if (!entry) return resume()
+ console.error(" unblockEntry", entry && entry.path)
entry.on("end", unblockEntry)
dest.addEntry(entry)
})()
function resume () {
- stream.removeListener("entry", saveEntry)
- stream.removeListener("data", save)
- stream.removeListener("end", save)
+ console.error(" resume", stream.path, buf)
buf.forEach(function (b) {
if (b) dest.write(b)
else dest.end()
})
+ stream.removeListener("entry", saveEntry)
+ stream.removeListener("data", save)
+ stream.removeListener("end", save)
+
stream.pipe = orig
stream.pipe(dest)
stream.resume()
View
171 lib/dir-reader.js
@@ -0,0 +1,171 @@
+// A thing that emits "entry" events with Reader objects
+// Pausing it causes it to stop emitting entry events, and also
+// pauses the current entry if there is one.
+
+module.exports = DirReader
+
+var fs = require("graceful-fs")
+ , fstream = require("../fstream.js")
+ , Reader = fstream.Reader
+ , inherits = require("inherits")
+ , mkdir = require("mkdirp")
+ , path = require("path")
+ , Reader = require("./reader.js")
+
+inherits(DirReader, Reader)
+
+function DirReader (props) {
+ var me = this
+ if (!(me instanceof DirReader)) throw new Error(
+ "DirReader must be called as constructor.")
+
+ // should already be established as a Directory type
+ if (props.type !== "Directory" || !props.Directory) {
+ throw new Error("Non-directory type "+ props.type)
+ }
+
+ me._entries = null
+ me._index = -1
+ me._paused = false
+ me._length = -1
+
+ // me._read = function () {
+ // process.nextTick(DirReader.prototype._read.bind(me))
+ // }
+ Reader.call(this, props)
+}
+
+DirReader.prototype._getEntries = function () {
+ var me = this
+ fs.readdir(me.path, function (er, entries) {
+ if (er) return me.emit("error", er)
+ me._entries = entries
+ me._length = entries.length
+ me._read()
+ })
+}
+
+// start walking the dir, and emit an "entry" event for each one.
+DirReader.prototype._read = function () {
+ var me = this
+
+ if (!me._entries) return me._getEntries()
+
+ console.error("\n\nDIRREADERREAD")
+ if (me._paused || me._currentEntry) {
+ console.error(" (oh, i see you're busy)")
+ return
+ }
+
+ me._index ++
+ console.error("\n\nDirReader %s[%d]=%j",
+ me.path, me._index, me._entries[me._index])
+
+ if (me._index >= me._length) {
+ return me.emit("end")
+ }
+
+ // ok, handle this one, then.
+
+ // save creating a proxy, by stat'ing the thing now.
+ var p = path.resolve(me.path, me._entries[me._index])
+ // set this to prevent trying to _read() again in the stat time.
+ me._currentEntry = p
+ console.error(" currentEntry=%s", p)
+ fs[ me.props.follow ? "stat" : "lstat" ](p, function (er, stat) {
+ if (er) return me.emit("error", er)
+
+ console.error(" statted!", me._currentEntry === p)
+
+ var entry = Reader({ path: p
+ , depth: me.depth + 1
+ , root: me.root || me
+ , parent: me
+ , follow: me.follow
+ , filter: me.filter
+ }, stat)
+
+ me._currentEntry = entry
+
+ console.error(" created child entry", !!entry, entry && entry.path, p)
+
+ // "entry" events are for direct entries in a specific dir.
+ // "child" events are for any and all children at all levels.
+ // This nomenclature is not completely final.
+
+ // entry.on("pause", function () {
+ // if (!me._paused) {
+ // console.error("child pause!")
+ // me.pause()
+ // }
+ // })
+
+ entry.on("resume", function () {
+ if (me._paused) {
+ console.error(" child resume!", p)
+ me.resume()
+ }
+ })
+
+ entry.on("ready", function () {
+ console.error(" ready", p)
+ me.emit("entry", entry)
+ me.emit("child", entry)
+ })
+
+ var ended = false
+ entry.on("end", onend)
+ entry.on("close", onend)
+ function onend () {
+ if (ended) return
+ ended = true
+ console.error(" ending fuck fuck fuck", entry && entry.path, p)
+ me.emit("childEnd", entry)
+ me.emit("entryEnd", entry)
+ me._currentEntry = null
+ me._read()
+ }
+
+ // proxy up some events.
+
+ entry.on("data", function (c) {
+ me.emit("data", c)
+ })
+
+ entry.on("error", function (er) {
+ me.emit("error", er)
+ })
+
+ entry.on("child", function (child) {
+ me.emit("child", child)
+ })
+
+ entry.on("childEnd", function (child) {
+ me.emit("childEnd", child)
+ })
+
+ })
+}
+
+DirReader.prototype.pause = function () {
+ var me = this
+ if (me._paused) return
+ // console.error(" pause", me.path, new Error().stack)
+ me._paused = true
+ if (me._currentEntry && me._currentEntry.pause) {
+ me._currentEntry.pause()
+ }
+ // me.emit("pause")
+}
+
+DirReader.prototype.resume = function () {
+ var me = this
+ // if (!me._paused) return
+ // console.error("\n\nresume", me.path)
+ me._paused = false
+ if (me._currentEntry && me._currentEntry.resume) {
+ // console.error(" resume currentEntry", me._currentEntry.path)
+ me._currentEntry.resume()
+ } else me._read()
+ me.emit("resume")
+}
View
45 lib/dir-writer.js
@@ -8,7 +8,7 @@ module.exports = DirWriter
var fs = require("graceful-fs")
, fstream = require("../fstream.js")
- , Writer = fstream.Writer
+ , Writer = require("./writer.js")
, inherits = require("inherits")
, mkdir = require("mkdirp")
, path = require("path")
@@ -23,7 +23,8 @@ function DirWriter (props) {
// should already be established as a Directory type
if (props.type !== "Directory" || !props.Directory) {
- throw new Error("Non-directory type "+ props.type)
+ throw new Error("Non-directory type "+ props.type + " " +
+ JSON.stringify(props))
}
Writer.call(this, props)
@@ -54,6 +55,9 @@ DirWriter.prototype.end = function () {
DirWriter.prototype.add = function (entry) {
var me = this
+
+ console.error("add %s <- %s", me.path, entry.path)
+
collect(entry)
if (!me._ready || me._currentEntry) {
me._buffer.push(entry)
@@ -68,15 +72,19 @@ DirWriter.prototype.add = function (entry) {
me._buffer.push(entry)
me._process()
- return false
+ return 0 === this._buffer.length
}
DirWriter.prototype._process = function () {
var me = this
+
+ console.error("process %j %s", me._processing, me.path)
+
if (me._processing) return
var entry = me._buffer.shift()
if (!entry) {
+ console.error("draining")
me.emit("drain")
if (me._ended) me._finish()
return
@@ -89,36 +97,43 @@ DirWriter.prototype._process = function () {
// don't allow recursive copying
var p = entry
do {
- if (p.path === me.path) {
+ console.error("recursion?\n %s\n %s", me.root.path, p.path)
+ if (p.path === me.root.path || p.path === me.path) {
+ console.error(" yes, recursion")
me._processing = false
+ if (entry._collected) entry.pipe()
return me._process()
}
} while (p = p.parent)
+ console.error("not recursive\n\n")
+
// chop off the entry's root dir, replace with ours
- var opts = { parent: me
- , root: me.root || me
- , type: entry.type
- , depth: me.depth + 1 }
+ var props = { parent: me
+ , root: me.root || me
+ , type: entry.type
+ , depth: me.depth + 1 }
var p = entry.path || entry.props.path
- , root = entry.root || entry.parent
- if (root) {
- p = p.substr(root.path.length + 1)
+ if (entry.parent) {
+ p = p.substr(entry.parent.path.length + 1)
}
- opts.path = path.join(me.path, path.join("/", p))
+ props.path = path.join(me.path, path.join("/", p))
// all the rest of the stuff, copy over from the source.
Object.keys(entry.props).forEach(function (k) {
- if (!opts.hasOwnProperty(k)) {
- opts[k] = entry.props[k]
+ if (!props.hasOwnProperty(k)) {
+ props[k] = entry.props[k]
}
})
// not sure at this point what kind of writer this is.
- var child = me._currentChild = new Writer(opts)
+ var child = me._currentChild = new Writer(props)
+ console.error("\n\n\n\n\npipetarget child", child.path)
child.on("ready", function () {
+ console.error("pipe", entry.path, "->", child.path)
entry.pipe(child)
+ entry.resume()
})
child.on("end", onend)
View
96 lib/file-reader.js
@@ -0,0 +1,96 @@
+// Basically just a wrapper around an fs.ReadStream
+
+module.exports = FileReader
+
+var fs = require("graceful-fs")
+ , fstream = require("../fstream.js")
+ , Reader = fstream.Reader
+ , inherits = require("inherits")
+ , mkdir = require("mkdirp")
+ , Reader = require("./reader.js")
+
+inherits(FileReader, Reader)
+
+function FileReader (props) {
+ var me = this
+ if (!(me instanceof FileReader)) throw new Error(
+ "FileReader must be called as constructor.")
+
+ // should already be established as a File type
+ // XXX Todo: preserve hardlinks by tracking dev+inode+nlink,
+ // with a HardLinkReader class.
+ if (!((props.type === "Link" && props.Link) ||
+ (props.type === "File" && props.File))) {
+ throw new Error("Non-file type "+ props.type)
+ }
+
+ me._buffer = []
+ me._bytesEmitted = 0
+ Reader.call(me, props)
+}
+
+FileReader.prototype._getStream = function () {
+ var me = this
+ , stream = me._stream = fs.createReadStream(me.path, me.props)
+
+ stream.on("open", me.emit.bind(me, "open"))
+
+ stream.on("data", function (c) {
+ me._bytesEmitted += c.length
+ // no point saving empty chunks
+ if (!c.length) return
+ else if (me._paused) me._buffer.push(c)
+ else me.emit("data", c)
+ })
+
+ stream.on("end", function () {
+ if (me._paused || me._buffer.length) {
+ me._buffer.push(null)
+ me._read()
+ } else me.emit("end")
+
+ if (me._bytesEmitted !== me.props.size) {
+ var er = new Error("Didn't get expected byte count\n"+
+ "expected: "+me.props.size + "\n" +
+ "actual: "+me._bytesEmitted)
+ er.path = er.file = me.path
+ me.emit("error", er)
+ }
+ })
+
+ stream.on("close", me.emit.bind(me, "close"))
+
+ me._read()
+}
+
+FileReader.prototype._read = function () {
+ var me = this
+ if (me._paused) return
+ if (!me._stream) return me._getStream()
+
+ // clear out the buffer, if there is one.
+ if (me._buffer.length) {
+ var buf = me._buffer
+ me._buffer.length = 0
+ buf.forEach(function (c) {
+ if (c === null) me.emit("end")
+ else me.emit("data", c)
+ })
+ }
+ // that's about all there is to it.
+}
+
+FileReader.prototype.pause = function () {
+ var me = this
+ me._paused = true
+ if (me._stream) me._stream.pause()
+}
+
+FileReader.prototype.resume = function () {
+ var me = this
+ // empty the buffer, if there is one.
+ // then resume the underlying impl stream.
+ me._paused = false
+ me._read()
+ if (me._stream) me._stream.resume()
+}
View
11 lib/file-writer.js
@@ -2,8 +2,7 @@ module.exports = FileWriter
var fs = require("graceful-fs")
, mkdir = require("mkdirp")
- , fstream = require("../fstream.js")
- , Writer = fstream.Writer
+ , Writer = require("./writer.js")
, inherits = require("inherits")
inherits(FileWriter, Writer)
@@ -13,8 +12,8 @@ function FileWriter (props) {
if (!(me instanceof FileWriter)) throw new Error(
"FileWriter must be called as constructor.")
- // should already be established as a Directory type
- if (props.type !== "File" || !props.Directory) {
+ // should already be established as a File type
+ if (props.type !== "File" || !props.File) {
throw new Error("Non-file type "+ props.type)
}
@@ -44,10 +43,10 @@ FileWriter.prototype._create = function () {
}
FileWriter.prototype.write = function (c) {
- me._bytesWritten += c.length
-
var me = this
, ret = me._stream.write(c)
+
+ me._bytesWritten += c.length
// allow 2 buffered writes, because otherwise there's just too
// much stop and go bs.
return ret || (me._stream._queue && me._stream._queue.length <= 2)
View
1  lib/get-type.js
@@ -13,6 +13,7 @@ function getType (st) {
, type
if (st.type && -1 !== types.indexOf(st.type)) {
+ st[st.type] = true
return st.type
}
View
53 lib/link-reader.js
@@ -0,0 +1,53 @@
+// Basically just a wrapper around an fs.readlink
+//
+// XXX: Enhance this to support the Link type, by keeping
+// a lookup table of {<dev+inode>:<path>}, so that hardlinks
+// can be preserved in tarballs.
+
+module.exports = LinkReader
+
+var fs = require("graceful-fs")
+ , fstream = require("../fstream.js")
+ , Reader = fstream.Reader
+ , inherits = require("inherits")
+ , mkdir = require("mkdirp")
+ , Reader = require("./reader.js")
+
+inherits(LinkReader, Reader)
+
+function LinkReader (props) {
+ var me = this
+ if (!(me instanceof LinkReader)) throw new Error(
+ "LinkReader must be called as constructor.")
+
+ if (!((props.type === "Link" && props.Link) ||
+ (props.type === "SymbolicLink" && props.SymbolicLink))) {
+ throw new Error("Non-link type "+ props.type)
+ }
+
+ Reader.call(me, props)
+}
+
+// When piping a LinkReader into a LinkWriter, we have to
+// already have the linkpath property set, so that has to
+// happen *before* the "ready" event, which means we need to
+// override the _stat method.
+LinkReader.prototype._stat = function (currentStat) {
+ var me = this
+ fs.readlink(me.path, function (er, linkpath) {
+ if (er) return me.emit("error", er)
+ me.linkpath = me.props.linkpath = linkpath
+ me.emit("linkpath", linkpath)
+ Reader._stat.call(me, currentStat)
+ })
+}
+
+LinkReader.prototype._read = function () {
+ if (this._paused) return
+ // basically just a no-op, since we got all the info we need
+ // from the _stat method
+ if (!me._ended) {
+ me.emit("end")
+ me._ended = true
+ }
+}
View
3  lib/link-writer.js
@@ -2,8 +2,7 @@
module.exports = LinkWriter
var fs = require("graceful-fs")
- , fstream = require("../fstream.js")
- , Writer = fstream.Writer
+ , Writer = require("./writer.js")
, inherits = require("inherits")
, collect = require("./collect.js")
View
80 lib/proxy-reader.js
@@ -0,0 +1,80 @@
+// A reader for when we don't yet know what kind of thing
+// the thing is.
+
+module.exports = ProxyReader
+
+var Reader = require("./reader.js")
+ , getType = require("./get-type.js")
+ , inherits = require("inherits")
+ , fs = require("graceful-fs")
+
+inherits(ProxyReader, Reader)
+
+function ProxyReader (props) {
+ var me = this
+ if (!(me instanceof ProxyReader)) throw new Error(
+ "ProxyReader must be called as constructor.")
+
+ me.props = props
+ me._buffer = []
+
+ Reader.call(me, props)
+}
+
+ProxyReader.prototype._stat = function () {
+ var me = this
+ , props = me.props
+ // stat the thing to see what the proxy should be.
+ , stat = props.follow ? "stat" : "lstat"
+
+ fs[stat](props.path, function (er, current) {
+ var type
+ if (er || !current) {
+ type = "File"
+ } else {
+ type = getType(current)
+ }
+
+ props[type] = true
+ props.type = me.type = type
+
+ me._old = current
+ me._addProxy(Reader(props, current))
+ })
+}
+
+ProxyReader.prototype._addProxy = function (proxy) {
+ var me = this
+ if (me._proxy) {
+ return me.emit("error", new Error("proxy already set"))
+ }
+
+ me._proxy = proxy
+ ; [ "ready"
+ , "error"
+ , "close"
+ , "data"
+ , "end"
+ , "linkpath"
+ , "entry"
+ , "warn"
+ ].forEach(function (ev) {
+ proxy.on(ev, me.emit.bind(me, ev))
+ })
+
+ me.emit("proxy", proxy)
+
+ var calls = me._buffer
+ me._buffer.length = 0
+ calls.forEach(function (c) {
+ proxy[c[0]].apply(proxy, c[1])
+ })
+}
+
+ProxyReader.prototype.pause = function () {
+ return this._proxy ? this._proxy.pause() : false
+}
+
+ProxyReader.prototype.resume = function (c) {
+ return this._proxy ? this._proxy.resume() : false
+}
View
91 lib/proxy-writer.js
@@ -0,0 +1,91 @@
+// A writer for when we don't know what kind of thing
+// the thing is. That is, it's not explicitly set,
+// so we're going to make it whatever the thing already
+// is, or "File"
+//
+// Until then, collect all events.
+
+module.exports = ProxyWriter
+
+var Writer = require("./writer.js")
+ , getType = require("./get-type.js")
+ , inherits = require("inherits")
+ , collect = require("./collect.js")
+
+inherits(ProxyWriter, Writer)
+
+function ProxyWriter (props) {
+ var me = this
+ if (!(me instanceof ProxyWriter)) throw new Error(
+ "ProxyWriter must be called as constructor.")
+
+ me.props = props
+
+ Writer.call(me, props)
+}
+
+ProxyWriter.prototype._stat = function () {
+ var me = this
+ , props = me.props
+ // stat the thing to see what the proxy should be.
+ , stat = props.follow ? "stat" : "lstat"
+
+ fs[stat](props.path, function (er, current) {
+ var type
+ if (er || !current) {
+ type = "File"
+ } else {
+ type = getType(current)
+ }
+
+ props[type] = true
+ props.type = me.type = type
+
+ me._old = current
+ me._addProxy(Writer(props, current))
+ })
+}
+
+ProxyWriter.prototype._addProxy = function (proxy) {
+ var me = this
+ if (me._proxy) {
+ return me.emit("error", new Error("proxy already set"))
+ }
+
+ me._proxy = proxy
+ ; [ "ready"
+ , "error"
+ , "close"
+ , "pipe"
+ , "drain"
+ , "warn"
+ ].forEach(function (ev) {
+ proxy.on(ev, me.emit.bind(me, ev))
+ })
+
+ me.emit("proxy", proxy)
+
+ var calls = me._buffer
+ me._buffer.length = 0
+ calls.forEach(function (c) {
+ proxy[c[0]].apply(proxy, c[1])
+ })
+}
+
+ProxyWriter.prototype.add = function (entry) {
+ collect(entry)
+
+ if (!this._proxy) {
+ this._buffer.push(["add", [entry]])
+ return false
+ }
+ return this._proxy.add(entry)
+}
+
+ProxyWriter.prototype.write = function (c) {
+ if (!this._proxy) {
+ this._buffer.push(["write", [c]])
+ return false
+ }
+ return this._proxy.write(c)
+}
View
254 lib/reader.js
@@ -4,146 +4,192 @@ module.exports = Reader
var fs = require("graceful-fs")
, Stream = require("stream").Stream
, inherits = require("inherits")
- , rimraf = require("rimraf")
- , mkdir = require("mkdirp")
, path = require("path")
- , proxyEvents = require("./proxy-events.js")
, getType = require("./get-type.js")
-function Reader (opts) {
- if (typeof opts === "string") opts = { path: opts }
+// Must do this *before* loading the child classes
+inherits(Reader, Stream)
+
+var DirReader = require("./dir-reader.js")
+ , FileReader = require("./file-reader.js")
+ , LinkReader = require("./link-reader.js")
+ , ProxyReader = require("./proxy-reader.js")
+
+function Reader (props, currentStat) {
var me = this
- if (!(me instanceof Reader)) return new Reader(opts)
+
+ if (typeof props === "string") {
+ props = { path: props }
+ }
+
+ if (!props.path) {
+ throw new Error("Must provide a path")
+ }
+
+ // polymorphism.
+ // call fstream.Reader(dir) to get a DirReader object, etc.
+ // Note that, unlike in the Writer case, ProxyReader is going
+ // to be the *normal* state of affairs, since we rarely know
+ // the type of a file prior to reading it.
+ var type = getType(props)
+ , ClassType = Reader
+
+ if (currentStat && !type) {
+ type = getType(currentStat)
+ props[type] = true
+ props.type = type
+ }
+
+ switch (type) {
+ case "Directory":
+ ClassType = DirReader
+ break
+
+ case "Link":
+ // XXX hard links are just files.
+ // However, it would be good to keep track of files' dev+inode
+ // and nlink values, and create a HardLinkReader that emits
+ // a linkpath value of the original copy, so that the tar
+ // writer can preserve them.
+ // ClassType = HardLinkReader
+ // break
+
+ case "File":
+ ClassType = FileReader
+ break
+
+ case "SymbolicLink":
+ ClassType = LinkReader
+ break
+
+ case null:
+ ClassType = ProxyReader
+ break
+ }
+
+ if (!(me instanceof ClassType)) {
+ me = new ClassType(props)
+ console.error("<Reader returning "+ClassType.name+">")
+ return me
+ }
+
Stream.call(me)
+
me.readable = true
me.writable = false
- if (!opts.path) {
- return me.emit("error", new Error("path option is required"))
- }
+ me.type = type
+ me.props = props
+ me.depth = props.depth || 0
+ me.parent = props.parent || null
+ me.root = props.root || (props.parent && props.parent.root) || me
+ me.path = props.path
+ me.basename = path.basename(props.path)
+ me.dirname = path.dirname(props.path)
+
+ me.size = props.size
+ me.filter = typeof props.filter === "function" ? props.filter : null
+
+ // start the ball rolling.
+ // this will stat the thing, and then call me._read()
+ // to start reading whatever it is.
+ me._stat(currentStat)
+}
- var stat = opts.follow ? "stat" : "lstat"
- me.filter = typeof opts.filter === "function" ? opts.filter : null
- me.depth = opts.depth || 0
- me.parent = opts.parent || null
- me.root = opts.root || (opts.parent && opts.parent.root) || me
- me.path = opts.path
- me.basename = path.basename(opts.path)
- me.dirname = path.dirname(opts.path)
+Reader.prototype._stat = function (currentStat) {
+ var me = this
+ , props = me.props
+ , stat = props.follow ? "stat" : "lstat"
- fs[stat](me.path, function (er, props) {
- if (er) return me.emit("error", er)
- me.props = props
- me.type = getType(props)
+ if (currentStat) process.nextTick(statCb.bind(null, null, currentStat))
+ else fs[stat](me.path, statCb)
- // if the filter doesn't pass, then just skip over this one.
- // still have to emit end so that dir-walking can move on.
- if (me.filter) {
- var ret = me.filter()
- if (!ret) return me.emit("end")
- }
- me.emit("stat", props)
+ function statCb (er, props_) {
+ if (er) return me.emit("error", er)
- // if it's a directory, then we'll be emitting "file" events.
- if (props.isDirectory()) {
- dirWalk(me)
- return
- } else if (props.isSymbolicLink()) {
- readlink(me)
- return
+ // special little thing for handling hardlinks.
+ if (props.nlink && props.nlink > 1) {
+ var k = props.dev + ":" + props.ino
+ if (!hardLinks[k]) hardLinks[k] = me.path
+ else {
+ // switch into hardlink mode.
+ me.type = me.props.type = "Link"
+ me.Link = me.props.Link = true
+ me.linkpath = me.props.linkpath = hardLinks[k]
+ // Setting __proto__ would arguably be the "correct"
+ // approach here, but that just seems too wrong.
+ me._stat = me._read = LinkReader.prototype._read
+ }
}
- // TODO: special handling for character devices, block devices,
- // FIFO's, sockets. They stat as size=0, but emit all sorts of data.
+ Object.keys(props_).forEach(function (k) {
+ props[k] = props_[k]
+ })
- // if it's got a zero-size, then just emit "end" right now
- if (props.size === 0) {
- me.emit("end")
- return
+ var type = getType(props)
+ if (me.type && me.type !== type) {
+ me.emit("error", new Error("Unexpected type: " + type))
}
- // otherwise, set up a readable stream for it.
- // TODO: Check that the bytesEmitted at the end matches
- // the expected size.
- var s = me._stream = fs.createReadStream(me.path, opts)
- proxyEvents(["open", "error", "data", "end", "close"], s, me)
- })
-}
-
-inherits(Reader, Stream)
+ // if the filter doesn't pass, then just skip over this one.
+ // still have to emit end so that dir-walking can move on.
+ if (me.filter) {
+ var ret = me.filter()
+ if (!ret) {
+ console.error(" didn't pass filter", me.path)
+ return me.emit("end")
+ }
+ }
-function readlink (me) {
- fs.readlink(me.path, function (er, lp) {
- if (er) return me.emit("error", er)
- me.emit("linkpath", lp)
- me.emit("end")
- })
-}
+ me.emit("ready", props)
-function dirWalk (me) {
- fs.readdir(me.path, function (er, entries) {
- if (er) return me.emit("error", er)
- me.emit("entries", entries)
-
- // now, go through all the entries and create Readers for them
- // Use the same filter, if one was provided, so that we don't
- // dive into directories that are excluded.
- var len = entries.length
- , f = 0
-
- ;(function walk () {
- if (f === len) return me.emit("end")
- var fst = Reader({ path: path.resolve(me.path, entries[f])
- , filter: me.filter
- , depth: me.depth + 1
- , root: me.root
- , parent: me })
-
- // treat the child entry as the underlying stream,
- // so that pause/resume is propagated properly
- me._stream = fst
-
- fst.on("error", function (e) {
- me.emit("error", e)
- })
-
- fst.on("stat", function (p) {
- me.emit("entry", fst)
- })
-
- // bubble up
- fst.on("entry", function (entry) {
- me.emit("entry", entry)
- })
-
- fst.on("end", function () {
- me._stream = null
- f ++
- walk()
- })
- })()
- })
+ // if it's a directory, then we'll be emitting "file" events.
+ me._read()
+ }
}
-
Reader.prototype.pipe = function (dest, opts) {
var me = this
if (typeof dest.add === "function") {
// piping to a multi-compatible, and we've got directory entries.
me.on("entry", function (entry) {
- if (false === dest.add(entry)) me.pause()
+ if (false === dest.add(entry)) {
+ me.pause()
+ }
})
}
+ // dest.on("drain", function () {
+ // console.error(" pipe drain", dest.path)
+ // console.error(" expect to resume", me.path)
+ // })
+
Stream.prototype.pipe.apply(this, arguments)
}
Reader.prototype.pause = function () {
- if (this._stream) this._stream.pause()
+ this._paused = true
}
Reader.prototype.resume = function () {
- if (this._stream) this._stream.resume()
+ this._paused = false
+ this._read()
+}
+
+Reader.prototype._read = function () {
+ me.warn("Cannot read unknown type: "+me.type)
+}
+
+Reader.prototype.warn = function (msg, code) {
+ var me = this
+ if (!me.listeners("warn")) {
+ if (code) console.error(code)
+ console.error(msg)
+ } else {
+ var er = new Error(msg)
+ er.errno = er.code = code
+ me.emit("warn", er)
+ }
}
View
55 lib/writer.js
@@ -9,16 +9,22 @@ var fs = require("graceful-fs")
, path = require("path")
, umask = process.umask()
, getType = require("./get-type.js")
- , DirWriter = require("./dir-writer.js")
- , LinkWriter = require("./link-writer.js")
- , FileWriter = require("./file-writer.js")
+// Must do this *before* loading the child classes
inherits(Writer, Stream)
Writer.dirmode = 0777 & (~umask)
Writer.filemode = 0666 & (~umask)
-function Writer (props) {
+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") {
@@ -27,23 +33,11 @@ function Writer (props) {
if (!props.path) throw new Error("Must provide a path")
-
// polymorphism.
// call fstream.Writer(dir) to get a DirWriter object, etc.
var type = getType(props)
, ClassType = Writer
- if (!type) {
- // XXX refactor so that a sync stat isn't necessary, ever.
- try {
- type = getType(fs.statSync(props.path))
- } catch (ex) {
- type = "File"
- }
- props.type = type
- props[type] = true
- }
-
switch (type) {
case "Directory":
ClassType = DirWriter
@@ -55,10 +49,16 @@ function Writer (props) {
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)
+ console.error("Writer<%s>", ClassType.name, props.path)
+
// now get down to business.
@@ -66,7 +66,7 @@ function Writer (props) {
// props is what we want to set.
// set some convenience properties as well.
- me.type = type
+ me.type = props.type
me.props = props
me.depth = props.depth || 0
me.clobber = false === props.clobber ? props.clobber : true
@@ -92,7 +92,7 @@ function Writer (props) {
// start the ball rolling.
// this checks what's there already, and then calls
// me._create() to call the impl-specific creation stuff.
- me._stat()
+ me._stat(current)
}
Writer.prototype.warn = function (msg, code) {
@@ -120,12 +120,15 @@ Writer.prototype._create = function () {
})
}
-Writer.prototype._stat = function () {
+Writer.prototype._stat = function (current) {
var me = this
, props = me.props
, stat = props.follow ? "stat" : "lstat"
- fs[stat](props.path, function (er, current) {
+ if (current) statCb(null, current)
+ else fs[stat](props.path, statCb)
+
+ function statCb (er, current) {
// 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)
@@ -142,7 +145,7 @@ Writer.prototype._stat = function () {
})
}
return create(me)
- })
+ }
}
function create (me) {
@@ -162,8 +165,13 @@ Writer.prototype._finish = function () {
var errState = null
var done = false
- if (me._old) setProps(me._old)
- else {
+ 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)
+ setProps(me._old)
+ } else {
var stat = me.props.follow ? "stat" : "lstat"
fs[stat](me.path, function (er, current) {
if (er) return me.emit("error", er)
@@ -218,6 +226,7 @@ Writer.prototype._finish = function () {
}
function next (er) {
+ // console.error(" FINISH HIM!", me.path)
if (errState) return
if (er) return me.emit("error", errState = er)
if (--todo > 0) return
Please sign in to comment.
Something went wrong with that request. Please try again.