youtube: update scraper for youtube.com change
[conkeror.git] / modules / download-manager.js
blobf6849c7a301871420fb163b8fbce415d54686741
1 /**
2  * (C) Copyright 2008 Jeremy Maitin-Shepard
3  * (C) Copyright 2009 John Foerch
4  *
5  * Use, modification, and distribution are subject to the terms specified in the
6  * COPYING file.
7 **/
9 in_module(null);
11 require("special-buffer.js");
12 require("mime-type-override.js");
13 require("minibuffer-read-mime-type.js");
15 var download_manager_service = Cc["@mozilla.org/download-manager;1"]
16     .getService(Ci.nsIDownloadManager);
18 var unmanaged_download_info_list = [];
19 var id_to_download_info = {};
21 // Import these constants for convenience
22 const DOWNLOAD_NOTSTARTED = Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
23 const DOWNLOAD_DOWNLOADING = Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING;
24 const DOWNLOAD_FINISHED = Ci.nsIDownloadManager.DOWNLOAD_FINISHED;
25 const DOWNLOAD_FAILED = Ci.nsIDownloadManager.DOWNLOAD_FAILED;
26 const DOWNLOAD_CANCELED = Ci.nsIDownloadManager.DOWNLOAD_CANCELED;
27 const DOWNLOAD_PAUSED = Ci.nsIDownloadManager.DOWNLOAD_PAUSED;
28 const DOWNLOAD_QUEUED = Ci.nsIDownloadManager.DOWNLOAD_QUEUED;
29 const DOWNLOAD_BLOCKED = Ci.nsIDownloadManager.DOWNLOAD_BLOCKED;
30 const DOWNLOAD_SCANNING = Ci.nsIDownloadManager.DOWNLOAD_SCANNING;
33 const DOWNLOAD_NOT_TEMPORARY = 0;
34 const DOWNLOAD_TEMPORARY_FOR_ACTION = 1;
35 const DOWNLOAD_TEMPORARY_FOR_COMMAND = 2;
37 function download_info (source_buffer, mozilla_info, target_file) {
38     this.source_buffer = source_buffer;
39     this.target_file = target_file;
40     if (mozilla_info != null)
41         this.attach(mozilla_info);
43 download_info.prototype = {
44     constructor: download_info,
45     attach: function (mozilla_info) {
46         if (!this.target_file)
47             this.__defineGetter__("target_file", function () {
48                     return this.mozilla_info.targetFile;
49                 });
50         else if (this.target_file.path != mozilla_info.targetFile.path)
51             throw interactive_error("Download target file unexpected.");
52         this.mozilla_info = mozilla_info;
53         id_to_download_info[mozilla_info.id] = this;
54         download_added_hook.run(this);
55     },
57     target_file: null,
59     shell_command: null,
61     shell_command_cwd: null,
63     temporary_status: DOWNLOAD_NOT_TEMPORARY,
65     action_description: null,
67     set_shell_command: function (str, cwd) {
68         this.shell_command = str;
69         this.shell_command_cwd = cwd;
70         if (this.mozilla_info)
71             download_shell_command_change_hook.run(this);
72     },
74     /**
75      * None of the following members may be used until attach is called
76      */
78     // Reflectors to properties of nsIDownload
79     get state () { return this.mozilla_info.state; },
80     get display_name () { return this.mozilla_info.displayName; },
81     get amount_transferred () { return this.mozilla_info.amountTransferred; },
82     get percent_complete () { return this.mozilla_info.percentComplete; },
83     get size () {
84         var s = this.mozilla_info.size;
85         /* nsIDownload.size is a PRUint64, and will have value
86          * LL_MAXUINT (2^64 - 1) to indicate an unknown size.  Because
87          * JavaScript only has a double numerical type, this value
88          * cannot be represented exactly, so 2^36 is used instead as the cutoff. */
89         if (s < 68719476736 /* 2^36 */)
90             return s;
91         return -1;
92     },
93     get source () { return this.mozilla_info.source; },
94     get start_time () { return this.mozilla_info.startTime; },
95     get speed () { return this.mozilla_info.speed; },
96     get MIME_info () { return this.mozilla_info.MIMEInfo; },
97     get MIME_type () {
98         if (this.MIME_info)
99             return this.MIME_info.MIMEType;
100         return null;
101     },
102     get id () { return this.mozilla_info.id; },
103     get referrer () { return this.mozilla_info.referrer; },
105     target_file_text: function () {
106         let target = this.target_file.path;
107         let display = this.display_name;
108         if (target.indexOf(display, target.length - display.length) == -1)
109             target += " (" + display + ")";
110         return target;
111     },
113     throw_if_removed: function () {
114         if (this.removed)
115             throw interactive_error("Download has already been removed from the download manager.");
116     },
118     throw_state_error: function () {
119         switch (this.state) {
120         case DOWNLOAD_DOWNLOADING:
121             throw interactive_error("Download is already in progress.");
122         case DOWNLOAD_FINISHED:
123             throw interactive_error("Download has already completed.");
124         case DOWNLOAD_FAILED:
125             throw interactive_error("Download has already failed.");
126         case DOWNLOAD_CANCELED:
127             throw interactive_error("Download has already been canceled.");
128         case DOWNLOAD_PAUSED:
129             throw interactive_error("Download has already been paused.");
130         case DOWNLOAD_QUEUED:
131             throw interactive_error("Download is queued.");
132         default:
133             throw new Error("Download has unexpected state: " + this.state);
134         }
135     },
137     // Download manager operations
138     cancel: function ()  {
139         this.throw_if_removed();
140         switch (this.state) {
141         case DOWNLOAD_DOWNLOADING:
142         case DOWNLOAD_PAUSED:
143         case DOWNLOAD_QUEUED:
144             try {
145                 download_manager_service.cancelDownload(this.id);
146             } catch (e) {
147                 throw interactive_error("Download cannot be canceled.");
148             }
149             break;
150         default:
151             this.throw_state_error();
152         }
153     },
155     retry: function () {
156         this.throw_if_removed();
157         switch (this.state) {
158         case DOWNLOAD_CANCELED:
159         case DOWNLOAD_FAILED:
160             try {
161                 download_manager_service.retryDownload(this.id);
162             } catch (e) {
163                 throw interactive_error("Download cannot be retried.");
164             }
165             break;
166         default:
167             this.throw_state_error();
168         }
169     },
171     resume: function () {
172         this.throw_if_removed();
173         switch (this.state) {
174         case DOWNLOAD_PAUSED:
175             try {
176                 download_manager_service.resumeDownload(this.id);
177             } catch (e) {
178                 throw interactive_error("Download cannot be resumed.");
179             }
180             break;
181         default:
182             this.throw_state_error();
183         }
184     },
186     pause: function () {
187         this.throw_if_removed();
188         switch (this.state) {
189         case DOWNLOAD_DOWNLOADING:
190         case DOWNLOAD_QUEUED:
191             try {
192                 download_manager_service.pauseDownload(this.id);
193             } catch (e) {
194                 throw interactive_error("Download cannot be paused.");
195             }
196             break;
197         default:
198             this.throw_state_error();
199         }
200     },
202     remove: function () {
203         this.throw_if_removed();
204         switch (this.state) {
205         case DOWNLOAD_FAILED:
206         case DOWNLOAD_CANCELED:
207         case DOWNLOAD_FINISHED:
208             try {
209                 download_manager_service.removeDownload(this.id);
210             } catch (e) {
211                 throw interactive_error("Download cannot be removed.");
212             }
213             break;
214         default:
215             throw interactive_error("Download is still in progress.");
216         }
217     },
219     delete_target: function () {
220         if (this.state != DOWNLOAD_FINISHED)
221             throw interactive_error("Download has not finished.");
222         try {
223             this.target_file.remove(false);
224         } catch (e) {
225             if ("result" in e) {
226                 switch (e.result) {
227                 case Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
228                     throw interactive_error("File has already been deleted.");
229                 case Cr.NS_ERROR_FILE_ACCESS_DENIED:
230                     throw interactive_error("Access denied");
231                 case Cr.NS_ERROR_FILE_DIR_NOT_EMPTY:
232                     throw interactive_error("Failed to delete file.");
233                 }
234             }
235             throw e;
236         }
237     }
240 var define_download_local_hook = simple_local_hook_definer();
242 // FIXME: add more parameters
243 function register_download (buffer, source_uri, target_file) {
244     var info = new download_info(buffer, null, target_file);
245     info.registered_time_stamp = Date.now();
246     info.registered_source_uri = source_uri;
247     unmanaged_download_info_list.push(info);
248     return info;
251 function match_registered_download (mozilla_info) {
252     let list = unmanaged_download_info_list;
253     let t = Date.now();
254     for (let i = 0; i < list.length; ++i) {
255         let x = list[i];
256         if (x.registered_source_uri == mozilla_info.source) {
257             list.splice(i, 1);
258             return x;
259         }
260         if (t - x.registered_time_stamp > download_info_max_queue_delay) {
261             list.splice(i, 1);
262             --i;
263             continue;
264         }
265     }
266     return null;
269 define_download_local_hook("download_added_hook");
270 define_download_local_hook("download_removed_hook");
271 define_download_local_hook("download_finished_hook");
272 define_download_local_hook("download_progress_change_hook");
273 define_download_local_hook("download_state_change_hook");
274 define_download_local_hook("download_shell_command_change_hook");
276 define_variable('delete_temporary_files_for_command', true,
277     'If this is set to true, temporary files downloaded to run a command '+
278     'on them will be deleted once the command completes. If not, the file '+
279     'will stay around forever unless deleted outside the browser.');
281 var download_info_max_queue_delay = 100;
283 var download_progress_listener = {
284     QueryInterface: generate_QI(Ci.nsIDownloadProgressListener),
286     onDownloadStateChange: function (state, download) {
287         var info = null;
288         /* FIXME: Determine if only new downloads will have this state
289          * as their previous state. */
291         dumpln("download state change: " + download.source.spec + ": " + state + ", " + download.state + ", " + download.id);
293         if (state == DOWNLOAD_NOTSTARTED) {
294             info = match_registered_download(download);
295             if (info == null) {
296                 info = new download_info(null, download);
297                 dumpln("error: encountered unknown new download");
298             } else {
299                 info.attach(download);
300             }
301         } else {
302             info = id_to_download_info[download.id];
303             if (info == null) {
304                 dumpln("Error: encountered unknown download");
306             } else {
307                 info.mozilla_info = download;
308                 download_state_change_hook.run(info);
309                 if (info.state == DOWNLOAD_FINISHED) {
310                     download_finished_hook.run(info);
312                     if (info.shell_command != null) {
313                         info.running_shell_command = true;
314                         co_call(function () {
315                             try {
316                                 yield shell_command_with_argument(info.shell_command,
317                                                                   info.target_file.path,
318                                                                   $cwd = info.shell_command_cwd);
319                             } catch (e) {
320                                 handle_interactive_error(info.source_buffer.window, e);
321                             } finally  {
322                                 if (info.temporary_status == DOWNLOAD_TEMPORARY_FOR_COMMAND)
323                                     if(delete_temporary_files_for_command) {
324                                         info.target_file.remove(false /* not recursive */);
325                                     }
326                                 info.running_shell_command = false;
327                                 download_shell_command_change_hook.run(info);
328                             }
329                         }());
330                         download_shell_command_change_hook.run(info);
331                     }
332                 }
333             }
334         }
335     },
337     onProgressChange: function (progress, request, cur_self_progress, max_self_progress,
338                                 cur_total_progress, max_total_progress,
339                                 download) {
340         var info = id_to_download_info[download.id];
341         if (info == null) {
342             dumpln("error: encountered unknown download in progress change");
343             return;
344         }
345         info.mozilla_info = download;
346         download_progress_change_hook.run(info);
347         //dumpln("download progress change: " + download.source.spec + ": " + cur_self_progress + "/" + max_self_progress + " "
348         // + cur_total_progress + "/" + max_total_progress + ", " + download.state + ", " + download.id);
349     },
351     onSecurityChange: function (progress, request, state, download) {
352     },
354     onStateChange: function (progress, request, state_flags, status, download) {
355     }
358 var download_observer = {
359     observe: function (subject, topic, data) {
360         switch(topic) {
361         case "download-manager-remove-download":
362             var ids = [];
363             if (!subject) {
364                 // Remove all downloads
365                 for (let i in id_to_download_info)
366                     ids.push(i);
367             } else {
368                 let id = subject.QueryInterface(Ci.nsISupportsPRUint32);
369                 /* FIXME: determine if this should really be an error */
370                 if (!(id in id_to_download_info)) {
371                     dumpln("Error: download-manager-remove-download event received for unknown download: " + id);
372                 } else
373                     ids.push(id);
374             }
375             for each (let i in ids) {
376                 dumpln("deleting download: " + i);
377                 let d = id_to_download_info[i];
378                 d.removed = true;
379                 download_removed_hook.run(d);
380                 delete id_to_download_info[i];
381             }
382             break;
383         }
384     }
386 observer_service.addObserver(download_observer, "download-manager-remove-download", false);
388 download_manager_service.addListener(download_progress_listener);
390 define_variable("download_buffer_min_update_interval", 2000,
391     "Minimum interval (in milliseconds) between updates in download progress buffers.\n" +
392     "Lowering this interval will increase the promptness of the progress display at " +
393     "the cost of using additional processor time.");
395 function download_buffer_modality (buffer, element) {
396     buffer.keymaps.push(download_buffer_keymap);
399 define_keywords("$info");
400 function download_buffer (window) {
401     this.constructor_begin();
402     keywords(arguments);
403     special_buffer.call(this, window, forward_keywords(arguments));
404     this.info = arguments.$info;
405     this.local.cwd = this.info.mozilla_info.targetFile.parent;
406     this.description = this.info.mozilla_info.source.spec;
407     this.update_title();
409     this.progress_change_handler_fn = method_caller(this, this.handle_progress_change);
410     add_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
411     add_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
412     this.command_change_handler_fn = method_caller(this, this.update_command_field);
413     add_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
414     this.modalities.push(download_buffer_modality);
415     this.constructor_end();
417 download_buffer.prototype = {
418     constructor: download_buffer,
419     __proto__: special_buffer.prototype,
421     destroy: function () {
422         remove_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
423         remove_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
424         remove_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
426         // Remove all node references
427         delete this.status_textnode;
428         delete this.target_file_node;
429         delete this.transferred_div_node;
430         delete this.transferred_textnode;
431         delete this.progress_container_node;
432         delete this.progress_bar_node;
433         delete this.percent_textnode;
434         delete this.time_textnode;
435         delete this.command_div_node;
436         delete this.command_label_textnode;
437         delete this.command_textnode;
439         special_buffer.prototype.destroy.call(this);
440     },
442     update_title: function () {
443         // FIXME: do this properly
444         var new_title;
445         var info = this.info;
446         var append_transfer_info = false;
447         var append_speed_info = true;
448         var label = null;
449         switch(info.state) {
450         case DOWNLOAD_DOWNLOADING:
451             label = "Downloading";
452             append_transfer_info = true;
453             break;
454         case DOWNLOAD_FINISHED:
455             label = "Download complete";
456             break;
457         case DOWNLOAD_FAILED:
458             label = "Download failed";
459             append_transfer_info = true;
460             append_speed_info = false;
461             break;
462         case DOWNLOAD_CANCELED:
463             label = "Download canceled";
464             append_transfer_info = true;
465             append_speed_info = false;
466             break;
467         case DOWNLOAD_PAUSED:
468             label = "Download paused";
469             append_transfer_info = true;
470             append_speed_info = false;
471             break;
472         case DOWNLOAD_QUEUED:
473         default:
474             label = "Download queued";
475             break;
476         }
478         if (append_transfer_info) {
479             if (append_speed_info)
480                 new_title = label + " at " + pretty_print_file_size(info.speed).join(" ") + "/s: ";
481             else
482                 new_title = label + ": ";
483             var trans = pretty_print_file_size(info.amount_transferred);
484             if (info.size >= 0) {
485                 var total = pretty_print_file_size(info.size);
486                 if (trans[1] == total[1])
487                     new_title += trans[0] + "/" + total[0] + " " + total[1];
488                 else
489                     new_title += trans.join(" ") + "/" + total.join(" ");
490             } else
491                 new_title += trans.join(" ");
492             if (info.percent_complete >= 0)
493                 new_title += " (" + info.percent_complete + "%)";
494         } else
495             new_title = label;
496         if (new_title != this.title) {
497             this.title = new_title;
498             return true;
499         }
500         return false;
501     },
503     handle_progress_change: function () {
504         var cur_time = Date.now();
505         if (this.last_update == null ||
506             (cur_time - this.last_update) > download_buffer_min_update_interval ||
507             this.info.state != this.previous_state) {
509             if (this.update_title())
510                 buffer_title_change_hook.run(this);
512             if (this.generated) {
513                 this.update_fields();
514             }
515             this.previous_status = this.info.status;
516             this.last_update = cur_time;
517         }
518     },
520     generate: function () {
521         var d = this.document;
522         var g = new dom_generator(d, XHTML_NS);
524         /* Warning: If any additional node references are saved in
525          * this function, appropriate code to delete the saved
526          * properties must be added to destroy method. */
528         var info = this.info;
530         d.body.setAttribute("class", "download-buffer");
532         g.add_stylesheet("chrome://conkeror-gui/content/downloads.css");
534         var row, cell;
535         var table = g.element("table", d.body);
537         row = g.element("tr", table, "class", "download-info", "id", "download-source");
538         cell = g.element("td", row, "class", "download-label");
539         this.status_textnode = g.text("", cell);
540         cell = g.element("td", row, "class", "download-value");
541         g.text(info.source.spec, cell);
543         row = g.element("tr", table, "class", "download-info", "id", "download-target");
544         cell = g.element("td", row, "class", "download-label");
545         var target_label;
546         if (info.temporary_status != DOWNLOAD_NOT_TEMPORARY)
547             target_label = "Temp. file:";
548         else
549             target_label = "Target:";
550         g.text(target_label, cell);
551         cell = g.element("td", row, "class", "download-value");
552         this.target_file_node = g.text("", cell);
554         row = g.element("tr", table, "class", "download-info", "id", "download-mime-type");
555         cell = g.element("td", row, "class", "download-label");
556         g.text("MIME type:", cell);
557         cell = g.element("td", row, "class", "download-value");
558         g.text(info.MIME_type || "unknown", cell);
560         this.transferred_div_node = row =
561             g.element("tr", table, "class", "download-info", "id", "download-transferred");
562         cell = g.element("td", row, "class", "download-label");
563         g.text("Transferred:", cell);
564         cell = g.element("td", row, "class", "download-value");
565         var sub_item = g.element("div", cell);
566         this.transferred_textnode = g.text("", sub_item);
567         sub_item = g.element("div", cell, "id", "download-percent");
568         this.percent_textnode = g.text("", sub_item);
569         this.progress_container_node = sub_item = g.element("div", cell, "id", "download-progress-container");
570         this.progress_bar_node = g.element("div", sub_item, "id", "download-progress-bar");
572         row = g.element("tr", table, "class", "download-info", "id", "download-time");
573         cell = g.element("td", row, "class", "download-label");
574         g.text("Time:", cell);
575         cell = g.element("td", row, "class", "download-value");
576         this.time_textnode = g.text("", cell);
578         if (info.action_description != null) {
579             row = g.element("tr", table, "class", "download-info", "id", "download-action");
580             cell = g.element("div", row, "class", "download-label");
581             g.text("Action:", cell);
582             cell = g.element("div", row, "class", "download-value");
583             g.text(info.action_description, cell);
584         }
586         this.command_div_node = row = g.element("tr", table, "class", "download-info", "id", "download-command");
587         cell = g.element("td", row, "class", "download-label");
588         this.command_label_textnode = g.text("Run command:", cell);
589         cell = g.element("td", row, "class", "download-value");
590         this.command_textnode = g.text("", cell);
592         this.update_fields();
593         this.update_command_field();
594     },
596     update_fields: function () {
597         if (!this.generated)
598             return;
599         var info = this.info;
600         var label = null;
601         switch (info.state) {
602         case DOWNLOAD_DOWNLOADING:
603             label = "Downloading";
604             break;
605         case DOWNLOAD_FINISHED:
606             label = "Completed";
607             break;
608         case DOWNLOAD_FAILED:
609             label = "Failed";
610             break;
611         case DOWNLOAD_CANCELED:
612             label = "Canceled";
613             break;
614         case DOWNLOAD_PAUSED:
615             label = "Paused";
616             break;
617         case DOWNLOAD_QUEUED:
618         default:
619             label = "Queued";
620             break;
621         }
622         this.status_textnode.nodeValue = label + ":";
623         this.target_file_node.nodeValue = info.target_file_text();
624         this.update_time_field();
626         var tran_text = "";
627         if (info.state == DOWNLOAD_FINISHED)
628             tran_text = pretty_print_file_size(info.size).join(" ");
629         else {
630             var trans = pretty_print_file_size(info.amount_transferred);
631             if (info.size >= 0) {
632                 var total = pretty_print_file_size(info.size);
633                 if (trans[1] == total[1])
634                     tran_text += trans[0] + "/" + total[0] + " " + total[1];
635                 else
636                     tran_text += trans.join(" ") + "/" + total.join(" ");
637             } else
638                 tran_text += trans.join(" ");
639         }
640         this.transferred_textnode.nodeValue = tran_text;
641         if (info.percent_complete >= 0) {
642             this.progress_container_node.style.display = "";
643             this.percent_textnode.nodeValue = info.percent_complete + "%";
644             this.progress_bar_node.style.width = info.percent_complete + "%";
645         } else {
646             this.percent_textnode.nodeValue = "";
647             this.progress_container_node.style.display = "none";
648         }
650         this.update_command_field();
651     },
653     update_time_field: function () {
654         var info = this.info;
655         var elapsed_text = pretty_print_time((Date.now() - info.start_time / 1000) / 1000) + " elapsed";
656         var text = "";
657         if (info.state == DOWNLOAD_DOWNLOADING)
658             text = pretty_print_file_size(info.speed).join(" ") + "/s, ";
659         if (info.state == DOWNLOAD_DOWNLOADING &&
660             info.size >= 0 &&
661             info.speed > 0)
662         {
663             let remaining = (info.size - info.amount_transferred) / info.speed;
664             text += pretty_print_time(remaining) + " left (" + elapsed_text + ")";
665         } else
666             text = elapsed_text;
667         this.time_textnode.nodeValue = text;
668     },
670     update_command_field: function () {
671         if (!this.generated)
672             return;
673         if (this.info.shell_command != null) {
674             this.command_div_node.style.display = "";
675             var label;
676             if (this.info.running_shell_command)
677                 label = "Running:";
678             else if (this.info.state == DOWNLOAD_FINISHED)
679                 label = "Ran command:";
680             else
681                 label = "Run command:";
682             this.command_label_textnode.nodeValue = label;
683             this.command_textnode.nodeValue = this.info.shell_command;
684         } else
685             this.command_div_node.style.display = "none";
686     }
689 function download_cancel (buffer) {
690     check_buffer(buffer, download_buffer);
691     var info = buffer.info;
692     info.cancel();
693     buffer.window.minibuffer.message("Download canceled");
695 interactive("download-cancel",
696     "Cancel the current download.\n" +
697     "The download can later be retried using the `download-retry' "+
698     "command, but any data already transferred will be lost.",
699     function (I) {
700         let result = yield I.window.minibuffer.read_single_character_option(
701             $prompt = "Cancel this download? (y/n)",
702             $options = ["y", "n"]);
703         if (result == "y")
704             download_cancel(I.buffer);
705     });
707 function download_retry (buffer) {
708     check_buffer(buffer, download_buffer);
709     var info = buffer.info;
710     info.retry();
711     buffer.window.minibuffer.message("Download retried");
713 interactive("download-retry",
714     "Retry a failed or canceled download.\n" +
715     "This command can be used to retry a download that failed or "+
716     "was canceled using the `download-cancel' command.  The download "+
717     "will begin from the start again.",
718     function (I) { download_retry(I.buffer); });
720 function download_pause (buffer) {
721     check_buffer(buffer, download_buffer);
722     buffer.info.pause();
723     buffer.window.minibuffer.message("Download paused");
725 interactive("download-pause",
726     "Pause the current download.\n" +
727     "The download can later be resumed using the `download-resume' command. "+
728     "The data already transferred will not be lost.",
729     function (I) { download_pause(I.buffer); });
731 function download_resume (buffer) {
732     check_buffer(buffer, download_buffer);
733     buffer.info.resume();
734     buffer.window.minibuffer.message("Download resumed");
736 interactive("download-resume",
737     "Resume the current download.\n" +
738     "This command can be used to resume a download paused using the "+
739     "`download-pause' command.",
740     function (I) { download_resume(I.buffer); });
742 function download_remove (buffer) {
743     check_buffer(buffer, download_buffer);
744     buffer.info.remove();
745     buffer.window.minibuffer.message("Download removed");
747 interactive("download-remove",
748     "Remove the current download from the download manager.\n" +
749     "This command can only be used on inactive (paused, canceled, "+
750     "completed, or failed) downloads.",
751     function (I) { download_remove(I.buffer); });
753 function download_retry_or_resume (buffer) {
754     check_buffer(buffer, download_buffer);
755     var info = buffer.info;
756     if (info.state == DOWNLOAD_PAUSED)
757         download_resume(buffer);
758     else
759         download_retry(buffer);
761 interactive("download-retry-or-resume",
762     "Retry or resume the current download.\n" +
763     "This command can be used to resume a download paused using the " +
764     "`download-pause' command or canceled using the `download-cancel' "+
765     "command.",
766     function (I) { download_retry_or_resume(I.buffer); });
768 function download_pause_or_resume (buffer) {
769     check_buffer(buffer, download_buffer);
770     var info = buffer.info;
771     if (info.state == DOWNLOAD_PAUSED)
772         download_resume(buffer);
773     else
774         download_pause(buffer);
776 interactive("download-pause-or-resume",
777     "Pause or resume the current download.\n" +
778     "This command toggles the paused state of the current download.",
779     function (I) { download_pause_or_resume(I.buffer); });
781 function download_delete_target (buffer) {
782     check_buffer(buffer, download_buffer);
783     var info = buffer.info;
784     info.delete_target();
785     buffer.window.minibuffer.message("Deleted file: " + info.target_file.path);
787 interactive("download-delete-target",
788     "Delete the target file of the current download.\n" +
789     "This command can only be used if the download has finished successfully.",
790     function (I) { download_delete_target(I.buffer); });
792 function download_shell_command (buffer, cwd, cmd) {
793     check_buffer(buffer, download_buffer);
794     var info = buffer.info;
795     if (info.state == DOWNLOAD_FINISHED) {
796         shell_command_with_argument_blind(cmd, info.target_file.path, $cwd = cwd);
797         return;
798     }
799     if (info.state != DOWNLOAD_DOWNLOADING && info.state != DOWNLOAD_PAUSED && info.state != DOWNLOAD_QUEUED)
800         info.throw_state_error();
801     if (cmd == null || cmd.length == 0)
802         info.set_shell_command(null, cwd);
803     else
804         info.set_shell_command(cmd, cwd);
805     buffer.window.minibuffer.message("Queued shell command: " + cmd);
807 interactive("download-shell-command",
808     "Run a shell command on the target file of the current download.\n"+
809     "If the download is still in progress, the shell command will be queued "+
810     "to run when the download finishes.",
811     function (I) {
812         var buffer = check_buffer(I.buffer, download_buffer);
813         var cwd = buffer.info.shell_command_cwd || I.local.cwd;
814         var cmd = yield I.minibuffer.read_shell_command(
815             $cwd = cwd,
816             $initial_value = buffer.info.shell_command ||
817                 external_content_handlers.get(buffer.info.MIME_type));
818         download_shell_command(buffer, cwd, cmd);
819     });
821 function download_manager_ui () {}
822 download_manager_ui.prototype = {
823     constructor: download_manager_ui,
824     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
826     getAttention: function () {},
827     show: function () {},
828     visible: false
832 interactive("download-manager-show-builtin-ui",
833     "Show the built-in (Firefox-style) download manager window.",
834     function (I) {
835         Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
836             .getService(Ci.nsIDownloadManagerUI)
837             .show(I.window);
838     });
842  * Download-show
843  */ 
845 define_variable("download_temporary_file_open_buffer_delay", 500,
846     "Delay (in milliseconds) before a download buffer is opened for "+
847     "temporary downloads.  If the download completes before this amount "+
848     "of time, no download buffer will be opened.  This variable takes "+
849     "effect only if `open_download_buffer_automatically' is in "+
850     "`download_added_hook', which is the case by default.");
852 define_variable("download_buffer_automatic_open_target", OPEN_NEW_WINDOW,
853     "Target(s) for download buffers created by "+
854     "`open_download_buffer_automatically'.");
856 minibuffer_auto_complete_preferences.download = true;
857 minibuffer.prototype.read_download = function () {
858     keywords(arguments,
859              $prompt = "Download",
860              $completer = all_word_completer(
861                  $completions = function (visitor) {
862                      var dls = download_manager_service.activeDownloads;
863                      while (dls.hasMoreElements()) {
864                          let dl = dls.getNext();
865                          visitor(id_to_download_info[dl.id]);
866                      }
867                  },
868                  $get_string = function (x) x.display_name,
869                  $get_description = function (x) x.source.spec,
870                  $get_value = function (x) x),
871              $auto_complete = "download",
872              $auto_complete_initial = true,
873              $match_required = true);
874     var result = yield this.read(forward_keywords(arguments));
875     yield co_return(result);
878 function download_show (window, target, info) {
879     if (! window)
880         target = OPEN_NEW_WINDOW;
881     create_buffer(window, buffer_creator(download_buffer, $info = info), target);
884 function download_show_new_window (I) {
885     var info = yield I.minibuffer.read_download($prompt = "Show download:");
886     download_show(I.window, OPEN_NEW_WINDOW, info);
889 function download_show_new_buffer (I) {
890     var info = yield I.minibuffer.read_download($prompt = "Show download:");
891     download_show(I.window, OPEN_NEW_BUFFER, info);
894 function download_show_new_buffer_background (I) {
895     var info = yield I.minibuffer.read_download($prompt = "Show download:");
896     download_show(I.window, OPEN_NEW_BUFFER_BACKGROUND, info);
899 function open_download_buffer_automatically (info) {
900     var buf = info.source_buffer;
901     var target = download_buffer_automatic_open_target;
902     if (info.temporary_status == DOWNLOAD_NOT_TEMPORARY ||
903         download_temporary_file_open_buffer_delay == 0)
904     {
905         download_show(buf.window, target, info);
906     } else {
907         var timer = null;
908         function finish () {
909             timer.cancel();
910         }
911         add_hook.call(info, "download_finished_hook", finish);
912         timer = call_after_timeout(function () {
913                 remove_hook.call(info, "download_finished_hook", finish);
914                 download_show(buf.window, target, info);
915             }, download_temporary_file_open_buffer_delay);
916     }
918 add_hook("download_added_hook", open_download_buffer_automatically);
920 interactive("download-show",
921     "Prompt for an ongoing download and open a download buffer showing "+
922     "its progress.",
923     alternates(download_show_new_buffer,
924                download_show_new_window));
926 provide("download-manager");