/* -*- 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 #ifdef DOWNLOAD_SCANNER #include "nsDownloadScanner.h" #endif #endif #ifdef XP_MACOSX #include #endif #ifdef MOZ_WIDGET_ANDROID #include "AndroidBridge.h" #endif #ifdef MOZ_WIDGET_GTK2 #include #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 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& aDownloads, bool aSetResume) { nsresult retVal = NS_OK; for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) { nsRefPtr 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& aDownloads, bool aResumeAll) { nsresult retVal = NS_OK; for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) { nsRefPtr 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& aDownloads) { nsresult rv = NS_OK; for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) { nsRefPtr 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 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(aClosure); (void)dlMgr->ResumeAllDownloads(false); } already_AddRefed nsDownloadManager::GetFileDBConnection(nsIFile *dbFile) const { NS_ASSERTION(dbFile, "GetFileDBConnection called with an invalid nsIFile"); nsCOMPtr storage = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); NS_ENSURE_TRUE(storage, nullptr); nsCOMPtr 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 nsDownloadManager::GetPrivateDBConnection() const { nsCOMPtr storage = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); NS_ENSURE_TRUE(storage, nullptr); nsCOMPtr 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 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 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 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 storage = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); NS_ENSURE_TRUE(storage, NS_ERROR_NOT_AVAILABLE); nsCOMPtr 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 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 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 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 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 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 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 history = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID); (void)mObserverService->NotifyObservers( static_cast(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 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 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 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 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 dbConn = IsInGlobalPrivateBrowsing() ? mPrivateDBConn : mDBConn; // First, let's query the database and see if it even exists nsCOMPtr 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 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 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 handler = do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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(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 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& 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 downloadDir; nsresult rv; nsCOMPtr 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 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 dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr prefService = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 downloadDir; nsCOMPtr 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 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 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 targetFileURL = do_QueryInterface(aTarget, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr targetFile; rv = targetFileURL->GetFile(getter_AddRefs(targetFile)); NS_ENSURE_SUCCESS(rv, rv); nsRefPtr 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 handlerApp; (void)aMIMEInfo->GetPreferredApplicationHandler(getter_AddRefs(handlerApp)); nsCOMPtr locHandlerApp = do_QueryInterface(handlerApp); if (locHandlerApp) { nsCOMPtr 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 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 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 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 mResult; nsCOMPtr mCallback; }; } // anonymous namespace NS_IMETHODIMP nsDownloadManager::GetDownloadByGUID(const nsACString& aGUID, nsIDownloadManagerResult* aCallback) { nsDownload *itm = FindDownload(aGUID); nsresult rv = NS_OK; nsRefPtr dl; if (!itm) { rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl)); itm = dl.get(); } nsRefPtr 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 pbs = do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID); if (pbs) { pbs->GetPrivateBrowsingEnabled(&inPrivateBrowsing); } #endif return inPrivateBrowsing; } nsDownload * nsDownloadManager::FindDownload(uint32_t aID) { nsCOMArray& 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 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 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 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 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 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 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 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 dbConn = IsInGlobalPrivateBrowsing() ? mPrivateDBConn : mDBConn; nsCOMPtr 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 id; nsCOMPtr 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 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 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 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, uint32_t aTransitionType) { // 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& 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 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 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 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) { nsCOMPtr dmui = do_GetService("@mozilla.org/download-manager-ui;1", &rv); NS_ENSURE_SUCCESS(rv, rv); return dmui->Show(nullptr, 0, nsIDownloadManagerUI::REASON_USER_INTERACTED, aData && NS_strcmp(aData, NS_LITERAL_STRING("private").get()) == 0); } 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 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 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 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(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(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 wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID); nsCOMPtr dmWindow; if (wm) { wm->GetMostRecentWindow(NS_LITERAL_STRING("Download:Manager").get(), getter_AddRefs(dmWindow)); } // Show alert. nsCOMPtr 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, ¬hing, &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 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 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 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, mPrivate ? NS_LITERAL_STRING("private") : NS_LITERAL_STRING("non-private"), mDownloadManager, EmptyString()); } } } #if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK2) nsCOMPtr fileURL = do_QueryInterface(mTarget); nsCOMPtr 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 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 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 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 channel(do_QueryInterface(aRequest)); nsCOMPtr 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 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 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 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 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 file; // We need a nsIFile clone to deal with file size caching issues. :( nsCOMPtr 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 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 fileURL = do_QueryInterface(mTarget, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr 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& 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 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 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 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 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 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 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 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 channel; nsCOMPtr ir(do_QueryInterface(wbp)); rv = NS_NewChannel(getter_AddRefs(channel), mSource, nullptr, nullptr, ir); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 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 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 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 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 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 wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr dmWindow; rv = wm->GetMostRecentWindow(NS_LITERAL_STRING("Download:Manager").get(), getter_AddRefs(dmWindow)); NS_ENSURE_SUCCESS(rv, rv); // Show alert nsCOMPtr prompter = do_GetService("@mozilla.org/embedcomp/prompt-service;1", &rv); NS_ENSURE_SUCCESS(rv, rv); return prompter->Alert(dmWindow, title, message); }