123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- const fs = require('fs-extra');
- const zlib = require('zlib');
- const path = require('path');
- const unbzip2Stream = require('unbzip2-stream');
- const tar = require('tar-fs');
- const ZipStreamer = require('./ZipStreamer');
- const appLogger = new (require('./AppLogger'))();//singleton
- const utils = require('./utils');
- const FileDetector = require('./FileDetector');
- class FileDecompressor {
- constructor(limitFileSize = 0) {
- this.detector = new FileDetector();
- this.limitFileSize = limitFileSize;
- }
- async decompressNested(filename, outputDir) {
- await fs.ensureDir(outputDir);
-
- const fileType = await this.detector.detectFile(filename);
- let result = {
- sourceFile: filename,
- sourceFileType: fileType,
- selectedFile: filename,
- filesDir: outputDir,
- files: []
- };
- if (!fileType || !(fileType.ext == 'zip' || fileType.ext == 'bz2' || fileType.ext == 'gz' || fileType.ext == 'tar')) {
- return result;
- }
- result.files = await this.decompressTarZZ(fileType.ext, filename, outputDir);
- let sel = filename;
- let max = 0;
- if (result.files.length) {
- //ищем файл с максимальным размером
- for (let file of result.files) {
- if (file.size > max) {
- sel = `${outputDir}/${file.path}`;
- max = file.size;
- }
- }
- }
- result.selectedFile = sel;
- if (sel != filename) {
- result.nesting = await this.decompressNested(sel, `${outputDir}/${utils.randomHexString(10)}`);
- }
- return result;
- }
- async unpack(filename, outputDir) {
- const fileType = await this.detector.detectFile(filename);
- if (!fileType)
- throw new Error('Не удалось определить формат файла');
- return await this.decompress(fileType.ext, filename, outputDir);
- }
- async unpackTarZZ(filename, outputDir) {
- const fileType = await this.detector.detectFile(filename);
- if (!fileType)
- throw new Error('Не удалось определить формат файла');
- return await this.decompressTarZZ(fileType.ext, filename, outputDir);
- }
- async decompressTarZZ(fileExt, filename, outputDir) {
- const files = await this.decompress(fileExt, filename, outputDir);
- if (fileExt == 'tar' || files.length != 1)
- return files;
- const tarFilename = `${outputDir}/${files[0].path}`;
- const fileType = await this.detector.detectFile(tarFilename);
- if (!fileType || fileType.ext != 'tar')
- return files;
- const newTarFilename = `${outputDir}/${utils.randomHexString(30)}`;
- await fs.rename(tarFilename, newTarFilename);
-
- const tarFiles = await this.decompress('tar', newTarFilename, outputDir);
- await fs.remove(newTarFilename);
- return tarFiles;
- }
- async decompress(fileExt, filename, outputDir) {
- let files = [];
- switch (fileExt) {
- case 'zip':
- files = await this.unZip(filename, outputDir);
- break;
- case 'bz2':
- files = await this.unBz2(filename, outputDir);
- break;
- case 'gz':
- files = await this.unGz(filename, outputDir);
- break;
- case 'tar':
- files = await this.unTar(filename, outputDir);
- break;
- default:
- throw new Error(`FileDecompressor: неизвестный формат файла '${fileExt}'`);
- }
- return files;
- }
- async unZip(filename, outputDir) {
- const zip = new ZipStreamer();
- return await zip.unpack(filename, outputDir, null, this.limitFileSize);
- }
- unBz2(filename, outputDir) {
- return this.decompressByStream(unbzip2Stream(), filename, outputDir);
- }
- unGz(filename, outputDir) {
- return this.decompressByStream(zlib.createGunzip(), filename, outputDir);
- }
- unTar(filename, outputDir) {
- return new Promise((resolve, reject) => { (async() => {
- const files = [];
- if (this.limitFileSize) {
- if ((await fs.stat(filename)).size > this.limitFileSize) {
- reject('Файл слишком большой');
- return;
- }
- }
- const tarExtract = tar.extract(outputDir, {
- map: (header) => {
- files.push({path: header.name, size: header.size});
- return header;
- }
- });
- tarExtract.on('finish', () => {
- resolve(files);
- });
- tarExtract.on('error', (err) => {
- reject(err);
- });
- const inputStream = fs.createReadStream(filename);
- inputStream.on('error', (err) => {
- reject(err);
- });
- inputStream.pipe(tarExtract);
- })().catch(reject); });
- }
- decompressByStream(stream, filename, outputDir) {
- return new Promise((resolve, reject) => { (async() => {
- const file = {path: path.parse(filename).name};
- let outFilename = `${outputDir}/${file.path}`;
- if (await fs.pathExists(outFilename)) {
- file.path = `${utils.randomHexString(10)}-${file.path}`;
- outFilename = `${outputDir}/${file.path}`;
- }
-
- const inputStream = fs.createReadStream(filename);
- const outputStream = fs.createWriteStream(outFilename);
- outputStream.on('finish', async() => {
- try {
- file.size = (await fs.stat(outFilename)).size;
- } catch (e) {
- reject(e);
- }
- resolve([file]);
- });
- stream.on('error', reject);
- if (this.limitFileSize) {
- let readSize = 0;
- stream.on('data', (buffer) => {
- readSize += buffer.length;
- if (readSize > this.limitFileSize)
- stream.destroy(new Error('Файл слишком большой'));
- });
- }
- inputStream.on('error', reject);
- outputStream.on('error', reject);
-
- inputStream.pipe(stream).pipe(outputStream);
- })().catch(reject); });
- }
- async gzipBuffer(buf) {
- return new Promise((resolve, reject) => {
- zlib.gzip(buf, {level: 1}, (err, result) => {
- if (err) reject(err);
- resolve(result);
- });
- });
- }
- async gzipFile(inputFile, outputFile, level = 1) {
- return new Promise((resolve, reject) => {
- const gzip = zlib.createGzip({level});
- const input = fs.createReadStream(inputFile);
- const output = fs.createWriteStream(outputFile);
- input.pipe(gzip).pipe(output).on('finish', (err) => {
- if (err) reject(err);
- else resolve();
- });
- });
- }
- async gzipFileIfNotExists(filename, outDir) {
- const hash = await utils.getFileHash(filename, 'sha256', 'hex');
- const outFilename = `${outDir}/${hash}`;
- if (!await fs.pathExists(outFilename)) {
- await this.gzipFile(filename, outFilename, 1);
- // переупакуем через некоторое время на максималках
- const filenameCopy = `${filename}.copy`;
- await fs.copy(filename, filenameCopy);
- (async() => {
- await utils.sleep(5000);
- const filenameGZ = `${filename}.gz`;
- await this.gzipFile(filenameCopy, filenameGZ, 9);
- await fs.move(filenameGZ, outFilename, {overwrite: true});
- await fs.remove(filenameCopy);
- })().catch((e) => { if (appLogger.inited) appLogger.log(LM_ERR, `FileDecompressor.gzipFileIfNotExists: ${e.message}`) });
- } else {
- await utils.touchFile(outFilename);
- }
- return outFilename;
- }
- }
- module.exports = FileDecompressor;
|