index.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. 'use strict';
  2. const path = require('path');
  3. const electron = require('electron');
  4. const unusedFilename = require('unused-filename');
  5. const pupa = require('pupa');
  6. const extName = require('ext-name');
  7. const {app, shell} = electron;
  8. function getFilenameFromMime(name, mime) {
  9. const exts = extName.mime(mime);
  10. if (exts.length !== 1) {
  11. return name;
  12. }
  13. return `${name}.${exts[0].ext}`;
  14. }
  15. function registerListener(session, options, cb = () => {}) {
  16. const downloadItems = new Set();
  17. let receivedBytes = 0;
  18. let completedBytes = 0;
  19. let totalBytes = 0;
  20. const activeDownloadItems = () => downloadItems.size;
  21. const progressDownloadItems = () => receivedBytes / totalBytes;
  22. options = Object.assign({
  23. showBadge: true
  24. }, options);
  25. const listener = (e, item, webContents) => {
  26. downloadItems.add(item);
  27. totalBytes += item.getTotalBytes();
  28. let hostWebContents = webContents;
  29. if (webContents.getType() === 'webview') {
  30. ({hostWebContents} = webContents);
  31. }
  32. const win = electron.BrowserWindow.fromWebContents(hostWebContents);
  33. const dir = options.directory || app.getPath('downloads');
  34. let filePath;
  35. if (options.filename) {
  36. filePath = path.join(dir, options.filename);
  37. } else {
  38. const filename = item.getFilename();
  39. const name = path.extname(filename) ? filename : getFilenameFromMime(filename, item.getMimeType());
  40. filePath = unusedFilename.sync(path.join(dir, name));
  41. }
  42. const errorMessage = options.errorMessage || 'The download of {filename} was interrupted';
  43. const errorTitle = options.errorTitle || 'Download Error';
  44. if (!options.saveAs) {
  45. item.setSavePath(filePath);
  46. }
  47. if (typeof options.onStarted === 'function') {
  48. options.onStarted(item);
  49. }
  50. item.on('updated', () => {
  51. receivedBytes = [...downloadItems].reduce((receivedBytes, item) => {
  52. receivedBytes += item.getReceivedBytes();
  53. return receivedBytes;
  54. }, completedBytes);
  55. if (options.showBadge && ['darwin', 'linux'].includes(process.platform)) {
  56. app.setBadgeCount(activeDownloadItems());
  57. }
  58. if (!win.isDestroyed()) {
  59. win.setProgressBar(progressDownloadItems());
  60. }
  61. if (typeof options.onProgress === 'function') {
  62. options.onProgress(progressDownloadItems());
  63. }
  64. });
  65. item.on('done', (e, state) => {
  66. completedBytes += item.getTotalBytes();
  67. downloadItems.delete(item);
  68. if (options.showBadge && ['darwin', 'linux'].includes(process.platform)) {
  69. app.setBadgeCount(activeDownloadItems());
  70. }
  71. if (!win.isDestroyed() && !activeDownloadItems()) {
  72. win.setProgressBar(-1);
  73. receivedBytes = 0;
  74. completedBytes = 0;
  75. totalBytes = 0;
  76. }
  77. if (options.unregisterWhenDone) {
  78. session.removeListener('will-download', listener);
  79. }
  80. if (state === 'cancelled') {
  81. if (typeof options.onCancel === 'function') {
  82. options.onCancel(item);
  83. }
  84. } else if (state === 'interrupted') {
  85. const message = pupa(errorMessage, {filename: item.getFilename()});
  86. electron.dialog.showErrorBox(errorTitle, message);
  87. cb(new Error(message));
  88. } else if (state === 'completed') {
  89. if (process.platform === 'darwin') {
  90. app.dock.downloadFinished(filePath);
  91. }
  92. if (options.openFolderWhenDone) {
  93. shell.showItemInFolder(path.join(dir, item.getFilename()));
  94. }
  95. cb(null, item);
  96. }
  97. });
  98. };
  99. session.on('will-download', listener);
  100. }
  101. module.exports = (options = {}) => {
  102. app.on('session-created', session => {
  103. registerListener(session, options);
  104. });
  105. };
  106. module.exports.download = (win, url, options) => new Promise((resolve, reject) => {
  107. options = Object.assign({}, options, {unregisterWhenDone: true});
  108. registerListener(win.webContents.session, options, (err, item) => {
  109. if (err) {
  110. reject(err);
  111. } else {
  112. resolve(item);
  113. }
  114. });
  115. win.webContents.downloadURL(url);
  116. });