Bug 392097 - only showing 7 days of download history, until I search, then I see more items. r=sdwilsh, b-ff3=beltzner

git-svn-id: svn://10.0.0.236/trunk@241420 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
edward.lee%engineering.uiuc.edu
2007-12-16 01:35:38 +00:00
parent 972a70468d
commit 2c573de473
5 changed files with 216 additions and 234 deletions

View File

@@ -219,7 +219,6 @@ pref("browser.download.manager.closeWhenDone", false);
pref("browser.download.manager.openDelay", 0);
pref("browser.download.manager.focusWhenStarting", false);
pref("browser.download.manager.flashCount", 2);
pref("browser.download.manager.displayedHistoryDays", 7);
pref("browser.download.manager.addToRecentDocs", true);
// search engines URL

View File

@@ -65,7 +65,7 @@ DownloadProgressListener.prototype = {
let state = aDownload.state;
switch (state) {
case nsIDM.DOWNLOAD_QUEUED:
buildActiveDownloadsList();
prependList(aDownload);
break;
case nsIDM.DOWNLOAD_FAILED:
case nsIDM.DOWNLOAD_CANCELED:

View File

@@ -78,7 +78,8 @@
return state == dl.DOWNLOAD_NOTSTARTED ||
state == dl.DOWNLOAD_QUEUED ||
state == dl.DOWNLOAD_DOWNLOADING ||
state == dl.DOWNLOAD_PAUSED;
state == dl.DOWNLOAD_PAUSED ||
state == dl.DOWNLOAD_SCANNING;
]]>
</getter>
</property>

View File

@@ -47,8 +47,6 @@
const PREF_BDM_CLOSEWHENDONE = "browser.download.manager.closeWhenDone";
const PREF_BDM_ALERTONEXEOPEN = "browser.download.manager.alertOnEXEOpen";
const PREF_BDM_DISPLAYEDHISTORYDAYS =
"browser.download.manager.displayedHistoryDays";
const nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
"nsILocalFile", "initWithPath");
@@ -59,13 +57,18 @@ var Ci = Components.interfaces;
const nsIDM = Ci.nsIDownloadManager;
let gDownloadManager = Cc["@mozilla.org/download-manager;1"].getService(nsIDM);
var gDownloadListener = null;
var gDownloadsView = null;
var gDownloadsActiveArea = null;
var gDownloadsDoneArea = null;
var gDownloadInfoPopup = null;
var gUserInterfered = false;
var gSearching = false;
let gDownloadListener = null;
let gDownloadsView = null;
let gSearchBox = null;
let gDownloadInfoPopup = null;
let gSearchTerms = "";
let gBuilder = 0;
// Control the performance of the incremental list building by setting how many
// milliseconds to wait before building more of the list and how many items to
// add between each delay.
const gListBuildDelay = 100;
const gListBuildChunk = 10;
// If the user has interacted with the window in a significant way, we should
// not auto-close the window. Tough UI decisions about what is "significant."
@@ -101,12 +104,13 @@ let gStr = {
fileExecutableSecurityWarningDontAsk: "fileExecutableSecurityWarningDontAsk"
};
// base query used to display download items, use replaceInsert to set WHERE
let gBaseQuery = "SELECT id, target, name, source, state, startTime, " +
"endTime, referrer, currBytes, maxBytes " +
"FROM moz_downloads " +
"WHERE #1 " +
"ORDER BY endTime ASC, startTime ASC";
// The statement to query for downloads that are active or match the search
let gStmt = gDownloadManager.DBConnection.createStatement(
"SELECT id, target, name, source, state, startTime, endTime, referrer, " +
"currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " +
"FROM moz_downloads " +
"WHERE isActive OR name LIKE ?6 ESCAPE '/' " +
"ORDER BY isActive DESC, endTime DESC, startTime DESC");
////////////////////////////////////////////////////////////////////////////////
//// Utility Functions
@@ -119,43 +123,6 @@ function fireEventForElement(aElement, aEventType)
aElement.dispatchEvent(e);
}
function createDownloadItem(aID, aFile, aTarget, aURI, aState, aProgress,
aStartTime, aEndTime, aReferrer, aCurrBytes,
aMaxBytes)
{
var dl = document.createElement("richlistitem");
dl.setAttribute("type", "download");
dl.setAttribute("id", "dl" + aID);
dl.setAttribute("dlid", aID);
dl.setAttribute("image", "moz-icon://" + aFile + "?size=32");
dl.setAttribute("file", aFile);
dl.setAttribute("target", aTarget);
dl.setAttribute("uri", aURI);
dl.setAttribute("state", aState);
dl.setAttribute("progress", aProgress);
dl.setAttribute("startTime", aStartTime);
dl.setAttribute("endTime", aEndTime);
if (aReferrer)
dl.setAttribute("referrer", aReferrer);
dl.setAttribute("currBytes", aCurrBytes);
dl.setAttribute("maxBytes", aMaxBytes);
dl.setAttribute("lastSeconds", Infinity);
updateTime(dl);
updateStatus(dl);
try {
var file = getLocalFileFromNativePathOrUrl(aFile);
dl.setAttribute("path", file.nativePath || file.path);
return dl;
}
catch (ex) {
// aFile might not be a file: url or a valid native path
// see bug #392386 for details
}
return null;
}
function getDownload(aID)
{
return document.getElementById("dl" + aID);
@@ -181,13 +148,21 @@ function downloadCompleted(aDownload)
// Show the finish time because we're done
updateTime(dl);
// If we are displaying search results, we do not want to add it to the list
// of completed downloads
if (!gSearching) {
gDownloadsView.insertBefore(dl, gDownloadsDoneArea.nextSibling);
evenOddCellAttribution();
} else
// If we aren't displaying search results, move the download to after the
// active ones
if (gSearchTerms.length == 0) {
// Iterate down until we find a non-active download
let next = dl.nextSibling;
while (next && next.inProgress)
next = next.nextSibling;
// Move the item and color everything after where it moved from
let fixup = dl.nextSibling;
gDownloadsView.insertBefore(dl, next);
stripeifyList(fixup);
} else {
removeFromView(dl);
}
// getTypeFromFile fails if it can't find a type for this file.
try {
@@ -459,10 +434,9 @@ function onUpdateProgress()
function Startup()
{
gDownloadsView = document.getElementById("downloadView");
gDownloadsActiveArea = document.getElementById("active-downloads-area");
gDownloadsDoneArea = document.getElementById("done-downloads-area");
gDownloadInfoPopup = document.getElementById("information");
gDownloadsView = document.getElementById("downloadView");
gSearchBox = document.getElementById("searchbox");
gDownloadInfoPopup = document.getElementById("information");
// convert strings to those in the string bundle
let (sb = document.getElementById("downloadStrings")) {
@@ -471,7 +445,7 @@ function Startup()
gStr[name] = typeof value == "string" ? getStr(value) : value.map(getStr);
}
buildDefaultView();
buildDownloadList();
// View event listeners
gDownloadsView.addEventListener("dblclick", onDownloadDblClick, false);
@@ -498,6 +472,10 @@ function Shutdown()
let obs = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
obs.removeObserver(gDownloadObserver, "download-manager-remove-download");
clearTimeout(gBuilder);
gStmt.reset();
gStmt.finalize();
}
let gDownloadObserver = {
@@ -507,7 +485,7 @@ let gDownloadObserver = {
// A null subject here indicates "remove all"
if (!aSubject) {
// Rebuild the default view
buildDefaultView();
buildDownloadList();
break;
}
@@ -781,9 +759,8 @@ var gDownloadViewController = {
function setSearchboxFocus()
{
var searchbox = document.getElementById("searchbox");
searchbox.focus();
searchbox.select();
gSearchBox.focus();
gSearchBox.select();
}
function onDownloadShowInfo()
@@ -805,7 +782,46 @@ function openExternal(aFile)
}
////////////////////////////////////////////////////////////////////////////////
//// Utility functions
//// Utility Functions
/**
* Create a download richlistitem with the provided attributes. Some attributes
* are *required* while optional ones will only be set on the item if provided.
*
* @param aAttrs
* An object that must have the following properties: dlid, file,
* target, uri, state, progress, startTime, endTime, currBytes,
* maxBytes; optional properties: referrer
* @return An initialized download richlistitem
*/
function createDownloadItem(aAttrs)
{
let dl = document.createElement("richlistitem");
// Copy the attributes from the argument into the item
for (let attr in aAttrs)
dl.setAttribute(attr, aAttrs[attr]);
// Initialize other attributes
dl.setAttribute("type", "download");
dl.setAttribute("id", "dl" + aAttrs.dlid);
dl.setAttribute("image", "moz-icon://" + aAttrs.file + "?size=32");
dl.setAttribute("lastSeconds", Infinity);
// Initialize more complex attributes
updateTime(dl);
updateStatus(dl);
try {
let file = getLocalFileFromNativePathOrUrl(aAttrs.file);
dl.setAttribute("path", file.nativePath || file.path);
return dl;
} catch (e) {
// aFile might not be a file: url or a valid native path
// see bug #392386 for details
}
return null;
}
/**
* Updates the status for a download item depending on its state
@@ -1051,7 +1067,9 @@ function removeFromView(aDownload)
let index = gDownloadsView.selectedIndex;
gDownloadsView.removeChild(aDownload);
gDownloadsView.selectedIndex = Math.min(index, gDownloadsView.itemCount - 1);
evenOddCellAttribution();
// Color everything after from the newly selected item
stripeifyList(gDownloadsView.selectedItem);
}
function getReferrerOrSource(aDownload)
@@ -1065,204 +1083,169 @@ function getReferrerOrSource(aDownload)
}
/**
* Builds the default view that the download manager starts out with.
* Initiate building the download list to have the active downloads followed by
* completed ones filtered by the search term if necessary.
*/
function buildDefaultView()
function buildDownloadList()
{
buildActiveDownloadsList();
// Clear out values before using them
clearTimeout(gBuilder);
gStmt.reset();
gSearchTerms = "";
let pref = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch);
let days = pref.getIntPref(PREF_BDM_DISPLAYEDHISTORYDAYS);
buildDownloadListWithTime(Date.now() - days * 24 * 60 * 60 * 1000);
// select the first visible download item, if any
var children = gDownloadsView.children;
if (children.length > 0)
gDownloadsView.selectedItem = children[0];
}
/**
* Gives every second cell an odd attribute so they can receive alternating
* backgrounds from the stylesheets.
*/
function evenOddCellAttribution()
{
let alternateCell = false;
let allDownloadsInView = gDownloadsView.getElementsByTagName("richlistitem");
for (let i = 0; i < allDownloadsInView.length; i++) {
if (alternateCell)
allDownloadsInView[i].setAttribute("alternate", "true");
else
allDownloadsInView[i].removeAttribute("alternate");
alternateCell = !alternateCell;
// Clear the list before adding items by replacing with a shallow copy
let (empty = gDownloadsView.cloneNode(false)) {
gDownloadsView.parentNode.replaceChild(empty, gDownloadsView);
gDownloadsView = empty;
}
// If the search box isn't empty, trim the search terms
if (!gSearchBox.hasAttribute("empty"))
gSearchTerms = gSearchBox.value.replace(/^\s+|\s+$/, "");
let like = "%" + gStmt.escapeStringForLIKE(gSearchTerms, "/") + "%";
try {
gStmt.bindInt32Parameter(0, nsIDM.DOWNLOAD_NOTSTARTED);
gStmt.bindInt32Parameter(1, nsIDM.DOWNLOAD_DOWNLOADING);
gStmt.bindInt32Parameter(2, nsIDM.DOWNLOAD_PAUSED);
gStmt.bindInt32Parameter(3, nsIDM.DOWNLOAD_QUEUED);
gStmt.bindInt32Parameter(4, nsIDM.DOWNLOAD_SCANNING);
gStmt.bindStringParameter(5, like);
} catch (e) {
// Something must have gone wrong when binding, so clear and quit
gStmt.reset();
return;
}
// Start building the list and select the first item
stepListBuilder(1);
gDownloadsView.selectedIndex = 0;
}
/**
* Builds the downloads list with a given statement and reference node.
* Incrementally build the download list by adding at most the requested number
* of items if there are items to add. After doing that, it will schedule
* another chunk of items specified by gListBuildDelay and gListBuildChunk.
*
* @param aStmt
* The compiled SQL statement to build with. This needs to have the
* following columns in this order to work properly:
* id, target, name, source, state, startTime, endTime, referrer,
* currBytes, and maxBytes
* This statement should be ordered on the endTime ASC so that the end
* result is a list of downloads with their end time's descending.
* @param aRef
* The node we use for placement of the download objects. We place each
* new node above the previously inserted one.
* @param aNumItems
* Number of items to add to the list before taking a break
*/
function buildDownloadList(aStmt, aRef)
{
while (aRef.nextSibling && aRef.nextSibling.tagName == "richlistitem")
gDownloadsView.removeChild(aRef.nextSibling);
function stepListBuilder(aNumItems) {
try {
// If we're done adding all items, we can quit
if (!gStmt.executeStep())
return;
while (aStmt.executeStep()) {
let id = aStmt.getInt64(0);
let state = aStmt.getInt32(4);
let percentComplete = 100;
if (state == nsIDM.DOWNLOAD_NOTSTARTED ||
state == nsIDM.DOWNLOAD_DOWNLOADING ||
state == nsIDM.DOWNLOAD_PAUSED) {
// so we have an in-progress download that we need to determine the
// proper percentage complete for. This download will actually be in
// the active downloads array internally, so calling getDownload is cheap.
let dl = gDownloadManager.getDownload(id);
percentComplete = dl.percentComplete;
// Try to get the attribute values from the statement
let attrs = {
dlid: gStmt.getInt64(0),
file: gStmt.getString(1),
target: gStmt.getString(2),
uri: gStmt.getString(3),
state: gStmt.getInt32(4),
startTime: Math.round(gStmt.getInt64(5) / 1000),
endTime: Math.round(gStmt.getInt64(6) / 1000),
currBytes: gStmt.getInt64(8),
maxBytes: gStmt.getInt64(9)
};
// Only add the referrer if it's not null
let (referrer = gStmt.getString(7)) {
if (referrer)
attrs.referrer = referrer;
}
let dl = createDownloadItem(id,
aStmt.getString(1),
aStmt.getString(2),
aStmt.getString(3),
state,
percentComplete,
Math.round(aStmt.getInt64(5) / 1000),
Math.round(aStmt.getInt64(6) / 1000),
aStmt.getString(7),
aStmt.getInt64(8),
aStmt.getInt64(9));
if (dl)
gDownloadsView.insertBefore(dl, aRef.nextSibling);
}
evenOddCellAttribution();
}
var gActiveDownloadsQuery = null;
function buildActiveDownloadsList()
{
// Are there any active downloads?
if (gDownloadManager.activeDownloadCount == 0)
// If the download is active, grab the real progress, otherwise default 100
attrs.progress = gStmt.getInt32(10) ?
gDownloadManager.getDownload(attrs.dlid).percentComplete : 100;
// Make the item and add it to the end
let item = createDownloadItem(attrs);
if (item) {
// Add item to the end and color just that one item
gDownloadsView.appendChild(item);
stripeifyList(item);
}
} catch (e) {
// Something went wrong when stepping or getting values, so clear and quit
gStmt.reset();
return;
// repopulate the list
var db = gDownloadManager.DBConnection;
var stmt = gActiveDownloadsQuery;
if (!stmt) {
stmt = db.createStatement(replaceInsert(gBaseQuery, 1,
"state = ?1 OR state = ?2 OR state = ?3 OR state = ?4 OR state = ?5"));
gActiveDownloadsQuery = stmt;
}
try {
stmt.bindInt32Parameter(0, nsIDM.DOWNLOAD_NOTSTARTED);
stmt.bindInt32Parameter(1, nsIDM.DOWNLOAD_DOWNLOADING);
stmt.bindInt32Parameter(2, nsIDM.DOWNLOAD_PAUSED);
stmt.bindInt32Parameter(3, nsIDM.DOWNLOAD_QUEUED);
stmt.bindInt32Parameter(4, nsIDM.DOWNLOAD_SCANNING);
buildDownloadList(stmt, gDownloadsActiveArea);
} finally {
stmt.reset();
// Add another item to the list if we should; otherwise, let the UI update
// and continue later
if (aNumItems > 1) {
stepListBuilder(aNumItems - 1);
} else {
// Use a shorter delay for earlier downloads to display them faster
let delay = Math.min(gDownloadsView.itemCount, gListBuildDelay);
gBuilder = setTimeout(stepListBuilder, delay, gListBuildChunk);
}
}
/**
* Builds the download view with downloads from a given time until now.
* Add a download to the front of the download list
*
* @param aTime
* The time that we want to start displaying downloads from. This time
* is in milliseconds (what is returned from Date.now()).
* @param aDownload
* The nsIDownload to make into a richlistitem
*/
var gDownloadListWithTimeQuery = null;
function buildDownloadListWithTime(aTime)
function prependList(aDownload)
{
var db = gDownloadManager.DBConnection;
var stmt = gDownloadListWithTimeQuery;
if (!stmt) {
stmt = db.createStatement(replaceInsert(gBaseQuery, 1, "startTime >= ?1 " +
"AND (state = ?2 OR state = ?3 OR state = ?4 OR state = ?5 OR state = ?6)"));
gDownloadListWithTimeQuery = stmt;
}
let attrs = {
dlid: aDownload.id,
file: aDownload.target.spec,
target: aDownload.displayName,
uri: aDownload.source.spec,
state: aDownload.state,
progress: aDownload.percentComplete,
startTime: Math.round(aDownload.startTime / 1000),
endTime: Date.now(),
currBytes: aDownload.amountTransferred,
maxBytes: aDownload.size
};
try {
stmt.bindInt64Parameter(0, aTime * 1000);
stmt.bindInt32Parameter(1, nsIDM.DOWNLOAD_FINISHED);
stmt.bindInt32Parameter(2, nsIDM.DOWNLOAD_FAILED);
stmt.bindInt32Parameter(3, nsIDM.DOWNLOAD_CANCELED);
stmt.bindInt32Parameter(4, nsIDM.DOWNLOAD_BLOCKED);
stmt.bindInt32Parameter(5, nsIDM.DOWNLOAD_DIRTY);
buildDownloadList(stmt, gDownloadsDoneArea);
} finally {
stmt.reset();
// Make the item and add it to the beginning
let item = createDownloadItem(attrs);
if (item) {
// Add item to the beginning and color the whole list
gDownloadsView.insertBefore(item, gDownloadsView.firstChild);
stripeifyList(item);
}
}
/**
* Builds the download list with an array of search terms. This also changes
* the label of the second group of downloads to search results (or the locale
* equivalent).
* Stripeify the download list by setting or clearing the "alternate" attribute
* on items starting from a particular item and continuing to the end.
*
* @param aTerms
* An array of search terms that will be checked for past downloads. If
* this array is empty, we clear the search results and build the default
* view.
* @param aItem
* Download rishlist item to start stripeifying
*/
function buildDownloadListWithSearch(aTerms)
function stripeifyList(aItem)
{
gSearching = true;
let alt = "alternate";
// Set the item to be opposite of the other
let flipFrom = function(aOther) aOther && aOther.hasAttribute(alt) ?
aItem.removeAttribute(alt) : aItem.setAttribute(alt, "true");
// remove and trailing or leading whitespace first
aTerms = aTerms.replace(/^\s+|\s+$/, "");
if (aTerms.length == 0) {
gSearching = false;
buildDefaultView();
return;
// Keep coloring items as the opposite of its previous until no more
while (aItem) {
flipFrom(aItem.previousSibling);
aItem = aItem.nextSibling;
}
var db = gDownloadManager.DBConnection;
let stmt = db.createStatement(replaceInsert(gBaseQuery, 1,
"name LIKE ?1 ESCAPE '/' AND state != ?2 AND state != ?3"));
try {
var paramForLike = stmt.escapeStringForLIKE(aTerms, '/');
stmt.bindStringParameter(0, "%" + paramForLike + "%");
stmt.bindInt32Parameter(1, nsIDM.DOWNLOAD_DOWNLOADING);
stmt.bindInt32Parameter(2, nsIDM.DOWNLOAD_PAUSED);
buildDownloadList(stmt, gDownloadsDoneArea);
} finally {
stmt.reset();
}
}
function performSearch() {
buildDownloadListWithSearch(document.getElementById("searchbox").value);
}
function onSearchboxBlur() {
var searchbox = document.getElementById("searchbox");
if (searchbox.value == "") {
searchbox.setAttribute("empty", "true");
searchbox.value = searchbox.getAttribute("defaultValue");
if (gSearchBox.value == "") {
gSearchBox.setAttribute("empty", "true");
gSearchBox.value = gSearchBox.getAttribute("defaultValue");
}
}
function onSearchboxFocus() {
var searchbox = document.getElementById("searchbox");
if (searchbox.hasAttribute("empty")) {
searchbox.value = "";
searchbox.removeAttribute("empty");
if (gSearchBox.hasAttribute("empty")) {
gSearchBox.value = "";
gSearchBox.removeAttribute("empty");
}
}

View File

@@ -25,6 +25,7 @@
# Ben Goodger <ben@bengoodger.com> (v2.0)
# Blake Ross <blakeross@telocity.com>
# Shawn Wilsher <me@shawnwilsher.com> (v3.0)
# Edward Lee <edward.lee@engineering.uiuc.edu>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -193,13 +194,11 @@
<richlistbox id="downloadView" flex="1" context="downloadContextMenu"
ondragover="nsDragAndDrop.dragOver(event, gDownloadDNDObserver);"
ondragdrop="nsDragAndDrop.drop(event, gDownloadDNDObserver);">
<hbox id="active-downloads-area" hidden="true"/>
<hbox id="done-downloads-area" hidden="true"/>
</richlistbox>
<hbox id="search">
<textbox type="timed" timeout="500" id="searchbox"
oncommand="performSearch();" empty="true"
oncommand="buildDownloadList();" empty="true"
value="&searchBox.label;" defaultValue="&searchBox.label;"
onblur="onSearchboxBlur();" onfocus="onSearchboxFocus();"/>
<spacer flex="1"/>