]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/downloads.jsm
Import 1.0b7.1 supporting Firefox up to 8.*
[dactyl.git] / common / modules / downloads.jsm
index 403c1e418a4642ec6867d1a6b47babb0b8557549..faee6e22a7a1ce5a28bcda6019c2e99dc9c05a68 100644 (file)
@@ -7,7 +7,7 @@
 Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("downloads", {
     exports: ["Download", "Downloads", "downloads"],
-    use: ["io", "prefs", "services", "util"]
+    use: ["io", "messages", "prefs", "services", "util"]
 }, this);
 
 Cu.import("resource://gre/modules/DownloadUtils.jsm", this);
@@ -32,30 +32,38 @@ var Download = Class("Download", {
             <tr highlight="Download" key="row" xmlns:dactyl={NS} xmlns={XHTML}>
                 <td highlight="DownloadTitle">
                     <span highlight="Link">
-                        <a key="launch" dactyl:command="download.command"
+                        <a key="launch"
                            href={self.target.spec} path={self.targetFile.path}>{self.displayName}</a>
                         <span highlight="LinkInfo">{self.targetFile.path}</span>
                     </span>
                 </td>
                 <td highlight="DownloadState" key="state"/>
                 <td highlight="DownloadButtons Buttons">
-                    <a highlight="Button" key="pause">Pause</a>
-                    <a highlight="Button" key="remove">Remove</a>
-                    <a highlight="Button" key="resume">Resume</a>
-                    <a highlight="Button" key="retry">Retry</a>
-                    <a highlight="Button" key="cancel">Cancel</a>
-                    <a highlight="Button" key="delete">Delete</a>
+                    <a highlight="Button" href="javascript:0" key="pause">{_("download.action.Pause")}</a>
+                    <a highlight="Button" href="javascript:0" key="remove">{_("download.action.Remove")}</a>
+                    <a highlight="Button" href="javascript:0" key="resume">{_("download.action.Resume")}</a>
+                    <a highlight="Button" href="javascript:0" key="retry">{_("download.action.Retry")}</a>
+                    <a highlight="Button" href="javascript:0" key="cancel">{_("download.action.Cancel")}</a>
+                    <a highlight="Button" href="javascript:0" key="delete">{_("download.action.Delete")}</a>
                 </td>
                 <td highlight="DownloadProgress" key="progress">
                     <span highlight="DownloadProgressHave" key="progressHave"
                     />/<span highlight="DownloadProgressTotal" key="progressTotal"/>
                 </td>
                 <td highlight="DownloadPercent" key="percent"/>
+                <td highlight="DownloadSpeed" key="speed"/>
                 <td highlight="DownloadTime" key="time"/>
                 <td><a highlight="DownloadSource" key="source" href={self.source.spec}>{self.source.spec}</a></td>
             </tr>,
             this.list.document, this.nodes);
 
+        this.nodes.launch.addEventListener("click", function (event) {
+            if (event.button == 0) {
+                event.preventDefault();
+                self.command("launch");
+            }
+        }, false);
+
         self.updateStatus();
         return self;
     },
@@ -78,10 +86,13 @@ var Download = Class("Download", {
     })),
 
     command: function command(name) {
-        util.assert(set.has(this.allowedCommands, name), "Unknown command");
-        util.assert(this.allowedCommands[name], "Command not allowed");
+        util.assert(Set.has(this.allowedCommands, name), _("download.unknownCommand"));
+        util.assert(this.allowedCommands[name], _("download.commandNotAllowed"));
 
-        services.downloadManager[name + "Download"](this.id);
+        if (Set.has(this.commands, name))
+            this.commands[name].call(this);
+        else
+            services.downloadManager[name + "Download"](this.id);
     },
 
     commands: {
@@ -95,7 +106,7 @@ var Download = Class("Download", {
             function action() {
                 try {
                     if (this.MIMEInfo && this.MIMEInfo.preferredAction == this.MIMEInfo.useHelperApp)
-                        this.MIMEInfo.launchWithFile(file)
+                        this.MIMEInfo.launchWithFile(file);
                     else
                         file.launch();
                 }
@@ -106,7 +117,7 @@ var Download = Class("Download", {
 
             let file = io.File(this.targetFile);
             if (file.isExecutable() && prefs.get("browser.download.manager.alertOnEXEOpen", true))
-                this.list.modules.commandline.input("This will launch an executable download. Continue? (yes/[no]/always) ",
+                this.list.modules.commandline.input(_("download.prompt.launchExecutable") + " ",
                     function (resp) {
                         if (/^a(lways)$/i.test(resp)) {
                             prefs.set("browser.download.manager.alertOnEXEOpen", false);
@@ -120,26 +131,48 @@ var Download = Class("Download", {
         }
     },
 
-    compare: function compare(other) String.localeCompare(this.displayName, other.displayName),
+    _compare: {
+        active: function (a, b) a.alive - b.alive,
+        complete: function (a, b) a.percentComplete - b.percentComplete,
+        date: function (a, b) a.startTime - b.startTime,
+        filename: function (a, b) String.localeCompare(a.targetFile.leafName, b.targetFile.leafName),
+        size: function (a, b) a.size - b.size,
+        speed: function (a, b) a.speed - b.speed,
+        time: function (a, b) a.timeRemaining - b.timeRemaining,
+        url: function (a, b) String.localeCompare(a.source.spec, b.source.spec)
+    },
+
+    compare: function compare(other) values(this.list.sortOrder).map(function (order) {
+        let val = this._compare[order.substr(1)](this, other);
+
+        return (order[0] == "-") ? -val : val;
+    }, this).nth(util.identity, 0) || 0,
 
     timeRemaining: Infinity,
 
     updateProgress: function updateProgress() {
         let self = this.__proto__;
 
-        if (this.amountTransferred === this.size)
+        if (this.amountTransferred === this.size) {
+            this.nodes.speed.textContent = "";
             this.nodes.time.textContent = "";
-        else if (this.speed == 0 || this.size == 0)
-            this.nodes.time.textContent = "Unknown";
+        }
         else {
-            let seconds = (this.size - this.amountTransferred) / this.speed;
-            [, self.timeRemaining] = DownloadUtils.getTimeLeft(seconds, this.timeRemaining);
-            if (this.timeRemaining)
-                this.nodes.time.textContent = util.formatSeconds(this.timeRemaining);
-            else
-                this.nodes.time.textContent = "~1 second";
+            this.nodes.speed.textContent = util.formatBytes(this.speed, 1, true) + "/s";
+
+            if (this.speed == 0 || this.size == 0)
+                this.nodes.time.textContent = _("download.unknown");
+            else {
+                let seconds = (this.size - this.amountTransferred) / this.speed;
+                [, self.timeRemaining] = DownloadUtils.getTimeLeft(seconds, this.timeRemaining);
+                if (this.timeRemaining)
+                    this.nodes.time.textContent = util.formatSeconds(this.timeRemaining);
+                else
+                    this.nodes.time.textContent = _("download.almostDone");
+            }
         }
-        let total = this.nodes.progressTotal.textContent = this.size ? util.formatBytes(this.size, 1, true) : "Unknown";
+
+        let total = this.nodes.progressTotal.textContent = this.size ? util.formatBytes(this.size, 1, true) : _("download.unknown");
         let suffix = RegExp(/( [a-z]+)?$/i.exec(total)[0] + "$");
         this.nodes.progressHave.textContent = util.formatBytes(this.amountTransferred, 1, true).replace(suffix, "");
 
@@ -165,7 +198,8 @@ var DownloadList = Class("DownloadList",
                          XPCOM([Ci.nsIDownloadProgressListener,
                                 Ci.nsIObserver,
                                 Ci.nsISupportsWeakReference]), {
-    init: function init(modules, filter) {
+    init: function init(modules, filter, sort) {
+        this.sortOrder = sort;
         this.modules = modules;
         this.filter = filter && filter.toLowerCase();
         this.nodes = {
@@ -173,6 +207,7 @@ var DownloadList = Class("DownloadList",
         };
         this.downloads = {};
     },
+
     cleanup: function cleanup() {
         this.observe.unregister();
         services.downloadManager.removeListener(this);
@@ -182,26 +217,28 @@ var DownloadList = Class("DownloadList",
 
         util.xmlToDom(<table highlight="Downloads" key="list" xmlns={XHTML}>
                         <tr highlight="DownloadHead">
-                            <span>Title</span>
-                            <span>Status</span>
+                            <span>{_("title.Title")}</span>
+                            <span>{_("title.Status")}</span>
                             <span/>
-                            <span>Progress</span>
+                            <span>{_("title.Progress")}</span>
                             <span/>
-                            <span>Time remaining</span>
-                            <span>Source</span>
+                            <span>{_("title.Speed")}</span>
+                            <span>{_("title.Time remaining")}</span>
+                            <span>{_("title.Source")}</span>
                         </tr>
                         <tr highlight="Download"><span><div style="min-height: 1ex; /* FIXME */"/></span></tr>
                         <tr highlight="Download" key="totals" active="true">
-                            <td><span highlight="Title">Totals:</span>&#xa0;<span key="total"/></td>
+                            <td><span highlight="Title">{_("title.Totals")}:</span>&#xa0;<span key="total"/></td>
                             <td/>
                             <td highlight="DownloadButtons">
-                                <a highlight="Button" key="clear">Clear</a>
+                                <a highlight="Button" href="javascript:0" key="clear">{_("download.action.Clear")}</a>
                             </td>
                             <td highlight="DownloadProgress" key="progress">
                                 <span highlight="DownloadProgressHave" key="progressHave"
                                 />/<span highlight="DownloadProgressTotal" key="progressTotal"/>
                             </td>
                             <td highlight="DownloadPercent" key="percent"/>
+                            <td highlight="DownloadSpeed" key="speed"/>
                             <td highlight="DownloadTime" key="time"/>
                             <td/>
                         </tr>
@@ -253,6 +290,17 @@ var DownloadList = Class("DownloadList",
         }
     },
 
+    sort: function sort() {
+        let list = values(this.downloads).sort(function (a, b) a.compare(b));
+
+        for (let [i, download] in iter(list))
+            if (this.nodes.list.childNodes[i + 1] != download.nodes.row)
+                this.nodes.list.insertBefore(download.nodes.row,
+                                             this.nodes.list.childNodes[i + 1]);
+    },
+
+    shouldSort: function shouldSort() Array.some(arguments, function (val) this.sortOrder.some(function (v) v.substr(1) == val), this),
+
     update: function update() {
         for (let node in values(this.nodes))
             if (node.update && node.update != update)
@@ -277,9 +325,12 @@ var DownloadList = Class("DownloadList",
 
         let active = downloads.filter(function (dl) dl.alive).length;
         if (active)
-            this.nodes.total.textContent = active + " active";
-        else for (let key in values(["total", "percent", "time"]))
+            this.nodes.total.textContent = _("download.nActive", active);
+        else for (let key in values(["total", "percent", "speed", "time"]))
             this.nodes[key].textContent = "";
+
+        if (this.shouldSort("complete", "size", "speed", "time"))
+            this.sort();
     },
 
     observers: {
@@ -305,11 +356,15 @@ var DownloadList = Class("DownloadList",
                 this.nodes.list.scrollIntoView(false);
             }
             this.update();
+
+            if (this.shouldSort("active"))
+                this.sort();
         }
         catch (e) {
             util.reportError(e);
         }
     },
+
     onProgressChange: function (webProgress, request,
                                 curProgress, maxProgress,
                                 curTotalProgress, maxTotalProgress,
@@ -328,17 +383,85 @@ var DownloadList = Class("DownloadList",
 var Downloads = Module("downloads", {
 }, {
 }, {
-    commands: function (dactyl, modules, window) {
-        const { commands } = modules;
+    commands: function initCommands(dactyl, modules, window) {
+        const { commands, CommandOption } = modules;
 
         commands.add(["downl[oads]", "dl"],
             "Display the downloads list",
             function (args) {
-                let downloads = DownloadList(modules, args[0]);
+                let downloads = DownloadList(modules, args[0], args["-sort"]);
                 modules.commandline.echo(downloads);
             },
             {
-                argCount: "?"
+                argCount: "?",
+                options: [
+                    {
+                        names: ["-sort", "-s"],
+                        description: "Sort order (see 'downloadsort')",
+                        type: CommandOption.LIST,
+                        get default() modules.options["downloadsort"],
+                        completer: function (context, args) modules.options.get("downloadsort").completer(context, { values: args["-sort"] }),
+                        validator: function (value) modules.options.get("downloadsort").validator(value)
+                    }
+                ]
+            });
+
+        commands.add(["dlc[lear]"],
+            "Clear completed downloads",
+            function (args) { services.downloadManager.cleanUp(); });
+    },
+    options: function initOptions(dactyl, modules, window) {
+        const { options } = modules;
+
+        if (false)
+        options.add(["downloadcolumns", "dlc"],
+            "The columns to show in the download manager",
+            "stringlist", "filename,state,buttons,progress,percent,time,url",
+            {
+                values: {
+                    buttons:    "Control buttons",
+                    filename:   "Target filename",
+                    percent:    "Percent complete",
+                    size:       "File size",
+                    speed:      "Download speed",
+                    state:      "The download's state",
+                    time:       "Time remaining",
+                    url:        "Source URL"
+                }
+            });
+
+        options.add(["downloadsort", "dlsort", "dls"],
+            ":downloads sort order",
+            "stringlist", "-active,+filename",
+            {
+                values: {
+                    active:     "Whether download is active",
+                    complete:   "Percent complete",
+                    date:       "Date and time the download began",
+                    filename:   "Target filename",
+                    size:       "File size",
+                    speed:      "Download speed",
+                    time:       "Time remaining",
+                    url:        "Source URL"
+                },
+
+                completer: function (context, extra) {
+                    let seen = Set.has(Set(extra.values.map(function (val) val.substr(1))));
+
+                    context.completions = iter(this.values).filter(function ([k, v]) !seen(k))
+                                                           .map(function ([k, v]) [["+" + k, [v, " (", _("sort.ascending"), ")"].join("")],
+                                                                                   ["-" + k, [v, " (", _("sort.descending"), ")"].join("")]])
+                                                           .flatten().array;
+                },
+
+                has: function () Array.some(arguments, function (val) this.value.some(function (v) v.substr(1) == val)),
+
+                validator: function (value) {
+                    let seen = {};
+                    return value.every(function (val) /^[+-]/.test(val) && Set.has(this.values, val.substr(1))
+                                                                        && !Set.add(seen, val.substr(1)),
+                                       this) && value.length;
+                }
             });
     }
 });