From 0b5f01f577f1a6ecf5880aa1aabd58776b95c10b Mon Sep 17 00:00:00 2001 From: "sdwilsh@shawnwilsher.com" Date: Wed, 5 Sep 2007 21:59:34 -0700 Subject: [PATCH] Bug 377243 - [SoC] Implement Download Resume. Patch by Brahmana and Shawn Wilsher . r=sdwilsh, r=mconnor, a=[wanted-firefox3] --- .../downloads/src/nsDownloadManager.cpp | 190 +++++++++++++++++- .../downloads/src/nsDownloadManager.h | 4 + .../schema_migration/test_migration_to_4.js | 79 ++++++++ .../downloads/test/schema_migration/v3.sqlite | Bin 0 -> 7168 bytes 4 files changed, 262 insertions(+), 11 deletions(-) create mode 100644 toolkit/components/downloads/test/schema_migration/test_migration_to_4.js create mode 100644 toolkit/components/downloads/test/schema_migration/v3.sqlite diff --git a/toolkit/components/downloads/src/nsDownloadManager.cpp b/toolkit/components/downloads/src/nsDownloadManager.cpp index 7e00536f5179..57dfed131306 100644 --- a/toolkit/components/downloads/src/nsDownloadManager.cpp +++ b/toolkit/components/downloads/src/nsDownloadManager.cpp @@ -23,6 +23,7 @@ * Blake Ross (Original Author) * Ben Goodger (Original Author) * Shawn Wilsher + * Srirang G Doddihal * * 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 @@ -71,6 +72,7 @@ #include "nsIHttpChannel.h" #include "nsIDownloadManagerUI.h" #include "nsTArray.h" +#include "nsIResumableChannel.h" #ifdef XP_WIN #include @@ -89,7 +91,7 @@ static PRBool gStoppingDownloads = PR_FALSE; static const PRInt64 gUpdateInterval = 400 * PR_USEC_PER_MSEC; -#define DM_SCHEMA_VERSION 3 +#define DM_SCHEMA_VERSION 4 #define DM_DB_NAME NS_LITERAL_STRING("downloads.sqlite") #define DM_DB_CORRUPT_FILENAME NS_LITERAL_STRING("downloads.sqlite.corrupt") @@ -147,10 +149,66 @@ nsDownloadManager::CompleteDownload(nsDownload *aDownload) { // we've stopped, so break the cycle we created at download start aDownload->mCancelable = nsnull; + aDownload->mEntityID.Truncate(); + if (aDownload->mWasResumed) + (void)ExecuteDesiredAction(aDownload); (void)mCurrentDownloads.RemoveObject(aDownload); } +nsresult +nsDownloadManager::ExecuteDesiredAction(nsDownload *aDownload) +{ + // If we have a temp file and we have resumed, we have to do what the external + // helper app service would have done. + if (!aDownload->mTempFile && !aDownload->mWasResumed) + return NS_OK; + + // Find out if it was a SaveToDisk kind of a download + nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk; + nsresult rv; + if (aDownload->mMIMEInfo) { + rv = aDownload->mMIMEInfo->GetPreferredAction(&action); + NS_ENSURE_SUCCESS(rv, rv); + } + + switch (action) { + case nsIMIMEInfo::saveToDisk: + // For this instance, we need to move the file to the proper location + { + nsCOMPtr target; + rv = aDownload->GetTargetFile(getter_AddRefs(target)); + NS_ENSURE_SUCCESS(rv, rv); + + // MoveTo will fail if the file already exists, but we've already + // obtained confirmation from the user that this is OK. So, we have + // to remove it if it exists. + PRBool fileExists; + if (NS_SUCCEEDED(target->Exists(&fileExists)) && fileExists) { + rv = target->Remove(PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + } + + // extract the new leaf name from the file location + nsAutoString fileName; + rv = target->GetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr dir; + rv = target->GetParent(getter_AddRefs(dir)); + NS_ENSURE_SUCCESS(rv, rv); + if (dir) { + rv = aDownload->mTempFile->MoveTo(dir, fileName); + NS_ENSURE_SUCCESS(rv, rv); + } + } + break; + default: + break; + } + + return NS_OK; +} + nsresult nsDownloadManager::InitDB(PRBool *aDoImport) { @@ -277,6 +335,20 @@ nsDownloadManager::InitDB(PRBool *aDoImport) } // Fallthrough to the next upgrade + case 3: // This version adds a column to the database (entityID) + { + rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "ALTER TABLE moz_downloads " + "ADD COLUMN entityID TEXT")); + NS_ENSURE_SUCCESS(rv, rv); + + // Finally, update the schemaVersion variable and the database schema + schemaVersion = 4; + rv = mDBConn->SetSchemaVersion(schemaVersion); + NS_ENSURE_SUCCESS(rv, rv); + } + // Fallthrough to the next upgrade + case DM_SCHEMA_VERSION: break; @@ -303,7 +375,8 @@ nsDownloadManager::InitDB(PRBool *aDoImport) { nsCOMPtr stmt; rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( - "SELECT id, name, source, target, startTime, endTime, state, referrer " + "SELECT id, name, source, target, startTime, endTime, state, referrer, " + "entityID " "FROM moz_downloads"), getter_AddRefs(stmt)); if (NS_SUCCEEDED(rv)) break; @@ -344,7 +417,8 @@ nsDownloadManager::CreateTable() "startTime INTEGER, " "endTime INTEGER, " "state INTEGER, " - "referrer TEXT" + "referrer TEXT, " + "entityID TEXT" ")")); } @@ -631,8 +705,8 @@ nsDownloadManager::Init() rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "UPDATE moz_downloads " - "SET startTime = ?1, endTime = ?2, state = ?3, referrer = ?4 " - "WHERE id = ?5"), getter_AddRefs(mUpdateDownloadStatement)); + "SET startTime = ?1, endTime = ?2, state = ?3, referrer = ?4, entityID = ?5 " + "WHERE id = ?6"), getter_AddRefs(mUpdateDownloadStatement)); NS_ENSURE_SUCCESS(rv, rv); // The following three AddObserver calls must be the last lines in this function, @@ -676,7 +750,7 @@ nsDownloadManager::GetDownloadFromDB(PRUint32 aID, nsDownload **retVal) // First, let's query the database and see if it even exists nsCOMPtr stmt; nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( - "SELECT id, state, startTime, source, target, name, referrer " + "SELECT id, state, startTime, source, target, name, referrer, entityID " "FROM moz_downloads " "WHERE id = ?1"), getter_AddRefs(stmt)); NS_ENSURE_SUCCESS(rv, rv); @@ -742,6 +816,9 @@ nsDownloadManager::GetDownloadFromDB(PRUint32 aID, nsDownload **retVal) dl->mCurrBytes = 0; } + rv = stmt->GetUTF8String(7, dl->mEntityID); + NS_ENSURE_SUCCESS(rv, rv); + // Addrefing and returning NS_ADDREF(*retVal = dl); return NS_OK; @@ -1413,6 +1490,7 @@ nsDownload::nsDownload() : mDownloadState(nsIDownloadManager::DOWNLOAD_NOTSTARTE mStartTime(0), mLastUpdate(PR_Now() - (PRUint32)gUpdateInterval), mPaused(PR_FALSE), + mWasResumed(PR_FALSE), mSpeed(0) { } @@ -1635,6 +1713,13 @@ nsDownload::OnProgressChange64(nsIWebProgress *aWebProgress, } } + //Fetch the entityID + nsCOMPtr resumableChannel(do_QueryInterface(aRequest)); + if (resumableChannel) { + rv = resumableChannel->GetEntityID(mEntityID); + NS_ENSURE_SUCCESS(rv, rv); + } + // Update the state and the database rv = SetState(nsIDownloadManager::DOWNLOAD_DOWNLOADING); NS_ENSURE_SUCCESS(rv, rv); @@ -1956,15 +2041,94 @@ nsDownload::PauseResume(PRBool aPause) if (mPaused == aPause || !mRequest) return NS_OK; - if (aPause) { - nsresult rv = mRequest->Suspend(); + nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk; + nsresult rv; + if (mMIMEInfo) { + rv = mMIMEInfo->GetPreferredAction(&action); NS_ENSURE_SUCCESS(rv, rv); + } + + PRBool resumable = PR_FALSE; + if (action == nsIMIMEInfo::saveToDisk && !mEntityID.IsEmpty()) + resumable = PR_TRUE; + + if (aPause) { + if (resumable) { + rv = mCancelable->Cancel(NS_BINDING_ABORTED); + NS_ENSURE_SUCCESS(rv, rv); + } else { + // This is for non-resumable downloads and downloads that are used with + // "Open With...". + rv = mRequest->Suspend(); + NS_ENSURE_SUCCESS(rv, rv); + } mPaused = PR_TRUE; return SetState(nsIDownloadManager::DOWNLOAD_PAUSED); } - nsresult rv = mRequest->Resume(); - NS_ENSURE_SUCCESS(rv, rv); + if (resumable) { + mWasResumed = PR_TRUE; + + nsCOMPtr wbp = + do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = wbp->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION); + NS_ENSURE_SUCCESS(rv, rv); + + // Create a new channel for the source URI + nsCOMPtr channel; + nsCOMPtr ir(do_QueryInterface(wbp)); + rv = NS_NewChannel(getter_AddRefs(channel), mSource, nsnull, nsnull, ir); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the size of the temporary or target file to be used as offset. + PRInt64 fileSize; + nsCOMPtr targetLocalFile(mTempFile); + if (!targetLocalFile) { + rv = GetTargetFile(getter_AddRefs(targetLocalFile)); + NS_ENSURE_SUCCESS(rv, rv); + } + // We need to get a new nsIFile though because of caching issues with the + // file size. Cloning it takes care of this :( + nsCOMPtr clone; + rv = targetLocalFile->Clone(getter_AddRefs(clone)); + NS_ENSURE_SUCCESS(rv, rv); + rv = clone->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, rv); + + // Set the channel to resume at the right position along with the entityID + nsCOMPtr resumableChannel(do_QueryInterface(channel)); + if (!resumableChannel) + return NS_ERROR_UNEXPECTED; + rv = resumableChannel->ResumeAt(fileSize, mEntityID); + NS_ENSURE_SUCCESS(rv, rv); + + // Set the referrer + if (mReferrer) { + nsCOMPtr httpChannel(do_QueryInterface(channel)); + if (httpChannel) { + rv = httpChannel->SetReferrer(mReferrer); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // Creates a cycle that will be broken when the download finishes + mCancelable = wbp; + (void)wbp->SetProgressListener(this); + + // Save the channel using nsIWBP. + rv = wbp->SaveChannel(channel, targetLocalFile); + if (NS_FAILED(rv)) { + mCancelable = nsnull; + (void)wbp->SetProgressListener(nsnull); + return rv; + } + } else { + rv = mRequest->Resume(); + NS_ENSURE_SUCCESS(rv, rv); + } + mPaused = PR_FALSE; return SetState(nsIDownloadManager::DOWNLOAD_DOWNLOADING); } @@ -2000,8 +2164,12 @@ nsDownload::UpdateDB() } NS_ENSURE_SUCCESS(rv, rv); + //entityID + rv = stmt->BindUTF8StringParameter(4, mEntityID); + NS_ENSURE_SUCCESS(rv, rv); + // id - rv = stmt->BindInt64Parameter(4, mID); + rv = stmt->BindInt64Parameter(5, mID); NS_ENSURE_SUCCESS(rv, rv); return stmt->Execute(); diff --git a/toolkit/components/downloads/src/nsDownloadManager.h b/toolkit/components/downloads/src/nsDownloadManager.h index d760a59d5b32..2ccf60c8bf6e 100644 --- a/toolkit/components/downloads/src/nsDownloadManager.h +++ b/toolkit/components/downloads/src/nsDownloadManager.h @@ -23,6 +23,7 @@ * Blake Ross * Ben Goodger * Shawn Wilsher + * Srirang G Doddihal * * 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 @@ -158,6 +159,7 @@ protected: const PRUnichar* aDontCancelButton); PRInt32 GetRetentionBehavior(); + nsresult ExecuteDesiredAction(nsDownload *aDownload); static PRBool IsInFinalStage(DownloadState aState) { @@ -227,6 +229,7 @@ protected: private: nsString mDisplayName; + nsCString mEntityID; nsCOMPtr mSource; nsCOMPtr mReferrer; @@ -245,6 +248,7 @@ private: PRTime mStartTime; PRTime mLastUpdate; PRBool mPaused; + PRBool mWasResumed; double mSpeed; friend class nsDownloadManager; diff --git a/toolkit/components/downloads/test/schema_migration/test_migration_to_4.js b/toolkit/components/downloads/test/schema_migration/test_migration_to_4.js new file mode 100644 index 000000000000..c560581d9042 --- /dev/null +++ b/toolkit/components/downloads/test/schema_migration/test_migration_to_4.js @@ -0,0 +1,79 @@ +/* ***** 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 + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Shawn Wilsher (Original Author) + * Srirang G Doddihal + * + * 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 ***** */ + +// This file tests migration from v3 to v4 + +function run_test() +{ + // First import the downloads.sqlite file + importDatabaseFile("v3.sqlite"); + + // ok, now it is OK to init the download manager - this will perform the + // migration! + var dm = Cc["@mozilla.org/download-manager;1"]. + getService(Ci.nsIDownloadManager); + var dbConn = dm.DBConnection; + var stmt = null; + + // check schema version + do_check_true(dbConn.schemaVersion >= 4); + + // Check that the column exists (statement should not throw) + stmt = dbConn.createStatement("SELECT entityID FROM moz_downloads"); + + // now we check the entries + stmt = dbConn.createStatement( + "SELECT name, source, target, startTime, endTime, state, referrer, entityID " + + "FROM moz_downloads " + + "WHERE id = 27"); + stmt.executeStep(); + do_check_eq("Firefox 2.0.0.6.dmg", stmt.getString(0)); + do_check_eq("http://ftp-mozilla.netscape.com/pub/mozilla.org/firefox/releases/2.0.0.6/mac/en-US/Firefox%202.0.0.6.dmg", + stmt.getUTF8String(1)); + do_check_eq("file:///Users/sdwilsh/Desktop/Firefox%202.0.0.6.dmg", + stmt.getUTF8String(2)); + do_check_eq(1187390974170783, stmt.getInt64(3)); + do_check_eq(1187391001257446, stmt.getInt64(4)); + do_check_eq(1, stmt.getInt32(5)); + do_check_eq("http://www.mozilla.com/en-US/products/download.html?product=firefox-2.0.0.6&os=osx&lang=en-US",stmt.getUTF8String(6)); + do_check_true(stmt.getIsNull(7)); + stmt.reset(); + + cleanup(); +} + diff --git a/toolkit/components/downloads/test/schema_migration/v3.sqlite b/toolkit/components/downloads/test/schema_migration/v3.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..9924dbe31b401bfbbf25bf6003acdf26a13692c7 GIT binary patch literal 7168 zcmeHLYitx%6uz^&(iQ@OFQ7mjLwFdvGi_;$sEusfrJ#V)E`i!KIJ+L_@Txh5cosn`Nx_l!5BlL=g!X5Ep#6gm!j#k zvuEziz2}~L?m6E%=XPw~AeoqrYlb42>`D|s41?CNEJ6VZWv+XG&^zGXnYRcr@Zk3v z`RGje$oQW`@IQ4;Y2bgO0XxK$&0V&P=?j@cRK|+dyIs^$s;mhj8Po8!w1-2HFdGRq zZwRwvKC#OskzKzj5^f8(v(L4!-xzA|WS+84Cmx-W|;= zLX5|1a7zd8@?BC_lf^kM$rw2ETL?A?PsEfYlO*4Y$xc(##~*?Ms|HUEX@@91^Xsmt zD_km-;yeMVM43r-Lldo-NqEm%xTLAbPr0v~T(N_$L`yZ&q>(*KWkF3eISc?z7lUe^ z1=TS5R3i(T*=un^EXG-i#SzTwR&-QhnvuBE8v3pG$UdqeO8+XUiu#r=m;oy4E@VA% zQxAF@q+*lA1RXoJGr5`?VOAe}3p6$2J9X)DNfkBIcN_xy5X5HYJTng-hVCj1aD~y! z%h4jC6QDpQLAjkv^uor0i^4A#H!zM8k)!rZcWo`Sn>$+h(Vls|+3SS@bxPIKuY5=8 zBM0_fw%f`oJOdgFKJ{7PbGznKp9hMZd2S7goFTA+HT%DMZP1~YufJ?>4OE~fY~@AJ zUL=it~p4#!r?5!SrFrM7aHLofVv(QiLChvHKctzyPT&vDATV5Yp| zR6!ypg{NF_l`HLfgC(pf6t$JMr|XXj5p#yRoe4*zjuO z+%^8&$IopA-3=anqiI%_Cwj>9@74Hyiyv|dSWr*i8sD9{ZuI=a6p0fgip()fprpgs zF1`CBDCzJY2M4eDOR6P%&Vv