FileDecompressor.js 5.5 KB

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