michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: this.EXPORTED_SYMBOLS = []; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Downloads.jsm"); michael@0: Cu.import("resource://gre/modules/Task.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "ppmm", michael@0: "@mozilla.org/parentprocessmessagemanager;1", michael@0: "nsIMessageBroadcaster"); michael@0: michael@0: function debug(aStr) { michael@0: #ifdef MOZ_DEBUG michael@0: dump("-*- DownloadsAPI.jsm : " + aStr + "\n"); michael@0: #endif michael@0: } michael@0: michael@0: function sendPromiseMessage(aMm, aMessageName, aData, aError) { michael@0: debug("sendPromiseMessage " + aMessageName); michael@0: let msg = { michael@0: id: aData.id, michael@0: promiseId: aData.promiseId michael@0: }; michael@0: michael@0: if (aError) { michael@0: msg.error = aError; michael@0: } michael@0: michael@0: aMm.sendAsyncMessage(aMessageName, msg); michael@0: } michael@0: michael@0: let DownloadsAPI = { michael@0: init: function() { michael@0: debug("init"); michael@0: michael@0: this._ids = new WeakMap(); // Maps toolkit download objects to ids. michael@0: this._index = {}; // Maps ids to downloads. michael@0: michael@0: ["Downloads:GetList", michael@0: "Downloads:ClearAllDone", michael@0: "Downloads:Remove", michael@0: "Downloads:Pause", michael@0: "Downloads:Resume"].forEach((msgName) => { michael@0: ppmm.addMessageListener(msgName, this); michael@0: }); michael@0: michael@0: let self = this; michael@0: Task.spawn(function () { michael@0: let list = yield Downloads.getList(Downloads.ALL); michael@0: yield list.addView(self); michael@0: michael@0: debug("view added to download list."); michael@0: }).then(null, Components.utils.reportError); michael@0: michael@0: this._currentId = 0; michael@0: }, michael@0: michael@0: /** michael@0: * Returns a unique id for each download, hashing the url and the path. michael@0: */ michael@0: downloadId: function(aDownload) { michael@0: let id = this._ids.get(aDownload, null); michael@0: if (!id) { michael@0: id = "download-" + this._currentId++; michael@0: this._ids.set(aDownload, id); michael@0: this._index[id] = aDownload; michael@0: } michael@0: return id; michael@0: }, michael@0: michael@0: getDownloadById: function(aId) { michael@0: return this._index[aId]; michael@0: }, michael@0: michael@0: /** michael@0: * Converts a download object into a plain json object that we'll michael@0: * send to the DOM side. michael@0: */ michael@0: jsonDownload: function(aDownload) { michael@0: let res = { michael@0: totalBytes: aDownload.totalBytes, michael@0: currentBytes: aDownload.currentBytes, michael@0: url: aDownload.source.url, michael@0: path: aDownload.target.path, michael@0: contentType: aDownload.contentType, michael@0: startTime: aDownload.startTime.getTime() michael@0: }; michael@0: michael@0: if (aDownload.error) { michael@0: res.error = aDownload.error; michael@0: } michael@0: michael@0: res.id = this.downloadId(aDownload); michael@0: michael@0: // The state of the download. Can be any of "downloading", "stopped", michael@0: // "succeeded", finalized". michael@0: michael@0: // Default to "stopped" michael@0: res.state = "stopped"; michael@0: if (!aDownload.stopped && michael@0: !aDownload.canceled && michael@0: !aDownload.succeeded && michael@0: !aDownload.DownloadError) { michael@0: res.state = "downloading"; michael@0: } else if (aDownload.succeeded) { michael@0: res.state = "succeeded"; michael@0: } michael@0: return res; michael@0: }, michael@0: michael@0: /** michael@0: * download view methods. michael@0: */ michael@0: onDownloadAdded: function(aDownload) { michael@0: let download = this.jsonDownload(aDownload); michael@0: debug("onDownloadAdded " + uneval(download)); michael@0: ppmm.broadcastAsyncMessage("Downloads:Added", download); michael@0: }, michael@0: michael@0: onDownloadRemoved: function(aDownload) { michael@0: let download = this.jsonDownload(aDownload); michael@0: download.state = "finalized"; michael@0: debug("onDownloadRemoved " + uneval(download)); michael@0: ppmm.broadcastAsyncMessage("Downloads:Removed", download); michael@0: this._index[this._ids.get(aDownload)] = null; michael@0: this._ids.delete(aDownload); michael@0: }, michael@0: michael@0: onDownloadChanged: function(aDownload) { michael@0: let download = this.jsonDownload(aDownload); michael@0: debug("onDownloadChanged " + uneval(download)); michael@0: ppmm.broadcastAsyncMessage("Downloads:Changed", download); michael@0: }, michael@0: michael@0: receiveMessage: function(aMessage) { michael@0: if (!aMessage.target.assertPermission("downloads")) { michael@0: debug("No 'downloads' permission!"); michael@0: return; michael@0: } michael@0: michael@0: debug("message: " + aMessage.name); michael@0: michael@0: switch (aMessage.name) { michael@0: case "Downloads:GetList": michael@0: this.getList(aMessage.data, aMessage.target); michael@0: break; michael@0: case "Downloads:ClearAllDone": michael@0: this.clearAllDone(aMessage.data, aMessage.target); michael@0: break; michael@0: case "Downloads:Remove": michael@0: this.remove(aMessage.data, aMessage.target); michael@0: break; michael@0: case "Downloads:Pause": michael@0: this.pause(aMessage.data, aMessage.target); michael@0: break; michael@0: case "Downloads:Resume": michael@0: this.resume(aMessage.data, aMessage.target); michael@0: break; michael@0: default: michael@0: debug("Invalid message: " + aMessage.name); michael@0: } michael@0: }, michael@0: michael@0: getList: function(aData, aMm) { michael@0: debug("getList called!"); michael@0: let self = this; michael@0: Task.spawn(function () { michael@0: let list = yield Downloads.getList(Downloads.ALL); michael@0: let downloads = yield list.getAll(); michael@0: let res = []; michael@0: downloads.forEach((aDownload) => { michael@0: res.push(self.jsonDownload(aDownload)); michael@0: }); michael@0: aMm.sendAsyncMessage("Downloads:GetList:Return", res); michael@0: }).then(null, Components.utils.reportError); michael@0: }, michael@0: michael@0: clearAllDone: function(aData, aMm) { michael@0: debug("clearAllDone called!"); michael@0: let self = this; michael@0: Task.spawn(function () { michael@0: let list = yield Downloads.getList(Downloads.ALL); michael@0: yield list.removeFinished(); michael@0: list = yield Downloads.getList(Downloads.ALL); michael@0: let downloads = yield list.getAll(); michael@0: let res = []; michael@0: downloads.forEach((aDownload) => { michael@0: res.push(self.jsonDownload(aDownload)); michael@0: }); michael@0: aMm.sendAsyncMessage("Downloads:ClearAllDone:Return", res); michael@0: }).then(null, Components.utils.reportError); michael@0: }, michael@0: michael@0: remove: function(aData, aMm) { michael@0: debug("remove id " + aData.id); michael@0: let download = this.getDownloadById(aData.id); michael@0: if (!download) { michael@0: sendPromiseMessage(aMm, "Downloads:Remove:Return", michael@0: aData, "NoSuchDownload"); michael@0: return; michael@0: } michael@0: michael@0: Task.spawn(function() { michael@0: yield download.finalize(true); michael@0: let list = yield Downloads.getList(Downloads.ALL); michael@0: yield list.remove(download); michael@0: }).then( michael@0: function() { michael@0: sendPromiseMessage(aMm, "Downloads:Remove:Return", aData); michael@0: }, michael@0: function() { michael@0: sendPromiseMessage(aMm, "Downloads:Remove:Return", michael@0: aData, "RemoveError"); michael@0: } michael@0: ); michael@0: }, michael@0: michael@0: pause: function(aData, aMm) { michael@0: debug("pause id " + aData.id); michael@0: let download = this.getDownloadById(aData.id); michael@0: if (!download) { michael@0: sendPromiseMessage(aMm, "Downloads:Pause:Return", michael@0: aData, "NoSuchDownload"); michael@0: return; michael@0: } michael@0: michael@0: download.cancel().then( michael@0: function() { michael@0: sendPromiseMessage(aMm, "Downloads:Pause:Return", aData); michael@0: }, michael@0: function() { michael@0: sendPromiseMessage(aMm, "Downloads:Pause:Return", michael@0: aData, "PauseError"); michael@0: } michael@0: ); michael@0: }, michael@0: michael@0: resume: function(aData, aMm) { michael@0: debug("resume id " + aData.id); michael@0: let download = this.getDownloadById(aData.id); michael@0: if (!download) { michael@0: sendPromiseMessage(aMm, "Downloads:Resume:Return", michael@0: aData, "NoSuchDownload"); michael@0: return; michael@0: } michael@0: michael@0: download.start().then( michael@0: function() { michael@0: sendPromiseMessage(aMm, "Downloads:Resume:Return", aData); michael@0: }, michael@0: function() { michael@0: sendPromiseMessage(aMm, "Downloads:Resume:Return", michael@0: aData, "ResumeError"); michael@0: } michael@0: ); michael@0: } michael@0: }; michael@0: michael@0: DownloadsAPI.init();