1
0
mirror of https://github.com/Ylianst/MeshAgent synced 2025-12-06 00:13:33 +00:00
Files
MeshAgent/modules/zip-writer.js
Bryan Roe 86d074f56f 1. Added event moderation support
2. Added progress support to zip-writer
2020-08-18 14:14:51 -07:00

383 lines
12 KiB
JavaScript

/*
Copyright 2020 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var EOCDR = 101010256;
var CDR = 33639248;
var LFR = 67324752;
var DDR = 134695760;
var duplex = require('stream').Duplex;
function convertToMSDOSTime(datetimestring)
{
// '2020-06-17T20:58:29Z';
var datepart = datetimestring.split('T')[0].split('-');
dt = (parseInt(datepart[0]) - 1980) << 9;
dt |= (parseInt(datepart[1]) << 5);
dt |= (parseInt(datepart[2]));
var timepart = datetimestring.split('T')[1].split(':');
var tmp = (parseInt(timepart[0]) << 11);
tmp |= (parseInt(timepart[1]) << 5);
tmp |= (parseInt(timepart[2].split('Z')[0]) / 2);
return ({ date: dt, time: tmp });
}
function getBaseFolder(val)
{
var test = []
var D = process.platform == 'win32' ? '\\' : '/';
var base = '';
var tmp;
var i;
var ok;
for (i = 0; i < val.length; ++i)
{
if (process.platform == 'win32')
{
test.push(val[i].split('/').join('\\').split(D));
}
else
{
test.push(val[i].split(D));
}
}
if (val.length == 1)
{
if (test[0].length == 1) { return (''); }
test[0].pop();
return (test.join(D) + D);
}
while (true)
{
ok = true;
for (i = 0; i < val.length; ++i)
{
if (i == 0)
{
tmp = test[i].shift();
}
else
{
if (tmp != test[i].shift())
{
ok = false;
break;
}
}
}
if (ok)
{
base += (base == '' ? tmp : (D + tmp));
}
else
{
break;
}
}
return (base == '' ? '' : (base + D));
}
function finished(options)
{
console.info1('Writing Central Directory Records...');
var pos;
var CD;
var namelen;
this._pendingCDR = [];
this._CDRSize = 0;
// Write the Central Directory Headers
for(pos in options._localFileTable)
{
namelen = options._localFileTable[pos].readUInt32LE(26);
CD = Buffer.alloc(46 + namelen);
this._CDRSize += (46 + namelen);
options._localFileTable[pos].copy(CD, 46, 30, 30 + namelen);
options._localFileTable[pos].copy(CD, 16, 14, 14 + 12);
options._localFileTable[pos].copy(CD, 12, 10, 14);
CD.writeUInt32LE(CDR, 0); // Signature
CD.writeUInt16LE(20, 4); // Version
CD.writeUInt16LE(20, 6); // Minimum
CD.writeUInt16LE(0x08, 8); // General Purpose Bit Flag
CD.writeUInt16LE(8, 10); // Compression Method
CD.writeUInt16LE(namelen, 28); // File Name Length
CD.writeUInt16LE(1, 36); // Internal Attributes
CD.writeUInt32LE(32, 38); // External Attributes
CD.writeUInt32LE(parseInt(pos), 42); // Relative Offset
console.info1(' Record:');
console.info1(' FileName: ' + CD.slice(46).toString());
console.info1(' Compressed Size: ' + CD.readUInt32LE(20));
console.info1(' Uncompressed Size: ' + CD.readUInt32LE(24));
console.info1(' Last Modified Time: ' + CD.readUInt16LE(12));
console.info1(' Last Modified Date: ' + CD.readUInt16LE(14));
console.info1(' Local Record Offset: ' + CD.readUInt32LE(42));
this._pendingCDR.unshift(CD);
}
this._NumberOfCDR = this._pendingCDR.length;
this._CDRPosition = this._currentPosition;
this.write(this._pendingCDR.pop(), this._writeCDR);
}
function next(options)
{
if (!options) { options = this.options; }
// this = zip-stream
while (options.files.length > 0 && !require('fs').existsSync(options.files.peek())) { options.files.pop(); }
if (options.files.length == 0) { finished.call(this, options); return; }
var fstat = require('fs').statSync(options.files.peek());
this._currentFile = options.files.peek();
this._currentFD = require('fs').openSync(this._currentFile, require('fs').constants.O_RDONLY);
this._currentName = this._currentFile.substring(options._baseFolder.length);
this._currentFileLength = fstat.size;
this._currentFileReadBytes = 0;
this._currentCRC = 0;
this._compressedBytes = 0;
this._timestamp = convertToMSDOSTime(fstat.mtime);
if (!this._ubuffer) { this._ubuffer = Buffer.alloc(4096); }
var nameBuffer = Buffer.from(this._currentName);
this._header = Buffer.alloc(30 + nameBuffer.length);
this._header.writeUInt32LE(LFR, 0); // Signature
this._header.writeUInt16LE(0x08, 6); // General Purpose Bit Flag
this._header.writeUInt16LE(8, 8); // Compression Method
this._header.writeUInt16LE(this._timestamp.time, 10); // File Last Modification Time
this._header.writeUInt16LE(this._timestamp.date, 12); // File Last Modification Date
this._header.writeUInt32LE(this._currentFileLength, 22); // Uncompressed size
this._header.writeUInt16LE(nameBuffer.length, 26); // File name length
nameBuffer.copy(this._header, 30); // File name
options._localFileTable[this._currentPosition] = this._header;
this.write(this._header);
this._compressor = require('compressed-stream').createCompressor({ WBITS: -15 });
this._compressor.compressedBytes = this._currentPosition;
this._compressor.parent = this;
this._compressor.pipe(this, { end: false });
require('fs').read(this._currentFD, { buffer: this._ubuffer }, this._uncompressedReadSink);
}
function checkFiles(files)
{
var checked = [];
var tmp;
var s, j;
for(var i in files)
{
s = require('fs').statSync(files[i]);
if(s.isFile())
{
checked.push(files[i]);
}
else if (s.isDirectory())
{
tmp = require('fs').readdirSync(files[i]);
for (j in tmp)
{
tmp[j] = files[i] + (process.platform == 'win32' ? '\\' : '/') + tmp[j];
}
tmp = checkFiles(tmp);
for(j in tmp)
{
checked.push(tmp[j]);
}
}
}
return (checked);
}
function write(options)
{
if (!options.files || options.files.length == 0) { throw ('No file specified'); }
// Check if any folders were specified
options.files = checkFiles(options.files);
var ret = new duplex(
{
write: function (chunk, flush)
{
this._currentPosition += chunk.length;
if (this._pushOK)
{
this._pushOK = this.push(chunk);
if (this._pushOK)
{
flush();
this._flush = null;
}
else
{
this._flush = flush;
}
}
else
{
this._pendingData.push(chunk);
this._flush = flush;
}
},
final: function (flush)
{
if (this._pushOK)
{
this.push(null);
flush();
}
else
{
this._ended = true;
}
},
read(size)
{
this._pushOK = true;
while (this._pendingData.length > 0 && (this._pushOK = this.push(this._pendingData.shift())));
if (this._pushOK)
{
if (this._flush)
{
this._flush();
this._flush = null;
}
else
{
this.emit('drain');
}
}
}
});
require('events').EventEmitter.call(ret, true)
.createEvent('progress')
.createEvent('cancel')
.addMethod('cancel', function(callback)
{
this._cancel = true;
if(callback!=null) { this.once('cancel', callback); }
});
ret._currentPosition = 0;
ret._ObjectID = 'zip-writer.duplexStream';
ret.bufferMode = 1;
ret.options = options;
ret._pendingData = [];
ret._pushOK = false;
ret._ended = false;
ret._flush = null;
ret.pause();
options._localFileTable = {};
options._baseFolder = (options.basePath == null ? getBaseFolder(options.files) : options.basePath);
if (options._baseFolder != '')
{
if (!options._baseFolder.endsWith(process.platform == 'win32' ? '\\' : '/')) { options._baseFolder += (process.platform == 'win32' ? '\\' : '/'); }
}
ret._uncompressedReadSink = function _uncompressedReadSink(err, bytesRead, buffer)
{
var self = _uncompressedReadSink.self;
if(self._cancel)
{
self._compressor.end();
self._compressor.unpipe();
try
{
require('fs').closeSync(self._currentFD);
}
catch(e)
{}
self.options.files.length = 0;
self.emit('cancel');
self.end();
return;
}
if(bytesRead == 0)
{
// DONE
self._compressor.end();
self._compressor.unpipe();
self._header.writeUInt32LE(self._currentCRC, 14); // Uncompressed CRC
self._header.writeUInt32LE(self._currentPosition - self._compressor.compressedBytes, 18); // Compresed Size
self._header.writeUInt32LE(self._currentFileLength, 22); // Uncompressed Size
require('fs').closeSync(self._currentFD);
self.options.files.pop();
self.write(self._header.slice(14, 26), next);
return;
}
self._currentFileReadBytes += bytesRead;
var ratio = self._currentFileReadBytes / self._currentFileLength;
ratio = Math.floor(ratio * 100);
self.emit('progress', self._currentFile, ratio);
buffer = buffer.slice(0, bytesRead);
self._currentCRC = crc32(buffer, self._currentCRC); // Update CRC
self._compressor.write(buffer, function ()
{
require('fs').read(self._currentFD, { buffer: self._ubuffer }, self._uncompressedReadSink);
});
};
ret._uncompressedReadSink.self = ret;
ret._writeCDR = function _writeCDR(err, bytesRead, buffer)
{
if(this._pendingCDR.length>0)
{
this.write(this._pendingCDR.pop(), this._writeCDR);
return;
}
else
{
console.info1('Write End of Central Directory');
var ecdr = Buffer.alloc(22);
ecdr.writeUInt32LE(EOCDR, 0); // Signature
ecdr.writeUInt16LE(this._NumberOfCDR, 8); // Number of CD Records on this disk
ecdr.writeUInt16LE(this._NumberOfCDR, 10); // Total number of CD Records
ecdr.writeUInt32LE(this._CDRSize, 12); // Size of CD Records in bytes
ecdr.writeUInt32LE(this._CDRPosition, 16); // Offset start of CDR
this.write(ecdr, function ()
{
this.end();
});
}
};
next.call(ret, options);
return (ret);
}
module.exports = { write: write };