michael@0: // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- 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: let Cu = Components.utils; michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: function dump(a) { michael@0: Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a); michael@0: } michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Notifications", michael@0: "resource://gre/modules/Notifications.jsm"); michael@0: michael@0: const URI_GENERIC_ICON_DOWNLOAD = "drawable://alert_download"; michael@0: const URI_PAUSE_ICON = "drawable://pause"; michael@0: const URI_CANCEL_ICON = "drawable://close"; michael@0: const URI_RESUME_ICON = "drawable://play"; michael@0: michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); michael@0: michael@0: var Downloads = { michael@0: _initialized: false, michael@0: _dlmgr: null, michael@0: _progressAlert: null, michael@0: _privateDownloads: [], michael@0: _showingPrompt: false, michael@0: _downloadsIdMap: {}, michael@0: michael@0: _getLocalFile: function dl__getLocalFile(aFileURI) { michael@0: // if this is a URL, get the file from that michael@0: // XXX it's possible that using a null char-set here is bad michael@0: const fileUrl = Services.io.newURI(aFileURI, null, null).QueryInterface(Ci.nsIFileURL); michael@0: return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile); michael@0: }, michael@0: michael@0: init: function dl_init() { michael@0: if (this._initialized) michael@0: return; michael@0: this._initialized = true; michael@0: michael@0: // Monitor downloads and display alerts michael@0: this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); michael@0: this._progressAlert = new AlertDownloadProgressListener(); michael@0: this._dlmgr.addPrivacyAwareListener(this._progressAlert); michael@0: Services.obs.addObserver(this, "last-pb-context-exited", true); michael@0: }, michael@0: michael@0: openDownload: function dl_openDownload(aDownload) { michael@0: let fileUri = aDownload.target.spec; michael@0: let guid = aDownload.guid; michael@0: let f = this._getLocalFile(fileUri); michael@0: try { michael@0: f.launch(); michael@0: } catch (ex) { michael@0: // in case we are not able to open the file (i.e. there is no app able to handle it) michael@0: // we just open the browser tab showing it michael@0: BrowserApp.addTab("about:downloads?id=" + guid); michael@0: } michael@0: }, michael@0: michael@0: cancelDownload: function dl_cancelDownload(aDownload) { michael@0: aDownload.cancel(); michael@0: let fileURI = aDownload.target.spec; michael@0: let f = this._getLocalFile(fileURI); michael@0: michael@0: OS.File.remove(f.path); michael@0: }, michael@0: michael@0: showCancelConfirmPrompt: function dl_showCancelConfirmPrompt(aDownload) { michael@0: if (this._showingPrompt) michael@0: return; michael@0: this._showingPrompt = true; michael@0: // Open a prompt that offers a choice to cancel the download michael@0: let title = Strings.browser.GetStringFromName("downloadCancelPromptTitle"); michael@0: let message = Strings.browser.GetStringFromName("downloadCancelPromptMessage"); michael@0: let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_YES + michael@0: Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_NO; michael@0: let choice = Services.prompt.confirmEx(null, title, message, flags, michael@0: null, null, null, null, {}); michael@0: if (choice == 0) michael@0: this.cancelDownload(aDownload); michael@0: this._showingPrompt = false; michael@0: }, michael@0: michael@0: handleClickEvent: function dl_handleClickEvent(aDownload) { michael@0: // Only open the downloaded file if the download is complete michael@0: if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) michael@0: this.openDownload(aDownload); michael@0: else if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING || michael@0: aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_PAUSED) michael@0: this.showCancelConfirmPrompt(aDownload); michael@0: }, michael@0: michael@0: clickCallback: function dl_clickCallback(aDownloadId) { michael@0: this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) { michael@0: if (Components.isSuccessCode(status)) michael@0: this.handleClickEvent(download); michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: pauseClickCallback: function dl_buttonPauseCallback(aDownloadId) { michael@0: this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) { michael@0: if (Components.isSuccessCode(status)) michael@0: download.pause(); michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: resumeClickCallback: function dl_buttonPauseCallback(aDownloadId) { michael@0: this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) { michael@0: if (Components.isSuccessCode(status)) michael@0: download.resume(); michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: cancelClickCallback: function dl_buttonPauseCallback(aDownloadId) { michael@0: this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) { michael@0: if (Components.isSuccessCode(status)) michael@0: this.cancelDownload(download); michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: notificationCanceledCallback: function dl_notifCancelCallback(aId, aDownloadId) { michael@0: let notificationId = this._downloadsIdMap[aDownloadId]; michael@0: if (notificationId && notificationId == aId) michael@0: delete this._downloadsIdMap[aDownloadId]; michael@0: }, michael@0: michael@0: createNotification: function dl_createNotif(aDownload, aOptions) { michael@0: let notificationId = Notifications.create(aOptions); michael@0: this._downloadsIdMap[aDownload.guid] = notificationId; michael@0: }, michael@0: michael@0: updateNotification: function dl_updateNotif(aDownload, aOptions) { michael@0: let notificationId = this._downloadsIdMap[aDownload.guid]; michael@0: if (notificationId) michael@0: Notifications.update(notificationId, aOptions); michael@0: }, michael@0: michael@0: cancelNotification: function dl_cleanNotif(aDownload) { michael@0: Notifications.cancel(this._downloadsIdMap[aDownload.guid]); michael@0: delete this._downloadsIdMap[aDownload.guid]; michael@0: }, michael@0: michael@0: // observer for last-pb-context-exited michael@0: observe: function dl_observe(aSubject, aTopic, aData) { michael@0: let download; michael@0: while ((download = this._privateDownloads.pop())) { michael@0: try { michael@0: let notificationId = aDownload.guid; michael@0: Notifications.clear(notificationId); michael@0: Downloads.removeNotification(download); michael@0: } catch (e) { michael@0: dump("Error removing private download: " + e); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: QueryInterface: function (aIID) { michael@0: if (!aIID.equals(Ci.nsISupports) && michael@0: !aIID.equals(Ci.nsIObserver) && michael@0: !aIID.equals(Ci.nsISupportsWeakReference)) michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: return this; michael@0: } michael@0: }; michael@0: michael@0: const PAUSE_BUTTON = { michael@0: buttonId: "pause", michael@0: title : Strings.browser.GetStringFromName("alertDownloadsPause"), michael@0: icon : URI_PAUSE_ICON, michael@0: onClicked: function (aId, aCookie) { michael@0: Downloads.pauseClickCallback(aCookie); michael@0: } michael@0: }; michael@0: michael@0: const CANCEL_BUTTON = { michael@0: buttonId: "cancel", michael@0: title : Strings.browser.GetStringFromName("alertDownloadsCancel"), michael@0: icon : URI_CANCEL_ICON, michael@0: onClicked: function (aId, aCookie) { michael@0: Downloads.cancelClickCallback(aCookie); michael@0: } michael@0: }; michael@0: michael@0: const RESUME_BUTTON = { michael@0: buttonId: "resume", michael@0: title : Strings.browser.GetStringFromName("alertDownloadsResume"), michael@0: icon: URI_RESUME_ICON, michael@0: onClicked: function (aId, aCookie) { michael@0: Downloads.resumeClickCallback(aCookie); michael@0: } michael@0: }; michael@0: michael@0: function DownloadNotifOptions (aDownload, aTitle, aMessage) { michael@0: this.icon = URI_GENERIC_ICON_DOWNLOAD; michael@0: this.onCancel = function (aId, aCookie) { michael@0: Downloads.notificationCanceledCallback(aId, aCookie); michael@0: } michael@0: this.onClick = function (aId, aCookie) { michael@0: Downloads.clickCallback(aCookie); michael@0: } michael@0: this.title = aTitle; michael@0: this.message = aMessage; michael@0: this.buttons = null; michael@0: this.cookie = aDownload.guid; michael@0: this.persistent = true; michael@0: } michael@0: michael@0: function DownloadProgressNotifOptions (aDownload, aButtons) { michael@0: DownloadNotifOptions.apply(this, [aDownload, aDownload.displayName, aDownload.percentComplete + "%"]); michael@0: this.ongoing = true; michael@0: this.progress = aDownload.percentComplete; michael@0: this.buttons = aButtons; michael@0: } michael@0: michael@0: // AlertDownloadProgressListener is used to display progress in the alert notifications. michael@0: function AlertDownloadProgressListener() { } michael@0: michael@0: AlertDownloadProgressListener.prototype = { michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// nsIDownloadProgressListener michael@0: onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) { michael@0: let strings = Strings.browser; michael@0: let availableSpace = -1; michael@0: try { michael@0: // diskSpaceAvailable is not implemented on all systems michael@0: let availableSpace = aDownload.targetFile.diskSpaceAvailable; michael@0: } catch(ex) { } michael@0: let contentLength = aDownload.size; michael@0: if (availableSpace > 0 && contentLength > 0 && contentLength > availableSpace) { michael@0: Downloads.updateNotification(aDownload, new DownloadNotifOptions(aDownload, michael@0: strings.GetStringFromName("alertDownloadsNoSpace"), michael@0: strings.GetStringFromName("alertDownloadsSize"))); michael@0: aDownload.cancel(); michael@0: } michael@0: michael@0: if (aDownload.percentComplete == -1) { michael@0: // Undetermined progress is not supported yet michael@0: return; michael@0: } michael@0: michael@0: Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [PAUSE_BUTTON, CANCEL_BUTTON])); michael@0: }, michael@0: michael@0: onDownloadStateChange: function(aState, aDownload) { michael@0: let state = aDownload.state; michael@0: switch (state) { michael@0: case Ci.nsIDownloadManager.DOWNLOAD_QUEUED: { michael@0: NativeWindow.toast.show(Strings.browser.GetStringFromName("alertDownloadsToast"), "long"); michael@0: Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload, michael@0: Strings.browser.GetStringFromName("alertDownloadsStart2"), michael@0: aDownload.displayName)); michael@0: break; michael@0: } michael@0: case Ci.nsIDownloadManager.DOWNLOAD_PAUSED: { michael@0: Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [RESUME_BUTTON, CANCEL_BUTTON])); michael@0: break; michael@0: } michael@0: case Ci.nsIDownloadManager.DOWNLOAD_FAILED: michael@0: case Ci.nsIDownloadManager.DOWNLOAD_CANCELED: michael@0: case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL: michael@0: case Ci.nsIDownloadManager.DOWNLOAD_DIRTY: michael@0: case Ci.nsIDownloadManager.DOWNLOAD_FINISHED: { michael@0: Downloads.cancelNotification(aDownload); michael@0: if (aDownload.isPrivate) { michael@0: let index = Downloads._privateDownloads.indexOf(aDownload); michael@0: if (index != -1) { michael@0: Downloads._privateDownloads.splice(index, 1); michael@0: } michael@0: } michael@0: michael@0: if (state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) { michael@0: Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload, michael@0: Strings.browser.GetStringFromName("alertDownloadsDone2"), michael@0: aDownload.displayName)); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { }, michael@0: onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { }, michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// nsISupports michael@0: QueryInterface: function (aIID) { michael@0: if (!aIID.equals(Ci.nsIDownloadProgressListener) && michael@0: !aIID.equals(Ci.nsISupports)) michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: return this; michael@0: } michael@0: };