FileDecompressor.js 8.2 KB

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