Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/DebugOnly.h"
8 #include "mozIStorageService.h"
9 #include "nsIAlertsService.h"
10 #include "nsIArray.h"
11 #include "nsIClassInfoImpl.h"
12 #include "nsIDOMWindow.h"
13 #include "nsIDownloadHistory.h"
14 #include "nsIDownloadManagerUI.h"
15 #include "nsIMIMEService.h"
16 #include "nsIParentalControlsService.h"
17 #include "nsIPrefService.h"
18 #include "nsIPromptService.h"
19 #include "nsIResumableChannel.h"
20 #include "nsIWebBrowserPersist.h"
21 #include "nsIWindowMediator.h"
22 #include "nsILocalFileWin.h"
23 #include "nsILoadContext.h"
24 #include "nsIXULAppInfo.h"
26 #include "nsAppDirectoryServiceDefs.h"
27 #include "nsArrayEnumerator.h"
28 #include "nsCExternalHandlerService.h"
29 #include "nsDirectoryServiceDefs.h"
30 #include "nsDownloadManager.h"
31 #include "nsNetUtil.h"
32 #include "nsThreadUtils.h"
34 #include "mozStorageCID.h"
35 #include "nsDocShellCID.h"
36 #include "nsEmbedCID.h"
37 #include "nsToolkitCompsCID.h"
39 #include "SQLFunctions.h"
41 #include "mozilla/Preferences.h"
43 #ifdef XP_WIN
44 #include <shlobj.h>
45 #include "nsWindowsHelpers.h"
46 #ifdef DOWNLOAD_SCANNER
47 #include "nsDownloadScanner.h"
48 #endif
49 #endif
51 #ifdef XP_MACOSX
52 #include <CoreFoundation/CoreFoundation.h>
53 #endif
55 #ifdef MOZ_WIDGET_ANDROID
56 #include "AndroidBridge.h"
57 using namespace mozilla::widget::android;
58 #endif
60 #ifdef MOZ_WIDGET_GTK
61 #include <gtk/gtk.h>
62 #endif
64 using namespace mozilla;
65 using mozilla::downloads::GenerateGUID;
67 #define DOWNLOAD_MANAGER_BUNDLE "chrome://mozapps/locale/downloads/downloads.properties"
68 #define DOWNLOAD_MANAGER_ALERT_ICON "chrome://mozapps/skin/downloads/downloadIcon.png"
69 #define PREF_BD_USEJSTRANSFER "browser.download.useJSTransfer"
70 #define PREF_BDM_SHOWALERTONCOMPLETE "browser.download.manager.showAlertOnComplete"
71 #define PREF_BDM_SHOWALERTINTERVAL "browser.download.manager.showAlertInterval"
72 #define PREF_BDM_RETENTION "browser.download.manager.retention"
73 #define PREF_BDM_QUITBEHAVIOR "browser.download.manager.quitBehavior"
74 #define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs"
75 #define PREF_BDM_SCANWHENDONE "browser.download.manager.scanWhenDone"
76 #define PREF_BDM_RESUMEONWAKEDELAY "browser.download.manager.resumeOnWakeDelay"
77 #define PREF_BH_DELETETEMPFILEONEXIT "browser.helperApps.deleteTempFileOnExit"
79 static const int64_t gUpdateInterval = 400 * PR_USEC_PER_MSEC;
81 #define DM_SCHEMA_VERSION 9
82 #define DM_DB_NAME NS_LITERAL_STRING("downloads.sqlite")
83 #define DM_DB_CORRUPT_FILENAME NS_LITERAL_STRING("downloads.sqlite.corrupt")
85 #define NS_SYSTEMINFO_CONTRACTID "@mozilla.org/system-info;1"
87 ////////////////////////////////////////////////////////////////////////////////
88 //// nsDownloadManager
90 NS_IMPL_ISUPPORTS(
91 nsDownloadManager
92 , nsIDownloadManager
93 , nsINavHistoryObserver
94 , nsIObserver
95 , nsISupportsWeakReference
96 )
98 nsDownloadManager *nsDownloadManager::gDownloadManagerService = nullptr;
100 nsDownloadManager *
101 nsDownloadManager::GetSingleton()
102 {
103 if (gDownloadManagerService) {
104 NS_ADDREF(gDownloadManagerService);
105 return gDownloadManagerService;
106 }
108 gDownloadManagerService = new nsDownloadManager();
109 if (gDownloadManagerService) {
110 #if defined(MOZ_WIDGET_GTK)
111 g_type_init();
112 #endif
113 NS_ADDREF(gDownloadManagerService);
114 if (NS_FAILED(gDownloadManagerService->Init()))
115 NS_RELEASE(gDownloadManagerService);
116 }
118 return gDownloadManagerService;
119 }
121 nsDownloadManager::~nsDownloadManager()
122 {
123 #ifdef DOWNLOAD_SCANNER
124 if (mScanner) {
125 delete mScanner;
126 mScanner = nullptr;
127 }
128 #endif
129 gDownloadManagerService = nullptr;
130 }
132 nsresult
133 nsDownloadManager::ResumeRetry(nsDownload *aDl)
134 {
135 // Keep a reference in case we need to cancel the download
136 nsRefPtr<nsDownload> dl = aDl;
138 // Try to resume the active download
139 nsresult rv = dl->Resume();
141 // If not, try to retry the download
142 if (NS_FAILED(rv)) {
143 // First cancel the download so it's no longer active
144 rv = dl->Cancel();
146 // Then retry it
147 if (NS_SUCCEEDED(rv))
148 rv = dl->Retry();
149 }
151 return rv;
152 }
154 nsresult
155 nsDownloadManager::PauseAllDownloads(bool aSetResume)
156 {
157 nsresult rv = PauseAllDownloads(mCurrentDownloads, aSetResume);
158 nsresult rv2 = PauseAllDownloads(mCurrentPrivateDownloads, aSetResume);
159 NS_ENSURE_SUCCESS(rv, rv);
160 NS_ENSURE_SUCCESS(rv2, rv2);
161 return NS_OK;
162 }
164 nsresult
165 nsDownloadManager::PauseAllDownloads(nsCOMArray<nsDownload>& aDownloads, bool aSetResume)
166 {
167 nsresult retVal = NS_OK;
168 for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) {
169 nsRefPtr<nsDownload> dl = aDownloads[i];
171 // Only pause things that need to be paused
172 if (!dl->IsPaused()) {
173 // Set auto-resume before pausing so that it gets into the DB
174 dl->mAutoResume = aSetResume ? nsDownload::AUTO_RESUME :
175 nsDownload::DONT_RESUME;
177 // Try to pause the download but don't bail now if we fail
178 nsresult rv = dl->Pause();
179 if (NS_FAILED(rv))
180 retVal = rv;
181 }
182 }
184 return retVal;
185 }
187 nsresult
188 nsDownloadManager::ResumeAllDownloads(bool aResumeAll)
189 {
190 nsresult rv = ResumeAllDownloads(mCurrentDownloads, aResumeAll);
191 nsresult rv2 = ResumeAllDownloads(mCurrentPrivateDownloads, aResumeAll);
192 NS_ENSURE_SUCCESS(rv, rv);
193 NS_ENSURE_SUCCESS(rv2, rv2);
194 return NS_OK;
195 }
197 nsresult
198 nsDownloadManager::ResumeAllDownloads(nsCOMArray<nsDownload>& aDownloads, bool aResumeAll)
199 {
200 nsresult retVal = NS_OK;
201 for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) {
202 nsRefPtr<nsDownload> dl = aDownloads[i];
204 // If aResumeAll is true, then resume everything; otherwise, check if the
205 // download should auto-resume
206 if (aResumeAll || dl->ShouldAutoResume()) {
207 // Reset auto-resume before retrying so that it gets into the DB through
208 // ResumeRetry's eventual call to SetState. We clear the value now so we
209 // don't accidentally query completed downloads that were previously
210 // auto-resumed (and try to resume them).
211 dl->mAutoResume = nsDownload::DONT_RESUME;
213 // Try to resume/retry the download but don't bail now if we fail
214 nsresult rv = ResumeRetry(dl);
215 if (NS_FAILED(rv))
216 retVal = rv;
217 }
218 }
220 return retVal;
221 }
223 nsresult
224 nsDownloadManager::RemoveAllDownloads()
225 {
226 nsresult rv = RemoveAllDownloads(mCurrentDownloads);
227 nsresult rv2 = RemoveAllDownloads(mCurrentPrivateDownloads);
228 NS_ENSURE_SUCCESS(rv, rv);
229 NS_ENSURE_SUCCESS(rv2, rv2);
230 return NS_OK;
231 }
233 nsresult
234 nsDownloadManager::RemoveAllDownloads(nsCOMArray<nsDownload>& aDownloads)
235 {
236 nsresult rv = NS_OK;
237 for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) {
238 nsRefPtr<nsDownload> dl = aDownloads[0];
240 nsresult result = NS_OK;
241 if (!dl->mPrivate && dl->IsPaused() && GetQuitBehavior() != QUIT_AND_CANCEL)
242 aDownloads.RemoveObject(dl);
243 else
244 result = dl->Cancel();
246 // Track the failure, but don't miss out on other downloads
247 if (NS_FAILED(result))
248 rv = result;
249 }
251 return rv;
252 }
254 nsresult
255 nsDownloadManager::RemoveDownloadsForURI(mozIStorageStatement* aStatement, nsIURI *aURI)
256 {
257 mozStorageStatementScoper scope(aStatement);
259 nsAutoCString source;
260 nsresult rv = aURI->GetSpec(source);
261 NS_ENSURE_SUCCESS(rv, rv);
263 rv = aStatement->BindUTF8StringByName(
264 NS_LITERAL_CSTRING("source"), source);
265 NS_ENSURE_SUCCESS(rv, rv);
267 bool hasMore = false;
268 nsAutoTArray<nsCString, 4> downloads;
269 // Get all the downloads that match the provided URI
270 while (NS_SUCCEEDED(aStatement->ExecuteStep(&hasMore)) &&
271 hasMore) {
272 nsAutoCString downloadGuid;
273 rv = aStatement->GetUTF8String(0, downloadGuid);
274 NS_ENSURE_SUCCESS(rv, rv);
276 downloads.AppendElement(downloadGuid);
277 }
279 // Remove each download ignoring any failure so we reach other downloads
280 for (int32_t i = downloads.Length(); --i >= 0; )
281 (void)RemoveDownload(downloads[i]);
283 return NS_OK;
284 }
286 void // static
287 nsDownloadManager::ResumeOnWakeCallback(nsITimer *aTimer, void *aClosure)
288 {
289 // Resume the downloads that were set to autoResume
290 nsDownloadManager *dlMgr = static_cast<nsDownloadManager *>(aClosure);
291 (void)dlMgr->ResumeAllDownloads(false);
292 }
294 already_AddRefed<mozIStorageConnection>
295 nsDownloadManager::GetFileDBConnection(nsIFile *dbFile) const
296 {
297 NS_ASSERTION(dbFile, "GetFileDBConnection called with an invalid nsIFile");
299 nsCOMPtr<mozIStorageService> storage =
300 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
301 NS_ENSURE_TRUE(storage, nullptr);
303 nsCOMPtr<mozIStorageConnection> conn;
304 nsresult rv = storage->OpenDatabase(dbFile, getter_AddRefs(conn));
305 if (rv == NS_ERROR_FILE_CORRUPTED) {
306 // delete and try again, since we don't care so much about losing a user's
307 // download history
308 rv = dbFile->Remove(false);
309 NS_ENSURE_SUCCESS(rv, nullptr);
310 rv = storage->OpenDatabase(dbFile, getter_AddRefs(conn));
311 }
312 NS_ENSURE_SUCCESS(rv, nullptr);
314 return conn.forget();
315 }
317 already_AddRefed<mozIStorageConnection>
318 nsDownloadManager::GetPrivateDBConnection() const
319 {
320 nsCOMPtr<mozIStorageService> storage =
321 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
322 NS_ENSURE_TRUE(storage, nullptr);
324 nsCOMPtr<mozIStorageConnection> conn;
325 nsresult rv = storage->OpenSpecialDatabase("memory", getter_AddRefs(conn));
326 NS_ENSURE_SUCCESS(rv, nullptr);
328 return conn.forget();
329 }
331 void
332 nsDownloadManager::CloseAllDBs()
333 {
334 CloseDB(mDBConn, mUpdateDownloadStatement, mGetIdsForURIStatement);
335 CloseDB(mPrivateDBConn, mUpdatePrivateDownloadStatement, mGetPrivateIdsForURIStatement);
336 }
338 void
339 nsDownloadManager::CloseDB(mozIStorageConnection* aDBConn,
340 mozIStorageStatement* aUpdateStmt,
341 mozIStorageStatement* aGetIdsStmt)
342 {
343 DebugOnly<nsresult> rv = aGetIdsStmt->Finalize();
344 MOZ_ASSERT(NS_SUCCEEDED(rv));
345 rv = aUpdateStmt->Finalize();
346 MOZ_ASSERT(NS_SUCCEEDED(rv));
347 rv = aDBConn->AsyncClose(nullptr);
348 MOZ_ASSERT(NS_SUCCEEDED(rv));
349 }
351 static nsresult
352 InitSQLFunctions(mozIStorageConnection* aDBConn)
353 {
354 nsresult rv = mozilla::downloads::GenerateGUIDFunction::create(aDBConn);
355 NS_ENSURE_SUCCESS(rv, rv);
356 return NS_OK;
357 }
359 nsresult
360 nsDownloadManager::InitPrivateDB()
361 {
362 bool ready = false;
363 if (mPrivateDBConn && NS_SUCCEEDED(mPrivateDBConn->GetConnectionReady(&ready)) && ready)
364 CloseDB(mPrivateDBConn, mUpdatePrivateDownloadStatement, mGetPrivateIdsForURIStatement);
365 mPrivateDBConn = GetPrivateDBConnection();
366 if (!mPrivateDBConn)
367 return NS_ERROR_NOT_AVAILABLE;
369 nsresult rv = InitSQLFunctions(mPrivateDBConn);
370 NS_ENSURE_SUCCESS(rv, rv);
372 rv = CreateTable(mPrivateDBConn);
373 NS_ENSURE_SUCCESS(rv, rv);
375 rv = InitStatements(mPrivateDBConn, getter_AddRefs(mUpdatePrivateDownloadStatement),
376 getter_AddRefs(mGetPrivateIdsForURIStatement));
377 NS_ENSURE_SUCCESS(rv, rv);
378 return NS_OK;
379 }
381 nsresult
382 nsDownloadManager::InitFileDB()
383 {
384 nsresult rv;
386 nsCOMPtr<nsIFile> dbFile;
387 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
388 getter_AddRefs(dbFile));
389 NS_ENSURE_SUCCESS(rv, rv);
390 rv = dbFile->Append(DM_DB_NAME);
391 NS_ENSURE_SUCCESS(rv, rv);
393 bool ready = false;
394 if (mDBConn && NS_SUCCEEDED(mDBConn->GetConnectionReady(&ready)) && ready)
395 CloseDB(mDBConn, mUpdateDownloadStatement, mGetIdsForURIStatement);
396 mDBConn = GetFileDBConnection(dbFile);
397 NS_ENSURE_TRUE(mDBConn, NS_ERROR_NOT_AVAILABLE);
399 rv = InitSQLFunctions(mDBConn);
400 NS_ENSURE_SUCCESS(rv, rv);
402 bool tableExists;
403 rv = mDBConn->TableExists(NS_LITERAL_CSTRING("moz_downloads"), &tableExists);
404 NS_ENSURE_SUCCESS(rv, rv);
406 if (!tableExists) {
407 rv = CreateTable(mDBConn);
408 NS_ENSURE_SUCCESS(rv, rv);
410 // We're done with the initialization now and can skip the remaining
411 // upgrading logic.
412 return NS_OK;
413 }
415 // Checking the database schema now
416 int32_t schemaVersion;
417 rv = mDBConn->GetSchemaVersion(&schemaVersion);
418 NS_ENSURE_SUCCESS(rv, rv);
420 // Changing the database? Be sure to do these two things!
421 // 1) Increment DM_SCHEMA_VERSION
422 // 2) Implement the proper downgrade/upgrade code for the current version
424 switch (schemaVersion) {
425 // Upgrading
426 // Every time you increment the database schema, you need to implement
427 // the upgrading code from the previous version to the new one.
428 // Also, don't forget to make a unit test to test your upgrading code!
429 case 1: // Drop a column (iconURL) from the database (bug 385875)
430 {
431 // Safely wrap this in a transaction so we don't hose the whole DB
432 mozStorageTransaction safeTransaction(mDBConn, true);
434 // Create a temporary table that will store the existing records
435 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
436 "CREATE TEMPORARY TABLE moz_downloads_backup ("
437 "id INTEGER PRIMARY KEY, "
438 "name TEXT, "
439 "source TEXT, "
440 "target TEXT, "
441 "startTime INTEGER, "
442 "endTime INTEGER, "
443 "state INTEGER"
444 ")"));
445 NS_ENSURE_SUCCESS(rv, rv);
447 // Insert into a temporary table
448 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
449 "INSERT INTO moz_downloads_backup "
450 "SELECT id, name, source, target, startTime, endTime, state "
451 "FROM moz_downloads"));
452 NS_ENSURE_SUCCESS(rv, rv);
454 // Drop the old table
455 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
456 "DROP TABLE moz_downloads"));
457 NS_ENSURE_SUCCESS(rv, rv);
459 // Now recreate it with this schema version
460 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
461 "CREATE TABLE moz_downloads ("
462 "id INTEGER PRIMARY KEY, "
463 "name TEXT, "
464 "source TEXT, "
465 "target TEXT, "
466 "startTime INTEGER, "
467 "endTime INTEGER, "
468 "state INTEGER"
469 ")"));
470 NS_ENSURE_SUCCESS(rv, rv);
472 // Insert the data back into it
473 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
474 "INSERT INTO moz_downloads "
475 "SELECT id, name, source, target, startTime, endTime, state "
476 "FROM moz_downloads_backup"));
477 NS_ENSURE_SUCCESS(rv, rv);
479 // And drop our temporary table
480 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
481 "DROP TABLE moz_downloads_backup"));
482 NS_ENSURE_SUCCESS(rv, rv);
484 // Finally, update the schemaVersion variable and the database schema
485 schemaVersion = 2;
486 rv = mDBConn->SetSchemaVersion(schemaVersion);
487 NS_ENSURE_SUCCESS(rv, rv);
488 }
489 // Fallthrough to the next upgrade
491 case 2: // Add referrer column to the database
492 {
493 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
494 "ALTER TABLE moz_downloads "
495 "ADD COLUMN referrer TEXT"));
496 NS_ENSURE_SUCCESS(rv, rv);
498 // Finally, update the schemaVersion variable and the database schema
499 schemaVersion = 3;
500 rv = mDBConn->SetSchemaVersion(schemaVersion);
501 NS_ENSURE_SUCCESS(rv, rv);
502 }
503 // Fallthrough to the next upgrade
505 case 3: // This version adds a column to the database (entityID)
506 {
507 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
508 "ALTER TABLE moz_downloads "
509 "ADD COLUMN entityID TEXT"));
510 NS_ENSURE_SUCCESS(rv, rv);
512 // Finally, update the schemaVersion variable and the database schema
513 schemaVersion = 4;
514 rv = mDBConn->SetSchemaVersion(schemaVersion);
515 NS_ENSURE_SUCCESS(rv, rv);
516 }
517 // Fallthrough to the next upgrade
519 case 4: // This version adds a column to the database (tempPath)
520 {
521 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
522 "ALTER TABLE moz_downloads "
523 "ADD COLUMN tempPath TEXT"));
524 NS_ENSURE_SUCCESS(rv, rv);
526 // Finally, update the schemaVersion variable and the database schema
527 schemaVersion = 5;
528 rv = mDBConn->SetSchemaVersion(schemaVersion);
529 NS_ENSURE_SUCCESS(rv, rv);
530 }
531 // Fallthrough to the next upgrade
533 case 5: // This version adds two columns for tracking transfer progress
534 {
535 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
536 "ALTER TABLE moz_downloads "
537 "ADD COLUMN currBytes INTEGER NOT NULL DEFAULT 0"));
538 NS_ENSURE_SUCCESS(rv, rv);
540 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
541 "ALTER TABLE moz_downloads "
542 "ADD COLUMN maxBytes INTEGER NOT NULL DEFAULT -1"));
543 NS_ENSURE_SUCCESS(rv, rv);
545 // Finally, update the schemaVersion variable and the database schema
546 schemaVersion = 6;
547 rv = mDBConn->SetSchemaVersion(schemaVersion);
548 NS_ENSURE_SUCCESS(rv, rv);
549 }
550 // Fallthrough to the next upgrade
552 case 6: // This version adds three columns to DB (MIME type related info)
553 {
554 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
555 "ALTER TABLE moz_downloads "
556 "ADD COLUMN mimeType TEXT"));
557 NS_ENSURE_SUCCESS(rv, rv);
559 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
560 "ALTER TABLE moz_downloads "
561 "ADD COLUMN preferredApplication TEXT"));
562 NS_ENSURE_SUCCESS(rv, rv);
564 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
565 "ALTER TABLE moz_downloads "
566 "ADD COLUMN preferredAction INTEGER NOT NULL DEFAULT 0"));
567 NS_ENSURE_SUCCESS(rv, rv);
569 // Finally, update the schemaVersion variable and the database schema
570 schemaVersion = 7;
571 rv = mDBConn->SetSchemaVersion(schemaVersion);
572 NS_ENSURE_SUCCESS(rv, rv);
573 }
574 // Fallthrough to next upgrade
576 case 7: // This version adds a column to remember to auto-resume downloads
577 {
578 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
579 "ALTER TABLE moz_downloads "
580 "ADD COLUMN autoResume INTEGER NOT NULL DEFAULT 0"));
581 NS_ENSURE_SUCCESS(rv, rv);
583 // Finally, update the schemaVersion variable and the database schema
584 schemaVersion = 8;
585 rv = mDBConn->SetSchemaVersion(schemaVersion);
586 NS_ENSURE_SUCCESS(rv, rv);
587 }
588 // Fallthrough to the next upgrade
590 // Warning: schema versions >=8 must take into account that they can
591 // be operating on schemas from unknown, future versions that have
592 // been downgraded. Operations such as adding columns may fail,
593 // since the column may already exist.
595 case 8: // This version adds a column for GUIDs
596 {
597 bool exists;
598 rv = mDBConn->IndexExists(NS_LITERAL_CSTRING("moz_downloads_guid_uniqueindex"),
599 &exists);
600 NS_ENSURE_SUCCESS(rv, rv);
601 if (!exists) {
602 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
603 "ALTER TABLE moz_downloads ADD COLUMN guid TEXT"));
604 NS_ENSURE_SUCCESS(rv, rv);
605 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
606 "CREATE UNIQUE INDEX moz_downloads_guid_uniqueindex ON moz_downloads (guid)"));
607 NS_ENSURE_SUCCESS(rv, rv);
608 }
610 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
611 "UPDATE moz_downloads SET guid = GENERATE_GUID() WHERE guid ISNULL"));
612 NS_ENSURE_SUCCESS(rv, rv);
614 // Finally, update the database schema
615 schemaVersion = 9;
616 rv = mDBConn->SetSchemaVersion(schemaVersion);
617 NS_ENSURE_SUCCESS(rv, rv);
618 }
619 // Fallthrough to the next upgrade
621 // Extra sanity checking for developers
622 #ifndef DEBUG
623 case DM_SCHEMA_VERSION:
624 #endif
625 break;
627 case 0:
628 {
629 NS_WARNING("Could not get download database's schema version!");
631 // The table may still be usable - someone may have just messed with the
632 // schema version, so let's just treat this like a downgrade and verify
633 // that the needed columns are there. If they aren't there, we'll drop
634 // the table anyway.
635 rv = mDBConn->SetSchemaVersion(DM_SCHEMA_VERSION);
636 NS_ENSURE_SUCCESS(rv, rv);
637 }
638 // Fallthrough to downgrade check
640 // Downgrading
641 // If columns have been added to the table, we can still use the ones we
642 // understand safely. If columns have been deleted or alterd, we just
643 // drop the table and start from scratch. If you change how a column
644 // should be interpreted, make sure you also change its name so this
645 // check will catch it.
646 default:
647 {
648 nsCOMPtr<mozIStorageStatement> stmt;
649 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
650 "SELECT id, name, source, target, tempPath, startTime, endTime, state, "
651 "referrer, entityID, currBytes, maxBytes, mimeType, "
652 "preferredApplication, preferredAction, autoResume, guid "
653 "FROM moz_downloads"), getter_AddRefs(stmt));
654 if (NS_SUCCEEDED(rv)) {
655 // We have a database that contains all of the elements that make up
656 // the latest known schema. Reset the version to force an upgrade
657 // path if this downgraded database is used in a later version.
658 mDBConn->SetSchemaVersion(DM_SCHEMA_VERSION);
659 break;
660 }
662 // if the statement fails, that means all the columns were not there.
663 // First we backup the database
664 nsCOMPtr<mozIStorageService> storage =
665 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
666 NS_ENSURE_TRUE(storage, NS_ERROR_NOT_AVAILABLE);
667 nsCOMPtr<nsIFile> backup;
668 rv = storage->BackupDatabaseFile(dbFile, DM_DB_CORRUPT_FILENAME, nullptr,
669 getter_AddRefs(backup));
670 NS_ENSURE_SUCCESS(rv, rv);
672 // Then we dump it
673 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
674 "DROP TABLE moz_downloads"));
675 NS_ENSURE_SUCCESS(rv, rv);
677 rv = CreateTable(mDBConn);
678 NS_ENSURE_SUCCESS(rv, rv);
679 }
680 break;
681 }
683 return NS_OK;
684 }
686 nsresult
687 nsDownloadManager::CreateTable(mozIStorageConnection* aDBConn)
688 {
689 nsresult rv = aDBConn->SetSchemaVersion(DM_SCHEMA_VERSION);
690 if (NS_FAILED(rv)) return rv;
692 rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
693 "CREATE TABLE moz_downloads ("
694 "id INTEGER PRIMARY KEY, "
695 "name TEXT, "
696 "source TEXT, "
697 "target TEXT, "
698 "tempPath TEXT, "
699 "startTime INTEGER, "
700 "endTime INTEGER, "
701 "state INTEGER, "
702 "referrer TEXT, "
703 "entityID TEXT, "
704 "currBytes INTEGER NOT NULL DEFAULT 0, "
705 "maxBytes INTEGER NOT NULL DEFAULT -1, "
706 "mimeType TEXT, "
707 "preferredApplication TEXT, "
708 "preferredAction INTEGER NOT NULL DEFAULT 0, "
709 "autoResume INTEGER NOT NULL DEFAULT 0, "
710 "guid TEXT"
711 ")"));
712 if (NS_FAILED(rv)) return rv;
714 rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
715 "CREATE UNIQUE INDEX moz_downloads_guid_uniqueindex "
716 "ON moz_downloads(guid)"));
717 return rv;
718 }
720 nsresult
721 nsDownloadManager::RestoreDatabaseState()
722 {
723 // Restore downloads that were in a scanning state. We can assume that they
724 // have been dealt with by the virus scanner
725 nsCOMPtr<mozIStorageStatement> stmt;
726 nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
727 "UPDATE moz_downloads "
728 "SET state = :state "
729 "WHERE state = :state_cond"), getter_AddRefs(stmt));
730 NS_ENSURE_SUCCESS(rv, rv);
732 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_FINISHED);
733 NS_ENSURE_SUCCESS(rv, rv);
734 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state_cond"), nsIDownloadManager::DOWNLOAD_SCANNING);
735 NS_ENSURE_SUCCESS(rv, rv);
737 rv = stmt->Execute();
738 NS_ENSURE_SUCCESS(rv, rv);
740 // Convert supposedly-active downloads into downloads that should auto-resume
741 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
742 "UPDATE moz_downloads "
743 "SET autoResume = :autoResume "
744 "WHERE state = :notStarted "
745 "OR state = :queued "
746 "OR state = :downloading"), getter_AddRefs(stmt));
747 NS_ENSURE_SUCCESS(rv, rv);
749 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), nsDownload::AUTO_RESUME);
750 NS_ENSURE_SUCCESS(rv, rv);
751 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("notStarted"), nsIDownloadManager::DOWNLOAD_NOTSTARTED);
752 NS_ENSURE_SUCCESS(rv, rv);
753 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("queued"), nsIDownloadManager::DOWNLOAD_QUEUED);
754 NS_ENSURE_SUCCESS(rv, rv);
755 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("downloading"), nsIDownloadManager::DOWNLOAD_DOWNLOADING);
756 NS_ENSURE_SUCCESS(rv, rv);
758 rv = stmt->Execute();
759 NS_ENSURE_SUCCESS(rv, rv);
761 // Switch any download that is supposed to automatically resume and is in a
762 // finished state to *not* automatically resume. See Bug 409179 for details.
763 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
764 "UPDATE moz_downloads "
765 "SET autoResume = :autoResume "
766 "WHERE state = :state "
767 "AND autoResume = :autoResume_cond"),
768 getter_AddRefs(stmt));
769 NS_ENSURE_SUCCESS(rv, rv);
771 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), nsDownload::DONT_RESUME);
772 NS_ENSURE_SUCCESS(rv, rv);
773 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_FINISHED);
774 NS_ENSURE_SUCCESS(rv, rv);
775 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume_cond"), nsDownload::AUTO_RESUME);
776 NS_ENSURE_SUCCESS(rv, rv);
778 rv = stmt->Execute();
779 NS_ENSURE_SUCCESS(rv, rv);
781 return NS_OK;
782 }
784 nsresult
785 nsDownloadManager::RestoreActiveDownloads()
786 {
787 nsCOMPtr<mozIStorageStatement> stmt;
788 nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
789 "SELECT id "
790 "FROM moz_downloads "
791 "WHERE (state = :state AND LENGTH(entityID) > 0) "
792 "OR autoResume != :autoResume"), getter_AddRefs(stmt));
793 NS_ENSURE_SUCCESS(rv, rv);
795 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_PAUSED);
796 NS_ENSURE_SUCCESS(rv, rv);
797 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), nsDownload::DONT_RESUME);
798 NS_ENSURE_SUCCESS(rv, rv);
800 nsresult retVal = NS_OK;
801 bool hasResults;
802 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResults)) && hasResults) {
803 nsRefPtr<nsDownload> dl;
804 // Keep trying to add even if we fail one, but make sure to return failure.
805 // Additionally, be careful to not call anything that tries to change the
806 // database because we're iterating over a live statement.
807 if (NS_FAILED(GetDownloadFromDB(stmt->AsInt32(0), getter_AddRefs(dl))) ||
808 NS_FAILED(AddToCurrentDownloads(dl)))
809 retVal = NS_ERROR_FAILURE;
810 }
812 // Try to resume only the downloads that should auto-resume
813 rv = ResumeAllDownloads(false);
814 NS_ENSURE_SUCCESS(rv, rv);
816 return retVal;
817 }
819 int64_t
820 nsDownloadManager::AddDownloadToDB(const nsAString &aName,
821 const nsACString &aSource,
822 const nsACString &aTarget,
823 const nsAString &aTempPath,
824 int64_t aStartTime,
825 int64_t aEndTime,
826 const nsACString &aMimeType,
827 const nsACString &aPreferredApp,
828 nsHandlerInfoAction aPreferredAction,
829 bool aPrivate,
830 nsACString& aNewGUID)
831 {
832 mozIStorageConnection* dbConn = aPrivate ? mPrivateDBConn : mDBConn;
833 nsCOMPtr<mozIStorageStatement> stmt;
834 nsresult rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
835 "INSERT INTO moz_downloads "
836 "(name, source, target, tempPath, startTime, endTime, state, "
837 "mimeType, preferredApplication, preferredAction, guid) VALUES "
838 "(:name, :source, :target, :tempPath, :startTime, :endTime, :state, "
839 ":mimeType, :preferredApplication, :preferredAction, :guid)"),
840 getter_AddRefs(stmt));
841 NS_ENSURE_SUCCESS(rv, 0);
843 rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName);
844 NS_ENSURE_SUCCESS(rv, 0);
846 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("source"), aSource);
847 NS_ENSURE_SUCCESS(rv, 0);
849 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("target"), aTarget);
850 NS_ENSURE_SUCCESS(rv, 0);
852 rv = stmt->BindStringByName(NS_LITERAL_CSTRING("tempPath"), aTempPath);
853 NS_ENSURE_SUCCESS(rv, 0);
855 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startTime"), aStartTime);
856 NS_ENSURE_SUCCESS(rv, 0);
858 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("endTime"), aEndTime);
859 NS_ENSURE_SUCCESS(rv, 0);
861 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_NOTSTARTED);
862 NS_ENSURE_SUCCESS(rv, 0);
864 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("mimeType"), aMimeType);
865 NS_ENSURE_SUCCESS(rv, 0);
867 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("preferredApplication"), aPreferredApp);
868 NS_ENSURE_SUCCESS(rv, 0);
870 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("preferredAction"), aPreferredAction);
871 NS_ENSURE_SUCCESS(rv, 0);
873 nsAutoCString guid;
874 rv = GenerateGUID(guid);
875 NS_ENSURE_SUCCESS(rv, 0);
876 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
877 NS_ENSURE_SUCCESS(rv, 0);
879 bool hasMore;
880 rv = stmt->ExecuteStep(&hasMore); // we want to keep our lock
881 NS_ENSURE_SUCCESS(rv, 0);
883 int64_t id = 0;
884 rv = dbConn->GetLastInsertRowID(&id);
885 NS_ENSURE_SUCCESS(rv, 0);
887 aNewGUID = guid;
889 // lock on DB from statement will be released once we return
890 return id;
891 }
893 nsresult
894 nsDownloadManager::InitDB()
895 {
896 nsresult rv = InitPrivateDB();
897 NS_ENSURE_SUCCESS(rv, rv);
899 rv = InitFileDB();
900 NS_ENSURE_SUCCESS(rv, rv);
902 rv = InitStatements(mDBConn, getter_AddRefs(mUpdateDownloadStatement),
903 getter_AddRefs(mGetIdsForURIStatement));
904 NS_ENSURE_SUCCESS(rv, rv);
905 return NS_OK;
906 }
908 nsresult
909 nsDownloadManager::InitStatements(mozIStorageConnection* aDBConn,
910 mozIStorageStatement** aUpdateStatement,
911 mozIStorageStatement** aGetIdsStatement)
912 {
913 nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
914 "UPDATE moz_downloads "
915 "SET tempPath = :tempPath, startTime = :startTime, endTime = :endTime, "
916 "state = :state, referrer = :referrer, entityID = :entityID, "
917 "currBytes = :currBytes, maxBytes = :maxBytes, autoResume = :autoResume "
918 "WHERE id = :id"), aUpdateStatement);
919 NS_ENSURE_SUCCESS(rv, rv);
921 rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
922 "SELECT guid "
923 "FROM moz_downloads "
924 "WHERE source = :source"), aGetIdsStatement);
925 NS_ENSURE_SUCCESS(rv, rv);
927 return NS_OK;
928 }
930 nsresult
931 nsDownloadManager::Init()
932 {
933 nsresult rv;
935 nsCOMPtr<nsIStringBundleService> bundleService =
936 mozilla::services::GetStringBundleService();
937 if (!bundleService)
938 return NS_ERROR_FAILURE;
940 rv = bundleService->CreateBundle(DOWNLOAD_MANAGER_BUNDLE,
941 getter_AddRefs(mBundle));
942 NS_ENSURE_SUCCESS(rv, rv);
944 #if !defined(MOZ_JSDOWNLOADS)
945 // When MOZ_JSDOWNLOADS is undefined, we still check the preference that can
946 // be used to enable the JavaScript API during the migration process.
947 mUseJSTransfer = Preferences::GetBool(PREF_BD_USEJSTRANSFER, false);
948 #else
950 nsAutoCString appID;
951 nsCOMPtr<nsIXULAppInfo> info = do_GetService("@mozilla.org/xre/app-info;1");
952 if (info) {
953 rv = info->GetID(appID);
954 NS_ENSURE_SUCCESS(rv, rv);
955 }
957 // The webapp runtime doesn't use the new JS downloads API yet.
958 // The conversion of the webapp runtime to use the JavaScript API for
959 // downloads is tracked in bug 911636.
960 if (appID.EqualsLiteral("webapprt@mozilla.org")) {
961 mUseJSTransfer = false;
962 } else {
963 #if !defined(XP_WIN)
964 mUseJSTransfer = true;
965 #else
966 // When MOZ_JSDOWNLOADS is defined on Windows, this component is disabled
967 // unless we are running in Windows Metro. The conversion of Windows Metro
968 // to use the JavaScript API for downloads is tracked in bug 906042.
969 mUseJSTransfer = !IsRunningInWindowsMetro();
970 #endif
971 }
973 #endif
975 if (mUseJSTransfer)
976 return NS_OK;
978 // Clean up any old downloads.rdf files from before Firefox 3
979 {
980 nsCOMPtr<nsIFile> oldDownloadsFile;
981 bool fileExists;
982 if (NS_SUCCEEDED(NS_GetSpecialDirectory(NS_APP_DOWNLOADS_50_FILE,
983 getter_AddRefs(oldDownloadsFile))) &&
984 NS_SUCCEEDED(oldDownloadsFile->Exists(&fileExists)) &&
985 fileExists) {
986 (void)oldDownloadsFile->Remove(false);
987 }
988 }
990 mObserverService = mozilla::services::GetObserverService();
991 if (!mObserverService)
992 return NS_ERROR_FAILURE;
994 rv = InitDB();
995 NS_ENSURE_SUCCESS(rv, rv);
997 #ifdef DOWNLOAD_SCANNER
998 mScanner = new nsDownloadScanner();
999 if (!mScanner)
1000 return NS_ERROR_OUT_OF_MEMORY;
1001 rv = mScanner->Init();
1002 if (NS_FAILED(rv)) {
1003 delete mScanner;
1004 mScanner = nullptr;
1005 }
1006 #endif
1008 // Do things *after* initializing various download manager properties such as
1009 // restoring downloads to a consistent state
1010 rv = RestoreDatabaseState();
1011 NS_ENSURE_SUCCESS(rv, rv);
1013 rv = RestoreActiveDownloads();
1014 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to restore all active downloads");
1016 nsCOMPtr<nsINavHistoryService> history =
1017 do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
1019 (void)mObserverService->NotifyObservers(
1020 static_cast<nsIDownloadManager *>(this),
1021 "download-manager-initialized",
1022 nullptr);
1024 // The following AddObserver calls must be the last lines in this function,
1025 // because otherwise, this function may fail (and thus, this object would be not
1026 // completely initialized), but the observerservice would still keep a reference
1027 // to us and notify us about shutdown, which may cause crashes.
1028 // failure to add an observer is not critical
1029 (void)mObserverService->AddObserver(this, "quit-application", true);
1030 (void)mObserverService->AddObserver(this, "quit-application-requested", true);
1031 (void)mObserverService->AddObserver(this, "offline-requested", true);
1032 (void)mObserverService->AddObserver(this, "sleep_notification", true);
1033 (void)mObserverService->AddObserver(this, "wake_notification", true);
1034 (void)mObserverService->AddObserver(this, "suspend_process_notification", true);
1035 (void)mObserverService->AddObserver(this, "resume_process_notification", true);
1036 (void)mObserverService->AddObserver(this, "profile-before-change", true);
1037 (void)mObserverService->AddObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC, true);
1038 (void)mObserverService->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, true);
1039 (void)mObserverService->AddObserver(this, "last-pb-context-exited", true);
1040 (void)mObserverService->AddObserver(this, "last-pb-context-exiting", true);
1042 if (history)
1043 (void)history->AddObserver(this, true);
1045 return NS_OK;
1046 }
1048 int32_t
1049 nsDownloadManager::GetRetentionBehavior()
1050 {
1051 // We use 0 as the default, which is "remove when done"
1052 nsresult rv;
1053 nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
1054 NS_ENSURE_SUCCESS(rv, 0);
1056 int32_t val;
1057 rv = pref->GetIntPref(PREF_BDM_RETENTION, &val);
1058 NS_ENSURE_SUCCESS(rv, 0);
1060 // Allow the Downloads Panel to change the retention behavior. We do this to
1061 // allow proper migration to the new feature when using the same profile on
1062 // multiple versions of the product (bug 697678). Implementation note: in
1063 // order to allow observers to change the retention value, we have to pass an
1064 // object in the aSubject parameter, we cannot use aData for that.
1065 nsCOMPtr<nsISupportsPRInt32> retentionBehavior =
1066 do_CreateInstance(NS_SUPPORTS_PRINT32_CONTRACTID);
1067 retentionBehavior->SetData(val);
1068 (void)mObserverService->NotifyObservers(retentionBehavior,
1069 "download-manager-change-retention",
1070 nullptr);
1071 retentionBehavior->GetData(&val);
1073 return val;
1074 }
1076 enum nsDownloadManager::QuitBehavior
1077 nsDownloadManager::GetQuitBehavior()
1078 {
1079 // We use 0 as the default, which is "remember and resume the download"
1080 nsresult rv;
1081 nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
1082 NS_ENSURE_SUCCESS(rv, QUIT_AND_RESUME);
1084 int32_t val;
1085 rv = pref->GetIntPref(PREF_BDM_QUITBEHAVIOR, &val);
1086 NS_ENSURE_SUCCESS(rv, QUIT_AND_RESUME);
1088 switch (val) {
1089 case 1:
1090 return QUIT_AND_PAUSE;
1091 case 2:
1092 return QUIT_AND_CANCEL;
1093 default:
1094 return QUIT_AND_RESUME;
1095 }
1096 }
1098 // Using a globally-unique GUID, search all databases (both private and public).
1099 // A return value of NS_ERROR_NOT_AVAILABLE means no download with the given GUID
1100 // could be found, either private or public.
1102 nsresult
1103 nsDownloadManager::GetDownloadFromDB(const nsACString& aGUID, nsDownload **retVal)
1104 {
1105 MOZ_ASSERT(!FindDownload(aGUID),
1106 "If it is a current download, you should not call this method!");
1108 NS_NAMED_LITERAL_CSTRING(query,
1109 "SELECT id, state, startTime, source, target, tempPath, name, referrer, "
1110 "entityID, currBytes, maxBytes, mimeType, preferredAction, "
1111 "preferredApplication, autoResume, guid "
1112 "FROM moz_downloads "
1113 "WHERE guid = :guid");
1114 // First, let's query the database and see if it even exists
1115 nsCOMPtr<mozIStorageStatement> stmt;
1116 nsresult rv = mDBConn->CreateStatement(query, getter_AddRefs(stmt));
1117 NS_ENSURE_SUCCESS(rv, rv);
1119 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
1120 NS_ENSURE_SUCCESS(rv, rv);
1122 rv = GetDownloadFromDB(mDBConn, stmt, retVal);
1124 // If the download cannot be found in the public database, try again
1125 // in the private one. Otherwise, return whatever successful result
1126 // or failure obtained from the public database.
1127 if (rv == NS_ERROR_NOT_AVAILABLE) {
1128 rv = mPrivateDBConn->CreateStatement(query, getter_AddRefs(stmt));
1129 NS_ENSURE_SUCCESS(rv, rv);
1131 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
1132 NS_ENSURE_SUCCESS(rv, rv);
1134 rv = GetDownloadFromDB(mPrivateDBConn, stmt, retVal);
1136 // Only if it still cannot be found do we report the failure.
1137 if (rv == NS_ERROR_NOT_AVAILABLE) {
1138 *retVal = nullptr;
1139 }
1140 }
1141 return rv;
1142 }
1144 nsresult
1145 nsDownloadManager::GetDownloadFromDB(uint32_t aID, nsDownload **retVal)
1146 {
1147 NS_WARNING("Using integer IDs without compat mode enabled");
1149 MOZ_ASSERT(!FindDownload(aID),
1150 "If it is a current download, you should not call this method!");
1152 // First, let's query the database and see if it even exists
1153 nsCOMPtr<mozIStorageStatement> stmt;
1154 nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
1155 "SELECT id, state, startTime, source, target, tempPath, name, referrer, "
1156 "entityID, currBytes, maxBytes, mimeType, preferredAction, "
1157 "preferredApplication, autoResume, guid "
1158 "FROM moz_downloads "
1159 "WHERE id = :id"), getter_AddRefs(stmt));
1160 NS_ENSURE_SUCCESS(rv, rv);
1162 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aID);
1163 NS_ENSURE_SUCCESS(rv, rv);
1165 return GetDownloadFromDB(mDBConn, stmt, retVal);
1166 }
1168 nsresult
1169 nsDownloadManager::GetDownloadFromDB(mozIStorageConnection* aDBConn,
1170 mozIStorageStatement* stmt,
1171 nsDownload **retVal)
1172 {
1173 bool hasResults = false;
1174 nsresult rv = stmt->ExecuteStep(&hasResults);
1175 if (NS_FAILED(rv) || !hasResults)
1176 return NS_ERROR_NOT_AVAILABLE;
1178 // We have a download, so lets create it
1179 nsRefPtr<nsDownload> dl = new nsDownload();
1180 if (!dl)
1181 return NS_ERROR_OUT_OF_MEMORY;
1182 dl->mPrivate = aDBConn == mPrivateDBConn;
1184 dl->mDownloadManager = this;
1186 int32_t i = 0;
1187 // Setting all properties of the download now
1188 dl->mCancelable = nullptr;
1189 dl->mID = stmt->AsInt64(i++);
1190 dl->mDownloadState = stmt->AsInt32(i++);
1191 dl->mStartTime = stmt->AsInt64(i++);
1193 nsCString source;
1194 stmt->GetUTF8String(i++, source);
1195 rv = NS_NewURI(getter_AddRefs(dl->mSource), source);
1196 NS_ENSURE_SUCCESS(rv, rv);
1198 nsCString target;
1199 stmt->GetUTF8String(i++, target);
1200 rv = NS_NewURI(getter_AddRefs(dl->mTarget), target);
1201 NS_ENSURE_SUCCESS(rv, rv);
1203 nsString tempPath;
1204 stmt->GetString(i++, tempPath);
1205 if (!tempPath.IsEmpty()) {
1206 rv = NS_NewLocalFile(tempPath, true, getter_AddRefs(dl->mTempFile));
1207 NS_ENSURE_SUCCESS(rv, rv);
1208 }
1210 stmt->GetString(i++, dl->mDisplayName);
1212 nsCString referrer;
1213 rv = stmt->GetUTF8String(i++, referrer);
1214 if (NS_SUCCEEDED(rv) && !referrer.IsEmpty()) {
1215 rv = NS_NewURI(getter_AddRefs(dl->mReferrer), referrer);
1216 NS_ENSURE_SUCCESS(rv, rv);
1217 }
1219 rv = stmt->GetUTF8String(i++, dl->mEntityID);
1220 NS_ENSURE_SUCCESS(rv, rv);
1222 int64_t currBytes = stmt->AsInt64(i++);
1223 int64_t maxBytes = stmt->AsInt64(i++);
1224 dl->SetProgressBytes(currBytes, maxBytes);
1226 // Build mMIMEInfo only if the mimeType in DB is not empty
1227 nsAutoCString mimeType;
1228 rv = stmt->GetUTF8String(i++, mimeType);
1229 NS_ENSURE_SUCCESS(rv, rv);
1231 if (!mimeType.IsEmpty()) {
1232 nsCOMPtr<nsIMIMEService> mimeService =
1233 do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
1234 NS_ENSURE_SUCCESS(rv, rv);
1236 rv = mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(),
1237 getter_AddRefs(dl->mMIMEInfo));
1238 NS_ENSURE_SUCCESS(rv, rv);
1240 nsHandlerInfoAction action = stmt->AsInt32(i++);
1241 rv = dl->mMIMEInfo->SetPreferredAction(action);
1242 NS_ENSURE_SUCCESS(rv, rv);
1244 nsAutoCString persistentDescriptor;
1245 rv = stmt->GetUTF8String(i++, persistentDescriptor);
1246 NS_ENSURE_SUCCESS(rv, rv);
1248 if (!persistentDescriptor.IsEmpty()) {
1249 nsCOMPtr<nsILocalHandlerApp> handler =
1250 do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv);
1251 NS_ENSURE_SUCCESS(rv, rv);
1253 nsCOMPtr<nsIFile> localExecutable;
1254 rv = NS_NewNativeLocalFile(EmptyCString(), false,
1255 getter_AddRefs(localExecutable));
1256 NS_ENSURE_SUCCESS(rv, rv);
1258 rv = localExecutable->SetPersistentDescriptor(persistentDescriptor);
1259 NS_ENSURE_SUCCESS(rv, rv);
1261 rv = handler->SetExecutable(localExecutable);
1262 NS_ENSURE_SUCCESS(rv, rv);
1264 rv = dl->mMIMEInfo->SetPreferredApplicationHandler(handler);
1265 NS_ENSURE_SUCCESS(rv, rv);
1266 }
1267 } else {
1268 // Compensate for the i++s skipped in the true block
1269 i += 2;
1270 }
1272 dl->mAutoResume =
1273 static_cast<enum nsDownload::AutoResume>(stmt->AsInt32(i++));
1275 rv = stmt->GetUTF8String(i++, dl->mGUID);
1276 NS_ENSURE_SUCCESS(rv, rv);
1278 // Handle situations where we load a download from a database that has been
1279 // used in an older version and not gone through the upgrade path (ie. it
1280 // contains empty GUID entries).
1281 if (dl->mGUID.IsEmpty()) {
1282 rv = GenerateGUID(dl->mGUID);
1283 NS_ENSURE_SUCCESS(rv, rv);
1284 nsCOMPtr<mozIStorageStatement> stmt;
1285 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
1286 "UPDATE moz_downloads SET guid = :guid "
1287 "WHERE id = :id"),
1288 getter_AddRefs(stmt));
1289 NS_ENSURE_SUCCESS(rv, rv);
1290 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), dl->mGUID);
1291 NS_ENSURE_SUCCESS(rv, rv);
1292 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), dl->mID);
1293 NS_ENSURE_SUCCESS(rv, rv);
1294 rv = stmt->Execute();
1295 NS_ENSURE_SUCCESS(rv, rv);
1296 }
1298 // Addrefing and returning
1299 NS_ADDREF(*retVal = dl);
1300 return NS_OK;
1301 }
1303 nsresult
1304 nsDownloadManager::AddToCurrentDownloads(nsDownload *aDl)
1305 {
1306 nsCOMArray<nsDownload>& currentDownloads =
1307 aDl->mPrivate ? mCurrentPrivateDownloads : mCurrentDownloads;
1308 if (!currentDownloads.AppendObject(aDl))
1309 return NS_ERROR_OUT_OF_MEMORY;
1311 aDl->mDownloadManager = this;
1312 return NS_OK;
1313 }
1315 void
1316 nsDownloadManager::SendEvent(nsDownload *aDownload, const char *aTopic)
1317 {
1318 (void)mObserverService->NotifyObservers(aDownload, aTopic, nullptr);
1319 }
1321 ////////////////////////////////////////////////////////////////////////////////
1322 //// nsIDownloadManager
1324 NS_IMETHODIMP
1325 nsDownloadManager::GetActivePrivateDownloadCount(int32_t* aResult)
1326 {
1327 NS_ENSURE_STATE(!mUseJSTransfer);
1329 *aResult = mCurrentPrivateDownloads.Count();
1330 return NS_OK;
1331 }
1333 NS_IMETHODIMP
1334 nsDownloadManager::GetActiveDownloadCount(int32_t *aResult)
1335 {
1336 NS_ENSURE_STATE(!mUseJSTransfer);
1338 *aResult = mCurrentDownloads.Count();
1340 return NS_OK;
1341 }
1343 NS_IMETHODIMP
1344 nsDownloadManager::GetActiveDownloads(nsISimpleEnumerator **aResult)
1345 {
1346 NS_ENSURE_STATE(!mUseJSTransfer);
1348 return NS_NewArrayEnumerator(aResult, mCurrentDownloads);
1349 }
1351 NS_IMETHODIMP
1352 nsDownloadManager::GetActivePrivateDownloads(nsISimpleEnumerator **aResult)
1353 {
1354 NS_ENSURE_STATE(!mUseJSTransfer);
1356 return NS_NewArrayEnumerator(aResult, mCurrentPrivateDownloads);
1357 }
1359 /**
1360 * For platforms where helper apps use the downloads directory (i.e. mobile),
1361 * this should be kept in sync with nsExternalHelperAppService.cpp
1362 */
1363 NS_IMETHODIMP
1364 nsDownloadManager::GetDefaultDownloadsDirectory(nsIFile **aResult)
1365 {
1366 nsCOMPtr<nsIFile> downloadDir;
1368 nsresult rv;
1369 nsCOMPtr<nsIProperties> dirService =
1370 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
1371 NS_ENSURE_SUCCESS(rv, rv);
1373 // OSX 10.4:
1374 // Desktop
1375 // OSX 10.5:
1376 // User download directory
1377 // Vista:
1378 // Downloads
1379 // XP/2K:
1380 // My Documents/Downloads
1381 // Linux:
1382 // XDG user dir spec, with a fallback to Home/Downloads
1384 nsXPIDLString folderName;
1385 mBundle->GetStringFromName(MOZ_UTF16("downloadsFolder"),
1386 getter_Copies(folderName));
1388 #if defined (XP_MACOSX)
1389 rv = dirService->Get(NS_OSX_DEFAULT_DOWNLOAD_DIR,
1390 NS_GET_IID(nsIFile),
1391 getter_AddRefs(downloadDir));
1392 NS_ENSURE_SUCCESS(rv, rv);
1393 #elif defined(XP_WIN)
1394 rv = dirService->Get(NS_WIN_DEFAULT_DOWNLOAD_DIR,
1395 NS_GET_IID(nsIFile),
1396 getter_AddRefs(downloadDir));
1397 NS_ENSURE_SUCCESS(rv, rv);
1399 // Check the os version
1400 nsCOMPtr<nsIPropertyBag2> infoService =
1401 do_GetService(NS_SYSTEMINFO_CONTRACTID, &rv);
1402 NS_ENSURE_SUCCESS(rv, rv);
1404 int32_t version;
1405 NS_NAMED_LITERAL_STRING(osVersion, "version");
1406 rv = infoService->GetPropertyAsInt32(osVersion, &version);
1407 NS_ENSURE_SUCCESS(rv, rv);
1408 if (version < 6) { // XP/2K
1409 // First get "My Documents"
1410 rv = dirService->Get(NS_WIN_PERSONAL_DIR,
1411 NS_GET_IID(nsIFile),
1412 getter_AddRefs(downloadDir));
1413 NS_ENSURE_SUCCESS(rv, rv);
1415 rv = downloadDir->Append(folderName);
1416 NS_ENSURE_SUCCESS(rv, rv);
1418 // This could be the first time we are creating the downloads folder in My
1419 // Documents, so make sure it exists.
1420 bool exists;
1421 rv = downloadDir->Exists(&exists);
1422 NS_ENSURE_SUCCESS(rv, rv);
1423 if (!exists) {
1424 rv = downloadDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
1425 NS_ENSURE_SUCCESS(rv, rv);
1426 }
1427 }
1428 #elif defined(XP_UNIX)
1429 #if defined(MOZ_WIDGET_ANDROID)
1430 // Android doesn't have a $HOME directory, and by default we only have
1431 // write access to /data/data/org.mozilla.{$APP} and /sdcard
1432 char* downloadDirPath = getenv("DOWNLOADS_DIRECTORY");
1433 if (downloadDirPath) {
1434 rv = NS_NewNativeLocalFile(nsDependentCString(downloadDirPath),
1435 true, getter_AddRefs(downloadDir));
1436 NS_ENSURE_SUCCESS(rv, rv);
1437 }
1438 else {
1439 rv = NS_ERROR_FAILURE;
1440 }
1441 #else
1442 rv = dirService->Get(NS_UNIX_DEFAULT_DOWNLOAD_DIR,
1443 NS_GET_IID(nsIFile),
1444 getter_AddRefs(downloadDir));
1445 // fallback to Home/Downloads
1446 if (NS_FAILED(rv)) {
1447 rv = dirService->Get(NS_UNIX_HOME_DIR,
1448 NS_GET_IID(nsIFile),
1449 getter_AddRefs(downloadDir));
1450 NS_ENSURE_SUCCESS(rv, rv);
1451 rv = downloadDir->Append(folderName);
1452 NS_ENSURE_SUCCESS(rv, rv);
1453 }
1454 #endif
1455 #else
1456 rv = dirService->Get(NS_OS_HOME_DIR,
1457 NS_GET_IID(nsIFile),
1458 getter_AddRefs(downloadDir));
1459 NS_ENSURE_SUCCESS(rv, rv);
1460 rv = downloadDir->Append(folderName);
1461 NS_ENSURE_SUCCESS(rv, rv);
1462 #endif
1464 downloadDir.forget(aResult);
1466 return NS_OK;
1467 }
1469 #define NS_BRANCH_DOWNLOAD "browser.download."
1470 #define NS_PREF_FOLDERLIST "folderList"
1471 #define NS_PREF_DIR "dir"
1473 NS_IMETHODIMP
1474 nsDownloadManager::GetUserDownloadsDirectory(nsIFile **aResult)
1475 {
1476 nsresult rv;
1477 nsCOMPtr<nsIProperties> dirService =
1478 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
1479 NS_ENSURE_SUCCESS(rv, rv);
1481 nsCOMPtr<nsIPrefService> prefService =
1482 do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
1483 NS_ENSURE_SUCCESS(rv, rv);
1485 nsCOMPtr<nsIPrefBranch> prefBranch;
1486 rv = prefService->GetBranch(NS_BRANCH_DOWNLOAD,
1487 getter_AddRefs(prefBranch));
1488 NS_ENSURE_SUCCESS(rv, rv);
1490 int32_t val;
1491 rv = prefBranch->GetIntPref(NS_PREF_FOLDERLIST,
1492 &val);
1493 NS_ENSURE_SUCCESS(rv, rv);
1495 switch(val) {
1496 case 0: // Desktop
1497 {
1498 nsCOMPtr<nsIFile> downloadDir;
1499 nsCOMPtr<nsIProperties> dirService =
1500 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
1501 NS_ENSURE_SUCCESS(rv, rv);
1502 rv = dirService->Get(NS_OS_DESKTOP_DIR,
1503 NS_GET_IID(nsIFile),
1504 getter_AddRefs(downloadDir));
1505 NS_ENSURE_SUCCESS(rv, rv);
1506 downloadDir.forget(aResult);
1507 return NS_OK;
1508 }
1509 break;
1510 case 1: // Downloads
1511 return GetDefaultDownloadsDirectory(aResult);
1512 case 2: // Custom
1513 {
1514 nsCOMPtr<nsIFile> customDirectory;
1515 prefBranch->GetComplexValue(NS_PREF_DIR,
1516 NS_GET_IID(nsIFile),
1517 getter_AddRefs(customDirectory));
1518 if (customDirectory) {
1519 bool exists = false;
1520 (void)customDirectory->Exists(&exists);
1522 if (!exists) {
1523 rv = customDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
1524 if (NS_SUCCEEDED(rv)) {
1525 customDirectory.forget(aResult);
1526 return NS_OK;
1527 }
1529 // Create failed, so it still doesn't exist. Fall out and get the
1530 // default downloads directory.
1531 }
1533 bool writable = false;
1534 bool directory = false;
1535 (void)customDirectory->IsWritable(&writable);
1536 (void)customDirectory->IsDirectory(&directory);
1538 if (exists && writable && directory) {
1539 customDirectory.forget(aResult);
1540 return NS_OK;
1541 }
1542 }
1543 rv = GetDefaultDownloadsDirectory(aResult);
1544 if (NS_SUCCEEDED(rv)) {
1545 (void)prefBranch->SetComplexValue(NS_PREF_DIR,
1546 NS_GET_IID(nsIFile),
1547 *aResult);
1548 }
1549 return rv;
1550 }
1551 break;
1552 }
1553 return NS_ERROR_INVALID_ARG;
1554 }
1556 NS_IMETHODIMP
1557 nsDownloadManager::AddDownload(DownloadType aDownloadType,
1558 nsIURI *aSource,
1559 nsIURI *aTarget,
1560 const nsAString& aDisplayName,
1561 nsIMIMEInfo *aMIMEInfo,
1562 PRTime aStartTime,
1563 nsIFile *aTempFile,
1564 nsICancelable *aCancelable,
1565 bool aIsPrivate,
1566 nsIDownload **aDownload)
1567 {
1568 NS_ENSURE_STATE(!mUseJSTransfer);
1570 NS_ENSURE_ARG_POINTER(aSource);
1571 NS_ENSURE_ARG_POINTER(aTarget);
1572 NS_ENSURE_ARG_POINTER(aDownload);
1574 nsresult rv;
1576 // target must be on the local filesystem
1577 nsCOMPtr<nsIFileURL> targetFileURL = do_QueryInterface(aTarget, &rv);
1578 NS_ENSURE_SUCCESS(rv, rv);
1580 nsCOMPtr<nsIFile> targetFile;
1581 rv = targetFileURL->GetFile(getter_AddRefs(targetFile));
1582 NS_ENSURE_SUCCESS(rv, rv);
1584 nsRefPtr<nsDownload> dl = new nsDownload();
1585 if (!dl)
1586 return NS_ERROR_OUT_OF_MEMORY;
1588 // give our new nsIDownload some info so it's ready to go off into the world
1589 dl->mTarget = aTarget;
1590 dl->mSource = aSource;
1591 dl->mTempFile = aTempFile;
1592 dl->mPrivate = aIsPrivate;
1594 dl->mDisplayName = aDisplayName;
1595 if (dl->mDisplayName.IsEmpty())
1596 targetFile->GetLeafName(dl->mDisplayName);
1598 dl->mMIMEInfo = aMIMEInfo;
1599 dl->SetStartTime(aStartTime == 0 ? PR_Now() : aStartTime);
1601 // Creates a cycle that will be broken when the download finishes
1602 dl->mCancelable = aCancelable;
1604 // Adding to the DB
1605 nsAutoCString source, target;
1606 aSource->GetSpec(source);
1607 aTarget->GetSpec(target);
1609 // Track the temp file for exthandler downloads
1610 nsAutoString tempPath;
1611 if (aTempFile)
1612 aTempFile->GetPath(tempPath);
1614 // Break down MIMEInfo but don't panic if we can't get all the pieces - we
1615 // can still download the file
1616 nsAutoCString persistentDescriptor, mimeType;
1617 nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk;
1618 if (aMIMEInfo) {
1619 (void)aMIMEInfo->GetType(mimeType);
1621 nsCOMPtr<nsIHandlerApp> handlerApp;
1622 (void)aMIMEInfo->GetPreferredApplicationHandler(getter_AddRefs(handlerApp));
1623 nsCOMPtr<nsILocalHandlerApp> locHandlerApp = do_QueryInterface(handlerApp);
1625 if (locHandlerApp) {
1626 nsCOMPtr<nsIFile> executable;
1627 (void)locHandlerApp->GetExecutable(getter_AddRefs(executable));
1628 (void)executable->GetPersistentDescriptor(persistentDescriptor);
1629 }
1631 (void)aMIMEInfo->GetPreferredAction(&action);
1632 }
1634 int64_t id = AddDownloadToDB(dl->mDisplayName, source, target, tempPath,
1635 dl->mStartTime, dl->mLastUpdate,
1636 mimeType, persistentDescriptor, action,
1637 dl->mPrivate, dl->mGUID /* outparam */);
1638 NS_ENSURE_TRUE(id, NS_ERROR_FAILURE);
1639 dl->mID = id;
1641 rv = AddToCurrentDownloads(dl);
1642 (void)dl->SetState(nsIDownloadManager::DOWNLOAD_QUEUED);
1643 NS_ENSURE_SUCCESS(rv, rv);
1645 #ifdef DOWNLOAD_SCANNER
1646 if (mScanner) {
1647 bool scan = true;
1648 nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
1649 if (prefs) {
1650 (void)prefs->GetBoolPref(PREF_BDM_SCANWHENDONE, &scan);
1651 }
1652 // We currently apply local security policy to downloads when we scan
1653 // via windows all-in-one download security api. The CheckPolicy call
1654 // below is a pre-emptive part of that process. So tie applying security
1655 // zone policy settings when downloads are intiated to the same pref
1656 // that triggers applying security zone policy settings after a download
1657 // completes. (bug 504804)
1658 if (scan) {
1659 AVCheckPolicyState res = mScanner->CheckPolicy(aSource, aTarget);
1660 if (res == AVPOLICY_BLOCKED) {
1661 // This download will get deleted during a call to IAE's Save,
1662 // so go ahead and mark it as blocked and avoid the download.
1663 (void)CancelDownload(id);
1664 (void)dl->SetState(nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY);
1665 }
1666 }
1667 }
1668 #endif
1670 // Check with parental controls to see if file downloads
1671 // are allowed for this user. If not allowed, cancel the
1672 // download and mark its state as being blocked.
1673 nsCOMPtr<nsIParentalControlsService> pc =
1674 do_CreateInstance(NS_PARENTALCONTROLSSERVICE_CONTRACTID);
1675 if (pc) {
1676 bool enabled = false;
1677 (void)pc->GetBlockFileDownloadsEnabled(&enabled);
1678 if (enabled) {
1679 (void)CancelDownload(id);
1680 (void)dl->SetState(nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL);
1681 }
1683 // Log the event if required by pc settings.
1684 bool logEnabled = false;
1685 (void)pc->GetLoggingEnabled(&logEnabled);
1686 if (logEnabled) {
1687 (void)pc->Log(nsIParentalControlsService::ePCLog_FileDownload,
1688 enabled,
1689 aSource,
1690 nullptr);
1691 }
1692 }
1694 NS_ADDREF(*aDownload = dl);
1696 return NS_OK;
1697 }
1699 NS_IMETHODIMP
1700 nsDownloadManager::GetDownload(uint32_t aID, nsIDownload **aDownloadItem)
1701 {
1702 NS_ENSURE_STATE(!mUseJSTransfer);
1704 NS_WARNING("Using integer IDs without compat mode enabled");
1706 nsDownload *itm = FindDownload(aID);
1708 nsRefPtr<nsDownload> dl;
1709 if (!itm) {
1710 nsresult rv = GetDownloadFromDB(aID, getter_AddRefs(dl));
1711 NS_ENSURE_SUCCESS(rv, rv);
1713 itm = dl.get();
1714 }
1716 NS_ADDREF(*aDownloadItem = itm);
1718 return NS_OK;
1719 }
1721 namespace {
1722 class AsyncResult : public nsRunnable
1723 {
1724 public:
1725 AsyncResult(nsresult aStatus, nsIDownload* aResult,
1726 nsIDownloadManagerResult* aCallback)
1727 : mStatus(aStatus), mResult(aResult), mCallback(aCallback)
1728 {
1729 }
1731 NS_IMETHOD Run()
1732 {
1733 mCallback->HandleResult(mStatus, mResult);
1734 return NS_OK;
1735 }
1737 private:
1738 nsresult mStatus;
1739 nsCOMPtr<nsIDownload> mResult;
1740 nsCOMPtr<nsIDownloadManagerResult> mCallback;
1741 };
1742 } // anonymous namespace
1744 NS_IMETHODIMP
1745 nsDownloadManager::GetDownloadByGUID(const nsACString& aGUID,
1746 nsIDownloadManagerResult* aCallback)
1747 {
1748 NS_ENSURE_STATE(!mUseJSTransfer);
1750 nsDownload *itm = FindDownload(aGUID);
1752 nsresult rv = NS_OK;
1753 nsRefPtr<nsDownload> dl;
1754 if (!itm) {
1755 rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl));
1756 itm = dl.get();
1757 }
1759 nsRefPtr<AsyncResult> runnable = new AsyncResult(rv, itm, aCallback);
1760 NS_DispatchToMainThread(runnable);
1761 return NS_OK;
1762 }
1764 nsDownload *
1765 nsDownloadManager::FindDownload(uint32_t aID)
1766 {
1767 // we shouldn't ever have many downloads, so we can loop over them
1768 for (int32_t i = mCurrentDownloads.Count() - 1; i >= 0; --i) {
1769 nsDownload *dl = mCurrentDownloads[i];
1770 if (dl->mID == aID)
1771 return dl;
1772 }
1774 return nullptr;
1775 }
1777 nsDownload *
1778 nsDownloadManager::FindDownload(const nsACString& aGUID)
1779 {
1780 // we shouldn't ever have many downloads, so we can loop over them
1781 for (int32_t i = mCurrentDownloads.Count() - 1; i >= 0; --i) {
1782 nsDownload *dl = mCurrentDownloads[i];
1783 if (dl->mGUID == aGUID)
1784 return dl;
1785 }
1787 for (int32_t i = mCurrentPrivateDownloads.Count() - 1; i >= 0; --i) {
1788 nsDownload *dl = mCurrentPrivateDownloads[i];
1789 if (dl->mGUID == aGUID)
1790 return dl;
1791 }
1793 return nullptr;
1794 }
1796 NS_IMETHODIMP
1797 nsDownloadManager::CancelDownload(uint32_t aID)
1798 {
1799 NS_ENSURE_STATE(!mUseJSTransfer);
1801 NS_WARNING("Using integer IDs without compat mode enabled");
1803 // We AddRef here so we don't lose access to member variables when we remove
1804 nsRefPtr<nsDownload> dl = FindDownload(aID);
1806 // if it's null, someone passed us a bad id.
1807 if (!dl)
1808 return NS_ERROR_FAILURE;
1810 return dl->Cancel();
1811 }
1813 nsresult
1814 nsDownloadManager::RetryDownload(const nsACString& aGUID)
1815 {
1816 nsRefPtr<nsDownload> dl;
1817 nsresult rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl));
1818 NS_ENSURE_SUCCESS(rv, rv);
1820 return RetryDownload(dl);
1821 }
1824 NS_IMETHODIMP
1825 nsDownloadManager::RetryDownload(uint32_t aID)
1826 {
1827 NS_ENSURE_STATE(!mUseJSTransfer);
1829 NS_WARNING("Using integer IDs without compat mode enabled");
1831 nsRefPtr<nsDownload> dl;
1832 nsresult rv = GetDownloadFromDB(aID, getter_AddRefs(dl));
1833 NS_ENSURE_SUCCESS(rv, rv);
1835 return RetryDownload(dl);
1836 }
1838 nsresult
1839 nsDownloadManager::RetryDownload(nsDownload* dl)
1840 {
1841 // if our download is not canceled or failed, we should fail
1842 if (dl->mDownloadState != nsIDownloadManager::DOWNLOAD_FAILED &&
1843 dl->mDownloadState != nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL &&
1844 dl->mDownloadState != nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY &&
1845 dl->mDownloadState != nsIDownloadManager::DOWNLOAD_DIRTY &&
1846 dl->mDownloadState != nsIDownloadManager::DOWNLOAD_CANCELED)
1847 return NS_ERROR_FAILURE;
1849 // If the download has failed and is resumable then we first try resuming it
1850 nsresult rv;
1851 if (dl->mDownloadState == nsIDownloadManager::DOWNLOAD_FAILED && dl->IsResumable()) {
1852 rv = dl->Resume();
1853 if (NS_SUCCEEDED(rv))
1854 return rv;
1855 }
1857 // reset time and download progress
1858 dl->SetStartTime(PR_Now());
1859 dl->SetProgressBytes(0, -1);
1861 nsCOMPtr<nsIWebBrowserPersist> wbp =
1862 do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv);
1863 NS_ENSURE_SUCCESS(rv, rv);
1865 rv = wbp->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES |
1866 nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION);
1867 NS_ENSURE_SUCCESS(rv, rv);
1869 rv = AddToCurrentDownloads(dl);
1870 NS_ENSURE_SUCCESS(rv, rv);
1872 rv = dl->SetState(nsIDownloadManager::DOWNLOAD_QUEUED);
1873 NS_ENSURE_SUCCESS(rv, rv);
1875 // Creates a cycle that will be broken when the download finishes
1876 dl->mCancelable = wbp;
1877 (void)wbp->SetProgressListener(dl);
1879 rv = wbp->SavePrivacyAwareURI(dl->mSource, nullptr, nullptr, nullptr, nullptr,
1880 dl->mTarget, dl->mPrivate);
1881 if (NS_FAILED(rv)) {
1882 dl->mCancelable = nullptr;
1883 (void)wbp->SetProgressListener(nullptr);
1884 return rv;
1885 }
1887 return NS_OK;
1888 }
1890 static nsresult
1891 RemoveDownloadByGUID(const nsACString& aGUID, mozIStorageConnection* aDBConn)
1892 {
1893 nsCOMPtr<mozIStorageStatement> stmt;
1894 nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
1895 "DELETE FROM moz_downloads "
1896 "WHERE guid = :guid"), getter_AddRefs(stmt));
1897 NS_ENSURE_SUCCESS(rv, rv);
1899 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
1900 NS_ENSURE_SUCCESS(rv, rv);
1902 rv = stmt->Execute();
1903 NS_ENSURE_SUCCESS(rv, rv);
1905 return NS_OK;
1906 }
1908 nsresult
1909 nsDownloadManager::RemoveDownload(const nsACString& aGUID)
1910 {
1911 nsRefPtr<nsDownload> dl = FindDownload(aGUID);
1912 MOZ_ASSERT(!dl, "Can't call RemoveDownload on a download in progress!");
1913 if (dl)
1914 return NS_ERROR_FAILURE;
1916 nsresult rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl));
1917 NS_ENSURE_SUCCESS(rv, rv);
1919 if (dl->mPrivate) {
1920 RemoveDownloadByGUID(aGUID, mPrivateDBConn);
1921 } else {
1922 RemoveDownloadByGUID(aGUID, mDBConn);
1923 }
1925 return NotifyDownloadRemoval(dl);
1926 }
1928 NS_IMETHODIMP
1929 nsDownloadManager::RemoveDownload(uint32_t aID)
1930 {
1931 NS_ENSURE_STATE(!mUseJSTransfer);
1933 NS_WARNING("Using integer IDs without compat mode enabled");
1935 nsRefPtr<nsDownload> dl = FindDownload(aID);
1936 MOZ_ASSERT(!dl, "Can't call RemoveDownload on a download in progress!");
1937 if (dl)
1938 return NS_ERROR_FAILURE;
1940 nsresult rv = GetDownloadFromDB(aID, getter_AddRefs(dl));
1941 NS_ENSURE_SUCCESS(rv, rv);
1943 nsCOMPtr<mozIStorageStatement> stmt;
1944 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
1945 "DELETE FROM moz_downloads "
1946 "WHERE id = :id"), getter_AddRefs(stmt));
1947 NS_ENSURE_SUCCESS(rv, rv);
1949 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aID); // unsigned; 64-bit to prevent overflow
1950 NS_ENSURE_SUCCESS(rv, rv);
1952 rv = stmt->Execute();
1953 NS_ENSURE_SUCCESS(rv, rv);
1955 // Notify the UI with the topic and download id
1956 return NotifyDownloadRemoval(dl);
1957 }
1959 nsresult
1960 nsDownloadManager::NotifyDownloadRemoval(nsDownload* aRemoved)
1961 {
1962 nsCOMPtr<nsISupportsPRUint32> id;
1963 nsCOMPtr<nsISupportsCString> guid;
1964 nsresult rv;
1966 // Only send an integer ID notification if the download is public.
1967 bool sendDeprecatedNotification = !(aRemoved && aRemoved->mPrivate);
1969 if (sendDeprecatedNotification && aRemoved) {
1970 id = do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
1971 NS_ENSURE_SUCCESS(rv, rv);
1972 uint32_t dlID;
1973 rv = aRemoved->GetId(&dlID);
1974 NS_ENSURE_SUCCESS(rv, rv);
1975 rv = id->SetData(dlID);
1976 NS_ENSURE_SUCCESS(rv, rv);
1977 }
1979 if (sendDeprecatedNotification) {
1980 mObserverService->NotifyObservers(id,
1981 "download-manager-remove-download",
1982 nullptr);
1983 }
1985 if (aRemoved) {
1986 guid = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
1987 NS_ENSURE_SUCCESS(rv, rv);
1988 nsAutoCString guidStr;
1989 rv = aRemoved->GetGuid(guidStr);
1990 NS_ENSURE_SUCCESS(rv, rv);
1991 rv = guid->SetData(guidStr);
1992 NS_ENSURE_SUCCESS(rv, rv);
1993 }
1995 mObserverService->NotifyObservers(guid,
1996 "download-manager-remove-download-guid",
1997 nullptr);
1998 return NS_OK;
1999 }
2001 static nsresult
2002 DoRemoveDownloadsByTimeframe(mozIStorageConnection* aDBConn,
2003 int64_t aStartTime,
2004 int64_t aEndTime)
2005 {
2006 nsCOMPtr<mozIStorageStatement> stmt;
2007 nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
2008 "DELETE FROM moz_downloads "
2009 "WHERE startTime >= :startTime "
2010 "AND startTime <= :endTime "
2011 "AND state NOT IN (:downloading, :paused, :queued)"), getter_AddRefs(stmt));
2012 NS_ENSURE_SUCCESS(rv, rv);
2014 // Bind the times
2015 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startTime"), aStartTime);
2016 NS_ENSURE_SUCCESS(rv, rv);
2017 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("endTime"), aEndTime);
2018 NS_ENSURE_SUCCESS(rv, rv);
2020 // Bind the active states
2021 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("downloading"), nsIDownloadManager::DOWNLOAD_DOWNLOADING);
2022 NS_ENSURE_SUCCESS(rv, rv);
2023 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("paused"), nsIDownloadManager::DOWNLOAD_PAUSED);
2024 NS_ENSURE_SUCCESS(rv, rv);
2025 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("queued"), nsIDownloadManager::DOWNLOAD_QUEUED);
2026 NS_ENSURE_SUCCESS(rv, rv);
2028 // Execute
2029 rv = stmt->Execute();
2030 NS_ENSURE_SUCCESS(rv, rv);
2032 return NS_OK;
2033 }
2035 NS_IMETHODIMP
2036 nsDownloadManager::RemoveDownloadsByTimeframe(int64_t aStartTime,
2037 int64_t aEndTime)
2038 {
2039 NS_ENSURE_STATE(!mUseJSTransfer);
2041 nsresult rv = DoRemoveDownloadsByTimeframe(mDBConn, aStartTime, aEndTime);
2042 nsresult rv2 = DoRemoveDownloadsByTimeframe(mPrivateDBConn, aStartTime, aEndTime);
2043 NS_ENSURE_SUCCESS(rv, rv);
2044 NS_ENSURE_SUCCESS(rv2, rv2);
2046 // Notify the UI with the topic and null subject to indicate "remove multiple"
2047 return NotifyDownloadRemoval(nullptr);
2048 }
2050 NS_IMETHODIMP
2051 nsDownloadManager::CleanUp()
2052 {
2053 NS_ENSURE_STATE(!mUseJSTransfer);
2055 return CleanUp(mDBConn);
2056 }
2058 NS_IMETHODIMP
2059 nsDownloadManager::CleanUpPrivate()
2060 {
2061 NS_ENSURE_STATE(!mUseJSTransfer);
2063 return CleanUp(mPrivateDBConn);
2064 }
2066 nsresult
2067 nsDownloadManager::CleanUp(mozIStorageConnection* aDBConn)
2068 {
2069 DownloadState states[] = { nsIDownloadManager::DOWNLOAD_FINISHED,
2070 nsIDownloadManager::DOWNLOAD_FAILED,
2071 nsIDownloadManager::DOWNLOAD_CANCELED,
2072 nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL,
2073 nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY,
2074 nsIDownloadManager::DOWNLOAD_DIRTY };
2076 nsCOMPtr<mozIStorageStatement> stmt;
2077 nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
2078 "DELETE FROM moz_downloads "
2079 "WHERE state = ? "
2080 "OR state = ? "
2081 "OR state = ? "
2082 "OR state = ? "
2083 "OR state = ? "
2084 "OR state = ?"), getter_AddRefs(stmt));
2085 NS_ENSURE_SUCCESS(rv, rv);
2086 for (uint32_t i = 0; i < ArrayLength(states); ++i) {
2087 rv = stmt->BindInt32ByIndex(i, states[i]);
2088 NS_ENSURE_SUCCESS(rv, rv);
2089 }
2091 rv = stmt->Execute();
2092 NS_ENSURE_SUCCESS(rv, rv);
2094 // Notify the UI with the topic and null subject to indicate "remove multiple"
2095 return NotifyDownloadRemoval(nullptr);
2096 }
2098 static nsresult
2099 DoGetCanCleanUp(mozIStorageConnection* aDBConn, bool *aResult)
2100 {
2101 // This method should never return anything but NS_OK for the benefit of
2102 // unwitting consumers.
2104 *aResult = false;
2106 DownloadState states[] = { nsIDownloadManager::DOWNLOAD_FINISHED,
2107 nsIDownloadManager::DOWNLOAD_FAILED,
2108 nsIDownloadManager::DOWNLOAD_CANCELED,
2109 nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL,
2110 nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY,
2111 nsIDownloadManager::DOWNLOAD_DIRTY };
2113 nsCOMPtr<mozIStorageStatement> stmt;
2114 nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
2115 "SELECT COUNT(*) "
2116 "FROM moz_downloads "
2117 "WHERE state = ? "
2118 "OR state = ? "
2119 "OR state = ? "
2120 "OR state = ? "
2121 "OR state = ? "
2122 "OR state = ?"), getter_AddRefs(stmt));
2123 NS_ENSURE_SUCCESS(rv, NS_OK);
2124 for (uint32_t i = 0; i < ArrayLength(states); ++i) {
2125 rv = stmt->BindInt32ByIndex(i, states[i]);
2126 NS_ENSURE_SUCCESS(rv, NS_OK);
2127 }
2129 bool moreResults; // We don't really care...
2130 rv = stmt->ExecuteStep(&moreResults);
2131 NS_ENSURE_SUCCESS(rv, NS_OK);
2133 int32_t count;
2134 rv = stmt->GetInt32(0, &count);
2135 NS_ENSURE_SUCCESS(rv, NS_OK);
2137 if (count > 0)
2138 *aResult = true;
2140 return NS_OK;
2141 }
2143 NS_IMETHODIMP
2144 nsDownloadManager::GetCanCleanUp(bool *aResult)
2145 {
2146 NS_ENSURE_STATE(!mUseJSTransfer);
2148 return DoGetCanCleanUp(mDBConn, aResult);
2149 }
2151 NS_IMETHODIMP
2152 nsDownloadManager::GetCanCleanUpPrivate(bool *aResult)
2153 {
2154 NS_ENSURE_STATE(!mUseJSTransfer);
2156 return DoGetCanCleanUp(mPrivateDBConn, aResult);
2157 }
2159 NS_IMETHODIMP
2160 nsDownloadManager::PauseDownload(uint32_t aID)
2161 {
2162 NS_ENSURE_STATE(!mUseJSTransfer);
2164 NS_WARNING("Using integer IDs without compat mode enabled");
2166 nsDownload *dl = FindDownload(aID);
2167 if (!dl)
2168 return NS_ERROR_FAILURE;
2170 return dl->Pause();
2171 }
2173 NS_IMETHODIMP
2174 nsDownloadManager::ResumeDownload(uint32_t aID)
2175 {
2176 NS_ENSURE_STATE(!mUseJSTransfer);
2178 NS_WARNING("Using integer IDs without compat mode enabled");
2180 nsDownload *dl = FindDownload(aID);
2181 if (!dl)
2182 return NS_ERROR_FAILURE;
2184 return dl->Resume();
2185 }
2187 NS_IMETHODIMP
2188 nsDownloadManager::GetDBConnection(mozIStorageConnection **aDBConn)
2189 {
2190 NS_ENSURE_STATE(!mUseJSTransfer);
2192 NS_ADDREF(*aDBConn = mDBConn);
2194 return NS_OK;
2195 }
2197 NS_IMETHODIMP
2198 nsDownloadManager::GetPrivateDBConnection(mozIStorageConnection **aDBConn)
2199 {
2200 NS_ENSURE_STATE(!mUseJSTransfer);
2202 NS_ADDREF(*aDBConn = mPrivateDBConn);
2204 return NS_OK;
2205 }
2207 NS_IMETHODIMP
2208 nsDownloadManager::AddListener(nsIDownloadProgressListener *aListener)
2209 {
2210 NS_ENSURE_STATE(!mUseJSTransfer);
2212 mListeners.AppendObject(aListener);
2213 return NS_OK;
2214 }
2216 NS_IMETHODIMP
2217 nsDownloadManager::AddPrivacyAwareListener(nsIDownloadProgressListener *aListener)
2218 {
2219 NS_ENSURE_STATE(!mUseJSTransfer);
2221 mPrivacyAwareListeners.AppendObject(aListener);
2222 return NS_OK;
2223 }
2225 NS_IMETHODIMP
2226 nsDownloadManager::RemoveListener(nsIDownloadProgressListener *aListener)
2227 {
2228 NS_ENSURE_STATE(!mUseJSTransfer);
2230 mListeners.RemoveObject(aListener);
2231 mPrivacyAwareListeners.RemoveObject(aListener);
2232 return NS_OK;
2233 }
2235 void
2236 nsDownloadManager::NotifyListenersOnDownloadStateChange(int16_t aOldState,
2237 nsDownload *aDownload)
2238 {
2239 for (int32_t i = mPrivacyAwareListeners.Count() - 1; i >= 0; --i) {
2240 mPrivacyAwareListeners[i]->OnDownloadStateChange(aOldState, aDownload);
2241 }
2243 // Only privacy-aware listeners should receive notifications about private
2244 // downloads, while non-privacy-aware listeners receive no sign they exist.
2245 if (aDownload->mPrivate) {
2246 return;
2247 }
2249 for (int32_t i = mListeners.Count() - 1; i >= 0; --i) {
2250 mListeners[i]->OnDownloadStateChange(aOldState, aDownload);
2251 }
2252 }
2254 void
2255 nsDownloadManager::NotifyListenersOnProgressChange(nsIWebProgress *aProgress,
2256 nsIRequest *aRequest,
2257 int64_t aCurSelfProgress,
2258 int64_t aMaxSelfProgress,
2259 int64_t aCurTotalProgress,
2260 int64_t aMaxTotalProgress,
2261 nsDownload *aDownload)
2262 {
2263 for (int32_t i = mPrivacyAwareListeners.Count() - 1; i >= 0; --i) {
2264 mPrivacyAwareListeners[i]->OnProgressChange(aProgress, aRequest, aCurSelfProgress,
2265 aMaxSelfProgress, aCurTotalProgress,
2266 aMaxTotalProgress, aDownload);
2267 }
2269 // Only privacy-aware listeners should receive notifications about private
2270 // downloads, while non-privacy-aware listeners receive no sign they exist.
2271 if (aDownload->mPrivate) {
2272 return;
2273 }
2275 for (int32_t i = mListeners.Count() - 1; i >= 0; --i) {
2276 mListeners[i]->OnProgressChange(aProgress, aRequest, aCurSelfProgress,
2277 aMaxSelfProgress, aCurTotalProgress,
2278 aMaxTotalProgress, aDownload);
2279 }
2280 }
2282 void
2283 nsDownloadManager::NotifyListenersOnStateChange(nsIWebProgress *aProgress,
2284 nsIRequest *aRequest,
2285 uint32_t aStateFlags,
2286 nsresult aStatus,
2287 nsDownload *aDownload)
2288 {
2289 for (int32_t i = mPrivacyAwareListeners.Count() - 1; i >= 0; --i) {
2290 mPrivacyAwareListeners[i]->OnStateChange(aProgress, aRequest, aStateFlags, aStatus,
2291 aDownload);
2292 }
2294 // Only privacy-aware listeners should receive notifications about private
2295 // downloads, while non-privacy-aware listeners receive no sign they exist.
2296 if (aDownload->mPrivate) {
2297 return;
2298 }
2300 for (int32_t i = mListeners.Count() - 1; i >= 0; --i) {
2301 mListeners[i]->OnStateChange(aProgress, aRequest, aStateFlags, aStatus,
2302 aDownload);
2303 }
2304 }
2306 ////////////////////////////////////////////////////////////////////////////////
2307 //// nsINavHistoryObserver
2309 NS_IMETHODIMP
2310 nsDownloadManager::OnBeginUpdateBatch()
2311 {
2312 // This method in not normally invoked when mUseJSTransfer is enabled, however
2313 // we provide an extra check in case it is called manually by add-ons.
2314 NS_ENSURE_STATE(!mUseJSTransfer);
2316 // We already have a transaction, so don't make another
2317 if (mHistoryTransaction)
2318 return NS_OK;
2320 // Start a transaction that commits when deleted
2321 mHistoryTransaction = new mozStorageTransaction(mDBConn, true);
2323 return NS_OK;
2324 }
2326 NS_IMETHODIMP
2327 nsDownloadManager::OnEndUpdateBatch()
2328 {
2329 // Get rid of the transaction and cause it to commit
2330 mHistoryTransaction = nullptr;
2332 return NS_OK;
2333 }
2335 NS_IMETHODIMP
2336 nsDownloadManager::OnVisit(nsIURI *aURI, int64_t aVisitID, PRTime aTime,
2337 int64_t aSessionID, int64_t aReferringID,
2338 uint32_t aTransitionType, const nsACString& aGUID,
2339 bool aHidden)
2340 {
2341 return NS_OK;
2342 }
2344 NS_IMETHODIMP
2345 nsDownloadManager::OnTitleChanged(nsIURI *aURI,
2346 const nsAString &aPageTitle,
2347 const nsACString &aGUID)
2348 {
2349 return NS_OK;
2350 }
2352 NS_IMETHODIMP
2353 nsDownloadManager::OnFrecencyChanged(nsIURI* aURI,
2354 int32_t aNewFrecency,
2355 const nsACString& aGUID,
2356 bool aHidden,
2357 PRTime aLastVisitDate)
2358 {
2359 return NS_OK;
2360 }
2362 NS_IMETHODIMP
2363 nsDownloadManager::OnManyFrecenciesChanged()
2364 {
2365 return NS_OK;
2366 }
2368 NS_IMETHODIMP
2369 nsDownloadManager::OnDeleteURI(nsIURI *aURI,
2370 const nsACString& aGUID,
2371 uint16_t aReason)
2372 {
2373 // This method in not normally invoked when mUseJSTransfer is enabled, however
2374 // we provide an extra check in case it is called manually by add-ons.
2375 NS_ENSURE_STATE(!mUseJSTransfer);
2377 nsresult rv = RemoveDownloadsForURI(mGetIdsForURIStatement, aURI);
2378 nsresult rv2 = RemoveDownloadsForURI(mGetPrivateIdsForURIStatement, aURI);
2379 NS_ENSURE_SUCCESS(rv, rv);
2380 NS_ENSURE_SUCCESS(rv2, rv2);
2381 return NS_OK;
2382 }
2384 NS_IMETHODIMP
2385 nsDownloadManager::OnClearHistory()
2386 {
2387 return CleanUp();
2388 }
2390 NS_IMETHODIMP
2391 nsDownloadManager::OnPageChanged(nsIURI *aURI,
2392 uint32_t aChangedAttribute,
2393 const nsAString& aNewValue,
2394 const nsACString &aGUID)
2395 {
2396 return NS_OK;
2397 }
2399 NS_IMETHODIMP
2400 nsDownloadManager::OnDeleteVisits(nsIURI *aURI, PRTime aVisitTime,
2401 const nsACString& aGUID,
2402 uint16_t aReason, uint32_t aTransitionType)
2403 {
2404 // Don't bother removing downloads until the page is removed.
2405 return NS_OK;
2406 }
2408 ////////////////////////////////////////////////////////////////////////////////
2409 //// nsIObserver
2411 NS_IMETHODIMP
2412 nsDownloadManager::Observe(nsISupports *aSubject,
2413 const char *aTopic,
2414 const char16_t *aData)
2415 {
2416 // This method in not normally invoked when mUseJSTransfer is enabled, however
2417 // we provide an extra check in case it is called manually by add-ons.
2418 NS_ENSURE_STATE(!mUseJSTransfer);
2420 // We need to count the active public downloads that could be lost
2421 // by quitting, and add any active private ones as well, since per-window
2422 // private browsing may be active.
2423 int32_t currDownloadCount = mCurrentDownloads.Count();
2425 // If we don't need to cancel all the downloads on quit, only count the ones
2426 // that aren't resumable.
2427 if (GetQuitBehavior() != QUIT_AND_CANCEL) {
2428 for (int32_t i = currDownloadCount - 1; i >= 0; --i) {
2429 if (mCurrentDownloads[i]->IsResumable()) {
2430 currDownloadCount--;
2431 }
2432 }
2434 // We have a count of the public, non-resumable downloads. Now we need
2435 // to add the total number of private downloads, since they are in danger
2436 // of being lost.
2437 currDownloadCount += mCurrentPrivateDownloads.Count();
2438 }
2440 nsresult rv;
2441 if (strcmp(aTopic, "oncancel") == 0) {
2442 nsCOMPtr<nsIDownload> dl = do_QueryInterface(aSubject, &rv);
2443 NS_ENSURE_SUCCESS(rv, rv);
2445 dl->Cancel();
2446 } else if (strcmp(aTopic, "profile-before-change") == 0) {
2447 CloseAllDBs();
2448 } else if (strcmp(aTopic, "quit-application") == 0) {
2449 // Try to pause all downloads and, if appropriate, mark them as auto-resume
2450 // unless user has specified that downloads should be canceled
2451 enum QuitBehavior behavior = GetQuitBehavior();
2452 if (behavior != QUIT_AND_CANCEL)
2453 (void)PauseAllDownloads(bool(behavior != QUIT_AND_PAUSE));
2455 // Remove downloads to break cycles and cancel downloads
2456 (void)RemoveAllDownloads();
2458 // Now that active downloads have been canceled, remove all completed or
2459 // aborted downloads if the user's retention policy specifies it.
2460 if (GetRetentionBehavior() == 1)
2461 CleanUp();
2462 } else if (strcmp(aTopic, "quit-application-requested") == 0 &&
2463 currDownloadCount) {
2464 nsCOMPtr<nsISupportsPRBool> cancelDownloads =
2465 do_QueryInterface(aSubject, &rv);
2466 NS_ENSURE_SUCCESS(rv, rv);
2467 #ifndef XP_MACOSX
2468 ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
2469 MOZ_UTF16("quitCancelDownloadsAlertTitle"),
2470 MOZ_UTF16("quitCancelDownloadsAlertMsgMultiple"),
2471 MOZ_UTF16("quitCancelDownloadsAlertMsg"),
2472 MOZ_UTF16("dontQuitButtonWin"));
2473 #else
2474 ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
2475 MOZ_UTF16("quitCancelDownloadsAlertTitle"),
2476 MOZ_UTF16("quitCancelDownloadsAlertMsgMacMultiple"),
2477 MOZ_UTF16("quitCancelDownloadsAlertMsgMac"),
2478 MOZ_UTF16("dontQuitButtonMac"));
2479 #endif
2480 } else if (strcmp(aTopic, "offline-requested") == 0 && currDownloadCount) {
2481 nsCOMPtr<nsISupportsPRBool> cancelDownloads =
2482 do_QueryInterface(aSubject, &rv);
2483 NS_ENSURE_SUCCESS(rv, rv);
2484 ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
2485 MOZ_UTF16("offlineCancelDownloadsAlertTitle"),
2486 MOZ_UTF16("offlineCancelDownloadsAlertMsgMultiple"),
2487 MOZ_UTF16("offlineCancelDownloadsAlertMsg"),
2488 MOZ_UTF16("dontGoOfflineButton"));
2489 }
2490 else if (strcmp(aTopic, NS_IOSERVICE_GOING_OFFLINE_TOPIC) == 0) {
2491 // Pause all downloads, and mark them to auto-resume.
2492 (void)PauseAllDownloads(true);
2493 }
2494 else if (strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC) == 0 &&
2495 nsDependentString(aData).EqualsLiteral(NS_IOSERVICE_ONLINE)) {
2496 // We can now resume all downloads that are supposed to auto-resume.
2497 (void)ResumeAllDownloads(false);
2498 }
2499 else if (strcmp(aTopic, "alertclickcallback") == 0) {
2500 nsCOMPtr<nsIDownloadManagerUI> dmui =
2501 do_GetService("@mozilla.org/download-manager-ui;1", &rv);
2502 NS_ENSURE_SUCCESS(rv, rv);
2503 return dmui->Show(nullptr, nullptr, nsIDownloadManagerUI::REASON_USER_INTERACTED,
2504 aData && NS_strcmp(aData, MOZ_UTF16("private")) == 0);
2505 } else if (strcmp(aTopic, "sleep_notification") == 0 ||
2506 strcmp(aTopic, "suspend_process_notification") == 0) {
2507 // Pause downloads if we're sleeping, and mark the downloads as auto-resume
2508 (void)PauseAllDownloads(true);
2509 } else if (strcmp(aTopic, "wake_notification") == 0 ||
2510 strcmp(aTopic, "resume_process_notification") == 0) {
2511 int32_t resumeOnWakeDelay = 10000;
2512 nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID);
2513 if (pref)
2514 (void)pref->GetIntPref(PREF_BDM_RESUMEONWAKEDELAY, &resumeOnWakeDelay);
2516 // Wait a little bit before trying to resume to avoid resuming when network
2517 // connections haven't restarted yet
2518 mResumeOnWakeTimer = do_CreateInstance("@mozilla.org/timer;1");
2519 if (resumeOnWakeDelay >= 0 && mResumeOnWakeTimer) {
2520 (void)mResumeOnWakeTimer->InitWithFuncCallback(ResumeOnWakeCallback,
2521 this, resumeOnWakeDelay, nsITimer::TYPE_ONE_SHOT);
2522 }
2523 } else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
2524 // Upon leaving private browsing mode, cancel all private downloads,
2525 // remove all trace of them, and then blow away the private database
2526 // and recreate a blank one.
2527 RemoveAllDownloads(mCurrentPrivateDownloads);
2528 InitPrivateDB();
2529 } else if (strcmp(aTopic, "last-pb-context-exiting") == 0) {
2530 // If there are active private downloads, prompt the user to confirm leaving
2531 // private browsing mode (thereby cancelling them). Otherwise, silently proceed.
2532 if (!mCurrentPrivateDownloads.Count())
2533 return NS_OK;
2535 nsCOMPtr<nsISupportsPRBool> cancelDownloads = do_QueryInterface(aSubject, &rv);
2536 NS_ENSURE_SUCCESS(rv, rv);
2538 ConfirmCancelDownloads(mCurrentPrivateDownloads.Count(), cancelDownloads,
2539 MOZ_UTF16("leavePrivateBrowsingCancelDownloadsAlertTitle"),
2540 MOZ_UTF16("leavePrivateBrowsingWindowsCancelDownloadsAlertMsgMultiple"),
2541 MOZ_UTF16("leavePrivateBrowsingWindowsCancelDownloadsAlertMsg"),
2542 MOZ_UTF16("dontLeavePrivateBrowsingButton"));
2543 }
2545 return NS_OK;
2546 }
2548 void
2549 nsDownloadManager::ConfirmCancelDownloads(int32_t aCount,
2550 nsISupportsPRBool *aCancelDownloads,
2551 const char16_t *aTitle,
2552 const char16_t *aCancelMessageMultiple,
2553 const char16_t *aCancelMessageSingle,
2554 const char16_t *aDontCancelButton)
2555 {
2556 // If user has already dismissed quit request, then do nothing
2557 bool quitRequestCancelled = false;
2558 aCancelDownloads->GetData(&quitRequestCancelled);
2559 if (quitRequestCancelled)
2560 return;
2562 nsXPIDLString title, message, quitButton, dontQuitButton;
2564 mBundle->GetStringFromName(aTitle, getter_Copies(title));
2566 nsAutoString countString;
2567 countString.AppendInt(aCount);
2568 const char16_t *strings[1] = { countString.get() };
2569 if (aCount > 1) {
2570 mBundle->FormatStringFromName(aCancelMessageMultiple, strings, 1,
2571 getter_Copies(message));
2572 mBundle->FormatStringFromName(MOZ_UTF16("cancelDownloadsOKTextMultiple"),
2573 strings, 1, getter_Copies(quitButton));
2574 } else {
2575 mBundle->GetStringFromName(aCancelMessageSingle, getter_Copies(message));
2576 mBundle->GetStringFromName(MOZ_UTF16("cancelDownloadsOKText"),
2577 getter_Copies(quitButton));
2578 }
2580 mBundle->GetStringFromName(aDontCancelButton, getter_Copies(dontQuitButton));
2582 // Get Download Manager window, to be parent of alert.
2583 nsCOMPtr<nsIWindowMediator> wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
2584 nsCOMPtr<nsIDOMWindow> dmWindow;
2585 if (wm) {
2586 wm->GetMostRecentWindow(MOZ_UTF16("Download:Manager"),
2587 getter_AddRefs(dmWindow));
2588 }
2590 // Show alert.
2591 nsCOMPtr<nsIPromptService> prompter(do_GetService(NS_PROMPTSERVICE_CONTRACTID));
2592 if (prompter) {
2593 int32_t flags = (nsIPromptService::BUTTON_TITLE_IS_STRING * nsIPromptService::BUTTON_POS_0) + (nsIPromptService::BUTTON_TITLE_IS_STRING * nsIPromptService::BUTTON_POS_1);
2594 bool nothing = false;
2595 int32_t button;
2596 prompter->ConfirmEx(dmWindow, title, message, flags, quitButton.get(), dontQuitButton.get(), nullptr, nullptr, ¬hing, &button);
2598 aCancelDownloads->SetData(button == 1);
2599 }
2600 }
2602 ////////////////////////////////////////////////////////////////////////////////
2603 //// nsDownload
2605 NS_IMPL_CLASSINFO(nsDownload, nullptr, 0, NS_DOWNLOAD_CID)
2606 NS_IMPL_ISUPPORTS_CI(
2607 nsDownload
2608 , nsIDownload
2609 , nsITransfer
2610 , nsIWebProgressListener
2611 , nsIWebProgressListener2
2612 )
2614 nsDownload::nsDownload() : mDownloadState(nsIDownloadManager::DOWNLOAD_NOTSTARTED),
2615 mID(0),
2616 mPercentComplete(0),
2617 mCurrBytes(0),
2618 mMaxBytes(-1),
2619 mStartTime(0),
2620 mLastUpdate(PR_Now() - (uint32_t)gUpdateInterval),
2621 mResumedAt(-1),
2622 mSpeed(0),
2623 mHasMultipleFiles(false),
2624 mPrivate(false),
2625 mAutoResume(DONT_RESUME)
2626 {
2627 }
2629 nsDownload::~nsDownload()
2630 {
2631 }
2633 NS_IMETHODIMP nsDownload::SetSha256Hash(const nsACString& aHash) {
2634 MOZ_ASSERT(NS_IsMainThread(), "Must call SetSha256Hash on main thread");
2635 // This will be used later to query the application reputation service.
2636 mHash = aHash;
2637 return NS_OK;
2638 }
2640 NS_IMETHODIMP nsDownload::SetSignatureInfo(nsIArray* aSignatureInfo) {
2641 MOZ_ASSERT(NS_IsMainThread(), "Must call SetSignatureInfo on main thread");
2642 // This will be used later to query the application reputation service.
2643 mSignatureInfo = aSignatureInfo;
2644 return NS_OK;
2645 }
2647 #ifdef MOZ_ENABLE_GIO
2648 static void gio_set_metadata_done(GObject *source_obj, GAsyncResult *res, gpointer user_data)
2649 {
2650 GError *err = nullptr;
2651 g_file_set_attributes_finish(G_FILE(source_obj), res, nullptr, &err);
2652 if (err) {
2653 #ifdef DEBUG
2654 NS_DebugBreak(NS_DEBUG_WARNING, "Set file metadata failed: ", err->message, __FILE__, __LINE__);
2655 #endif
2656 g_error_free(err);
2657 }
2658 }
2659 #endif
2661 nsresult
2662 nsDownload::SetState(DownloadState aState)
2663 {
2664 NS_ASSERTION(mDownloadState != aState,
2665 "Trying to set the download state to what it already is set to!");
2667 int16_t oldState = mDownloadState;
2668 mDownloadState = aState;
2670 // We don't want to lose access to our member variables
2671 nsRefPtr<nsDownload> kungFuDeathGrip = this;
2673 // When the state changed listener is dispatched, queries to the database and
2674 // the download manager api should reflect what the nsIDownload object would
2675 // return. So, if a download is done (finished, canceled, etc.), it should
2676 // first be removed from the current downloads. We will also have to update
2677 // the database *before* notifying listeners. At this point, you can safely
2678 // dispatch to the observers as well.
2679 switch (aState) {
2680 case nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL:
2681 case nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY:
2682 case nsIDownloadManager::DOWNLOAD_DIRTY:
2683 case nsIDownloadManager::DOWNLOAD_CANCELED:
2684 case nsIDownloadManager::DOWNLOAD_FAILED:
2685 #ifdef ANDROID
2686 // If we still have a temp file, remove it
2687 bool tempExists;
2688 if (mTempFile && NS_SUCCEEDED(mTempFile->Exists(&tempExists)) && tempExists) {
2689 nsresult rv = mTempFile->Remove(false);
2690 NS_ENSURE_SUCCESS(rv, rv);
2691 }
2692 #endif
2694 // Transfers are finished, so break the reference cycle
2695 Finalize();
2696 break;
2697 #ifdef DOWNLOAD_SCANNER
2698 case nsIDownloadManager::DOWNLOAD_SCANNING:
2699 {
2700 nsresult rv = mDownloadManager->mScanner ? mDownloadManager->mScanner->ScanDownload(this) : NS_ERROR_NOT_INITIALIZED;
2701 // If we failed, then fall through to 'download finished'
2702 if (NS_SUCCEEDED(rv))
2703 break;
2704 mDownloadState = aState = nsIDownloadManager::DOWNLOAD_FINISHED;
2705 }
2706 #endif
2707 case nsIDownloadManager::DOWNLOAD_FINISHED:
2708 {
2709 nsresult rv = ExecuteDesiredAction();
2710 if (NS_FAILED(rv)) {
2711 // We've failed to execute the desired action. As a result, we should
2712 // fail the download so the user can try again.
2713 (void)FailDownload(rv, nullptr);
2714 return rv;
2715 }
2717 // Now that we're done with handling the download, clean it up
2718 Finalize();
2720 nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID));
2722 // Master pref to control this function.
2723 bool showTaskbarAlert = true;
2724 if (pref)
2725 pref->GetBoolPref(PREF_BDM_SHOWALERTONCOMPLETE, &showTaskbarAlert);
2727 if (showTaskbarAlert) {
2728 int32_t alertInterval = 2000;
2729 if (pref)
2730 pref->GetIntPref(PREF_BDM_SHOWALERTINTERVAL, &alertInterval);
2732 int64_t alertIntervalUSec = alertInterval * PR_USEC_PER_MSEC;
2733 int64_t goat = PR_Now() - mStartTime;
2734 showTaskbarAlert = goat > alertIntervalUSec;
2736 int32_t size = mPrivate ?
2737 mDownloadManager->mCurrentPrivateDownloads.Count() :
2738 mDownloadManager->mCurrentDownloads.Count();
2739 if (showTaskbarAlert && size == 0) {
2740 nsCOMPtr<nsIAlertsService> alerts =
2741 do_GetService("@mozilla.org/alerts-service;1");
2742 if (alerts) {
2743 nsXPIDLString title, message;
2745 mDownloadManager->mBundle->GetStringFromName(
2746 MOZ_UTF16("downloadsCompleteTitle"),
2747 getter_Copies(title));
2748 mDownloadManager->mBundle->GetStringFromName(
2749 MOZ_UTF16("downloadsCompleteMsg"),
2750 getter_Copies(message));
2752 bool removeWhenDone =
2753 mDownloadManager->GetRetentionBehavior() == 0;
2755 // If downloads are automatically removed per the user's
2756 // retention policy, there's no reason to make the text clickable
2757 // because if it is, they'll click open the download manager and
2758 // the items they downloaded will have been removed.
2759 alerts->ShowAlertNotification(
2760 NS_LITERAL_STRING(DOWNLOAD_MANAGER_ALERT_ICON), title,
2761 message, !removeWhenDone,
2762 mPrivate ? NS_LITERAL_STRING("private") : NS_LITERAL_STRING("non-private"),
2763 mDownloadManager, EmptyString(), NS_LITERAL_STRING("auto"),
2764 EmptyString(), nullptr);
2765 }
2766 }
2767 }
2769 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK)
2770 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mTarget);
2771 nsCOMPtr<nsIFile> file;
2772 nsAutoString path;
2774 if (fileURL &&
2775 NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) &&
2776 file &&
2777 NS_SUCCEEDED(file->GetPath(path))) {
2779 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
2780 // On Windows and Gtk, add the download to the system's "recent documents"
2781 // list, with a pref to disable.
2782 {
2783 bool addToRecentDocs = true;
2784 if (pref)
2785 pref->GetBoolPref(PREF_BDM_ADDTORECENTDOCS, &addToRecentDocs);
2787 if (addToRecentDocs && !mPrivate) {
2788 #ifdef XP_WIN
2789 ::SHAddToRecentDocs(SHARD_PATHW, path.get());
2790 #elif defined(MOZ_WIDGET_GTK)
2791 GtkRecentManager* manager = gtk_recent_manager_get_default();
2793 gchar* uri = g_filename_to_uri(NS_ConvertUTF16toUTF8(path).get(),
2794 nullptr, nullptr);
2795 if (uri) {
2796 gtk_recent_manager_add_item(manager, uri);
2797 g_free(uri);
2798 }
2799 #endif
2800 }
2801 #ifdef MOZ_ENABLE_GIO
2802 // Use GIO to store the source URI for later display in the file manager.
2803 GFile* gio_file = g_file_new_for_path(NS_ConvertUTF16toUTF8(path).get());
2804 nsCString source_uri;
2805 mSource->GetSpec(source_uri);
2806 GFileInfo *file_info = g_file_info_new();
2807 g_file_info_set_attribute_string(file_info, "metadata::download-uri", source_uri.get());
2808 g_file_set_attributes_async(gio_file,
2809 file_info,
2810 G_FILE_QUERY_INFO_NONE,
2811 G_PRIORITY_DEFAULT,
2812 nullptr, gio_set_metadata_done, nullptr);
2813 g_object_unref(file_info);
2814 g_object_unref(gio_file);
2815 #endif
2816 }
2817 #endif
2818 #ifdef XP_MACOSX
2819 // On OS X, make the downloads stack bounce.
2820 CFStringRef observedObject = ::CFStringCreateWithCString(kCFAllocatorDefault,
2821 NS_ConvertUTF16toUTF8(path).get(),
2822 kCFStringEncodingUTF8);
2823 CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
2824 ::CFNotificationCenterPostNotification(center, CFSTR("com.apple.DownloadFileFinished"),
2825 observedObject, nullptr, TRUE);
2826 ::CFRelease(observedObject);
2827 #endif
2828 #ifdef MOZ_WIDGET_ANDROID
2829 nsCOMPtr<nsIMIMEInfo> mimeInfo;
2830 nsAutoCString contentType;
2831 GetMIMEInfo(getter_AddRefs(mimeInfo));
2833 if (mimeInfo)
2834 mimeInfo->GetMIMEType(contentType);
2836 mozilla::widget::android::GeckoAppShell::ScanMedia(path, NS_ConvertUTF8toUTF16(contentType));
2837 #endif
2838 }
2840 #ifdef XP_WIN
2841 // Adjust file attributes so that by default, new files are indexed
2842 // by desktop search services. Skip off those that land in the temp
2843 // folder.
2844 nsCOMPtr<nsIFile> tempDir, fileDir;
2845 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir));
2846 NS_ENSURE_SUCCESS(rv, rv);
2847 (void)file->GetParent(getter_AddRefs(fileDir));
2849 bool isTemp = false;
2850 if (fileDir)
2851 (void)fileDir->Equals(tempDir, &isTemp);
2853 nsCOMPtr<nsILocalFileWin> localFileWin(do_QueryInterface(file));
2854 if (!isTemp && localFileWin)
2855 (void)localFileWin->SetFileAttributesWin(nsILocalFileWin::WFA_SEARCH_INDEXED);
2856 #endif
2858 #endif
2859 // Now remove the download if the user's retention policy is "Remove when Done"
2860 if (mDownloadManager->GetRetentionBehavior() == 0)
2861 mDownloadManager->RemoveDownload(mGUID);
2862 }
2863 break;
2864 default:
2865 break;
2866 }
2868 // Before notifying the listener, we must update the database so that calls
2869 // to it work out properly.
2870 nsresult rv = UpdateDB();
2871 NS_ENSURE_SUCCESS(rv, rv);
2873 mDownloadManager->NotifyListenersOnDownloadStateChange(oldState, this);
2875 switch (mDownloadState) {
2876 case nsIDownloadManager::DOWNLOAD_DOWNLOADING:
2877 // Only send the dl-start event to downloads that are actually starting.
2878 if (oldState == nsIDownloadManager::DOWNLOAD_QUEUED) {
2879 if (!mPrivate)
2880 mDownloadManager->SendEvent(this, "dl-start");
2881 }
2882 break;
2883 case nsIDownloadManager::DOWNLOAD_FAILED:
2884 if (!mPrivate)
2885 mDownloadManager->SendEvent(this, "dl-failed");
2886 break;
2887 case nsIDownloadManager::DOWNLOAD_SCANNING:
2888 if (!mPrivate)
2889 mDownloadManager->SendEvent(this, "dl-scanning");
2890 break;
2891 case nsIDownloadManager::DOWNLOAD_FINISHED:
2892 if (!mPrivate)
2893 mDownloadManager->SendEvent(this, "dl-done");
2894 break;
2895 case nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL:
2896 case nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY:
2897 if (!mPrivate)
2898 mDownloadManager->SendEvent(this, "dl-blocked");
2899 break;
2900 case nsIDownloadManager::DOWNLOAD_DIRTY:
2901 if (!mPrivate)
2902 mDownloadManager->SendEvent(this, "dl-dirty");
2903 break;
2904 case nsIDownloadManager::DOWNLOAD_CANCELED:
2905 if (!mPrivate)
2906 mDownloadManager->SendEvent(this, "dl-cancel");
2907 break;
2908 default:
2909 break;
2910 }
2911 return NS_OK;
2912 }
2914 ////////////////////////////////////////////////////////////////////////////////
2915 //// nsIWebProgressListener2
2917 NS_IMETHODIMP
2918 nsDownload::OnProgressChange64(nsIWebProgress *aWebProgress,
2919 nsIRequest *aRequest,
2920 int64_t aCurSelfProgress,
2921 int64_t aMaxSelfProgress,
2922 int64_t aCurTotalProgress,
2923 int64_t aMaxTotalProgress)
2924 {
2925 if (!mRequest)
2926 mRequest = aRequest; // used for pause/resume
2928 if (mDownloadState == nsIDownloadManager::DOWNLOAD_QUEUED) {
2929 // Obtain the referrer
2930 nsresult rv;
2931 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2932 nsCOMPtr<nsIURI> referrer = mReferrer;
2933 if (channel)
2934 (void)NS_GetReferrerFromChannel(channel, getter_AddRefs(mReferrer));
2936 // Restore the original referrer if the new one isn't useful
2937 if (!mReferrer)
2938 mReferrer = referrer;
2940 // If we have a MIME info, we know that exthandler has already added this to
2941 // the history, but if we do not, we'll have to add it ourselves.
2942 if (!mMIMEInfo && !mPrivate) {
2943 nsCOMPtr<nsIDownloadHistory> dh =
2944 do_GetService(NS_DOWNLOADHISTORY_CONTRACTID);
2945 if (dh)
2946 (void)dh->AddDownload(mSource, mReferrer, mStartTime, mTarget);
2947 }
2949 // Fetch the entityID, but if we can't get it, don't panic (non-resumable)
2950 nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(aRequest));
2951 if (resumableChannel)
2952 (void)resumableChannel->GetEntityID(mEntityID);
2954 // Before we update the state and dispatch state notifications, we want to
2955 // ensure that we have the correct state for this download with regards to
2956 // its percent completion and size.
2957 SetProgressBytes(0, aMaxTotalProgress);
2959 // Update the state and the database
2960 rv = SetState(nsIDownloadManager::DOWNLOAD_DOWNLOADING);
2961 NS_ENSURE_SUCCESS(rv, rv);
2962 }
2964 // filter notifications since they come in so frequently
2965 PRTime now = PR_Now();
2966 PRIntervalTime delta = now - mLastUpdate;
2967 if (delta < gUpdateInterval)
2968 return NS_OK;
2970 mLastUpdate = now;
2972 // Calculate the speed using the elapsed delta time and bytes downloaded
2973 // during that time for more accuracy.
2974 double elapsedSecs = double(delta) / PR_USEC_PER_SEC;
2975 if (elapsedSecs > 0) {
2976 double speed = double(aCurTotalProgress - mCurrBytes) / elapsedSecs;
2977 if (mCurrBytes == 0) {
2978 mSpeed = speed;
2979 } else {
2980 // Calculate 'smoothed average' of 10 readings.
2981 mSpeed = mSpeed * 0.9 + speed * 0.1;
2982 }
2983 }
2985 SetProgressBytes(aCurTotalProgress, aMaxTotalProgress);
2987 // Report to the listener our real sizes
2988 int64_t currBytes, maxBytes;
2989 (void)GetAmountTransferred(&currBytes);
2990 (void)GetSize(&maxBytes);
2991 mDownloadManager->NotifyListenersOnProgressChange(
2992 aWebProgress, aRequest, currBytes, maxBytes, currBytes, maxBytes, this);
2994 // If the maximums are different, then there must be more than one file
2995 if (aMaxSelfProgress != aMaxTotalProgress)
2996 mHasMultipleFiles = true;
2998 return NS_OK;
2999 }
3001 NS_IMETHODIMP
3002 nsDownload::OnRefreshAttempted(nsIWebProgress *aWebProgress,
3003 nsIURI *aUri,
3004 int32_t aDelay,
3005 bool aSameUri,
3006 bool *allowRefresh)
3007 {
3008 *allowRefresh = true;
3009 return NS_OK;
3010 }
3012 ////////////////////////////////////////////////////////////////////////////////
3013 //// nsIWebProgressListener
3015 NS_IMETHODIMP
3016 nsDownload::OnProgressChange(nsIWebProgress *aWebProgress,
3017 nsIRequest *aRequest,
3018 int32_t aCurSelfProgress,
3019 int32_t aMaxSelfProgress,
3020 int32_t aCurTotalProgress,
3021 int32_t aMaxTotalProgress)
3022 {
3023 return OnProgressChange64(aWebProgress, aRequest,
3024 aCurSelfProgress, aMaxSelfProgress,
3025 aCurTotalProgress, aMaxTotalProgress);
3026 }
3028 NS_IMETHODIMP
3029 nsDownload::OnLocationChange(nsIWebProgress *aWebProgress,
3030 nsIRequest *aRequest, nsIURI *aLocation,
3031 uint32_t aFlags)
3032 {
3033 return NS_OK;
3034 }
3036 NS_IMETHODIMP
3037 nsDownload::OnStatusChange(nsIWebProgress *aWebProgress,
3038 nsIRequest *aRequest, nsresult aStatus,
3039 const char16_t *aMessage)
3040 {
3041 if (NS_FAILED(aStatus))
3042 return FailDownload(aStatus, aMessage);
3043 return NS_OK;
3044 }
3046 NS_IMETHODIMP
3047 nsDownload::OnStateChange(nsIWebProgress *aWebProgress,
3048 nsIRequest *aRequest, uint32_t aStateFlags,
3049 nsresult aStatus)
3050 {
3051 MOZ_ASSERT(NS_IsMainThread(), "Must call OnStateChange in main thread");
3053 // We don't want to lose access to our member variables
3054 nsRefPtr<nsDownload> kungFuDeathGrip = this;
3056 // Check if we're starting a request; the NETWORK flag is necessary to not
3057 // pick up the START of *each* file but only for the whole request
3058 if ((aStateFlags & STATE_START) && (aStateFlags & STATE_IS_NETWORK)) {
3059 nsresult rv;
3060 nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
3061 if (NS_SUCCEEDED(rv)) {
3062 uint32_t status;
3063 rv = channel->GetResponseStatus(&status);
3064 // HTTP 450 - Blocked by parental control proxies
3065 if (NS_SUCCEEDED(rv) && status == 450) {
3066 // Cancel using the provided object
3067 (void)Cancel();
3069 // Fail the download
3070 (void)SetState(nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL);
3071 }
3072 }
3073 } else if ((aStateFlags & STATE_STOP) && (aStateFlags & STATE_IS_NETWORK) &&
3074 IsFinishable()) {
3075 // We got both STOP and NETWORK so that means the whole request is done
3076 // (and not just a single file if there are multiple files)
3077 if (NS_SUCCEEDED(aStatus)) {
3078 // We can't completely trust the bytes we've added up because we might be
3079 // missing on some/all of the progress updates (especially from cache).
3080 // Our best bet is the file itself, but if for some reason it's gone or
3081 // if we have multiple files, the next best is what we've calculated.
3082 int64_t fileSize;
3083 nsCOMPtr<nsIFile> file;
3084 // We need a nsIFile clone to deal with file size caching issues. :(
3085 nsCOMPtr<nsIFile> clone;
3086 if (!mHasMultipleFiles &&
3087 NS_SUCCEEDED(GetTargetFile(getter_AddRefs(file))) &&
3088 NS_SUCCEEDED(file->Clone(getter_AddRefs(clone))) &&
3089 NS_SUCCEEDED(clone->GetFileSize(&fileSize)) && fileSize > 0) {
3090 mCurrBytes = mMaxBytes = fileSize;
3092 // If we resumed, keep the fact that we did and fix size calculations
3093 if (WasResumed())
3094 mResumedAt = 0;
3095 } else if (mMaxBytes == -1) {
3096 mMaxBytes = mCurrBytes;
3097 } else {
3098 mCurrBytes = mMaxBytes;
3099 }
3101 mPercentComplete = 100;
3102 mLastUpdate = PR_Now();
3104 #ifdef DOWNLOAD_SCANNER
3105 bool scan = true;
3106 nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
3107 if (prefs)
3108 (void)prefs->GetBoolPref(PREF_BDM_SCANWHENDONE, &scan);
3110 if (scan)
3111 (void)SetState(nsIDownloadManager::DOWNLOAD_SCANNING);
3112 else
3113 (void)SetState(nsIDownloadManager::DOWNLOAD_FINISHED);
3114 #else
3115 (void)SetState(nsIDownloadManager::DOWNLOAD_FINISHED);
3116 #endif
3117 } else {
3118 // We failed for some unknown reason -- fail with a generic message
3119 (void)FailDownload(aStatus, nullptr);
3120 }
3121 }
3123 mDownloadManager->NotifyListenersOnStateChange(aWebProgress, aRequest,
3124 aStateFlags, aStatus, this);
3125 return NS_OK;
3126 }
3128 NS_IMETHODIMP
3129 nsDownload::OnSecurityChange(nsIWebProgress *aWebProgress,
3130 nsIRequest *aRequest, uint32_t aState)
3131 {
3132 return NS_OK;
3133 }
3135 ////////////////////////////////////////////////////////////////////////////////
3136 //// nsIDownload
3138 NS_IMETHODIMP
3139 nsDownload::Init(nsIURI *aSource,
3140 nsIURI *aTarget,
3141 const nsAString& aDisplayName,
3142 nsIMIMEInfo *aMIMEInfo,
3143 PRTime aStartTime,
3144 nsIFile *aTempFile,
3145 nsICancelable *aCancelable,
3146 bool aIsPrivate)
3147 {
3148 NS_WARNING("Huh...how did we get here?!");
3149 return NS_OK;
3150 }
3152 NS_IMETHODIMP
3153 nsDownload::GetState(int16_t *aState)
3154 {
3155 *aState = mDownloadState;
3156 return NS_OK;
3157 }
3159 NS_IMETHODIMP
3160 nsDownload::GetDisplayName(nsAString &aDisplayName)
3161 {
3162 aDisplayName = mDisplayName;
3163 return NS_OK;
3164 }
3166 NS_IMETHODIMP
3167 nsDownload::GetCancelable(nsICancelable **aCancelable)
3168 {
3169 *aCancelable = mCancelable;
3170 NS_IF_ADDREF(*aCancelable);
3171 return NS_OK;
3172 }
3174 NS_IMETHODIMP
3175 nsDownload::GetTarget(nsIURI **aTarget)
3176 {
3177 *aTarget = mTarget;
3178 NS_IF_ADDREF(*aTarget);
3179 return NS_OK;
3180 }
3182 NS_IMETHODIMP
3183 nsDownload::GetSource(nsIURI **aSource)
3184 {
3185 *aSource = mSource;
3186 NS_IF_ADDREF(*aSource);
3187 return NS_OK;
3188 }
3190 NS_IMETHODIMP
3191 nsDownload::GetStartTime(int64_t *aStartTime)
3192 {
3193 *aStartTime = mStartTime;
3194 return NS_OK;
3195 }
3197 NS_IMETHODIMP
3198 nsDownload::GetPercentComplete(int32_t *aPercentComplete)
3199 {
3200 *aPercentComplete = mPercentComplete;
3201 return NS_OK;
3202 }
3204 NS_IMETHODIMP
3205 nsDownload::GetAmountTransferred(int64_t *aAmountTransferred)
3206 {
3207 *aAmountTransferred = mCurrBytes + (WasResumed() ? mResumedAt : 0);
3208 return NS_OK;
3209 }
3211 NS_IMETHODIMP
3212 nsDownload::GetSize(int64_t *aSize)
3213 {
3214 *aSize = mMaxBytes + (WasResumed() && mMaxBytes != -1 ? mResumedAt : 0);
3215 return NS_OK;
3216 }
3218 NS_IMETHODIMP
3219 nsDownload::GetMIMEInfo(nsIMIMEInfo **aMIMEInfo)
3220 {
3221 *aMIMEInfo = mMIMEInfo;
3222 NS_IF_ADDREF(*aMIMEInfo);
3223 return NS_OK;
3224 }
3226 NS_IMETHODIMP
3227 nsDownload::GetTargetFile(nsIFile **aTargetFile)
3228 {
3229 nsresult rv;
3231 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mTarget, &rv);
3232 if (NS_FAILED(rv)) {
3233 return rv;
3234 }
3236 nsCOMPtr<nsIFile> file;
3237 rv = fileURL->GetFile(getter_AddRefs(file));
3238 if (NS_FAILED(rv)) {
3239 return rv;
3240 }
3242 file.forget(aTargetFile);
3243 return rv;
3244 }
3246 NS_IMETHODIMP
3247 nsDownload::GetSpeed(double *aSpeed)
3248 {
3249 *aSpeed = mSpeed;
3250 return NS_OK;
3251 }
3253 NS_IMETHODIMP
3254 nsDownload::GetId(uint32_t *aId)
3255 {
3256 if (mPrivate) {
3257 return NS_ERROR_NOT_AVAILABLE;
3258 }
3259 *aId = mID;
3260 return NS_OK;
3261 }
3263 NS_IMETHODIMP
3264 nsDownload::GetGuid(nsACString &aGUID)
3265 {
3266 aGUID = mGUID;
3267 return NS_OK;
3268 }
3270 NS_IMETHODIMP
3271 nsDownload::GetReferrer(nsIURI **referrer)
3272 {
3273 NS_IF_ADDREF(*referrer = mReferrer);
3274 return NS_OK;
3275 }
3277 NS_IMETHODIMP
3278 nsDownload::GetResumable(bool *resumable)
3279 {
3280 *resumable = IsResumable();
3281 return NS_OK;
3282 }
3284 NS_IMETHODIMP
3285 nsDownload::GetIsPrivate(bool *isPrivate)
3286 {
3287 *isPrivate = mPrivate;
3288 return NS_OK;
3289 }
3291 ////////////////////////////////////////////////////////////////////////////////
3292 //// nsDownload Helper Functions
3294 void
3295 nsDownload::Finalize()
3296 {
3297 // We're stopping, so break the cycle we created at download start
3298 mCancelable = nullptr;
3300 // Reset values that aren't needed anymore, so the DB can be updated as well
3301 mEntityID.Truncate();
3302 mTempFile = nullptr;
3304 // Remove ourself from the active downloads
3305 nsCOMArray<nsDownload>& currentDownloads = mPrivate ?
3306 mDownloadManager->mCurrentPrivateDownloads :
3307 mDownloadManager->mCurrentDownloads;
3308 (void)currentDownloads.RemoveObject(this);
3310 // Make sure we do not automatically resume
3311 mAutoResume = DONT_RESUME;
3312 }
3314 nsresult
3315 nsDownload::ExecuteDesiredAction()
3316 {
3317 // nsExternalHelperAppHandler is the only caller of AddDownload that sets a
3318 // tempfile parameter. In this case, execute the desired action according to
3319 // the saved mime info.
3320 if (!mTempFile) {
3321 return NS_OK;
3322 }
3324 // We need to bail if for some reason the temp file got removed
3325 bool fileExists;
3326 if (NS_FAILED(mTempFile->Exists(&fileExists)) || !fileExists)
3327 return NS_ERROR_FILE_NOT_FOUND;
3329 // Assume an unknown action is save to disk
3330 nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk;
3331 if (mMIMEInfo) {
3332 nsresult rv = mMIMEInfo->GetPreferredAction(&action);
3333 NS_ENSURE_SUCCESS(rv, rv);
3334 }
3336 nsresult retVal = NS_OK;
3337 switch (action) {
3338 case nsIMIMEInfo::saveToDisk:
3339 // Move the file to the proper location
3340 retVal = MoveTempToTarget();
3341 break;
3342 case nsIMIMEInfo::useHelperApp:
3343 case nsIMIMEInfo::useSystemDefault:
3344 // For these cases we have to move the file to the target location and
3345 // open with the appropriate application
3346 retVal = OpenWithApplication();
3347 break;
3348 default:
3349 break;
3350 }
3352 return retVal;
3353 }
3355 nsresult
3356 nsDownload::MoveTempToTarget()
3357 {
3358 nsCOMPtr<nsIFile> target;
3359 nsresult rv = GetTargetFile(getter_AddRefs(target));
3360 NS_ENSURE_SUCCESS(rv, rv);
3362 // MoveTo will fail if the file already exists, but we've already obtained
3363 // confirmation from the user that this is OK, so remove it if it exists.
3364 bool fileExists;
3365 if (NS_SUCCEEDED(target->Exists(&fileExists)) && fileExists) {
3366 rv = target->Remove(false);
3367 NS_ENSURE_SUCCESS(rv, rv);
3368 }
3370 // Extract the new leaf name from the file location
3371 nsAutoString fileName;
3372 rv = target->GetLeafName(fileName);
3373 NS_ENSURE_SUCCESS(rv, rv);
3374 nsCOMPtr<nsIFile> dir;
3375 rv = target->GetParent(getter_AddRefs(dir));
3376 NS_ENSURE_SUCCESS(rv, rv);
3377 rv = mTempFile->MoveTo(dir, fileName);
3378 NS_ENSURE_SUCCESS(rv, rv);
3380 return NS_OK;
3381 }
3383 nsresult
3384 nsDownload::OpenWithApplication()
3385 {
3386 // First move the temporary file to the target location
3387 nsCOMPtr<nsIFile> target;
3388 nsresult rv = GetTargetFile(getter_AddRefs(target));
3389 NS_ENSURE_SUCCESS(rv, rv);
3391 // Move the temporary file to the target location
3392 rv = MoveTempToTarget();
3393 NS_ENSURE_SUCCESS(rv, rv);
3395 // We do not verify the return value here because, irrespective of success
3396 // or failure of the method, the deletion of temp file has to take place, as
3397 // per the corresponding preference. But we store this separately as this is
3398 // what we ultimately return from this function.
3399 nsresult retVal = mMIMEInfo->LaunchWithFile(target);
3401 bool deleteTempFileOnExit;
3402 nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
3403 if (!prefs || NS_FAILED(prefs->GetBoolPref(PREF_BH_DELETETEMPFILEONEXIT,
3404 &deleteTempFileOnExit))) {
3405 // No prefservice or no pref set; use default value
3406 #if !defined(XP_MACOSX)
3407 // Mac users have been very verbal about temp files being deleted on
3408 // app exit - they don't like it - but we'll continue to do this on
3409 // other platforms for now.
3410 deleteTempFileOnExit = true;
3411 #else
3412 deleteTempFileOnExit = false;
3413 #endif
3414 }
3416 // Always schedule files to be deleted at the end of the private browsing
3417 // mode, regardless of the value of the pref.
3418 if (deleteTempFileOnExit || mPrivate) {
3419 // Use the ExternalHelperAppService to push the temporary file to the list
3420 // of files to be deleted on exit.
3421 nsCOMPtr<nsPIExternalAppLauncher> appLauncher(do_GetService
3422 (NS_EXTERNALHELPERAPPSERVICE_CONTRACTID));
3424 // Even if we are unable to get this service we return the result
3425 // of LaunchWithFile() which makes more sense.
3426 if (appLauncher) {
3427 if (mPrivate) {
3428 (void)appLauncher->DeleteTemporaryPrivateFileWhenPossible(target);
3429 } else {
3430 (void)appLauncher->DeleteTemporaryFileOnExit(target);
3431 }
3432 }
3433 }
3435 return retVal;
3436 }
3438 void
3439 nsDownload::SetStartTime(int64_t aStartTime)
3440 {
3441 mStartTime = aStartTime;
3442 mLastUpdate = aStartTime;
3443 }
3445 void
3446 nsDownload::SetProgressBytes(int64_t aCurrBytes, int64_t aMaxBytes)
3447 {
3448 mCurrBytes = aCurrBytes;
3449 mMaxBytes = aMaxBytes;
3451 // Get the real bytes that include resume position
3452 int64_t currBytes, maxBytes;
3453 (void)GetAmountTransferred(&currBytes);
3454 (void)GetSize(&maxBytes);
3456 if (currBytes == maxBytes)
3457 mPercentComplete = 100;
3458 else if (maxBytes <= 0)
3459 mPercentComplete = -1;
3460 else
3461 mPercentComplete = (int32_t)((double)currBytes / maxBytes * 100 + .5);
3462 }
3464 NS_IMETHODIMP
3465 nsDownload::Pause()
3466 {
3467 if (!IsResumable())
3468 return NS_ERROR_UNEXPECTED;
3470 nsresult rv = CancelTransfer();
3471 NS_ENSURE_SUCCESS(rv, rv);
3473 return SetState(nsIDownloadManager::DOWNLOAD_PAUSED);
3474 }
3476 nsresult
3477 nsDownload::CancelTransfer()
3478 {
3479 nsresult rv = NS_OK;
3480 if (mCancelable) {
3481 rv = mCancelable->Cancel(NS_BINDING_ABORTED);
3482 // we're done with this, so break the cycle
3483 mCancelable = nullptr;
3484 }
3486 return rv;
3487 }
3489 NS_IMETHODIMP
3490 nsDownload::Cancel()
3491 {
3492 // Don't cancel if download is already finished
3493 if (IsFinished())
3494 return NS_OK;
3496 // Have the download cancel its connection
3497 (void)CancelTransfer();
3499 // Dump the temp file because we know we don't need the file anymore. The
3500 // underlying transfer creating the file doesn't delete the file because it
3501 // can't distinguish between a pause that cancels the transfer or a real
3502 // cancel.
3503 if (mTempFile) {
3504 bool exists;
3505 mTempFile->Exists(&exists);
3506 if (exists)
3507 mTempFile->Remove(false);
3508 }
3510 nsCOMPtr<nsIFile> file;
3511 if (NS_SUCCEEDED(GetTargetFile(getter_AddRefs(file))))
3512 {
3513 bool exists;
3514 file->Exists(&exists);
3515 if (exists)
3516 file->Remove(false);
3517 }
3519 nsresult rv = SetState(nsIDownloadManager::DOWNLOAD_CANCELED);
3520 NS_ENSURE_SUCCESS(rv, rv);
3522 return NS_OK;
3523 }
3525 NS_IMETHODIMP
3526 nsDownload::Resume()
3527 {
3528 if (!IsPaused() || !IsResumable())
3529 return NS_ERROR_UNEXPECTED;
3531 nsresult rv;
3532 nsCOMPtr<nsIWebBrowserPersist> wbp =
3533 do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv);
3534 NS_ENSURE_SUCCESS(rv, rv);
3536 rv = wbp->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_APPEND_TO_FILE |
3537 nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION);
3538 NS_ENSURE_SUCCESS(rv, rv);
3540 // Create a new channel for the source URI
3541 nsCOMPtr<nsIChannel> channel;
3542 nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(wbp));
3543 rv = NS_NewChannel(getter_AddRefs(channel), mSource, nullptr, nullptr, ir);
3544 NS_ENSURE_SUCCESS(rv, rv);
3546 nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel);
3547 if (pbChannel) {
3548 pbChannel->SetPrivate(mPrivate);
3549 }
3551 // Make sure we can get a file, either the temporary or the real target, for
3552 // both purposes of file size and a target to write to
3553 nsCOMPtr<nsIFile> targetLocalFile(mTempFile);
3554 if (!targetLocalFile) {
3555 rv = GetTargetFile(getter_AddRefs(targetLocalFile));
3556 NS_ENSURE_SUCCESS(rv, rv);
3557 }
3559 // Get the file size to be used as an offset, but if anything goes wrong
3560 // along the way, we'll silently restart at 0.
3561 int64_t fileSize;
3562 // We need a nsIFile clone to deal with file size caching issues. :(
3563 nsCOMPtr<nsIFile> clone;
3564 if (NS_FAILED(targetLocalFile->Clone(getter_AddRefs(clone))) ||
3565 NS_FAILED(clone->GetFileSize(&fileSize)))
3566 fileSize = 0;
3568 // Set the channel to resume at the right position along with the entityID
3569 nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(channel));
3570 if (!resumableChannel)
3571 return NS_ERROR_UNEXPECTED;
3572 rv = resumableChannel->ResumeAt(fileSize, mEntityID);
3573 NS_ENSURE_SUCCESS(rv, rv);
3575 // If we know the max size, we know what it should be when resuming
3576 int64_t maxBytes;
3577 GetSize(&maxBytes);
3578 SetProgressBytes(0, maxBytes != -1 ? maxBytes - fileSize : -1);
3579 // Track where we resumed because progress notifications restart at 0
3580 mResumedAt = fileSize;
3582 // Set the referrer
3583 if (mReferrer) {
3584 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
3585 if (httpChannel) {
3586 rv = httpChannel->SetReferrer(mReferrer);
3587 NS_ENSURE_SUCCESS(rv, rv);
3588 }
3589 }
3591 // Creates a cycle that will be broken when the download finishes
3592 mCancelable = wbp;
3593 (void)wbp->SetProgressListener(this);
3595 // Save the channel using nsIWBP
3596 rv = wbp->SaveChannel(channel, targetLocalFile);
3597 if (NS_FAILED(rv)) {
3598 mCancelable = nullptr;
3599 (void)wbp->SetProgressListener(nullptr);
3600 return rv;
3601 }
3603 return SetState(nsIDownloadManager::DOWNLOAD_DOWNLOADING);
3604 }
3606 NS_IMETHODIMP
3607 nsDownload::Remove()
3608 {
3609 return mDownloadManager->RemoveDownload(mGUID);
3610 }
3612 NS_IMETHODIMP
3613 nsDownload::Retry()
3614 {
3615 return mDownloadManager->RetryDownload(mGUID);
3616 }
3618 bool
3619 nsDownload::IsPaused()
3620 {
3621 return mDownloadState == nsIDownloadManager::DOWNLOAD_PAUSED;
3622 }
3624 bool
3625 nsDownload::IsResumable()
3626 {
3627 return !mEntityID.IsEmpty();
3628 }
3630 bool
3631 nsDownload::WasResumed()
3632 {
3633 return mResumedAt != -1;
3634 }
3636 bool
3637 nsDownload::ShouldAutoResume()
3638 {
3639 return mAutoResume == AUTO_RESUME;
3640 }
3642 bool
3643 nsDownload::IsFinishable()
3644 {
3645 return mDownloadState == nsIDownloadManager::DOWNLOAD_NOTSTARTED ||
3646 mDownloadState == nsIDownloadManager::DOWNLOAD_QUEUED ||
3647 mDownloadState == nsIDownloadManager::DOWNLOAD_DOWNLOADING;
3648 }
3650 bool
3651 nsDownload::IsFinished()
3652 {
3653 return mDownloadState == nsIDownloadManager::DOWNLOAD_FINISHED;
3654 }
3656 nsresult
3657 nsDownload::UpdateDB()
3658 {
3659 NS_ASSERTION(mID, "Download ID is stored as zero. This is bad!");
3660 NS_ASSERTION(mDownloadManager, "Egads! We have no download manager!");
3662 mozIStorageStatement *stmt = mPrivate ?
3663 mDownloadManager->mUpdatePrivateDownloadStatement : mDownloadManager->mUpdateDownloadStatement;
3665 nsAutoString tempPath;
3666 if (mTempFile)
3667 (void)mTempFile->GetPath(tempPath);
3668 nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("tempPath"), tempPath);
3670 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startTime"), mStartTime);
3671 NS_ENSURE_SUCCESS(rv, rv);
3673 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("endTime"), mLastUpdate);
3674 NS_ENSURE_SUCCESS(rv, rv);
3676 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), mDownloadState);
3677 NS_ENSURE_SUCCESS(rv, rv);
3679 if (mReferrer) {
3680 nsAutoCString referrer;
3681 rv = mReferrer->GetSpec(referrer);
3682 NS_ENSURE_SUCCESS(rv, rv);
3683 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("referrer"), referrer);
3684 } else {
3685 rv = stmt->BindNullByName(NS_LITERAL_CSTRING("referrer"));
3686 }
3687 NS_ENSURE_SUCCESS(rv, rv);
3689 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("entityID"), mEntityID);
3690 NS_ENSURE_SUCCESS(rv, rv);
3692 int64_t currBytes;
3693 (void)GetAmountTransferred(&currBytes);
3694 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("currBytes"), currBytes);
3695 NS_ENSURE_SUCCESS(rv, rv);
3697 int64_t maxBytes;
3698 (void)GetSize(&maxBytes);
3699 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("maxBytes"), maxBytes);
3700 NS_ENSURE_SUCCESS(rv, rv);
3702 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), mAutoResume);
3703 NS_ENSURE_SUCCESS(rv, rv);
3705 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mID);
3706 NS_ENSURE_SUCCESS(rv, rv);
3708 return stmt->Execute();
3709 }
3711 nsresult
3712 nsDownload::FailDownload(nsresult aStatus, const char16_t *aMessage)
3713 {
3714 // Grab the bundle before potentially losing our member variables
3715 nsCOMPtr<nsIStringBundle> bundle = mDownloadManager->mBundle;
3717 (void)SetState(nsIDownloadManager::DOWNLOAD_FAILED);
3719 // Get title for alert.
3720 nsXPIDLString title;
3721 nsresult rv = bundle->GetStringFromName(
3722 MOZ_UTF16("downloadErrorAlertTitle"), getter_Copies(title));
3723 NS_ENSURE_SUCCESS(rv, rv);
3725 // Get a generic message if we weren't supplied one
3726 nsXPIDLString message;
3727 message = aMessage;
3728 if (message.IsEmpty()) {
3729 rv = bundle->GetStringFromName(
3730 MOZ_UTF16("downloadErrorGeneric"), getter_Copies(message));
3731 NS_ENSURE_SUCCESS(rv, rv);
3732 }
3734 // Get Download Manager window to be parent of alert
3735 nsCOMPtr<nsIWindowMediator> wm =
3736 do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
3737 NS_ENSURE_SUCCESS(rv, rv);
3738 nsCOMPtr<nsIDOMWindow> dmWindow;
3739 rv = wm->GetMostRecentWindow(MOZ_UTF16("Download:Manager"),
3740 getter_AddRefs(dmWindow));
3741 NS_ENSURE_SUCCESS(rv, rv);
3743 // Show alert
3744 nsCOMPtr<nsIPromptService> prompter =
3745 do_GetService("@mozilla.org/embedcomp/prompt-service;1", &rv);
3746 NS_ENSURE_SUCCESS(rv, rv);
3747 return prompter->Alert(dmWindow, title, message);
3748 }