FileDecompressor.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. const fs = require('fs-extra');
  2. const zlib = require('zlib');
  3. const path = require('path');
  4. const unbzip2Stream = require('unbzip2-stream');
  5. const tar = require('tar-fs');
  6. const ZipStreamer = require('./ZipStreamer');
  7. const utils = require('./utils');
  8. const FileDetector = require('./FileDetector');
  9. class FileDecompressor {
  10. constructor() {
  11. this.detector = new FileDetector();
  12. }
  13. async decompressNested(filename, outputDir) {
  14. await fs.ensureDir(outputDir);
  15. const fileType = await this.detector.detectFile(filename);
  16. let result = {
  17. sourceFile: filename,
  18. sourceFileType: fileType,
  19. selectedFile: filename,
  20. filesDir: outputDir,
  21. files: []
  22. };
  23. if (!fileType || !(fileType.ext == 'zip' || fileType.ext == 'bz2' || fileType.ext == 'gz' || fileType.ext == 'tar')) {
  24. return result;
  25. }
  26. result.files = await this.decompressTarZZ(fileType.ext, filename, outputDir);
  27. let sel = filename;
  28. let max = 0;
  29. if (result.files.length) {
  30. //ищем файл с максимальным размером
  31. for (let file of result.files) {
  32. if (file.size > max) {
  33. sel = `${outputDir}/${file.path}`;
  34. max = file.size;
  35. }
  36. }
  37. }
  38. result.selectedFile = sel;
  39. if (sel != filename) {
  40. result.nesting = await this.decompressNested(sel, `${outputDir}/${utils.randomHexString(10)}`);
  41. }
  42. return result;
  43. }
  44. async unpack(filename, outputDir) {
  45. const fileType = await this.detector.detectFile(filename);
  46. if (!fileType)
  47. throw new Error('Не удалось определить формат файла');
  48. return await this.decompress(fileType.ext, filename, outputDir);
  49. }
  50. async unpackTarZZ(filename, outputDir) {
  51. const fileType = await this.detector.detectFile(filename);
  52. if (!fileType)
  53. throw new Error('Не удалось определить формат файла');
  54. return await this.decompressTarZZ(fileType.ext, filename, outputDir);
  55. }
  56. async decompressTarZZ(fileExt, filename, outputDir) {
  57. const files = await this.decompress(fileExt, filename, outputDir);
  58. if (fileExt == 'tar' || files.length != 1)
  59. return files;
  60. const tarFilename = `${outputDir}/${files[0].path}`;
  61. const fileType = await this.detector.detectFile(tarFilename);
  62. if (!fileType || fileType.ext != 'tar')
  63. return files;
  64. const newTarFilename = `${outputDir}/${utils.randomHexString(30)}`;
  65. await fs.rename(tarFilename, newTarFilename);
  66. const tarFiles = await this.decompress('tar', newTarFilename, outputDir);
  67. await fs.remove(newTarFilename);
  68. return tarFiles;
  69. }
  70. async decompress(fileExt, filename, outputDir) {
  71. let files = [];
  72. switch (fileExt) {
  73. case 'zip':
  74. files = await this.unZip(filename, outputDir);
  75. break;
  76. case 'bz2':
  77. files = await this.unBz2(filename, outputDir);
  78. break;
  79. case 'gz':
  80. files = await this.unGz(filename, outputDir);
  81. break;
  82. case 'tar':
  83. files = await this.unTar(filename, outputDir);
  84. break;
  85. default:
  86. throw new Error(`FileDecompressor: неизвестный формат файла '${fileExt}'`);
  87. }
  88. return files;
  89. }
  90. async unZip(filename, outputDir) {
  91. const zip = new ZipStreamer();
  92. return await await zip.unpack(filename, outputDir);
  93. }
  94. unBz2(filename, outputDir) {
  95. return this.decompressByStream(unbzip2Stream(), filename, outputDir);
  96. }
  97. unGz(filename, outputDir) {
  98. return this.decompressByStream(zlib.createGunzip(), filename, outputDir);
  99. }
  100. unTar(filename, outputDir) {
  101. return new Promise((resolve, reject) => {
  102. const files = [];
  103. const tarExtract = tar.extract(outputDir, {
  104. map: (header) => {
  105. files.push({path: header.name, size: header.size});
  106. return header;
  107. }
  108. });
  109. tarExtract.on('finish', () => {
  110. resolve(files);
  111. });
  112. tarExtract.on('error', (err) => {
  113. reject(err);
  114. });
  115. const inputStream = fs.createReadStream(filename);
  116. inputStream.on('error', (err) => {
  117. reject(err);
  118. });
  119. inputStream.pipe(tarExtract);
  120. });
  121. }
  122. decompressByStream(stream, filename, outputDir) {
  123. return new Promise((resolve, reject) => { (async() => {
  124. const file = {path: path.parse(filename).name};
  125. let outFilename = `${outputDir}/${file.path}`;
  126. if (await fs.pathExists(outFilename)) {
  127. file.path = `${utils.randomHexString(10)}-${file.path}`;
  128. outFilename = `${outputDir}/${file.path}`;
  129. }
  130. const inputStream = fs.createReadStream(filename);
  131. const outputStream = fs.createWriteStream(outFilename);
  132. outputStream.on('finish', async() => {
  133. try {
  134. file.size = (await fs.stat(outFilename)).size;
  135. } catch (e) {
  136. reject(e);
  137. }
  138. resolve([file]);
  139. });
  140. stream.on('error', reject);
  141. inputStream.on('error', reject);
  142. outputStream.on('error', reject);
  143. inputStream.pipe(stream).pipe(outputStream);
  144. })().catch(reject); });
  145. }
  146. async gzipBuffer(buf) {
  147. return new Promise((resolve, reject) => {
  148. zlib.gzip(buf, {level: 1}, (err, result) => {
  149. if (err) reject(err);
  150. resolve(result);
  151. });
  152. });
  153. }
  154. async gzipFile(inputFile, outputFile) {
  155. return new Promise((resolve, reject) => {
  156. const gzip = zlib.createGzip({level: 1});
  157. const input = fs.createReadStream(inputFile);
  158. const output = fs.createWriteStream(outputFile);
  159. input.pipe(gzip).pipe(output).on('finish', (err) => {
  160. if (err) reject(err);
  161. else resolve();
  162. });
  163. });
  164. }
  165. async gzipFileIfNotExists(filename, outDir) {
  166. const hash = await utils.getFileHash(filename, 'sha256', 'hex');
  167. const outFilename = `${outDir}/${hash}`;
  168. if (!await fs.pathExists(outFilename)) {
  169. await this.gzipFile(filename, outFilename);
  170. } else {
  171. await utils.touchFile(outFilename);
  172. }
  173. return outFilename;
  174. }
  175. }
  176. module.exports = FileDecompressor;