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
JavaScript
////////////////////////////////////////////////////////////////
//
// 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
});
})();