FileDecompressor.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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 appLogger = new (require('./AppLogger'))();//singleton
  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.decompressTarZZ(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 unpack(filename, outputDir) {
  46. const fileType = await this.detector.detectFile(filename);
  47. if (!fileType)
  48. throw new Error('Не удалось определить формат файла');
  49. return await this.decompress(fileType.ext, filename, outputDir);
  50. }
  51. async unpackTarZZ(filename, outputDir) {
  52. const fileType = await this.detector.detectFile(filename);
  53. if (!fileType)
  54. throw new Error('Не удалось определить формат файла');
  55. return await this.decompressTarZZ(fileType.ext, filename, outputDir);
  56. }
  57. async decompressTarZZ(fileExt, filename, outputDir) {
  58. const files = await this.decompress(fileExt, filename, outputDir);
  59. if (fileExt == 'tar' || files.length != 1)
  60. return files;
  61. const tarFilename = `${outputDir}/${files[0].path}`;
  62. const fileType = await this.detector.detectFile(tarFilename);
  63. if (!fileType || fileType.ext != 'tar')
  64. return files;
  65. const newTarFilename = `${outputDir}/${utils.randomHexString(30)}`;
  66. await fs.rename(tarFilename, newTarFilename);
  67. const tarFiles = await this.decompress('tar', newTarFilename, outputDir);
  68. await fs.remove(newTarFilename);
  69. return tarFiles;
  70. }
  71. async decompress(fileExt, filename, outputDir) {
  72. let files = [];
  73. switch (fileExt) {
  74. case 'zip':
  75. files = await this.unZip(filename, outputDir);
  76. break;
  77. case 'bz2':
  78. files = await this.unBz2(filename, outputDir);
  79. break;
  80. case 'gz':
  81. files = await this.unGz(filename, outputDir);
  82. break;
  83. case 'tar':
  84. files = await this.unTar(filename, outputDir);
  85. break;
  86. default:
  87. throw new Error(`FileDecompressor: неизвестный формат файла '${fileExt}'`);
  88. }
  89. return files;
  90. }
  91. async unZip(filename, outputDir) {
  92. const zip = new ZipStreamer();
  93. return await zip.unpack(filename, outputDir);
  94. }
  95. unBz2(filename, outputDir) {
  96. return this.decompressByStream(unbzip2Stream(), filename, outputDir);
  97. }
  98. unGz(filename, outputDir) {
  99. return this.decompressByStream(zlib.createGunzip(), filename, outputDir);
  100. }
  101. unTar(filename, outputDir) {
  102. return new Promise((resolve, reject) => {
  103. const files = [];
  104. const tarExtract = tar.extract(outputDir, {
  105. map: (header) => {
  106. files.push({path: header.name, size: header.size});
  107. return header;
  108. }
  109. });
  110. tarExtract.on('finish', () => {
  111. resolve(files);
  112. });
  113. tarExtract.on('error', (err) => {
  114. reject(err);
  115. });
  116. const inputStream = fs.createReadStream(filename);
  117. inputStream.on('error', (err) => {
  118. reject(err);
  119. });
  120. inputStream.pipe(tarExtract);
  121. });
  122. }
  123. decompressByStream(stream, filename, outputDir) {
  124. return new Promise((resolve, reject) => { (async() => {
  125. const file = {path: path.parse(filename).name};
  126. let outFilename = `${outputDir}/${file.path}`;
  127. if (await fs.pathExists(outFilename)) {
  128. file.path = `${utils.randomHexString(10)}-${file.path}`;
  129. outFilename = `${outputDir}/${file.path}`;
  130. }
  131. const inputStream = fs.createReadStream(filename);
  132. const outputStream = fs.createWriteStream(outFilename);
  133. outputStream.on('finish', async() => {
  134. try {
  135. file.size = (await fs.stat(outFilename)).size;
  136. } catch (e) {
  137. reject(e);
  138. }
  139. resolve([file]);
  140. });
  141. stream.on('error', reject);
  142. inputStream.on('error', reject);
  143. outputStream.on('error', reject);
  144. inputStream.pipe(stream).pipe(outputStream);
  145. })().catch(reject); });
  146. }
  147. async gzipBuffer(buf) {
  148. return new Promise((resolve, reject) => {
  149. zlib.gzip(buf, {level: 1}, (err, result) => {
  150. if (err) reject(err);
  151. resolve(result);
  152. });
  153. });
  154. }
  155. async gzipFile(inputFile, outputFile, level = 1) {
  156. return new Promise((resolve, reject) => {
  157. const gzip = zlib.createGzip({level});
  158. const input = fs.createReadStream(inputFile);
  159. const output = fs.createWriteStream(outputFile);
  160. input.pipe(gzip).pipe(output).on('finish', (err) => {
  161. if (err) reject(err);
  162. else resolve();
  163. });
  164. });
  165. }
  166. async gzipFileIfNotExists(filename, outDir) {
  167. const hash = await utils.getFileHash(filename, 'sha256', 'hex');
  168. const outFilename = `${outDir}/${hash}`;
  169. if (!await fs.pathExists(outFilename)) {
  170. await this.gzipFile(filename, outFilename, 1);
  171. // переупакуем через некоторое время на максималках
  172. const filenameCopy = `${filename}.copy`;
  173. await fs.copy(filename, filenameCopy);
  174. (async() => {
  175. await utils.sleep(5000);
  176. const filenameGZ = `${filename}.gz`;
  177. await this.gzipFile(filenameCopy, filenameGZ, 9);
  178. await fs.move(filenameGZ, outFilename, {overwrite: true});
  179. await fs.remove(filenameCopy);
  180. })().catch((e) => { if (appLogger.inited) appLogger.log(LM_ERR, `FileDecompressor.gzipFileIfNotExists: ${e.message}`) });
  181. } else {
  182. await utils.touchFile(outFilename);
  183. }
  184. return outFilename;
  185. }
  186. }
  187. module.exports = FileDecompressor;