2 * (C) Copyright 2008 Jeremy Maitin-Shepard
4 * Use, modification, and distribution are subject to the terms specified in the
8 require("special-buffer.js");
9 require("mime-type-override.js");
10 require("minibuffer-read-mime-type.js");
12 var download_manager_service = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
13 //var download_manager_ui = Cc["@mozilla.org/download-manager-ui;1"].getService(Ci.nsIDownloadManagerUI);
14 var download_manager_builtin_ui = Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
15 .getService(Ci.nsIDownloadManagerUI);
17 /* This implements nsIHelperAppLauncherDialog interface. */
18 function download_helper()
20 download_helper.prototype = {
21 QueryInterface: generate_QI(Ci.nsIHelperAppLauncherDialog, Ci.nsIWebProgressListener2),
23 handle_show: function () {
24 var action_chosen = false;
26 var can_view_internally = this.frame != null &&
27 can_override_mime_type_for_uri(this.launcher.source);
29 this.panel = create_info_panel(this.window, "download-panel",
30 [["downloading", "Downloading:", this.launcher.source.spec],
31 ["mime-type", "Mime type:", this.launcher.MIMEInfo.MIMEType]]);
32 var action = yield this.window.minibuffer.read_single_character_option(
33 $prompt = "Action to perform: (s: save; o: open; O: open URL; c: copy URL; " +
34 (can_view_internally ? "i: view internally; t: view as text)" : ")"),
35 $options = (can_view_internally ? ["s", "o", "O", "c", "i", "t"] : ["s", "o", "O", "c"]));
38 var suggested_path = suggest_save_path_from_file_name(this.launcher.suggestedFileName, this.buffer);
39 var file = yield this.window.minibuffer.read_file_check_overwrite(
40 $prompt = "Save to file:",
41 $initial_value = suggested_path,
43 register_download(this.buffer, this.launcher.source);
44 this.launcher.saveToDisk(file, false);
47 } else if (action == "o") {
48 var cwd = this.buffer ? this.buffer.cwd : default_directory.path;
49 var mime_type = this.launcher.MIMEInfo.MIMEType;
50 var suggested_action = get_mime_type_external_handler(mime_type);
51 var command = yield this.window.minibuffer.read_shell_command(
52 $initial_value = suggested_action,
54 var file = get_temporary_file(this.launcher.suggestedFileName);
55 var info = register_download(this.buffer, this.launcher.source);
56 info.temporary_status = DOWNLOAD_TEMPORARY_FOR_COMMAND;
57 info.set_shell_command(command, cwd);
58 this.launcher.saveToDisk(file, false);
60 } else if (action == "O") {
62 this.abort(); // abort download
63 let mime_type = this.launcher.MIMEInfo.MIMEType;
64 let cwd = this.buffer ? this.buffer.cwd : this.window.buffers.current.cwd;
65 let cmd = yield this.window.minibuffer.read_shell_command(
67 $initial_value = get_mime_type_external_handler(mime_type));
68 shell_command_with_argument_blind(cmd, this.launcher.source.spec, $cwd = cwd);
69 } else if (action == "c") {
71 this.abort(); // abort download
72 let uri = this.launcher.source.spec;
73 writeToClipboard(uri);
74 this.window.minibuffer.message("Copied: " + uri);
75 } else /* if (action == "i" || action == "t") */ {
78 mime_type = "text/plain";
80 let suggested_type = this.launcher.MIMEInfo.MIMEType;
81 if (gecko_viewable_mime_type_list.indexOf(suggested_type) == -1)
82 suggested_type = "text/plain";
83 mime_type = yield this.window.minibuffer.read_gecko_viewable_mime_type(
84 $prompt = "View internally as",
85 $initial_value = suggested_type,
89 this.abort(); // abort before reloading
91 override_mime_type_for_next_load(this.launcher.source, mime_type);
92 this.frame.location = this.launcher.source.spec; // reload
95 handle_interactive_error(this.window, e);
103 show : function (launcher, context, reason) {
104 this.launcher = launcher;
106 // Get associated buffer; if that fails (hopefully not), just get any window
111 frame = context.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal);
112 window = get_window_from_frame(frame.top);
114 buffer = get_buffer_from_frame(window, frame);
116 window = get_recent_conkeror_window();
118 if (window == null) {
119 // FIXME: need to handle this case perhaps where no windows exist
120 this.abort(); // for now, just cancel the download
126 this.window = window;
127 this.buffer = buffer;
129 co_call(this.handle_show());
132 abort : function () {
133 const NS_BINDING_ABORTED = 0x804b0002;
134 this.launcher.cancel(NS_BINDING_ABORTED);
137 cleanup : function () {
139 this.panel.destroy();
141 this.launcher = null;
147 promptForSaveToFile : function(launcher, context, default_file, suggested_file_extension) {
153 var unmanaged_download_info_list = [];
154 var id_to_download_info = {};
156 // Import these constants for convenience
157 const DOWNLOAD_NOTSTARTED = Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
158 const DOWNLOAD_DOWNLOADING = Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING;
159 const DOWNLOAD_FINISHED = Ci.nsIDownloadManager.DOWNLOAD_FINISHED;
160 const DOWNLOAD_FAILED = Ci.nsIDownloadManager.DOWNLOAD_FAILED;
161 const DOWNLOAD_CANCELED = Ci.nsIDownloadManager.DOWNLOAD_CANCELED;
162 const DOWNLOAD_PAUSED = Ci.nsIDownloadManager.DOWNLOAD_PAUSED;
163 const DOWNLOAD_QUEUED = Ci.nsIDownloadManager.DOWNLOAD_QUEUED;
164 const DOWNLOAD_BLOCKED = Ci.nsIDownloadManager.DOWNLOAD_BLOCKED;
165 const DOWNLOAD_SCANNING = Ci.nsIDownloadManager.DOWNLOAD_SCANNING;
168 const DOWNLOAD_NOT_TEMPORARY = 0;
169 const DOWNLOAD_TEMPORARY_FOR_ACTION = 1;
170 const DOWNLOAD_TEMPORARY_FOR_COMMAND = 2;
172 function download_info(source_buffer, mozilla_info, target_file) {
173 this.source_buffer = source_buffer;
174 this.target_file = target_file;
175 if (mozilla_info != null)
176 this.attach(mozilla_info);
178 download_info.prototype = {
179 attach : function (mozilla_info) {
180 if (!this.target_file)
181 target_file = this.mozilla_info.targetFile;
182 else if (this.target_file.path != mozilla_info.targetFile.path)
183 throw interactive_error("Download target file unexpected.");
184 this.mozilla_info = mozilla_info;
185 id_to_download_info[mozilla_info.id] = this;
186 download_added_hook.run(this);
191 shell_command : null,
193 shell_command_cwd : null,
195 temporary_status : DOWNLOAD_NOT_TEMPORARY,
197 action_description : null,
199 set_shell_command : function (str, cwd) {
200 this.shell_command = str;
201 this.shell_command_cwd = cwd;
202 if (this.mozilla_info)
203 download_shell_command_change_hook.run(this);
207 * None of the following members may be used until attach is called
210 // Reflectors to properties of nsIDownload
211 get state () { return this.mozilla_info.state; },
212 get display_name () { return this.mozilla_info.displayName; },
213 get amount_transferred () { return this.mozilla_info.amountTransferred; },
214 get percent_complete () { return this.mozilla_info.percentComplete; },
216 var s = this.mozilla_info.size;
217 /* nsIDownload.size is a PRUint64, and will have value
218 * LL_MAXUINT (2^64 - 1) to indicate an unknown size. Because
219 * JavaScript only has a double numerical type, this value
220 * cannot be represented exactly, so 2^36 is used instead as the cutoff. */
221 if (s < 68719476736 /* 2^36 */)
225 get source () { return this.mozilla_info.source; },
226 get start_time () { return this.mozilla_info.startTime; },
227 get speed () { return this.mozilla_info.speed; },
228 get MIME_info () { return this.mozilla_info.MIMEInfo; },
231 return this.MIME_info.MIMEType;
234 get id () { return this.mozilla_info.id; },
235 get referrer () { return this.mozilla_info.referrer; },
237 target_file_text : function() {
238 let target = this.target_file.path;
239 let display = this.display_name;
240 if (target.indexOf(display, target.length - display.length) == -1)
241 target += " (" + display + ")";
245 throw_if_removed : function () {
247 throw interactive_error("Download has already been removed from the download manager.");
250 throw_state_error : function () {
251 switch (this.state) {
252 case DOWNLOAD_DOWNLOADING:
253 throw interactive_error("Download is already in progress.");
254 case DOWNLOAD_FINISHED:
255 throw interactive_error("Download has already completed.");
256 case DOWNLOAD_FAILED:
257 throw interactive_error("Download has already failed.");
258 case DOWNLOAD_CANCELED:
259 throw interactive_error("Download has already been canceled.");
260 case DOWNLOAD_PAUSED:
261 throw interactive_error("Download has already been paused.");
262 case DOWNLOAD_QUEUED:
263 throw interactive_error("Download is queued.");
265 throw new Error("Download has unexpected state: " + this.state);
269 // Download manager operations
270 cancel : function () {
271 this.throw_if_removed();
272 switch (this.state) {
273 case DOWNLOAD_DOWNLOADING:
274 case DOWNLOAD_PAUSED:
275 case DOWNLOAD_QUEUED:
277 download_manager_service.cancelDownload(this.id);
279 throw interactive_error("Download cannot be canceled.");
283 this.throw_state_error();
287 retry : function () {
288 this.throw_if_removed();
289 switch (this.state) {
290 case DOWNLOAD_CANCELED:
291 case DOWNLOAD_FAILED:
293 download_manager_service.retryDownload(this.id);
295 throw interactive_error("Download cannot be retried.");
299 this.throw_state_error();
303 resume : function () {
304 this.throw_if_removed();
305 switch (this.state) {
306 case DOWNLOAD_PAUSED:
308 download_manager_service.resumeDownload(this.id);
310 throw interactive_error("Download cannot be resumed.");
314 this.throw_state_error();
318 pause : function () {
319 this.throw_if_removed();
320 switch (this.state) {
321 case DOWNLOAD_DOWNLOADING:
322 case DOWNLOAD_QUEUED:
324 download_manager_service.pauseDownload(this.id);
326 throw interactive_error("Download cannot be paused.");
330 this.throw_state_error();
334 remove : function () {
335 this.throw_if_removed();
336 switch (this.state) {
337 case DOWNLOAD_FAILED:
338 case DOWNLOAD_CANCELED:
339 case DOWNLOAD_FINISHED:
341 download_manager_service.removeDownload(this.id);
343 throw interactive_error("Download cannot be removed.");
347 throw interactive_error("Download is still in progress.");
351 delete_target : function () {
352 if (this.state != DOWNLOAD_FINISHED)
353 throw interactive_error("Download has not finished.");
355 this.target_file.remove(false);
359 case Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
360 throw interactive_error("File has already been deleted.");
361 case Cr.NS_ERROR_FILE_ACCESS_DENIED:
362 throw interactive_error("Access denied");
363 case Cr.NS_ERROR_FILE_DIR_NOT_EMPTY:
364 throw interactive_error("Failed to delete file.");
372 var define_download_local_hook = simple_local_hook_definer();
374 // FIXME: add more parameters
375 function register_download(buffer, source_uri, target_file) {
376 var info = new download_info(buffer, null, target_file);
377 info.registered_time_stamp = Date.now();
378 info.registered_source_uri = source_uri;
379 unmanaged_download_info_list.push(info);
383 function match_registered_download(mozilla_info) {
384 let list = unmanaged_download_info_list;
386 for (let i = 0; i < list.length; ++i) {
388 if (x.registered_source_uri == mozilla_info.source) {
392 if (t - x.registered_time_stamp > download_info_max_queue_delay) {
401 define_download_local_hook("download_added_hook");
402 define_download_local_hook("download_removed_hook");
403 define_download_local_hook("download_finished_hook");
404 define_download_local_hook("download_progress_change_hook");
405 define_download_local_hook("download_state_change_hook");
406 define_download_local_hook("download_shell_command_change_hook");
408 define_variable('delete_temporary_files_for_command', true,
409 'If this is set to true, temporary files '
410 + 'downloaded to run a command on them will be '
411 + 'deleted once the command completes. If not, the '
412 + 'file will stay around forever unless deleted '
413 + 'outside the browser.');
415 var download_info_max_queue_delay = 100;
417 var download_progress_listener = {
418 QueryInterface: generate_QI(Ci.nsIDownloadProgressListener),
420 onDownloadStateChange : function (state, download) {
422 /* FIXME: Determine if only new downloads will have this state
423 * as their previous state. */
425 dumpln("download state change: " + download.source.spec + ": " + state + ", " + download.state + ", " + download.id);
427 if (state == DOWNLOAD_NOTSTARTED) {
428 info = match_registered_download(download);
430 info = new download_info(null, download);
431 dumpln("error: encountered unknown new download");
433 info.attach(download);
436 info = id_to_download_info[download.id];
438 dumpln("Error: encountered unknown download");
441 info.mozilla_info = download;
442 download_state_change_hook.run(info);
443 if (info.state == DOWNLOAD_FINISHED) {
444 download_finished_hook.run(info);
446 if (info.shell_command != null) {
447 info.running_shell_command = true;
448 co_call(function () {
450 yield shell_command_with_argument(info.shell_command,
451 info.target_file.path,
452 $cwd = info.shell_command_cwd);
454 if (info.temporary_status == DOWNLOAD_TEMPORARY_FOR_COMMAND)
455 if(delete_temporary_files_for_command) {
456 info.target_file.remove(false /* not recursive */);
458 info.running_shell_command = false;
459 download_shell_command_change_hook.run(info);
462 download_shell_command_change_hook.run(info);
469 onProgressChange : function (progress, request, cur_self_progress, max_self_progress,
470 cur_total_progress, max_total_progress,
472 var info = id_to_download_info[download.id];
474 dumpln("error: encountered unknown download in progress change");
477 info.mozilla_info = download;
478 download_progress_change_hook.run(info);
479 //dumpln("download progress change: " + download.source.spec + ": " + cur_self_progress + "/" + max_self_progress + " "
480 // + cur_total_progress + "/" + max_total_progress + ", " + download.state + ", " + download.id);
483 onSecurityChange : function (progress, request, state, download) {
486 onStateChange : function (progress, request, state_flags, status, download) {
490 var download_observer = {
491 observe : function(subject, topic, data) {
493 case "download-manager-remove-download":
496 // Remove all downloads
497 for (let i in id_to_download_info)
500 let id = subject.QueryInterface(Ci.nsISupportsPRUint32);
501 /* FIXME: determine if this should really be an error */
502 if (!(id in id_to_download_info)) {
503 dumpln("Error: download-manager-remove-download event received for unknown download: " + id);
507 for each (let i in ids) {
508 dumpln("deleting download: " + i);
509 let d = id_to_download_info[i];
511 download_removed_hook.run(d);
512 delete id_to_download_info[i];
518 observer_service.addObserver(download_observer, "download-manager-remove-download", false);
520 download_manager_service.addListener(download_progress_listener);
522 function pretty_print_file_size(val) {
523 const GIBI = 1073741824; /* 2^30 */
524 const MEBI = 1048576; /* 2^20 */
525 const KIBI = 1024; /* 2^10 */
531 else if (val < MEBI) {
534 } else if (val < GIBI) {
547 return [val.toFixed(precision), suffix];
550 function pretty_print_time(val) {
551 val = Math.round(val);
552 var seconds = val % 60;
554 val = Math.floor(val / 60);
556 var minutes = val % 60;
558 var hours = Math.floor(val / 60);
563 parts.push(hours + " hours");
565 parts.push("1 hour");
568 parts.push(minutes + " minutes");
569 else if (minutes == 1)
570 parts.push("1 minute");
572 if (minutes <= 1 && hours == 0) {
574 parts.push(seconds + " seconds");
576 parts.push("1 second");
579 return parts.join(", ");
583 "download_buffer_min_update_interval", 2000,
584 "Minimum interval (in milliseconds) between updates in download progress buffers.\n" +
585 "Lowering this interval will increase the promptness of the progress display at " +
586 "the cost of using additional processor time.");
588 define_keywords("$info");
589 function download_buffer(window, element) {
590 this.constructor_begin();
592 special_buffer.call(this, window, element, forward_keywords(arguments));
593 this.info = arguments.$info;
594 this.configuration.cwd = this.info.mozilla_info.targetFile.parent.path;
595 this.description = this.info.mozilla_info.source.spec;
596 this.keymap = download_buffer_keymap;
599 this.progress_change_handler_fn = method_caller(this, this.handle_progress_change);
600 add_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
601 add_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
602 this.command_change_handler_fn = method_caller(this, this.update_command_field);
603 add_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
604 this.constructor_end();
606 download_buffer.prototype = {
607 __proto__: special_buffer.prototype,
609 handle_kill : function () {
610 this.__proto__.__proto__.handle_kill.call(this);
611 remove_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
612 remove_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
613 remove_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
615 // Remove all node references
616 delete this.status_textnode;
617 delete this.target_file_node;
618 delete this.transferred_div_node;
619 delete this.transferred_textnode;
620 delete this.progress_container_node;
621 delete this.progress_bar_node;
622 delete this.time_textnode;
623 delete this.command_div_node;
624 delete this.command_label_textnode;
625 delete this.command_textnode;
628 update_title : function () {
629 // FIXME: do this properly
631 var info = this.info;
632 var append_transfer_info = false;
633 var append_speed_info = true;
636 case DOWNLOAD_DOWNLOADING:
637 label = "Downloading";
638 append_transfer_info = true;
640 case DOWNLOAD_FINISHED:
641 label = "Download complete";
643 case DOWNLOAD_FAILED:
644 label = "Download failed";
645 append_transfer_info = true;
646 append_speed_info = false;
648 case DOWNLOAD_CANCELED:
649 label = "Download canceled";
650 append_transfer_info = true;
651 append_speed_info = false;
653 case DOWNLOAD_PAUSED:
654 label = "Download paused";
655 append_transfer_info = true;
656 append_speed_info = false;
658 case DOWNLOAD_QUEUED:
660 label = "Download queued";
664 if (append_transfer_info) {
665 if (append_speed_info)
666 new_title = label + " at " + pretty_print_file_size(info.speed).join(" ") + "/s: ";
668 new_title = label + ": ";
669 var trans = pretty_print_file_size(info.amount_transferred);
670 if (info.size >= 0) {
671 var total = pretty_print_file_size(info.size);
672 if (trans[1] == total[1])
673 new_title += trans[0] + "/" + total[0] + " " + total[1];
675 new_title += trans.join(" ") + "/" + total.join(" ");
677 new_title += trans.join(" ");
678 if (info.percent_complete >= 0)
679 new_title += " (" + info.percent_complete + "%)";
682 if (new_title != this.title) {
683 this.title = new_title;
689 handle_progress_change : function () {
690 var cur_time = Date.now();
691 if (this.last_update == null ||
692 (cur_time - this.last_update) > download_buffer_min_update_interval ||
693 this.info.state != this.previous_state) {
695 if (this.update_title())
696 buffer_title_change_hook.run(this);
698 if (this.generated) {
699 this.update_fields();
701 this.previous_status = this.info.status;
702 this.last_update = cur_time;
706 generate: function() {
707 var d = this.document;
708 var g = new dom_generator(d, XHTML_NS);
710 /* Warning: If any additional node references are saved in
711 * this function, appropriate code to delete the saved
712 * properties must be added to handle_kill. */
714 var info = this.info;
716 d.body.setAttribute("class", "download-buffer");
718 g.add_stylesheet("chrome://conkeror-gui/content/downloads.css");
723 div = g.element("div", d.body, "class", "download-info", "id", "download-source");
724 label = g.element("div", div, "class", "download-label");
725 this.status_textnode = g.text("", label);
726 value = g.element("div", div, "class", "download-value");
727 g.text(info.source.spec, value);
729 div = g.element("div", d.body, "class", "download-info", "id", "download-target");
730 label = g.element("div", div, "class", "download-label");
732 if (info.temporary_status != DOWNLOAD_NOT_TEMPORARY)
733 target_label = "Temp. file:";
735 target_label = "Target:";
736 g.text(target_label, label);
737 value = g.element("div", div, "class", "download-value");
738 this.target_file_node = g.text("", value);
740 div = g.element("div", d.body, "class", "download-info", "id", "download-mime-type");
741 label = g.element("div", div, "class", "download-label");
742 g.text("MIME type:", label);
743 value = g.element("div", div, "class", "download-value");
744 g.text(info.MIME_type || "unknown", value);
746 this.transferred_div_node = div = g.element("div", d.body,
747 "class", "download-info",
748 "id", "download-transferred");
749 label = g.element("div", div, "class", "download-label");
750 g.text("Transferred:", label);
751 value = g.element("div", div, "class", "download-value");
752 this.transferred_textnode = g.text("", value);
753 this.progress_container_node = value = g.element("div", div, "id", "download-progress-container");
754 this.progress_bar_node = g.element("div", value, "id", "download-progress-bar");
755 value = g.element("div", div, "class", "download-value", "id", "download-percent");
756 this.percent_textnode = g.text("", value);
758 div = g.element("div", d.body, "class", "download-info", "id", "download-time");
759 label = g.element("div", div, "class", "download-label");
760 g.text("Time:", label);
761 value = g.element("div", div, "class", "download-value");
762 this.time_textnode = g.text("", value);
764 if (info.action_description != null) {
765 div = g.element("div", d.body, "class", "download-info", "id", "download-action");
766 label = g.element("div", div, "class", "download-label");
767 g.text("Action:", label);
768 value = g.element("div", div, "class", "download-value");
769 g.text(info.action_description, value);
772 this.command_div_node = div = g.element("div", d.body, "class", "download-info", "id", "download-command");
773 label = g.element("div", div, "class", "download-label");
774 this.command_label_textnode = g.text("Run command:", label);
775 value = g.element("div", div, "class", "download-value");
776 this.command_textnode = g.text("", value);
778 this.update_fields();
780 this.update_command_field();
783 update_fields : function () {
786 var info = this.info;
789 case DOWNLOAD_DOWNLOADING:
790 label = "Downloading";
792 case DOWNLOAD_FINISHED:
795 case DOWNLOAD_FAILED:
798 case DOWNLOAD_CANCELED:
801 case DOWNLOAD_PAUSED:
804 case DOWNLOAD_QUEUED:
809 this.status_textnode.nodeValue = label + ":";
810 this.target_file_node.nodeValue = info.target_file_text();
811 this.update_time_field();
814 if (info.state == DOWNLOAD_FINISHED)
815 tran_text = pretty_print_file_size(info.size).join(" ");
817 var trans = pretty_print_file_size(info.amount_transferred);
818 if (info.size >= 0) {
819 var total = pretty_print_file_size(info.size);
820 if (trans[1] == total[1])
821 tran_text += trans[0] + "/" + total[0] + " " + total[1];
823 tran_text += trans.join(" ") + "/" + total.join(" ");
825 tran_text += trans.join(" ");
827 this.transferred_textnode.nodeValue = tran_text;
828 if (info.percent_complete >= 0) {
829 this.progress_container_node.style.display = "";
830 this.percent_textnode.nodeValue = info.percent_complete + "%";
831 this.progress_bar_node.style.width = info.percent_complete + "%";
833 this.percent_textnode.nodeValue = "";
834 this.progress_container_node.style.display = "none";
837 this.update_command_field();
840 update_time_field : function () {
841 var info = this.info;
842 var elapsed_text = pretty_print_time((Date.now() - info.start_time / 1000) / 1000) + " elapsed";
844 if (info.state == DOWNLOAD_DOWNLOADING) {
845 text = pretty_print_file_size(info.speed).join(" ") + "/s, ";
847 if (info.state == DOWNLOAD_DOWNLOADING &&
850 let remaining = (info.size - info.amount_transferred) / info.speed;
851 text += pretty_print_time(remaining) + " left (" + elapsed_text + ")";
855 this.time_textnode.nodeValue = text;
858 update_command_field : function () {
861 if (this.info.shell_command != null) {
862 this.command_div_node.style.display = "";
864 if (this.info.running_shell_command)
866 else if (this.info.state == DOWNLOAD_FINISHED)
867 label = "Ran command:";
869 label = "Run command:";
870 this.command_label_textnode.nodeValue = label;
871 this.command_textnode.nodeValue = this.info.shell_command;
873 this.command_div_node.style.display = "none";
878 function download_cancel(buffer) {
879 check_buffer(buffer, download_buffer);
880 var info = buffer.info;
882 buffer.window.minibuffer.message("Download canceled");
884 interactive("download-cancel",
885 "Cancel the current download.\n" +
886 "The download can later be retried using the `download-retry' command, but any " +
887 "data already transferred will be lost.",
888 function (I) {download_cancel(I.buffer);});
890 function download_retry(buffer) {
891 check_buffer(buffer, download_buffer);
892 var info = buffer.info;
894 buffer.window.minibuffer.message("Download retried");
896 interactive("download-retry",
897 "Retry a failed or canceled download.\n" +
898 "This command can be used to retry a download that failed or was cancled using " +
899 "the `download-cancel' command. The download will begin from the start again.",
900 function (I) {download_retry(I.buffer);});
902 function download_pause(buffer) {
903 check_buffer(buffer, download_buffer);
905 buffer.window.minibuffer.message("Download paused");
907 interactive("download-pause",
908 "Pause the current download.\n" +
909 "The download can later be resumed using the `download-resume' command. The " +
910 "data already transferred will not be lost.",
911 function (I) {download_pause(I.buffer);});
913 function download_resume(buffer) {
914 check_buffer(buffer, download_buffer);
915 buffer.info.resume();
916 buffer.window.minibuffer.message("Download resumed");
918 interactive("download-resume",
919 "Resume the current download.\n" +
920 "This command can be used to resume a download paused using the `download-pause' command.",
921 function (I) {download_resume(I.buffer);});
923 function download_remove(buffer) {
924 check_buffer(buffer, download_buffer);
925 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, completed, or failed) downloads.",
931 function (I) {download_remove(I.buffer);});
933 function download_retry_or_resume(buffer) {
934 check_buffer(buffer, download_buffer);
935 var info = buffer.info;
936 if (info.state == DOWNLOAD_PAUSED)
937 download_resume(buffer);
939 download_retry(buffer);
941 interactive("download-retry-or-resume",
942 "Retry or resume the current download.\n" +
943 "This command can be used to resume a download paused using the `download-pause' " +
944 "command or canceled using the `download-cancel' command.",
945 function (I) {download_retry_or_resume(I.buffer);});
947 function download_pause_or_resume(buffer) {
948 check_buffer(buffer, download_buffer);
949 var info = buffer.info;
950 if (info.state == DOWNLOAD_PAUSED)
951 download_resume(buffer);
953 download_pause(buffer);
955 interactive("download-pause-or-resume",
956 "Pause or resume the current download.\n" +
957 "This command toggles the paused state of the current download.",
958 function (I) {download_pause_or_resume(I.buffer);});
960 function download_delete_target(buffer) {
961 check_buffer(buffer, download_buffer);
962 var info = buffer.info;
963 info.delete_target();
964 buffer.window.minibuffer.message("Deleted file: " + info.target_file.path);
966 interactive("download-delete-target",
967 "Delete the target file of the current download.\n" +
968 "This command can only be used if the download has finished successfully.",
969 function (I) {download_delete_target(I.buffer);});
971 function download_shell_command(buffer, cwd, cmd) {
972 check_buffer(buffer, download_buffer);
973 var info = buffer.info;
974 if (info.state == DOWNLOAD_FINISHED) {
975 shell_command_with_argument_blind(cmd, info.target_file.path, $cwd = cwd);
978 if (info.state != DOWNLOAD_DOWNLOADING && info.state != DOWNLOAD_PAUSED && info.state != DOWNLOAD_QUEUED)
979 info.throw_state_error();
980 if (cmd == null || cmd.length == 0)
981 info.set_shell_command(null, cwd);
983 info.set_shell_command(cmd, cwd);
984 buffer.window.minibuffer.message("Queued shell command: " + cmd);
986 interactive("download-shell-command",
987 "Run a shell command on the target file of the current download.\n" +
988 "If the download is still in progress, the shell command will be queued " +
989 "to run when the download finishes.",
991 var buffer = check_buffer(I.buffer, download_buffer);
992 var cwd = buffer.info.shell_command_cwd || buffer.cwd;
993 var cmd = yield I.minibuffer.read_shell_command(
995 $initial_value = buffer.info.shell_command ||
996 get_mime_type_external_handler(buffer.info.MIME_type));
997 download_shell_command(buffer, cwd, cmd);
1000 function download_manager_ui()
1002 download_manager_ui.prototype = {
1003 QueryInterface : XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
1005 getAttention : function () {},
1006 show : function () {},
1011 function download_manager_show_builtin_ui(window) {
1012 download_manager_builtin_ui.show(window);
1014 interactive("download-manager-show-builtin-ui",
1015 "Show the built-in (Firefox-style) download manager user interface.",
1016 function (I) {download_manager_show_builtin_ui(I.window);});
1020 define_variable("download_temporary_file_open_buffer_delay", 500,
1021 "Delay (in milliseconds) before a download buffer is opened for temporary downloads.\n" +
1022 "This variable takes effect only if `open_download_buffer_automatically' is in " +
1023 "`download_added_hook', as it is by default.");
1026 define_variable("download_buffer_automatic_open_target", OPEN_NEW_WINDOW,
1027 "Target for download buffers created by the `open_download_buffer_automatically' function.\n" +
1028 "This variable takes effect only if `open_download_buffer_automatically' is in " +
1029 "`download_added_hook', as it is by default.");
1031 function open_download_buffer_automatically(info) {
1032 var buf = info.source_buffer;
1033 var target = download_buffer_automatic_open_target;
1035 target = OPEN_NEW_WINDOW;
1036 if (info.temporary_status == DOWNLOAD_NOT_TEMPORARY ||
1037 !(download_temporary_file_open_buffer_delay > 0))
1038 create_buffer(buf.window, buffer_creator(download_buffer, $info = info), target);
1044 add_hook.call(info, "download_finished_hook", finish);
1045 timer = call_after_timeout(function () {
1046 remove_hook.call(info, "download_finished_hook", finish);
1047 create_buffer(buf.window, buffer_creator(download_buffer, $info = info), target);
1048 }, download_temporary_file_open_buffer_delay);
1051 add_hook("download_added_hook", open_download_buffer_automatically);