mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 20:47:44 +00:00
470 lines
15 KiB
JavaScript
470 lines
15 KiB
JavaScript
|
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||
|
|
||
|
const URI_GENERIC_ICON_DOWNLOAD = "chrome://browser/skin/images/alert-downloads-30.png";
|
||
|
|
||
|
var Downloads = {
|
||
|
_inited: false,
|
||
|
_progressAlert: null,
|
||
|
|
||
|
get manager() {
|
||
|
return Cc["@mozilla.org/download-manager;1"]
|
||
|
.getService(Ci.nsIDownloadManager);
|
||
|
},
|
||
|
|
||
|
_getReferrerOrSource: function dh__getReferrerOrSource(aDownload) {
|
||
|
return aDownload.referrer.spec || aDownload.source.spec;
|
||
|
},
|
||
|
|
||
|
_getLocalFile: function dh__getLocalFile(aFileURI) {
|
||
|
// XXX it's possible that using a null char-set here is bad
|
||
|
|
||
|
const fileUrl = Services.io.newURI(aFileURI.spec || aFileURI, null, null).QueryInterface(Ci.nsIFileURL);
|
||
|
return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
|
||
|
},
|
||
|
|
||
|
init: function dh_init() {
|
||
|
if (this._inited)
|
||
|
return;
|
||
|
|
||
|
this._inited = true;
|
||
|
|
||
|
Services.obs.addObserver(this, "dl-start", true);
|
||
|
Services.obs.addObserver(this, "dl-done", true);
|
||
|
},
|
||
|
|
||
|
uninit: function dh_uninit() {
|
||
|
Services.obs.removeObserver(this, "dl-start");
|
||
|
Services.obs.removeObserver(this, "dl-done");
|
||
|
},
|
||
|
|
||
|
openDownload: function dh_openDownload(aDownload) {
|
||
|
// expects xul item
|
||
|
let id = aDownload.getAttribute("downloadId");
|
||
|
let download = this.manager.getDownload(id)
|
||
|
let fileURI = download.target;
|
||
|
let file = this._getLocalFile(fileURI);
|
||
|
|
||
|
try {
|
||
|
file.launch();
|
||
|
} catch (ex) { }
|
||
|
},
|
||
|
|
||
|
removeDownload: function dh_removeDownload(aDownload) {
|
||
|
// aDownload is the XUL element here,
|
||
|
// and .target currently returns the target attribute (string value)
|
||
|
let id = aDownload.getAttribute("downloadId");
|
||
|
let download = this.manager.getDownload(id);
|
||
|
|
||
|
if (download) {
|
||
|
// TODO <sfoster> add class/mark element for removal transition?
|
||
|
this.manager.removeDownload(id);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
cancelDownload: function dh_cancelDownload(aDownload) {
|
||
|
let id = aDownload.getAttribute("downloadId");
|
||
|
let download = this.manager.getDownload(id)
|
||
|
this.manager.cancelDownload(id);
|
||
|
|
||
|
let fileURI = download.target;
|
||
|
let file = this._getLocalFile(fileURI);
|
||
|
|
||
|
if (file.exists())
|
||
|
file.remove(false);
|
||
|
},
|
||
|
|
||
|
pauseDownload: function dh_pauseDownload(aDownload) {
|
||
|
let id = aDownload.getAttribute("downloadId");
|
||
|
this.manager.pauseDownload(id);
|
||
|
},
|
||
|
|
||
|
resumeDownload: function dh_resumeDownload(aDownload) {
|
||
|
let id = aDownload.getAttribute("downloadId");
|
||
|
this.manager.resumeDownload(id);
|
||
|
},
|
||
|
|
||
|
retryDownload: function dh_retryDownload(aDownload) {
|
||
|
let id = aDownload.getAttribute("downloadId");
|
||
|
this.manager.retryDownload(id);
|
||
|
},
|
||
|
|
||
|
showPage: function dh_showPage(aDownload) {
|
||
|
let id = aDownload.getAttribute("downloadId");
|
||
|
let download = this.manager.getDownload(id)
|
||
|
let uri = this._getReferrerOrSource(download);
|
||
|
if (uri)
|
||
|
BrowserUI.newTab(uri, Browser.selectedTab);
|
||
|
},
|
||
|
|
||
|
showAlert: function dh_showAlert(aName, aMessage, aTitle, aIcon) {
|
||
|
var notifier = Cc["@mozilla.org/alerts-service;1"]
|
||
|
.getService(Ci.nsIAlertsService);
|
||
|
|
||
|
// Callback for tapping on the alert popup
|
||
|
let observer = {
|
||
|
observe: function (aSubject, aTopic, aData) {
|
||
|
if (aTopic == "alertclickcallback")
|
||
|
PanelUI.show("downloads-container");
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if (!aTitle)
|
||
|
aTitle = Strings.browser.GetStringFromName("alertDownloads");
|
||
|
|
||
|
if (!aIcon)
|
||
|
aIcon = URI_GENERIC_ICON_DOWNLOAD;
|
||
|
|
||
|
notifier.showAlertNotification(aIcon, aTitle, aMessage, true, "", observer, aName);
|
||
|
},
|
||
|
|
||
|
observe: function (aSubject, aTopic, aData) {
|
||
|
let download = aSubject.QueryInterface(Ci.nsIDownload);
|
||
|
let msgKey = "";
|
||
|
|
||
|
switch (aTopic) {
|
||
|
case "dl-start":
|
||
|
msgKey = "alertDownloadsStart";
|
||
|
if (!this._progressAlert) {
|
||
|
this._progressAlert = new AlertDownloadProgressListener();
|
||
|
this.manager.addListener(this._progressAlert);
|
||
|
}
|
||
|
break;
|
||
|
case "dl-done":
|
||
|
msgKey = "alertDownloadsDone";
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (msgKey) {
|
||
|
let message = Strings.browser.formatStringFromName(msgKey, [download.displayName], 1);
|
||
|
let url = download.target.spec.replace("file:", "download:");
|
||
|
this.showAlert(url, message);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
QueryInterface: function (aIID) {
|
||
|
if (!aIID.equals(Ci.nsIObserver) &&
|
||
|
!aIID.equals(Ci.nsISupportsWeakReference) &&
|
||
|
!aIID.equals(Ci.nsISupports))
|
||
|
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||
|
return this;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Wraps a list/grid control implementing nsIDOMXULSelectControlElement and
|
||
|
* fills it with the user's downloads. It observes download notifications,
|
||
|
* so it'll automatically update to reflect current download progress.
|
||
|
*
|
||
|
* @param aSet Control implementing nsIDOMXULSelectControlElement.
|
||
|
* @param {Number} aLimit Maximum number of items to show in the view.
|
||
|
*/
|
||
|
function DownloadsView(aSet, aLimit) {
|
||
|
this._set = aSet;
|
||
|
this._limit = aLimit;
|
||
|
|
||
|
this._progress = new DownloadProgressListener(this);
|
||
|
Downloads.manager.addListener(this._progress);
|
||
|
|
||
|
// Look for the removed download notification
|
||
|
let obs = Cc["@mozilla.org/observer-service;1"].
|
||
|
getService(Ci.nsIObserverService);
|
||
|
obs.addObserver(this, "download-manager-remove-download-guid", false);
|
||
|
|
||
|
this.getDownloads();
|
||
|
}
|
||
|
|
||
|
DownloadsView.prototype = {
|
||
|
_progress: null,
|
||
|
_stmt: null,
|
||
|
_timeoutID: null,
|
||
|
_set: null,
|
||
|
_limit: null,
|
||
|
|
||
|
_getItemForDownloadGuid: function dv__getItemForDownload(aGuid) {
|
||
|
return this._set.querySelector("richgriditem[downloadGuid='" + aGuid + "']");
|
||
|
},
|
||
|
|
||
|
_getItemForDownload: function dv__getItemForDownload(aDownload) {
|
||
|
return this._set.querySelector("richgriditem[downloadId='" + aDownload.id + "']");
|
||
|
},
|
||
|
|
||
|
_getDownloadForItem: function dv__getDownloadForItem(anItem) {
|
||
|
let id = anItem.getAttribute("downloadId");
|
||
|
return Downloads.manager.getDownload(id);
|
||
|
},
|
||
|
|
||
|
_getAttrsForDownload: function dv__getAttrsForDownload(aDownload) {
|
||
|
// expects a DownloadManager download object
|
||
|
return {
|
||
|
typeName: 'download',
|
||
|
downloadId: aDownload.id,
|
||
|
downloadGuid: aDownload.guid,
|
||
|
name: aDownload.displayName,
|
||
|
target: aDownload.target,
|
||
|
iconURI: "moz-icon://" + aDownload.displayName + "?size=64",
|
||
|
date: DownloadUtils.getReadableDates(new Date())[0],
|
||
|
domain: DownloadUtils.getURIHost(aDownload.source.spec)[0],
|
||
|
size: this._getDownloadSize(aDownload.size),
|
||
|
state: aDownload.state
|
||
|
};
|
||
|
|
||
|
},
|
||
|
_updateItemWithAttrs: function dv__updateItemWithAttrs(anItem, aAttrs) {
|
||
|
for (let name in aAttrs)
|
||
|
anItem.setAttribute(name, aAttrs[name]);
|
||
|
},
|
||
|
|
||
|
_getDownloadSize: function dv__getDownloadSize (aSize) {
|
||
|
let displaySize = DownloadUtils.convertByteUnits(aSize);
|
||
|
if (displaySize[0] > 0) // [0] is size, [1] is units
|
||
|
return displaySize.join("");
|
||
|
else
|
||
|
return Strings.browser.GetStringFromName("downloadsUnknownSize");
|
||
|
},
|
||
|
|
||
|
_initStatement: function dv__initStatement() {
|
||
|
if (this._stmt)
|
||
|
this._stmt.finalize();
|
||
|
|
||
|
let limitClause = this._limit ? ("LIMIT " + this._limit) : "";
|
||
|
|
||
|
this._stmt = Downloads.manager.DBConnection.createStatement(
|
||
|
"SELECT id, guid, name, target, source, state, startTime, endTime, referrer, " +
|
||
|
"currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " +
|
||
|
"FROM moz_downloads " +
|
||
|
"ORDER BY isActive DESC, endTime DESC, startTime DESC " +
|
||
|
limitClause);
|
||
|
},
|
||
|
|
||
|
_stepDownloads: function dv__stepDownloads(aNumItems) {
|
||
|
try {
|
||
|
if (!this._stmt.executeStep()) {
|
||
|
// final record;
|
||
|
this._stmt.finalize();
|
||
|
this._stmt = null;
|
||
|
this._fire("DownloadsReady", this._set);
|
||
|
return;
|
||
|
}
|
||
|
let attrs = {
|
||
|
typeName: 'download',
|
||
|
// TODO: <sfoster> the remove event gives us guid not id; should we use guid as the download (record) id?
|
||
|
downloadGuid: this._stmt.row.guid,
|
||
|
downloadId: this._stmt.row.id,
|
||
|
name: this._stmt.row.name,
|
||
|
target: this._stmt.row.target,
|
||
|
iconURI: "moz-icon://" + this._stmt.row.name + "?size=25",
|
||
|
date: DownloadUtils.getReadableDates(new Date(this._stmt.row.endTime / 1000))[0],
|
||
|
domain: DownloadUtils.getURIHost(this._stmt.row.source)[0],
|
||
|
size: this._getDownloadSize(this._stmt.row.maxBytes),
|
||
|
state: this._stmt.row.state
|
||
|
};
|
||
|
|
||
|
let item = this._set.appendItem(attrs.target, attrs.downloadId);
|
||
|
this._updateItemWithAttrs(item, attrs);
|
||
|
} catch (e) {
|
||
|
// Something went wrong when stepping or getting values, so quit
|
||
|
this._stmt.reset();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Add another item to the list if we should;
|
||
|
// otherwise, let the UI update and continue later
|
||
|
if (aNumItems > 1) {
|
||
|
this._stepDownloads(aNumItems - 1);
|
||
|
} else {
|
||
|
// Use a shorter delay for earlier downloads to display them faster
|
||
|
let delay = Math.min(this._set.itemCount * 10, 300);
|
||
|
let self = this;
|
||
|
this._timeoutID = setTimeout(function() { self._stepDownloads(5); }, delay);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_fire: function _fire(aName, anElement) {
|
||
|
let event = document.createEvent("Events");
|
||
|
event.initEvent(aName, true, true);
|
||
|
anElement.dispatchEvent(event);
|
||
|
},
|
||
|
|
||
|
observe: function dv_managerObserver(aSubject, aTopic, aData) {
|
||
|
// observer-service message handler
|
||
|
switch (aTopic) {
|
||
|
case "download-manager-remove-download-guid":
|
||
|
let guid = aSubject.QueryInterface(Ci.nsISupportsCString);
|
||
|
this.removeDownload({
|
||
|
guid: guid
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
getDownloads: function dv_getDownloads() {
|
||
|
this._initStatement();
|
||
|
clearTimeout(this._timeoutID);
|
||
|
|
||
|
// Since we're pulling in all downloads, clear the list to avoid duplication
|
||
|
this.clearDownloads();
|
||
|
|
||
|
this._stmt.reset();
|
||
|
this._stmt.bindInt32Parameter(0, Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED);
|
||
|
this._stmt.bindInt32Parameter(1, Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING);
|
||
|
this._stmt.bindInt32Parameter(2, Ci.nsIDownloadManager.DOWNLOAD_PAUSED);
|
||
|
this._stmt.bindInt32Parameter(3, Ci.nsIDownloadManager.DOWNLOAD_QUEUED);
|
||
|
this._stmt.bindInt32Parameter(4, Ci.nsIDownloadManager.DOWNLOAD_SCANNING);
|
||
|
|
||
|
// Take a quick break before we actually start building the list
|
||
|
let self = this;
|
||
|
this._timeoutID = setTimeout(function() {
|
||
|
self._stepDownloads(1);
|
||
|
}, 0);
|
||
|
},
|
||
|
|
||
|
clearDownloads: function dv_clearDownloads() {
|
||
|
while (this._set.itemCount > 0)
|
||
|
this._set.removeItemAt(0);
|
||
|
},
|
||
|
|
||
|
addDownload: function dv_addDownload(aDownload) {
|
||
|
// expects a download manager download object
|
||
|
let attrs = this._getAttrsForDownload(aDownload);
|
||
|
let item = this._set.insertItemAt(0, attrs.target, attrs.downloadId);
|
||
|
this._updateItemWithAttrs(item, attrs);
|
||
|
},
|
||
|
|
||
|
updateDownload: function dv_updateDownload(aDownload) {
|
||
|
// expects a download manager download object
|
||
|
let item = this._getItemForDownload(aDownload);
|
||
|
|
||
|
if (!item)
|
||
|
return;
|
||
|
|
||
|
let attrs = this._getAttrsForDownload(aDownload);
|
||
|
this._updateItemWithAttrs(item, attrs);
|
||
|
},
|
||
|
|
||
|
removeDownload: function dv_removeDownload(aDownload) {
|
||
|
// expects an object with id or guid property
|
||
|
let item;
|
||
|
if (aDownload.id) {
|
||
|
item = this._getItemForDownload(aDownload.id);
|
||
|
} else if (aDownload.guid) {
|
||
|
item = this._getItemForDownloadGuid(aDownload.guid);
|
||
|
}
|
||
|
if (!item)
|
||
|
return;
|
||
|
|
||
|
let idx = this._set.getIndexOfItem(item);
|
||
|
if (idx < 0)
|
||
|
return;
|
||
|
// any transition needed here?
|
||
|
this._set.removeItemAt(idx);
|
||
|
},
|
||
|
|
||
|
destruct: function dv_destruct() {
|
||
|
Downloads.manager.removeListener(this._progress);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var DownloadsPanelView = {
|
||
|
_view: null,
|
||
|
|
||
|
get _grid() { return document.getElementById("downloads-list"); },
|
||
|
get visible() { return PanelUI.isPaneVisible("downloads-container"); },
|
||
|
|
||
|
init: function init() {
|
||
|
this._view = new DownloadsView(this._grid);
|
||
|
},
|
||
|
|
||
|
show: function show() {
|
||
|
this._grid.arrangeItems();
|
||
|
},
|
||
|
|
||
|
uninit: function uninit() {
|
||
|
this._view.destruct();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Notifies a DownloadsView about updates in the state of various downloads.
|
||
|
*
|
||
|
* @param aView An instance of DownloadsView.
|
||
|
*/
|
||
|
function DownloadProgressListener(aView) {
|
||
|
this._view = aView;
|
||
|
}
|
||
|
|
||
|
DownloadProgressListener.prototype = {
|
||
|
_view: null,
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
//// nsIDownloadProgressListener
|
||
|
onDownloadStateChange: function dPL_onDownloadStateChange(aState, aDownload) {
|
||
|
let state = aDownload.state;
|
||
|
switch (state) {
|
||
|
case Ci.nsIDownloadManager.DOWNLOAD_QUEUED:
|
||
|
case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY:
|
||
|
this._view.addDownload(aDownload);
|
||
|
break;
|
||
|
case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
|
||
|
case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
|
||
|
case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
|
||
|
case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
|
||
|
case Ci.nsIDownloadManager.DOWNLOAD_FINISHED:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
this._view.updateDownload(aDownload);
|
||
|
},
|
||
|
|
||
|
onProgressChange: function dPL_onProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) {
|
||
|
// TODO <jwilde>: Add more detailed progress information.
|
||
|
this._view.updateDownload(aDownload);
|
||
|
},
|
||
|
|
||
|
onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
|
||
|
onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
//// nsISupports
|
||
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadProgressListener])
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Tracks download progress so that additional information can be displayed
|
||
|
* about its download in alert popups.
|
||
|
*/
|
||
|
function AlertDownloadProgressListener() { }
|
||
|
|
||
|
AlertDownloadProgressListener.prototype = {
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
//// nsIDownloadProgressListener
|
||
|
onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) {
|
||
|
let strings = Strings.browser;
|
||
|
let availableSpace = -1;
|
||
|
|
||
|
try {
|
||
|
// diskSpaceAvailable is not implemented on all systems
|
||
|
let availableSpace = aDownload.targetFile.diskSpaceAvailable;
|
||
|
} catch(ex) { }
|
||
|
|
||
|
let contentLength = aDownload.size;
|
||
|
if (availableSpace > 0 && contentLength > 0 && contentLength > availableSpace) {
|
||
|
Downloads.showAlert(aDownload.target.spec.replace("file:", "download:"),
|
||
|
strings.GetStringFromName("alertDownloadsNoSpace"),
|
||
|
strings.GetStringFromName("alertDownloadsSize"));
|
||
|
Downloads.cancelDownload(aDownload.id);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onDownloadStateChange: function(aState, aDownload) { },
|
||
|
onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
|
||
|
onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
//// nsISupports
|
||
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadProgressListener])
|
||
|
};
|