2012-10-03 00:44:50 +02:00
|
|
|
// Copyright Joyent, Inc. and other Node contributors.
|
|
|
|
//
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
// copy of this software and associated documentation files (the
|
|
|
|
// "Software"), to deal in the Software without restriction, including
|
|
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
|
|
// following conditions:
|
|
|
|
//
|
|
|
|
// The above copyright notice and this permission notice shall be included
|
|
|
|
// in all copies or substantial portions of the Software.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
|
|
|
// A bit simpler than readable streams.
|
|
|
|
// Implement an async ._write(chunk, cb), and it'll handle all
|
|
|
|
// the drain event emission and buffering.
|
|
|
|
|
|
|
|
module.exports = Writable
|
|
|
|
|
|
|
|
var util = require('util');
|
|
|
|
var Stream = require('stream');
|
2012-10-09 20:01:53 +02:00
|
|
|
var Duplex = require('_stream_duplex');
|
2012-10-03 00:44:50 +02:00
|
|
|
|
|
|
|
util.inherits(Writable, Stream);
|
|
|
|
|
|
|
|
function WritableState(options) {
|
|
|
|
options = options || {};
|
|
|
|
this.highWaterMark = options.highWaterMark || 16 * 1024;
|
2012-10-05 16:43:34 +02:00
|
|
|
this.highWaterMark = options.hasOwnProperty('highWaterMark') ?
|
|
|
|
options.highWaterMark : 16 * 1024;
|
|
|
|
this.lowWaterMark = options.hasOwnProperty('lowWaterMark') ?
|
|
|
|
options.lowWaterMark : 1024;
|
2012-10-03 00:44:50 +02:00
|
|
|
this.needDrain = false;
|
|
|
|
this.ended = false;
|
|
|
|
this.ending = false;
|
|
|
|
|
2012-10-09 20:01:53 +02:00
|
|
|
// should we decode strings into buffers before passing to _write?
|
|
|
|
// this is here so that some node-core streams can optimize string
|
|
|
|
// handling at a lower level.
|
|
|
|
this.decodeStrings = options.hasOwnProperty('decodeStrings') ?
|
|
|
|
options.decodeStrings : true;
|
|
|
|
|
2012-10-03 00:44:50 +02:00
|
|
|
// not an actual buffer we keep track of, but a measurement
|
|
|
|
// of how much we're waiting to get pushed to some underlying
|
|
|
|
// socket or file.
|
|
|
|
this.length = 0;
|
|
|
|
|
|
|
|
this.writing = false;
|
|
|
|
this.buffer = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
function Writable(options) {
|
2012-10-08 23:43:17 +02:00
|
|
|
// Writable ctor is applied to Duplexes, though they're not
|
|
|
|
// instanceof Writable, they're instanceof Readable.
|
|
|
|
if (!(this instanceof Writable) && !(this instanceof Duplex))
|
|
|
|
return new Writable(options);
|
|
|
|
|
2012-10-03 00:44:50 +02:00
|
|
|
this._writableState = new WritableState(options);
|
|
|
|
|
|
|
|
// legacy.
|
|
|
|
this.writable = true;
|
|
|
|
|
|
|
|
Stream.call(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Override this method for sync streams
|
|
|
|
// override the _write(chunk, cb) method for async streams
|
2012-10-10 02:31:29 +02:00
|
|
|
Writable.prototype.write = function(chunk, encoding, cb) {
|
2012-10-03 00:44:50 +02:00
|
|
|
var state = this._writableState;
|
|
|
|
if (state.ended) {
|
|
|
|
this.emit('error', new Error('write after end'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-10 02:31:29 +02:00
|
|
|
if (typeof encoding === 'function') {
|
|
|
|
cb = encoding;
|
|
|
|
encoding = null;
|
|
|
|
}
|
|
|
|
|
2012-10-03 00:44:50 +02:00
|
|
|
var l = chunk.length;
|
2012-10-09 20:01:53 +02:00
|
|
|
if (false === state.decodeStrings)
|
|
|
|
chunk = [chunk, encoding];
|
|
|
|
else if (typeof chunk === 'string' || encoding) {
|
|
|
|
chunk = new Buffer(chunk + '', encoding);
|
|
|
|
l = chunk.length;
|
|
|
|
}
|
|
|
|
|
2012-10-03 00:44:50 +02:00
|
|
|
state.length += l;
|
|
|
|
|
2012-10-05 16:45:03 +02:00
|
|
|
var ret = state.length < state.highWaterMark;
|
|
|
|
if (ret === false)
|
|
|
|
state.needDrain = true;
|
|
|
|
|
2012-10-10 02:31:29 +02:00
|
|
|
// if we're already writing something, then just put this
|
|
|
|
// in the queue, and wait our turn.
|
2012-10-03 00:44:50 +02:00
|
|
|
if (state.writing) {
|
2012-10-10 02:31:29 +02:00
|
|
|
state.buffer.push([chunk, cb]);
|
2012-10-03 00:44:50 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
state.writing = true;
|
2012-10-10 02:31:29 +02:00
|
|
|
var sync = true;
|
2012-10-09 20:01:53 +02:00
|
|
|
this._write(chunk, writecb.bind(this));
|
2012-10-10 02:31:29 +02:00
|
|
|
sync = false;
|
2012-10-09 20:01:53 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
function writecb(er) {
|
2012-10-03 00:44:50 +02:00
|
|
|
state.writing = false;
|
|
|
|
if (er) {
|
2012-10-10 02:31:29 +02:00
|
|
|
if (cb) {
|
|
|
|
if (sync)
|
|
|
|
process.nextTick(cb.bind(null, er));
|
|
|
|
else
|
|
|
|
cb(er);
|
|
|
|
} else
|
|
|
|
this.emit('error', er);
|
2012-10-03 00:44:50 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
state.length -= l;
|
|
|
|
|
2012-10-10 02:31:29 +02:00
|
|
|
if (cb) {
|
|
|
|
// don't call the cb until the next tick if we're in sync mode.
|
|
|
|
// also, defer if we're about to write some more right now.
|
|
|
|
if (sync || state.buffer.length)
|
|
|
|
process.nextTick(cb);
|
|
|
|
else
|
|
|
|
cb();
|
|
|
|
}
|
|
|
|
|
2012-10-03 00:44:50 +02:00
|
|
|
if (state.length === 0 && (state.ended || state.ending)) {
|
|
|
|
// emit 'finish' at the very end.
|
|
|
|
this.emit('finish');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there's something in the buffer waiting, then do that, too.
|
|
|
|
if (state.buffer.length) {
|
2012-10-10 02:31:29 +02:00
|
|
|
var chunkCb = state.buffer.shift();
|
|
|
|
chunk = chunkCb[0];
|
|
|
|
cb = chunkCb[1];
|
|
|
|
|
|
|
|
if (false === state.decodeStrings)
|
|
|
|
l = chunk[0].length;
|
|
|
|
else
|
|
|
|
l = chunk.length;
|
|
|
|
|
2012-10-03 00:44:50 +02:00
|
|
|
state.writing = true;
|
|
|
|
this._write(chunk, writecb.bind(this));
|
|
|
|
}
|
|
|
|
|
2012-10-05 16:43:34 +02:00
|
|
|
if (state.length <= state.lowWaterMark && state.needDrain) {
|
2012-10-03 00:44:50 +02:00
|
|
|
// Must force callback to be called on nextTick, so that we don't
|
|
|
|
// emit 'drain' before the write() consumer gets the 'false' return
|
|
|
|
// value, and has a chance to attach a 'drain' listener.
|
|
|
|
process.nextTick(function() {
|
|
|
|
if (!state.needDrain)
|
|
|
|
return;
|
|
|
|
state.needDrain = false;
|
|
|
|
this.emit('drain');
|
|
|
|
}.bind(this));
|
|
|
|
}
|
2012-10-09 20:01:53 +02:00
|
|
|
}
|
2012-10-03 00:44:50 +02:00
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
Writable.prototype._write = function(chunk, cb) {
|
|
|
|
process.nextTick(cb.bind(this, new Error('not implemented')));
|
|
|
|
};
|
|
|
|
|
|
|
|
Writable.prototype.end = function(chunk, encoding) {
|
|
|
|
var state = this._writableState;
|
|
|
|
state.ending = true;
|
|
|
|
if (chunk)
|
|
|
|
this.write(chunk, encoding);
|
|
|
|
else if (state.length === 0)
|
|
|
|
this.emit('finish');
|
|
|
|
state.ended = true;
|
|
|
|
};
|