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