chunkstream.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. 'use strict';
  2. var util = require('util');
  3. var Stream = require('stream');
  4. var ChunkStream = module.exports = function() {
  5. Stream.call(this);
  6. this._buffers = [];
  7. this._buffered = 0;
  8. this._reads = [];
  9. this._paused = false;
  10. this._encoding = 'utf8';
  11. this.writable = true;
  12. };
  13. util.inherits(ChunkStream, Stream);
  14. ChunkStream.prototype.read = function(length, callback) {
  15. this._reads.push({
  16. length: Math.abs(length), // if length < 0 then at most this length
  17. allowLess: length < 0,
  18. func: callback
  19. });
  20. process.nextTick(function() {
  21. this._process();
  22. // its paused and there is not enought data then ask for more
  23. if (this._paused && this._reads.length > 0) {
  24. this._paused = false;
  25. this.emit('drain');
  26. }
  27. }.bind(this));
  28. };
  29. ChunkStream.prototype.write = function(data, encoding) {
  30. if (!this.writable) {
  31. this.emit('error', new Error('Stream not writable'));
  32. return false;
  33. }
  34. var dataBuffer;
  35. if (Buffer.isBuffer(data)) {
  36. dataBuffer = data;
  37. }
  38. else {
  39. dataBuffer = new Buffer(data, encoding || this._encoding);
  40. }
  41. this._buffers.push(dataBuffer);
  42. this._buffered += dataBuffer.length;
  43. this._process();
  44. // ok if there are no more read requests
  45. if (this._reads && this._reads.length === 0) {
  46. this._paused = true;
  47. }
  48. return this.writable && !this._paused;
  49. };
  50. ChunkStream.prototype.end = function(data, encoding) {
  51. if (data) {
  52. this.write(data, encoding);
  53. }
  54. this.writable = false;
  55. // already destroyed
  56. if (!this._buffers) {
  57. return;
  58. }
  59. // enqueue or handle end
  60. if (this._buffers.length === 0) {
  61. this._end();
  62. }
  63. else {
  64. this._buffers.push(null);
  65. this._process();
  66. }
  67. };
  68. ChunkStream.prototype.destroySoon = ChunkStream.prototype.end;
  69. ChunkStream.prototype._end = function() {
  70. if (this._reads.length > 0) {
  71. this.emit('error',
  72. new Error('Unexpected end of input')
  73. );
  74. }
  75. this.destroy();
  76. };
  77. ChunkStream.prototype.destroy = function() {
  78. if (!this._buffers) {
  79. return;
  80. }
  81. this.writable = false;
  82. this._reads = null;
  83. this._buffers = null;
  84. this.emit('close');
  85. };
  86. ChunkStream.prototype._processReadAllowingLess = function(read) {
  87. // ok there is any data so that we can satisfy this request
  88. this._reads.shift(); // == read
  89. // first we need to peek into first buffer
  90. var smallerBuf = this._buffers[0];
  91. // ok there is more data than we need
  92. if (smallerBuf.length > read.length) {
  93. this._buffered -= read.length;
  94. this._buffers[0] = smallerBuf.slice(read.length);
  95. read.func.call(this, smallerBuf.slice(0, read.length));
  96. }
  97. else {
  98. // ok this is less than maximum length so use it all
  99. this._buffered -= smallerBuf.length;
  100. this._buffers.shift(); // == smallerBuf
  101. read.func.call(this, smallerBuf);
  102. }
  103. };
  104. ChunkStream.prototype._processRead = function(read) {
  105. this._reads.shift(); // == read
  106. var pos = 0;
  107. var count = 0;
  108. var data = new Buffer(read.length);
  109. // create buffer for all data
  110. while (pos < read.length) {
  111. var buf = this._buffers[count++];
  112. var len = Math.min(buf.length, read.length - pos);
  113. buf.copy(data, pos, 0, len);
  114. pos += len;
  115. // last buffer wasn't used all so just slice it and leave
  116. if (len !== buf.length) {
  117. this._buffers[--count] = buf.slice(len);
  118. }
  119. }
  120. // remove all used buffers
  121. if (count > 0) {
  122. this._buffers.splice(0, count);
  123. }
  124. this._buffered -= read.length;
  125. read.func.call(this, data);
  126. };
  127. ChunkStream.prototype._process = function() {
  128. try {
  129. // as long as there is any data and read requests
  130. while (this._buffered > 0 && this._reads && this._reads.length > 0) {
  131. var read = this._reads[0];
  132. // read any data (but no more than length)
  133. if (read.allowLess) {
  134. this._processReadAllowingLess(read);
  135. }
  136. else if (this._buffered >= read.length) {
  137. // ok we can meet some expectations
  138. this._processRead(read);
  139. }
  140. else {
  141. // not enought data to satisfy first request in queue
  142. // so we need to wait for more
  143. break;
  144. }
  145. }
  146. if (this._buffers && !this.writable) {
  147. this._end();
  148. }
  149. }
  150. catch (ex) {
  151. this.emit('error', ex);
  152. }
  153. };