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");
13 var download_manager_service = Cc["@mozilla.org/download-manager;1"]
14 .getService(Ci.nsIDownloadManager);
16 var download_manager_builtin_ui = Components
17 .classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
18 .getService(Ci.nsIDownloadManagerUI);
21 var unmanaged_download_info_list = [];
22 var id_to_download_info = {};
24 // Import these constants for convenience
25 const DOWNLOAD_NOTSTARTED = Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
26 const DOWNLOAD_DOWNLOADING = Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING;
27 const DOWNLOAD_FINISHED = Ci.nsIDownloadManager.DOWNLOAD_FINISHED;
28 const DOWNLOAD_FAILED = Ci.nsIDownloadManager.DOWNLOAD_FAILED;
29 const DOWNLOAD_CANCELED = Ci.nsIDownloadManager.DOWNLOAD_CANCELED;
30 const DOWNLOAD_PAUSED = Ci.nsIDownloadManager.DOWNLOAD_PAUSED;
31 const DOWNLOAD_QUEUED = Ci.nsIDownloadManager.DOWNLOAD_QUEUED;
32 const DOWNLOAD_BLOCKED = Ci.nsIDownloadManager.DOWNLOAD_BLOCKED;
33 const DOWNLOAD_SCANNING = Ci.nsIDownloadManager.DOWNLOAD_SCANNING;
36 const DOWNLOAD_NOT_TEMPORARY = 0;
37 const DOWNLOAD_TEMPORARY_FOR_ACTION = 1;
38 const DOWNLOAD_TEMPORARY_FOR_COMMAND = 2;
40 function download_info (source_buffer, mozilla_info, target_file) {
41 this.source_buffer = source_buffer;
42 this.target_file = target_file;
43 if (mozilla_info != null)
44 this.attach(mozilla_info);
46 download_info.prototype = {
47 attach : function (mozilla_info) {
48 if (!this.target_file)
49 this.__defineGetter__("target_file", function(){
50 return this.mozilla_info.targetFile;
52 else if (this.target_file.path != mozilla_info.targetFile.path)
53 throw interactive_error("Download target file unexpected.");
54 this.mozilla_info = mozilla_info;
55 id_to_download_info[mozilla_info.id] = this;
56 download_added_hook.run(this);
63 shell_command_cwd : null,
65 temporary_status : DOWNLOAD_NOT_TEMPORARY,
67 action_description : null,
69 set_shell_command : function (str, cwd) {
70 this.shell_command = str;
71 this.shell_command_cwd = cwd;
72 if (this.mozilla_info)
73 download_shell_command_change_hook.run(this);
77 * None of the following members may be used until attach is called
80 // Reflectors to properties of nsIDownload
81 get state () { return this.mozilla_info.state; },
82 get display_name () { return this.mozilla_info.displayName; },
83 get amount_transferred () { return this.mozilla_info.amountTransferred; },
84 get percent_complete () { return this.mozilla_info.percentComplete; },
86 var s = this.mozilla_info.size;
87 /* nsIDownload.size is a PRUint64, and will have value
88 * LL_MAXUINT (2^64 - 1) to indicate an unknown size. Because
89 * JavaScript only has a double numerical type, this value
90 * cannot be represented exactly, so 2^36 is used instead as the cutoff. */
91 if (s < 68719476736 /* 2^36 */)
95 get source () { return this.mozilla_info.source; },
96 get start_time () { return this.mozilla_info.startTime; },
97 get speed () { return this.mozilla_info.speed; },
98 get MIME_info () { return this.mozilla_info.MIMEInfo; },
101 return this.MIME_info.MIMEType;
104 get id () { return this.mozilla_info.id; },
105 get referrer () { return this.mozilla_info.referrer; },
107 target_file_text : function () {
108 let target = this.target_file.path;
109 let display = this.display_name;
110 if (target.indexOf(display, target.length - display.length) == -1)
111 target += " (" + display + ")";
115 throw_if_removed : function () {
117 throw interactive_error("Download has already been removed from the download manager.");
120 throw_state_error : function () {
121 switch (this.state) {
122 case DOWNLOAD_DOWNLOADING:
123 throw interactive_error("Download is already in progress.");
124 case DOWNLOAD_FINISHED:
125 throw interactive_error("Download has already completed.");
126 case DOWNLOAD_FAILED:
127 throw interactive_error("Download has already failed.");
128 case DOWNLOAD_CANCELED:
129 throw interactive_error("Download has already been canceled.");
130 case DOWNLOAD_PAUSED:
131 throw interactive_error("Download has already been paused.");
132 case DOWNLOAD_QUEUED:
133 throw interactive_error("Download is queued.");
135 throw new Error("Download has unexpected state: " + this.state);
139 // Download manager operations
140 cancel : function () {
141 this.throw_if_removed();
142 switch (this.state) {
143 case DOWNLOAD_DOWNLOADING:
144 case DOWNLOAD_PAUSED:
145 case DOWNLOAD_QUEUED:
147 download_manager_service.cancelDownload(this.id);
149 throw interactive_error("Download cannot be canceled.");
153 this.throw_state_error();
157 retry : function () {
158 this.throw_if_removed();
159 switch (this.state) {
160 case DOWNLOAD_CANCELED:
161 case DOWNLOAD_FAILED:
163 download_manager_service.retryDownload(this.id);
165 throw interactive_error("Download cannot be retried.");
169 this.throw_state_error();
173 resume : function () {
174 this.throw_if_removed();
175 switch (this.state) {
176 case DOWNLOAD_PAUSED:
178 download_manager_service.resumeDownload(this.id);
180 throw interactive_error("Download cannot be resumed.");
184 this.throw_state_error();
188 pause : function () {
189 this.throw_if_removed();
190 switch (this.state) {
191 case DOWNLOAD_DOWNLOADING:
192 case DOWNLOAD_QUEUED:
194 download_manager_service.pauseDownload(this.id);
196 throw interactive_error("Download cannot be paused.");
200 this.throw_state_error();
204 remove : function () {
205 this.throw_if_removed();
206 switch (this.state) {
207 case DOWNLOAD_FAILED:
208 case DOWNLOAD_CANCELED:
209 case DOWNLOAD_FINISHED:
211 download_manager_service.removeDownload(this.id);
213 throw interactive_error("Download cannot be removed.");
217 throw interactive_error("Download is still in progress.");
221 delete_target : function () {
222 if (this.state != DOWNLOAD_FINISHED)
223 throw interactive_error("Download has not finished.");
225 this.target_file.remove(false);
229 case Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
230 throw interactive_error("File has already been deleted.");
231 case Cr.NS_ERROR_FILE_ACCESS_DENIED:
232 throw interactive_error("Access denied");
233 case Cr.NS_ERROR_FILE_DIR_NOT_EMPTY:
234 throw interactive_error("Failed to delete file.");
242 var define_download_local_hook = simple_local_hook_definer();
244 // FIXME: add more parameters
245 function register_download (buffer, source_uri, target_file) {
246 var info = new download_info(buffer, null, target_file);
247 info.registered_time_stamp = Date.now();
248 info.registered_source_uri = source_uri;
249 unmanaged_download_info_list.push(info);
253 function match_registered_download (mozilla_info) {
254 let list = unmanaged_download_info_list;
256 for (let i = 0; i < list.length; ++i) {
258 if (x.registered_source_uri == mozilla_info.source) {
262 if (t - x.registered_time_stamp > download_info_max_queue_delay) {
271 define_download_local_hook("download_added_hook");
272 define_download_local_hook("download_removed_hook");
273 define_download_local_hook("download_finished_hook");
274 define_download_local_hook("download_progress_change_hook");
275 define_download_local_hook("download_state_change_hook");
276 define_download_local_hook("download_shell_command_change_hook");
278 define_variable('delete_temporary_files_for_command', true,
279 'If this is set to true, temporary files downloaded to run a command '+
280 'on them will be deleted once the command completes. If not, the file '+
281 'will stay around forever unless deleted outside the browser.');
283 var download_info_max_queue_delay = 100;
285 var download_progress_listener = {
286 QueryInterface: generate_QI(Ci.nsIDownloadProgressListener),
288 onDownloadStateChange : function (state, download) {
290 /* FIXME: Determine if only new downloads will have this state
291 * as their previous state. */
293 dumpln("download state change: " + download.source.spec + ": " + state + ", " + download.state + ", " + download.id);
295 if (state == DOWNLOAD_NOTSTARTED) {
296 info = match_registered_download(download);
298 info = new download_info(null, download);
299 dumpln("error: encountered unknown new download");
301 info.attach(download);
304 info = id_to_download_info[download.id];
306 dumpln("Error: encountered unknown download");
309 info.mozilla_info = download;
310 download_state_change_hook.run(info);
311 if (info.state == DOWNLOAD_FINISHED) {
312 download_finished_hook.run(info);
314 if (info.shell_command != null) {
315 info.running_shell_command = true;
316 co_call(function () {
318 yield shell_command_with_argument(info.shell_command,
319 info.target_file.path,
320 $cwd = info.shell_command_cwd);
322 handle_interactive_error(info.source_buffer.window, e);
324 if (info.temporary_status == DOWNLOAD_TEMPORARY_FOR_COMMAND)
325 if(delete_temporary_files_for_command) {
326 info.target_file.remove(false /* not recursive */);
328 info.running_shell_command = false;
329 download_shell_command_change_hook.run(info);
332 download_shell_command_change_hook.run(info);
339 onProgressChange : function (progress, request, cur_self_progress, max_self_progress,
340 cur_total_progress, max_total_progress,
342 var info = id_to_download_info[download.id];
344 dumpln("error: encountered unknown download in progress change");
347 info.mozilla_info = download;
348 download_progress_change_hook.run(info);
349 //dumpln("download progress change: " + download.source.spec + ": " + cur_self_progress + "/" + max_self_progress + " "
350 // + cur_total_progress + "/" + max_total_progress + ", " + download.state + ", " + download.id);
353 onSecurityChange : function (progress, request, state, download) {
356 onStateChange : function (progress, request, state_flags, status, download) {
360 var download_observer = {
361 observe : function (subject, topic, data) {
363 case "download-manager-remove-download":
366 // Remove all downloads
367 for (let i in id_to_download_info)
370 let id = subject.QueryInterface(Ci.nsISupportsPRUint32);
371 /* FIXME: determine if this should really be an error */
372 if (!(id in id_to_download_info)) {
373 dumpln("Error: download-manager-remove-download event received for unknown download: " + id);
377 for each (let i in ids) {
378 dumpln("deleting download: " + i);
379 let d = id_to_download_info[i];
381 download_removed_hook.run(d);
382 delete id_to_download_info[i];
388 observer_service.addObserver(download_observer, "download-manager-remove-download", false);
390 download_manager_service.addListener(download_progress_listener);
392 function pretty_print_file_size (val) {
393 const GIBI = 1073741824; /* 2^30 */
394 const MEBI = 1048576; /* 2^20 */
395 const KIBI = 1024; /* 2^10 */
401 else if (val < MEBI) {
404 } else if (val < GIBI) {
417 return [val.toFixed(precision), suffix];
420 function pretty_print_time (val) {
421 val = Math.round(val);
422 var seconds = val % 60;
424 val = Math.floor(val / 60);
426 var minutes = val % 60;
428 var hours = Math.floor(val / 60);
433 parts.push(hours + " hours");
435 parts.push("1 hour");
438 parts.push(minutes + " minutes");
439 else if (minutes == 1)
440 parts.push("1 minute");
442 if (minutes <= 1 && hours == 0) {
444 parts.push(seconds + " seconds");
446 parts.push("1 second");
449 return parts.join(", ");
452 define_variable("download_buffer_min_update_interval", 2000,
453 "Minimum interval (in milliseconds) between updates in download progress buffers.\n" +
454 "Lowering this interval will increase the promptness of the progress display at " +
455 "the cost of using additional processor time.");
457 function download_buffer_modality (buffer, element) {
458 buffer.keymaps.push(download_buffer_keymap);
461 define_keywords("$info");
462 function download_buffer (window, element) {
463 this.constructor_begin();
465 special_buffer.call(this, window, element, forward_keywords(arguments));
466 this.info = arguments.$info;
467 this.local.cwd = this.info.mozilla_info.targetFile.parent;
468 this.description = this.info.mozilla_info.source.spec;
471 this.progress_change_handler_fn = method_caller(this, this.handle_progress_change);
472 add_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
473 add_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
474 this.command_change_handler_fn = method_caller(this, this.update_command_field);
475 add_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
476 this.modalities.push(download_buffer_modality);
477 this.constructor_end();
479 download_buffer.prototype = {
480 __proto__: special_buffer.prototype,
482 handle_kill : function () {
483 special_buffer.prototype.handle_kill.call(this);
484 remove_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
485 remove_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
486 remove_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
488 // Remove all node references
489 delete this.status_textnode;
490 delete this.target_file_node;
491 delete this.transferred_div_node;
492 delete this.transferred_textnode;
493 delete this.progress_container_node;
494 delete this.progress_bar_node;
495 delete this.percent_textnode;
496 delete this.time_textnode;
497 delete this.command_div_node;
498 delete this.command_label_textnode;
499 delete this.command_textnode;
502 update_title : function () {
503 // FIXME: do this properly
505 var info = this.info;
506 var append_transfer_info = false;
507 var append_speed_info = true;
510 case DOWNLOAD_DOWNLOADING:
511 label = "Downloading";
512 append_transfer_info = true;
514 case DOWNLOAD_FINISHED:
515 label = "Download complete";
517 case DOWNLOAD_FAILED:
518 label = "Download failed";
519 append_transfer_info = true;
520 append_speed_info = false;
522 case DOWNLOAD_CANCELED:
523 label = "Download canceled";
524 append_transfer_info = true;
525 append_speed_info = false;
527 case DOWNLOAD_PAUSED:
528 label = "Download paused";
529 append_transfer_info = true;
530 append_speed_info = false;
532 case DOWNLOAD_QUEUED:
534 label = "Download queued";
538 if (append_transfer_info) {
539 if (append_speed_info)
540 new_title = label + " at " + pretty_print_file_size(info.speed).join(" ") + "/s: ";
542 new_title = label + ": ";
543 var trans = pretty_print_file_size(info.amount_transferred);
544 if (info.size >= 0) {
545 var total = pretty_print_file_size(info.size);
546 if (trans[1] == total[1])
547 new_title += trans[0] + "/" + total[0] + " " + total[1];
549 new_title += trans.join(" ") + "/" + total.join(" ");
551 new_title += trans.join(" ");
552 if (info.percent_complete >= 0)
553 new_title += " (" + info.percent_complete + "%)";
556 if (new_title != this.title) {
557 this.title = new_title;
563 handle_progress_change : function () {
564 var cur_time = Date.now();
565 if (this.last_update == null ||
566 (cur_time - this.last_update) > download_buffer_min_update_interval ||
567 this.info.state != this.previous_state) {
569 if (this.update_title())
570 buffer_title_change_hook.run(this);
572 if (this.generated) {
573 this.update_fields();
575 this.previous_status = this.info.status;
576 this.last_update = cur_time;
580 generate : function () {
581 var d = this.document;
582 var g = new dom_generator(d, XHTML_NS);
584 /* Warning: If any additional node references are saved in
585 * this function, appropriate code to delete the saved
586 * properties must be added to handle_kill. */
588 var info = this.info;
590 d.body.setAttribute("class", "download-buffer");
592 g.add_stylesheet("chrome://conkeror-gui/content/downloads.css");
595 var table = g.element("table", d.body);
597 row = g.element("tr", table, "class", "download-info", "id", "download-source");
598 cell = g.element("td", row, "class", "download-label");
599 this.status_textnode = g.text("", cell);
600 cell = g.element("td", row, "class", "download-value");
601 g.text(info.source.spec, cell);
603 row = g.element("tr", table, "class", "download-info", "id", "download-target");
604 cell = g.element("td", row, "class", "download-label");
606 if (info.temporary_status != DOWNLOAD_NOT_TEMPORARY)
607 target_label = "Temp. file:";
609 target_label = "Target:";
610 g.text(target_label, cell);
611 cell = g.element("td", row, "class", "download-value");
612 this.target_file_node = g.text("", cell);
614 row = g.element("tr", table, "class", "download-info", "id", "download-mime-type");
615 cell = g.element("td", row, "class", "download-label");
616 g.text("MIME type:", cell);
617 cell = g.element("td", row, "class", "download-value");
618 g.text(info.MIME_type || "unknown", cell);
620 this.transferred_div_node = row =
621 g.element("tr", table, "class", "download-info", "id", "download-transferred");
622 cell = g.element("td", row, "class", "download-label");
623 g.text("Transferred:", cell);
624 cell = g.element("td", row, "class", "download-value");
625 var sub_item = g.element("div", cell);
626 this.transferred_textnode = g.text("", sub_item);
627 sub_item = g.element("div", cell, "id", "download-percent");
628 this.percent_textnode = g.text("", sub_item);
629 this.progress_container_node = sub_item = g.element("div", cell, "id", "download-progress-container");
630 this.progress_bar_node = g.element("div", sub_item, "id", "download-progress-bar");
632 row = g.element("tr", table, "class", "download-info", "id", "download-time");
633 cell = g.element("td", row, "class", "download-label");
634 g.text("Time:", cell);
635 cell = g.element("td", row, "class", "download-value");
636 this.time_textnode = g.text("", cell);
638 if (info.action_description != null) {
639 row = g.element("tr", table, "class", "download-info", "id", "download-action");
640 cell = g.element("div", row, "class", "download-label");
641 g.text("Action:", cell);
642 cell = g.element("div", row, "class", "download-value");
643 g.text(info.action_description, cell);
646 this.command_div_node = row = g.element("tr", table, "class", "download-info", "id", "download-command");
647 cell = g.element("td", row, "class", "download-label");
648 this.command_label_textnode = g.text("Run command:", cell);
649 cell = g.element("td", row, "class", "download-value");
650 this.command_textnode = g.text("", cell);
653 this.update_fields();
654 this.update_command_field();
657 update_fields : function () {
660 var info = this.info;
663 case DOWNLOAD_DOWNLOADING:
664 label = "Downloading";
666 case DOWNLOAD_FINISHED:
669 case DOWNLOAD_FAILED:
672 case DOWNLOAD_CANCELED:
675 case DOWNLOAD_PAUSED:
678 case DOWNLOAD_QUEUED:
683 this.status_textnode.nodeValue = label + ":";
684 this.target_file_node.nodeValue = info.target_file_text();
685 this.update_time_field();
688 if (info.state == DOWNLOAD_FINISHED)
689 tran_text = pretty_print_file_size(info.size).join(" ");
691 var trans = pretty_print_file_size(info.amount_transferred);
692 if (info.size >= 0) {
693 var total = pretty_print_file_size(info.size);
694 if (trans[1] == total[1])
695 tran_text += trans[0] + "/" + total[0] + " " + total[1];
697 tran_text += trans.join(" ") + "/" + total.join(" ");
699 tran_text += trans.join(" ");
701 this.transferred_textnode.nodeValue = tran_text;
702 if (info.percent_complete >= 0) {
703 this.progress_container_node.style.display = "";
704 this.percent_textnode.nodeValue = info.percent_complete + "%";
705 this.progress_bar_node.style.width = info.percent_complete + "%";
707 this.percent_textnode.nodeValue = "";
708 this.progress_container_node.style.display = "none";
711 this.update_command_field();
714 update_time_field : function () {
715 var info = this.info;
716 var elapsed_text = pretty_print_time((Date.now() - info.start_time / 1000) / 1000) + " elapsed";
718 if (info.state == DOWNLOAD_DOWNLOADING) {
719 text = pretty_print_file_size(info.speed).join(" ") + "/s, ";
721 if (info.state == DOWNLOAD_DOWNLOADING &&
724 let remaining = (info.size - info.amount_transferred) / info.speed;
725 text += pretty_print_time(remaining) + " left (" + elapsed_text + ")";
729 this.time_textnode.nodeValue = text;
732 update_command_field : function () {
735 if (this.info.shell_command != null) {
736 this.command_div_node.style.display = "";
738 if (this.info.running_shell_command)
740 else if (this.info.state == DOWNLOAD_FINISHED)
741 label = "Ran command:";
743 label = "Run command:";
744 this.command_label_textnode.nodeValue = label;
745 this.command_textnode.nodeValue = this.info.shell_command;
747 this.command_div_node.style.display = "none";
752 function download_cancel (buffer) {
753 check_buffer(buffer, download_buffer);
754 var info = buffer.info;
756 buffer.window.minibuffer.message("Download canceled");
758 interactive("download-cancel",
759 "Cancel the current download.\n" +
760 "The download can later be retried using the `download-retry' command, but any " +
761 "data already transferred will be lost.",
763 let result = yield I.window.minibuffer.read_single_character_option(
764 $prompt = "Cancel this download? (y/n)",
765 $options = ["y", "n"]);
767 download_cancel(I.buffer);
770 function download_retry (buffer) {
771 check_buffer(buffer, download_buffer);
772 var info = buffer.info;
774 buffer.window.minibuffer.message("Download retried");
776 interactive("download-retry",
777 "Retry a failed or canceled download.\n" +
778 "This command can be used to retry a download that failed or was canceled using " +
779 "the `download-cancel' command. The download will begin from the start again.",
780 function (I) {download_retry(I.buffer);});
782 function download_pause (buffer) {
783 check_buffer(buffer, download_buffer);
785 buffer.window.minibuffer.message("Download paused");
787 interactive("download-pause",
788 "Pause the current download.\n" +
789 "The download can later be resumed using the `download-resume' command. The " +
790 "data already transferred will not be lost.",
791 function (I) {download_pause(I.buffer);});
793 function download_resume (buffer) {
794 check_buffer(buffer, download_buffer);
795 buffer.info.resume();
796 buffer.window.minibuffer.message("Download resumed");
798 interactive("download-resume",
799 "Resume the current download.\n" +
800 "This command can be used to resume a download paused using the `download-pause' command.",
801 function (I) { download_resume(I.buffer); });
803 function download_remove (buffer) {
804 check_buffer(buffer, download_buffer);
805 buffer.info.remove();
806 buffer.window.minibuffer.message("Download removed");
808 interactive("download-remove",
809 "Remove the current download from the download manager.\n" +
810 "This command can only be used on inactive (paused, canceled, "+
811 "completed, or failed) downloads.",
812 function (I) {download_remove(I.buffer);});
814 function download_retry_or_resume (buffer) {
815 check_buffer(buffer, download_buffer);
816 var info = buffer.info;
817 if (info.state == DOWNLOAD_PAUSED)
818 download_resume(buffer);
820 download_retry(buffer);
822 interactive("download-retry-or-resume",
823 "Retry or resume the current download.\n" +
824 "This command can be used to resume a download paused using the `download-pause' " +
825 "command or canceled using the `download-cancel' command.",
826 function (I) {download_retry_or_resume(I.buffer);});
828 function download_pause_or_resume (buffer) {
829 check_buffer(buffer, download_buffer);
830 var info = buffer.info;
831 if (info.state == DOWNLOAD_PAUSED)
832 download_resume(buffer);
834 download_pause(buffer);
836 interactive("download-pause-or-resume",
837 "Pause or resume the current download.\n" +
838 "This command toggles the paused state of the current download.",
839 function (I) {download_pause_or_resume(I.buffer);});
841 function download_delete_target (buffer) {
842 check_buffer(buffer, download_buffer);
843 var info = buffer.info;
844 info.delete_target();
845 buffer.window.minibuffer.message("Deleted file: " + info.target_file.path);
847 interactive("download-delete-target",
848 "Delete the target file of the current download.\n" +
849 "This command can only be used if the download has finished successfully.",
850 function (I) {download_delete_target(I.buffer);});
852 function download_shell_command (buffer, cwd, cmd) {
853 check_buffer(buffer, download_buffer);
854 var info = buffer.info;
855 if (info.state == DOWNLOAD_FINISHED) {
856 shell_command_with_argument_blind(cmd, info.target_file.path, $cwd = cwd);
859 if (info.state != DOWNLOAD_DOWNLOADING && info.state != DOWNLOAD_PAUSED && info.state != DOWNLOAD_QUEUED)
860 info.throw_state_error();
861 if (cmd == null || cmd.length == 0)
862 info.set_shell_command(null, cwd);
864 info.set_shell_command(cmd, cwd);
865 buffer.window.minibuffer.message("Queued shell command: " + cmd);
867 interactive("download-shell-command",
868 "Run a shell command on the target file of the current download.\n" +
869 "If the download is still in progress, the shell command will be queued " +
870 "to run when the download finishes.",
872 var buffer = check_buffer(I.buffer, download_buffer);
873 var cwd = buffer.info.shell_command_cwd || I.local.cwd;
874 var cmd = yield I.minibuffer.read_shell_command(
876 $initial_value = buffer.info.shell_command ||
877 external_content_handlers.get(buffer.info.MIME_type));
878 download_shell_command(buffer, cwd, cmd);
881 function download_manager_ui () {}
882 download_manager_ui.prototype = {
883 QueryInterface : XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
885 getAttention : function () {},
886 show : function () {},
891 function download_manager_show_builtin_ui (window) {
892 download_manager_builtin_ui.show(window);
894 interactive("download-manager-show-builtin-ui",
895 "Show the built-in (Firefox-style) download manager user interface.",
896 function (I) {download_manager_show_builtin_ui(I.window);});
899 define_variable("download_temporary_file_open_buffer_delay", 500,
900 "Delay (in milliseconds) before a download buffer is opened for "+
901 "temporary downloads. If the download completes before this amount "+
902 "of time, no download buffer will be opened. This variable takes "+
903 "effect only if `open_download_buffer_automatically' is in "+
904 "`download_added_hook', which is the case by default.");
907 define_variable("download_buffer_automatic_open_target",
908 [OPEN_NEW_WINDOW, OPEN_NEW_BUFFER_BACKGROUND],
909 "Target(s) for download buffers created by "+
910 "`open_download_buffer_automatically' and `download-show'.\n"+
911 "It can be a single target or an array of two targets. When it is an "+
912 "array, the `download-show' command will use the second target when "+
913 "called with universal-argument.");
916 function open_download_buffer_automatically (info, target) {
917 var buf = info.source_buffer;
918 if (target == null) {
919 if (typeof(download_buffer_automatic_open_target) == "object")
920 target = download_buffer_automatic_open_target[0];
922 target = download_buffer_automatic_open_target;
925 target = OPEN_NEW_WINDOW;
926 if (info.temporary_status == DOWNLOAD_NOT_TEMPORARY ||
927 download_temporary_file_open_buffer_delay == 0)
929 create_buffer(buf.window, buffer_creator(download_buffer, $info = info), target);
935 add_hook.call(info, "download_finished_hook", finish);
936 timer = call_after_timeout(function () {
937 remove_hook.call(info, "download_finished_hook", finish);
938 create_buffer(buf.window, buffer_creator(download_buffer, $info = info), target);
939 }, download_temporary_file_open_buffer_delay);
942 add_hook("download_added_hook", open_download_buffer_automatically);
949 minibuffer_auto_complete_preferences.download = true;
951 minibuffer.prototype.read_download = function () {
953 $prompt = "Download",
954 $completer = all_word_completer(
955 $completions = function (visitor) {
956 var dls = download_manager_service.activeDownloads;
957 while (dls.hasMoreElements()) {
958 let dl = dls.getNext();
959 visitor(id_to_download_info[dl.id]);
962 $get_string = function (x) x.display_name,
963 $get_description = function (x) x.source.spec,
964 $get_value = function (x) x),
965 $auto_complete = "download",
966 $auto_complete_initial = true,
967 $match_required = true);
968 var result = yield this.read(forward_keywords(arguments));
969 yield co_return(result);
972 interactive("download-show",
973 "Prompt for an ongoing download and open a download buffer showing "+
974 "its progress. When called with universal argument, the second "+
975 "target from `download_buffer_automatic_open_target' will be used.",
978 if (I.P && typeof(download_buffer_automatic_open_target) == "object")
979 target = download_buffer_automatic_open_target[1];
980 open_download_buffer_automatically(
981 (yield I.minibuffer.read_download($prompt = "Show download:")),