diff --git a/mozilla/toolkit/components/downloads/src/Makefile.in b/mozilla/toolkit/components/downloads/src/Makefile.in index 7270dd45a1b..269ca1b1c4f 100644 --- a/mozilla/toolkit/components/downloads/src/Makefile.in +++ b/mozilla/toolkit/components/downloads/src/Makefile.in @@ -66,6 +66,7 @@ REQUIRES = xpcom \ alerts \ storage \ xulapp \ + exthandler \ $(NULL) CPPSRCS = \ diff --git a/mozilla/toolkit/components/downloads/src/nsDownloadManager.cpp b/mozilla/toolkit/components/downloads/src/nsDownloadManager.cpp index 8fff55f407f..1dfa62fd0cf 100644 --- a/mozilla/toolkit/components/downloads/src/nsDownloadManager.cpp +++ b/mozilla/toolkit/components/downloads/src/nsDownloadManager.cpp @@ -74,6 +74,9 @@ #include "nsIDownloadManagerUI.h" #include "nsTArray.h" #include "nsIResumableChannel.h" +#include "nsCExternalHandlerService.h" +#include "nsIExternalHelperAppService.h" +#include "nsIMIMEService.h" #ifdef XP_WIN #include @@ -89,10 +92,11 @@ static PRBool gStoppingDownloads = PR_FALSE; #define PREF_BDM_RETENTION "browser.download.manager.retention" #define PREF_BDM_CLOSEWHENDONE "browser.download.manager.closeWhenDone" #define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs" +#define PREF_BH_DELETETEMPFILEONEXIT "browser.helperApps.deleteTempFileOnExit" static const PRInt64 gUpdateInterval = 400 * PR_USEC_PER_MSEC; -#define DM_SCHEMA_VERSION 6 +#define DM_SCHEMA_VERSION 7 #define DM_DB_NAME NS_LITERAL_STRING("downloads.sqlite") #define DM_DB_CORRUPT_FILENAME NS_LITERAL_STRING("downloads.sqlite.corrupt") @@ -323,6 +327,30 @@ nsDownloadManager::InitDB(PRBool *aDoImport) } // Fallthrough to the next upgrade + case 6: // This version adds three columns to DB (MIME type related info) + { + rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "ALTER TABLE moz_downloads " + "ADD COLUMN mimeType TEXT")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "ALTER TABLE moz_downloads " + "ADD COLUMN preferredApplication TEXT")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "ALTER TABLE moz_downloads " + "ADD COLUMN preferredAction INTEGER NOT NULL DEFAULT 0")); + NS_ENSURE_SUCCESS(rv, rv); + + // Finally, update the schemaVersion variable and the database schema + schemaVersion = 7; + rv = mDBConn->SetSchemaVersion(schemaVersion); + NS_ENSURE_SUCCESS(rv, rv); + } + // Fallthrough to next upgrade + // Extra sanity checking for developers #ifndef DEBUG case DM_SCHEMA_VERSION: @@ -353,7 +381,8 @@ nsDownloadManager::InitDB(PRBool *aDoImport) nsCOMPtr stmt; rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT id, name, source, target, tempPath, startTime, endTime, state, " - "referrer, entityID, currBytes, maxBytes " + "referrer, entityID, currBytes, maxBytes, mimeType, " + "preferredApplication, preferredAction " "FROM moz_downloads"), getter_AddRefs(stmt)); if (NS_SUCCEEDED(rv)) break; @@ -398,7 +427,10 @@ nsDownloadManager::CreateTable() "referrer TEXT, " "entityID TEXT, " "currBytes INTEGER NOT NULL DEFAULT 0, " - "maxBytes INTEGER NOT NULL DEFAULT -1" + "maxBytes INTEGER NOT NULL DEFAULT -1, " + "mimeType TEXT, " + "preferredApplication TEXT, " + "preferredAction INTEGER NOT NULL DEFAULT 0" ")")); } @@ -535,7 +567,8 @@ nsDownloadManager::ImportDownloadHistory() if (NS_FAILED(rv)) continue; (void)AddDownloadToDB(name, source, target, EmptyString(), startTime, - endTime, state); + endTime, state, EmptyCString(), EmptyCString(), + nsIMIMEInfo::saveToDisk); } return NS_OK; @@ -636,13 +669,17 @@ nsDownloadManager::AddDownloadToDB(const nsAString &aName, const nsAString &aTempPath, PRInt64 aStartTime, PRInt64 aEndTime, - PRInt32 aState) + PRInt32 aState, + const nsACString &aMimeType, + const nsACString &aPreferredApp, + nsHandlerInfoAction aPreferredAction) { nsCOMPtr stmt; nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "INSERT INTO moz_downloads " - "(name, source, target, tempPath, startTime, endTime, state) " - "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"), getter_AddRefs(stmt)); + "(name, source, target, tempPath, startTime, endTime, state, " + "mimeType, preferredApplication, preferredAction) " + "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)"), getter_AddRefs(stmt)); NS_ENSURE_SUCCESS(rv, 0); PRInt32 i = 0; @@ -674,6 +711,18 @@ nsDownloadManager::AddDownloadToDB(const nsAString &aName, rv = stmt->BindInt32Parameter(i++, aState); NS_ENSURE_SUCCESS(rv, 0); + // mimeType + rv = stmt->BindUTF8StringParameter(i++, aMimeType); + NS_ENSURE_SUCCESS(rv, 0); + + // preferredApplication + rv = stmt->BindUTF8StringParameter(i++, aPreferredApp); + NS_ENSURE_SUCCESS(rv, 0); + + // preferredAction + rv = stmt->BindInt32Parameter(i++, aPreferredAction); + NS_ENSURE_SUCCESS(rv, 0); + PRBool hasMore; rv = stmt->ExecuteStep(&hasMore); // we want to keep our lock NS_ENSURE_SUCCESS(rv, 0); @@ -775,7 +824,8 @@ nsDownloadManager::GetDownloadFromDB(PRUint32 aID, nsDownload **retVal) nsCOMPtr stmt; nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT id, state, startTime, source, target, tempPath, name, referrer, " - "entityID, currBytes, maxBytes " + "entityID, currBytes, maxBytes, mimeType, preferredAction, " + "preferredApplication " "FROM moz_downloads " "WHERE id = ?1"), getter_AddRefs(stmt)); NS_ENSURE_SUCCESS(rv, rv); @@ -833,6 +883,52 @@ nsDownloadManager::GetDownloadFromDB(PRUint32 aID, nsDownload **retVal) PRInt64 maxBytes = stmt->AsInt64(i++); dl->SetProgressBytes(currBytes, maxBytes); + // Build mMIMEInfo only if the mimeType in DB is not empty + nsCAutoString mimeType; + rv = stmt->GetUTF8String(i++, mimeType); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mimeType.IsEmpty()) { + nsCOMPtr mimeService = + do_GetService(NS_MIMESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(), + getter_AddRefs(dl->mMIMEInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + nsHandlerInfoAction action = stmt->AsInt32(i++); + rv = dl->mMIMEInfo->SetPreferredAction(action); + NS_ENSURE_SUCCESS(rv, rv); + + nsCAutoString persistentDescriptor; + rv = stmt->GetUTF8String(i++, persistentDescriptor); + NS_ENSURE_SUCCESS(rv, rv); + + if (!persistentDescriptor.IsEmpty()) { + nsCOMPtr handler = + do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr localExecutable; + rv = NS_NewNativeLocalFile(EmptyCString(), PR_FALSE, + getter_AddRefs(localExecutable)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = localExecutable->SetPersistentDescriptor(persistentDescriptor); + NS_ENSURE_SUCCESS(rv, rv); + + rv = handler->SetExecutable(localExecutable); + NS_ENSURE_SUCCESS(rv, rv); + + rv = dl->mMIMEInfo->SetPreferredApplicationHandler(handler); + NS_ENSURE_SUCCESS(rv, rv); + } + } else { + // Compensate for the i++s skipped in the true block + i += 2; + } + // Addrefing and returning NS_ADDREF(*retVal = dl); return NS_OK; @@ -1090,9 +1186,33 @@ nsDownloadManager::AddDownload(DownloadType aDownloadType, if (aTempFile) aTempFile->GetPath(tempPath); + // Break down MIMEInfo but don't panic if we can't get all the pieces - we + // can still download the file + nsCAutoString persistentDescriptor, mimeType; + nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk; + if (aMIMEInfo) { + (void)aMIMEInfo->GetType(mimeType); + + nsCOMPtr handlerApp; + (void)aMIMEInfo->GetPreferredApplicationHandler(getter_AddRefs(handlerApp)); + nsCOMPtr locHandlerApp = do_QueryInterface(handlerApp); + + if (locHandlerApp) { + nsCOMPtr executable; + (void)locHandlerApp->GetExecutable(getter_AddRefs(executable)); + nsCOMPtr locExecutable = do_QueryInterface(executable); + + if (locExecutable) + (void)locExecutable->GetPersistentDescriptor(persistentDescriptor); + } + + (void)aMIMEInfo->GetPreferredAction(&action); + } + PRInt64 id = AddDownloadToDB(dl->mDisplayName, source, target, tempPath, dl->mStartTime, dl->mLastUpdate, - nsIDownloadManager::DOWNLOAD_NOTSTARTED); + nsIDownloadManager::DOWNLOAD_NOTSTARTED, + mimeType, persistentDescriptor, action); NS_ENSURE_TRUE(id, NS_ERROR_FAILURE); dl->mID = id; @@ -2066,17 +2186,23 @@ nsDownload::ExecuteDesiredAction() NS_ENSURE_SUCCESS(rv, rv); } - nsresult ret = NS_OK; + nsresult retVal = NS_OK; switch (action) { case nsIMIMEInfo::saveToDisk: // Move the file to the proper location - ret = MoveTempToTarget(); + retVal = MoveTempToTarget(); + break; + case nsIMIMEInfo::useHelperApp: + case nsIMIMEInfo::useSystemDefault: + // For these cases we have to move the file to the target location and + // open with the appropriate application + retVal = OpenWithApplication(); break; default: break; } - return ret; + return retVal; } nsresult @@ -2107,6 +2233,60 @@ nsDownload::MoveTempToTarget() return NS_OK; } +nsresult +nsDownload::OpenWithApplication() +{ + // First move the temporary file to the target location + nsCOMPtr target; + nsresult rv = GetTargetFile(getter_AddRefs(target)); + NS_ENSURE_SUCCESS(rv, rv); + + // Make sure the suggested name is unique since in this case we don't + // have a file name that was guaranteed to be unique by going through + // the File Save dialog + rv = target->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + // Move the temporary file to the target location + rv = MoveTempToTarget(); + NS_ENSURE_SUCCESS(rv, rv); + + // We do not verify the return value here because, irrespective of success + // or failure of the method, the deletion of temp file has to take place, as + // per the corresponding preference. But we store this separately as this is + // what we ultimately return from this function. + nsresult retVal = mMIMEInfo->LaunchWithFile(target); + + PRBool deleteTempFileOnExit; + nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (!prefs || NS_FAILED(prefs->GetBoolPref(PREF_BH_DELETETEMPFILEONEXIT, + &deleteTempFileOnExit))) { + // No prefservice or no pref set; use default value +#if !defined(XP_MACOSX) + // Mac users have been very verbal about temp files being deleted on + // app exit - they don't like it - but we'll continue to do this on + // other platforms for now. + deleteTempFileOnExit = PR_TRUE; +#else + deleteTempFileOnExit = PR_FALSE; +#endif + } + + if (deleteTempFileOnExit) { + // Use the ExternalHelperAppService to push the temporary file to the list + // of files to be deleted on exit. + nsCOMPtr appLauncher(do_GetService + (NS_EXTERNALHELPERAPPSERVICE_CONTRACTID)); + + // Even if we are unable to get this service we return the result + // of LaunchWithFile() which makes more sense. + if (appLauncher) + (void)appLauncher->DeleteTemporaryFileOnExit(target); + } + + return retVal; +} + void nsDownload::SetStartTime(PRInt64 aStartTime) { @@ -2258,12 +2438,7 @@ nsDownload::IsPaused() PRBool nsDownload::IsResumable() { - nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk; - if (mMIMEInfo) - (void)mMIMEInfo->GetPreferredAction(&action); - - // For now we can only resume saveToDisk type actions (not open with) - return action == nsIMIMEInfo::saveToDisk && !mEntityID.IsEmpty(); + return !mEntityID.IsEmpty(); } PRBool diff --git a/mozilla/toolkit/components/downloads/src/nsDownloadManager.h b/mozilla/toolkit/components/downloads/src/nsDownloadManager.h index 8285c3894f9..3b41d8315fe 100644 --- a/mozilla/toolkit/components/downloads/src/nsDownloadManager.h +++ b/mozilla/toolkit/components/downloads/src/nsDownloadManager.h @@ -133,7 +133,10 @@ protected: const nsAString &aTempPath, PRInt64 aStartTime, PRInt64 aEndTime, - PRInt32 aState); + PRInt32 aState, + const nsACString &aMimeType, + const nsACString &aPreferredApp, + nsHandlerInfoAction aPreferredAction); void NotifyListenersOnDownloadStateChange(PRInt16 aOldState, nsIDownload *aDownload); @@ -300,6 +303,18 @@ protected: */ nsresult FailDownload(nsresult aStatus, const PRUnichar *aMessage); + /** + * Opens the downloaded file with the appropriate application, which is + * either the OS default, MIME type default, or the one selected by the user. + * + * This also adds the temporary file to the "To be deleted on Exit" list, if + * the corresponding user preference is set (except on OS X). + * + * This function was adopted from nsExternalAppHandler::OpenWithApplication + * (uriloader/exthandler/nsExternalHelperAppService.cpp). + */ + nsresult OpenWithApplication(); + nsDownloadManager *mDownloadManager; nsCOMPtr mTarget; diff --git a/mozilla/toolkit/components/downloads/test/schema_migration/test_migration_to_7.js b/mozilla/toolkit/components/downloads/test/schema_migration/test_migration_to_7.js new file mode 100644 index 00000000000..0e80f81dc57 --- /dev/null +++ b/mozilla/toolkit/components/downloads/test/schema_migration/test_migration_to_7.js @@ -0,0 +1,102 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Download Manager Test Code. + * + * The Initial Developer of the Original Code is + * Edward Lee . + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by declaring the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not declare + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +function run_test() +{ + // We're testing migration to this version from one version below + var targetVersion = 7; + + // First import the downloads.sqlite file + importDatabaseFile("v" + (targetVersion - 1) + ".sqlite"); + + // Init the download manager which will try migrating to the new version + var dm = Cc["@mozilla.org/download-manager;1"]. + getService(Ci.nsIDownloadManager); + var dbConn = dm.DBConnection; + + // Check schema version + do_check_true(dbConn.schemaVersion >= targetVersion); + + // Make sure all the columns are there + var stmt = dbConn.createStatement( + "SELECT name, source, target, tempPath, startTime, endTime, state, " + + "referrer, entityID, currBytes, maxBytes, mimeType, " + + "preferredApplication, preferredAction " + + "FROM moz_downloads " + + "WHERE id = 28"); + stmt.executeStep(); + + // This data is based on the original values in the table + var data = [ + "firefox-3.0a9pre.en-US.linux-i686.tar.bz2", + "http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/latest-trunk/firefox-3.0a9pre.en-US.linux-i686.tar.bz2", + "file:///Users/Ed/Desktop/firefox-3.0a9pre.en-US.linux-i686.tar.bz2", + "/Users/Ed/Desktop/+EZWafFQ.bz2.part", + 1192469856209164, + 1192469877017396, + 4, + "http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/latest-trunk/", + "%2210e66c1-8a2d6b-9b33f380%22/9055595/Mon, 15 Oct 2007 11:45:34 GMT", + 1210772, + 9055595, + // For the new columns added, check for null or default values + true, + true, + 0, + ]; + + // Make sure the values are correct after the migration + var i = 0; + do_check_eq(data[i], stmt.getString(i++)); + do_check_eq(data[i], stmt.getUTF8String(i++)); + do_check_eq(data[i], stmt.getUTF8String(i++)); + do_check_eq(data[i], stmt.getString(i++)); + do_check_eq(data[i], stmt.getInt64(i++)); + do_check_eq(data[i], stmt.getInt64(i++)); + do_check_eq(data[i], stmt.getInt32(i++)); + do_check_eq(data[i], stmt.getUTF8String(i++)); + do_check_eq(data[i], stmt.getUTF8String(i++)); + do_check_eq(data[i], stmt.getInt64(i++)); + do_check_eq(data[i], stmt.getInt64(i++)); + do_check_eq(data[i], stmt.getIsNull(i++)); + do_check_eq(data[i], stmt.getIsNull(i++)); + do_check_eq(data[i], stmt.getInt32(i++)); + + stmt.reset(); + stmt.finalize(); + + cleanup(); +}