FileDownloader.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. const axios = require('axios');
  2. const utils = require('./utils');
  3. const userAgent = 'Mozilla/5.0 (X11; HasCodingOs 1.0; Linux x64) AppleWebKit/637.36 (KHTML, like Gecko) Chrome/70.0.3112.101 Safari/637.36 HasBrowser/5.0';
  4. class FileDownloader {
  5. constructor(limitDownloadSize = 0) {
  6. this.limitDownloadSize = limitDownloadSize;
  7. }
  8. async load(url, callback, abort) {
  9. let errMes = '';
  10. const options = {
  11. headers: {
  12. 'user-agent': userAgent,
  13. timeout: 300*1000,
  14. },
  15. responseType: 'stream',
  16. };
  17. try {
  18. const res = await axios.get(url, options);
  19. let estSize = 0;
  20. if (res.headers['content-length']) {
  21. estSize = res.headers['content-length'];
  22. }
  23. if (this.limitDownloadSize && estSize > this.limitDownloadSize) {
  24. throw new Error('Файл слишком большой');
  25. }
  26. let prevProg = 0;
  27. let transferred = 0;
  28. const download = this.streamToBuffer(res.data, (chunk) => {
  29. transferred += chunk.length;
  30. if (this.limitDownloadSize) {
  31. if (transferred > this.limitDownloadSize) {
  32. errMes = 'Файл слишком большой';
  33. res.request.abort();
  34. }
  35. }
  36. let prog = 0;
  37. if (estSize)
  38. prog = Math.round(transferred/estSize*100);
  39. else
  40. prog = Math.round(transferred/(transferred + 200000)*100);
  41. if (prog != prevProg && callback)
  42. callback(prog);
  43. prevProg = prog;
  44. if (abort && abort()) {
  45. errMes = 'abort';
  46. res.request.abort();
  47. }
  48. });
  49. return await download;
  50. } catch (error) {
  51. errMes = (errMes ? errMes : error.message);
  52. throw new Error(errMes);
  53. }
  54. }
  55. async head(url) {
  56. const options = {
  57. headers: {
  58. 'user-agent': userAgent,
  59. timeout: 10*1000,
  60. },
  61. };
  62. const res = await axios.head(url, options);
  63. return res.headers;
  64. }
  65. streamToBuffer(stream, progress, timeout = 30*1000) {
  66. return new Promise((resolve, reject) => {
  67. if (!progress)
  68. progress = () => {};
  69. const _buf = [];
  70. let resolved = false;
  71. let timer = 0;
  72. stream.on('data', (chunk) => {
  73. timer = 0;
  74. _buf.push(chunk);
  75. progress(chunk);
  76. });
  77. stream.on('end', () => {
  78. resolved = true;
  79. timer = timeout;
  80. resolve(Buffer.concat(_buf));
  81. });
  82. stream.on('error', (err) => {
  83. reject(err);
  84. });
  85. stream.on('aborted', () => {
  86. reject(new Error('aborted'));
  87. });
  88. //бодяга с timer и timeout, чтобы гарантировать отсутствие зависания по каким-либо причинам
  89. (async() => {
  90. while (timer < timeout) {
  91. await utils.sleep(1000);
  92. timer += 1000;
  93. }
  94. if (!resolved)
  95. reject(new Error('FileDownloader: timed out'))
  96. })();
  97. });
  98. }
  99. }
  100. module.exports = FileDownloader;