fileManager.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /* global MAXFILESIZE */
  2. import FileSender from './fileSender';
  3. import FileReceiver from './fileReceiver';
  4. import { copyToClipboard, delay, openLinksInNewTab, percent } from './utils';
  5. import * as metrics from './metrics';
  6. import { hasPassword } from './api';
  7. import Archive from './archive';
  8. import { bytes } from './utils';
  9. export default function(state, emitter) {
  10. let lastRender = 0;
  11. let updateTitle = false;
  12. function render() {
  13. emitter.emit('render');
  14. }
  15. async function checkFiles() {
  16. const files = state.storage.files.slice();
  17. let rerender = false;
  18. for (const file of files) {
  19. const oldLimit = file.dlimit;
  20. const oldTotal = file.dtotal;
  21. await file.updateDownloadCount();
  22. if (file.dtotal === file.dlimit) {
  23. state.storage.remove(file.id);
  24. rerender = true;
  25. } else if (oldLimit !== file.dlimit || oldTotal !== file.dtotal) {
  26. rerender = true;
  27. }
  28. }
  29. if (rerender) {
  30. render();
  31. }
  32. }
  33. function updateProgress() {
  34. if (updateTitle) {
  35. emitter.emit('DOMTitleChange', percent(state.transfer.progressRatio));
  36. }
  37. render();
  38. }
  39. emitter.on('DOMContentLoaded', () => {
  40. document.addEventListener('blur', () => (updateTitle = true));
  41. document.addEventListener('focus', () => {
  42. updateTitle = false;
  43. emitter.emit('DOMTitleChange', 'Firefox Send');
  44. });
  45. checkFiles();
  46. });
  47. emitter.on('navigate', checkFiles);
  48. emitter.on('render', () => {
  49. lastRender = Date.now();
  50. });
  51. emitter.on('changeLimit', async ({ file, value }) => {
  52. await file.changeLimit(value);
  53. state.storage.writeFile(file);
  54. metrics.changedDownloadLimit(file);
  55. });
  56. emitter.on('removeUpload', async ({ index }) => {
  57. state.archive.remove(index);
  58. render();
  59. });
  60. emitter.on('delete', async ({ file, location }) => {
  61. try {
  62. metrics.deletedUpload({
  63. size: file.size,
  64. time: file.time,
  65. speed: file.speed,
  66. type: file.type,
  67. ttl: file.expiresAt - Date.now(),
  68. location
  69. });
  70. state.storage.remove(file.id);
  71. await file.del();
  72. } catch (e) {
  73. state.raven.captureException(e);
  74. }
  75. });
  76. emitter.on('cancel', () => {
  77. state.transfer.cancel();
  78. });
  79. emitter.on('addFiles', async ({ files }) => {
  80. if (state.archive) {
  81. if (!state.archive.addFiles(files)) {
  82. // eslint-disable-next-line no-alert
  83. alert(state.translate('fileTooBig', { size: bytes(MAXFILESIZE) }));
  84. return;
  85. }
  86. } else {
  87. const archive = new Archive(files);
  88. if (!archive.checkSize()) {
  89. // eslint-disable-next-line no-alert
  90. alert(state.translate('fileTooBig', { size: bytes(MAXFILESIZE) }));
  91. return;
  92. }
  93. state.archive = archive;
  94. }
  95. render();
  96. });
  97. emitter.on('upload', async ({ type, dlCount, password }) => {
  98. if (!state.archive) return;
  99. const size = state.archive.size;
  100. const sender = new FileSender(state.archive);
  101. sender.on('progress', updateProgress);
  102. sender.on('encrypting', render);
  103. sender.on('complete', render);
  104. state.transfer = sender;
  105. state.uploading = true;
  106. render();
  107. const links = openLinksInNewTab();
  108. await delay(200);
  109. try {
  110. metrics.startedUpload({ size, type });
  111. const ownedFile = await sender.upload();
  112. ownedFile.type = type;
  113. state.storage.totalUploads += 1;
  114. metrics.completedUpload(ownedFile);
  115. state.storage.addFile(ownedFile);
  116. if (password) {
  117. emitter.emit('password', { password, file: ownedFile });
  118. }
  119. emitter.emit('changeLimit', { file: ownedFile, value: dlCount });
  120. const cancelBtn = document.getElementById('cancel-upload');
  121. if (cancelBtn) {
  122. cancelBtn.hidden = 'hidden';
  123. }
  124. if (document.querySelector('.page')) {
  125. await delay(1000);
  126. }
  127. emitter.emit('pushState', `/share/${ownedFile.id}`);
  128. } catch (err) {
  129. if (err.message === '0') {
  130. //cancelled. do nothing
  131. metrics.cancelledUpload({ size, type });
  132. render();
  133. } else {
  134. // eslint-disable-next-line no-console
  135. console.error(err);
  136. state.raven.captureException(err);
  137. metrics.stoppedUpload({ size, type, err });
  138. emitter.emit('pushState', '/error');
  139. }
  140. } finally {
  141. openLinksInNewTab(links, false);
  142. state.files = [];
  143. state.password = '';
  144. state.uploading = false;
  145. state.transfer = null;
  146. }
  147. });
  148. emitter.on('password', async ({ password, file }) => {
  149. try {
  150. state.settingPassword = true;
  151. render();
  152. await file.setPassword(password);
  153. state.storage.writeFile(file);
  154. metrics.addedPassword({ size: file.size });
  155. await delay(1000);
  156. } catch (err) {
  157. // eslint-disable-next-line no-console
  158. console.error(err);
  159. state.passwordSetError = err;
  160. } finally {
  161. state.settingPassword = false;
  162. }
  163. render();
  164. });
  165. emitter.on('getPasswordExist', async ({ id }) => {
  166. try {
  167. state.fileInfo = await hasPassword(id);
  168. render();
  169. } catch (e) {
  170. if (e.message === '404') {
  171. return emitter.emit('pushState', '/404');
  172. }
  173. }
  174. });
  175. emitter.on('getMetadata', async () => {
  176. const file = state.fileInfo;
  177. const receiver = new FileReceiver(file);
  178. try {
  179. await receiver.getMetadata();
  180. state.transfer = receiver;
  181. } catch (e) {
  182. if (e.message === '401') {
  183. file.password = null;
  184. if (!file.requiresPassword) {
  185. return emitter.emit('pushState', '/404');
  186. }
  187. }
  188. }
  189. render();
  190. });
  191. emitter.on('download', async file => {
  192. state.transfer.on('progress', updateProgress);
  193. state.transfer.on('decrypting', render);
  194. state.transfer.on('complete', render);
  195. const links = openLinksInNewTab();
  196. const size = file.size;
  197. try {
  198. const start = Date.now();
  199. metrics.startedDownload({ size: file.size, ttl: file.ttl });
  200. const dl = state.transfer.download({
  201. stream: state.capabilities.streamDownload
  202. });
  203. render();
  204. await dl;
  205. const time = Date.now() - start;
  206. const speed = size / (time / 1000);
  207. if (document.querySelector('.page')) {
  208. await delay(1000);
  209. }
  210. state.storage.totalDownloads += 1;
  211. state.transfer.reset();
  212. metrics.completedDownload({ size, time, speed });
  213. } catch (err) {
  214. if (err.message === '0') {
  215. // download cancelled
  216. state.transfer.reset();
  217. render();
  218. } else {
  219. // eslint-disable-next-line no-console
  220. console.error(err);
  221. state.transfer = null;
  222. const location = err.message === '404' ? '/404' : '/error';
  223. if (location === '/error') {
  224. state.raven.captureException(err);
  225. metrics.stoppedDownload({ size, err });
  226. }
  227. emitter.emit('pushState', location);
  228. }
  229. } finally {
  230. openLinksInNewTab(links, false);
  231. }
  232. });
  233. emitter.on('copy', ({ url, location }) => {
  234. copyToClipboard(url);
  235. metrics.copiedLink({ location });
  236. });
  237. setInterval(() => {
  238. // poll for updates of the download counts
  239. // TODO something for the share page: || state.route === '/share/:id'
  240. if (state.route === '/') {
  241. checkFiles();
  242. }
  243. }, 2 * 60 * 1000);
  244. setInterval(() => {
  245. // poll for rerendering the file list countdown timers
  246. if (
  247. state.route === '/' &&
  248. state.storage.files.length > 0 &&
  249. Date.now() - lastRender > 30000
  250. ) {
  251. render();
  252. }
  253. }, 60000);
  254. }