gecko/toolkit/components/downloads/nsDownloadManager.cpp
Marco Bonardo 643d69f402 Bug 766799 - Notify hidden visits to history observers.
Redirect sources and framed visits are considered hidden and thus not shown in
common UI history queries.  So far were not even notified, but the right thing
to do is to let the observer handle them based on its needs.
r=Mano sr=Mossop
2012-12-20 23:45:00 +01:00

3757 lines
120 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "mozilla/DebugOnly.h"
#include "mozIStorageService.h"
#include "nsIAlertsService.h"
#include "nsIClassInfoImpl.h"
#include "nsIDOMWindow.h"
#include "nsIDownloadHistory.h"
#include "nsIDownloadManagerUI.h"
#include "nsIMIMEService.h"
#include "nsIParentalControlsService.h"
#include "nsIPrefService.h"
#include "nsIPromptService.h"
#include "nsIResumableChannel.h"
#include "nsIWebBrowserPersist.h"
#include "nsIWindowMediator.h"
#include "nsILocalFileWin.h"
#include "nsILoadContext.h"
#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING
#include "nsIPrivateBrowsingService.h"
#endif
#include "nsAppDirectoryServiceDefs.h"
#include "nsArrayEnumerator.h"
#include "nsCExternalHandlerService.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDownloadManager.h"
#include "nsNetUtil.h"
#include "nsThreadUtils.h"
#include "mozStorageCID.h"
#include "nsDocShellCID.h"
#include "nsEmbedCID.h"
#include "nsToolkitCompsCID.h"
#include "SQLFunctions.h"
#ifdef XP_WIN
#include <shlobj.h>
#ifdef DOWNLOAD_SCANNER
#include "nsDownloadScanner.h"
#endif
#endif
#ifdef XP_MACOSX
#include <CoreFoundation/CoreFoundation.h>
#endif
#ifdef MOZ_WIDGET_ANDROID
#include "AndroidBridge.h"
#endif
#ifdef MOZ_WIDGET_GTK2
#include <gtk/gtk.h>
#endif
using namespace mozilla;
using mozilla::downloads::GenerateGUID;
#define DOWNLOAD_MANAGER_BUNDLE "chrome://mozapps/locale/downloads/downloads.properties"
#define DOWNLOAD_MANAGER_ALERT_ICON "chrome://mozapps/skin/downloads/downloadIcon.png"
#define PREF_BDM_SHOWALERTONCOMPLETE "browser.download.manager.showAlertOnComplete"
#define PREF_BDM_SHOWALERTINTERVAL "browser.download.manager.showAlertInterval"
#define PREF_BDM_RETENTION "browser.download.manager.retention"
#define PREF_BDM_QUITBEHAVIOR "browser.download.manager.quitBehavior"
#define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs"
#define PREF_BDM_SCANWHENDONE "browser.download.manager.scanWhenDone"
#define PREF_BDM_RESUMEONWAKEDELAY "browser.download.manager.resumeOnWakeDelay"
#define PREF_BH_DELETETEMPFILEONEXIT "browser.helperApps.deleteTempFileOnExit"
static const int64_t gUpdateInterval = 400 * PR_USEC_PER_MSEC;
#define DM_SCHEMA_VERSION 9
#define DM_DB_NAME NS_LITERAL_STRING("downloads.sqlite")
#define DM_DB_CORRUPT_FILENAME NS_LITERAL_STRING("downloads.sqlite.corrupt")
#define NS_SYSTEMINFO_CONTRACTID "@mozilla.org/system-info;1"
#ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING
const bool gUsePerWindowPrivateBrowsing = true;
#else
const bool gUsePerWindowPrivateBrowsing = false;
#endif
////////////////////////////////////////////////////////////////////////////////
//// nsDownloadManager
NS_IMPL_ISUPPORTS4(
nsDownloadManager
, nsIDownloadManager
, nsINavHistoryObserver
, nsIObserver
, nsISupportsWeakReference
)
nsDownloadManager *nsDownloadManager::gDownloadManagerService = nullptr;
nsDownloadManager *
nsDownloadManager::GetSingleton()
{
if (gDownloadManagerService) {
NS_ADDREF(gDownloadManagerService);
return gDownloadManagerService;
}
gDownloadManagerService = new nsDownloadManager();
if (gDownloadManagerService) {
#if defined(MOZ_WIDGET_GTK2)
g_type_init();
#endif
NS_ADDREF(gDownloadManagerService);
if (NS_FAILED(gDownloadManagerService->Init()))
NS_RELEASE(gDownloadManagerService);
}
return gDownloadManagerService;
}
nsDownloadManager::~nsDownloadManager()
{
#ifdef DOWNLOAD_SCANNER
if (mScanner) {
delete mScanner;
mScanner = nullptr;
}
#endif
gDownloadManagerService = nullptr;
}
nsresult
nsDownloadManager::ResumeRetry(nsDownload *aDl)
{
// Keep a reference in case we need to cancel the download
nsRefPtr<nsDownload> dl = aDl;
// Try to resume the active download
nsresult rv = dl->Resume();
// If not, try to retry the download
if (NS_FAILED(rv)) {
// First cancel the download so it's no longer active
rv = dl->Cancel();
// Then retry it
if (NS_SUCCEEDED(rv))
rv = dl->Retry();
}
return rv;
}
nsresult
nsDownloadManager::PauseAllDownloads(bool aSetResume)
{
nsresult rv = PauseAllDownloads(mCurrentDownloads, aSetResume);
nsresult rv2 = PauseAllDownloads(mCurrentPrivateDownloads, aSetResume);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv2, rv2);
return NS_OK;
}
nsresult
nsDownloadManager::PauseAllDownloads(nsCOMArray<nsDownload>& aDownloads, bool aSetResume)
{
nsresult retVal = NS_OK;
for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) {
nsRefPtr<nsDownload> dl = aDownloads[i];
// Only pause things that need to be paused
if (!dl->IsPaused()) {
// Set auto-resume before pausing so that it gets into the DB
dl->mAutoResume = aSetResume ? nsDownload::AUTO_RESUME :
nsDownload::DONT_RESUME;
// Try to pause the download but don't bail now if we fail
nsresult rv = dl->Pause();
if (NS_FAILED(rv))
retVal = rv;
}
}
return retVal;
}
nsresult
nsDownloadManager::ResumeAllDownloads(bool aResumeAll)
{
nsresult rv = ResumeAllDownloads(mCurrentDownloads, aResumeAll);
nsresult rv2 = ResumeAllDownloads(mCurrentPrivateDownloads, aResumeAll);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv2, rv2);
return NS_OK;
}
nsresult
nsDownloadManager::ResumeAllDownloads(nsCOMArray<nsDownload>& aDownloads, bool aResumeAll)
{
nsresult retVal = NS_OK;
for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) {
nsRefPtr<nsDownload> dl = aDownloads[i];
// If aResumeAll is true, then resume everything; otherwise, check if the
// download should auto-resume
if (aResumeAll || dl->ShouldAutoResume()) {
// Reset auto-resume before retrying so that it gets into the DB through
// ResumeRetry's eventual call to SetState. We clear the value now so we
// don't accidentally query completed downloads that were previously
// auto-resumed (and try to resume them).
dl->mAutoResume = nsDownload::DONT_RESUME;
// Try to resume/retry the download but don't bail now if we fail
nsresult rv = ResumeRetry(dl);
if (NS_FAILED(rv))
retVal = rv;
}
}
return retVal;
}
nsresult
nsDownloadManager::RemoveAllDownloads()
{
nsresult rv = RemoveAllDownloads(mCurrentDownloads);
nsresult rv2 = RemoveAllDownloads(mCurrentPrivateDownloads);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv2, rv2);
return NS_OK;
}
nsresult
nsDownloadManager::RemoveAllDownloads(nsCOMArray<nsDownload>& aDownloads)
{
nsresult rv = NS_OK;
for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) {
nsRefPtr<nsDownload> dl = aDownloads[0];
nsresult result = NS_OK;
if (dl->IsPaused() && GetQuitBehavior() != QUIT_AND_CANCEL)
aDownloads.RemoveObject(dl);
else
result = dl->Cancel();
// Track the failure, but don't miss out on other downloads
if (NS_FAILED(result))
rv = result;
}
return rv;
}
nsresult
nsDownloadManager::RemoveDownloadsForURI(mozIStorageStatement* aStatement, nsIURI *aURI)
{
mozStorageStatementScoper scope(aStatement);
nsAutoCString source;
nsresult rv = aURI->GetSpec(source);
NS_ENSURE_SUCCESS(rv, rv);
rv = aStatement->BindUTF8StringByName(
NS_LITERAL_CSTRING("source"), source);
NS_ENSURE_SUCCESS(rv, rv);
bool hasMore = false;
nsAutoTArray<nsCString, 4> downloads;
// Get all the downloads that match the provided URI
while (NS_SUCCEEDED(aStatement->ExecuteStep(&hasMore)) &&
hasMore) {
nsAutoCString downloadGuid;
rv = aStatement->GetUTF8String(0, downloadGuid);
NS_ENSURE_SUCCESS(rv, rv);
downloads.AppendElement(downloadGuid);
}
// Remove each download ignoring any failure so we reach other downloads
for (int32_t i = downloads.Length(); --i >= 0; )
(void)RemoveDownload(downloads[i]);
return NS_OK;
}
void // static
nsDownloadManager::ResumeOnWakeCallback(nsITimer *aTimer, void *aClosure)
{
// Resume the downloads that were set to autoResume
nsDownloadManager *dlMgr = static_cast<nsDownloadManager *>(aClosure);
(void)dlMgr->ResumeAllDownloads(false);
}
already_AddRefed<mozIStorageConnection>
nsDownloadManager::GetFileDBConnection(nsIFile *dbFile) const
{
NS_ASSERTION(dbFile, "GetFileDBConnection called with an invalid nsIFile");
nsCOMPtr<mozIStorageService> storage =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
NS_ENSURE_TRUE(storage, nullptr);
nsCOMPtr<mozIStorageConnection> conn;
nsresult rv = storage->OpenDatabase(dbFile, getter_AddRefs(conn));
if (rv == NS_ERROR_FILE_CORRUPTED) {
// delete and try again, since we don't care so much about losing a user's
// download history
rv = dbFile->Remove(false);
NS_ENSURE_SUCCESS(rv, nullptr);
rv = storage->OpenDatabase(dbFile, getter_AddRefs(conn));
}
NS_ENSURE_SUCCESS(rv, nullptr);
return conn.forget();
}
already_AddRefed<mozIStorageConnection>
nsDownloadManager::GetPrivateDBConnection() const
{
nsCOMPtr<mozIStorageService> storage =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
NS_ENSURE_TRUE(storage, nullptr);
nsCOMPtr<mozIStorageConnection> conn;
nsresult rv = storage->OpenSpecialDatabase("memory", getter_AddRefs(conn));
NS_ENSURE_SUCCESS(rv, nullptr);
return conn.forget();
}
void
nsDownloadManager::CloseAllDBs()
{
CloseDB(mDBConn, mUpdateDownloadStatement, mGetIdsForURIStatement);
CloseDB(mPrivateDBConn, mUpdatePrivateDownloadStatement, mGetPrivateIdsForURIStatement);
}
void
nsDownloadManager::CloseDB(mozIStorageConnection* aDBConn,
mozIStorageStatement* aUpdateStmt,
mozIStorageStatement* aGetIdsStmt)
{
DebugOnly<nsresult> rv = aGetIdsStmt->Finalize();
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = aUpdateStmt->Finalize();
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = aDBConn->AsyncClose(nullptr);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
static nsresult
InitSQLFunctions(mozIStorageConnection* aDBConn)
{
nsresult rv = mozilla::downloads::GenerateGUIDFunction::create(aDBConn);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
nsDownloadManager::InitPrivateDB()
{
bool ready = false;
if (mPrivateDBConn && NS_SUCCEEDED(mPrivateDBConn->GetConnectionReady(&ready)) && ready)
CloseDB(mPrivateDBConn, mUpdatePrivateDownloadStatement, mGetPrivateIdsForURIStatement);
mPrivateDBConn = GetPrivateDBConnection();
if (!mPrivateDBConn)
return NS_ERROR_NOT_AVAILABLE;
nsresult rv = InitSQLFunctions(mPrivateDBConn);
NS_ENSURE_SUCCESS(rv, rv);
rv = CreateTable(mPrivateDBConn);
NS_ENSURE_SUCCESS(rv, rv);
rv = InitStatements(mPrivateDBConn, getter_AddRefs(mUpdatePrivateDownloadStatement),
getter_AddRefs(mGetPrivateIdsForURIStatement));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
nsDownloadManager::InitFileDB()
{
nsresult rv;
nsCOMPtr<nsIFile> dbFile;
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(dbFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = dbFile->Append(DM_DB_NAME);
NS_ENSURE_SUCCESS(rv, rv);
bool ready = false;
if (mDBConn && NS_SUCCEEDED(mDBConn->GetConnectionReady(&ready)) && ready)
CloseDB(mDBConn, mUpdateDownloadStatement, mGetIdsForURIStatement);
mDBConn = GetFileDBConnection(dbFile);
NS_ENSURE_TRUE(mDBConn, NS_ERROR_NOT_AVAILABLE);
rv = InitSQLFunctions(mDBConn);
NS_ENSURE_SUCCESS(rv, rv);
bool tableExists;
rv = mDBConn->TableExists(NS_LITERAL_CSTRING("moz_downloads"), &tableExists);
NS_ENSURE_SUCCESS(rv, rv);
if (!tableExists) {
rv = CreateTable(mDBConn);
NS_ENSURE_SUCCESS(rv, rv);
// We're done with the initialization now and can skip the remaining
// upgrading logic.
return NS_OK;
}
// Checking the database schema now
int32_t schemaVersion;
rv = mDBConn->GetSchemaVersion(&schemaVersion);
NS_ENSURE_SUCCESS(rv, rv);
// Changing the database? Be sure to do these two things!
// 1) Increment DM_SCHEMA_VERSION
// 2) Implement the proper downgrade/upgrade code for the current version
switch (schemaVersion) {
// Upgrading
// Every time you increment the database schema, you need to implement
// the upgrading code from the previous version to the new one.
// Also, don't forget to make a unit test to test your upgrading code!
case 1: // Drop a column (iconURL) from the database (bug 385875)
{
// Safely wrap this in a transaction so we don't hose the whole DB
mozStorageTransaction safeTransaction(mDBConn, true);
// Create a temporary table that will store the existing records
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TEMPORARY TABLE moz_downloads_backup ("
"id INTEGER PRIMARY KEY, "
"name TEXT, "
"source TEXT, "
"target TEXT, "
"startTime INTEGER, "
"endTime INTEGER, "
"state INTEGER"
")"));
NS_ENSURE_SUCCESS(rv, rv);
// Insert into a temporary table
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT INTO moz_downloads_backup "
"SELECT id, name, source, target, startTime, endTime, state "
"FROM moz_downloads"));
NS_ENSURE_SUCCESS(rv, rv);
// Drop the old table
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE moz_downloads"));
NS_ENSURE_SUCCESS(rv, rv);
// Now recreate it with this schema version
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE moz_downloads ("
"id INTEGER PRIMARY KEY, "
"name TEXT, "
"source TEXT, "
"target TEXT, "
"startTime INTEGER, "
"endTime INTEGER, "
"state INTEGER"
")"));
NS_ENSURE_SUCCESS(rv, rv);
// Insert the data back into it
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT INTO moz_downloads "
"SELECT id, name, source, target, startTime, endTime, state "
"FROM moz_downloads_backup"));
NS_ENSURE_SUCCESS(rv, rv);
// And drop our temporary table
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE moz_downloads_backup"));
NS_ENSURE_SUCCESS(rv, rv);
// Finally, update the schemaVersion variable and the database schema
schemaVersion = 2;
rv = mDBConn->SetSchemaVersion(schemaVersion);
NS_ENSURE_SUCCESS(rv, rv);
}
// Fallthrough to the next upgrade
case 2: // Add referrer column to the database
{
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_downloads "
"ADD COLUMN referrer TEXT"));
NS_ENSURE_SUCCESS(rv, rv);
// Finally, update the schemaVersion variable and the database schema
schemaVersion = 3;
rv = mDBConn->SetSchemaVersion(schemaVersion);
NS_ENSURE_SUCCESS(rv, rv);
}
// Fallthrough to the next upgrade
case 3: // This version adds a column to the database (entityID)
{
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_downloads "
"ADD COLUMN entityID TEXT"));
NS_ENSURE_SUCCESS(rv, rv);
// Finally, update the schemaVersion variable and the database schema
schemaVersion = 4;
rv = mDBConn->SetSchemaVersion(schemaVersion);
NS_ENSURE_SUCCESS(rv, rv);
}
// Fallthrough to the next upgrade
case 4: // This version adds a column to the database (tempPath)
{
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_downloads "
"ADD COLUMN tempPath TEXT"));
NS_ENSURE_SUCCESS(rv, rv);
// Finally, update the schemaVersion variable and the database schema
schemaVersion = 5;
rv = mDBConn->SetSchemaVersion(schemaVersion);
NS_ENSURE_SUCCESS(rv, rv);
}
// Fallthrough to the next upgrade
case 5: // This version adds two columns for tracking transfer progress
{
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_downloads "
"ADD COLUMN currBytes INTEGER NOT NULL DEFAULT 0"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_downloads "
"ADD COLUMN maxBytes INTEGER NOT NULL DEFAULT -1"));
NS_ENSURE_SUCCESS(rv, rv);
// Finally, update the schemaVersion variable and the database schema
schemaVersion = 6;
rv = mDBConn->SetSchemaVersion(schemaVersion);
NS_ENSURE_SUCCESS(rv, rv);
}
// Fallthrough to the next upgrade
case 6: // This version adds three columns to DB (MIME type related info)
{
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_downloads "
"ADD COLUMN mimeType TEXT"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_downloads "
"ADD COLUMN preferredApplication TEXT"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_downloads "
"ADD COLUMN preferredAction INTEGER NOT NULL DEFAULT 0"));
NS_ENSURE_SUCCESS(rv, rv);
// Finally, update the schemaVersion variable and the database schema
schemaVersion = 7;
rv = mDBConn->SetSchemaVersion(schemaVersion);
NS_ENSURE_SUCCESS(rv, rv);
}
// Fallthrough to next upgrade
case 7: // This version adds a column to remember to auto-resume downloads
{
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_downloads "
"ADD COLUMN autoResume INTEGER NOT NULL DEFAULT 0"));
NS_ENSURE_SUCCESS(rv, rv);
// Finally, update the schemaVersion variable and the database schema
schemaVersion = 8;
rv = mDBConn->SetSchemaVersion(schemaVersion);
NS_ENSURE_SUCCESS(rv, rv);
}
// Fallthrough to the next upgrade
// Warning: schema versions >=8 must take into account that they can
// be operating on schemas from unknown, future versions that have
// been downgraded. Operations such as adding columns may fail,
// since the column may already exist.
case 8: // This version adds a column for GUIDs
{
bool exists;
rv = mDBConn->IndexExists(NS_LITERAL_CSTRING("moz_downloads_guid_uniqueindex"),
&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists) {
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_downloads ADD COLUMN guid TEXT"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE UNIQUE INDEX moz_downloads_guid_uniqueindex ON moz_downloads (guid)"));
NS_ENSURE_SUCCESS(rv, rv);
}
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE moz_downloads SET guid = GENERATE_GUID() WHERE guid ISNULL"));
NS_ENSURE_SUCCESS(rv, rv);
// Finally, update the database schema
schemaVersion = 9;
rv = mDBConn->SetSchemaVersion(schemaVersion);
NS_ENSURE_SUCCESS(rv, rv);
}
// Fallthrough to the next upgrade
// Extra sanity checking for developers
#ifndef DEBUG
case DM_SCHEMA_VERSION:
#endif
break;
case 0:
{
NS_WARNING("Could not get download database's schema version!");
// The table may still be usable - someone may have just messed with the
// schema version, so let's just treat this like a downgrade and verify
// that the needed columns are there. If they aren't there, we'll drop
// the table anyway.
rv = mDBConn->SetSchemaVersion(DM_SCHEMA_VERSION);
NS_ENSURE_SUCCESS(rv, rv);
}
// Fallthrough to downgrade check
// Downgrading
// If columns have been added to the table, we can still use the ones we
// understand safely. If columns have been deleted or alterd, we just
// drop the table and start from scratch. If you change how a column
// should be interpreted, make sure you also change its name so this
// check will catch it.
default:
{
nsCOMPtr<mozIStorageStatement> stmt;
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT id, name, source, target, tempPath, startTime, endTime, state, "
"referrer, entityID, currBytes, maxBytes, mimeType, "
"preferredApplication, preferredAction, autoResume, guid "
"FROM moz_downloads"), getter_AddRefs(stmt));
if (NS_SUCCEEDED(rv)) {
// We have a database that contains all of the elements that make up
// the latest known schema. Reset the version to force an upgrade
// path if this downgraded database is used in a later version.
mDBConn->SetSchemaVersion(DM_SCHEMA_VERSION);
break;
}
// if the statement fails, that means all the columns were not there.
// First we backup the database
nsCOMPtr<mozIStorageService> storage =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
NS_ENSURE_TRUE(storage, NS_ERROR_NOT_AVAILABLE);
nsCOMPtr<nsIFile> backup;
rv = storage->BackupDatabaseFile(dbFile, DM_DB_CORRUPT_FILENAME, nullptr,
getter_AddRefs(backup));
NS_ENSURE_SUCCESS(rv, rv);
// Then we dump it
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE moz_downloads"));
NS_ENSURE_SUCCESS(rv, rv);
rv = CreateTable(mDBConn);
NS_ENSURE_SUCCESS(rv, rv);
}
break;
}
return NS_OK;
}
nsresult
nsDownloadManager::CreateTable(mozIStorageConnection* aDBConn)
{
nsresult rv = aDBConn->SetSchemaVersion(DM_SCHEMA_VERSION);
if (NS_FAILED(rv)) return rv;
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE moz_downloads ("
"id INTEGER PRIMARY KEY, "
"name TEXT, "
"source TEXT, "
"target TEXT, "
"tempPath TEXT, "
"startTime INTEGER, "
"endTime INTEGER, "
"state INTEGER, "
"referrer TEXT, "
"entityID TEXT, "
"currBytes INTEGER NOT NULL DEFAULT 0, "
"maxBytes INTEGER NOT NULL DEFAULT -1, "
"mimeType TEXT, "
"preferredApplication TEXT, "
"preferredAction INTEGER NOT NULL DEFAULT 0, "
"autoResume INTEGER NOT NULL DEFAULT 0, "
"guid TEXT"
")"));
if (NS_FAILED(rv)) return rv;
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE UNIQUE INDEX moz_downloads_guid_uniqueindex "
"ON moz_downloads(guid)"));
return rv;
}
nsresult
nsDownloadManager::RestoreDatabaseState()
{
// Restore downloads that were in a scanning state. We can assume that they
// have been dealt with by the virus scanner
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_downloads "
"SET state = :state "
"WHERE state = :state_cond"), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_FINISHED);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state_cond"), nsIDownloadManager::DOWNLOAD_SCANNING);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Convert supposedly-active downloads into downloads that should auto-resume
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_downloads "
"SET autoResume = :autoResume "
"WHERE state = :notStarted "
"OR state = :queued "
"OR state = :downloading"), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), nsDownload::AUTO_RESUME);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("notStarted"), nsIDownloadManager::DOWNLOAD_NOTSTARTED);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("queued"), nsIDownloadManager::DOWNLOAD_QUEUED);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("downloading"), nsIDownloadManager::DOWNLOAD_DOWNLOADING);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Switch any download that is supposed to automatically resume and is in a
// finished state to *not* automatically resume. See Bug 409179 for details.
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_downloads "
"SET autoResume = :autoResume "
"WHERE state = :state "
"AND autoResume = :autoResume_cond"),
getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), nsDownload::DONT_RESUME);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_FINISHED);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume_cond"), nsDownload::AUTO_RESUME);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
nsDownloadManager::RestoreActiveDownloads()
{
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT id "
"FROM moz_downloads "
"WHERE (state = :state AND LENGTH(entityID) > 0) "
"OR autoResume != :autoResume"), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_PAUSED);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), nsDownload::DONT_RESUME);
NS_ENSURE_SUCCESS(rv, rv);
nsresult retVal = NS_OK;
bool hasResults;
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResults)) && hasResults) {
nsRefPtr<nsDownload> dl;
// Keep trying to add even if we fail one, but make sure to return failure.
// Additionally, be careful to not call anything that tries to change the
// database because we're iterating over a live statement.
if (NS_FAILED(GetDownloadFromDB(stmt->AsInt32(0), getter_AddRefs(dl))) ||
NS_FAILED(AddToCurrentDownloads(dl)))
retVal = NS_ERROR_FAILURE;
}
// Try to resume only the downloads that should auto-resume
rv = ResumeAllDownloads(false);
NS_ENSURE_SUCCESS(rv, rv);
return retVal;
}
int64_t
nsDownloadManager::AddDownloadToDB(const nsAString &aName,
const nsACString &aSource,
const nsACString &aTarget,
const nsAString &aTempPath,
int64_t aStartTime,
int64_t aEndTime,
const nsACString &aMimeType,
const nsACString &aPreferredApp,
nsHandlerInfoAction aPreferredAction,
bool aPrivate,
nsACString& aNewGUID)
{
mozIStorageConnection* dbConn = aPrivate ? mPrivateDBConn : mDBConn;
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
"INSERT INTO moz_downloads "
"(name, source, target, tempPath, startTime, endTime, state, "
"mimeType, preferredApplication, preferredAction, guid) VALUES "
"(:name, :source, :target, :tempPath, :startTime, :endTime, :state, "
":mimeType, :preferredApplication, :preferredAction, :guid)"),
getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, 0);
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName);
NS_ENSURE_SUCCESS(rv, 0);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("source"), aSource);
NS_ENSURE_SUCCESS(rv, 0);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("target"), aTarget);
NS_ENSURE_SUCCESS(rv, 0);
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("tempPath"), aTempPath);
NS_ENSURE_SUCCESS(rv, 0);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startTime"), aStartTime);
NS_ENSURE_SUCCESS(rv, 0);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("endTime"), aEndTime);
NS_ENSURE_SUCCESS(rv, 0);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_NOTSTARTED);
NS_ENSURE_SUCCESS(rv, 0);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("mimeType"), aMimeType);
NS_ENSURE_SUCCESS(rv, 0);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("preferredApplication"), aPreferredApp);
NS_ENSURE_SUCCESS(rv, 0);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("preferredAction"), aPreferredAction);
NS_ENSURE_SUCCESS(rv, 0);
nsAutoCString guid;
rv = GenerateGUID(guid);
NS_ENSURE_SUCCESS(rv, 0);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
NS_ENSURE_SUCCESS(rv, 0);
bool hasMore;
rv = stmt->ExecuteStep(&hasMore); // we want to keep our lock
NS_ENSURE_SUCCESS(rv, 0);
int64_t id = 0;
rv = dbConn->GetLastInsertRowID(&id);
NS_ENSURE_SUCCESS(rv, 0);
aNewGUID = guid;
// lock on DB from statement will be released once we return
return id;
}
nsresult
nsDownloadManager::InitDB()
{
nsresult rv = InitPrivateDB();
NS_ENSURE_SUCCESS(rv, rv);
rv = InitFileDB();
NS_ENSURE_SUCCESS(rv, rv);
rv = InitStatements(mDBConn, getter_AddRefs(mUpdateDownloadStatement),
getter_AddRefs(mGetIdsForURIStatement));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
nsDownloadManager::InitStatements(mozIStorageConnection* aDBConn,
mozIStorageStatement** aUpdateStatement,
mozIStorageStatement** aGetIdsStatement)
{
nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_downloads "
"SET tempPath = :tempPath, startTime = :startTime, endTime = :endTime, "
"state = :state, referrer = :referrer, entityID = :entityID, "
"currBytes = :currBytes, maxBytes = :maxBytes, autoResume = :autoResume "
"WHERE id = :id"), aUpdateStatement);
NS_ENSURE_SUCCESS(rv, rv);
rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT guid "
"FROM moz_downloads "
"WHERE source = :source"), aGetIdsStatement);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
nsDownloadManager::Init()
{
// Clean up any old downloads.rdf files from before Firefox 3
{
nsCOMPtr<nsIFile> oldDownloadsFile;
bool fileExists;
if (NS_SUCCEEDED(NS_GetSpecialDirectory(NS_APP_DOWNLOADS_50_FILE,
getter_AddRefs(oldDownloadsFile))) &&
NS_SUCCEEDED(oldDownloadsFile->Exists(&fileExists)) &&
fileExists) {
(void)oldDownloadsFile->Remove(false);
}
}
mObserverService = mozilla::services::GetObserverService();
if (!mObserverService)
return NS_ERROR_FAILURE;
nsCOMPtr<nsIStringBundleService> bundleService =
mozilla::services::GetStringBundleService();
if (!bundleService)
return NS_ERROR_FAILURE;
nsresult rv = InitDB();
NS_ENSURE_SUCCESS(rv, rv);
rv = bundleService->CreateBundle(DOWNLOAD_MANAGER_BUNDLE,
getter_AddRefs(mBundle));
NS_ENSURE_SUCCESS(rv, rv);
#ifdef DOWNLOAD_SCANNER
mScanner = new nsDownloadScanner();
if (!mScanner)
return NS_ERROR_OUT_OF_MEMORY;
rv = mScanner->Init();
if (NS_FAILED(rv)) {
delete mScanner;
mScanner = nullptr;
}
#endif
// Do things *after* initializing various download manager properties such as
// restoring downloads to a consistent state
rv = RestoreDatabaseState();
NS_ENSURE_SUCCESS(rv, rv);
rv = RestoreActiveDownloads();
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to restore all active downloads");
nsCOMPtr<nsINavHistoryService> history =
do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
(void)mObserverService->NotifyObservers(
static_cast<nsIDownloadManager *>(this),
"download-manager-initialized",
nullptr);
// The following AddObserver calls must be the last lines in this function,
// because otherwise, this function may fail (and thus, this object would be not
// completely initialized), but the observerservice would still keep a reference
// to us and notify us about shutdown, which may cause crashes.
// failure to add an observer is not critical
(void)mObserverService->AddObserver(this, "quit-application", true);
(void)mObserverService->AddObserver(this, "quit-application-requested", true);
(void)mObserverService->AddObserver(this, "offline-requested", true);
(void)mObserverService->AddObserver(this, "sleep_notification", true);
(void)mObserverService->AddObserver(this, "wake_notification", true);
(void)mObserverService->AddObserver(this, "suspend_process_notification", true);
(void)mObserverService->AddObserver(this, "resume_process_notification", true);
(void)mObserverService->AddObserver(this, "profile-before-change", true);
(void)mObserverService->AddObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC, true);
(void)mObserverService->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, true);
#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING
(void)mObserverService->AddObserver(this, NS_PRIVATE_BROWSING_REQUEST_TOPIC, true);
(void)mObserverService->AddObserver(this, NS_PRIVATE_BROWSING_SWITCH_TOPIC, true);
#endif
(void)mObserverService->AddObserver(this, "last-pb-context-exited", true);
(void)mObserverService->AddObserver(this, "last-pb-context-exiting", true);
if (history)
(void)history->AddObserver(this, true);
return NS_OK;
}
int32_t
nsDownloadManager::GetRetentionBehavior()
{
// We use 0 as the default, which is "remove when done"
nsresult rv;
nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, 0);
int32_t val;
rv = pref->GetIntPref(PREF_BDM_RETENTION, &val);
NS_ENSURE_SUCCESS(rv, 0);
// Allow the Downloads Panel to change the retention behavior. We do this to
// allow proper migration to the new feature when using the same profile on
// multiple versions of the product (bug 697678). Implementation note: in
// order to allow observers to change the retention value, we have to pass an
// object in the aSubject parameter, we cannot use aData for that.
nsCOMPtr<nsISupportsPRInt32> retentionBehavior =
do_CreateInstance(NS_SUPPORTS_PRINT32_CONTRACTID);
retentionBehavior->SetData(val);
(void)mObserverService->NotifyObservers(retentionBehavior,
"download-manager-change-retention",
nullptr);
retentionBehavior->GetData(&val);
return val;
}
enum nsDownloadManager::QuitBehavior
nsDownloadManager::GetQuitBehavior()
{
// We use 0 as the default, which is "remember and resume the download"
nsresult rv;
nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, QUIT_AND_RESUME);
int32_t val;
rv = pref->GetIntPref(PREF_BDM_QUITBEHAVIOR, &val);
NS_ENSURE_SUCCESS(rv, QUIT_AND_RESUME);
switch (val) {
case 1:
return QUIT_AND_PAUSE;
case 2:
return QUIT_AND_CANCEL;
default:
return QUIT_AND_RESUME;
}
}
// Using a globally-unique GUID, search all databases (both private and public).
// A return value of NS_ERROR_NOT_AVAILABLE means no download with the given GUID
// could be found, either private or public.
nsresult
nsDownloadManager::GetDownloadFromDB(const nsACString& aGUID, nsDownload **retVal)
{
MOZ_ASSERT(!FindDownload(aGUID),
"If it is a current download, you should not call this method!");
nsDependentCString query = NS_LITERAL_CSTRING(
"SELECT id, state, startTime, source, target, tempPath, name, referrer, "
"entityID, currBytes, maxBytes, mimeType, preferredAction, "
"preferredApplication, autoResume, guid "
"FROM moz_downloads "
"WHERE guid = :guid");
// First, let's query the database and see if it even exists
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mDBConn->CreateStatement(query, getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
NS_ENSURE_SUCCESS(rv, rv);
rv = GetDownloadFromDB(mDBConn, stmt, retVal);
// If the download cannot be found in the public database, try again
// in the private one. Otherwise, return whatever successful result
// or failure obtained from the public database.
if (rv == NS_ERROR_NOT_AVAILABLE) {
rv = mPrivateDBConn->CreateStatement(query, getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
NS_ENSURE_SUCCESS(rv, rv);
rv = GetDownloadFromDB(mPrivateDBConn, stmt, retVal);
// Only if it still cannot be found do we report the failure.
if (rv == NS_ERROR_NOT_AVAILABLE) {
*retVal = nullptr;
}
}
return rv;
}
nsresult
nsDownloadManager::GetDownloadFromDB(uint32_t aID, nsDownload **retVal)
{
if (gUsePerWindowPrivateBrowsing)
NS_WARNING("Using integer IDs without compat mode enabled");
MOZ_ASSERT(!FindDownload(aID),
"If it is a current download, you should not call this method!");
nsCOMPtr<mozIStorageConnection> dbConn =
IsInGlobalPrivateBrowsing() ? mPrivateDBConn : mDBConn;
// First, let's query the database and see if it even exists
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT id, state, startTime, source, target, tempPath, name, referrer, "
"entityID, currBytes, maxBytes, mimeType, preferredAction, "
"preferredApplication, autoResume, guid "
"FROM moz_downloads "
"WHERE id = :id"), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aID);
NS_ENSURE_SUCCESS(rv, rv);
return GetDownloadFromDB(dbConn, stmt, retVal);
}
nsresult
nsDownloadManager::GetDownloadFromDB(mozIStorageConnection* aDBConn,
mozIStorageStatement* stmt,
nsDownload **retVal)
{
bool hasResults = false;
nsresult rv = stmt->ExecuteStep(&hasResults);
if (NS_FAILED(rv) || !hasResults)
return NS_ERROR_NOT_AVAILABLE;
// We have a download, so lets create it
nsRefPtr<nsDownload> dl = new nsDownload();
if (!dl)
return NS_ERROR_OUT_OF_MEMORY;
dl->mPrivate = aDBConn == mPrivateDBConn;
dl->mDownloadManager = this;
int32_t i = 0;
// Setting all properties of the download now
dl->mCancelable = nullptr;
dl->mID = stmt->AsInt64(i++);
dl->mDownloadState = stmt->AsInt32(i++);
dl->mStartTime = stmt->AsInt64(i++);
nsCString source;
stmt->GetUTF8String(i++, source);
rv = NS_NewURI(getter_AddRefs(dl->mSource), source);
NS_ENSURE_SUCCESS(rv, rv);
nsCString target;
stmt->GetUTF8String(i++, target);
rv = NS_NewURI(getter_AddRefs(dl->mTarget), target);
NS_ENSURE_SUCCESS(rv, rv);
nsString tempPath;
stmt->GetString(i++, tempPath);
if (!tempPath.IsEmpty()) {
rv = NS_NewLocalFile(tempPath, true, getter_AddRefs(dl->mTempFile));
NS_ENSURE_SUCCESS(rv, rv);
}
stmt->GetString(i++, dl->mDisplayName);
nsCString referrer;
rv = stmt->GetUTF8String(i++, referrer);
if (NS_SUCCEEDED(rv) && !referrer.IsEmpty()) {
rv = NS_NewURI(getter_AddRefs(dl->mReferrer), referrer);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = stmt->GetUTF8String(i++, dl->mEntityID);
NS_ENSURE_SUCCESS(rv, rv);
int64_t currBytes = stmt->AsInt64(i++);
int64_t maxBytes = stmt->AsInt64(i++);
dl->SetProgressBytes(currBytes, maxBytes);
// Build mMIMEInfo only if the mimeType in DB is not empty
nsAutoCString mimeType;
rv = stmt->GetUTF8String(i++, mimeType);
NS_ENSURE_SUCCESS(rv, rv);
if (!mimeType.IsEmpty()) {
nsCOMPtr<nsIMIMEService> mimeService =
do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(),
getter_AddRefs(dl->mMIMEInfo));
NS_ENSURE_SUCCESS(rv, rv);
nsHandlerInfoAction action = stmt->AsInt32(i++);
rv = dl->mMIMEInfo->SetPreferredAction(action);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString persistentDescriptor;
rv = stmt->GetUTF8String(i++, persistentDescriptor);
NS_ENSURE_SUCCESS(rv, rv);
if (!persistentDescriptor.IsEmpty()) {
nsCOMPtr<nsILocalHandlerApp> handler =
do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> localExecutable;
rv = NS_NewNativeLocalFile(EmptyCString(), false,
getter_AddRefs(localExecutable));
NS_ENSURE_SUCCESS(rv, rv);
rv = localExecutable->SetPersistentDescriptor(persistentDescriptor);
NS_ENSURE_SUCCESS(rv, rv);
rv = handler->SetExecutable(localExecutable);
NS_ENSURE_SUCCESS(rv, rv);
rv = dl->mMIMEInfo->SetPreferredApplicationHandler(handler);
NS_ENSURE_SUCCESS(rv, rv);
}
} else {
// Compensate for the i++s skipped in the true block
i += 2;
}
dl->mAutoResume =
static_cast<enum nsDownload::AutoResume>(stmt->AsInt32(i++));
rv = stmt->GetUTF8String(i++, dl->mGUID);
NS_ENSURE_SUCCESS(rv, rv);
// Handle situations where we load a download from a database that has been
// used in an older version and not gone through the upgrade path (ie. it
// contains empty GUID entries).
if (dl->mGUID.IsEmpty()) {
rv = GenerateGUID(dl->mGUID);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageStatement> stmt;
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_downloads SET guid = :guid "
"WHERE id = :id"),
getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), dl->mGUID);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), dl->mID);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
}
// Addrefing and returning
NS_ADDREF(*retVal = dl);
return NS_OK;
}
nsresult
nsDownloadManager::AddToCurrentDownloads(nsDownload *aDl)
{
nsCOMArray<nsDownload>& currentDownloads =
aDl->mPrivate ? mCurrentPrivateDownloads : mCurrentDownloads;
if (!currentDownloads.AppendObject(aDl))
return NS_ERROR_OUT_OF_MEMORY;
aDl->mDownloadManager = this;
return NS_OK;
}
void
nsDownloadManager::SendEvent(nsDownload *aDownload, const char *aTopic)
{
(void)mObserverService->NotifyObservers(aDownload, aTopic, nullptr);
}
////////////////////////////////////////////////////////////////////////////////
//// nsIDownloadManager
NS_IMETHODIMP
nsDownloadManager::GetActivePrivateDownloadCount(int32_t* aResult)
{
*aResult = mCurrentPrivateDownloads.Count();
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::GetActiveDownloadCount(int32_t *aResult)
{
*aResult = IsInGlobalPrivateBrowsing() ?
mCurrentPrivateDownloads.Count() : mCurrentDownloads.Count();
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::GetActiveDownloads(nsISimpleEnumerator **aResult)
{
return NS_NewArrayEnumerator(aResult,
IsInGlobalPrivateBrowsing() ?
mCurrentPrivateDownloads : mCurrentDownloads);
}
NS_IMETHODIMP
nsDownloadManager::GetActivePrivateDownloads(nsISimpleEnumerator **aResult)
{
return NS_NewArrayEnumerator(aResult, mCurrentPrivateDownloads);
}
/**
* For platforms where helper apps use the downloads directory (i.e. mobile),
* this should be kept in sync with nsExternalHelperAppService.cpp
*/
NS_IMETHODIMP
nsDownloadManager::GetDefaultDownloadsDirectory(nsIFile **aResult)
{
nsCOMPtr<nsIFile> downloadDir;
nsresult rv;
nsCOMPtr<nsIProperties> dirService =
do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// OSX 10.4:
// Desktop
// OSX 10.5:
// User download directory
// Vista:
// Downloads
// XP/2K:
// My Documents/Downloads
// Linux:
// XDG user dir spec, with a fallback to Home/Downloads
nsXPIDLString folderName;
mBundle->GetStringFromName(NS_LITERAL_STRING("downloadsFolder").get(),
getter_Copies(folderName));
#if defined (XP_MACOSX)
rv = dirService->Get(NS_OSX_DEFAULT_DOWNLOAD_DIR,
NS_GET_IID(nsIFile),
getter_AddRefs(downloadDir));
NS_ENSURE_SUCCESS(rv, rv);
#elif defined(XP_WIN)
rv = dirService->Get(NS_WIN_DEFAULT_DOWNLOAD_DIR,
NS_GET_IID(nsIFile),
getter_AddRefs(downloadDir));
NS_ENSURE_SUCCESS(rv, rv);
// Check the os version
nsCOMPtr<nsIPropertyBag2> infoService =
do_GetService(NS_SYSTEMINFO_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
int32_t version;
NS_NAMED_LITERAL_STRING(osVersion, "version");
rv = infoService->GetPropertyAsInt32(osVersion, &version);
NS_ENSURE_SUCCESS(rv, rv);
if (version < 6) { // XP/2K
// First get "My Documents"
rv = dirService->Get(NS_WIN_PERSONAL_DIR,
NS_GET_IID(nsIFile),
getter_AddRefs(downloadDir));
NS_ENSURE_SUCCESS(rv, rv);
rv = downloadDir->Append(folderName);
NS_ENSURE_SUCCESS(rv, rv);
// This could be the first time we are creating the downloads folder in My
// Documents, so make sure it exists.
bool exists;
rv = downloadDir->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists) {
rv = downloadDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
NS_ENSURE_SUCCESS(rv, rv);
}
}
#elif defined(XP_UNIX)
#if defined(MOZ_PLATFORM_MAEMO)
// As maemo does not follow the XDG "standard" (as usually desktop
// Linux distros do) neither has a working $HOME/Desktop folder
// for us to fallback into, "$HOME/MyDocs/.documents/" is the folder
// we found most apropriate to be the default target folder for downloads
// on the platform.
rv = dirService->Get(NS_UNIX_XDG_DOCUMENTS_DIR,
NS_GET_IID(nsIFile),
getter_AddRefs(downloadDir));
#elif defined(MOZ_WIDGET_ANDROID)
// Android doesn't have a $HOME directory, and by default we only have
// write access to /data/data/org.mozilla.{$APP} and /sdcard
char* downloadDirPath = getenv("DOWNLOADS_DIRECTORY");
if (downloadDirPath) {
rv = NS_NewNativeLocalFile(nsDependentCString(downloadDirPath),
true, getter_AddRefs(downloadDir));
NS_ENSURE_SUCCESS(rv, rv);
}
else {
rv = NS_ERROR_FAILURE;
}
#else
rv = dirService->Get(NS_UNIX_DEFAULT_DOWNLOAD_DIR,
NS_GET_IID(nsIFile),
getter_AddRefs(downloadDir));
// fallback to Home/Downloads
if (NS_FAILED(rv)) {
rv = dirService->Get(NS_UNIX_HOME_DIR,
NS_GET_IID(nsIFile),
getter_AddRefs(downloadDir));
NS_ENSURE_SUCCESS(rv, rv);
rv = downloadDir->Append(folderName);
NS_ENSURE_SUCCESS(rv, rv);
}
#endif
#else
rv = dirService->Get(NS_OS_HOME_DIR,
NS_GET_IID(nsIFile),
getter_AddRefs(downloadDir));
NS_ENSURE_SUCCESS(rv, rv);
rv = downloadDir->Append(folderName);
NS_ENSURE_SUCCESS(rv, rv);
#endif
downloadDir.forget(aResult);
return NS_OK;
}
#define NS_BRANCH_DOWNLOAD "browser.download."
#define NS_PREF_FOLDERLIST "folderList"
#define NS_PREF_DIR "dir"
NS_IMETHODIMP
nsDownloadManager::GetUserDownloadsDirectory(nsIFile **aResult)
{
nsresult rv;
nsCOMPtr<nsIProperties> dirService =
do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrefService> prefService =
do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrefBranch> prefBranch;
rv = prefService->GetBranch(NS_BRANCH_DOWNLOAD,
getter_AddRefs(prefBranch));
NS_ENSURE_SUCCESS(rv, rv);
int32_t val;
rv = prefBranch->GetIntPref(NS_PREF_FOLDERLIST,
&val);
NS_ENSURE_SUCCESS(rv, rv);
switch(val) {
case 0: // Desktop
{
nsCOMPtr<nsIFile> downloadDir;
nsCOMPtr<nsIProperties> dirService =
do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = dirService->Get(NS_OS_DESKTOP_DIR,
NS_GET_IID(nsIFile),
getter_AddRefs(downloadDir));
NS_ENSURE_SUCCESS(rv, rv);
downloadDir.forget(aResult);
return NS_OK;
}
break;
case 1: // Downloads
return GetDefaultDownloadsDirectory(aResult);
case 2: // Custom
{
nsCOMPtr<nsIFile> customDirectory;
prefBranch->GetComplexValue(NS_PREF_DIR,
NS_GET_IID(nsIFile),
getter_AddRefs(customDirectory));
if (customDirectory) {
bool exists = false;
(void)customDirectory->Exists(&exists);
if (!exists) {
rv = customDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
if (NS_SUCCEEDED(rv)) {
customDirectory.forget(aResult);
return NS_OK;
}
// Create failed, so it still doesn't exist. Fall out and get the
// default downloads directory.
}
bool writable = false;
bool directory = false;
(void)customDirectory->IsWritable(&writable);
(void)customDirectory->IsDirectory(&directory);
if (exists && writable && directory) {
customDirectory.forget(aResult);
return NS_OK;
}
}
rv = GetDefaultDownloadsDirectory(aResult);
if (NS_SUCCEEDED(rv)) {
(void)prefBranch->SetComplexValue(NS_PREF_DIR,
NS_GET_IID(nsIFile),
*aResult);
}
return rv;
}
break;
}
return NS_ERROR_INVALID_ARG;
}
NS_IMETHODIMP
nsDownloadManager::AddDownload(DownloadType aDownloadType,
nsIURI *aSource,
nsIURI *aTarget,
const nsAString& aDisplayName,
nsIMIMEInfo *aMIMEInfo,
PRTime aStartTime,
nsIFile *aTempFile,
nsICancelable *aCancelable,
bool aIsPrivate,
nsIDownload **aDownload)
{
NS_ENSURE_ARG_POINTER(aSource);
NS_ENSURE_ARG_POINTER(aTarget);
NS_ENSURE_ARG_POINTER(aDownload);
nsresult rv;
#if !(defined(MOZ_PER_WINDOW_PRIVATE_BROWSING)) && defined(DEBUG)
// This code makes sure that in global private browsing mode, the flag
// passed to us matches the global PB mode. This can be removed when
// per-window private browsing has been turned on.
nsCOMPtr<nsIPrivateBrowsingService> pbService =
do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID);
if (pbService) {
bool inPrivateBrowsing = false;
if (NS_SUCCEEDED(pbService->GetPrivateBrowsingEnabled(&inPrivateBrowsing))) {
MOZ_ASSERT(inPrivateBrowsing == aIsPrivate);
}
}
#endif
// target must be on the local filesystem
nsCOMPtr<nsIFileURL> targetFileURL = do_QueryInterface(aTarget, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> targetFile;
rv = targetFileURL->GetFile(getter_AddRefs(targetFile));
NS_ENSURE_SUCCESS(rv, rv);
nsRefPtr<nsDownload> dl = new nsDownload();
if (!dl)
return NS_ERROR_OUT_OF_MEMORY;
// give our new nsIDownload some info so it's ready to go off into the world
dl->mTarget = aTarget;
dl->mSource = aSource;
dl->mTempFile = aTempFile;
dl->mPrivate = aIsPrivate;
dl->mDisplayName = aDisplayName;
if (dl->mDisplayName.IsEmpty())
targetFile->GetLeafName(dl->mDisplayName);
dl->mMIMEInfo = aMIMEInfo;
dl->SetStartTime(aStartTime == 0 ? PR_Now() : aStartTime);
// Creates a cycle that will be broken when the download finishes
dl->mCancelable = aCancelable;
// Adding to the DB
nsAutoCString source, target;
aSource->GetSpec(source);
aTarget->GetSpec(target);
// Track the temp file for exthandler downloads
nsAutoString tempPath;
if (aTempFile)
aTempFile->GetPath(tempPath);
// Break down MIMEInfo but don't panic if we can't get all the pieces - we
// can still download the file
nsAutoCString persistentDescriptor, mimeType;
nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk;
if (aMIMEInfo) {
(void)aMIMEInfo->GetType(mimeType);
nsCOMPtr<nsIHandlerApp> handlerApp;
(void)aMIMEInfo->GetPreferredApplicationHandler(getter_AddRefs(handlerApp));
nsCOMPtr<nsILocalHandlerApp> locHandlerApp = do_QueryInterface(handlerApp);
if (locHandlerApp) {
nsCOMPtr<nsIFile> executable;
(void)locHandlerApp->GetExecutable(getter_AddRefs(executable));
(void)executable->GetPersistentDescriptor(persistentDescriptor);
}
(void)aMIMEInfo->GetPreferredAction(&action);
}
int64_t id = AddDownloadToDB(dl->mDisplayName, source, target, tempPath,
dl->mStartTime, dl->mLastUpdate,
mimeType, persistentDescriptor, action,
dl->mPrivate, dl->mGUID /* outparam */);
NS_ENSURE_TRUE(id, NS_ERROR_FAILURE);
dl->mID = id;
rv = AddToCurrentDownloads(dl);
(void)dl->SetState(nsIDownloadManager::DOWNLOAD_QUEUED);
NS_ENSURE_SUCCESS(rv, rv);
#ifdef DOWNLOAD_SCANNER
if (mScanner) {
bool scan = true;
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (prefs) {
(void)prefs->GetBoolPref(PREF_BDM_SCANWHENDONE, &scan);
}
// We currently apply local security policy to downloads when we scan
// via windows all-in-one download security api. The CheckPolicy call
// below is a pre-emptive part of that process. So tie applying security
// zone policy settings when downloads are intiated to the same pref
// that triggers applying security zone policy settings after a download
// completes. (bug 504804)
if (scan) {
AVCheckPolicyState res = mScanner->CheckPolicy(aSource, aTarget);
if (res == AVPOLICY_BLOCKED) {
// This download will get deleted during a call to IAE's Save,
// so go ahead and mark it as blocked and avoid the download.
(void)CancelDownload(id);
(void)dl->SetState(nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY);
}
}
}
#endif
// Check with parental controls to see if file downloads
// are allowed for this user. If not allowed, cancel the
// download and mark its state as being blocked.
nsCOMPtr<nsIParentalControlsService> pc =
do_CreateInstance(NS_PARENTALCONTROLSSERVICE_CONTRACTID);
if (pc) {
bool enabled = false;
(void)pc->GetBlockFileDownloadsEnabled(&enabled);
if (enabled) {
(void)CancelDownload(id);
(void)dl->SetState(nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL);
}
// Log the event if required by pc settings.
bool logEnabled = false;
(void)pc->GetLoggingEnabled(&logEnabled);
if (logEnabled) {
(void)pc->Log(nsIParentalControlsService::ePCLog_FileDownload,
enabled,
aSource,
nullptr);
}
}
NS_ADDREF(*aDownload = dl);
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::GetDownload(uint32_t aID, nsIDownload **aDownloadItem)
{
if (gUsePerWindowPrivateBrowsing) {
NS_WARNING("Using integer IDs without compat mode enabled");
}
nsDownload *itm = FindDownload(aID);
nsRefPtr<nsDownload> dl;
if (!itm) {
nsresult rv = GetDownloadFromDB(aID, getter_AddRefs(dl));
NS_ENSURE_SUCCESS(rv, rv);
itm = dl.get();
}
NS_ADDREF(*aDownloadItem = itm);
return NS_OK;
}
namespace {
class AsyncResult : public nsRunnable
{
public:
AsyncResult(nsresult aStatus, nsIDownload* aResult,
nsIDownloadManagerResult* aCallback)
: mStatus(aStatus), mResult(aResult), mCallback(aCallback)
{
}
NS_IMETHOD Run()
{
mCallback->HandleResult(mStatus, mResult);
return NS_OK;
}
private:
nsresult mStatus;
nsCOMPtr<nsIDownload> mResult;
nsCOMPtr<nsIDownloadManagerResult> mCallback;
};
} // anonymous namespace
NS_IMETHODIMP
nsDownloadManager::GetDownloadByGUID(const nsACString& aGUID,
nsIDownloadManagerResult* aCallback)
{
nsDownload *itm = FindDownload(aGUID);
nsresult rv = NS_OK;
nsRefPtr<nsDownload> dl;
if (!itm) {
rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl));
itm = dl.get();
}
nsRefPtr<AsyncResult> runnable = new AsyncResult(rv, itm, aCallback);
NS_DispatchToMainThread(runnable);
return NS_OK;
}
bool
nsDownloadManager::IsInGlobalPrivateBrowsing()
{
bool inPrivateBrowsing = false;
#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING
nsCOMPtr<nsIPrivateBrowsingService> pbs =
do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID);
if (pbs) {
pbs->GetPrivateBrowsingEnabled(&inPrivateBrowsing);
}
#endif
return inPrivateBrowsing;
}
nsDownload *
nsDownloadManager::FindDownload(uint32_t aID)
{
nsCOMArray<nsDownload>& currentDownloads =
IsInGlobalPrivateBrowsing() ?
mCurrentPrivateDownloads : mCurrentDownloads;
// we shouldn't ever have many downloads, so we can loop over them
for (int32_t i = currentDownloads.Count() - 1; i >= 0; --i) {
nsDownload *dl = currentDownloads[i];
if (dl->mID == aID)
return dl;
}
return nullptr;
}
nsDownload *
nsDownloadManager::FindDownload(const nsACString& aGUID)
{
// we shouldn't ever have many downloads, so we can loop over them
for (int32_t i = mCurrentDownloads.Count() - 1; i >= 0; --i) {
nsDownload *dl = mCurrentDownloads[i];
if (dl->mGUID == aGUID)
return dl;
}
for (int32_t i = mCurrentPrivateDownloads.Count() - 1; i >= 0; --i) {
nsDownload *dl = mCurrentPrivateDownloads[i];
if (dl->mGUID == aGUID)
return dl;
}
return nullptr;
}
NS_IMETHODIMP
nsDownloadManager::CancelDownload(uint32_t aID)
{
if (gUsePerWindowPrivateBrowsing) {
NS_WARNING("Using integer IDs without compat mode enabled");
}
// We AddRef here so we don't lose access to member variables when we remove
nsRefPtr<nsDownload> dl = FindDownload(aID);
// if it's null, someone passed us a bad id.
if (!dl)
return NS_ERROR_FAILURE;
return dl->Cancel();
}
nsresult
nsDownloadManager::RetryDownload(const nsACString& aGUID)
{
nsRefPtr<nsDownload> dl;
nsresult rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl));
NS_ENSURE_SUCCESS(rv, rv);
return RetryDownload(dl);
}
NS_IMETHODIMP
nsDownloadManager::RetryDownload(uint32_t aID)
{
if (gUsePerWindowPrivateBrowsing) {
NS_WARNING("Using integer IDs without compat mode enabled");
}
nsRefPtr<nsDownload> dl;
nsresult rv = GetDownloadFromDB(aID, getter_AddRefs(dl));
NS_ENSURE_SUCCESS(rv, rv);
return RetryDownload(dl);
}
nsresult
nsDownloadManager::RetryDownload(nsDownload* dl)
{
// if our download is not canceled or failed, we should fail
if (dl->mDownloadState != nsIDownloadManager::DOWNLOAD_FAILED &&
dl->mDownloadState != nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL &&
dl->mDownloadState != nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY &&
dl->mDownloadState != nsIDownloadManager::DOWNLOAD_DIRTY &&
dl->mDownloadState != nsIDownloadManager::DOWNLOAD_CANCELED)
return NS_ERROR_FAILURE;
// If the download has failed and is resumable then we first try resuming it
nsresult rv;
if (dl->mDownloadState == nsIDownloadManager::DOWNLOAD_FAILED && dl->IsResumable()) {
rv = dl->Resume();
if (NS_SUCCEEDED(rv))
return rv;
}
// reset time and download progress
dl->SetStartTime(PR_Now());
dl->SetProgressBytes(0, -1);
nsCOMPtr<nsIWebBrowserPersist> wbp =
do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = wbp->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES |
nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION);
NS_ENSURE_SUCCESS(rv, rv);
rv = AddToCurrentDownloads(dl);
NS_ENSURE_SUCCESS(rv, rv);
rv = dl->SetState(nsIDownloadManager::DOWNLOAD_QUEUED);
NS_ENSURE_SUCCESS(rv, rv);
// Creates a cycle that will be broken when the download finishes
dl->mCancelable = wbp;
(void)wbp->SetProgressListener(dl);
rv = wbp->SavePrivacyAwareURI(dl->mSource, nullptr, nullptr, nullptr, nullptr,
dl->mTarget, dl->mPrivate);
if (NS_FAILED(rv)) {
dl->mCancelable = nullptr;
(void)wbp->SetProgressListener(nullptr);
return rv;
}
return NS_OK;
}
static nsresult
RemoveDownloadByGUID(const nsACString& aGUID, mozIStorageConnection* aDBConn)
{
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_downloads "
"WHERE guid = :guid"), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
nsDownloadManager::RemoveDownload(const nsACString& aGUID)
{
nsRefPtr<nsDownload> dl = FindDownload(aGUID);
MOZ_ASSERT(!dl, "Can't call RemoveDownload on a download in progress!");
if (dl)
return NS_ERROR_FAILURE;
nsresult rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl));
NS_ENSURE_SUCCESS(rv, rv);
if (dl->mPrivate) {
RemoveDownloadByGUID(aGUID, mPrivateDBConn);
} else {
RemoveDownloadByGUID(aGUID, mDBConn);
}
return NotifyDownloadRemoval(dl);
}
NS_IMETHODIMP
nsDownloadManager::RemoveDownload(uint32_t aID)
{
if (gUsePerWindowPrivateBrowsing) {
NS_WARNING("Using integer IDs without compat mode enabled");
}
nsRefPtr<nsDownload> dl = FindDownload(aID);
MOZ_ASSERT(!dl, "Can't call RemoveDownload on a download in progress!");
if (dl)
return NS_ERROR_FAILURE;
nsresult rv = GetDownloadFromDB(aID, getter_AddRefs(dl));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageConnection> dbConn =
IsInGlobalPrivateBrowsing() ? mPrivateDBConn : mDBConn;
nsCOMPtr<mozIStorageStatement> stmt;
rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_downloads "
"WHERE id = :id"), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aID); // unsigned; 64-bit to prevent overflow
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Notify the UI with the topic and download id
return NotifyDownloadRemoval(dl);
}
nsresult
nsDownloadManager::NotifyDownloadRemoval(nsDownload* aRemoved)
{
nsCOMPtr<nsISupportsPRUint32> id;
nsCOMPtr<nsISupportsCString> guid;
nsresult rv;
// Only send an integer ID notification if the download is public,
// or we're in global PB compatiblity mode, or we're removing multiple downloads.
bool sendDeprecatedNotification = !gUsePerWindowPrivateBrowsing ||
!(aRemoved && aRemoved->mPrivate);
if (sendDeprecatedNotification && aRemoved) {
id = do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t dlID;
rv = aRemoved->GetId(&dlID);
NS_ENSURE_SUCCESS(rv, rv);
rv = id->SetData(dlID);
NS_ENSURE_SUCCESS(rv, rv);
}
if (sendDeprecatedNotification) {
mObserverService->NotifyObservers(id,
"download-manager-remove-download",
nullptr);
}
if (aRemoved) {
guid = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString guidStr;
rv = aRemoved->GetGuid(guidStr);
NS_ENSURE_SUCCESS(rv, rv);
rv = guid->SetData(guidStr);
NS_ENSURE_SUCCESS(rv, rv);
}
mObserverService->NotifyObservers(guid,
"download-manager-remove-download-guid",
nullptr);
return NS_OK;
}
static nsresult
DoRemoveDownloadsByTimeframe(mozIStorageConnection* aDBConn,
int64_t aStartTime,
int64_t aEndTime)
{
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_downloads "
"WHERE startTime >= :startTime "
"AND startTime <= :endTime "
"AND state NOT IN (:downloading, :paused, :queued)"), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
// Bind the times
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startTime"), aStartTime);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("endTime"), aEndTime);
NS_ENSURE_SUCCESS(rv, rv);
// Bind the active states
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("downloading"), nsIDownloadManager::DOWNLOAD_DOWNLOADING);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("paused"), nsIDownloadManager::DOWNLOAD_PAUSED);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("queued"), nsIDownloadManager::DOWNLOAD_QUEUED);
NS_ENSURE_SUCCESS(rv, rv);
// Execute
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::RemoveDownloadsByTimeframe(int64_t aStartTime,
int64_t aEndTime)
{
nsresult rv = DoRemoveDownloadsByTimeframe(mDBConn, aStartTime, aEndTime);
nsresult rv2 = DoRemoveDownloadsByTimeframe(mPrivateDBConn, aStartTime, aEndTime);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv2, rv2);
// Notify the UI with the topic and null subject to indicate "remove multiple"
return NotifyDownloadRemoval(nullptr);
}
NS_IMETHODIMP
nsDownloadManager::CleanUp()
{
return CleanUp(mDBConn);
}
NS_IMETHODIMP
nsDownloadManager::CleanUpPrivate()
{
return CleanUp(mPrivateDBConn);
}
nsresult
nsDownloadManager::CleanUp(mozIStorageConnection* aDBConn)
{
DownloadState states[] = { nsIDownloadManager::DOWNLOAD_FINISHED,
nsIDownloadManager::DOWNLOAD_FAILED,
nsIDownloadManager::DOWNLOAD_CANCELED,
nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL,
nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY,
nsIDownloadManager::DOWNLOAD_DIRTY };
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_downloads "
"WHERE state = ? "
"OR state = ? "
"OR state = ? "
"OR state = ? "
"OR state = ? "
"OR state = ?"), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
for (uint32_t i = 0; i < ArrayLength(states); ++i) {
rv = stmt->BindInt32ByIndex(i, states[i]);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Notify the UI with the topic and null subject to indicate "remove multiple"
return NotifyDownloadRemoval(nullptr);
}
static nsresult
DoGetCanCleanUp(mozIStorageConnection* aDBConn, bool *aResult)
{
// This method should never return anything but NS_OK for the benefit of
// unwitting consumers.
*aResult = false;
DownloadState states[] = { nsIDownloadManager::DOWNLOAD_FINISHED,
nsIDownloadManager::DOWNLOAD_FAILED,
nsIDownloadManager::DOWNLOAD_CANCELED,
nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL,
nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY,
nsIDownloadManager::DOWNLOAD_DIRTY };
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT COUNT(*) "
"FROM moz_downloads "
"WHERE state = ? "
"OR state = ? "
"OR state = ? "
"OR state = ? "
"OR state = ? "
"OR state = ?"), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, NS_OK);
for (uint32_t i = 0; i < ArrayLength(states); ++i) {
rv = stmt->BindInt32ByIndex(i, states[i]);
NS_ENSURE_SUCCESS(rv, NS_OK);
}
bool moreResults; // We don't really care...
rv = stmt->ExecuteStep(&moreResults);
NS_ENSURE_SUCCESS(rv, NS_OK);
int32_t count;
rv = stmt->GetInt32(0, &count);
NS_ENSURE_SUCCESS(rv, NS_OK);
if (count > 0)
*aResult = true;
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::GetCanCleanUp(bool *aResult)
{
return DoGetCanCleanUp(mDBConn, aResult);
}
NS_IMETHODIMP
nsDownloadManager::GetCanCleanUpPrivate(bool *aResult)
{
return DoGetCanCleanUp(mPrivateDBConn, aResult);
}
NS_IMETHODIMP
nsDownloadManager::PauseDownload(uint32_t aID)
{
if (gUsePerWindowPrivateBrowsing) {
NS_WARNING("Using integer IDs without compat mode enabled");
}
nsDownload *dl = FindDownload(aID);
if (!dl)
return NS_ERROR_FAILURE;
return dl->Pause();
}
NS_IMETHODIMP
nsDownloadManager::ResumeDownload(uint32_t aID)
{
if (gUsePerWindowPrivateBrowsing) {
NS_WARNING("Using integer IDs without compat mode enabled");
}
nsDownload *dl = FindDownload(aID);
if (!dl)
return NS_ERROR_FAILURE;
return dl->Resume();
}
NS_IMETHODIMP
nsDownloadManager::GetDBConnection(mozIStorageConnection **aDBConn)
{
NS_ADDREF(*aDBConn = IsInGlobalPrivateBrowsing() ? mPrivateDBConn : mDBConn);
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::GetPrivateDBConnection(mozIStorageConnection **aDBConn)
{
NS_ADDREF(*aDBConn = mPrivateDBConn);
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::AddListener(nsIDownloadProgressListener *aListener)
{
mListeners.AppendObject(aListener);
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::AddPrivacyAwareListener(nsIDownloadProgressListener *aListener)
{
mPrivacyAwareListeners.AppendObject(aListener);
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::RemoveListener(nsIDownloadProgressListener *aListener)
{
mListeners.RemoveObject(aListener);
mPrivacyAwareListeners.RemoveObject(aListener);
return NS_OK;
}
void
nsDownloadManager::NotifyListenersOnDownloadStateChange(int16_t aOldState,
nsDownload *aDownload)
{
for (int32_t i = mPrivacyAwareListeners.Count() - 1; i >= 0; --i) {
mPrivacyAwareListeners[i]->OnDownloadStateChange(aOldState, aDownload);
}
// In global PB compatibility mode, it's fine for all listeners to receive
// notifications about all downloads. Otherwise, only privacy-aware listeners
// should receive notifications about private downloads, while non-privacy-aware
// listeners receive no sign they exist.
if (aDownload->mPrivate && gUsePerWindowPrivateBrowsing) {
return;
}
for (int32_t i = mListeners.Count() - 1; i >= 0; --i) {
mListeners[i]->OnDownloadStateChange(aOldState, aDownload);
}
}
void
nsDownloadManager::NotifyListenersOnProgressChange(nsIWebProgress *aProgress,
nsIRequest *aRequest,
int64_t aCurSelfProgress,
int64_t aMaxSelfProgress,
int64_t aCurTotalProgress,
int64_t aMaxTotalProgress,
nsDownload *aDownload)
{
for (int32_t i = mPrivacyAwareListeners.Count() - 1; i >= 0; --i) {
mPrivacyAwareListeners[i]->OnProgressChange(aProgress, aRequest, aCurSelfProgress,
aMaxSelfProgress, aCurTotalProgress,
aMaxTotalProgress, aDownload);
}
// In global PB compatibility mode, it's fine for all listeners to receive
// notifications about all downloads. Otherwise, only privacy-aware listeners
// should receive notifications about private downloads, while non-privacy-aware
// listeners receive no sign they exist.
if (aDownload->mPrivate && gUsePerWindowPrivateBrowsing) {
return;
}
for (int32_t i = mListeners.Count() - 1; i >= 0; --i) {
mListeners[i]->OnProgressChange(aProgress, aRequest, aCurSelfProgress,
aMaxSelfProgress, aCurTotalProgress,
aMaxTotalProgress, aDownload);
}
}
void
nsDownloadManager::NotifyListenersOnStateChange(nsIWebProgress *aProgress,
nsIRequest *aRequest,
uint32_t aStateFlags,
nsresult aStatus,
nsDownload *aDownload)
{
for (int32_t i = mPrivacyAwareListeners.Count() - 1; i >= 0; --i) {
mPrivacyAwareListeners[i]->OnStateChange(aProgress, aRequest, aStateFlags, aStatus,
aDownload);
}
// In global PB compatibility mode, it's fine for all listeners to receive
// notifications about all downloads. Otherwise, only privacy-aware listeners
// should receive notifications about private downloads, while non-privacy-aware
// listeners receive no sign they exist.
if (aDownload->mPrivate && gUsePerWindowPrivateBrowsing) {
return;
}
for (int32_t i = mListeners.Count() - 1; i >= 0; --i) {
mListeners[i]->OnStateChange(aProgress, aRequest, aStateFlags, aStatus,
aDownload);
}
}
////////////////////////////////////////////////////////////////////////////////
//// nsINavHistoryObserver
NS_IMETHODIMP
nsDownloadManager::OnBeginUpdateBatch()
{
// We already have a transaction, so don't make another
if (mHistoryTransaction)
return NS_OK;
// Start a transaction that commits when deleted
mHistoryTransaction = new mozStorageTransaction(mDBConn, true);
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::OnEndUpdateBatch()
{
// Get rid of the transaction and cause it to commit
mHistoryTransaction = nullptr;
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::OnVisit(nsIURI *aURI, int64_t aVisitID, PRTime aTime,
int64_t aSessionID, int64_t aReferringID,
uint32_t aTransitionType, const nsACString& aGUID,
bool aHidden)
{
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::OnTitleChanged(nsIURI *aURI,
const nsAString &aPageTitle,
const nsACString &aGUID)
{
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::OnBeforeDeleteURI(nsIURI *aURI,
const nsACString& aGUID,
uint16_t aReason)
{
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::OnDeleteURI(nsIURI *aURI,
const nsACString& aGUID,
uint16_t aReason)
{
nsresult rv = RemoveDownloadsForURI(mGetIdsForURIStatement, aURI);
nsresult rv2 = RemoveDownloadsForURI(mGetPrivateIdsForURIStatement, aURI);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv2, rv2);
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::OnClearHistory()
{
return CleanUp();
}
NS_IMETHODIMP
nsDownloadManager::OnPageChanged(nsIURI *aURI,
uint32_t aChangedAttribute,
const nsAString& aNewValue,
const nsACString &aGUID)
{
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::OnDeleteVisits(nsIURI *aURI, PRTime aVisitTime,
const nsACString& aGUID,
uint16_t aReason)
{
// Don't bother removing downloads until the page is removed.
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// nsIObserver
NS_IMETHODIMP
nsDownloadManager::Observe(nsISupports *aSubject,
const char *aTopic,
const PRUnichar *aData)
{
// If we're in global private browsing mode, the total number of active
// downloads we want to warn about is the number of active private downloads.
// Otherwise, we need to count the active public downloads that could be lost
// by quitting, and add any active private ones as well, since per-window
// private browsing may be active.
nsCOMArray<nsDownload>& currDownloads =
IsInGlobalPrivateBrowsing() ? mCurrentPrivateDownloads : mCurrentDownloads;
int32_t currDownloadCount = currDownloads.Count();
// If we don't need to cancel all the downloads on quit, only count the ones
// that aren't resumable (this includes private downloads if we're in
// global private browsing mode).
if (GetQuitBehavior() != QUIT_AND_CANCEL && !IsInGlobalPrivateBrowsing()) {
for (int32_t i = currDownloadCount - 1; i >= 0; --i) {
if (currDownloads[i]->IsResumable()) {
currDownloadCount--;
}
}
if (gUsePerWindowPrivateBrowsing) {
// We have a count of the public, non-resumable downloads. Now we need
// to add the total number of private downloads, since they are in danger
// of being lost.
currDownloadCount += mCurrentPrivateDownloads.Count();
}
}
nsresult rv;
if (strcmp(aTopic, "oncancel") == 0) {
nsCOMPtr<nsIDownload> dl = do_QueryInterface(aSubject, &rv);
NS_ENSURE_SUCCESS(rv, rv);
dl->Cancel();
} else if (strcmp(aTopic, "profile-before-change") == 0) {
CloseAllDBs();
} else if (strcmp(aTopic, "quit-application") == 0) {
// Try to pause all downloads and, if appropriate, mark them as auto-resume
// unless user has specified that downloads should be canceled
enum QuitBehavior behavior = GetQuitBehavior();
if (behavior != QUIT_AND_CANCEL)
(void)PauseAllDownloads(bool(behavior != QUIT_AND_PAUSE));
// Remove downloads to break cycles and cancel downloads
(void)RemoveAllDownloads();
// Now that active downloads have been canceled, remove all completed or
// aborted downloads if the user's retention policy specifies it.
if (GetRetentionBehavior() == 1)
CleanUp();
} else if (strcmp(aTopic, "quit-application-requested") == 0 &&
currDownloadCount) {
nsCOMPtr<nsISupportsPRBool> cancelDownloads =
do_QueryInterface(aSubject, &rv);
NS_ENSURE_SUCCESS(rv, rv);
#ifndef XP_MACOSX
ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
NS_LITERAL_STRING("quitCancelDownloadsAlertTitle").get(),
NS_LITERAL_STRING("quitCancelDownloadsAlertMsgMultiple").get(),
NS_LITERAL_STRING("quitCancelDownloadsAlertMsg").get(),
NS_LITERAL_STRING("dontQuitButtonWin").get());
#else
ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
NS_LITERAL_STRING("quitCancelDownloadsAlertTitle").get(),
NS_LITERAL_STRING("quitCancelDownloadsAlertMsgMacMultiple").get(),
NS_LITERAL_STRING("quitCancelDownloadsAlertMsgMac").get(),
NS_LITERAL_STRING("dontQuitButtonMac").get());
#endif
} else if (strcmp(aTopic, "offline-requested") == 0 && currDownloadCount) {
nsCOMPtr<nsISupportsPRBool> cancelDownloads =
do_QueryInterface(aSubject, &rv);
NS_ENSURE_SUCCESS(rv, rv);
ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
NS_LITERAL_STRING("offlineCancelDownloadsAlertTitle").get(),
NS_LITERAL_STRING("offlineCancelDownloadsAlertMsgMultiple").get(),
NS_LITERAL_STRING("offlineCancelDownloadsAlertMsg").get(),
NS_LITERAL_STRING("dontGoOfflineButton").get());
}
else if (strcmp(aTopic, NS_IOSERVICE_GOING_OFFLINE_TOPIC) == 0) {
// Pause all downloads, and mark them to auto-resume.
(void)PauseAllDownloads(true);
}
else if (strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC) == 0 &&
nsDependentString(aData).EqualsLiteral(NS_IOSERVICE_ONLINE)) {
// We can now resume all downloads that are supposed to auto-resume.
(void)ResumeAllDownloads(false);
}
else if (strcmp(aTopic, "alertclickcallback") == 0) {
//TODO: This doens't make sense when clicking a notification related to
// private downloads when per-window mode is enabled. (bug 810208)
nsCOMPtr<nsIDownloadManagerUI> dmui =
do_GetService("@mozilla.org/download-manager-ui;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
return dmui->Show(nullptr, 0, nsIDownloadManagerUI::REASON_USER_INTERACTED);
} else if (strcmp(aTopic, "sleep_notification") == 0 ||
strcmp(aTopic, "suspend_process_notification") == 0) {
// Pause downloads if we're sleeping, and mark the downloads as auto-resume
(void)PauseAllDownloads(true);
} else if (strcmp(aTopic, "wake_notification") == 0 ||
strcmp(aTopic, "resume_process_notification") == 0) {
int32_t resumeOnWakeDelay = 10000;
nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (pref)
(void)pref->GetIntPref(PREF_BDM_RESUMEONWAKEDELAY, &resumeOnWakeDelay);
// Wait a little bit before trying to resume to avoid resuming when network
// connections haven't restarted yet
mResumeOnWakeTimer = do_CreateInstance("@mozilla.org/timer;1");
if (resumeOnWakeDelay >= 0 && mResumeOnWakeTimer) {
(void)mResumeOnWakeTimer->InitWithFuncCallback(ResumeOnWakeCallback,
this, resumeOnWakeDelay, nsITimer::TYPE_ONE_SHOT);
}
} else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
// Upon leaving private browsing mode, cancel all private downloads,
// remove all trace of them, and then blow away the private database
// and recreate a blank one.
PauseAllDownloads(mCurrentPrivateDownloads, true);
RemoveAllDownloads(mCurrentPrivateDownloads);
InitPrivateDB();
} else if (strcmp(aTopic, "last-pb-context-exiting") == 0) {
// If there are active private downloads, prompt the user to confirm leaving
// private browsing mode (thereby cancelling them). Otherwise, silently proceed.
if (!mCurrentPrivateDownloads.Count())
return NS_OK;
nsCOMPtr<nsISupportsPRBool> cancelDownloads = do_QueryInterface(aSubject, &rv);
NS_ENSURE_SUCCESS(rv, rv);
#ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING
ConfirmCancelDownloads(mCurrentPrivateDownloads.Count(), cancelDownloads,
NS_LITERAL_STRING("leavePrivateBrowsingCancelDownloadsAlertTitle").get(),
NS_LITERAL_STRING("leavePrivateBrowsingWindowsCancelDownloadsAlertMsgMultiple").get(),
NS_LITERAL_STRING("leavePrivateBrowsingWindowsCancelDownloadsAlertMsg").get(),
NS_LITERAL_STRING("dontLeavePrivateBrowsingButton").get());
#else
ConfirmCancelDownloads(mCurrentPrivateDownloads.Count(), cancelDownloads,
NS_LITERAL_STRING("leavePrivateBrowsingCancelDownloadsAlertTitle").get(),
NS_LITERAL_STRING("leavePrivateBrowsingCancelDownloadsAlertMsgMultiple").get(),
NS_LITERAL_STRING("leavePrivateBrowsingCancelDownloadsAlertMsg").get(),
NS_LITERAL_STRING("dontLeavePrivateBrowsingButton").get());
#endif
}
#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING
else if (strcmp(aTopic, NS_PRIVATE_BROWSING_REQUEST_TOPIC) == 0) {
if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_ENTER).Equals(aData) &&
currDownloadCount) {
nsCOMPtr<nsISupportsPRBool> cancelDownloads =
do_QueryInterface(aSubject, &rv);
NS_ENSURE_SUCCESS(rv, rv);
ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
NS_LITERAL_STRING("enterPrivateBrowsingCancelDownloadsAlertTitle").get(),
NS_LITERAL_STRING("enterPrivateBrowsingCancelDownloadsAlertMsgMultiple").get(),
NS_LITERAL_STRING("enterPrivateBrowsingCancelDownloadsAlertMsg").get(),
NS_LITERAL_STRING("dontEnterPrivateBrowsingButton").get());
}
}
else if (strcmp(aTopic, NS_PRIVATE_BROWSING_SWITCH_TOPIC) == 0) {
if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_ENTER).Equals(aData))
OnEnterPrivateBrowsingMode();
else if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_LEAVE).Equals(aData))
OnLeavePrivateBrowsingMode();
}
#endif
return NS_OK;
}
#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING
void
nsDownloadManager::OnEnterPrivateBrowsingMode()
{
// Pause all downloads, and mark them to auto-resume.
(void)PauseAllDownloads(true);
(void)RemoveAllDownloads();
// Notify that the database type changed before resuming current downloads
(void)mObserverService->NotifyObservers(
static_cast<nsIDownloadManager *>(this),
"download-manager-database-type-changed",
nullptr);
}
void
nsDownloadManager::OnLeavePrivateBrowsingMode()
{
nsresult rv = RestoreDatabaseState();
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to restore database state");
// Notify that the database type changed before resuming current downloads
(void)mObserverService->NotifyObservers(
static_cast<nsIDownloadManager *>(this),
"download-manager-database-type-changed",
nullptr);
rv = RestoreActiveDownloads();
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to restore all active downloads");
}
#endif
void
nsDownloadManager::ConfirmCancelDownloads(int32_t aCount,
nsISupportsPRBool *aCancelDownloads,
const PRUnichar *aTitle,
const PRUnichar *aCancelMessageMultiple,
const PRUnichar *aCancelMessageSingle,
const PRUnichar *aDontCancelButton)
{
// If user has already dismissed quit request, then do nothing
bool quitRequestCancelled = false;
aCancelDownloads->GetData(&quitRequestCancelled);
if (quitRequestCancelled)
return;
nsXPIDLString title, message, quitButton, dontQuitButton;
mBundle->GetStringFromName(aTitle, getter_Copies(title));
nsAutoString countString;
countString.AppendInt(aCount);
const PRUnichar *strings[1] = { countString.get() };
if (aCount > 1) {
mBundle->FormatStringFromName(aCancelMessageMultiple, strings, 1,
getter_Copies(message));
mBundle->FormatStringFromName(NS_LITERAL_STRING("cancelDownloadsOKTextMultiple").get(),
strings, 1, getter_Copies(quitButton));
} else {
mBundle->GetStringFromName(aCancelMessageSingle, getter_Copies(message));
mBundle->GetStringFromName(NS_LITERAL_STRING("cancelDownloadsOKText").get(),
getter_Copies(quitButton));
}
mBundle->GetStringFromName(aDontCancelButton, getter_Copies(dontQuitButton));
// Get Download Manager window, to be parent of alert.
nsCOMPtr<nsIWindowMediator> wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
nsCOMPtr<nsIDOMWindow> dmWindow;
if (wm) {
wm->GetMostRecentWindow(NS_LITERAL_STRING("Download:Manager").get(),
getter_AddRefs(dmWindow));
}
// Show alert.
nsCOMPtr<nsIPromptService> prompter(do_GetService(NS_PROMPTSERVICE_CONTRACTID));
if (prompter) {
int32_t flags = (nsIPromptService::BUTTON_TITLE_IS_STRING * nsIPromptService::BUTTON_POS_0) + (nsIPromptService::BUTTON_TITLE_IS_STRING * nsIPromptService::BUTTON_POS_1);
bool nothing = false;
int32_t button;
prompter->ConfirmEx(dmWindow, title, message, flags, quitButton.get(), dontQuitButton.get(), nullptr, nullptr, &nothing, &button);
aCancelDownloads->SetData(button == 1);
}
}
////////////////////////////////////////////////////////////////////////////////
//// nsDownload
NS_IMPL_CLASSINFO(nsDownload, NULL, 0, NS_DOWNLOAD_CID)
NS_IMPL_ISUPPORTS4_CI(
nsDownload
, nsIDownload
, nsITransfer
, nsIWebProgressListener
, nsIWebProgressListener2
)
nsDownload::nsDownload() : mDownloadState(nsIDownloadManager::DOWNLOAD_NOTSTARTED),
mID(0),
mPercentComplete(0),
mCurrBytes(0),
mMaxBytes(-1),
mStartTime(0),
mLastUpdate(PR_Now() - (uint32_t)gUpdateInterval),
mResumedAt(-1),
mSpeed(0),
mHasMultipleFiles(false),
mPrivate(false),
mAutoResume(DONT_RESUME)
{
}
nsDownload::~nsDownload()
{
}
nsresult
nsDownload::SetState(DownloadState aState)
{
NS_ASSERTION(mDownloadState != aState,
"Trying to set the download state to what it already is set to!");
int16_t oldState = mDownloadState;
mDownloadState = aState;
// We don't want to lose access to our member variables
nsRefPtr<nsDownload> kungFuDeathGrip = this;
// When the state changed listener is dispatched, queries to the database and
// the download manager api should reflect what the nsIDownload object would
// return. So, if a download is done (finished, canceled, etc.), it should
// first be removed from the current downloads. We will also have to update
// the database *before* notifying listeners. At this point, you can safely
// dispatch to the observers as well.
switch (aState) {
case nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL:
case nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY:
case nsIDownloadManager::DOWNLOAD_DIRTY:
case nsIDownloadManager::DOWNLOAD_CANCELED:
case nsIDownloadManager::DOWNLOAD_FAILED:
#ifdef ANDROID
// If we still have a temp file, remove it
bool tempExists;
if (mTempFile && NS_SUCCEEDED(mTempFile->Exists(&tempExists)) && tempExists) {
nsresult rv = mTempFile->Remove(false);
NS_ENSURE_SUCCESS(rv, rv);
}
#endif
// Transfers are finished, so break the reference cycle
Finalize();
break;
#ifdef DOWNLOAD_SCANNER
case nsIDownloadManager::DOWNLOAD_SCANNING:
{
nsresult rv = mDownloadManager->mScanner ? mDownloadManager->mScanner->ScanDownload(this) : NS_ERROR_NOT_INITIALIZED;
// If we failed, then fall through to 'download finished'
if (NS_SUCCEEDED(rv))
break;
mDownloadState = aState = nsIDownloadManager::DOWNLOAD_FINISHED;
}
#endif
case nsIDownloadManager::DOWNLOAD_FINISHED:
{
// Do what exthandler would have done if necessary
nsresult rv = ExecuteDesiredAction();
if (NS_FAILED(rv)) {
// We've failed to execute the desired action. As a result, we should
// fail the download so the user can try again.
(void)FailDownload(rv, nullptr);
return rv;
}
// Now that we're done with handling the download, clean it up
Finalize();
nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID));
// Master pref to control this function.
bool showTaskbarAlert = true;
if (pref)
pref->GetBoolPref(PREF_BDM_SHOWALERTONCOMPLETE, &showTaskbarAlert);
if (showTaskbarAlert) {
int32_t alertInterval = 2000;
if (pref)
pref->GetIntPref(PREF_BDM_SHOWALERTINTERVAL, &alertInterval);
int64_t alertIntervalUSec = alertInterval * PR_USEC_PER_MSEC;
int64_t goat = PR_Now() - mStartTime;
showTaskbarAlert = goat > alertIntervalUSec;
int32_t size = mPrivate ?
mDownloadManager->mCurrentPrivateDownloads.Count() :
mDownloadManager->mCurrentDownloads.Count();
if (showTaskbarAlert && size == 0) {
nsCOMPtr<nsIAlertsService> alerts =
do_GetService("@mozilla.org/alerts-service;1");
if (alerts) {
nsXPIDLString title, message;
mDownloadManager->mBundle->GetStringFromName(
NS_LITERAL_STRING("downloadsCompleteTitle").get(),
getter_Copies(title));
mDownloadManager->mBundle->GetStringFromName(
NS_LITERAL_STRING("downloadsCompleteMsg").get(),
getter_Copies(message));
bool removeWhenDone =
mDownloadManager->GetRetentionBehavior() == 0;
// If downloads are automatically removed per the user's
// retention policy, there's no reason to make the text clickable
// because if it is, they'll click open the download manager and
// the items they downloaded will have been removed.
alerts->ShowAlertNotification(
NS_LITERAL_STRING(DOWNLOAD_MANAGER_ALERT_ICON), title,
message, !removeWhenDone, EmptyString(), mDownloadManager,
EmptyString());
}
}
}
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK2)
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mTarget);
nsCOMPtr<nsIFile> file;
nsAutoString path;
if (fileURL &&
NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) &&
file &&
NS_SUCCEEDED(file->GetPath(path))) {
#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK2)
// On Windows and Gtk, add the download to the system's "recent documents"
// list, with a pref to disable.
{
bool addToRecentDocs = true;
if (pref)
pref->GetBoolPref(PREF_BDM_ADDTORECENTDOCS, &addToRecentDocs);
if (addToRecentDocs && !mPrivate) {
#ifdef XP_WIN
::SHAddToRecentDocs(SHARD_PATHW, path.get());
#elif defined(MOZ_WIDGET_GTK2)
GtkRecentManager* manager = gtk_recent_manager_get_default();
gchar* uri = g_filename_to_uri(NS_ConvertUTF16toUTF8(path).get(),
NULL, NULL);
if (uri) {
gtk_recent_manager_add_item(manager, uri);
g_free(uri);
}
#endif
}
#ifdef MOZ_ENABLE_GIO
// Use GIO to store the source URI for later display in the file manager.
GFile* gio_file = g_file_new_for_path(NS_ConvertUTF16toUTF8(path).get());
nsCString source_uri;
mSource->GetSpec(source_uri);
g_file_set_attribute(gio_file, "metadata::download-uri",
G_FILE_ATTRIBUTE_TYPE_STRING,
(gpointer)source_uri.get(),
G_FILE_QUERY_INFO_NONE, NULL, NULL);
g_object_unref(gio_file);
#endif
}
#endif
#ifdef XP_MACOSX
// On OS X, make the downloads stack bounce.
CFStringRef observedObject = ::CFStringCreateWithCString(kCFAllocatorDefault,
NS_ConvertUTF16toUTF8(path).get(),
kCFStringEncodingUTF8);
CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
::CFNotificationCenterPostNotification(center, CFSTR("com.apple.DownloadFileFinished"),
observedObject, NULL, TRUE);
::CFRelease(observedObject);
#endif
#ifdef MOZ_WIDGET_ANDROID
nsCOMPtr<nsIMIMEInfo> mimeInfo;
nsAutoCString contentType;
GetMIMEInfo(getter_AddRefs(mimeInfo));
if (mimeInfo)
mimeInfo->GetMIMEType(contentType);
mozilla::AndroidBridge::Bridge()->ScanMedia(path, contentType);
#endif
}
#ifdef XP_WIN
// Adjust file attributes so that by default, new files are indexed
// by desktop search services. Skip off those that land in the temp
// folder.
nsCOMPtr<nsIFile> tempDir, fileDir;
rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir));
NS_ENSURE_SUCCESS(rv, rv);
(void)file->GetParent(getter_AddRefs(fileDir));
bool isTemp = false;
if (fileDir)
(void)fileDir->Equals(tempDir, &isTemp);
nsCOMPtr<nsILocalFileWin> localFileWin(do_QueryInterface(file));
if (!isTemp && localFileWin)
(void)localFileWin->SetFileAttributesWin(nsILocalFileWin::WFA_SEARCH_INDEXED);
#endif
#endif
// Now remove the download if the user's retention policy is "Remove when Done"
if (mDownloadManager->GetRetentionBehavior() == 0)
mDownloadManager->RemoveDownload(mGUID);
}
break;
default:
break;
}
// Before notifying the listener, we must update the database so that calls
// to it work out properly.
nsresult rv = UpdateDB();
NS_ENSURE_SUCCESS(rv, rv);
mDownloadManager->NotifyListenersOnDownloadStateChange(oldState, this);
switch (mDownloadState) {
case nsIDownloadManager::DOWNLOAD_DOWNLOADING:
// Only send the dl-start event to downloads that are actually starting.
if (oldState == nsIDownloadManager::DOWNLOAD_QUEUED) {
if (!gUsePerWindowPrivateBrowsing || !mPrivate)
mDownloadManager->SendEvent(this, "dl-start");
}
break;
case nsIDownloadManager::DOWNLOAD_FAILED:
if (!gUsePerWindowPrivateBrowsing || !mPrivate)
mDownloadManager->SendEvent(this, "dl-failed");
break;
case nsIDownloadManager::DOWNLOAD_SCANNING:
if (!gUsePerWindowPrivateBrowsing || !mPrivate)
mDownloadManager->SendEvent(this, "dl-scanning");
break;
case nsIDownloadManager::DOWNLOAD_FINISHED:
if (!gUsePerWindowPrivateBrowsing || !mPrivate)
mDownloadManager->SendEvent(this, "dl-done");
break;
case nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL:
case nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY:
if (!gUsePerWindowPrivateBrowsing || !mPrivate)
mDownloadManager->SendEvent(this, "dl-blocked");
break;
case nsIDownloadManager::DOWNLOAD_DIRTY:
if (!gUsePerWindowPrivateBrowsing || !mPrivate)
mDownloadManager->SendEvent(this, "dl-dirty");
break;
case nsIDownloadManager::DOWNLOAD_CANCELED:
if (!gUsePerWindowPrivateBrowsing || !mPrivate)
mDownloadManager->SendEvent(this, "dl-cancel");
break;
default:
break;
}
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// nsIWebProgressListener2
NS_IMETHODIMP
nsDownload::OnProgressChange64(nsIWebProgress *aWebProgress,
nsIRequest *aRequest,
int64_t aCurSelfProgress,
int64_t aMaxSelfProgress,
int64_t aCurTotalProgress,
int64_t aMaxTotalProgress)
{
if (!mRequest)
mRequest = aRequest; // used for pause/resume
if (mDownloadState == nsIDownloadManager::DOWNLOAD_QUEUED) {
// Obtain the referrer
nsresult rv;
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
nsCOMPtr<nsIURI> referrer = mReferrer;
if (channel)
(void)NS_GetReferrerFromChannel(channel, getter_AddRefs(mReferrer));
// Restore the original referrer if the new one isn't useful
if (!mReferrer)
mReferrer = referrer;
// If we have a MIME info, we know that exthandler has already added this to
// the history, but if we do not, we'll have to add it ourselves.
if (!mMIMEInfo && !mPrivate) {
nsCOMPtr<nsIDownloadHistory> dh =
do_GetService(NS_DOWNLOADHISTORY_CONTRACTID);
if (dh)
(void)dh->AddDownload(mSource, mReferrer, mStartTime, mTarget);
}
// Fetch the entityID, but if we can't get it, don't panic (non-resumable)
nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(aRequest));
if (resumableChannel)
(void)resumableChannel->GetEntityID(mEntityID);
// Before we update the state and dispatch state notifications, we want to
// ensure that we have the correct state for this download with regards to
// its percent completion and size.
SetProgressBytes(0, aMaxTotalProgress);
// Update the state and the database
rv = SetState(nsIDownloadManager::DOWNLOAD_DOWNLOADING);
NS_ENSURE_SUCCESS(rv, rv);
}
// filter notifications since they come in so frequently
PRTime now = PR_Now();
PRIntervalTime delta = now - mLastUpdate;
if (delta < gUpdateInterval)
return NS_OK;
mLastUpdate = now;
// Calculate the speed using the elapsed delta time and bytes downloaded
// during that time for more accuracy.
double elapsedSecs = double(delta) / PR_USEC_PER_SEC;
if (elapsedSecs > 0) {
double speed = double(aCurTotalProgress - mCurrBytes) / elapsedSecs;
if (mCurrBytes == 0) {
mSpeed = speed;
} else {
// Calculate 'smoothed average' of 10 readings.
mSpeed = mSpeed * 0.9 + speed * 0.1;
}
}
SetProgressBytes(aCurTotalProgress, aMaxTotalProgress);
// Report to the listener our real sizes
int64_t currBytes, maxBytes;
(void)GetAmountTransferred(&currBytes);
(void)GetSize(&maxBytes);
mDownloadManager->NotifyListenersOnProgressChange(
aWebProgress, aRequest, currBytes, maxBytes, currBytes, maxBytes, this);
// If the maximums are different, then there must be more than one file
if (aMaxSelfProgress != aMaxTotalProgress)
mHasMultipleFiles = true;
return NS_OK;
}
NS_IMETHODIMP
nsDownload::OnRefreshAttempted(nsIWebProgress *aWebProgress,
nsIURI *aUri,
int32_t aDelay,
bool aSameUri,
bool *allowRefresh)
{
*allowRefresh = true;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// nsIWebProgressListener
NS_IMETHODIMP
nsDownload::OnProgressChange(nsIWebProgress *aWebProgress,
nsIRequest *aRequest,
int32_t aCurSelfProgress,
int32_t aMaxSelfProgress,
int32_t aCurTotalProgress,
int32_t aMaxTotalProgress)
{
return OnProgressChange64(aWebProgress, aRequest,
aCurSelfProgress, aMaxSelfProgress,
aCurTotalProgress, aMaxTotalProgress);
}
NS_IMETHODIMP
nsDownload::OnLocationChange(nsIWebProgress *aWebProgress,
nsIRequest *aRequest, nsIURI *aLocation,
uint32_t aFlags)
{
return NS_OK;
}
NS_IMETHODIMP
nsDownload::OnStatusChange(nsIWebProgress *aWebProgress,
nsIRequest *aRequest, nsresult aStatus,
const PRUnichar *aMessage)
{
if (NS_FAILED(aStatus))
return FailDownload(aStatus, aMessage);
return NS_OK;
}
NS_IMETHODIMP
nsDownload::OnStateChange(nsIWebProgress *aWebProgress,
nsIRequest *aRequest, uint32_t aStateFlags,
nsresult aStatus)
{
// We don't want to lose access to our member variables
nsRefPtr<nsDownload> kungFuDeathGrip = this;
// Check if we're starting a request; the NETWORK flag is necessary to not
// pick up the START of *each* file but only for the whole request
if ((aStateFlags & STATE_START) && (aStateFlags & STATE_IS_NETWORK)) {
nsresult rv;
nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
if (NS_SUCCEEDED(rv)) {
uint32_t status;
rv = channel->GetResponseStatus(&status);
// HTTP 450 - Blocked by parental control proxies
if (NS_SUCCEEDED(rv) && status == 450) {
// Cancel using the provided object
(void)Cancel();
// Fail the download
(void)SetState(nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL);
}
}
} else if ((aStateFlags & STATE_STOP) && (aStateFlags & STATE_IS_NETWORK) &&
IsFinishable()) {
// We got both STOP and NETWORK so that means the whole request is done
// (and not just a single file if there are multiple files)
if (NS_SUCCEEDED(aStatus)) {
// We can't completely trust the bytes we've added up because we might be
// missing on some/all of the progress updates (especially from cache).
// Our best bet is the file itself, but if for some reason it's gone or
// if we have multiple files, the next best is what we've calculated.
int64_t fileSize;
nsCOMPtr<nsIFile> file;
// We need a nsIFile clone to deal with file size caching issues. :(
nsCOMPtr<nsIFile> clone;
if (!mHasMultipleFiles &&
NS_SUCCEEDED(GetTargetFile(getter_AddRefs(file))) &&
NS_SUCCEEDED(file->Clone(getter_AddRefs(clone))) &&
NS_SUCCEEDED(clone->GetFileSize(&fileSize)) && fileSize > 0) {
mCurrBytes = mMaxBytes = fileSize;
// If we resumed, keep the fact that we did and fix size calculations
if (WasResumed())
mResumedAt = 0;
} else if (mMaxBytes == -1) {
mMaxBytes = mCurrBytes;
} else {
mCurrBytes = mMaxBytes;
}
mPercentComplete = 100;
mLastUpdate = PR_Now();
#ifdef DOWNLOAD_SCANNER
bool scan = true;
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (prefs)
(void)prefs->GetBoolPref(PREF_BDM_SCANWHENDONE, &scan);
if (scan)
(void)SetState(nsIDownloadManager::DOWNLOAD_SCANNING);
else
(void)SetState(nsIDownloadManager::DOWNLOAD_FINISHED);
#else
(void)SetState(nsIDownloadManager::DOWNLOAD_FINISHED);
#endif
} else {
// We failed for some unknown reason -- fail with a generic message
(void)FailDownload(aStatus, nullptr);
}
}
mDownloadManager->NotifyListenersOnStateChange(aWebProgress, aRequest,
aStateFlags, aStatus, this);
return NS_OK;
}
NS_IMETHODIMP
nsDownload::OnSecurityChange(nsIWebProgress *aWebProgress,
nsIRequest *aRequest, uint32_t aState)
{
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// nsIDownload
NS_IMETHODIMP
nsDownload::Init(nsIURI *aSource,
nsIURI *aTarget,
const nsAString& aDisplayName,
nsIMIMEInfo *aMIMEInfo,
PRTime aStartTime,
nsIFile *aTempFile,
nsICancelable *aCancelable,
bool aIsPrivate)
{
NS_WARNING("Huh...how did we get here?!");
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetState(int16_t *aState)
{
*aState = mDownloadState;
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetDisplayName(nsAString &aDisplayName)
{
aDisplayName = mDisplayName;
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetCancelable(nsICancelable **aCancelable)
{
*aCancelable = mCancelable;
NS_IF_ADDREF(*aCancelable);
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetTarget(nsIURI **aTarget)
{
*aTarget = mTarget;
NS_IF_ADDREF(*aTarget);
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetSource(nsIURI **aSource)
{
*aSource = mSource;
NS_IF_ADDREF(*aSource);
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetStartTime(int64_t *aStartTime)
{
*aStartTime = mStartTime;
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetPercentComplete(int32_t *aPercentComplete)
{
*aPercentComplete = mPercentComplete;
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetAmountTransferred(int64_t *aAmountTransferred)
{
*aAmountTransferred = mCurrBytes + (WasResumed() ? mResumedAt : 0);
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetSize(int64_t *aSize)
{
*aSize = mMaxBytes + (WasResumed() && mMaxBytes != -1 ? mResumedAt : 0);
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetMIMEInfo(nsIMIMEInfo **aMIMEInfo)
{
*aMIMEInfo = mMIMEInfo;
NS_IF_ADDREF(*aMIMEInfo);
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetTargetFile(nsIFile **aTargetFile)
{
nsresult rv;
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mTarget, &rv);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIFile> file;
rv = fileURL->GetFile(getter_AddRefs(file));
if (NS_SUCCEEDED(rv))
rv = CallQueryInterface(file, aTargetFile);
return rv;
}
NS_IMETHODIMP
nsDownload::GetSpeed(double *aSpeed)
{
*aSpeed = mSpeed;
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetId(uint32_t *aId)
{
if (mPrivate && gUsePerWindowPrivateBrowsing) {
return NS_ERROR_NOT_AVAILABLE;
}
*aId = mID;
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetGuid(nsACString &aGUID)
{
aGUID = mGUID;
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetReferrer(nsIURI **referrer)
{
NS_IF_ADDREF(*referrer = mReferrer);
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetResumable(bool *resumable)
{
*resumable = IsResumable();
return NS_OK;
}
NS_IMETHODIMP
nsDownload::GetIsPrivate(bool *isPrivate)
{
*isPrivate = mPrivate;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// nsDownload Helper Functions
void
nsDownload::Finalize()
{
// We're stopping, so break the cycle we created at download start
mCancelable = nullptr;
// Reset values that aren't needed anymore, so the DB can be updated as well
mEntityID.Truncate();
mTempFile = nullptr;
// Remove ourself from the active downloads
nsCOMArray<nsDownload>& currentDownloads = mPrivate ?
mDownloadManager->mCurrentPrivateDownloads :
mDownloadManager->mCurrentDownloads;
(void)currentDownloads.RemoveObject(this);
// Make sure we do not automatically resume
mAutoResume = DONT_RESUME;
}
nsresult
nsDownload::ExecuteDesiredAction()
{
// If we have a temp file and we have resumed, we have to do what the
// external helper app service would have done.
if (!mTempFile || !WasResumed())
return NS_OK;
// We need to bail if for some reason the temp file got removed
bool fileExists;
if (NS_FAILED(mTempFile->Exists(&fileExists)) || !fileExists)
return NS_ERROR_FILE_NOT_FOUND;
// Assume an unknown action is save to disk
nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk;
if (mMIMEInfo) {
nsresult rv = mMIMEInfo->GetPreferredAction(&action);
NS_ENSURE_SUCCESS(rv, rv);
}
nsresult retVal = NS_OK;
switch (action) {
case nsIMIMEInfo::saveToDisk:
// Move the file to the proper location
retVal = MoveTempToTarget();
break;
case nsIMIMEInfo::useHelperApp:
case nsIMIMEInfo::useSystemDefault:
// For these cases we have to move the file to the target location and
// open with the appropriate application
retVal = OpenWithApplication();
break;
default:
break;
}
return retVal;
}
nsresult
nsDownload::MoveTempToTarget()
{
nsCOMPtr<nsIFile> target;
nsresult rv = GetTargetFile(getter_AddRefs(target));
NS_ENSURE_SUCCESS(rv, rv);
// MoveTo will fail if the file already exists, but we've already obtained
// confirmation from the user that this is OK, so remove it if it exists.
bool fileExists;
if (NS_SUCCEEDED(target->Exists(&fileExists)) && fileExists) {
rv = target->Remove(false);
NS_ENSURE_SUCCESS(rv, rv);
}
// Extract the new leaf name from the file location
nsAutoString fileName;
rv = target->GetLeafName(fileName);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> dir;
rv = target->GetParent(getter_AddRefs(dir));
NS_ENSURE_SUCCESS(rv, rv);
rv = mTempFile->MoveTo(dir, fileName);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
nsDownload::OpenWithApplication()
{
// First move the temporary file to the target location
nsCOMPtr<nsIFile> target;
nsresult rv = GetTargetFile(getter_AddRefs(target));
NS_ENSURE_SUCCESS(rv, rv);
// Move the temporary file to the target location
rv = MoveTempToTarget();
NS_ENSURE_SUCCESS(rv, rv);
// We do not verify the return value here because, irrespective of success
// or failure of the method, the deletion of temp file has to take place, as
// per the corresponding preference. But we store this separately as this is
// what we ultimately return from this function.
nsresult retVal = mMIMEInfo->LaunchWithFile(target);
bool deleteTempFileOnExit;
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (!prefs || NS_FAILED(prefs->GetBoolPref(PREF_BH_DELETETEMPFILEONEXIT,
&deleteTempFileOnExit))) {
// No prefservice or no pref set; use default value
#if !defined(XP_MACOSX)
// Mac users have been very verbal about temp files being deleted on
// app exit - they don't like it - but we'll continue to do this on
// other platforms for now.
deleteTempFileOnExit = true;
#else
deleteTempFileOnExit = false;
#endif
}
// Always schedule files to be deleted at the end of the private browsing
// mode, regardless of the value of the pref.
if (deleteTempFileOnExit || mPrivate) {
// Use the ExternalHelperAppService to push the temporary file to the list
// of files to be deleted on exit.
nsCOMPtr<nsPIExternalAppLauncher> appLauncher(do_GetService
(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID));
// Even if we are unable to get this service we return the result
// of LaunchWithFile() which makes more sense.
if (appLauncher) {
if (mPrivate) {
(void)appLauncher->DeleteTemporaryPrivateFileWhenPossible(target);
} else {
(void)appLauncher->DeleteTemporaryFileOnExit(target);
}
}
}
return retVal;
}
void
nsDownload::SetStartTime(int64_t aStartTime)
{
mStartTime = aStartTime;
mLastUpdate = aStartTime;
}
void
nsDownload::SetProgressBytes(int64_t aCurrBytes, int64_t aMaxBytes)
{
mCurrBytes = aCurrBytes;
mMaxBytes = aMaxBytes;
// Get the real bytes that include resume position
int64_t currBytes, maxBytes;
(void)GetAmountTransferred(&currBytes);
(void)GetSize(&maxBytes);
if (currBytes == maxBytes)
mPercentComplete = 100;
else if (maxBytes <= 0)
mPercentComplete = -1;
else
mPercentComplete = (int32_t)((double)currBytes / maxBytes * 100 + .5);
}
NS_IMETHODIMP
nsDownload::Pause()
{
if (!IsResumable())
return NS_ERROR_UNEXPECTED;
nsresult rv = CancelTransfer();
NS_ENSURE_SUCCESS(rv, rv);
return SetState(nsIDownloadManager::DOWNLOAD_PAUSED);
}
nsresult
nsDownload::CancelTransfer()
{
nsresult rv = NS_OK;
if (mCancelable) {
rv = mCancelable->Cancel(NS_BINDING_ABORTED);
// we're done with this, so break the cycle
mCancelable = nullptr;
}
return rv;
}
NS_IMETHODIMP
nsDownload::Cancel()
{
// Don't cancel if download is already finished
if (IsFinished())
return NS_OK;
// if the download is fake-paused, we have to resume it so we can cancel it
if (IsPaused() && !IsResumable())
(void)Resume();
// Have the download cancel its connection
(void)CancelTransfer();
// Dump the temp file because we know we don't need the file anymore. The
// underlying transfer creating the file doesn't delete the file because it
// can't distinguish between a pause that cancels the transfer or a real
// cancel.
if (mTempFile) {
bool exists;
mTempFile->Exists(&exists);
if (exists)
mTempFile->Remove(false);
}
nsCOMPtr<nsIFile> file;
if (NS_SUCCEEDED(GetTargetFile(getter_AddRefs(file))))
{
bool exists;
file->Exists(&exists);
if (exists)
file->Remove(false);
}
nsresult rv = SetState(nsIDownloadManager::DOWNLOAD_CANCELED);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsDownload::Resume()
{
if (!IsPaused() || !IsResumable())
return NS_ERROR_UNEXPECTED;
nsresult rv;
nsCOMPtr<nsIWebBrowserPersist> wbp =
do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = wbp->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_APPEND_TO_FILE |
nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION);
NS_ENSURE_SUCCESS(rv, rv);
// Create a new channel for the source URI
nsCOMPtr<nsIChannel> channel;
nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(wbp));
rv = NS_NewChannel(getter_AddRefs(channel), mSource, nullptr, nullptr, ir);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel);
if (pbChannel) {
pbChannel->SetPrivate(mPrivate);
}
// Make sure we can get a file, either the temporary or the real target, for
// both purposes of file size and a target to write to
nsCOMPtr<nsIFile> targetLocalFile(mTempFile);
if (!targetLocalFile) {
rv = GetTargetFile(getter_AddRefs(targetLocalFile));
NS_ENSURE_SUCCESS(rv, rv);
}
// Get the file size to be used as an offset, but if anything goes wrong
// along the way, we'll silently restart at 0.
int64_t fileSize;
// We need a nsIFile clone to deal with file size caching issues. :(
nsCOMPtr<nsIFile> clone;
if (NS_FAILED(targetLocalFile->Clone(getter_AddRefs(clone))) ||
NS_FAILED(clone->GetFileSize(&fileSize)))
fileSize = 0;
// Set the channel to resume at the right position along with the entityID
nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(channel));
if (!resumableChannel)
return NS_ERROR_UNEXPECTED;
rv = resumableChannel->ResumeAt(fileSize, mEntityID);
NS_ENSURE_SUCCESS(rv, rv);
// If we know the max size, we know what it should be when resuming
int64_t maxBytes;
GetSize(&maxBytes);
SetProgressBytes(0, maxBytes != -1 ? maxBytes - fileSize : -1);
// Track where we resumed because progress notifications restart at 0
mResumedAt = fileSize;
// Set the referrer
if (mReferrer) {
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
if (httpChannel) {
rv = httpChannel->SetReferrer(mReferrer);
NS_ENSURE_SUCCESS(rv, rv);
}
}
// Creates a cycle that will be broken when the download finishes
mCancelable = wbp;
(void)wbp->SetProgressListener(this);
// Save the channel using nsIWBP
rv = wbp->SaveChannel(channel, targetLocalFile);
if (NS_FAILED(rv)) {
mCancelable = nullptr;
(void)wbp->SetProgressListener(nullptr);
return rv;
}
return SetState(nsIDownloadManager::DOWNLOAD_DOWNLOADING);
}
NS_IMETHODIMP
nsDownload::Remove()
{
return mDownloadManager->RemoveDownload(mGUID);
}
NS_IMETHODIMP
nsDownload::Retry()
{
return mDownloadManager->RetryDownload(mGUID);
}
bool
nsDownload::IsPaused()
{
return mDownloadState == nsIDownloadManager::DOWNLOAD_PAUSED;
}
bool
nsDownload::IsResumable()
{
return !mEntityID.IsEmpty();
}
bool
nsDownload::WasResumed()
{
return mResumedAt != -1;
}
bool
nsDownload::ShouldAutoResume()
{
return mAutoResume == AUTO_RESUME;
}
bool
nsDownload::IsFinishable()
{
return mDownloadState == nsIDownloadManager::DOWNLOAD_NOTSTARTED ||
mDownloadState == nsIDownloadManager::DOWNLOAD_QUEUED ||
mDownloadState == nsIDownloadManager::DOWNLOAD_DOWNLOADING;
}
bool
nsDownload::IsFinished()
{
return mDownloadState == nsIDownloadManager::DOWNLOAD_FINISHED;
}
nsresult
nsDownload::UpdateDB()
{
NS_ASSERTION(mID, "Download ID is stored as zero. This is bad!");
NS_ASSERTION(mDownloadManager, "Egads! We have no download manager!");
mozIStorageStatement *stmt = mPrivate ?
mDownloadManager->mUpdatePrivateDownloadStatement : mDownloadManager->mUpdateDownloadStatement;
nsAutoString tempPath;
if (mTempFile)
(void)mTempFile->GetPath(tempPath);
nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("tempPath"), tempPath);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startTime"), mStartTime);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("endTime"), mLastUpdate);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), mDownloadState);
NS_ENSURE_SUCCESS(rv, rv);
if (mReferrer) {
nsAutoCString referrer;
rv = mReferrer->GetSpec(referrer);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("referrer"), referrer);
} else {
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("referrer"));
}
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("entityID"), mEntityID);
NS_ENSURE_SUCCESS(rv, rv);
int64_t currBytes;
(void)GetAmountTransferred(&currBytes);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("currBytes"), currBytes);
NS_ENSURE_SUCCESS(rv, rv);
int64_t maxBytes;
(void)GetSize(&maxBytes);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("maxBytes"), maxBytes);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), mAutoResume);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mID);
NS_ENSURE_SUCCESS(rv, rv);
return stmt->Execute();
}
nsresult
nsDownload::FailDownload(nsresult aStatus, const PRUnichar *aMessage)
{
// Grab the bundle before potentially losing our member variables
nsCOMPtr<nsIStringBundle> bundle = mDownloadManager->mBundle;
(void)SetState(nsIDownloadManager::DOWNLOAD_FAILED);
// Get title for alert.
nsXPIDLString title;
nsresult rv = bundle->GetStringFromName(
NS_LITERAL_STRING("downloadErrorAlertTitle").get(), getter_Copies(title));
NS_ENSURE_SUCCESS(rv, rv);
// Get a generic message if we weren't supplied one
nsXPIDLString message;
message = aMessage;
if (message.IsEmpty()) {
rv = bundle->GetStringFromName(
NS_LITERAL_STRING("downloadErrorGeneric").get(), getter_Copies(message));
NS_ENSURE_SUCCESS(rv, rv);
}
// Get Download Manager window to be parent of alert
nsCOMPtr<nsIWindowMediator> wm =
do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDOMWindow> dmWindow;
rv = wm->GetMostRecentWindow(NS_LITERAL_STRING("Download:Manager").get(),
getter_AddRefs(dmWindow));
NS_ENSURE_SUCCESS(rv, rv);
// Show alert
nsCOMPtr<nsIPromptService> prompter =
do_GetService("@mozilla.org/embedcomp/prompt-service;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
return prompter->Alert(dmWindow, title, message);
}