new module to enable editing and deleting of bookmarks
[conkeror/arlinius.git] / modules / download-manager.js
blob1f3cd794fc869e74695c966602d3ecedddc53047
1 /**
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
6 * COPYING file.
7 **/
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;
47 });
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);
54 target_file: null,
55 shell_command: null,
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);
66 /**
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; },
75 get size () {
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 */)
82 return s;
83 return -1;
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; },
89 get MIME_type () {
90 if (this.MIME_info)
91 return this.MIME_info.MIMEType;
92 return null;
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 + ")";
102 return target;
105 throw_if_removed: function () {
106 if (this.removed)
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.");
124 default:
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:
136 try {
137 download_manager_service.cancelDownload(this.id);
138 } catch (e) {
139 throw interactive_error("Download cannot be canceled.");
141 break;
142 default:
143 this.throw_state_error();
147 retry: function () {
148 this.throw_if_removed();
149 switch (this.state) {
150 case DOWNLOAD_CANCELED:
151 case DOWNLOAD_FAILED:
152 try {
153 download_manager_service.retryDownload(this.id);
154 } catch (e) {
155 throw interactive_error("Download cannot be retried.");
157 break;
158 default:
159 this.throw_state_error();
163 resume: function () {
164 this.throw_if_removed();
165 switch (this.state) {
166 case DOWNLOAD_PAUSED:
167 try {
168 download_manager_service.resumeDownload(this.id);
169 } catch (e) {
170 throw interactive_error("Download cannot be resumed.");
172 break;
173 default:
174 this.throw_state_error();
178 pause: function () {
179 this.throw_if_removed();
180 switch (this.state) {
181 case DOWNLOAD_DOWNLOADING:
182 case DOWNLOAD_QUEUED:
183 try {
184 download_manager_service.pauseDownload(this.id);
185 } catch (e) {
186 throw interactive_error("Download cannot be paused.");
188 break;
189 default:
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:
200 try {
201 download_manager_service.removeDownload(this.id);
202 } catch (e) {
203 throw interactive_error("Download cannot be removed.");
205 break;
206 default:
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.");
214 try {
215 this.target_file.remove(false);
216 } catch (e) {
217 if ("result" in e) {
218 switch (e.result) {
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.");
227 throw e;
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);
239 return info;
242 function match_registered_download (mozilla_info) {
243 let list = unmanaged_download_info_list;
244 let t = Date.now();
245 for (let i = 0; i < list.length; ++i) {
246 let x = list[i];
247 if (x.registered_source_uri == mozilla_info.source) {
248 list.splice(i, 1);
249 return x;
251 if (t - x.registered_time_stamp > download_info_max_queue_delay) {
252 list.splice(i, 1);
253 --i;
254 continue;
257 return null;
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) {
278 var info = null;
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);
286 if (info == null) {
287 info = new download_info(null, download);
288 dumpln("error: encountered unknown new download");
289 } else {
290 info.attach(download);
292 } else {
293 info = id_to_download_info[download.id];
294 if (info == null) {
295 dumpln("Error: encountered unknown download");
297 } else {
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 () {
306 try {
307 yield shell_command_with_argument(info.shell_command,
308 info.target_file.path,
309 $cwd = info.shell_command_cwd);
310 } catch (e) {
311 handle_interactive_error(info.source_buffer.window, e);
312 } finally {
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);
320 }());
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,
330 download) {
331 var info = id_to_download_info[download.id];
332 if (info == null) {
333 dumpln("error: encountered unknown download in progress change");
334 return;
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) {
351 switch(topic) {
352 case "download-manager-remove-download":
353 var ids = [];
354 if (!subject) {
355 // Remove all downloads
356 for (let i in id_to_download_info)
357 ids.push(i);
358 } else {
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);
363 } else
364 ids.push(id);
366 for each (let i in ids) {
367 dumpln("deleting download: " + i);
368 let d = id_to_download_info[i];
369 d.removed = true;
370 download_removed_hook.run(d);
371 delete id_to_download_info[i];
373 break;
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();
393 keywords(arguments);
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;
398 this.update_title();
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
436 var new_title;
437 var info = this.info;
438 var append_transfer_info = false;
439 var append_speed_info = true;
440 var label = null;
441 switch(info.state) {
442 case DOWNLOAD_DOWNLOADING:
443 label = "Downloading";
444 append_transfer_info = true;
445 break;
446 case DOWNLOAD_FINISHED:
447 label = "Download complete";
448 break;
449 case DOWNLOAD_FAILED:
450 label = "Download failed";
451 append_transfer_info = true;
452 append_speed_info = false;
453 break;
454 case DOWNLOAD_CANCELED:
455 label = "Download canceled";
456 append_transfer_info = true;
457 append_speed_info = false;
458 break;
459 case DOWNLOAD_PAUSED:
460 label = "Download paused";
461 append_transfer_info = true;
462 append_speed_info = false;
463 break;
464 case DOWNLOAD_QUEUED:
465 default:
466 label = "Download queued";
467 break;
470 if (append_transfer_info) {
471 if (append_speed_info)
472 new_title = label + " at " + pretty_print_file_size(info.speed).join(" ") + "/s: ";
473 else
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];
480 else
481 new_title += trans.join(" ") + "/" + total.join(" ");
482 } else
483 new_title += trans.join(" ");
484 if (info.percent_complete >= 0)
485 new_title += " (" + info.percent_complete + "%)";
486 } else
487 new_title = label;
488 if (new_title != this.title) {
489 this.title = new_title;
490 return true;
492 return false;
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");
526 var row, cell;
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");
537 var target_label;
538 if (info.temporary_status != DOWNLOAD_NOT_TEMPORARY)
539 target_label = "Temp. file:";
540 else
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 () {
589 if (!this.generated)
590 return;
591 var info = this.info;
592 var label = null;
593 switch (info.state) {
594 case DOWNLOAD_DOWNLOADING:
595 label = "Downloading";
596 break;
597 case DOWNLOAD_FINISHED:
598 label = "Completed";
599 break;
600 case DOWNLOAD_FAILED:
601 label = "Failed";
602 break;
603 case DOWNLOAD_CANCELED:
604 label = "Canceled";
605 break;
606 case DOWNLOAD_PAUSED:
607 label = "Paused";
608 break;
609 case DOWNLOAD_QUEUED:
610 default:
611 label = "Queued";
612 break;
614 this.status_textnode.nodeValue = label + ":";
615 this.target_file_node.nodeValue = info.target_file_text();
616 this.update_time_field();
618 var tran_text = "";
619 if (info.state == DOWNLOAD_FINISHED)
620 tran_text = pretty_print_file_size(info.size).join(" ");
621 else {
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];
627 else
628 tran_text += trans.join(" ") + "/" + total.join(" ");
629 } else
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 + "%";
637 } else {
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";
648 var text = "";
649 if (info.state == DOWNLOAD_DOWNLOADING)
650 text = pretty_print_file_size(info.speed).join(" ") + "/s, ";
651 if (info.state == DOWNLOAD_DOWNLOADING &&
652 info.size >= 0 &&
653 info.speed > 0)
655 let remaining = (info.size - info.amount_transferred) / info.speed;
656 text += pretty_print_time(remaining) + " left (" + elapsed_text + ")";
657 } else
658 text = elapsed_text;
659 this.time_textnode.nodeValue = text;
662 update_command_field: function () {
663 if (!this.generated)
664 return;
665 if (this.info.shell_command != null) {
666 this.command_div_node.style.display = "";
667 var label;
668 if (this.info.running_shell_command)
669 label = "Running:";
670 else if (this.info.state == DOWNLOAD_FINISHED)
671 label = "Ran command:";
672 else
673 label = "Run command:";
674 this.command_label_textnode.nodeValue = label;
675 this.command_textnode.nodeValue = this.info.shell_command;
676 } else
677 this.command_div_node.style.display = "none";
681 function download_cancel (buffer) {
682 check_buffer(buffer, download_buffer);
683 var info = buffer.info;
684 info.cancel();
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.",
691 function (I) {
692 let result = yield I.window.minibuffer.read_single_character_option(
693 $prompt = "Cancel this download? (y/n)",
694 $options = ["y", "n"]);
695 if (result == "y")
696 download_cancel(I.buffer);
699 function download_retry (buffer) {
700 check_buffer(buffer, download_buffer);
701 var info = buffer.info;
702 info.retry();
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);
714 buffer.info.pause();
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);
750 else
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' "+
757 "command.",
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);
765 else
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);
789 return;
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);
795 else
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.",
803 function (I) {
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(
807 $cwd = cwd,
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 () {},
820 visible: false
824 interactive("download-manager-show-builtin-ui",
825 "Show the built-in (Firefox-style) download manager window.",
826 function (I) {
827 Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
828 .getService(Ci.nsIDownloadManagerUI)
829 .show(I.window);
834 * Download-show
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 () {
850 keywords(arguments,
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) {
871 if (! window)
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);
898 } else {
899 var timer = null;
900 function finish () {
901 timer.cancel();
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 "+
914 "its progress.",
915 alternates(download_show_new_buffer,
916 download_show_new_window));
918 provide("download-manager");