index.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  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. const sessionListenerMap = new Map();
  16. const handlerMap = new Map();
  17. const downloadItems = new Set();
  18. let receivedBytes = 0;
  19. let completedBytes = 0;
  20. let totalBytes = 0;
  21. const activeDownloadItems = () => downloadItems.size;
  22. const progressDownloadItems = function (item) {
  23. if (item) {
  24. return item.getReceivedBytes() / item.getTotalBytes();
  25. }
  26. return receivedBytes / totalBytes;
  27. };
  28. function registerListener(session) {
  29. const listener = (e, item, webContents) => {
  30. const urlChains = item.getURLChain();
  31. const originUrl = urlChains[0];
  32. const key = decodeURIComponent(originUrl);
  33. const defaultHanlder = {
  34. options: {},
  35. resolve: () => { },
  36. reject: () => { }
  37. };
  38. const {options, resolve, reject} = handlerMap.get(key) || defaultHanlder;
  39. downloadItems.add(item);
  40. totalBytes += item.getTotalBytes();
  41. let hostWebContents = webContents;
  42. if (webContents.getType() === 'webview') {
  43. ({hostWebContents} = webContents);
  44. }
  45. const win = electron.BrowserWindow.fromWebContents(hostWebContents);
  46. const dir = options.directory || app.getPath('downloads');
  47. let filePath;
  48. if (options.filename) {
  49. filePath = path.join(dir, options.filename);
  50. } else {
  51. const filename = item.getFilename();
  52. const name = path.extname(filename) ? filename : getFilenameFromMime(filename, item.getMimeType());
  53. filePath = unusedFilename.sync(path.join(dir, name));
  54. }
  55. const errorMessage = options.errorMessage || 'The download of {filename} was interrupted';
  56. const errorTitle = options.errorTitle || 'Download Error';
  57. if (!options.saveAs) {
  58. item.setSavePath(filePath);
  59. }
  60. if (typeof options.onStarted === 'function') {
  61. options.onStarted(item);
  62. }
  63. item.on('updated', () => {
  64. receivedBytes = [...downloadItems].reduce((receivedBytes, item) => {
  65. receivedBytes += item.getReceivedBytes();
  66. return receivedBytes;
  67. }, completedBytes);
  68. if (!win.isDestroyed()) {
  69. win.setProgressBar(progressDownloadItems());
  70. }
  71. if (typeof options.onProgress === 'function') {
  72. const itemTransferredBytes = item.getReceivedBytes();
  73. const itemTotalBytes = item.getTotalBytes();
  74. options.onProgress({
  75. percent: itemTotalBytes ? itemTransferredBytes / itemTotalBytes : 0,
  76. transferredBytes: itemTransferredBytes,
  77. totalBytes: itemTotalBytes
  78. });
  79. }
  80. });
  81. item.once('done', (e, state) => {
  82. completedBytes += item.getTotalBytes();
  83. downloadItems.delete(item);
  84. if (!win.isDestroyed() && !activeDownloadItems()) {
  85. win.setProgressBar(-1);
  86. receivedBytes = 0;
  87. completedBytes = 0;
  88. totalBytes = 0;
  89. }
  90. if (state === 'interrupted') {
  91. const message = pupa(errorMessage, {filename: item.getFilename()});
  92. electron.dialog.showErrorBox(errorTitle, message);
  93. reject(new Error(message));
  94. } else if (state === 'cancelled') {
  95. reject(new Error('The download has been cancelled'));
  96. } else if (state === 'completed') {
  97. if (process.platform === 'darwin') {
  98. app.dock.downloadFinished(filePath);
  99. }
  100. if (options.openFolderWhenDone) {
  101. shell.showItemInFolder(filePath);
  102. }
  103. resolve(item);
  104. }
  105. if (handlerMap.has(key)) {
  106. handlerMap.delete(key);
  107. }
  108. });
  109. };
  110. if (!sessionListenerMap.get(session)) {
  111. sessionListenerMap.set(session, true);
  112. session.on('will-download', listener);
  113. }
  114. }
  115. function unregisterListener (session) {
  116. if (sessionListenerMap.has(session)) {
  117. sessionListenerMap.delete(session);
  118. }
  119. }
  120. module.exports = (options = {}) => {
  121. app.on('session-created', session => {
  122. registerListener(session, options);
  123. app.on('close', () => unregisterListener(session));
  124. });
  125. };
  126. module.exports.download = (win, url, options) => new Promise((resolve, reject) => {
  127. options = Object.assign({}, options, {unregisterWhenDone: true});
  128. const key = decodeURIComponent(url);
  129. handlerMap.set(key, {options, resolve, reject});
  130. registerListener(win.webContents.session);
  131. win.on('close', () => unregisterListener(win.webContents.session));
  132. win.webContents.downloadURL(url);
  133. });