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 unmanaged_download_info_list
= [];
17 var id_to_download_info
= {};
19 // Import these constants for convenience
20 const DOWNLOAD_NOTSTARTED
= Ci
.nsIDownloadManager
.DOWNLOAD_NOTSTARTED
;
21 const DOWNLOAD_DOWNLOADING
= Ci
.nsIDownloadManager
.DOWNLOAD_DOWNLOADING
;
22 const DOWNLOAD_FINISHED
= Ci
.nsIDownloadManager
.DOWNLOAD_FINISHED
;
23 const DOWNLOAD_FAILED
= Ci
.nsIDownloadManager
.DOWNLOAD_FAILED
;
24 const DOWNLOAD_CANCELED
= Ci
.nsIDownloadManager
.DOWNLOAD_CANCELED
;
25 const DOWNLOAD_PAUSED
= Ci
.nsIDownloadManager
.DOWNLOAD_PAUSED
;
26 const DOWNLOAD_QUEUED
= Ci
.nsIDownloadManager
.DOWNLOAD_QUEUED
;
27 const DOWNLOAD_BLOCKED
= Ci
.nsIDownloadManager
.DOWNLOAD_BLOCKED
;
28 const DOWNLOAD_SCANNING
= Ci
.nsIDownloadManager
.DOWNLOAD_SCANNING
;
31 const DOWNLOAD_NOT_TEMPORARY
= 0;
32 const DOWNLOAD_TEMPORARY_FOR_ACTION
= 1;
33 const DOWNLOAD_TEMPORARY_FOR_COMMAND
= 2;
35 function download_info (source_buffer
, mozilla_info
, target_file
) {
36 this.source_buffer
= source_buffer
;
37 this.target_file
= target_file
;
38 if (mozilla_info
!= null)
39 this.attach(mozilla_info
);
41 download_info
.prototype = {
42 constructor: download_info
,
43 attach: function (mozilla_info
) {
44 if (!this.target_file
)
45 this.__defineGetter__("target_file", function () {
46 return this.mozilla_info
.targetFile
;
48 else if (this.target_file
.path
!= mozilla_info
.targetFile
.path
)
49 throw interactive_error("Download target file unexpected.");
50 this.mozilla_info
= mozilla_info
;
51 id_to_download_info
[mozilla_info
.id
] = this;
52 download_added_hook
.run(this);
56 shell_command_cwd
: null,
57 temporary_status
: DOWNLOAD_NOT_TEMPORARY
,
58 action_description
: null,
59 set_shell_command: function (str
, cwd
) {
60 this.shell_command
= str
;
61 this.shell_command_cwd
= cwd
;
62 if (this.mozilla_info
)
63 download_shell_command_change_hook
.run(this);
67 * None of the following members may be used until attach is called
70 // Reflectors to properties of nsIDownload
71 get state () { return this.mozilla_info
.state
; },
72 get display_name () { return this.mozilla_info
.displayName
; },
73 get amount_transferred () { return this.mozilla_info
.amountTransferred
; },
74 get percent_complete () { return this.mozilla_info
.percentComplete
; },
76 var s
= this.mozilla_info
.size
;
77 /* nsIDownload.size is a PRUint64, and will have value
78 * LL_MAXUINT (2^64 - 1) to indicate an unknown size. Because
79 * JavaScript only has a double numerical type, this value
80 * cannot be represented exactly, so 2^36 is used instead as the cutoff. */
81 if (s
< 68719476736 /* 2^36 */)
85 get source () { return this.mozilla_info
.source
; },
86 get start_time () { return this.mozilla_info
.startTime
; },
87 get speed () { return this.mozilla_info
.speed
; },
88 get MIME_info () { return this.mozilla_info
.MIMEInfo
; },
91 return this.MIME_info
.MIMEType
;
94 get id () { return this.mozilla_info
.id
; },
95 get referrer () { return this.mozilla_info
.referrer
; },
97 target_file_text: function () {
98 let target
= this.target_file
.path
;
99 let display
= this.display_name
;
100 if (target
.indexOf(display
, target
.length
- display
.length
) == -1)
101 target
+= " (" + display
+ ")";
105 throw_if_removed: function () {
107 throw interactive_error("Download has already been removed from the download manager.");
110 throw_state_error: function () {
111 switch (this.state
) {
112 case DOWNLOAD_DOWNLOADING
:
113 throw interactive_error("Download is already in progress.");
114 case DOWNLOAD_FINISHED
:
115 throw interactive_error("Download has already completed.");
116 case DOWNLOAD_FAILED
:
117 throw interactive_error("Download has already failed.");
118 case DOWNLOAD_CANCELED
:
119 throw interactive_error("Download has already been canceled.");
120 case DOWNLOAD_PAUSED
:
121 throw interactive_error("Download has already been paused.");
122 case DOWNLOAD_QUEUED
:
123 throw interactive_error("Download is queued.");
125 throw new Error("Download has unexpected state: " + this.state
);
129 // Download manager operations
130 cancel: function () {
131 this.throw_if_removed();
132 switch (this.state
) {
133 case DOWNLOAD_DOWNLOADING
:
134 case DOWNLOAD_PAUSED
:
135 case DOWNLOAD_QUEUED
:
137 download_manager_service
.cancelDownload(this.id
);
139 throw interactive_error("Download cannot be canceled.");
143 this.throw_state_error();
148 this.throw_if_removed();
149 switch (this.state
) {
150 case DOWNLOAD_CANCELED
:
151 case DOWNLOAD_FAILED
:
153 download_manager_service
.retryDownload(this.id
);
155 throw interactive_error("Download cannot be retried.");
159 this.throw_state_error();
163 resume: function () {
164 this.throw_if_removed();
165 switch (this.state
) {
166 case DOWNLOAD_PAUSED
:
168 download_manager_service
.resumeDownload(this.id
);
170 throw interactive_error("Download cannot be resumed.");
174 this.throw_state_error();
179 this.throw_if_removed();
180 switch (this.state
) {
181 case DOWNLOAD_DOWNLOADING
:
182 case DOWNLOAD_QUEUED
:
184 download_manager_service
.pauseDownload(this.id
);
186 throw interactive_error("Download cannot be paused.");
190 this.throw_state_error();
194 remove: function () {
195 this.throw_if_removed();
196 switch (this.state
) {
197 case DOWNLOAD_FAILED
:
198 case DOWNLOAD_CANCELED
:
199 case DOWNLOAD_FINISHED
:
201 download_manager_service
.removeDownload(this.id
);
203 throw interactive_error("Download cannot be removed.");
207 throw interactive_error("Download is still in progress.");
211 delete_target: function () {
212 if (this.state
!= DOWNLOAD_FINISHED
)
213 throw interactive_error("Download has not finished.");
215 this.target_file
.remove(false);
219 case Cr
.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
:
220 throw interactive_error("File has already been deleted.");
221 case Cr
.NS_ERROR_FILE_ACCESS_DENIED
:
222 throw interactive_error("Access denied");
223 case Cr
.NS_ERROR_FILE_DIR_NOT_EMPTY
:
224 throw interactive_error("Failed to delete file.");
232 var define_download_local_hook
= simple_local_hook_definer();
234 function register_download (buffer
, source_uri
, target_file
) {
235 var info
= new download_info(buffer
, null, target_file
);
236 info
.registered_time_stamp
= Date
.now();
237 info
.registered_source_uri
= source_uri
;
238 unmanaged_download_info_list
.push(info
);
242 function match_registered_download (mozilla_info
) {
243 let list
= unmanaged_download_info_list
;
245 for (let i
= 0; i
< list
.length
; ++i
) {
247 if (x
.registered_source_uri
== mozilla_info
.source
) {
251 if (t
- x
.registered_time_stamp
> download_info_max_queue_delay
) {
260 define_download_local_hook("download_added_hook");
261 define_download_local_hook("download_removed_hook");
262 define_download_local_hook("download_finished_hook");
263 define_download_local_hook("download_progress_change_hook");
264 define_download_local_hook("download_state_change_hook");
265 define_download_local_hook("download_shell_command_change_hook");
267 define_variable('delete_temporary_files_for_command', true,
268 'If this is set to true, temporary files downloaded to run a command '+
269 'on them will be deleted once the command completes. If not, the file '+
270 'will stay around forever unless deleted outside the browser.');
272 var download_info_max_queue_delay
= 100;
274 var download_progress_listener
= {
275 QueryInterface
: generate_QI(Ci
.nsIDownloadProgressListener
),
277 onDownloadStateChange: function (state
, download
) {
279 /* FIXME: Determine if only new downloads will have this state
280 * as their previous state. */
282 dumpln("download state change: " + download
.source
.spec
+ ": " + state
+ ", " + download
.state
+ ", " + download
.id
);
284 if (state
== DOWNLOAD_NOTSTARTED
) {
285 info
= match_registered_download(download
);
287 info
= new download_info(null, download
);
288 dumpln("error: encountered unknown new download");
290 info
.attach(download
);
293 info
= id_to_download_info
[download
.id
];
295 dumpln("Error: encountered unknown download");
298 info
.mozilla_info
= download
;
299 download_state_change_hook
.run(info
);
300 if (info
.state
== DOWNLOAD_FINISHED
) {
301 download_finished_hook
.run(info
);
303 if (info
.shell_command
!= null) {
304 info
.running_shell_command
= true;
305 co_call(function () {
307 yield shell_command_with_argument(info
.shell_command
,
308 info
.target_file
.path
,
309 $cwd
= info
.shell_command_cwd
);
311 handle_interactive_error(info
.source_buffer
.window
, e
);
313 if (info
.temporary_status
== DOWNLOAD_TEMPORARY_FOR_COMMAND
)
314 if(delete_temporary_files_for_command
) {
315 info
.target_file
.remove(false /* not recursive */);
317 info
.running_shell_command
= false;
318 download_shell_command_change_hook
.run(info
);
321 download_shell_command_change_hook
.run(info
);
328 onProgressChange: function (progress
, request
, cur_self_progress
, max_self_progress
,
329 cur_total_progress
, max_total_progress
,
331 var info
= id_to_download_info
[download
.id
];
333 dumpln("error: encountered unknown download in progress change");
336 info
.mozilla_info
= download
;
337 download_progress_change_hook
.run(info
);
338 //dumpln("download progress change: " + download.source.spec + ": " + cur_self_progress + "/" + max_self_progress + " "
339 // + cur_total_progress + "/" + max_total_progress + ", " + download.state + ", " + download.id);
342 onSecurityChange: function (progress
, request
, state
, download
) {
345 onStateChange: function (progress
, request
, state_flags
, status
, download
) {
349 var download_observer
= {
350 observe: function (subject
, topic
, data
) {
352 case "download-manager-remove-download":
355 // Remove all downloads
356 for (let i
in id_to_download_info
)
359 let id
= subject
.QueryInterface(Ci
.nsISupportsPRUint32
);
360 /* FIXME: determine if this should really be an error */
361 if (!(id
in id_to_download_info
)) {
362 dumpln("Error: download-manager-remove-download event received for unknown download: " + id
);
366 for each (let i
in ids
) {
367 dumpln("deleting download: " + i
);
368 let d
= id_to_download_info
[i
];
370 download_removed_hook
.run(d
);
371 delete id_to_download_info
[i
];
377 observer_service
.addObserver(download_observer
, "download-manager-remove-download", false);
379 download_manager_service
.addListener(download_progress_listener
);
381 define_variable("download_buffer_min_update_interval", 2000,
382 "Minimum interval (in milliseconds) between updates in download progress buffers.\n" +
383 "Lowering this interval will increase the promptness of the progress display at " +
384 "the cost of using additional processor time.");
386 function download_buffer_modality (buffer
, element
) {
387 buffer
.keymaps
.push(download_buffer_keymap
);
390 define_keywords("$info");
391 function download_buffer (window
) {
392 this.constructor_begin();
394 special_buffer
.call(this, window
, forward_keywords(arguments
));
395 this.info
= arguments
.$info
;
396 this.local
.cwd
= this.info
.mozilla_info
.targetFile
.parent
;
397 this.description
= this.info
.mozilla_info
.source
.spec
;
400 this.progress_change_handler_fn
= method_caller(this, this.handle_progress_change
);
401 add_hook
.call(this.info
, "download_progress_change_hook", this.progress_change_handler_fn
);
402 add_hook
.call(this.info
, "download_state_change_hook", this.progress_change_handler_fn
);
403 this.command_change_handler_fn
= method_caller(this, this.update_command_field
);
404 add_hook
.call(this.info
, "download_shell_command_change_hook", this.command_change_handler_fn
);
405 this.modalities
.push(download_buffer_modality
);
406 this.constructor_end();
408 download_buffer
.prototype = {
409 constructor: download_buffer
,
410 __proto__
: special_buffer
.prototype,
411 toString: function () "#<download_buffer>",
413 destroy: function () {
414 remove_hook
.call(this.info
, "download_progress_change_hook", this.progress_change_handler_fn
);
415 remove_hook
.call(this.info
, "download_state_change_hook", this.progress_change_handler_fn
);
416 remove_hook
.call(this.info
, "download_shell_command_change_hook", this.command_change_handler_fn
);
418 // Remove all node references
419 delete this.status_textnode
;
420 delete this.target_file_node
;
421 delete this.transferred_div_node
;
422 delete this.transferred_textnode
;
423 delete this.progress_container_node
;
424 delete this.progress_bar_node
;
425 delete this.percent_textnode
;
426 delete this.time_textnode
;
427 delete this.command_div_node
;
428 delete this.command_label_textnode
;
429 delete this.command_textnode
;
431 special_buffer
.prototype.destroy
.call(this);
434 update_title: function () {
435 // FIXME: do this properly
437 var info
= this.info
;
438 var append_transfer_info
= false;
439 var append_speed_info
= true;
442 case DOWNLOAD_DOWNLOADING
:
443 label
= "Downloading";
444 append_transfer_info
= true;
446 case DOWNLOAD_FINISHED
:
447 label
= "Download complete";
449 case DOWNLOAD_FAILED
:
450 label
= "Download failed";
451 append_transfer_info
= true;
452 append_speed_info
= false;
454 case DOWNLOAD_CANCELED
:
455 label
= "Download canceled";
456 append_transfer_info
= true;
457 append_speed_info
= false;
459 case DOWNLOAD_PAUSED
:
460 label
= "Download paused";
461 append_transfer_info
= true;
462 append_speed_info
= false;
464 case DOWNLOAD_QUEUED
:
466 label
= "Download queued";
470 if (append_transfer_info
) {
471 if (append_speed_info
)
472 new_title
= label
+ " at " + pretty_print_file_size(info
.speed
).join(" ") + "/s: ";
474 new_title
= label
+ ": ";
475 var trans
= pretty_print_file_size(info
.amount_transferred
);
476 if (info
.size
>= 0) {
477 var total
= pretty_print_file_size(info
.size
);
478 if (trans
[1] == total
[1])
479 new_title
+= trans
[0] + "/" + total
[0] + " " + total
[1];
481 new_title
+= trans
.join(" ") + "/" + total
.join(" ");
483 new_title
+= trans
.join(" ");
484 if (info
.percent_complete
>= 0)
485 new_title
+= " (" + info
.percent_complete
+ "%)";
488 if (new_title
!= this.title
) {
489 this.title
= new_title
;
495 handle_progress_change: function () {
496 var cur_time
= Date
.now();
497 if (this.last_update
== null ||
498 (cur_time
- this.last_update
) > download_buffer_min_update_interval
||
499 this.info
.state
!= this.previous_state
) {
501 if (this.update_title())
502 buffer_title_change_hook
.run(this);
504 if (this.generated
) {
505 this.update_fields();
507 this.previous_status
= this.info
.status
;
508 this.last_update
= cur_time
;
512 generate: function () {
513 var d
= this.document
;
514 var g
= new dom_generator(d
, XHTML_NS
);
516 /* Warning: If any additional node references are saved in
517 * this function, appropriate code to delete the saved
518 * properties must be added to destroy method. */
520 var info
= this.info
;
522 d
.body
.setAttribute("class", "download-buffer");
524 g
.add_stylesheet("chrome://conkeror-gui/content/downloads.css");
527 var table
= g
.element("table", d
.body
);
529 row
= g
.element("tr", table
, "class", "download-info", "id", "download-source");
530 cell
= g
.element("td", row
, "class", "download-label");
531 this.status_textnode
= g
.text("", cell
);
532 cell
= g
.element("td", row
, "class", "download-value");
533 g
.text(info
.source
.spec
, cell
);
535 row
= g
.element("tr", table
, "class", "download-info", "id", "download-target");
536 cell
= g
.element("td", row
, "class", "download-label");
538 if (info
.temporary_status
!= DOWNLOAD_NOT_TEMPORARY
)
539 target_label
= "Temp. file:";
541 target_label
= "Target:";
542 g
.text(target_label
, cell
);
543 cell
= g
.element("td", row
, "class", "download-value");
544 this.target_file_node
= g
.text("", cell
);
546 row
= g
.element("tr", table
, "class", "download-info", "id", "download-mime-type");
547 cell
= g
.element("td", row
, "class", "download-label");
548 g
.text("MIME type:", cell
);
549 cell
= g
.element("td", row
, "class", "download-value");
550 g
.text(info
.MIME_type
|| "unknown", cell
);
552 this.transferred_div_node
= row
=
553 g
.element("tr", table
, "class", "download-info", "id", "download-transferred");
554 cell
= g
.element("td", row
, "class", "download-label");
555 g
.text("Transferred:", cell
);
556 cell
= g
.element("td", row
, "class", "download-value");
557 var sub_item
= g
.element("div", cell
);
558 this.transferred_textnode
= g
.text("", sub_item
);
559 sub_item
= g
.element("div", cell
, "id", "download-percent");
560 this.percent_textnode
= g
.text("", sub_item
);
561 this.progress_container_node
= sub_item
= g
.element("div", cell
, "id", "download-progress-container");
562 this.progress_bar_node
= g
.element("div", sub_item
, "id", "download-progress-bar");
564 row
= g
.element("tr", table
, "class", "download-info", "id", "download-time");
565 cell
= g
.element("td", row
, "class", "download-label");
566 g
.text("Time:", cell
);
567 cell
= g
.element("td", row
, "class", "download-value");
568 this.time_textnode
= g
.text("", cell
);
570 if (info
.action_description
!= null) {
571 row
= g
.element("tr", table
, "class", "download-info", "id", "download-action");
572 cell
= g
.element("div", row
, "class", "download-label");
573 g
.text("Action:", cell
);
574 cell
= g
.element("div", row
, "class", "download-value");
575 g
.text(info
.action_description
, cell
);
578 this.command_div_node
= row
= g
.element("tr", table
, "class", "download-info", "id", "download-command");
579 cell
= g
.element("td", row
, "class", "download-label");
580 this.command_label_textnode
= g
.text("Run command:", cell
);
581 cell
= g
.element("td", row
, "class", "download-value");
582 this.command_textnode
= g
.text("", cell
);
584 this.update_fields();
585 this.update_command_field();
588 update_fields: function () {
591 var info
= this.info
;
593 switch (info
.state
) {
594 case DOWNLOAD_DOWNLOADING
:
595 label
= "Downloading";
597 case DOWNLOAD_FINISHED
:
600 case DOWNLOAD_FAILED
:
603 case DOWNLOAD_CANCELED
:
606 case DOWNLOAD_PAUSED
:
609 case DOWNLOAD_QUEUED
:
614 this.status_textnode
.nodeValue
= label
+ ":";
615 this.target_file_node
.nodeValue
= info
.target_file_text();
616 this.update_time_field();
619 if (info
.state
== DOWNLOAD_FINISHED
)
620 tran_text
= pretty_print_file_size(info
.size
).join(" ");
622 var trans
= pretty_print_file_size(info
.amount_transferred
);
623 if (info
.size
>= 0) {
624 var total
= pretty_print_file_size(info
.size
);
625 if (trans
[1] == total
[1])
626 tran_text
+= trans
[0] + "/" + total
[0] + " " + total
[1];
628 tran_text
+= trans
.join(" ") + "/" + total
.join(" ");
630 tran_text
+= trans
.join(" ");
632 this.transferred_textnode
.nodeValue
= tran_text
;
633 if (info
.percent_complete
>= 0) {
634 this.progress_container_node
.style
.display
= "";
635 this.percent_textnode
.nodeValue
= info
.percent_complete
+ "%";
636 this.progress_bar_node
.style
.width
= info
.percent_complete
+ "%";
638 this.percent_textnode
.nodeValue
= "";
639 this.progress_container_node
.style
.display
= "none";
642 this.update_command_field();
645 update_time_field: function () {
646 var info
= this.info
;
647 var elapsed_text
= pretty_print_time((Date
.now() - info
.start_time
/ 1000) / 1000) + " elapsed";
649 if (info
.state
== DOWNLOAD_DOWNLOADING
)
650 text
= pretty_print_file_size(info
.speed
).join(" ") + "/s, ";
651 if (info
.state
== DOWNLOAD_DOWNLOADING
&&
655 let remaining
= (info
.size
- info
.amount_transferred
) / info
.speed
;
656 text
+= pretty_print_time(remaining
) + " left (" + elapsed_text
+ ")";
659 this.time_textnode
.nodeValue
= text
;
662 update_command_field: function () {
665 if (this.info
.shell_command
!= null) {
666 this.command_div_node
.style
.display
= "";
668 if (this.info
.running_shell_command
)
670 else if (this.info
.state
== DOWNLOAD_FINISHED
)
671 label
= "Ran command:";
673 label
= "Run command:";
674 this.command_label_textnode
.nodeValue
= label
;
675 this.command_textnode
.nodeValue
= this.info
.shell_command
;
677 this.command_div_node
.style
.display
= "none";
681 function download_cancel (buffer
) {
682 check_buffer(buffer
, download_buffer
);
683 var info
= buffer
.info
;
685 buffer
.window
.minibuffer
.message("Download canceled");
687 interactive("download-cancel",
688 "Cancel the current download.\n" +
689 "The download can later be retried using the `download-retry' "+
690 "command, but any data already transferred will be lost.",
692 let result
= yield I
.window
.minibuffer
.read_single_character_option(
693 $prompt
= "Cancel this download? (y/n)",
694 $options
= ["y", "n"]);
696 download_cancel(I
.buffer
);
699 function download_retry (buffer
) {
700 check_buffer(buffer
, download_buffer
);
701 var info
= buffer
.info
;
703 buffer
.window
.minibuffer
.message("Download retried");
705 interactive("download-retry",
706 "Retry a failed or canceled download.\n" +
707 "This command can be used to retry a download that failed or "+
708 "was canceled using the `download-cancel' command. The download "+
709 "will begin from the start again.",
710 function (I
) { download_retry(I
.buffer
); });
712 function download_pause (buffer
) {
713 check_buffer(buffer
, download_buffer
);
715 buffer
.window
.minibuffer
.message("Download paused");
717 interactive("download-pause",
718 "Pause the current download.\n" +
719 "The download can later be resumed using the `download-resume' command. "+
720 "The data already transferred will not be lost.",
721 function (I
) { download_pause(I
.buffer
); });
723 function download_resume (buffer
) {
724 check_buffer(buffer
, download_buffer
);
725 buffer
.info
.resume();
726 buffer
.window
.minibuffer
.message("Download resumed");
728 interactive("download-resume",
729 "Resume the current download.\n" +
730 "This command can be used to resume a download paused using the "+
731 "`download-pause' command.",
732 function (I
) { download_resume(I
.buffer
); });
734 function download_remove (buffer
) {
735 check_buffer(buffer
, download_buffer
);
736 buffer
.info
.remove();
737 buffer
.window
.minibuffer
.message("Download removed");
739 interactive("download-remove",
740 "Remove the current download from the download manager.\n" +
741 "This command can only be used on inactive (paused, canceled, "+
742 "completed, or failed) downloads.",
743 function (I
) { download_remove(I
.buffer
); });
745 function download_retry_or_resume (buffer
) {
746 check_buffer(buffer
, download_buffer
);
747 var info
= buffer
.info
;
748 if (info
.state
== DOWNLOAD_PAUSED
)
749 download_resume(buffer
);
751 download_retry(buffer
);
753 interactive("download-retry-or-resume",
754 "Retry or resume the current download.\n" +
755 "This command can be used to resume a download paused using the " +
756 "`download-pause' command or canceled using the `download-cancel' "+
758 function (I
) { download_retry_or_resume(I
.buffer
); });
760 function download_pause_or_resume (buffer
) {
761 check_buffer(buffer
, download_buffer
);
762 var info
= buffer
.info
;
763 if (info
.state
== DOWNLOAD_PAUSED
)
764 download_resume(buffer
);
766 download_pause(buffer
);
768 interactive("download-pause-or-resume",
769 "Pause or resume the current download.\n" +
770 "This command toggles the paused state of the current download.",
771 function (I
) { download_pause_or_resume(I
.buffer
); });
773 function download_delete_target (buffer
) {
774 check_buffer(buffer
, download_buffer
);
775 var info
= buffer
.info
;
776 info
.delete_target();
777 buffer
.window
.minibuffer
.message("Deleted file: " + info
.target_file
.path
);
779 interactive("download-delete-target",
780 "Delete the target file of the current download.\n" +
781 "This command can only be used if the download has finished successfully.",
782 function (I
) { download_delete_target(I
.buffer
); });
784 function download_shell_command (buffer
, cwd
, cmd
) {
785 check_buffer(buffer
, download_buffer
);
786 var info
= buffer
.info
;
787 if (info
.state
== DOWNLOAD_FINISHED
) {
788 shell_command_with_argument_blind(cmd
, info
.target_file
.path
, $cwd
= cwd
);
791 if (info
.state
!= DOWNLOAD_DOWNLOADING
&& info
.state
!= DOWNLOAD_PAUSED
&& info
.state
!= DOWNLOAD_QUEUED
)
792 info
.throw_state_error();
793 if (cmd
== null || cmd
.length
== 0)
794 info
.set_shell_command(null, cwd
);
796 info
.set_shell_command(cmd
, cwd
);
797 buffer
.window
.minibuffer
.message("Queued shell command: " + cmd
);
799 interactive("download-shell-command",
800 "Run a shell command on the target file of the current download.\n"+
801 "If the download is still in progress, the shell command will be queued "+
802 "to run when the download finishes.",
804 var buffer
= check_buffer(I
.buffer
, download_buffer
);
805 var cwd
= buffer
.info
.shell_command_cwd
|| I
.local
.cwd
;
806 var cmd
= yield I
.minibuffer
.read_shell_command(
808 $initial_value
= buffer
.info
.shell_command
||
809 external_content_handlers
.get(buffer
.info
.MIME_type
));
810 download_shell_command(buffer
, cwd
, cmd
);
813 function download_manager_ui () {}
814 download_manager_ui
.prototype = {
815 constructor: download_manager_ui
,
816 QueryInterface
: XPCOMUtils
.generateQI([Ci
.nsIDownloadManagerUI
]),
818 getAttention: function () {},
819 show: function () {},
824 interactive("download-manager-show-builtin-ui",
825 "Show the built-in (Firefox-style) download manager window.",
827 Components
.classesByID
["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
828 .getService(Ci
.nsIDownloadManagerUI
)
837 define_variable("download_temporary_file_open_buffer_delay", 500,
838 "Delay (in milliseconds) before a download buffer is opened for "+
839 "temporary downloads. If the download completes before this amount "+
840 "of time, no download buffer will be opened. This variable takes "+
841 "effect only if `open_download_buffer_automatically' is in "+
842 "`download_added_hook', which is the case by default.");
844 define_variable("download_buffer_automatic_open_target", OPEN_NEW_WINDOW
,
845 "Target(s) for download buffers created by "+
846 "`open_download_buffer_automatically'.");
848 minibuffer_auto_complete_preferences
.download
= true;
849 minibuffer
.prototype.read_download = function () {
851 $prompt
= "Download",
852 $completer
= all_word_completer(
853 $completions = function (visitor
) {
854 var dls
= download_manager_service
.activeDownloads
;
855 while (dls
.hasMoreElements()) {
856 let dl
= dls
.getNext();
857 visitor(id_to_download_info
[dl
.id
]);
860 $get_string = function (x
) x
.display_name
,
861 $get_description = function (x
) x
.source
.spec
,
862 $get_value = function (x
) x
),
863 $auto_complete
= "download",
864 $auto_complete_initial
= true,
865 $match_required
= true);
866 var result
= yield this.read(forward_keywords(arguments
));
867 yield co_return(result
);
870 function download_show (window
, target
, info
) {
872 target
= OPEN_NEW_WINDOW
;
873 create_buffer(window
, buffer_creator(download_buffer
, $info
= info
), target
);
876 function download_show_new_window (I
) {
877 var info
= yield I
.minibuffer
.read_download($prompt
= "Show download:");
878 download_show(I
.window
, OPEN_NEW_WINDOW
, info
);
881 function download_show_new_buffer (I
) {
882 var info
= yield I
.minibuffer
.read_download($prompt
= "Show download:");
883 download_show(I
.window
, OPEN_NEW_BUFFER
, info
);
886 function download_show_new_buffer_background (I
) {
887 var info
= yield I
.minibuffer
.read_download($prompt
= "Show download:");
888 download_show(I
.window
, OPEN_NEW_BUFFER_BACKGROUND
, info
);
891 function open_download_buffer_automatically (info
) {
892 var buf
= info
.source_buffer
;
893 var target
= download_buffer_automatic_open_target
;
894 if (info
.temporary_status
== DOWNLOAD_NOT_TEMPORARY
||
895 download_temporary_file_open_buffer_delay
== 0)
897 download_show(buf
.window
, target
, info
);
903 add_hook
.call(info
, "download_finished_hook", finish
);
904 timer
= call_after_timeout(function () {
905 remove_hook
.call(info
, "download_finished_hook", finish
);
906 download_show(buf
.window
, target
, info
);
907 }, download_temporary_file_open_buffer_delay
);
910 add_hook("download_added_hook", open_download_buffer_automatically
);
912 interactive("download-show",
913 "Prompt for an ongoing download and open a download buffer showing "+
915 alternates(download_show_new_buffer
,
916 download_show_new_window
));
918 provide("download-manager");