UNPKG

exceljs

Version:

Excel Workbook Manager - Read and Write xlsx and csv Files.

271 lines (241 loc) 8.1 kB
/** * Copyright (c) 2015 Guyon Roche * * 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. * */ 'use strict'; var events = require('events'); var Bluebird = require('bluebird'); var _ = require('lodash'); var utils = require('./utils'); // ============================================================================= // FlowControl - Used to slow down streaming to manageable speed // Implements a subset of Stream.Duplex: pipe() and write() var FlowControl = module.exports = function(options) { this.options = options = options || {}; // Buffer queue this.queue = []; // Consumer streams this.pipes = []; // Down-stream flow-control instances this.children = []; // Up-stream flow-control instances this.parent = options.parent; // Ensure we don't flush more than once at a time this.flushing = false; // determine timeout for flow control delays if (options.gc) { var gc = options.gc; if (gc.getTimeout) { this.getTimeout = gc.getTimeout; } else { // heap size below which we don't bother delaying var threshold = gc.threshold !== undefined ? gc.threshold : 150000000; // convert from heapsize to ms timeout var divisor = gc.divisor !== undefined ? gc.divisor : 500000; this.getTimeout = function() { var memory = process.memoryUsage(); var heapSize = memory.heapTotal; return (heapSize < threshold) ? 0 : Math.floor(heapSize / divisor); }; } } else { this.getTimeout = null; } }; utils.inherits(FlowControl, events.EventEmitter, { get name() { return [ 'FlowControl', this.parent ? 'Child' : 'Root', this.corked ? 'corked' : 'open' ].join(' '); }, get corked() { // We remain corked while we have children and at least one has data to consume return (this.children.length > 0) && _.some(this.children, function(child) { return child.queue && child.queue.length; }); }, get stem() { // the decision to stem the incoming data depends on whether the children are corked // and how many buffers we have backed up return this.corked || !this.queue || (this.queue.length > 2); }, _write: function(dst, data, encoding) { // Write to a single destination and return a promise // console.log(this.name + ' _write: ' + data.length); return new Bluebird(function(resolve, reject) { dst.write(data, encoding, function(error) { if (error) { // console.log(self.name + ' _write error: ' + error.message); reject(error); } else { // console.log(self.name + ' _write complete'); resolve(); } }); }); }, _pipe: function(chunk) { var self = this; // Write chunk to all pipes. A chunk with no data is the end var promises = []; this.pipes.forEach(function(pipe) { if (chunk.data) { if (pipe.sync) { pipe.stream.write(chunk.data, chunk.encoding); } else { promises.push(self._write(pipe.stream, chunk.data, chunk.encoding)); } } else { pipe.stream.end(); } }); if (!promises.length) { promises.push(Bluebird.resolve()); } return Bluebird.all(promises) .then(function() { try { chunk.callback(); } catch(e) { } }); }, _animate: function() { var count = 0; var seq = ['|', '/', '-', '\\']; //var cr = '\033[0G'; var cr = '\u001b[0G'; return setInterval(function() { process.stdout.write(seq[count++ % 4] + cr); }, 100); }, _delay: function() { // in certain situations it may be useful to delay processing (e.g. for GC) var timeout = this.getTimeout && this.getTimeout(); var self = this; if (timeout) { return new Bluebird(function(resolve, reject) { var anime = self._animate(); setTimeout(function() { clearInterval(anime); resolve(); }, timeout); }); } else { return Bluebird.resolve(); } }, _flush: function() { // If/while not corked and we have buffers to send, send them var self = this; // console.log(this.name + ' flush:' + (this.queue && this.queue.length)); if (this.queue && !this.flushing && !this.corked) { if (this.queue.length) { this.flushing = true; self._delay() .then(function() { return self._pipe(self.queue.shift()); }) .then(function() { setImmediate(function() { self.flushing = false; self._flush(); }); }); } if (!this.stem) { // Signal up-stream that we're ready for more data // console.log(self.name + ' emitting drain'); self.emit('drain'); } } }, write: function(data, encoding, callback) { // Called by up-stream pipe if (encoding instanceof Function) { callback = encoding; encoding = 'utf8'; } callback = callback || utils.nop; if (!this.queue) { throw new Error('Cannot write to stream after end'); } // Always queue chunks and then flush // console.log(this.name + ' write: ' + data.length); this.queue.push({ data: data, encoding: encoding, callback: callback }); this._flush(); // restrict further incoming data if we have backed up buffers or // the children are still busy var stemFlow = this.corked || (this.queue.length > 3); return !stemFlow; }, end: function(chunk, encoding, callback) { // Signal from up-stream var self = this; // console.log(this.name + 'End') this.queue.push({ callback: function() { self.queue = null; self.emit('finish'); } }); this._flush(); }, pipe: function(stream, options) { options = options || {}; // some streams don't call callbacks var sync = options.sync || false; this.pipes.push({ stream: stream, sync: sync }); }, unpipe: function(stream) { this.pipes = this.pipes.filter(function(pipe) { return pipe.stream !== stream; }); }, createChild: function() { // Create a down-stream flow-control var self = this; var options = _.extend({parent: this}, this.options); var child = new FlowControl(options); this.children.push(child); child.on('drain', function() { // a child is ready for more self._flush(); }); child.on('finish', function() { // One child has finished it's stream. Remove it and continue self.children = self.children.filter(function(item) { return item !== child; }); self._flush(); }); return child; } });