1 require("special-buffer.js");
2 var download_manager_service = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
3 //var download_manager_ui = Cc["@mozilla.org/download-manager-ui;1"].getService(Ci.nsIDownloadManagerUI);
4 var download_manager_builtin_ui = Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
5 .getService(Ci.nsIDownloadManagerUI);
7 /* This implements nsIHelperAppLauncherDialog interface. */
8 function download_helper()
10 download_helper.prototype = {
11 QueryInterface: generate_QI(Ci.nsIHelperAppLauncherDialog, Ci.nsIWebProgressListener2),
13 create_panel : function() {
14 this.panel = create_info_panel(this.window, "download-panel",
15 [["downloading", "Downloading:", this.launcher.source.spec],
16 ["mime-type", "Mime type:", this.launcher.MIMEInfo.MIMEType]]);
19 handle_show: function () {
20 var action_chosen = false;
23 var action = yield this.window.minibuffer.read_single_character_option(
24 $prompt = "Action to perform: (save or open)",
25 $options = ["s", "o"]);
29 var suggested_path = suggest_save_path_from_file_name(this.launcher.suggestedFileName, this.buffer);
30 var file = yield this.window.minibuffer.read_file_check_overwrite(
31 $prompt = "Save to file:",
32 $initial_value = suggested_path,
34 register_download(this.buffer, this.launcher.source);
35 this.launcher.saveToDisk(file, false);
39 var cwd = this.buffer ? this.buffer.cwd : default_directory.path;
40 var mime_type = this.launcher.MIMEInfo.MIMEType;
41 var suggested_action = get_external_handler_for_mime_type(mime_type);
42 var command = yield this.window.minibuffer.read_shell_command(
43 $initial_value = suggested_action,
45 var file = get_temporary_file(this.launcher.suggestedFileName);
46 var info = register_download(this.buffer, this.launcher.source);
47 info.temporary_status = DOWNLOAD_TEMPORARY_FOR_COMMAND;
48 info.set_shell_command(command, cwd);
49 this.launcher.saveToDisk(file, false);
53 handle_interactive_error(this.window, e);
62 show : function (launcher, context, reason) {
63 this.launcher = launcher;
65 // Get associated buffer; if that fails (hopefully not), just get any window
69 var frame = context.QueryInterface(Ci.nsIWebProgress).DOMWindow.top;
70 window = get_window_from_frame(frame);
72 buffer = get_buffer_from_frame(window, frame);
74 window = get_recent_conkeror_window();
77 // FIXME: need to handle this case perhaps where no windows exist
78 this.abort(); // for now, just cancel the download
86 co_call(this.handle_show());
90 const NS_BINDING_ABORTED = 0x804b0002;
91 this.launcher.cancel(NS_BINDING_ABORTED);
95 cleanup : function () {
104 promptForSaveToFile : function(launcher, context, default_file, suggested_file_extension) {
110 var unmanaged_download_info_list = [];
111 var id_to_download_info = {};
113 // Import these constants for convenience
114 const DOWNLOAD_NOTSTARTED = Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
115 const DOWNLOAD_DOWNLOADING = Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING;
116 const DOWNLOAD_FINISHED = Ci.nsIDownloadManager.DOWNLOAD_FINISHED;
117 const DOWNLOAD_FAILED = Ci.nsIDownloadManager.DOWNLOAD_FAILED;
118 const DOWNLOAD_CANCELED = Ci.nsIDownloadManager.DOWNLOAD_CANCELED;
119 const DOWNLOAD_PAUSED = Ci.nsIDownloadManager.DOWNLOAD_PAUSED;
120 const DOWNLOAD_QUEUED = Ci.nsIDownloadManager.DOWNLOAD_QUEUED;
121 const DOWNLOAD_BLOCKED = Ci.nsIDownloadManager.DOWNLOAD_BLOCKED;
122 const DOWNLOAD_SCANNING = Ci.nsIDownloadManager.DOWNLOAD_SCANNING;
125 const DOWNLOAD_NOT_TEMPORARY = 0;
126 const DOWNLOAD_TEMPORARY_FOR_ACTION = 1;
127 const DOWNLOAD_TEMPORARY_FOR_COMMAND = 2;
129 function download_info(source_buffer, mozilla_info) {
130 this.source_buffer = source_buffer;
131 if (mozilla_info != null)
132 this.attach(mozilla_info);
134 download_info.prototype = {
135 attach : function (mozilla_info) {
136 this.mozilla_info = mozilla_info;
137 id_to_download_info[mozilla_info.id] = this;
138 download_added_hook.run(this);
141 shell_command : null,
143 shell_command_cwd : null,
145 temporary_status : DOWNLOAD_NOT_TEMPORARY,
147 action_description : null,
149 set_shell_command : function (str, cwd) {
150 this.shell_command = str;
151 this.shell_command_cwd = cwd;
152 if (this.mozilla_info)
153 download_shell_command_change_hook.run(this);
157 * None of the following members may be used until attach is called
160 // Reflectors to properties of nsIDownload
161 get state () { return this.mozilla_info.state; },
162 get target_file () { return this.mozilla_info.targetFile; },
163 get amount_transferred () { return this.mozilla_info.amountTransferred; },
164 get percent_complete () { return this.mozilla_info.percentComplete; },
166 var s = this.mozilla_info.size;
167 /* nsIDownload.size is a PRUint64, and will have value
168 * LL_MAXUINT (2^64 - 1) to indicate an unknown size. Because
169 * JavaScript only has a double numerical type, this value
170 * cannot be represented exactly, so 2^36 is used instead as the cutoff. */
171 if (s < 68719476736 /* 2^36 */)
175 get source () { return this.mozilla_info.source; },
176 get start_time () { return this.mozilla_info.startTime; },
177 get speed () { return this.mozilla_info.speed; },
178 get MIME_info () { return this.mozilla_info.MIMEInfo; },
181 return this.MIME_info.MIMEType;
184 get id () { return this.mozilla_info.id; },
185 get referrer () { return this.mozilla_info.referrer; },
187 throw_if_removed : function () {
189 throw interactive_error("Download has already been removed from the download manager.");
192 throw_state_error : function () {
193 switch (this.state) {
194 case DOWNLOAD_DOWNLOADING:
195 throw interactive_error("Download is already in progress.");
196 case DOWNLOAD_FINISHED:
197 throw interactive_error("Download has already completed.");
198 case DOWNLOAD_FAILED:
199 throw interactive_error("Download has already failed.");
200 case DOWNLOAD_CANCELED:
201 throw interactive_error("Download has already been canceled.");
202 case DOWNLOAD_PAUSED:
203 throw interactive_error("Download has already been paused.");
204 case DOWNLOAD_QUEUED:
205 throw interactive_error("Download is queued.");
207 throw new Error("Download has unexpected state: " + this.state);
211 // Download manager operations
212 cancel : function () {
213 this.throw_if_removed();
214 switch (this.state) {
215 case DOWNLOAD_DOWNLOADING:
216 case DOWNLOAD_PAUSED:
217 case DOWNLOAD_QUEUED:
219 download_manager_service.cancelDownload(this.id);
221 throw interactive_error("Download cannot be canceled.");
225 this.throw_state_error();
229 retry : function () {
230 this.throw_if_removed();
231 switch (this.state) {
232 case DOWNLOAD_CANCELED:
233 case DOWNLOAD_FAILED:
235 download_manager_service.retryDownload(this.id);
237 throw interactive_error("Download cannot be retried.");
241 this.throw_state_error();
245 resume : function () {
246 this.throw_if_removed();
247 switch (this.state) {
248 case DOWNLOAD_PAUSED:
250 download_manager_service.resumeDownload(this.id);
252 throw interactive_error("Download cannot be resumed.");
256 this.throw_state_error();
260 pause : function () {
261 this.throw_if_removed();
262 switch (this.state) {
263 case DOWNLOAD_DOWNLOADING:
264 case DOWNLOAD_QUEUED:
266 download_manager_service.pauseDownload(this.id);
268 throw interactive_error("Download cannot be paused.");
272 this.throw_state_error();
276 remove : function () {
277 this.throw_if_removed();
278 switch (this.state) {
279 case DOWNLOAD_FAILED:
280 case DOWNLOAD_CANCELED:
281 case DOWNLOAD_FINISHED:
283 download_manager_service.removeDownload(this.id);
285 throw interactive_error("Download cannot be removed.");
289 throw interactive_error("Download is still in progress.");
293 delete_target : function () {
294 if (this.state != DOWNLOAD_FINISHED)
295 throw interactive_error("Download has not finished.");
297 this.target_file.remove(false);
301 case Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
302 throw interactive_error("File has already been deleted.");
303 case Cr.NS_ERROR_FILE_ACCESS_DENIED:
304 throw interactive_error("Access denied");
305 case Cr.NS_ERROR_FILE_DIR_NOT_EMPTY:
306 throw interactive_error("Failed to delete file.");
314 var define_download_local_hook = simple_local_hook_definer();
316 // FIXME: add more parameters
317 function register_download(buffer, source_uri) {
318 var info = new download_info(buffer);
319 info.registered_time_stamp = Date.now();
320 info.registered_source_uri = source_uri;
321 unmanaged_download_info_list.push(info);
325 function match_registered_download(mozilla_info) {
326 let list = unmanaged_download_info_list;
328 for (let i = 0; i < list.length; ++i) {
330 if (x.registered_source_uri == mozilla_info.source) {
334 if (t - x.registered_time_stamp > download_info_max_queue_delay) {
343 define_download_local_hook("download_added_hook");
344 define_download_local_hook("download_removed_hook");
345 define_download_local_hook("download_finished_hook");
346 define_download_local_hook("download_progress_change_hook");
347 define_download_local_hook("download_state_change_hook");
348 define_download_local_hook("download_shell_command_change_hook");
350 var download_info_max_queue_delay = 100;
352 var download_progress_listener = {
353 QueryInterface: generate_QI(Ci.nsIDownloadProgressListener),
355 onDownloadStateChange : function (state, download) {
357 /* FIXME: Determine if only new downloads will have this state
358 * as their previous state. */
360 dumpln("download state change: " + download.source.spec + ": " + state + ", " + download.state + ", " + download.id);
362 if (state == DOWNLOAD_NOTSTARTED) {
363 info = match_registered_download(download);
365 info = new download_info(null, download);
366 dumpln("error: encountered unknown new download");
368 info.attach(download);
371 info = id_to_download_info[download.id];
373 dumpln("Error: encountered unknown download");
376 info.mozilla_info = download;
377 download_state_change_hook.run(info);
378 if (info.state == DOWNLOAD_FINISHED) {
379 download_finished_hook.run(info);
381 if (info.shell_command != null) {
382 info.running_shell_command = true;
383 co_call(function () {
385 yield shell_command_with_argument(info.shell_command,
386 info.target_file.path,
387 $cwd = info.shell_command_cwd);
389 if (info.temporary_status == DOWNLOAD_TEMPORARY_FOR_COMMAND)
390 info.target_file.remove(false /* not recursive */);
391 info.running_shell_command = false;
392 download_shell_command_change_hook.run(info);
395 download_shell_command_change_hook.run(info);
402 onProgressChange : function (progress, request, cur_self_progress, max_self_progress,
403 cur_total_progress, max_total_progress,
405 var info = id_to_download_info[download.id];
407 dumpln("error: encountered unknown download in progress change");
410 info.mozilla_info = download;
411 download_progress_change_hook.run(info);
412 //dumpln("download progress change: " + download.source.spec + ": " + cur_self_progress + "/" + max_self_progress + " "
413 // + cur_total_progress + "/" + max_total_progress + ", " + download.state + ", " + download.id);
416 onSecurityChange : function (progress, request, state, download) {
419 onStateChange : function (progress, request, state_flags, status, download) {
423 var download_observer = {
424 observe : function(subject, topic, data) {
426 case "download-manager-remove-download":
429 // Remove all downloads
430 for (let i in id_to_download_info)
433 let id = subject.QueryInterface(Ci.nsISupportsPRUint32);
434 /* FIXME: determine if this should really be an error */
435 if (!(id in id_to_download_info)) {
436 dumpln("Error: download-manager-remove-download event received for unknown download: " + id);
440 for each (let i in ids) {
441 dumpln("deleting download: " + i);
442 let d = id_to_download_info[i];
444 download_removed_hook.run(d);
445 delete id_to_download_info[i];
451 observer_service.addObserver(download_observer, "download-manager-remove-download", false);
453 download_manager_service.addListener(download_progress_listener);
455 function pretty_print_file_size(val) {
456 const GIBI = 1073741824; /* 2^30 */
457 const MEBI = 1048576; /* 2^20 */
458 const KIBI = 1024; /* 2^10 */
464 else if (val < MEBI) {
467 } else if (val < GIBI) {
480 return [val.toFixed(precision), suffix];
483 function pretty_print_time(val) {
484 val = Math.round(val);
485 var seconds = val % 60;
487 val = Math.floor(val / 60);
489 var minutes = val % 60;
491 var hours = Math.floor(val / 60);
496 parts.push(hours + " hours");
498 parts.push("1 hour");
501 parts.push(minutes + " minutes");
502 else if (minutes == 1)
503 parts.push("1 minute");
505 if (minutes <= 1 && hours == 0) {
507 parts.push(seconds + " seconds");
509 parts.push("1 second");
512 return parts.join(", ");
516 "download_buffer_min_update_interval", 2000,
517 "Minimum interval (in milliseconds) between updates in download progress buffers.\n" +
518 "Lowering this interval will increase the promptness of the progress display at " +
519 "the cost of using additional processor time.");
521 define_keywords("$info");
522 function download_buffer(window, element) {
523 this.constructor_begin();
525 special_buffer.call(this, window, element, forward_keywords(arguments));
526 this.info = arguments.$info;
527 this.configuration.cwd = this.info.mozilla_info.targetFile.parent.path;
528 this.description = this.info.mozilla_info.source.spec;
529 this.keymap = download_buffer_keymap;
532 this.progress_change_handler_fn = method_caller(this, this.handle_progress_change);
533 add_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
534 add_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
535 this.command_change_handler_fn = method_caller(this, this.update_command_field);
536 add_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
537 this.constructor_end();
539 download_buffer.prototype = {
540 __proto__: special_buffer.prototype,
542 handle_kill : function () {
543 this.__proto__.handle_kill();
544 remove_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
545 remove_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
546 remove_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
548 // Remove all node references
549 delete this.status_textnode;
550 delete this.transferred_div_node;
551 delete this.transferred_textnode;
552 delete this.progress_container_node;
553 delete this.progress_bar_node;
554 delete this.time_textnode;
555 delete this.command_div_node;
556 delete this.command_label_textnode;
557 delete this.command_textnode;
560 update_title : function () {
561 // FIXME: do this properly
563 var info = this.info;
564 var append_transfer_info = false;
565 var append_speed_info = true;
568 case DOWNLOAD_DOWNLOADING:
569 label = "Downloading";
570 append_transfer_info = true;
572 case DOWNLOAD_FINISHED:
573 label = "Download complete";
575 case DOWNLOAD_FAILED:
576 label = "Download failed";
577 append_transfer_info = true;
578 append_speed_info = false;
580 case DOWNLOAD_CANCELED:
581 label = "Download canceled";
582 append_transfer_info = true;
583 append_speed_info = false;
585 case DOWNLOAD_PAUSED:
586 label = "Download paused";
587 append_transfer_info = true;
588 append_speed_info = false;
590 case DOWNLOAD_QUEUED:
592 label = "Download queued";
596 if (append_transfer_info) {
597 if (append_speed_info)
598 new_title = label + " at " + pretty_print_file_size(info.speed).join(" ") + "/s: ";
600 new_title = label + ": ";
601 var trans = pretty_print_file_size(info.amount_transferred);
602 if (info.size >= 0) {
603 var total = pretty_print_file_size(info.size);
604 if (trans[1] == total[1])
605 new_title += trans[0] + "/" + total[0] + " " + total[1];
607 new_title += trans.join(" ") + "/" + total.join(" ");
609 new_title += trans.join(" ");
610 if (info.percent_complete >= 0)
611 new_title += " (" + info.percent_complete + "%)";
614 if (new_title != this.title) {
615 this.title = new_title;
621 handle_progress_change : function () {
622 var cur_time = Date.now();
623 if (this.last_update == null ||
624 (cur_time - this.last_update) > download_buffer_min_update_interval ||
625 this.info.state != this.previous_state) {
627 if (this.update_title())
628 buffer_title_change_hook.run(this);
630 if (this.generated) {
631 this.update_fields();
633 this.previous_status = this.info.status;
634 this.last_update = cur_time;
638 generate: function() {
639 var d = this.document;
640 var g = new dom_generator(d, XHTML_NS);
642 /* Warning: If any additional node references are saved in
643 * this function, appropriate code to delete the saved
644 * properties must be added to handle_kill. */
646 var info = this.info;
648 d.body.setAttribute("class", "download-buffer");
650 g.add_stylesheet("chrome://conkeror/content/downloads.css");
655 div = g.element("div", d.body, "class", "download-info", "id", "download-source");
656 label = g.element("div", div, "class", "download-label");
657 this.status_textnode = g.text("", label);
658 value = g.element("div", div, "class", "download-value");
659 g.text(info.source.spec, value);
661 div = g.element("div", d.body, "class", "download-info", "id", "download-target");
662 label = g.element("div", div, "class", "download-label");
664 if (info.temporary_status != DOWNLOAD_NOT_TEMPORARY)
665 target_label = "Temp. file:";
667 target_label = "Target:";
668 g.text(target_label, label);
669 value = g.element("div", div, "class", "download-value");
670 g.text(info.target_file.path, value);
672 div = g.element("div", d.body, "class", "download-info", "id", "download-mime-type");
673 label = g.element("div", div, "class", "download-label");
674 g.text("MIME type:", label);
675 value = g.element("div", div, "class", "download-value");
676 g.text(info.MIME_type || "unknown", value);
678 this.transferred_div_node = div = g.element("div", d.body,
679 "class", "download-info",
680 "id", "download-transferred");
681 label = g.element("div", div, "class", "download-label");
682 g.text("Transferred:", label);
683 value = g.element("div", div, "class", "download-value");
684 this.transferred_textnode = g.text("", value);
685 this.progress_container_node = value = g.element("div", div, "id", "download-progress-container");
686 this.progress_bar_node = g.element("div", value, "id", "download-progress-bar");
687 value = g.element("div", div, "class", "download-value", "id", "download-percent");
688 this.percent_textnode = g.text("", value);
690 div = g.element("div", d.body, "class", "download-info", "id", "download-time");
691 label = g.element("div", div, "class", "download-label");
692 g.text("Time:", label);
693 value = g.element("div", div, "class", "download-value");
694 this.time_textnode = g.text("", value);
696 if (info.action_description != null) {
697 div = g.element("div", d.body, "class", "download-info", "id", "download-action");
698 label = g.element("div", div, "class", "download-label");
699 g.text("Action:", label);
700 value = g.element("div", div, "class", "download-value");
701 g.text(info.action_description, value);
704 this.command_div_node = div = g.element("div", d.body, "class", "download-info", "id", "download-command");
705 label = g.element("div", div, "class", "download-label");
706 this.command_label_textnode = g.text("Run command:", label);
707 value = g.element("div", div, "class", "download-value");
708 this.command_textnode = g.text("", value);
710 this.update_fields();
712 this.update_command_field();
715 update_fields : function () {
718 var info = this.info;
721 case DOWNLOAD_DOWNLOADING:
722 label = "Downloading";
724 case DOWNLOAD_FINISHED:
727 case DOWNLOAD_FAILED:
730 case DOWNLOAD_CANCELED:
733 case DOWNLOAD_PAUSED:
736 case DOWNLOAD_QUEUED:
741 this.status_textnode.nodeValue = label + ":";
742 this.update_time_field();
745 if (info.state == DOWNLOAD_FINISHED)
746 tran_text = pretty_print_file_size(info.size).join(" ");
748 var trans = pretty_print_file_size(info.amount_transferred);
749 if (info.size >= 0) {
750 var total = pretty_print_file_size(info.size);
751 if (trans[1] == total[1])
752 tran_text += trans[0] + "/" + total[0] + " " + total[1];
754 tran_text += trans.join(" ") + "/" + total.join(" ");
756 tran_text += trans.join(" ");
758 this.transferred_textnode.nodeValue = tran_text;
759 if (info.percent_complete >= 0) {
760 this.progress_container_node.style.display = "";
761 this.percent_textnode.nodeValue = info.percent_complete + "%";
762 this.progress_bar_node.style.width = info.percent_complete + "%";
764 this.percent_textnode.nodeValue = "";
765 this.progress_container_node.style.display = "none";
768 this.update_command_field();
771 update_time_field : function () {
772 var info = this.info;
773 var elapsed_text = pretty_print_time((Date.now() - info.start_time / 1000) / 1000) + " elapsed";
775 if (info.state == DOWNLOAD_DOWNLOADING) {
776 text = pretty_print_file_size(info.speed).join(" ") + "/s, ";
778 if (info.state == DOWNLOAD_DOWNLOADING &&
781 let remaining = (info.size - info.amount_transferred) / info.speed;
782 text += pretty_print_time(remaining) + " left (" + elapsed_text + ")";
786 this.time_textnode.nodeValue = text;
789 update_command_field : function () {
792 if (this.info.shell_command != null) {
793 this.command_div_node.style.display = "";
795 if (this.info.running_shell_command)
797 else if (this.info.state == DOWNLOAD_FINISHED)
798 label = "Ran command:";
800 label = "Run command:";
801 this.command_label_textnode.nodeValue = label;
802 this.command_textnode.nodeValue = this.info.shell_command;
804 this.command_div_node.style.display = "none";
809 function download_cancel(buffer) {
810 check_buffer(buffer, download_buffer);
811 var info = buffer.info;
813 buffer.window.minibuffer.message("Download canceled");
815 interactive("download-cancel",
816 "Cancel the current download.\n" +
817 "The download can later be retried using the `download-retry' command, but any " +
818 "data already transferred will be lost.",
819 function (I) {download_cancel(I.buffer);});
821 function download_retry(buffer) {
822 check_buffer(buffer, download_buffer);
823 var info = buffer.info;
825 buffer.window.minibuffer.message("Download retried");
827 interactive("download-retry",
828 "Retry a failed or canceled download.\n" +
829 "This command can be used to retry a download that failed or was cancled using " +
830 "the `download-cancel' command. The download will begin from the start again.",
831 function (I) {download_retry(I.buffer);});
833 function download_pause(buffer) {
834 check_buffer(buffer, download_buffer);
836 buffer.window.minibuffer.message("Download paused");
838 interactive("download-pause",
839 "Pause the current download.\n" +
840 "The download can later be resumed using the `download-resume' command. The " +
841 "data already transferred will not be lost.",
842 function (I) {download_pause(I.buffer);});
844 function download_resume(buffer) {
845 check_buffer(buffer, download_buffer);
846 buffer.info.resume();
847 buffer.window.minibuffer.message("Download resumed");
849 interactive("download-resume",
850 "Resume the current download.\n" +
851 "This command can be used to resume a download paused using the `download-pause' command.",
852 function (I) {download_resume(I.buffer);});
854 function download_remove(buffer) {
855 check_buffer(buffer, download_buffer);
856 buffer.info.remove();
857 buffer.window.minibuffer.message("Download removed");
859 interactive("download-remove",
860 "Remove the current download from the download manager.\n" +
861 "This command can only be used on inactive (paused, canceled, completed, or failed) downloads.",
862 function (I) {download_remove(I.buffer);});
864 function download_retry_or_resume(buffer) {
865 check_buffer(buffer, download_buffer);
866 var info = buffer.info;
867 if (info.state == DOWNLOAD_PAUSED)
868 download_resume(buffer);
870 download_retry(buffer);
872 interactive("download-retry-or-resume",
873 "Retry or resume the current download.\n" +
874 "This command can be used to resume a download paused using the `download-pause' " +
875 "command or canceled using the `download-cancel' command.",
876 function (I) {download_retry_or_resume(I.buffer);});
878 function download_pause_or_resume(buffer) {
879 check_buffer(buffer, download_buffer);
880 var info = buffer.info;
881 if (info.state == DOWNLOAD_PAUSED)
882 download_resume(buffer);
884 download_pause(buffer);
886 interactive("download-pause-or-resume",
887 "Pause or resume the current download.\n" +
888 "This command toggles the paused state of the current download.",
889 function (I) {download_pause_or_resume(I.buffer);});
891 function download_delete_target(buffer) {
892 check_buffer(buffer, download_buffer);
893 var info = buffer.info;
894 info.delete_target();
895 buffer.window.minibuffer.message("Deleted file: " + info.target_file.path);
897 interactive("download-delete-target",
898 "Delete the target file of the current download.\n" +
899 "This command can only be used if the download has finished successfully.",
900 function (I) {download_delete_target(I.buffer);});
902 function download_shell_command(buffer, cwd, cmd) {
903 check_buffer(buffer, download_buffer);
904 var info = buffer.info;
905 if (info.state == DOWNLOAD_FINISHED) {
906 shell_command_with_argument_blind(cmd, info.target_file.path, $cwd = cwd);
909 if (info.state != DOWNLOAD_DOWNLOADING && info.state != DOWNLOAD_PAUSED && info.state != DOWNLOAD_QUEUED)
910 info.throw_state_error();
911 if (cmd == null || cmd.length == 0)
912 info.set_shell_command(null, cwd);
914 info.set_shell_command(cmd, cwd);
915 buffer.window.minibuffer.message("Queued shell command: " + cmd);
917 interactive("download-shell-command",
918 "Run a shell command on the target file of the current download.\n" +
919 "If the download is still in progress, the shell command will be queued " +
920 "to run when the download finishes.",
922 var buffer = check_buffer(I.buffer, download_buffer);
923 var cwd = buffer.info.shell_command_cwd || buffer.cwd;
924 var cmd = yield I.minibuffer.read_shell_command(
926 $initial_value = buffer.info.shell_command ||
927 get_external_handler_for_mime_type(buffer.info.MIME_type));
928 download_shell_command(buffer, cwd, cmd);
931 function download_manager_ui()
933 download_manager_ui.prototype = {
934 QueryInterface : XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
936 getAttention : function () {},
937 show : function () {},
942 function download_manager_show_builtin_ui(window) {
943 download_manager_builtin_ui.show(window);
945 interactive("download-manager-show-builtin-ui",
946 "Show the built-in (Firefox-style) download manager user interface.",
947 function (I) {download_manager_show_builtin_ui(I.window);});
951 define_variable("download_temporary_file_open_buffer_delay", 500,
952 "Delay (in milliseconds) before a download buffer is opened for temporary downloads.\n" +
953 "This variable takes effect only if `open_download_buffer_automatically' is in " +
954 "`download_added_hook', as it is by default.");
957 define_variable("download_buffer_automatic_open_target", OPEN_NEW_WINDOW,
958 "Target for download buffers created by the `open_download_buffer_automatically' function.\n" +
959 "This variable takes effect only if `open_download_buffer_auotmatically' is in " +
960 "`download_added_hook', as it is by default.");
962 function open_download_buffer_automatically(info) {
963 var buf = info.source_buffer;
964 var target = download_buffer_automatic_open_target;
966 target = OPEN_NEW_WINDOW;
967 if (info.temporary_status == DOWNLOAD_NOT_TEMPORARY ||
968 !(download_temporary_file_open_buffer_delay > 0))
969 create_buffer(buf, buffer_creator(download_buffer, $info = info), target);
975 add_hook.call(info, "download_finished_hook", finish);
976 timer = call_after_timeout(function () {
977 remove_hook.call(info, "download_finished_hook", finish);
978 create_buffer(buf, buffer_creator(download_buffer, $info = info), target);
979 }, download_temporary_file_open_buffer_delay);
982 add_hook("download_added_hook", open_download_buffer_automatically);