2 * (C) Copyright 2008 Jeremy Maitin-Shepard
3 * (C) Copyright 2009 John Foerch
5 * Use, modification, and distribution are subject to the terms specified in the
9 require("special-buffer.js");
10 require("mime-type-override.js");
11 require("minibuffer-read-mime-type.js");
12 require("compat/Map.js");
14 var download_manager_service = Cc["@mozilla.org/download-manager;1"]
15 .getService(Ci.nsIDownloadManager);
17 var unmanaged_download_info_list = [];
19 var id_to_download_info = new Map();
22 Cu.import("resource://gre/modules/Downloads.jsm");
23 if (typeof(Downloads.getList) == 'undefined')
24 throw "bad Downloads.jsm version";
25 var use_downloads_jsm = true;
27 var lookup_download = function lookup_download(download) {
28 return id_to_download_info.get(download);
31 var use_downloads_jsm = false;
33 var lookup_download = function lookup_download(download) {
34 return id_to_download_info.get(download.id);
39 // Import these constants for convenience
40 const DOWNLOAD_NOTSTARTED = Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
41 const DOWNLOAD_DOWNLOADING = Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING;
42 const DOWNLOAD_FINISHED = Ci.nsIDownloadManager.DOWNLOAD_FINISHED;
43 const DOWNLOAD_FAILED = Ci.nsIDownloadManager.DOWNLOAD_FAILED;
44 const DOWNLOAD_CANCELED = Ci.nsIDownloadManager.DOWNLOAD_CANCELED;
45 const DOWNLOAD_PAUSED = Ci.nsIDownloadManager.DOWNLOAD_PAUSED;
46 const DOWNLOAD_QUEUED = Ci.nsIDownloadManager.DOWNLOAD_QUEUED;
47 const DOWNLOAD_SCANNING = Ci.nsIDownloadManager.DOWNLOAD_SCANNING;
50 const DOWNLOAD_NOT_TEMPORARY = 0;
51 const DOWNLOAD_TEMPORARY_FOR_ACTION = 1;
52 const DOWNLOAD_TEMPORARY_FOR_COMMAND = 2;
54 function download_info (source_buffer, mozilla_info, target_file) {
55 this.source_buffer = source_buffer;
56 this.target_file = target_file;
57 if (mozilla_info != null)
58 this.attach(mozilla_info);
60 download_info.prototype = {
61 constructor: download_info,
64 shell_command_cwd: null,
65 temporary_status: DOWNLOAD_NOT_TEMPORARY,
66 action_description: null,
67 set_shell_command: function (str, cwd) {
68 this.shell_command = str;
69 this.shell_command_cwd = cwd;
70 if (this.mozilla_info)
71 download_shell_command_change_hook.run(this);
75 * None of the following members may be used until attach is called
78 // Reflectors to properties of nsIDownload
79 get source () { return this.mozilla_info.source; },
80 get id () { return this.mozilla_info.id; },
81 get referrer () { return this.mozilla_info.referrer; },
83 target_file_text: function () {
84 let target = this.target_file.path;
85 let display = this.display_name;
86 if (target.indexOf(display, target.length - display.length) == -1)
87 target += " (" + display + ")";
91 throw_if_removed: function () {
93 throw interactive_error("Download has already been removed from the download manager.");
96 throw_state_error: function () {
98 case DOWNLOAD_DOWNLOADING:
99 throw interactive_error("Download is already in progress.");
100 case DOWNLOAD_FINISHED:
101 throw interactive_error("Download has already completed.");
102 case DOWNLOAD_FAILED:
103 throw interactive_error("Download has already failed.");
104 case DOWNLOAD_CANCELED:
105 throw interactive_error("Download has already been canceled.");
106 case DOWNLOAD_PAUSED:
107 throw interactive_error("Download has already been paused.");
108 case DOWNLOAD_QUEUED:
109 throw interactive_error("Download is queued.");
111 throw new Error("Download has unexpected state: " + this.state);
115 // Download manager operations
116 cancel: function () {
117 this.throw_if_removed();
118 switch (this.state) {
119 case DOWNLOAD_DOWNLOADING:
120 case DOWNLOAD_PAUSED:
121 case DOWNLOAD_QUEUED:
122 if (use_downloads_jsm) {
123 yield this.mozilla_info.finalize(true);
126 download_manager_service.cancelDownload(this.id);
128 throw interactive_error("Download cannot be canceled.");
133 this.throw_state_error();
138 this.throw_if_removed();
139 switch (this.state) {
140 case DOWNLOAD_CANCELED:
141 case DOWNLOAD_FAILED:
142 if (use_downloads_jsm) {
143 yield this.mozilla_info.start();
146 download_manager_service.retryDownload(this.id);
148 throw interactive_error("Download cannot be retried.");
153 this.throw_state_error();
157 resume: function () {
158 this.throw_if_removed();
159 switch (this.state) {
160 case DOWNLOAD_PAUSED:
161 if (use_downloads_jsm) {
162 yield this.mozilla_info.start();
166 download_manager_service.resumeDownload(this.id);
168 throw interactive_error("Download cannot be resumed.");
173 this.throw_state_error();
178 this.throw_if_removed();
179 switch (this.state) {
180 case DOWNLOAD_DOWNLOADING:
181 case DOWNLOAD_QUEUED:
182 if (use_downloads_jsm) {
183 yield this.mozilla_info.cancel();
186 download_manager_service.pauseDownload(this.id);
188 throw interactive_error("Download cannot be paused.");
193 this.throw_state_error();
197 remove: function () {
198 this.throw_if_removed();
199 switch (this.state) {
200 case DOWNLOAD_FAILED:
201 case DOWNLOAD_CANCELED:
202 case DOWNLOAD_FINISHED:
203 if (use_downloads_jsm) {
204 let list = yield Downloads.getList(Downloads.ALL);
205 yield list.remove(this.mozilla_info);
208 download_manager_service.removeDownload(this.id);
210 throw interactive_error("Download cannot be removed.");
215 throw interactive_error("Download is still in progress.");
219 delete_target: function () {
220 if (this.state != DOWNLOAD_FINISHED)
221 throw interactive_error("Download has not finished.");
223 this.target_file.remove(false);
227 case Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
228 throw interactive_error("File has already been deleted.");
229 case Cr.NS_ERROR_FILE_ACCESS_DENIED:
230 throw interactive_error("Access denied");
231 case Cr.NS_ERROR_FILE_DIR_NOT_EMPTY:
232 throw interactive_error("Failed to delete file.");
240 if (!use_downloads_jsm) {
241 download_info.prototype.__proto__ = {
242 attach: function (mozilla_info, existing) {
243 if (!this.target_file)
244 this.__defineGetter__("target_file", function () {
245 return this.mozilla_info.targetFile;
247 else if (this.target_file.path != mozilla_info.targetFile.path)
248 throw interactive_error("Download target file unexpected.");
250 this.mozilla_info = mozilla_info;
252 if (use_downloads_jsm) {
253 id_to_download_info.set(mozilla_info, this);
255 id_to_download_info.set(mozilla_info.id, this);
259 existing_download_added_hook.run(this);
261 download_added_hook.run(this);
263 get source_uri_string () { return this.mozilla_info.source.spec; },
264 get source_uri () { return this.mozilla_info.source; },
265 get display_name () { return this.mozilla_info.displayName; },
266 get amount_transferred () { return this.mozilla_info.amountTransferred; },
267 get percent_complete () { return this.mozilla_info.percentComplete; },
268 get speed () { return this.mozilla_info.speed; },
269 get state () { return this.mozilla_info.state; },
270 get start_time () { return this.mozilla_info.startTime / 1000; },
271 get MIME_info () { return this.mozilla_info.MIMEInfo; },
274 return this.MIME_info.MIMEType;
278 var s = this.mozilla_info.size;
279 /* nsIDownload.size is a PRUint64, and will have value
280 * LL_MAXUINT (2^64 - 1) to indicate an unknown size. Because
281 * JavaScript only has a double numerical type, this value
282 * cannot be represented exactly, so 2^36 is used instead as the cutoff. */
283 if (s < 68719476736 /* 2^36 */)
289 download_info.prototype.__proto__ = {
290 attach: function (mozilla_info, existing) {
291 if (!this.target_file)
292 this.__defineGetter__("target_file", function () {
293 return make_file(this.mozilla_info.target.path);
295 else if (this.target_file.path != mozilla_info.target.path)
296 throw interactive_error("Download target file unexpected.");
298 this.mozilla_info = mozilla_info;
299 id_to_download_info.set(mozilla_info, this);
302 existing_download_added_hook.run(this);
304 download_added_hook.run(this);
306 get source_uri () { return make_uri(this.mozilla_info.source.url); },
307 get source_uri_string () { return this.mozilla_info.source.url; },
308 get display_name () { return this.mozilla_info.target.path; },
309 get amount_transferred () { return this.mozilla_info.currentBytes; },
310 get percent_complete () { return this.mozilla_info.progress; },
311 get speed () { return 1000 * this.amount_transferred / (Date.now() - this.start_time); },
312 get start_time () { return this.mozilla_info.startTime.getTime(); },
313 get MIME_type () { return this.mozilla_info.contentType; },
315 if (this.mozilla_info.succeeded)
316 return DOWNLOAD_FINISHED;
317 if (this.mozilla_info.canceled)
318 return DOWNLOAD_CANCELED;
319 if (this.mozilla_info.error)
320 return DOWNLOAD_FAILED;
321 if (!this.mozilla_info.startTime)
322 return DOWNLOAD_NOTSTARTED;
323 return DOWNLOAD_DOWNLOADING;
326 if (this.mozilla_info.hasProgress)
327 return this.mozilla_info.totalBytes;
333 var define_download_local_hook = simple_local_hook_definer();
335 function register_download (buffer, source_uri, target_file) {
336 var info = new download_info(buffer, null, target_file);
337 info.registered_time_stamp = Date.now();
338 info.registered_source_uri = source_uri;
339 unmanaged_download_info_list.push(info);
343 function match_registered_download (source) {
344 let list = unmanaged_download_info_list;
346 for (let i = 0; i < list.length; ++i) {
348 if (x.registered_source_uri.spec == source) {
352 if (t - x.registered_time_stamp > download_info_max_queue_delay) {
361 define_download_local_hook("existing_download_added_hook");
362 define_download_local_hook("download_added_hook");
363 define_download_local_hook("download_removed_hook");
364 define_download_local_hook("download_finished_hook");
365 define_download_local_hook("download_progress_change_hook");
366 define_download_local_hook("download_state_change_hook");
367 define_download_local_hook("download_shell_command_change_hook");
369 define_variable('delete_temporary_files_for_command', true,
370 'If this is set to true, temporary files downloaded to run a command '+
371 'on them will be deleted once the command completes. If not, the file '+
372 'will stay around forever unless deleted outside the browser.');
374 var download_info_max_queue_delay = 100;
376 if (!use_downloads_jsm) {
378 var download_progress_listener = {
379 QueryInterface: generate_QI(Ci.nsIDownloadProgressListener),
381 onDownloadStateChange: function (state, download) {
383 /* FIXME: Determine if only new downloads will have this state
384 * as their previous state. */
386 dumpln("download state change: " + download.source.spec + ": " + state + ", " + download.state + ", " + download.id);
388 if (state == DOWNLOAD_NOTSTARTED) {
389 info = match_registered_download(download.source.spec);
391 info = new download_info(null, download);
392 dumpln("error: encountered unknown new download");
394 info.attach(download);
397 info = id_to_download_info.get(download.id);
399 dumpln("Error: encountered unknown download");
402 info.mozilla_info = download;
403 download_state_change_hook.run(info);
404 if (info.state == DOWNLOAD_FINISHED) {
405 download_finished_hook.run(info);
407 if (info.shell_command != null) {
408 info.running_shell_command = true;
411 yield shell_command_with_argument(info.shell_command,
412 info.target_file.path,
413 $cwd = info.shell_command_cwd);
415 handle_interactive_error(info.source_buffer.window, e);
417 if (info.temporary_status == DOWNLOAD_TEMPORARY_FOR_COMMAND)
418 if(delete_temporary_files_for_command) {
419 info.target_file.remove(false /* not recursive */);
421 info.running_shell_command = false;
422 download_shell_command_change_hook.run(info);
425 download_shell_command_change_hook.run(info);
432 onProgressChange: function (progress, request, cur_self_progress, max_self_progress,
433 cur_total_progress, max_total_progress,
435 var info = id_to_download_info.get(download.id);
437 dumpln("error: encountered unknown download in progress change");
440 info.mozilla_info = download;
441 download_progress_change_hook.run(info);
442 //dumpln("download progress change: " + download.source.spec + ": " + cur_self_progress + "/" + max_self_progress + " "
443 // + cur_total_progress + "/" + max_total_progress + ", " + download.state + ", " + download.id);
446 onSecurityChange: function (progress, request, state, download) {
449 onStateChange: function (progress, request, state_flags, status, download) {
453 var download_observer = {
454 observe: function (subject, topic, data) {
456 case "download-manager-remove-download":
459 // Remove all downloads
460 for (let i in id_to_download_info)
463 let id = subject.QueryInterface(Ci.nsISupportsPRUint32);
464 /* FIXME: determine if this should really be an error */
465 if (!(id in id_to_download_info)) {
466 dumpln("Error: download-manager-remove-download event received for unknown download: " + id);
470 for each (let i in ids) {
471 dumpln("deleting download: " + i);
472 let d = id_to_download_info[i];
474 download_removed_hook.run(d);
475 id_to_download_info.delete(i);
482 observer_service.addObserver(download_observer, "download-manager-remove-download", false);
485 download_manager_service.addListener(download_progress_listener);
487 dumpln("Failed to register download progress listener.");
493 let list = yield Downloads.getList(Downloads.ALL);
495 onDownloadAdded: function (download) {
496 // We never want the automatic launching to be used
497 // This is set by default when using nsIWebBrowserPersist
498 download.launchWhenSucceeded = false;
500 let info = match_registered_download(download.source.url);
503 info = new download_info(null, download);
504 dumpln("Encountered unknown new download");
506 info.attach(download);
509 onDownloadChanged: function (download) {
510 let info = lookup_download(download);
512 dumpln("error: onDownloadChanged: encountered unknown download");
514 download_progress_change_hook.run(info);
515 download_state_change_hook.run(info);
517 if (info.state == DOWNLOAD_FINISHED) {
518 download_finished_hook.run(info);
520 if (info.shell_command != null) {
521 info.running_shell_command = true;
524 yield shell_command_with_argument(info.shell_command,
525 info.target_file.path,
526 $cwd = info.shell_command_cwd);
528 handle_interactive_error(info.source_buffer.window, e);
530 if (info.temporary_status == DOWNLOAD_TEMPORARY_FOR_COMMAND)
531 if(delete_temporary_files_for_command) {
532 info.target_file.remove(false /* not recursive */);
534 info.running_shell_command = false;
535 download_shell_command_change_hook.run(info);
538 download_shell_command_change_hook.run(info);
543 onDownloadRemoved: function (download) {
544 let info = lookup_download(download);
546 dumpln("error: onDownloadRemoved: encountered unknown download");
548 dumpln("Removing download: " + info.source_uri_string);
550 download_removed_hook.run(info);
551 id_to_download_info.delete(download);
559 define_variable("download_buffer_min_update_interval", 2000,
560 "Minimum interval (in milliseconds) between updates in download progress buffers.\n" +
561 "Lowering this interval will increase the promptness of the progress display at " +
562 "the cost of using additional processor time.");
564 function download_buffer_modality (buffer, element) {
565 buffer.keymaps.push(download_buffer_keymap);
568 define_keywords("$info");
569 function download_buffer (window) {
570 this.constructor_begin();
572 special_buffer.call(this, window, forward_keywords(arguments));
573 this.info = arguments.$info;
574 this.local.cwd = this.info.target_file.parent;
575 this.description = this.info.source_uri_string;
578 this.progress_change_handler_fn = method_caller(this, this.handle_progress_change);
580 // With Downloads.jsm integration, download_progress_change_hook is redundant with download_state_change_hook
581 if (!use_downloads_jsm)
582 add_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
584 add_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
585 this.command_change_handler_fn = method_caller(this, this.update_command_field);
586 add_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
587 this.modalities.push(download_buffer_modality);
588 this.constructor_end();
590 download_buffer.prototype = {
591 constructor: download_buffer,
592 __proto__: special_buffer.prototype,
593 toString: function () "#<download_buffer>",
595 destroy: function () {
596 if (!use_downloads_jsm)
597 remove_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
599 remove_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
600 remove_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
602 // Remove all node references
603 delete this.status_textnode;
604 delete this.target_file_node;
605 delete this.transferred_div_node;
606 delete this.transferred_textnode;
607 delete this.progress_container_node;
608 delete this.progress_bar_node;
609 delete this.percent_textnode;
610 delete this.time_textnode;
611 delete this.command_div_node;
612 delete this.command_label_textnode;
613 delete this.command_textnode;
615 special_buffer.prototype.destroy.call(this);
618 update_title: function () {
620 // FIXME: do this properly
622 var info = this.info;
623 var append_transfer_info = false;
624 var append_speed_info = true;
627 case DOWNLOAD_DOWNLOADING:
628 label = "Downloading";
629 append_transfer_info = true;
631 case DOWNLOAD_FINISHED:
632 label = "Download complete";
634 case DOWNLOAD_FAILED:
635 label = "Download failed";
636 append_transfer_info = true;
637 append_speed_info = false;
639 case DOWNLOAD_CANCELED:
640 label = "Download canceled";
641 append_transfer_info = true;
642 append_speed_info = false;
644 case DOWNLOAD_PAUSED:
645 label = "Download paused";
646 append_transfer_info = true;
647 append_speed_info = false;
649 case DOWNLOAD_QUEUED:
651 label = "Download queued";
655 if (append_transfer_info) {
656 if (append_speed_info)
657 new_title = label + " at " + pretty_print_file_size(info.speed).join(" ") + "/s: ";
659 new_title = label + ": ";
660 var trans = pretty_print_file_size(info.amount_transferred);
661 if (info.size >= 0) {
662 var total = pretty_print_file_size(info.size);
663 if (trans[1] == total[1])
664 new_title += trans[0] + "/" + total[0] + " " + total[1];
666 new_title += trans.join(" ") + "/" + total.join(" ");
668 new_title += trans.join(" ");
669 if (info.percent_complete >= 0)
670 new_title += " (" + info.percent_complete + "%)";
673 if (new_title != this.title) {
674 this.title = new_title;
684 handle_progress_change: function () {
685 var cur_time = Date.now();
686 if (this.last_update == null ||
687 (cur_time - this.last_update) > download_buffer_min_update_interval ||
688 this.info.state != this.previous_state) {
690 if (this.update_title())
691 buffer_title_change_hook.run(this);
693 if (this.generated) {
694 this.update_fields();
696 this.previous_status = this.info.status;
697 this.last_update = cur_time;
701 generate: function () {
702 var d = this.document;
703 var g = new dom_generator(d, XHTML_NS);
705 /* Warning: If any additional node references are saved in
706 * this function, appropriate code to delete the saved
707 * properties must be added to destroy method. */
709 var info = this.info;
711 d.body.setAttribute("class", "download-buffer");
713 g.add_stylesheet("chrome://conkeror-gui/content/downloads.css");
716 var table = g.element("table", d.body);
718 row = g.element("tr", table, "class", "download-info", "id", "download-source");
719 cell = g.element("td", row, "class", "download-label");
720 this.status_textnode = g.text("", cell);
721 cell = g.element("td", row, "class", "download-value");
722 g.text(info.source_uri_string, cell);
724 row = g.element("tr", table, "class", "download-info", "id", "download-target");
725 cell = g.element("td", row, "class", "download-label");
727 if (info.temporary_status != DOWNLOAD_NOT_TEMPORARY)
728 target_label = "Temp. file:";
730 target_label = "Target:";
731 g.text(target_label, cell);
732 cell = g.element("td", row, "class", "download-value");
733 this.target_file_node = g.text("", cell);
735 row = g.element("tr", table, "class", "download-info", "id", "download-mime-type");
736 cell = g.element("td", row, "class", "download-label");
737 g.text("MIME type:", cell);
738 cell = g.element("td", row, "class", "download-value");
739 g.text(info.MIME_type || "unknown", cell);
741 this.transferred_div_node = row =
742 g.element("tr", table, "class", "download-info", "id", "download-transferred");
743 cell = g.element("td", row, "class", "download-label");
744 g.text("Transferred:", cell);
745 cell = g.element("td", row, "class", "download-value");
746 var sub_item = g.element("div", cell);
747 this.transferred_textnode = g.text("", sub_item);
748 sub_item = g.element("div", cell, "id", "download-percent");
749 this.percent_textnode = g.text("", sub_item);
750 this.progress_container_node = sub_item = g.element("div", cell, "id", "download-progress-container");
751 this.progress_bar_node = g.element("div", sub_item, "id", "download-progress-bar");
753 row = g.element("tr", table, "class", "download-info", "id", "download-time");
754 cell = g.element("td", row, "class", "download-label");
755 g.text("Time:", cell);
756 cell = g.element("td", row, "class", "download-value");
757 this.time_textnode = g.text("", cell);
759 if (info.action_description != null) {
760 row = g.element("tr", table, "class", "download-info", "id", "download-action");
761 cell = g.element("div", row, "class", "download-label");
762 g.text("Action:", cell);
763 cell = g.element("div", row, "class", "download-value");
764 g.text(info.action_description, cell);
767 this.command_div_node = row = g.element("tr", table, "class", "download-info", "id", "download-command");
768 cell = g.element("td", row, "class", "download-label");
769 this.command_label_textnode = g.text("Run command:", cell);
770 cell = g.element("td", row, "class", "download-value");
771 this.command_textnode = g.text("", cell);
773 this.update_fields();
774 this.update_command_field();
777 update_fields: function () {
780 var info = this.info;
782 switch (info.state) {
783 case DOWNLOAD_DOWNLOADING:
784 label = "Downloading";
786 case DOWNLOAD_FINISHED:
789 case DOWNLOAD_FAILED:
792 case DOWNLOAD_CANCELED:
795 case DOWNLOAD_PAUSED:
798 case DOWNLOAD_QUEUED:
803 this.status_textnode.nodeValue = label + ":";
804 this.target_file_node.nodeValue = info.target_file_text();
805 this.update_time_field();
808 if (info.state == DOWNLOAD_FINISHED)
809 tran_text = pretty_print_file_size(info.size).join(" ");
811 var trans = pretty_print_file_size(info.amount_transferred);
812 if (info.size >= 0) {
813 var total = pretty_print_file_size(info.size);
814 if (trans[1] == total[1])
815 tran_text += trans[0] + "/" + total[0] + " " + total[1];
817 tran_text += trans.join(" ") + "/" + total.join(" ");
819 tran_text += trans.join(" ");
821 this.transferred_textnode.nodeValue = tran_text;
822 if (info.percent_complete >= 0) {
823 this.progress_container_node.style.display = "";
824 this.percent_textnode.nodeValue = info.percent_complete + "%";
825 this.progress_bar_node.style.width = info.percent_complete + "%";
827 this.percent_textnode.nodeValue = "";
828 this.progress_container_node.style.display = "none";
831 this.update_command_field();
834 update_time_field: function () {
835 var info = this.info;
836 var elapsed_text = pretty_print_time((Date.now() - info.start_time) / 1000) + " elapsed";
838 if (info.state == DOWNLOAD_DOWNLOADING)
839 text = pretty_print_file_size(info.speed).join(" ") + "/s, ";
840 if (info.state == DOWNLOAD_DOWNLOADING &&
844 let remaining = (info.size - info.amount_transferred) / info.speed;
845 text += pretty_print_time(remaining) + " left (" + elapsed_text + ")";
848 this.time_textnode.nodeValue = text;
851 update_command_field: function () {
854 if (this.info.shell_command != null) {
855 this.command_div_node.style.display = "";
857 if (this.info.running_shell_command)
859 else if (this.info.state == DOWNLOAD_FINISHED)
860 label = "Ran command:";
862 label = "Run command:";
863 this.command_label_textnode.nodeValue = label;
864 this.command_textnode.nodeValue = this.info.shell_command;
866 this.command_div_node.style.display = "none";
870 function download_cancel (buffer) {
871 check_buffer(buffer, download_buffer);
872 var info = buffer.info;
874 buffer.window.minibuffer.message("Download canceled");
876 interactive("download-cancel",
877 "Cancel the current download.\n" +
878 "The download can later be retried using the `download-retry' "+
879 "command, but any data already transferred will be lost.",
881 let result = yield I.window.minibuffer.read_single_character_option(
882 $prompt = "Cancel this download? (y/n)",
883 $options = ["y", "n"]);
885 yield download_cancel(I.buffer);
888 function download_retry (buffer) {
889 check_buffer(buffer, download_buffer);
890 var info = buffer.info;
892 buffer.window.minibuffer.message("Download retried");
894 interactive("download-retry",
895 "Retry a failed or canceled download.\n" +
896 "This command can be used to retry a download that failed or "+
897 "was canceled using the `download-cancel' command. The download "+
898 "will begin from the start again.",
899 function (I) { yield download_retry(I.buffer); });
901 function download_pause (buffer) {
902 check_buffer(buffer, download_buffer);
903 yield buffer.info.pause();
904 buffer.window.minibuffer.message("Download paused");
906 interactive("download-pause",
907 "Pause the current download.\n" +
908 "The download can later be resumed using the `download-resume' command. "+
909 "The data already transferred will not be lost.",
910 function (I) { yield download_pause(I.buffer); });
912 function download_resume (buffer) {
913 check_buffer(buffer, download_buffer);
914 yield buffer.info.resume();
915 buffer.window.minibuffer.message("Download resumed");
917 interactive("download-resume",
918 "Resume the current download.\n" +
919 "This command can be used to resume a download paused using the "+
920 "`download-pause' command.",
921 function (I) { yield download_resume(I.buffer); });
923 function download_remove (buffer) {
924 check_buffer(buffer, download_buffer);
925 yield buffer.info.remove();
926 buffer.window.minibuffer.message("Download removed");
928 interactive("download-remove",
929 "Remove the current download from the download manager.\n" +
930 "This command can only be used on inactive (paused, canceled, "+
931 "completed, or failed) downloads.",
932 function (I) { yield download_remove(I.buffer); });
934 function download_retry_or_resume (buffer) {
935 check_buffer(buffer, download_buffer);
936 var info = buffer.info;
937 if (info.state == DOWNLOAD_PAUSED)
938 yield download_resume(buffer);
940 yield download_retry(buffer);
942 interactive("download-retry-or-resume",
943 "Retry or resume the current download.\n" +
944 "This command can be used to resume a download paused using the " +
945 "`download-pause' command or canceled using the `download-cancel' "+
947 function (I) { yield download_retry_or_resume(I.buffer); });
949 function download_pause_or_resume (buffer) {
950 check_buffer(buffer, download_buffer);
951 var info = buffer.info;
952 if (info.state == DOWNLOAD_PAUSED)
953 yield download_resume(buffer);
955 yield download_pause(buffer);
957 interactive("download-pause-or-resume",
958 "Pause or resume the current download.\n" +
959 "This command toggles the paused state of the current download.",
960 function (I) { yield download_pause_or_resume(I.buffer); });
962 function download_delete_target (buffer) {
963 check_buffer(buffer, download_buffer);
964 var info = buffer.info;
965 info.delete_target();
966 buffer.window.minibuffer.message("Deleted file: " + info.target_file.path);
968 interactive("download-delete-target",
969 "Delete the target file of the current download.\n" +
970 "This command can only be used if the download has finished successfully.",
971 function (I) { download_delete_target(I.buffer); });
973 function download_shell_command (buffer, cwd, cmd) {
974 check_buffer(buffer, download_buffer);
975 var info = buffer.info;
976 if (info.state == DOWNLOAD_FINISHED) {
977 shell_command_with_argument_blind(cmd, info.target_file.path, $cwd = cwd);
980 if (info.state != DOWNLOAD_DOWNLOADING &&
981 info.state != DOWNLOAD_PAUSED &&
982 info.state != DOWNLOAD_QUEUED)
984 info.throw_state_error();
986 if (cmd == null || cmd.length == 0)
987 info.set_shell_command(null, cwd);
989 info.set_shell_command(cmd, cwd);
990 buffer.window.minibuffer.message("Queued shell command: " + cmd);
992 interactive("download-shell-command",
993 "Run a shell command on the target file of the current download.\n"+
994 "If the download is still in progress, the shell command will be queued "+
995 "to run when the download finishes.",
997 var buffer = check_buffer(I.buffer, download_buffer);
998 var cwd = buffer.info.shell_command_cwd || I.local.cwd;
999 var cmd = yield I.minibuffer.read_shell_command(
1001 $initial_value = buffer.info.shell_command ||
1002 external_content_handlers.get(buffer.info.MIME_type));
1003 download_shell_command(buffer, cwd, cmd);
1006 function download_manager_ui () {}
1007 download_manager_ui.prototype = {
1008 constructor: download_manager_ui,
1009 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
1011 getAttention: function () {},
1012 show: function () {},
1017 interactive("download-manager-show-builtin-ui",
1018 "Show the built-in (Firefox-style) download manager window.",
1020 Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
1021 .getService(Ci.nsIDownloadManagerUI)
1030 define_variable("download_temporary_file_open_buffer_delay", 500,
1031 "Delay (in milliseconds) before a download buffer is opened for "+
1032 "temporary downloads. If the download completes before this amount "+
1033 "of time, no download buffer will be opened. This variable takes "+
1034 "effect only if `open_download_buffer_automatically' is in "+
1035 "`download_added_hook', which is the case by default.");
1037 define_variable("download_buffer_automatic_open_target", OPEN_NEW_WINDOW,
1038 "Target(s) for download buffers created by "+
1039 "`open_download_buffer_automatically'.");
1041 minibuffer_auto_complete_preferences.download = true;
1043 function download_completer (completions) {
1044 keywords(arguments);
1045 if (! use_downloads_jsm) {
1046 completions = function (visitor) {
1047 var dls = download_manager_service.activeDownloads;
1048 while (dls.hasMoreElements()) {
1049 let dl = dls.getNext();
1054 all_word_completer.call(this, forward_keywords(arguments),
1055 $completions = completions,
1056 $get_string = function (x) {
1057 if (use_downloads_jsm)
1058 return x.target.path;
1060 return x.displayName;
1062 $get_description = function (x) {
1063 if (use_downloads_jsm)
1064 return x.source.url;
1066 return x.source.spec
1069 download_completer.prototype = {
1070 constructor: download_completer,
1071 __proto__: all_word_completer.prototype,
1072 toString: function () "#<download_completer>"
1075 minibuffer.prototype.read_download = function () {
1077 $prompt = "Download",
1078 $auto_complete = "download",
1079 $auto_complete_initial = true,
1080 $require_match = true);
1081 if (use_downloads_jsm) {
1082 var list = yield Downloads.getList(Downloads.ALL);
1083 var all_downloads = yield list.getAll();
1084 var completer = new download_completer(all_downloads);
1086 completer = new download_completer();
1088 var result = yield this.read(forward_keywords(arguments),
1089 $completer = completer);
1090 yield co_return(result);
1094 function download_show (window, target, mozilla_info) {
1096 target = OPEN_NEW_WINDOW;
1097 var info = lookup_download(mozilla_info);
1099 info = new download_info(null, null);
1100 info.attach(mozilla_info, true /* existing */);
1102 create_buffer(window, buffer_creator(download_buffer, $info = info), target);
1105 function download_show_new_window (I) {
1106 var mozilla_info = yield I.minibuffer.read_download($prompt = "Show download:");
1107 download_show(I.window, OPEN_NEW_WINDOW, mozilla_info);
1110 function download_show_new_buffer (I) {
1111 var mozilla_info = yield I.minibuffer.read_download($prompt = "Show download:");
1112 download_show(I.window, OPEN_NEW_BUFFER, mozilla_info);
1115 function download_show_new_buffer_background (I) {
1116 var mozilla_info = yield I.minibuffer.read_download($prompt = "Show download:");
1117 download_show(I.window, OPEN_NEW_BUFFER_BACKGROUND, mozilla_info);
1120 function open_download_buffer_automatically (info) {
1121 var buf = info.source_buffer;
1122 var target = download_buffer_automatic_open_target;
1123 if (info.temporary_status == DOWNLOAD_NOT_TEMPORARY ||
1124 download_temporary_file_open_buffer_delay == 0)
1126 download_show(buf ? buf.window : null, target, info.mozilla_info);
1129 var finish = function finish () {
1132 add_hook.call(info, "download_finished_hook", finish);
1133 timer = call_after_timeout(function () {
1134 remove_hook.call(info, "download_finished_hook", finish);
1135 download_show(buf ? buf.window : null, target, info.mozilla_info);
1136 }, download_temporary_file_open_buffer_delay);
1139 add_hook("download_added_hook", open_download_buffer_automatically);
1141 interactive("download-show",
1142 "Prompt for an ongoing download and open a download buffer showing "+
1144 alternates(download_show_new_buffer,
1145 download_show_new_window));
1147 provide("download-manager");