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);
<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;
},
})),
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: {
function action() {
try {
if (this.MIMEInfo && this.MIMEInfo.preferredAction == this.MIMEInfo.useHelperApp)
- this.MIMEInfo.launchWithFile(file)
+ this.MIMEInfo.launchWithFile(file);
else
file.launch();
}
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);
}
},
- 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, "");
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 = {
};
this.downloads = {};
},
+
cleanup: function cleanup() {
this.observe.unregister();
services.downloadManager.removeListener(this);
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> <span key="total"/></td>
+ <td><span highlight="Title">{_("title.Totals")}:</span> <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>
}
},
+ 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)
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: {
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,
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;
+ }
});
}
});