X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fmodules%2Fdownloads.jsm;h=3f4a3501384d6c10baf8b0ab0ccd8cc0892070db;hb=refs%2Fheads%2Fupstream;hp=cbcd9a6e4fcaf4ff3ffccc4ae1bb3e35213c4850;hpb=354a049cce8415487552ce405cce167b7071fe1f;p=dactyl.git diff --git a/common/modules/downloads.jsm b/common/modules/downloads.jsm index cbcd9a6..3f4a350 100644 --- a/common/modules/downloads.jsm +++ b/common/modules/downloads.jsm @@ -1,4 +1,4 @@ -// Copyright (c) 2011-2013 Kris Maglione +// Copyright (c) 2011-2014 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -10,20 +10,22 @@ defineModule("downloads", { }); lazyRequire("overlay", ["overlay"]); +lazyRequire("promises", ["Task", "promises"]); -Cu.import("resource://gre/modules/DownloadUtils.jsm", this); +lazyRequire("resource://gre/modules/Downloads.jsm", ["Downloads"]); +lazyRequire("resource://gre/modules/DownloadUtils.jsm", ["DownloadUtils"]); var MAX_LOAD_TIME = 10 * 1000; let prefix = "DOWNLOAD_"; var states = iter([v, k.slice(prefix.length).toLowerCase()] for ([k, v] in Iterator(Ci.nsIDownloadManager)) - if (k.indexOf(prefix) == 0)) + if (k.startsWith(prefix))) .toObject(); var Download = Class("Download", { - init: function init(id, list) { - this.download = services.downloadManager.getDownload(id); + init: function init(download, list) { + this.download = download; this.list = list; this.nodes = { @@ -39,11 +41,9 @@ var Download = Class("Download", { this.targetFile.path]]], ["td", { highlight: "DownloadState", key: "state" }], ["td", { highlight: "DownloadButtons Buttons" }, - ["a", { highlight: "Button", href: "javascript:0", key: "pause" }, _("download.action.Pause")], + ["a", { highlight: "Button", href: "javascript:0", key: "stop" }, _("download.action.Stop")], ["a", { highlight: "Button", href: "javascript:0", key: "remove" }, _("download.action.Remove")], ["a", { highlight: "Button", href: "javascript:0", key: "resume" }, _("download.action.Resume")], - ["a", { highlight: "Button", href: "javascript:0", key: "retry" }, _("download.action.Retry")], - ["a", { highlight: "Button", href: "javascript:0", key: "cancel" }, _("download.action.Cancel")], ["a", { highlight: "Button", href: "javascript:0", key: "delete" }, _("download.action.Delete")]], ["td", { highlight: "DownloadProgress", key: "progress" }, ["span", { highlight: "DownloadProgressHave", key: "progressHave" }], @@ -53,8 +53,8 @@ var Download = Class("Download", { ["td", { highlight: "DownloadSpeed", key: "speed" }], ["td", { highlight: "DownloadTime", key: "time" }], ["td", {}, - ["a", { highlight: "DownloadSource", key: "source", href: this.source.spec }, - this.source.spec]]], + ["a", { highlight: "DownloadSource", key: "source", href: this.source.url }, + this.source.url]]], this.list.document, this.nodes); this.nodes.launch.addEventListener("click", (event) => { @@ -68,38 +68,40 @@ var Download = Class("Download", { return this; }, + get active() !this.stopped, + + get targetFile() File(this.download.target.path), + + get displayName() this.targetFile.leafName, + get status() states[this.state], inState: function inState(states) states.indexOf(this.status) >= 0, - get alive() this.inState(["downloading", "notstarted", "paused", "queued", "scanning"]), - allowedCommands: Class.Memoize(function () let (self = this) ({ - get cancel() self.cancelable && self.inState(["downloading", "paused", "starting"]), - get delete() !this.cancel && self.targetFile.exists(), - get launch() self.targetFile.exists() && self.inState(["finished"]), - get pause() self.inState(["downloading"]), - get remove() self.inState(["blocked_parental", "blocked_policy", - "canceled", "dirty", "failed", "finished"]), - get resume() self.resumable && self.inState(["paused"]), - get retry() self.inState(["canceled", "failed"]) + get delete() !self.active && (self.targetFile.exists() || self.hasPartialData), + get launch() self.targetFile.exists() && self.succeeded, + get stop() self.active, + get remove() !self.active, + get resume() self.canceled })), command: function command(name) { - util.assert(Set.has(this.allowedCommands, name), _("download.unknownCommand")); + util.assert(hasOwnProperty(this.allowedCommands, name), _("download.unknownCommand")); util.assert(this.allowedCommands[name], _("download.commandNotAllowed")); - if (Set.has(this.commands, name)) + if (hasOwnProperty(this.commands, name)) this.commands[name].call(this); - else - services.downloadManager[name + "Download"](this.id); }, commands: { - delete: function delete_() { - this.targetFile.remove(false); + delete: promises.task(function delete_() { + if (this.hasPartialData) + yield this.removePartialData(); + else if (this.targetFile.exists()) + this.targetFile.remove(false); this.updateStatus(); - }, + }), launch: function launch() { // Behavior mimics that of the builtin Download Manager. function action() { @@ -127,42 +129,52 @@ var Download = Class("Download", { }); else action.call(this); - } + }, + resume: function resume() { + this.download.start(); + }, + remove: promises.task(function remove() { + yield this.list.list.remove(this.download); + yield this.download.finalize(true); + }), + stop: function stop() { + this.download.cancel(); + }, }, _compare: { - active: (a, b) => a.alive - b.alive, + active: (a, b) => a.active - b.active, complete: (a, b) => a.percentComplete - b.percentComplete, - date: (a, b) => a.startTime - b.startTime, + date: (a, b) => a.startTime - b.startTime, filename: (a, b) => String.localeCompare(a.targetFile.leafName, b.targetFile.leafName), - size: (a, b) => a.size - b.size, - speed: (a, b) => a.speed - b.speed, - time: (a, b) => a.timeRemaining - b.timeRemaining, - url: (a, b) => String.localeCompare(a.source.spec, b.source.spec) + size: (a, b) => a.totalBytes - b.totalBytes, + speed: (a, b) => a.speed - b.speed, + time: (a, b) => a.timeRemaining - b.timeRemaining, + url: (a, b) => String.localeCompare(a.source.url, b.source.url) }, 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, + }, this).find(util.identity) || 0, timeRemaining: Infinity, updateProgress: function updateProgress() { let self = this.__proto__; - if (this.amountTransferred === this.size) { + if (!this.active) { this.nodes.speed.textContent = ""; this.nodes.time.textContent = ""; } else { this.nodes.speed.textContent = util.formatBytes(this.speed, 1, true) + "/s"; - if (this.speed == 0 || this.size == 0) + if (this.speed == 0 || !this.hasProgress) this.nodes.time.textContent = _("download.unknown"); else { - let seconds = (this.size - this.amountTransferred) / this.speed; + let seconds = (this.totalBytes - this.currentBytes) / this.speed; [, self.timeRemaining] = DownloadUtils.getTimeLeft(seconds, this.timeRemaining); if (this.timeRemaining) this.nodes.time.textContent = util.formatSeconds(this.timeRemaining); @@ -171,17 +183,20 @@ var Download = Class("Download", { } } - let total = this.nodes.progressTotal.textContent = this.size || !this.nActive ? util.formatBytes(this.size, 1, true) - : _("download.unknown"); + let total = this.nodes.progressTotal.textContent = + this.hasProgress && (this.totalBytes || !this.nActive) + ? util.formatBytes(this.totalBytes, 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, ""); + this.nodes.progressHave.textContent = util.formatBytes(this.currentBytes, 1, true).replace(suffix, ""); - this.nodes.percent.textContent = this.size ? Math.round(this.amountTransferred * 100 / this.size) + "%" : ""; + this.nodes.percent.textContent = this.hasProgress ? this.progress + "%" : ""; }, updateStatus: function updateStatus() { - this.nodes.row[this.alive ? "setAttribute" : "removeAttribute"]("active", "true"); + this.nodes.row[this.active ? "setAttribute" : "removeAttribute"]("active", "true"); this.nodes.row.setAttribute("status", this.status); this.nodes.state.textContent = util.capitalize(this.status); @@ -193,14 +208,6 @@ var Download = Class("Download", { this.updateProgress(); } }); -Object.keys(XPCOMShim([Ci.nsIDownload])).forEach(function (key) { - if (!(key in Download.prototype)) - Object.defineProperty(Download.prototype, key, { - get: function get() this.download[key], - set: function set(val) this.download[key] = val, - configurable: true - }); -}); var DownloadList = Class("DownloadList", XPCOM([Ci.nsIDownloadProgressListener, @@ -213,12 +220,13 @@ var DownloadList = Class("DownloadList", this.nodes = { commandTarget: this }; - this.downloads = {}; + this.downloads = Map(); }, cleanup: function cleanup() { - this.observe.unregister(); - services.downloadManager.removeListener(this); + if (this.list) + this.list.removeView(this); + this.dead = true; }, message: Class.Memoize(function () { @@ -258,40 +266,44 @@ var DownloadList = Class("DownloadList", this.index = Array.indexOf(this.nodes.list.childNodes, this.nodes.head); - let start = Date.now(); - for (let row in iter(services.downloadManager.DBConnection - .createStatement("SELECT id FROM moz_downloads"))) { - if (Date.now() - start > MAX_LOAD_TIME) { - util.dactyl.warn(_("download.givingUpAfter", (Date.now() - start) / 1000)); - break; + Task.spawn(function () { + this.list = yield Downloads.getList(Downloads.ALL); + + let start = Date.now(); + for (let download of yield this.list.getAll()) { + if (Date.now() - start > MAX_LOAD_TIME) { + util.dactyl.warn(_("download.givingUpAfter", (Date.now() - start) / 1000)); + break; + } + this.addDownload(download); } - this.addDownload(row.id); - } - this.update(); + this.update(); - util.addObserver(this); - services.downloadManager.addListener(this); + if (!this.dead) + this.list.addView(this); + }.bind(this)); return this.nodes.list; }), - addDownload: function addDownload(id) { - if (!(id in this.downloads)) { - let download = Download(id, this); - if (this.filter && download.displayName.indexOf(this.filter) === -1) + addDownload: function addDownload(download) { + if (!this.downloads.has(download)) { + download = Download(download, this); + if (this.filter && !download.displayName.contains(this.filter)) return; - this.downloads[id] = download; - let index = values(this.downloads).sort((a, b) => a.compare(b)) - .indexOf(download); + this.downloads.set(download.download, download); + let index = values(this.downloads).toArray() + .sort((a, b) => a.compare(b)) + .indexOf(download); this.nodes.list.insertBefore(download.nodes.row, this.nodes.list.childNodes[index + this.index + 1]); } }, - removeDownload: function removeDownload(id) { - if (id in this.downloads) { - this.nodes.list.removeChild(this.downloads[id].nodes.row); - delete this.downloads[id]; + removeDownload: function removeDownload(download) { + if (this.downloads.has(download)) { + this.nodes.list.removeChild(this.downloads.get(download).nodes.row); + delete this.downloads.delete(download); } }, @@ -301,17 +313,17 @@ var DownloadList = Class("DownloadList", }, allowedCommands: Class.Memoize(function () let (self = this) ({ - get clear() values(self.downloads).some(dl => dl.allowedCommands.remove) + get clear() iter(self.downloads.values()).some(dl => dl.allowedCommands.remove) })), commands: { clear: function () { - services.downloadManager.cleanUp(); + this.list.removeFinished(); } }, sort: function sort() { - let list = values(this.downloads).sort((a, b) => a.compare(b)); + let list = iter(this.downloads.values()).sort((a, b) => a.compare(b)); for (let [i, download] in iter(list)) if (this.nodes.list.childNodes[i + 1] != download.nodes.row) @@ -335,16 +347,19 @@ var DownloadList = Class("DownloadList", timeRemaining: Infinity, updateProgress: function updateProgress() { - let downloads = values(this.downloads).toArray(); - let active = downloads.filter(d => d.alive); + let downloads = iter(this.downloads.values()).toArray(); + let active = downloads.filter(d => d.active); let self = Object.create(this); - for (let prop in values(["amountTransferred", "size", "speed", "timeRemaining"])) + for (let prop in values(["currentBytes", "totalBytes", "speed", "timeRemaining"])) this[prop] = active.reduce((acc, dl) => dl[prop] + acc, 0); + this.hasProgress = active.every(d => d.hasProgress); + this.progress = Math.round((this.currentBytes / this.totalBytes) * 100); + this.nActive = active.length; + Download.prototype.updateProgress.call(self); - this.nActive = active.length; if (active.length) this.nodes.total.textContent = _("download.nActive", active.length); else for (let key in values(["total", "percent", "speed", "time"])) @@ -354,68 +369,87 @@ var DownloadList = Class("DownloadList", this.sort(); }, - observers: { - "download-manager-remove-download": function (id) { - if (id == null) - id = [k for ([k, dl] in iter(this.downloads)) if (dl.allowedCommands.remove)]; - else - id = [id.QueryInterface(Ci.nsISupportsPRUint32).data]; + onDownloadAdded: function onDownloadAdded(download) { + this.addDownload(download); - Array.concat(id).map(this.closure.removeDownload); - this.update(); - } + this.modules.mow.resize(false); + this.nodes.list.scrollIntoView(false); }, - onDownloadStateChange: function (state, download) { - try { - if (download.id in this.downloads) - this.downloads[download.id].updateStatus(); - else { - this.addDownload(download.id); + onDownloadRemoved: function onDownloadRemoved(download) { + this.removeDownload(download); + }, + + onDownloadChanged: function onDownloadChanged(download) { + if (this.downloads.has(download)) { + download = this.downloads.get(download) + + download.updateStatus(); + download.updateProgress(); - this.modules.mow.resize(false); - 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, - download) { - try { - if (download.id in this.downloads) - this.downloads[download.id].updateProgress(); - this.updateProgress(); - } - catch (e) { - util.reportError(e); - } } }); +["canceled", + "contentType", + "currentBytes", + "error", + "hasPartialData", + "hasProgress", + "launchWhenSucceeded", + "launcherPath", + "progress", + "saver", + "source", + "speed", + "startTime", + "stopped", + "succeeded", + "target", + "totalBytes", + "tryToKeepPartialData"].forEach(key => { + if (!(key in Download.prototype)) + Object.defineProperty(Download.prototype, key, { + get: function get() this.download[key], + set: function set(val) this.download[key] = val, + configurable: true + }); +}); + -var Downloads = Module("downloads", XPCOM(Ci.nsIDownloadProgressListener), { +var Downloads_ = Module("downloads", XPCOM(Ci.nsIDownloadProgressListener), { init: function () { - services.downloadManager.addListener(this); + Downloads.getList(Downloads.ALL).then(list => { + this.list = list; + if (!this.dead) + this.list.addView(this); + }); }, cleanup: function destroy() { - services.downloadManager.removeListener(this); + if (this.list) + this.list.removeView(this); + this.dead = true; + }, + + onDownloadAdded: function onDownloadAdded(download) { }, - onDownloadStateChange: function (state, download) { - if (download.state == services.downloadManager.DOWNLOAD_FINISHED) { - let url = download.source.spec; - let title = download.displayName; - let file = download.targetFile.path; - let size = download.size; + onDownloadRemoved: function onDownloadRemoved(download) { + }, + + onDownloadChanged: function onDownloadChanged(download) { + if (download.succeeded) { + let target = File(download.target.path); + + let url = download.source.url; + let title = target.leafName; + let file = target.path; + let size = download.totalBytes; overlay.modules.forEach(function (modules) { modules.dactyl.echomsg({ domains: [util.getHost(url)], message: _("io.downloadFinished", title, file) }, @@ -451,7 +485,7 @@ var Downloads = Module("downloads", XPCOM(Ci.nsIDownloadProgressListener), { commands.add(["dlc[lear]"], "Clear completed downloads", - function (args) { services.downloadManager.cleanUp(); }); + function (args) { downloads.list.removeFinished(); }); }, options: function initOptions(dactyl, modules, window) { const { options } = modules; @@ -489,9 +523,9 @@ var Downloads = Module("downloads", XPCOM(Ci.nsIDownloadProgressListener), { }, completer: function (context, extra) { - let seen = Set.has(Set(extra.values.map(val => val.substr(1)))); + let seen = RealSet(extra.values.map(val => val.substr(1))); - context.completions = iter(this.values).filter(([k, v]) => !seen(k)) + context.completions = iter(this.values).filter(([k, v]) => !seen.has(k)) .map(([k, v]) => [["+" + k, [v, " (", _("sort.ascending"), ")"].join("")], ["-" + k, [v, " (", _("sort.descending"), ")"].join("")]]) .flatten().array; @@ -500,9 +534,9 @@ var Downloads = Module("downloads", XPCOM(Ci.nsIDownloadProgressListener), { has: function () Array.some(arguments, val => this.value.some(v => v.substr(1) == val)), validator: function (value) { - let seen = {}; - return value.every(val => /^[+-]/.test(val) && Set.has(this.values, val.substr(1)) - && !Set.add(seen, val.substr(1))) + let seen = RealSet(); + return value.every(val => /^[+-]/.test(val) && hasOwnProperty(this.values, val.substr(1)) + && !seen.add(val.substr(1))) && value.length; } });