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 Components.utils.import("resource://gre/modules/Downloads.jsm");
23 var use_downloads_jsm = true;
25 function lookup_download(download) {
26 return id_to_download_info.get(download);
29 var use_downloads_jsm = false;
31 function lookup_download(download) {
32 return id_to_download_info.get(download.id);
37 // Import these constants for convenience
38 const DOWNLOAD_NOTSTARTED = Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
39 const DOWNLOAD_DOWNLOADING = Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING;
40 const DOWNLOAD_FINISHED = Ci.nsIDownloadManager.DOWNLOAD_FINISHED;
41 const DOWNLOAD_FAILED = Ci.nsIDownloadManager.DOWNLOAD_FAILED;
42 const DOWNLOAD_CANCELED = Ci.nsIDownloadManager.DOWNLOAD_CANCELED;
43 const DOWNLOAD_PAUSED = Ci.nsIDownloadManager.DOWNLOAD_PAUSED;
44 const DOWNLOAD_QUEUED = Ci.nsIDownloadManager.DOWNLOAD_QUEUED;
45 const DOWNLOAD_BLOCKED = Ci.nsIDownloadManager.DOWNLOAD_BLOCKED;
46 const DOWNLOAD_SCANNING = Ci.nsIDownloadManager.DOWNLOAD_SCANNING;
49 const DOWNLOAD_NOT_TEMPORARY = 0;
50 const DOWNLOAD_TEMPORARY_FOR_ACTION = 1;
51 const DOWNLOAD_TEMPORARY_FOR_COMMAND = 2;
53 function download_info (source_buffer, mozilla_info, target_file) {
54 this.source_buffer = source_buffer;
55 this.target_file = target_file;
56 if (mozilla_info != null)
57 this.attach(mozilla_info);
59 download_info.prototype = {
60 constructor: download_info,
63 shell_command_cwd: null,
64 temporary_status: DOWNLOAD_NOT_TEMPORARY,
65 action_description: null,
66 set_shell_command: function (str, cwd) {
67 this.shell_command = str;
68 this.shell_command_cwd = cwd;
69 if (this.mozilla_info)
70 download_shell_command_change_hook.run(this);
74 * None of the following members may be used until attach is called
77 // Reflectors to properties of nsIDownload
78 get source () { return this.mozilla_info.source; },
79 get id () { return this.mozilla_info.id; },
80 get referrer () { return this.mozilla_info.referrer; },
82 target_file_text: function () {
83 let target = this.target_file.path;
84 let display = this.display_name;
85 if (target.indexOf(display, target.length - display.length) == -1)
86 target += " (" + display + ")";
90 throw_if_removed: function () {
92 throw interactive_error("Download has already been removed from the download manager.");
95 throw_state_error: function () {
97 case DOWNLOAD_DOWNLOADING:
98 throw interactive_error("Download is already in progress.");
99 case DOWNLOAD_FINISHED:
100 throw interactive_error("Download has already completed.");
101 case DOWNLOAD_FAILED:
102 throw interactive_error("Download has already failed.");
103 case DOWNLOAD_CANCELED:
104 throw interactive_error("Download has already been canceled.");
105 case DOWNLOAD_PAUSED:
106 throw interactive_error("Download has already been paused.");
107 case DOWNLOAD_QUEUED:
108 throw interactive_error("Download is queued.");
110 throw new Error("Download has unexpected state: " + this.state);
114 // Download manager operations
115 cancel: function () {
116 this.throw_if_removed();
117 switch (this.state) {
118 case DOWNLOAD_DOWNLOADING:
119 case DOWNLOAD_PAUSED:
120 case DOWNLOAD_QUEUED:
121 if (use_downloads_jsm) {
122 yield this.mozilla_info.finalize(true);
125 download_manager_service.cancelDownload(this.id);
127 throw interactive_error("Download cannot be canceled.");
132 this.throw_state_error();
137 this.throw_if_removed();
138 switch (this.state) {
139 case DOWNLOAD_CANCELED:
140 case DOWNLOAD_FAILED:
141 if (use_downloads_jsm) {
142 yield this.mozilla_info.start();
145 download_manager_service.retryDownload(this.id);
147 throw interactive_error("Download cannot be retried.");
152 this.throw_state_error();
156 resume: function () {
157 this.throw_if_removed();
158 switch (this.state) {
159 case DOWNLOAD_PAUSED:
160 if (use_downloads_jsm) {
161 yield this.mozilla_info.start();
165 download_manager_service.resumeDownload(this.id);
167 throw interactive_error("Download cannot be resumed.");
172 this.throw_state_error();
177 this.throw_if_removed();
178 switch (this.state) {
179 case DOWNLOAD_DOWNLOADING:
180 case DOWNLOAD_QUEUED:
181 if (use_downloads_jsm) {
182 yield this.mozilla_info.cancel();
185 download_manager_service.pauseDownload(this.id);
187 throw interactive_error("Download cannot be paused.");
192 this.throw_state_error();
196 remove: function () {
197 this.throw_if_removed();
198 switch (this.state) {
199 case DOWNLOAD_FAILED:
200 case DOWNLOAD_CANCELED:
201 case DOWNLOAD_FINISHED:
202 if (use_downloads_jsm) {
203 let list = yield Downloads.getList(Downloads.ALL);
204 yield list.remove(this.mozilla_info);
207 download_manager_service.removeDownload(this.id);
209 throw interactive_error("Download cannot be removed.");
214 throw interactive_error("Download is still in progress.");
218 delete_target: function () {
219 if (this.state != DOWNLOAD_FINISHED)
220 throw interactive_error("Download has not finished.");
222 this.target_file.remove(false);
226 case Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
227 throw interactive_error("File has already been deleted.");
228 case Cr.NS_ERROR_FILE_ACCESS_DENIED:
229 throw interactive_error("Access denied");
230 case Cr.NS_ERROR_FILE_DIR_NOT_EMPTY:
231 throw interactive_error("Failed to delete file.");
239 if (!use_downloads_jsm) {
240 download_info.prototype.__proto__ = {
241 attach: function (mozilla_info, existing) {
242 if (!this.target_file)
243 this.__defineGetter__("target_file", function () {
244 return this.mozilla_info.targetFile;
246 else if (this.target_file.path != mozilla_info.targetFile.path)
247 throw interactive_error("Download target file unexpected.");
249 this.mozilla_info = mozilla_info;
251 if (use_downloads_jsm) {
252 id_to_download_info.set(mozilla_info, this);
254 id_to_download_info.set(mozilla_info.id, this);
258 existing_download_added_hook.run(this);
260 download_added_hook.run(this);
262 get source_uri_string () { return this.mozilla_info.source.spec; },
263 get source_uri () { return this.mozilla_info.source; },
264 get display_name () { return this.mozilla_info.displayName; },
265 get amount_transferred () { return this.mozilla_info.amountTransferred; },
266 get percent_complete () { return this.mozilla_info.percentComplete; },
267 get speed () { return this.mozilla_info.speed; },
268 get state () { return this.mozilla_info.state; },
269 get start_time () { return this.mozilla_info.startTime / 1000; },
270 get MIME_info () { return this.mozilla_info.MIMEInfo; },
273 return this.MIME_info.MIMEType;
277 var s = this.mozilla_info.size;
278 /* nsIDownload.size is a PRUint64, and will have value
279 * LL_MAXUINT (2^64 - 1) to indicate an unknown size. Because
280 * JavaScript only has a double numerical type, this value
281 * cannot be represented exactly, so 2^36 is used instead as the cutoff. */
282 if (s < 68719476736 /* 2^36 */)
288 download_info.prototype.__proto__ = {
289 attach: function (mozilla_info, existing) {
290 if (!this.target_file)
291 this.__defineGetter__("target_file", function () {
292 return make_file(this.mozilla_info.target.path);
294 else if (this.target_file.path != mozilla_info.target.path)
295 throw interactive_error("Download target file unexpected.");
297 this.mozilla_info = mozilla_info;
298 id_to_download_info.set(mozilla_info, this);
301 existing_download_added_hook.run(this);
303 download_added_hook.run(this);
305 get source_uri () { return make_uri(this.mozilla_info.source.url); },
306 get source_uri_string () { return this.mozilla_info.source.url; },
307 get display_name () { return this.mozilla_info.target.path; },
308 get amount_transferred () { return this.mozilla_info.currentBytes; },
309 get percent_complete () { return this.mozilla_info.progress; },
310 get speed () { return 1000 * this.amount_transferred / (Date.now() - this.start_time); },
311 get start_time () { return this.mozilla_info.startTime.getTime(); },
312 get MIME_type () { return this.mozilla_info.contentType; },
314 if (this.mozilla_info.succeeded)
315 return DOWNLOAD_FINISHED;
316 if (this.mozilla_info.canceled)
317 return DOWNLOAD_CANCELED;
318 if (this.mozilla_info.error)
319 return DOWNLOAD_FAILED;
320 if (!this.mozilla_info.startTime)
321 return DOWNLOAD_NOTSTARTED;
322 return DOWNLOAD_DOWNLOADING;
325 if (this.mozilla_info.hasProgress)
326 return this.mozilla_info.totalBytes;
332 var define_download_local_hook = simple_local_hook_definer();
334 function register_download (buffer, source_uri, target_file) {
335 var info = new download_info(buffer, null, target_file);
336 info.registered_time_stamp = Date.now();
337 info.registered_source_uri = source_uri;
338 unmanaged_download_info_list.push(info);
342 function match_registered_download (source) {
343 let list = unmanaged_download_info_list;
345 for (let i = 0; i < list.length; ++i) {
347 if (x.registered_source_uri.spec == source) {
351 if (t - x.registered_time_stamp > download_info_max_queue_delay) {
360 define_download_local_hook("existing_download_added_hook");
361 define_download_local_hook("download_added_hook");
362 define_download_local_hook("download_removed_hook");
363 define_download_local_hook("download_finished_hook");
364 define_download_local_hook("download_progress_change_hook");
365 define_download_local_hook("download_state_change_hook");
366 define_download_local_hook("download_shell_command_change_hook");
368 define_variable('delete_temporary_files_for_command', true,
369 'If this is set to true, temporary files downloaded to run a command '+
370 'on them will be deleted once the command completes. If not, the file '+
371 'will stay around forever unless deleted outside the browser.');
373 var download_info_max_queue_delay = 100;
375 if (!use_downloads_jsm) {
377 var download_progress_listener = {
378 QueryInterface: generate_QI(Ci.nsIDownloadProgressListener),
380 onDownloadStateChange: function (state, download) {
382 /* FIXME: Determine if only new downloads will have this state
383 * as their previous state. */
385 dumpln("download state change: " + download.source.spec + ": " + state + ", " + download.state + ", " + download.id);
387 if (state == DOWNLOAD_NOTSTARTED) {
388 info = match_registered_download(download.source.spec);
390 info = new download_info(null, download);
391 dumpln("error: encountered unknown new download");
393 info.attach(download);
396 info = id_to_download_info.get(download.id);
398 dumpln("Error: encountered unknown download");
401 info.mozilla_info = download;
402 download_state_change_hook.run(info);
403 if (info.state == DOWNLOAD_FINISHED) {
404 download_finished_hook.run(info);
406 if (info.shell_command != null) {
407 info.running_shell_command = true;
410 yield shell_command_with_argument(info.shell_command,
411 info.target_file.path,
412 $cwd = info.shell_command_cwd);
414 handle_interactive_error(info.source_buffer.window, e);
416 if (info.temporary_status == DOWNLOAD_TEMPORARY_FOR_COMMAND)
417 if(delete_temporary_files_for_command) {
418 info.target_file.remove(false /* not recursive */);
420 info.running_shell_command = false;
421 download_shell_command_change_hook.run(info);
424 download_shell_command_change_hook.run(info);
431 onProgressChange: function (progress, request, cur_self_progress, max_self_progress,
432 cur_total_progress, max_total_progress,
434 var info = id_to_download_info.get(download.id);
436 dumpln("error: encountered unknown download in progress change");
439 info.mozilla_info = download;
440 download_progress_change_hook.run(info);
441 //dumpln("download progress change: " + download.source.spec + ": " + cur_self_progress + "/" + max_self_progress + " "
442 // + cur_total_progress + "/" + max_total_progress + ", " + download.state + ", " + download.id);
445 onSecurityChange: function (progress, request, state, download) {
448 onStateChange: function (progress, request, state_flags, status, download) {
452 var download_observer = {
453 observe: function (subject, topic, data) {
455 case "download-manager-remove-download":
458 // Remove all downloads
459 for (let i in id_to_download_info)
462 let id = subject.QueryInterface(Ci.nsISupportsPRUint32);
463 /* FIXME: determine if this should really be an error */
464 if (!(id in id_to_download_info)) {
465 dumpln("Error: download-manager-remove-download event received for unknown download: " + id);
469 for each (let i in ids) {
470 dumpln("deleting download: " + i);
471 let d = id_to_download_info[i];
473 download_removed_hook.run(d);
474 id_to_download_info.delete(i);
481 observer_service.addObserver(download_observer, "download-manager-remove-download", false);
484 download_manager_service.addListener(download_progress_listener);
486 dumpln("Failed to register download progress listener.");
492 let list = yield Downloads.getList(Downloads.ALL);
494 onDownloadAdded: function (download) {
495 let info = match_registered_download(download.source.url);
498 info = new download_info(null, download);
499 dumpln("Encountered unknown new download");
501 info.attach(download);
504 onDownloadChanged: function (download) {
505 let info = lookup_download(download);
507 dumpln("error: onDownloadChanged: encountered unknown download");
509 download_progress_change_hook.run(info);
510 download_state_change_hook.run(info);
512 if (info.state == DOWNLOAD_FINISHED) {
513 download_finished_hook.run(info);
515 if (info.shell_command != null) {
516 info.running_shell_command = true;
519 yield shell_command_with_argument(info.shell_command,
520 info.target_file.path,
521 $cwd = info.shell_command_cwd);
523 handle_interactive_error(info.source_buffer.window, e);
525 if (info.temporary_status == DOWNLOAD_TEMPORARY_FOR_COMMAND)
526 if(delete_temporary_files_for_command) {
527 info.target_file.remove(false /* not recursive */);
529 info.running_shell_command = false;
530 download_shell_command_change_hook.run(info);
533 download_shell_command_change_hook.run(info);
538 onDownloadRemoved: function (download) {
539 let info = lookup_download(download);
541 dumpln("error: onDownloadRemoved: encountered unknown download");
543 dumpln("Removing download: " + info.source_uri_string);
545 download_removed_hook.run(info);
546 id_to_download_info.delete(download);
554 define_variable("download_buffer_min_update_interval", 2000,
555 "Minimum interval (in milliseconds) between updates in download progress buffers.\n" +
556 "Lowering this interval will increase the promptness of the progress display at " +
557 "the cost of using additional processor time.");
559 function download_buffer_modality (buffer, element) {
560 buffer.keymaps.push(download_buffer_keymap);
563 define_keywords("$info");
564 function download_buffer (window) {
565 this.constructor_begin();
567 special_buffer.call(this, window, forward_keywords(arguments));
568 this.info = arguments.$info;
569 this.local.cwd = this.info.target_file.parent;
570 this.description = this.info.source_uri_string;
573 this.progress_change_handler_fn = method_caller(this, this.handle_progress_change);
575 // With Downloads.jsm integration, download_progress_change_hook is redundant with download_state_change_hook
576 if (!use_downloads_jsm)
577 add_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
579 add_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
580 this.command_change_handler_fn = method_caller(this, this.update_command_field);
581 add_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
582 this.modalities.push(download_buffer_modality);
583 this.constructor_end();
585 download_buffer.prototype = {
586 constructor: download_buffer,
587 __proto__: special_buffer.prototype,
588 toString: function () "#<download_buffer>",
590 destroy: function () {
591 if (!use_downloads_jsm)
592 remove_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
594 remove_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
595 remove_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
597 // Remove all node references
598 delete this.status_textnode;
599 delete this.target_file_node;
600 delete this.transferred_div_node;
601 delete this.transferred_textnode;
602 delete this.progress_container_node;
603 delete this.progress_bar_node;
604 delete this.percent_textnode;
605 delete this.time_textnode;
606 delete this.command_div_node;
607 delete this.command_label_textnode;
608 delete this.command_textnode;
610 special_buffer.prototype.destroy.call(this);
613 update_title: function () {
615 // FIXME: do this properly
617 var info = this.info;
618 var append_transfer_info = false;
619 var append_speed_info = true;
622 case DOWNLOAD_DOWNLOADING:
623 label = "Downloading";
624 append_transfer_info = true;
626 case DOWNLOAD_FINISHED:
627 label = "Download complete";
629 case DOWNLOAD_FAILED:
630 label = "Download failed";
631 append_transfer_info = true;
632 append_speed_info = false;
634 case DOWNLOAD_CANCELED:
635 label = "Download canceled";
636 append_transfer_info = true;
637 append_speed_info = false;
639 case DOWNLOAD_PAUSED:
640 label = "Download paused";
641 append_transfer_info = true;
642 append_speed_info = false;
644 case DOWNLOAD_QUEUED:
646 label = "Download queued";
650 if (append_transfer_info) {
651 if (append_speed_info)
652 new_title = label + " at " + pretty_print_file_size(info.speed).join(" ") + "/s: ";
654 new_title = label + ": ";
655 var trans = pretty_print_file_size(info.amount_transferred);
656 if (info.size >= 0) {
657 var total = pretty_print_file_size(info.size);
658 if (trans[1] == total[1])
659 new_title += trans[0] + "/" + total[0] + " " + total[1];
661 new_title += trans.join(" ") + "/" + total.join(" ");
663 new_title += trans.join(" ");
664 if (info.percent_complete >= 0)
665 new_title += " (" + info.percent_complete + "%)";
668 if (new_title != this.title) {
669 this.title = new_title;
679 handle_progress_change: function () {
680 var cur_time = Date.now();
681 if (this.last_update == null ||
682 (cur_time - this.last_update) > download_buffer_min_update_interval ||
683 this.info.state != this.previous_state) {
685 if (this.update_title())
686 buffer_title_change_hook.run(this);
688 if (this.generated) {
689 this.update_fields();
691 this.previous_status = this.info.status;
692 this.last_update = cur_time;
696 generate: function () {
697 var d = this.document;
698 var g = new dom_generator(d, XHTML_NS);
700 /* Warning: If any additional node references are saved in
701 * this function, appropriate code to delete the saved
702 * properties must be added to destroy method. */
704 var info = this.info;
706 d.body.setAttribute("class", "download-buffer");
708 g.add_stylesheet("chrome://conkeror-gui/content/downloads.css");
711 var table = g.element("table", d.body);
713 row = g.element("tr", table, "class", "download-info", "id", "download-source");
714 cell = g.element("td", row, "class", "download-label");
715 this.status_textnode = g.text("", cell);
716 cell = g.element("td", row, "class", "download-value");
717 g.text(info.source_uri_string, cell);
719 row = g.element("tr", table, "class", "download-info", "id", "download-target");
720 cell = g.element("td", row, "class", "download-label");
722 if (info.temporary_status != DOWNLOAD_NOT_TEMPORARY)
723 target_label = "Temp. file:";
725 target_label = "Target:";
726 g.text(target_label, cell);
727 cell = g.element("td", row, "class", "download-value");
728 this.target_file_node = g.text("", cell);
730 row = g.element("tr", table, "class", "download-info", "id", "download-mime-type");
731 cell = g.element("td", row, "class", "download-label");
732 g.text("MIME type:", cell);
733 cell = g.element("td", row, "class", "download-value");
734 g.text(info.MIME_type || "unknown", cell);
736 this.transferred_div_node = row =
737 g.element("tr", table, "class", "download-info", "id", "download-transferred");
738 cell = g.element("td", row, "class", "download-label");
739 g.text("Transferred:", cell);
740 cell = g.element("td", row, "class", "download-value");
741 var sub_item = g.element("div", cell);
742 this.transferred_textnode = g.text("", sub_item);
743 sub_item = g.element("div", cell, "id", "download-percent");
744 this.percent_textnode = g.text("", sub_item);
745 this.progress_container_node = sub_item = g.element("div", cell, "id", "download-progress-container");
746 this.progress_bar_node = g.element("div", sub_item, "id", "download-progress-bar");
748 row = g.element("tr", table, "class", "download-info", "id", "download-time");
749 cell = g.element("td", row, "class", "download-label");
750 g.text("Time:", cell);
751 cell = g.element("td", row, "class", "download-value");
752 this.time_textnode = g.text("", cell);
754 if (info.action_description != null) {
755 row = g.element("tr", table, "class", "download-info", "id", "download-action");
756 cell = g.element("div", row, "class", "download-label");
757 g.text("Action:", cell);
758 cell = g.element("div", row, "class", "download-value");
759 g.text(info.action_description, cell);
762 this.command_div_node = row = g.element("tr", table, "class", "download-info", "id", "download-command");
763 cell = g.element("td", row, "class", "download-label");
764 this.command_label_textnode = g.text("Run command:", cell);
765 cell = g.element("td", row, "class", "download-value");
766 this.command_textnode = g.text("", cell);
768 this.update_fields();
769 this.update_command_field();
772 update_fields: function () {
775 var info = this.info;
777 switch (info.state) {
778 case DOWNLOAD_DOWNLOADING:
779 label = "Downloading";
781 case DOWNLOAD_FINISHED:
784 case DOWNLOAD_FAILED:
787 case DOWNLOAD_CANCELED:
790 case DOWNLOAD_PAUSED:
793 case DOWNLOAD_QUEUED:
798 this.status_textnode.nodeValue = label + ":";
799 this.target_file_node.nodeValue = info.target_file_text();
800 this.update_time_field();
803 if (info.state == DOWNLOAD_FINISHED)
804 tran_text = pretty_print_file_size(info.size).join(" ");
806 var trans = pretty_print_file_size(info.amount_transferred);
807 if (info.size >= 0) {
808 var total = pretty_print_file_size(info.size);
809 if (trans[1] == total[1])
810 tran_text += trans[0] + "/" + total[0] + " " + total[1];
812 tran_text += trans.join(" ") + "/" + total.join(" ");
814 tran_text += trans.join(" ");
816 this.transferred_textnode.nodeValue = tran_text;
817 if (info.percent_complete >= 0) {
818 this.progress_container_node.style.display = "";
819 this.percent_textnode.nodeValue = info.percent_complete + "%";
820 this.progress_bar_node.style.width = info.percent_complete + "%";
822 this.percent_textnode.nodeValue = "";
823 this.progress_container_node.style.display = "none";
826 this.update_command_field();
829 update_time_field: function () {
830 var info = this.info;
831 var elapsed_text = pretty_print_time((Date.now() - info.start_time) / 1000) + " elapsed";
833 if (info.state == DOWNLOAD_DOWNLOADING)
834 text = pretty_print_file_size(info.speed).join(" ") + "/s, ";
835 if (info.state == DOWNLOAD_DOWNLOADING &&
839 let remaining = (info.size - info.amount_transferred) / info.speed;
840 text += pretty_print_time(remaining) + " left (" + elapsed_text + ")";
843 this.time_textnode.nodeValue = text;
846 update_command_field: function () {
849 if (this.info.shell_command != null) {
850 this.command_div_node.style.display = "";
852 if (this.info.running_shell_command)
854 else if (this.info.state == DOWNLOAD_FINISHED)
855 label = "Ran command:";
857 label = "Run command:";
858 this.command_label_textnode.nodeValue = label;
859 this.command_textnode.nodeValue = this.info.shell_command;
861 this.command_div_node.style.display = "none";
865 function download_cancel (buffer) {
866 check_buffer(buffer, download_buffer);
867 var info = buffer.info;
869 buffer.window.minibuffer.message("Download canceled");
871 interactive("download-cancel",
872 "Cancel the current download.\n" +
873 "The download can later be retried using the `download-retry' "+
874 "command, but any data already transferred will be lost.",
876 let result = yield I.window.minibuffer.read_single_character_option(
877 $prompt = "Cancel this download? (y/n)",
878 $options = ["y", "n"]);
880 yield download_cancel(I.buffer);
883 function download_retry (buffer) {
884 check_buffer(buffer, download_buffer);
885 var info = buffer.info;
887 buffer.window.minibuffer.message("Download retried");
889 interactive("download-retry",
890 "Retry a failed or canceled download.\n" +
891 "This command can be used to retry a download that failed or "+
892 "was canceled using the `download-cancel' command. The download "+
893 "will begin from the start again.",
894 function (I) { yield download_retry(I.buffer); });
896 function download_pause (buffer) {
897 check_buffer(buffer, download_buffer);
898 yield buffer.info.pause();
899 buffer.window.minibuffer.message("Download paused");
901 interactive("download-pause",
902 "Pause the current download.\n" +
903 "The download can later be resumed using the `download-resume' command. "+
904 "The data already transferred will not be lost.",
905 function (I) { yield download_pause(I.buffer); });
907 function download_resume (buffer) {
908 check_buffer(buffer, download_buffer);
909 yield buffer.info.resume();
910 buffer.window.minibuffer.message("Download resumed");
912 interactive("download-resume",
913 "Resume the current download.\n" +
914 "This command can be used to resume a download paused using the "+
915 "`download-pause' command.",
916 function (I) { yield download_resume(I.buffer); });
918 function download_remove (buffer) {
919 check_buffer(buffer, download_buffer);
920 yield buffer.info.remove();
921 buffer.window.minibuffer.message("Download removed");
923 interactive("download-remove",
924 "Remove the current download from the download manager.\n" +
925 "This command can only be used on inactive (paused, canceled, "+
926 "completed, or failed) downloads.",
927 function (I) { yield download_remove(I.buffer); });
929 function download_retry_or_resume (buffer) {
930 check_buffer(buffer, download_buffer);
931 var info = buffer.info;
932 if (info.state == DOWNLOAD_PAUSED)
933 yield download_resume(buffer);
935 yield download_retry(buffer);
937 interactive("download-retry-or-resume",
938 "Retry or resume the current download.\n" +
939 "This command can be used to resume a download paused using the " +
940 "`download-pause' command or canceled using the `download-cancel' "+
942 function (I) { yield download_retry_or_resume(I.buffer); });
944 function download_pause_or_resume (buffer) {
945 check_buffer(buffer, download_buffer);
946 var info = buffer.info;
947 if (info.state == DOWNLOAD_PAUSED)
948 yield download_resume(buffer);
950 yield download_pause(buffer);
952 interactive("download-pause-or-resume",
953 "Pause or resume the current download.\n" +
954 "This command toggles the paused state of the current download.",
955 function (I) { yield download_pause_or_resume(I.buffer); });
957 function download_delete_target (buffer) {
958 check_buffer(buffer, download_buffer);
959 var info = buffer.info;
960 info.delete_target();
961 buffer.window.minibuffer.message("Deleted file: " + info.target_file.path);
963 interactive("download-delete-target",
964 "Delete the target file of the current download.\n" +
965 "This command can only be used if the download has finished successfully.",
966 function (I) { download_delete_target(I.buffer); });
968 function download_shell_command (buffer, cwd, cmd) {
969 check_buffer(buffer, download_buffer);
970 var info = buffer.info;
971 if (info.state == DOWNLOAD_FINISHED) {
972 shell_command_with_argument_blind(cmd, info.target_file.path, $cwd = cwd);
975 if (info.state != DOWNLOAD_DOWNLOADING && info.state != DOWNLOAD_PAUSED && info.state != DOWNLOAD_QUEUED)
976 info.throw_state_error();
977 if (cmd == null || cmd.length == 0)
978 info.set_shell_command(null, cwd);
980 info.set_shell_command(cmd, cwd);
981 buffer.window.minibuffer.message("Queued shell command: " + cmd);
983 interactive("download-shell-command",
984 "Run a shell command on the target file of the current download.\n"+
985 "If the download is still in progress, the shell command will be queued "+
986 "to run when the download finishes.",
988 var buffer = check_buffer(I.buffer, download_buffer);
989 var cwd = buffer.info.shell_command_cwd || I.local.cwd;
990 var cmd = yield I.minibuffer.read_shell_command(
992 $initial_value = buffer.info.shell_command ||
993 external_content_handlers.get(buffer.info.MIME_type));
994 download_shell_command(buffer, cwd, cmd);
997 function download_manager_ui () {}
998 download_manager_ui.prototype = {
999 constructor: download_manager_ui,
1000 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
1002 getAttention: function () {},
1003 show: function () {},
1008 interactive("download-manager-show-builtin-ui",
1009 "Show the built-in (Firefox-style) download manager window.",
1011 Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
1012 .getService(Ci.nsIDownloadManagerUI)
1021 define_variable("download_temporary_file_open_buffer_delay", 500,
1022 "Delay (in milliseconds) before a download buffer is opened for "+
1023 "temporary downloads. If the download completes before this amount "+
1024 "of time, no download buffer will be opened. This variable takes "+
1025 "effect only if `open_download_buffer_automatically' is in "+
1026 "`download_added_hook', which is the case by default.");
1028 define_variable("download_buffer_automatic_open_target", OPEN_NEW_WINDOW,
1029 "Target(s) for download buffers created by "+
1030 "`open_download_buffer_automatically'.");
1032 minibuffer_auto_complete_preferences.download = true;
1034 if (use_downloads_jsm) {
1035 minibuffer.prototype.read_download = function () {
1037 $prompt = "Download",
1038 $completer = function () {
1039 let list = yield Downloads.getList(Downloads.ALL);
1040 let all_downloads = yield list.getAll();
1041 let c = all_word_completer($completions = all_downloads,
1042 $get_string = function (x) x.target.path,
1043 $get_description = function (x) x.source.url,
1044 $get_value = function (x) x);
1045 yield co_return(c.apply(this, arguments));
1047 $auto_complete = "download",
1048 $auto_complete_initial = true,
1049 $match_required = true);
1050 var result = yield this.read(forward_keywords(arguments));
1051 yield co_return(result);
1054 minibuffer.prototype.read_download = function () {
1056 $prompt = "Download",
1057 $completer = all_word_completer(
1058 $completions = function (visitor) {
1059 var dls = download_manager_service.activeDownloads;
1060 while (dls.hasMoreElements()) {
1061 let dl = dls.getNext();
1065 $get_string = function (x) x.displayName,
1066 $get_description = function (x) x.source.spec,
1067 $get_value = function (x) x),
1068 $auto_complete = "download",
1069 $auto_complete_initial = true,
1070 $match_required = true);
1071 var result = yield this.read(forward_keywords(arguments));
1072 yield co_return(result);
1076 function download_show (window, target, mozilla_info) {
1078 target = OPEN_NEW_WINDOW;
1079 var info = lookup_download(mozilla_info);
1081 info = new download_info(null, null);
1082 info.attach(mozilla_info, true /* existing */);
1084 create_buffer(window, buffer_creator(download_buffer, $info = info), target);
1087 function download_show_new_window (I) {
1088 var mozilla_info = yield I.minibuffer.read_download($prompt = "Show download:");
1089 download_show(I.window, OPEN_NEW_WINDOW, mozilla_info);
1092 function download_show_new_buffer (I) {
1093 var mozilla_info = yield I.minibuffer.read_download($prompt = "Show download:");
1094 download_show(I.window, OPEN_NEW_BUFFER, mozilla_info);
1097 function download_show_new_buffer_background (I) {
1098 var mozilla_info = yield I.minibuffer.read_download($prompt = "Show download:");
1099 download_show(I.window, OPEN_NEW_BUFFER_BACKGROUND, mozilla_info);
1102 function open_download_buffer_automatically (info) {
1103 var buf = info.source_buffer;
1104 var target = download_buffer_automatic_open_target;
1105 if (info.temporary_status == DOWNLOAD_NOT_TEMPORARY ||
1106 download_temporary_file_open_buffer_delay == 0)
1108 download_show(buf ? buf.window : null, target, info.mozilla_info);
1111 function finish () {
1114 add_hook.call(info, "download_finished_hook", finish);
1115 timer = call_after_timeout(function () {
1116 remove_hook.call(info, "download_finished_hook", finish);
1117 download_show(buf ? buf.window : null, target, info.mozilla_info);
1118 }, download_temporary_file_open_buffer_delay);
1121 add_hook("download_added_hook", open_download_buffer_automatically);
1123 interactive("download-show",
1124 "Prompt for an ongoing download and open a download buffer showing "+
1126 alternates(download_show_new_buffer,
1127 download_show_new_window));
1129 provide("download-manager");