UNPKG

logiblock-latent

Version:

Latent is a writable stream that buffers data until you decide where to write it by calling open(filename).

224 lines (200 loc) 5.52 kB
//////////////////////////////////////////////////////////////// // // More of Kuy's Node madness follows // module.exports = (function(){ var fs = require("fs"); // _.extend is probably my most used Underscore function, but my implementation is a little better-featured var _ = { extend: function(obj) { var recurse = arguments.callee; Array.prototype.slice.call(arguments, 1).forEach(function(source) { for(var prop in source) { if(source[prop] instanceof Array) obj[prop] = ((obj[prop] instanceof Array)? obj[prop] : []).concat(source[prop]); else if((typeof(obj[prop]) == "object") && (typeof(source[prop]) == "object")) recurse(obj[prop], source[prop]); else obj[prop] = source[prop]; } }); return(obj); }, }; //////////////////////////////////////////////////////////////// // // EventEmitter is an extendable clone of the Node object of the same name EventEmitter.prototype = { on: function(event, fn) { (this.__listeners[event] || (this.__listeners[event] = [])).push(fn); return(this); }, removeListener: function(event, fn) { var l = this.__listeners[event]; if(l) for(var i in l) if(l[i] == fn) l.splice(i, 1); return(this); }, removeAllListeners: function(event) { if(event == undefined) this.__listeners = {}; else delete this.__listeners[event]; return(this); }, emit: function(event) { var a = Array.prototype.slice.call(arguments); a.shift(); var l = this.__listeners[event]; if(l) for(var i in l) l[i].apply(undefined, a); return(this); } }; function EventEmitter() { this.removeAllListeners(); } //////////////////////////////////////////////////////////////// // // StreamTee is a WritableStream that "tees" incoming bytes to one or more // destination WritableStreams: simply create a new StreamTee(dest1, dest2, ...) StreamTee.prototype = _.extend(EventEmitter.prototype, { __streams: null, write: function() { for(var i = 0; i < this.__streams.length; i++) this.__streams[i].write.apply(this.__streams[i], arguments); }, end: function() { for(var i = 0; i < this.__streams.length; i++) this.__streams[i].end.apply(this.__streams[i], arguments); }, destroy: function() { for(var i = 0; i < this.__streams.length; i++) this.__streams[i].destroy.apply(this.__streams[i], arguments); }, destroySoon: function() { for(var i = 0; i < this.__streams.length; i++) this.__streams[i].destroySoon.apply(this.__streams[i], arguments); } }); function StreamTee() { EventEmitter.call(this); this.writable = true; this.__streams = Array.prototype.slice.apply(arguments); var ths = this; for(var i = 0; i < this.__streams.length; i++) { this.__streams[i].on("drain", function(e){ths.emit("drain", e);}); this.__streams[i].on("error", function(e){ths.emit("error", e);}); this.__streams[i].on("close", function(e){ths.emit("close", e);}); this.__streams[i].on("finish", function(e){ths.emit("finish", e);}); this.__streams[i].on("pipe", function(e){ths.emit("pipe", e);}); } } //////////////////////////////////////////////////////////////// // // LatentStream is a WritableStream for those cases where you don't yet have a destination but // wish to start accepting and buffering data in memory. Call latentStream.open(path) when you know the path LatentStream.prototype = _.extend(EventEmitter.prototype, { open: function(path, options) { this.__path = path; this.__ws = fs.createWriteStream(path, options); var ths = this; this.__bind("drain"); this.__bind("error"); this.__bind("close"); this.__bind("finish"); this.__bind("pipe"); this.__ws.once("finish", function() { ths.close(); }); //execute the queue for(var i = 0; i < ths.__queue.length; i++) this.__ws[this.__queue[i].n].apply(this.__ws, this.__queue[i].a); //invoke the buffered call this.__queue = undefined; return(this); }, close: function() { this.writable = false; this.__queue = []; //flush for(var ev in this.__bindings) this.__ws.removeListener(ev, this.__bindings[ev]); this.__bindings = {}; this.__ws.removeAllListeners(); this.__ws = null; return(this); }, __bind: function(ev, f) { var ths = this; if(f == undefined) f = function() { var a = Array.prototype.slice.call(arguments); a.unshift(ev); LatentStream.prototype.emit.apply(ths, a); }; this.__ws.on(ev, f); this.__bindings[ev] = f; } }, (function() { //instantiate a template member function for these methods var methods = ["write", "end", "destroy", "destroySoon"]; var proto = {}; //holds the instantiated methods //each method does the job of buffering its call in the WriteStream isn't active, // otherwise it passes the call through. for(var i = 0; i < methods.length; i++) proto[methods[i]] = (function(name) { return(function() { if(!this.__ws) this.__queue.push({n: name, a: arguments}); //call later else if(this.writable) this.__ws[name].apply(this.__ws, arguments); //call now }); })(methods[i]); return(proto); })()); //invoke this anonymous completion to create the dynamic inherited prototype. Whoa! function LatentStream() { EventEmitter.call(this); this.writable = true; var ths = this; this.__ws = null; this.__queue = []; this.__bindings = {}; this.__listeners = {}; this.__defineGetter__("bytesWritten", function(){return(ths.__ws? ths.__ws.bytesWritten : 0);}); } return( { Tee: StreamTee, Latent: LatentStream }); })();