diff options
author | benjhayden@chromium.org <benjhayden@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-20 00:52:08 +0000 |
---|---|---|
committer | benjhayden@chromium.org <benjhayden@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-20 00:52:08 +0000 |
commit | 3d95e54652070dacfdb9188c3f6f48111bcc007e (patch) | |
tree | 7ad31f4dbdb170fc30cc00ce2b31d76ce146799d /chrome/browser | |
parent | 8b9d8f76949307b91f6399d452e41de59f2cbce6 (diff) | |
download | chromium_src-3d95e54652070dacfdb9188c3f6f48111bcc007e.zip chromium_src-3d95e54652070dacfdb9188c3f6f48111bcc007e.tar.gz chromium_src-3d95e54652070dacfdb9188c3f6f48111bcc007e.tar.bz2 |
Make DownloadHistory observe manager, items
Rip out half of DownloadManagerDelegate.
Make DownloadManager create persisted DownloadItems one at a time and return them to DownloadHistory.
Move DownloadPersistentStoreInfo from content to chrome.
Kill DownloadDatabase::CheckThread(). (Leftover from 85408.)
Change DownloadDatabase::RemoveDownloads() to take an explicit set of db_handles.
Merge DownloadDatabase::UpdateDownload[Path]().
After this CL, I'll send out another one to remove the usage of CancelableRequest from the downloads-specific HistoryService APIs.
BUG=154309
Review URL: https://chromiumcodereview.appspot.com/10915180
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@168670 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
25 files changed, 1844 insertions, 916 deletions
diff --git a/chrome/browser/download/chrome_download_manager_delegate.cc b/chrome/browser/download/chrome_download_manager_delegate.cc index fb4e5b6..345c4ce 100644 --- a/chrome/browser/download/chrome_download_manager_delegate.cc +++ b/chrome/browser/download/chrome_download_manager_delegate.cc @@ -29,6 +29,8 @@ #include "chrome/browser/extensions/api/downloads/downloads_api.h" #include "chrome/browser/extensions/crx_installer.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/intents/web_intents_util.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" @@ -141,6 +143,21 @@ void OnWebIntentDispatchCompleted( base::Bind(&DeleteFile, file_path)); } +typedef base::Callback<void(bool)> VisitedBeforeCallback; + +// Condenses the results from HistoryService::GetVisibleVisitCountToHost() to a +// single bool so that VisitedBeforeCallback can curry up to 5 other parameters +// without a struct. +void VisitCountsToVisitedBefore( + const VisitedBeforeCallback& callback, + HistoryService::Handle unused_handle, + bool found_visits, + int count, + base::Time first_visit) { + callback.Run(found_visits && count && + (first_visit.LocalMidnight() < base::Time::Now().LocalMidnight())); +} + } // namespace ChromeDownloadManagerDelegate::ChromeDownloadManagerDelegate(Profile* profile) @@ -154,18 +171,6 @@ ChromeDownloadManagerDelegate::~ChromeDownloadManagerDelegate() { void ChromeDownloadManagerDelegate::SetDownloadManager(DownloadManager* dm) { download_manager_ = dm; - download_history_.reset(new DownloadHistory(profile_)); - if (!profile_->IsOffTheRecord()) { - // DownloadManager should not be RefCountedThreadSafe. - // ChromeDownloadManagerDelegate outlives DownloadManager, and - // DownloadHistory uses a scoped canceller to cancel tasks when it is - // deleted. Almost all callbacks to DownloadManager should use weak pointers - // or bounce off a container object that uses ManagerGoingDown() to simulate - // a weak pointer. - download_history_->Load( - base::Bind(&DownloadManager::OnPersistentStoreQueryComplete, - download_manager_)); - } #if !defined(OS_ANDROID) extension_event_router_.reset(new ExtensionDownloadsEventRouter( profile_, download_manager_)); @@ -173,7 +178,6 @@ void ChromeDownloadManagerDelegate::SetDownloadManager(DownloadManager* dm) { } void ChromeDownloadManagerDelegate::Shutdown() { - download_history_.reset(); download_prefs_.reset(); #if !defined(OS_ANDROID) extension_event_router_.reset(); @@ -481,42 +485,6 @@ bool ChromeDownloadManagerDelegate::GenerateFileHash() { #endif } -void ChromeDownloadManagerDelegate::AddItemToPersistentStore( - DownloadItem* item) { - if (profile_->IsOffTheRecord()) { - OnItemAddedToPersistentStore( - item->GetId(), download_history_->GetNextFakeDbHandle()); - return; - } - download_history_->AddEntry(item, - base::Bind(&ChromeDownloadManagerDelegate::OnItemAddedToPersistentStore, - this)); -} - -void ChromeDownloadManagerDelegate::UpdateItemInPersistentStore( - DownloadItem* item) { - download_history_->UpdateEntry(item); -} - -void ChromeDownloadManagerDelegate::UpdatePathForItemInPersistentStore( - DownloadItem* item, - const FilePath& new_path) { - download_history_->UpdateDownloadPath(item, new_path); -} - -void ChromeDownloadManagerDelegate::RemoveItemFromPersistentStore( - DownloadItem* item) { - download_history_->RemoveEntry(item); -} - -void ChromeDownloadManagerDelegate::RemoveItemsFromPersistentStoreBetween( - base::Time remove_begin, - base::Time remove_end) { - if (profile_->IsOffTheRecord()) - return; - download_history_->RemoveEntriesBetween(remove_begin, remove_end); -} - void ChromeDownloadManagerDelegate::GetSaveDir(BrowserContext* browser_context, FilePath* website_save_dir, FilePath* download_save_dir, @@ -639,10 +607,22 @@ void ChromeDownloadManagerDelegate::CheckDownloadUrlDone( if (result != DownloadProtectionService::SAFE) danger_type = content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL; - download_history_->CheckVisitedReferrerBefore( - download_id, download->GetReferrerUrl(), - base::Bind(&ChromeDownloadManagerDelegate::CheckVisitedReferrerBeforeDone, - this, download_id, callback, danger_type)); + // HistoryServiceFactory redirects incognito profiles to on-record profiles. + HistoryService* history = HistoryServiceFactory::GetForProfile( + profile_, Profile::EXPLICIT_ACCESS); + if (!history || !download->GetReferrerUrl().is_valid()) { + // If the original profile doesn't have a HistoryService or the referrer url + // is invalid, then give up and assume the referrer has not been visited + // before. There's no history for on-record profiles in unit_tests, for + // example. + CheckVisitedReferrerBeforeDone(download_id, callback, danger_type, false); + return; + } + history->GetVisibleVisitCountToHost( + download->GetReferrerUrl(), &history_consumer_, + base::Bind(&VisitCountsToVisitedBefore, base::Bind( + &ChromeDownloadManagerDelegate::CheckVisitedReferrerBeforeDone, + this, download_id, callback, danger_type))); } void ChromeDownloadManagerDelegate::CheckClientDownloadDone( @@ -887,15 +867,3 @@ void ChromeDownloadManagerDelegate::OnTargetPathDetermined( } callback.Run(target_path, disposition, danger_type, intermediate_path); } - -void ChromeDownloadManagerDelegate::OnItemAddedToPersistentStore( - int32 download_id, int64 db_handle) { - // It's not immediately obvious, but HistoryBackend::CreateDownload() can - // call this function with an invalid |db_handle|. For instance, this can - // happen when the history database is offline. We cannot have multiple - // DownloadItems with the same invalid db_handle, so we need to assign a - // unique |db_handle| here. - if (db_handle == DownloadItem::kUninitializedHandle) - db_handle = download_history_->GetNextFakeDbHandle(); - download_manager_->OnItemAddedToPersistentStore(download_id, db_handle); -} diff --git a/chrome/browser/download/chrome_download_manager_delegate.h b/chrome/browser/download/chrome_download_manager_delegate.h index ac60ef7..9224165 100644 --- a/chrome/browser/download/chrome_download_manager_delegate.h +++ b/chrome/browser/download/chrome_download_manager_delegate.h @@ -9,16 +9,15 @@ #include "base/hash_tables.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" -#include "base/memory/weak_ptr.h" -#include "chrome/browser/safe_browsing/download_protection_service.h" +#include "chrome/browser/common/cancelable_request.h" #include "chrome/browser/download/download_path_reservation_tracker.h" +#include "chrome/browser/safe_browsing/download_protection_service.h" #include "content/public/browser/download_danger_type.h" #include "content/public/browser/download_item.h" #include "content/public/browser/download_manager_delegate.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" -class DownloadHistory; class DownloadPrefs; class ExtensionDownloadsEventRouter; class Profile; @@ -57,7 +56,7 @@ class ChromeDownloadManagerDelegate void SetDownloadManager(content::DownloadManager* dm); - // Should be called before the call to ShouldCompleteDownload() to + // Should be called before the first call to ShouldCompleteDownload() to // disable SafeBrowsing checks for |item|. static void DisableSafeBrowsing(content::DownloadItem* item); @@ -77,17 +76,6 @@ class ChromeDownloadManagerDelegate content::DownloadItem* item, const content::DownloadOpenDelayedCallback& callback) OVERRIDE; virtual bool GenerateFileHash() OVERRIDE; - virtual void AddItemToPersistentStore(content::DownloadItem* item) OVERRIDE; - virtual void UpdateItemInPersistentStore( - content::DownloadItem* item) OVERRIDE; - virtual void UpdatePathForItemInPersistentStore( - content::DownloadItem* item, - const FilePath& new_path) OVERRIDE; - virtual void RemoveItemFromPersistentStore( - content::DownloadItem* item) OVERRIDE; - virtual void RemoveItemsFromPersistentStoreBetween( - base::Time remove_begin, - base::Time remove_end) OVERRIDE; virtual void GetSaveDir(content::BrowserContext* browser_context, FilePath* website_save_dir, FilePath* download_save_dir, @@ -104,7 +92,6 @@ class ChromeDownloadManagerDelegate void ClearLastDownloadPath(); DownloadPrefs* download_prefs() { return download_prefs_.get(); } - DownloadHistory* download_history() { return download_history_.get(); } protected: // So that test classes can inherit from this for override purposes. @@ -172,10 +159,10 @@ class ChromeDownloadManagerDelegate // a reserved path for the download. The path is then passed into // OnPathReservationAvailable(). void CheckVisitedReferrerBeforeDone( - int32 download_id, - const content::DownloadTargetCallback& callback, - content::DownloadDangerType danger_type, - bool visited_referrer_before); + int32 download_id, + const content::DownloadTargetCallback& callback, + content::DownloadDangerType danger_type, + bool visited_referrer_before); #if defined (OS_CHROMEOS) // DriveDownloadObserver::SubstituteDriveDownloadPath callback. Calls @@ -215,9 +202,6 @@ class ChromeDownloadManagerDelegate content::DownloadDangerType danger_type, const FilePath& target_path); - // Callback from history system. - void OnItemAddedToPersistentStore(int32 download_id, int64 db_handle); - // Check policy of whether we should open this download with a web intents // dispatch. bool ShouldOpenWithWebIntents(const content::DownloadItem* item); @@ -236,13 +220,14 @@ class ChromeDownloadManagerDelegate Profile* profile_; int next_download_id_; scoped_ptr<DownloadPrefs> download_prefs_; - scoped_ptr<DownloadHistory> download_history_; // Maps from pending extension installations to DownloadItem IDs. typedef base::hash_map<extensions::CrxInstaller*, content::DownloadOpenDelayedCallback> CrxInstallerMap; CrxInstallerMap crx_installers_; + CancelableRequestConsumer history_consumer_; + content::NotificationRegistrar registrar_; // On Android, GET downloads are not handled by the DownloadManager. diff --git a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc index fc32f55..052fa05 100644 --- a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc +++ b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc @@ -303,6 +303,10 @@ void ChromeDownloadManagerDelegateTest::SetUp() { CHECK(profile()); delegate_ = new TestChromeDownloadManagerDelegate(profile()); + EXPECT_CALL(*download_manager_.get(), GetAllDownloads(_)) + .WillRepeatedly(Return()); + EXPECT_CALL(*download_manager_.get(), AddObserver(_)) + .WillRepeatedly(Return()); delegate_->SetDownloadManager(download_manager_.get()); pref_service_ = profile()->GetTestingPrefService(); web_contents()->SetDelegate(&web_contents_delegate_); diff --git a/chrome/browser/download/download_browsertest.cc b/chrome/browser/download/download_browsertest.cc index 0511dcd..51e27de 100644 --- a/chrome/browser/download/download_browsertest.cc +++ b/chrome/browser/download/download_browsertest.cc @@ -23,11 +23,14 @@ #include "chrome/browser/download/download_history.h" #include "chrome/browser/download/download_prefs.h" #include "chrome/browser/download/download_request_limiter.h" +#include "chrome/browser/download/download_service.h" +#include "chrome/browser/download/download_service_factory.h" #include "chrome/browser/download/download_shelf.h" #include "chrome/browser/download/download_test_file_chooser_observer.h" #include "chrome/browser/download/download_util.h" #include "chrome/browser/extensions/extension_install_prompt.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/history/download_row.h" #include "chrome/browser/history/history.h" #include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/net/url_request_mock_util.h" @@ -50,7 +53,6 @@ #include "chrome/test/base/ui_test_utils.h" #include "content/public/browser/download_item.h" #include "content/public/browser/download_manager.h" -#include "content/public/browser/download_persistent_store_info.h" #include "content/public/browser/download_save_info.h" #include "content/public/browser/download_url_parameters.h" #include "content/public/browser/notification_source.h" @@ -73,7 +75,6 @@ using content::BrowserContext; using content::BrowserThread; using content::DownloadItem; using content::DownloadManager; -using content::DownloadPersistentStoreInfo; using content::DownloadUrlParameters; using content::URLRequestMockHTTPJob; using content::URLRequestSlowDownloadJob; @@ -90,58 +91,6 @@ const FilePath kGoodCrxPath(FILE_PATH_LITERAL("extensions/good.crx")); const char kLargeThemeCrxId[] = "pjpgmfcmabopnnfonnhmdjglfpjjfkbf"; const FilePath kLargeThemePath(FILE_PATH_LITERAL("extensions/theme2.crx")); -// Get History Information. -class DownloadsHistoryDataCollector { - public: - DownloadsHistoryDataCollector(int64 download_db_handle, - DownloadManager* manager) - : result_valid_(false), - download_db_handle_(download_db_handle) { - HistoryService* hs = HistoryServiceFactory::GetForProfile( - Profile::FromBrowserContext(manager->GetBrowserContext()), - Profile::EXPLICIT_ACCESS); - DCHECK(hs); - hs->QueryDownloads( - &callback_consumer_, - base::Bind(&DownloadsHistoryDataCollector::OnQueryDownloadsComplete, - base::Unretained(this))); - - // TODO(rdsmith): Move message loop out of constructor. - // Cannot complete immediately because the history backend runs on a - // separate thread, so we can assume that the RunMessageLoop below will - // be exited by the Quit in OnQueryDownloadsComplete. - content::RunMessageLoop(); - } - - bool GetDownloadsHistoryEntry(DownloadPersistentStoreInfo* result) { - DCHECK(result); - *result = result_; - return result_valid_; - } - - private: - void OnQueryDownloadsComplete( - std::vector<DownloadPersistentStoreInfo>* entries) { - result_valid_ = false; - for (std::vector<DownloadPersistentStoreInfo>::const_iterator it = - entries->begin(); - it != entries->end(); ++it) { - if (it->db_handle == download_db_handle_) { - result_ = *it; - result_valid_ = true; - } - } - MessageLoopForUI::current()->Quit(); - } - - DownloadPersistentStoreInfo result_; - bool result_valid_; - int64 download_db_handle_; - CancelableRequestConsumer callback_consumer_; - - DISALLOW_COPY_AND_ASSIGN(DownloadsHistoryDataCollector); -}; - // Mock that simulates a permissions dialog where the user denies // permission to install. TODO(skerner): This could be shared with // extensions tests. Find a common place for this class. @@ -239,6 +188,51 @@ class MockDownloadOpeningObserver : public DownloadManager::Observer { DISALLOW_COPY_AND_ASSIGN(MockDownloadOpeningObserver); }; +class HistoryObserver : public DownloadHistory::Observer { + public: + explicit HistoryObserver(Profile* profile) + : profile_(profile), + waiting_(false), + seen_stored_(false) { + DownloadServiceFactory::GetForProfile(profile_)-> + GetDownloadHistory()->AddObserver(this); + } + + virtual ~HistoryObserver() { + DownloadService* service = DownloadServiceFactory::GetForProfile(profile_); + if (service && service->GetDownloadHistory()) + service->GetDownloadHistory()->RemoveObserver(this); + } + + virtual void OnDownloadStored( + content::DownloadItem* item, + const history::DownloadRow& info) OVERRIDE { + seen_stored_ = true; + if (waiting_) + MessageLoopForUI::current()->Quit(); + } + + virtual void OnDownloadHistoryDestroyed() OVERRIDE { + DownloadServiceFactory::GetForProfile(profile_)-> + GetDownloadHistory()->RemoveObserver(this); + } + + void WaitForStored() { + if (seen_stored_) + return; + waiting_ = true; + content::RunMessageLoop(); + waiting_ = false; + } + + private: + Profile* profile_; + bool waiting_; + bool seen_stored_; + + DISALLOW_COPY_AND_ASSIGN(HistoryObserver); +}; + class DownloadTest : public InProcessBrowserTest { public: // Choice of navigation or direct fetch. Used by |DownloadFileCheckErrors()|. @@ -1406,40 +1400,12 @@ IN_PROC_BROWSER_TEST_F(DownloadTest, NewWindow) { CheckDownload(browser(), file, file); } -// Confirm a download makes it into the history properly. IN_PROC_BROWSER_TEST_F(DownloadTest, DownloadHistoryCheck) { FilePath file(FILE_PATH_LITERAL("download-test1.lib")); - GURL url(URLRequestMockHTTPJob::GetMockUrl(file)); - FilePath origin_file(OriginFile(file)); - int64 origin_size; - file_util::GetFileSize(origin_file, &origin_size); - - // Download the file and wait. We do not expect the Select File dialog. - DownloadAndWait(browser(), url); - - // Get details of what downloads have just happened. - std::vector<DownloadItem*> downloads; - GetDownloads(browser(), &downloads); - ASSERT_EQ(1u, downloads.size()); - int64 db_handle = downloads[0]->GetDbHandle(); - - // Check state. - EXPECT_EQ(1, browser()->tab_count()); - CheckDownload(browser(), file, file); - EXPECT_TRUE(browser()->window()->IsDownloadShelfVisible()); - - // Check history results. - DownloadsHistoryDataCollector history_collector( - db_handle, - DownloadManagerForBrowser(browser())); - DownloadPersistentStoreInfo info; - EXPECT_TRUE(history_collector.GetDownloadsHistoryEntry(&info)) << db_handle; - EXPECT_EQ(file, info.path.BaseName()); - EXPECT_EQ(url, info.url); - // Ignore start_time. - EXPECT_EQ(origin_size, info.received_bytes); - EXPECT_EQ(origin_size, info.total_bytes); - EXPECT_EQ(DownloadItem::COMPLETE, info.state); + GURL download_url(URLRequestMockHTTPJob::GetMockUrl(file)); + HistoryObserver observer(browser()->profile()); + DownloadAndWait(browser(), download_url); + observer.WaitForStored(); } // Test for crbug.com/14505. This tests that chrome:// urls are still functional diff --git a/chrome/browser/download/download_history.cc b/chrome/browser/download/download_history.cc index 9d70195..11a5532 100644 --- a/chrome/browser/download/download_history.cc +++ b/chrome/browser/download/download_history.cc @@ -2,153 +2,428 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// DownloadHistory manages persisting DownloadItems to the history service by +// observing a single DownloadManager and all its DownloadItems using an +// AllDownloadItemNotifier. +// +// DownloadHistory decides whether and when to add items to, remove items from, +// and update items in the database. DownloadHistory uses DownloadHistoryData to +// store per-DownloadItem data such as its db_handle, whether the item is being +// added and waiting for its db_handle, and the last history::DownloadRow +// that was passed to the database. When the DownloadManager and its delegate +// (ChromeDownloadManagerDelegate) are initialized, DownloadHistory is created +// and queries the HistoryService. When the HistoryService calls back from +// QueryDownloads() to QueryCallback(), DownloadHistory uses +// DownloadManager::CreateDownloadItem() to inform DownloadManager of these +// persisted DownloadItems. CreateDownloadItem() internally calls +// OnDownloadCreated(), which normally adds items to the database, so +// QueryCallback() uses |loading_db_handle_| to disable adding these items to +// the database as it matches them up with their db_handles. If a download is +// removed via OnDownloadRemoved() while the item is still being added to the +// database, DownloadHistory uses |removed_while_adding_| to remember to remove +// the item when its ItemAdded() callback is called. All callbacks are bound +// with a weak pointer to DownloadHistory to prevent use-after-free bugs. +// ChromeDownloadManagerDelegate owns DownloadHistory, and deletes it in +// Shutdown(), which is called by DownloadManagerImpl::Shutdown() after all +// DownloadItems are destroyed. +// +// Strictly speaking, the weak pointers in the callbacks from the history system +// are redundant with the CancelableRequestConsumer. +// TODO(benjhayden) Use PostTaskAndReply with the weak pointers instead of +// CancelableRequestConsumer. This requires modifying the downloads-related +// portion of the HistoryService interface. + #include "chrome/browser/download/download_history.h" -#include "base/logging.h" +#include "base/metrics/histogram.h" #include "chrome/browser/download/download_crx_util.h" -#include "chrome/browser/history/history_marshaling.h" -#include "chrome/browser/history/history_service_factory.h" -#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/history/download_database.h" +#include "chrome/browser/history/download_row.h" +#include "chrome/browser/history/history.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/download_item.h" -#include "content/public/browser/download_persistent_store_info.h" +#include "content/public/browser/download_manager.h" + +namespace { + +// Per-DownloadItem data. This information does not belong inside DownloadItem, +// and keeping maps in DownloadHistory from DownloadItem to this information is +// error-prone and complicated. Unfortunately, DownloadHistory::removing_*_ and +// removed_while_adding_ cannot be moved into this class partly because +// DownloadHistoryData is destroyed when DownloadItems are destroyed, and we +// have no control over when DownloadItems are destroyed. +class DownloadHistoryData : public base::SupportsUserData::Data { + public: + static DownloadHistoryData* Get(content::DownloadItem* item) { + base::SupportsUserData::Data* data = item->GetUserData(kKey); + return (data == NULL) ? NULL : + static_cast<DownloadHistoryData*>(data); + } + + DownloadHistoryData(content::DownloadItem* item, int64 handle) + : is_adding_(false), + db_handle_(handle), + info_(NULL) { + item->SetUserData(kKey, this); + } + + virtual ~DownloadHistoryData() { + } + + // Whether this item is currently being added to the database. + bool is_adding() const { return is_adding_; } + void set_is_adding(bool a) { is_adding_ = a; } + + // Whether this item is already persisted in the database. + bool is_persisted() const { + return db_handle_ != history::DownloadDatabase::kUninitializedHandle; + } + + int64 db_handle() const { return db_handle_; } + void set_db_handle(int64 h) { db_handle_ = h; } + + // This allows DownloadHistory::OnDownloadUpdated() to see what changed in a + // DownloadItem if anything, in order to prevent writing to the database + // unnecessarily. It is nullified when the item is no longer in progress in + // order to save memory. + history::DownloadRow* info() { return info_.get(); } + void set_info(const history::DownloadRow& i) { + info_.reset(new history::DownloadRow(i)); + } + void clear_info() { + info_.reset(); + } -using content::DownloadItem; -using content::DownloadPersistentStoreInfo; + private: + static const char kKey[]; -DownloadHistory::DownloadHistory(Profile* profile) - : profile_(profile), - next_fake_db_handle_(DownloadItem::kUninitializedHandle - 1) { - DCHECK(profile); + bool is_adding_; + int64 db_handle_; + scoped_ptr<history::DownloadRow> info_; + + DISALLOW_COPY_AND_ASSIGN(DownloadHistoryData); +}; + +const char DownloadHistoryData::kKey[] = + "DownloadItem DownloadHistoryData"; + +history::DownloadRow GetDownloadRow( + content::DownloadItem* item) { + // TODO(asanka): Persist GetTargetFilePath() as well. + DownloadHistoryData* data = DownloadHistoryData::Get(item); + return history::DownloadRow( + item->GetFullPath(), + item->GetURL(), + item->GetReferrerUrl(), + item->GetStartTime(), + item->GetEndTime(), + item->GetReceivedBytes(), + item->GetTotalBytes(), + item->GetState(), + ((data != NULL) ? data->db_handle() + : history::DownloadDatabase::kUninitializedHandle), + item->GetOpened()); } -DownloadHistory::~DownloadHistory() {} +bool ShouldUpdateHistory(const history::DownloadRow* previous, + const history::DownloadRow& current) { + // Ignore url, referrer, start_time, db_handle, which don't change. + return ((previous == NULL) || + (previous->path != current.path) || + (previous->end_time != current.end_time) || + (previous->received_bytes != current.received_bytes) || + (previous->total_bytes != current.total_bytes) || + (previous->state != current.state) || + (previous->opened != current.opened)); +} -void DownloadHistory::GetNextId( - const HistoryService::DownloadNextIdCallback& callback) { - HistoryService* hs = HistoryServiceFactory::GetForProfile( - profile_, Profile::EXPLICIT_ACCESS); - if (!hs) - return; +typedef std::vector<history::DownloadRow> InfoVector; - hs->GetNextDownloadId(&history_consumer_, callback); +} // anonymous namespace + +DownloadHistory::HistoryAdapter::HistoryAdapter(HistoryService* history) + : history_(history) { } +DownloadHistory::HistoryAdapter::~HistoryAdapter() {} -void DownloadHistory::Load( +void DownloadHistory::HistoryAdapter::QueryDownloads( const HistoryService::DownloadQueryCallback& callback) { - HistoryService* hs = HistoryServiceFactory::GetForProfile( - profile_, Profile::EXPLICIT_ACCESS); - if (!hs) + history_->QueryDownloads(&consumer_, callback); +} + +void DownloadHistory::HistoryAdapter::CreateDownload( + const history::DownloadRow& info, + const HistoryService::DownloadCreateCallback& callback) { + history_->CreateDownload(info, &consumer_, callback); +} + +void DownloadHistory::HistoryAdapter::UpdateDownload( + const history::DownloadRow& data) { + history_->UpdateDownload(data); +} + +void DownloadHistory::HistoryAdapter::RemoveDownloads( + const std::set<int64>& db_handles) { + history_->RemoveDownloads(db_handles); +} + + +DownloadHistory::Observer::Observer() {} +DownloadHistory::Observer::~Observer() {} + +bool DownloadHistory::IsPersisted(content::DownloadItem* item) { + DownloadHistoryData* data = DownloadHistoryData::Get(item); + return data && data->is_persisted(); +} + +DownloadHistory::DownloadHistory( + content::DownloadManager* manager, + scoped_ptr<HistoryAdapter> history) + : ALLOW_THIS_IN_INITIALIZER_LIST(notifier_(manager, this)), + history_(history.Pass()), + loading_db_handle_(history::DownloadDatabase::kUninitializedHandle), + history_size_(0), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + content::DownloadManager::DownloadVector items; + notifier_.GetManager()->GetAllDownloads(&items); + for (content::DownloadManager::DownloadVector::const_iterator + it = items.begin(); it != items.end(); ++it) { + OnDownloadCreated(notifier_.GetManager(), *it); + } + history_->QueryDownloads(base::Bind( + &DownloadHistory::QueryCallback, weak_ptr_factory_.GetWeakPtr())); +} + +DownloadHistory::~DownloadHistory() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + FOR_EACH_OBSERVER(Observer, observers_, OnDownloadHistoryDestroyed()); + observers_.Clear(); +} + +void DownloadHistory::AddObserver(DownloadHistory::Observer* observer) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + observers_.AddObserver(observer); +} + +void DownloadHistory::RemoveObserver(DownloadHistory::Observer* observer) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + observers_.RemoveObserver(observer); +} + +void DownloadHistory::QueryCallback(InfoVector* infos) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + // ManagerGoingDown() may have happened before the history loaded. + if (!notifier_.GetManager()) return; + for (InfoVector::const_iterator it = infos->begin(); + it != infos->end(); ++it) { + // OnDownloadCreated() is called inside DM::CreateDownloadItem(), so set + // loading_db_handle_ to match up the created item with its db_handle. All + // methods run on the UI thread and CreateDownloadItem() is synchronous. + loading_db_handle_ = it->db_handle; + content::DownloadItem* download_item = + notifier_.GetManager()->CreateDownloadItem( + it->path, + it->url, + it->referrer_url, + it->start_time, + it->end_time, + it->received_bytes, + it->total_bytes, + it->state, + it->opened); + DownloadHistoryData* data = DownloadHistoryData::Get(download_item); - hs->QueryDownloads(&history_consumer_, callback); - - // This is the initial load, so do a cleanup of corrupt in-progress entries. - hs->CleanUpInProgressEntries(); -} - -void DownloadHistory::CheckVisitedReferrerBefore( - int32 download_id, - const GURL& referrer_url, - const VisitedBeforeDoneCallback& callback) { - if (referrer_url.is_valid()) { - HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( - profile_, Profile::EXPLICIT_ACCESS); - if (hs) { - HistoryService::Handle handle = - hs->GetVisibleVisitCountToHost(referrer_url, &history_consumer_, - base::Bind(&DownloadHistory::OnGotVisitCountToHost, - base::Unretained(this))); - visited_before_requests_[handle] = callback; - return; - } + // If this DCHECK fails, then you probably added an Observer that + // synchronously creates a DownloadItem in response to + // DownloadManager::OnDownloadCreated(), and your observer runs before + // DownloadHistory, and DownloadManager creates items synchronously. Just + // bounce your DownloadItem creation off the message loop to flush + // DownloadHistory::OnDownloadCreated. + DCHECK_EQ(it->db_handle, data->db_handle()); + ++history_size_; } - callback.Run(false); + notifier_.GetManager()->CheckForHistoryFilesRemoval(); } -void DownloadHistory::AddEntry( - DownloadItem* download_item, - const HistoryService::DownloadCreateCallback& callback) { - DCHECK(download_item); - // Do not store the download in the history database for a few special cases: - // - incognito mode (that is the point of this mode) - // - extensions (users don't think of extension installation as 'downloading') - // - temporary download, like in drag-and-drop - // - history service is not available (e.g. in tests) - // We have to make sure that these handles don't collide with normal db - // handles, so we use a negative value. Eventually, they could overlap, but - // you'd have to do enough downloading that your ISP would likely stab you in - // the neck first. YMMV. - HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( - profile_, Profile::EXPLICIT_ACCESS); - if (download_crx_util::IsExtensionDownload(*download_item) || - download_item->IsTemporary() || !hs) { - callback.Run(download_item->GetId(), GetNextFakeDbHandle()); +void DownloadHistory::MaybeAddToHistory(content::DownloadItem* item) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + int32 download_id = item->GetId(); + DownloadHistoryData* data = DownloadHistoryData::Get(item); + bool removing = (removing_handles_.find(data->db_handle()) != + removing_handles_.end()); + + // TODO(benjhayden): Remove IsTemporary(). + if (download_crx_util::IsExtensionDownload(*item) || + item->IsTemporary() || + data->is_adding() || + data->is_persisted() || + removing) return; + + data->set_is_adding(true); + if (data->info() == NULL) { + // Keep the info here regardless of whether the item is in progress so that, + // when ItemAdded() calls OnDownloadUpdated(), it can decide whether to + // Update the db and/or clear the info. + data->set_info(GetDownloadRow(item)); } - int32 id = download_item->GetId(); - DownloadPersistentStoreInfo history_info = - download_item->GetPersistentStoreInfo(); - hs->CreateDownload(id, history_info, &history_consumer_, callback); + history_->CreateDownload(*data->info(), base::Bind( + &DownloadHistory::ItemAdded, weak_ptr_factory_.GetWeakPtr(), + download_id)); } -void DownloadHistory::UpdateEntry(DownloadItem* download_item) { - // Don't store info in the database if the download was initiated while in - // incognito mode or if it hasn't been initialized in our database table. - if (download_item->GetDbHandle() <= DownloadItem::kUninitializedHandle) +void DownloadHistory::ItemAdded(int32 download_id, int64 db_handle) { + if (removed_while_adding_.find(download_id) != + removed_while_adding_.end()) { + removed_while_adding_.erase(download_id); + ScheduleRemoveDownload(download_id, db_handle); return; + } - HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( - profile_, Profile::EXPLICIT_ACCESS); - if (!hs) + if (!notifier_.GetManager()) return; - hs->UpdateDownload(download_item->GetPersistentStoreInfo()); -} -void DownloadHistory::UpdateDownloadPath(DownloadItem* download_item, - const FilePath& new_path) { - // No update necessary if the download was initiated while in incognito mode. - if (download_item->GetDbHandle() <= DownloadItem::kUninitializedHandle) + content::DownloadItem* item = notifier_.GetManager()->GetDownload( + download_id); + if (!item) { + // This item will have called OnDownloadDestroyed(). If the item should + // have been removed from history, then it would have also called + // OnDownloadRemoved(), which would have put |download_id| in + // removed_while_adding_, handled above. + return; + } + + DownloadHistoryData* data = DownloadHistoryData::Get(item); + data->set_is_adding(false); + + // The sql INSERT statement failed. Avoid an infinite loop: don't + // automatically retry. Retry adding the next time the item is updated by + // unsetting is_adding. + if (db_handle == history::DownloadDatabase::kUninitializedHandle) { + DVLOG(20) << __FUNCTION__ << " INSERT failed id=" << download_id; return; + } + + data->set_db_handle(db_handle); + + // Send to observers the actual history::DownloadRow that was sent to + // the db, plus the db_handle, instead of completely regenerating the + // history::DownloadRow, in order to accurately reflect the contents of + // the database. + data->info()->db_handle = db_handle; + FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored( + item, *data->info())); + + UMA_HISTOGRAM_CUSTOM_COUNTS("Download.HistorySize2", + history_size_, + 0/*min*/, + (1 << 23)/*max*/, + (1 << 7)/*num_buckets*/); + ++history_size_; - HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( - profile_, Profile::EXPLICIT_ACCESS); - if (hs) - hs->UpdateDownloadPath(new_path, download_item->GetDbHandle()); + // In case the item changed or became temporary while it was being added. + // Don't just update all of the item's observers because we're the only + // observer that can also see db_handle, which is the only thing that + // ItemAdded changed. + OnDownloadUpdated(notifier_.GetManager(), item); } -void DownloadHistory::RemoveEntry(DownloadItem* download_item) { - // No update necessary if the download was initiated while in incognito mode. - if (download_item->GetDbHandle() <= DownloadItem::kUninitializedHandle) +void DownloadHistory::OnDownloadCreated( + content::DownloadManager* manager, content::DownloadItem* item) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + // All downloads should pass through OnDownloadCreated exactly once. + CHECK(!DownloadHistoryData::Get(item)); + DownloadHistoryData* data = new DownloadHistoryData(item, loading_db_handle_); + loading_db_handle_ = history::DownloadDatabase::kUninitializedHandle; + if (item->GetState() == content::DownloadItem::IN_PROGRESS) { + data->set_info(GetDownloadRow(item)); + } + MaybeAddToHistory(item); +} + +void DownloadHistory::OnDownloadUpdated( + content::DownloadManager* manager, content::DownloadItem* item) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + DownloadHistoryData* data = DownloadHistoryData::Get(item); + if (!data->is_persisted()) { + MaybeAddToHistory(item); return; + } + if (item->IsTemporary()) { + OnDownloadRemoved(notifier_.GetManager(), item); + return; + } - HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( - profile_, Profile::EXPLICIT_ACCESS); - if (hs) - hs->RemoveDownload(download_item->GetDbHandle()); + history::DownloadRow current_info(GetDownloadRow(item)); + bool should_update = ShouldUpdateHistory(data->info(), current_info); + UMA_HISTOGRAM_ENUMERATION("Download.HistoryPropagatedUpdate", + should_update, 2); + if (should_update) { + history_->UpdateDownload(current_info); + FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored( + item, current_info)); + } + if (item->GetState() == content::DownloadItem::IN_PROGRESS) { + data->set_info(current_info); + } else { + data->clear_info(); + } } -void DownloadHistory::RemoveEntriesBetween(const base::Time remove_begin, - const base::Time remove_end) { - HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( - profile_, Profile::EXPLICIT_ACCESS); - if (hs) - hs->RemoveDownloadsBetween(remove_begin, remove_end); +void DownloadHistory::OnDownloadOpened( + content::DownloadManager* manager, content::DownloadItem* item) { + OnDownloadUpdated(manager, item); } -int64 DownloadHistory::GetNextFakeDbHandle() { - return next_fake_db_handle_--; +void DownloadHistory::OnDownloadRemoved( + content::DownloadManager* manager, content::DownloadItem* item) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + DownloadHistoryData* data = DownloadHistoryData::Get(item); + if (!data->is_persisted()) { + if (data->is_adding()) { + // ScheduleRemoveDownload will be called when history_ calls ItemAdded(). + removed_while_adding_.insert(item->GetId()); + } + return; + } + ScheduleRemoveDownload(item->GetId(), data->db_handle()); + data->set_db_handle(history::DownloadDatabase::kUninitializedHandle); + // ItemAdded increments history_size_ only if the item wasn't + // removed_while_adding_, so the next line does not belong in + // ScheduleRemoveDownload(). + --history_size_; +} + +void DownloadHistory::ScheduleRemoveDownload( + int32 download_id, int64 db_handle) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + if (db_handle == history::DownloadDatabase::kUninitializedHandle) + return; + + // For database efficiency, batch removals together if they happen all at + // once. + if (removing_handles_.empty()) { + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(&DownloadHistory::RemoveDownloadsBatch, + weak_ptr_factory_.GetWeakPtr())); + } + removing_handles_.insert(db_handle); + removing_ids_.insert(download_id); } -void DownloadHistory::OnGotVisitCountToHost(HistoryService::Handle handle, - bool found_visits, - int count, - base::Time first_visit) { - VisitedBeforeRequestsMap::iterator request = - visited_before_requests_.find(handle); - DCHECK(request != visited_before_requests_.end()); - VisitedBeforeDoneCallback callback = request->second; - visited_before_requests_.erase(request); - callback.Run(found_visits && count && - (first_visit.LocalMidnight() < base::Time::Now().LocalMidnight())); +void DownloadHistory::RemoveDownloadsBatch() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + HandleSet remove_handles; + IdSet remove_ids; + removing_handles_.swap(remove_handles); + removing_ids_.swap(remove_ids); + history_->RemoveDownloads(remove_handles); + FOR_EACH_OBSERVER(Observer, observers_, OnDownloadsRemoved(remove_ids)); } diff --git a/chrome/browser/download/download_history.h b/chrome/browser/download/download_history.h index 1a831ed7..4a27bbb 100644 --- a/chrome/browser/download/download_history.h +++ b/chrome/browser/download/download_history.h @@ -5,87 +5,149 @@ #ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_HISTORY_H_ #define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_HISTORY_H_ -#include <map> +#include <set> +#include <vector> #include "base/basictypes.h" #include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" #include "chrome/browser/common/cancelable_request.h" +#include "chrome/browser/download/all_download_item_notifier.h" #include "chrome/browser/history/history.h" +#include "content/public/browser/download_item.h" +#include "content/public/browser/download_manager.h" -class Profile; +namespace history { +struct DownloadRow; +} // namespace history -namespace base { -class Time; -} +// Observes a single DownloadManager and all its DownloadItems, keeping the +// DownloadDatabase up to date. +class DownloadHistory : public AllDownloadItemNotifier::Observer { + public: + typedef std::set<int32> IdSet; -namespace content { -class DownloadItem; -} + // Caller must guarantee that HistoryService outlives HistoryAdapter. + class HistoryAdapter { + public: + explicit HistoryAdapter(HistoryService* history); + virtual ~HistoryAdapter(); -// Interacts with the HistoryService on behalf of the download subsystem. -class DownloadHistory { - public: - typedef base::Callback<void(bool)> VisitedBeforeDoneCallback; + virtual void QueryDownloads( + const HistoryService::DownloadQueryCallback& callback); + + virtual void CreateDownload( + const history::DownloadRow& info, + const HistoryService::DownloadCreateCallback& callback); + + virtual void UpdateDownload(const history::DownloadRow& data); - explicit DownloadHistory(Profile* profile); - ~DownloadHistory(); + virtual void RemoveDownloads(const std::set<int64>& db_handles); - // Retrieves the next_id counter from the sql meta_table. - // Should be much faster than Load so that we may delay downloads until after - // this call with minimal performance penalty. - void GetNextId(const HistoryService::DownloadNextIdCallback& callback); + private: + HistoryService* history_; + CancelableRequestConsumer consumer_; + DISALLOW_COPY_AND_ASSIGN(HistoryAdapter); + }; - // Retrieves DownloadCreateInfos saved in the history. - void Load(const HistoryService::DownloadQueryCallback& callback); + class Observer { + public: + Observer(); + virtual ~Observer(); - // Checks whether |referrer_url| has been visited before today. This takes - // ownership of |callback|. - void CheckVisitedReferrerBefore(int32 download_id, - const GURL& referrer_url, - const VisitedBeforeDoneCallback& callback); + // Fires when a download is added to or updated in the database. When + // downloads are first added, this fires after the callback from the + // database so that |info| includes the |db_handle|. When downloads are + // updated, this fires right after the message is sent to the database. + // |info| always includes the |db_handle|. + virtual void OnDownloadStored(content::DownloadItem* item, + const history::DownloadRow& info) { + } - // Adds a new entry for a download to the history database. - void AddEntry(content::DownloadItem* download_item, - const HistoryService::DownloadCreateCallback& callback); + // Fires when RemoveDownloads messages are sent to the DB thread. + virtual void OnDownloadsRemoved(const IdSet& ids) {} - // Updates the history entry for |download_item|. - void UpdateEntry(content::DownloadItem* download_item); + // Fires when the DownloadHistory is being destroyed so that implementors + // can RemoveObserver() and nullify their DownloadHistory*s. + virtual void OnDownloadHistoryDestroyed() {} + }; - // Updates the download path for |download_item| to |new_path|. - void UpdateDownloadPath(content::DownloadItem* download_item, - const FilePath& new_path); + // Returns true if the item is persisted. + static bool IsPersisted(content::DownloadItem* item); - // Removes |download_item| from the history database. - void RemoveEntry(content::DownloadItem* download_item); + // Neither |manager| nor |history| may be NULL. + // DownloadService creates DownloadHistory some time after DownloadManager is + // created and destroys DownloadHistory as DownloadManager is shutting down. + DownloadHistory( + content::DownloadManager* manager, + scoped_ptr<HistoryAdapter> history); - // Removes download-related history entries in the given time range. - void RemoveEntriesBetween(const base::Time remove_begin, - const base::Time remove_end); + virtual ~DownloadHistory(); - // Returns a new unique database handle which will not collide with real ones. - int64 GetNextFakeDbHandle(); + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); private: - typedef std::map<HistoryService::Handle, VisitedBeforeDoneCallback> - VisitedBeforeRequestsMap; + typedef std::set<int64> HandleSet; + typedef std::set<content::DownloadItem*> ItemSet; + + // Callback from |history_| containing all entries in the downloads database + // table. + void QueryCallback( + std::vector<history::DownloadRow>* infos); + + // May add |item| to |history_|. + void MaybeAddToHistory(content::DownloadItem* item); + + // Callback from |history_| when an item was successfully inserted into the + // database. + void ItemAdded(int32 id, int64 db_handle); + + // AllDownloadItemNotifier::Observer + virtual void OnDownloadCreated( + content::DownloadManager* manager, content::DownloadItem* item) OVERRIDE; + virtual void OnDownloadUpdated( + content::DownloadManager* manager, content::DownloadItem* item) OVERRIDE; + virtual void OnDownloadOpened( + content::DownloadManager* manager, content::DownloadItem* item) OVERRIDE; + virtual void OnDownloadRemoved( + content::DownloadManager* manager, content::DownloadItem* item) OVERRIDE; + + // Schedule a record to be removed from |history_| the next time + // RemoveDownloadsBatch() runs. Schedule RemoveDownloadsBatch() to be run soon + // if it isn't already scheduled. + void ScheduleRemoveDownload(int32 download_id, int64 db_handle); + + // Removes all |removing_handles_| from |history_|. + void RemoveDownloadsBatch(); + + + AllDownloadItemNotifier notifier_; + + scoped_ptr<HistoryAdapter> history_; + + // |db_handle| of the item being created in response to QueryCallback(), + // matched up with created items in OnDownloadCreated() so that the item is + // not re-added to the database. For items not created by QueryCallback(), + // this is DownloadDatabase::kUninitializedHandle. + int64 loading_db_handle_; - void OnGotVisitCountToHost(HistoryService::Handle handle, - bool found_visits, - int count, - base::Time first_visit); + // |db_handles| and |ids| of items that are scheduled for removal from + // history, to facilitate batching removals together for database efficiency. + HandleSet removing_handles_; + IdSet removing_ids_; - Profile* profile_; + // |GetId()|s of items that were removed while they were being added, so that + // they can be removed when their db_handles are received from the database. + IdSet removed_while_adding_; - // In case we don't have a valid db_handle, we use |fake_db_handle_| instead. - // This is useful for incognito mode or when the history database is offline. - // Downloads are expected to have unique handles, so we decrement the next - // fake handle value on every use. - int64 next_fake_db_handle_; + // Count the number of items in the history for UMA. + int64 history_size_; - CancelableRequestConsumer history_consumer_; + ObserverList<Observer> observers_; - // The outstanding requests made by CheckVisitedReferrerBefore(). - VisitedBeforeRequestsMap visited_before_requests_; + base::WeakPtrFactory<DownloadHistory> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(DownloadHistory); }; diff --git a/chrome/browser/download/download_history_unittest.cc b/chrome/browser/download/download_history_unittest.cc new file mode 100644 index 0000000..319c0de --- /dev/null +++ b/chrome/browser/download/download_history_unittest.cc @@ -0,0 +1,741 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <set> +#include <vector> + +#include "base/rand_util.h" +#include "base/stl_util.h" +#include "chrome/browser/download/download_history.h" +#include "chrome/browser/history/download_database.h" +#include "chrome/browser/history/download_row.h" +#include "chrome/browser/history/history.h" +#include "content/public/test/mock_download_item.h" +#include "content/public/test/mock_download_manager.h" +#include "content/public/test/test_browser_thread.h" +#include "content/public/test/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::DoAll; +using testing::Invoke; +using testing::Return; +using testing::ReturnRef; +using testing::SetArgPointee; +using testing::WithArg; +using testing::_; + +namespace { + +void CheckInfoEqual(const history::DownloadRow& left, + const history::DownloadRow& right) { + EXPECT_EQ(left.path.value(), right.path.value()); + EXPECT_EQ(left.url.spec(), right.url.spec()); + EXPECT_EQ(left.referrer_url.spec(), right.referrer_url.spec()); + EXPECT_EQ(left.start_time.ToTimeT(), right.start_time.ToTimeT()); + EXPECT_EQ(left.end_time.ToTimeT(), right.end_time.ToTimeT()); + EXPECT_EQ(left.received_bytes, right.received_bytes); + EXPECT_EQ(left.total_bytes, right.total_bytes); + EXPECT_EQ(left.state, right.state); + EXPECT_EQ(left.db_handle, right.db_handle); + EXPECT_EQ(left.opened, right.opened); +} + +typedef std::set<int64> HandleSet; +typedef std::vector<history::DownloadRow> InfoVector; +typedef testing::NiceMock<content::MockDownloadItem> NiceMockDownloadItem; + +class FakeHistoryAdapter : public DownloadHistory::HistoryAdapter { + public: + FakeHistoryAdapter() + : DownloadHistory::HistoryAdapter(NULL), + slow_create_download_(false), + fail_create_download_(false), + handle_counter_(0) { + } + + virtual ~FakeHistoryAdapter() {} + + virtual void QueryDownloads( + const HistoryService::DownloadQueryCallback& callback) OVERRIDE { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(&FakeHistoryAdapter::QueryDownloadsDone, + base::Unretained(this), callback)); + } + + void QueryDownloadsDone( + const HistoryService::DownloadQueryCallback& callback) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + CHECK(expect_query_downloads_.get()); + callback.Run(expect_query_downloads_.get()); + expect_query_downloads_.reset(); + } + + void set_slow_create_download(bool slow) { slow_create_download_ = slow; } + + virtual void CreateDownload( + const history::DownloadRow& info, + const HistoryService::DownloadCreateCallback& callback) OVERRIDE { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + create_download_info_ = info; + // Must not call CreateDownload() again before FinishCreateDownload()! + DCHECK(create_download_callback_.is_null()); + create_download_callback_ = base::Bind( + callback, + (fail_create_download_ ? + history::DownloadDatabase::kUninitializedHandle : + handle_counter_++)); + fail_create_download_ = false; + if (!slow_create_download_) + FinishCreateDownload(); + } + + void FinishCreateDownload() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + create_download_callback_.Run(); + create_download_callback_.Reset(); + } + + virtual void UpdateDownload( + const history::DownloadRow& info) OVERRIDE { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + update_download_ = info; + } + + virtual void RemoveDownloads(const HandleSet& handles) OVERRIDE { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + for (HandleSet::const_iterator it = handles.begin(); + it != handles.end(); ++it) { + remove_downloads_.insert(*it); + } + } + + void ExpectWillQueryDownloads(scoped_ptr<InfoVector> infos) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + expect_query_downloads_ = infos.Pass(); + } + + void ExpectQueryDownloadsDone() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + EXPECT_TRUE(NULL == expect_query_downloads_.get()); + } + + void FailCreateDownload() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + fail_create_download_ = true; + } + + void ExpectDownloadCreated( + const history::DownloadRow& info) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + content::RunAllPendingInMessageLoop(content::BrowserThread::UI); + CheckInfoEqual(info, create_download_info_); + create_download_info_ = history::DownloadRow(); + } + + void ExpectNoDownloadCreated() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + content::RunAllPendingInMessageLoop(content::BrowserThread::UI); + CheckInfoEqual(history::DownloadRow(), create_download_info_); + } + + void ExpectDownloadUpdated(const history::DownloadRow& info) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + content::RunAllPendingInMessageLoop(content::BrowserThread::UI); + CheckInfoEqual(update_download_, info); + update_download_ = history::DownloadRow(); + } + + void ExpectNoDownloadUpdated() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + content::RunAllPendingInMessageLoop(content::BrowserThread::UI); + CheckInfoEqual(history::DownloadRow(), update_download_); + } + + void ExpectNoDownloadsRemoved() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + content::RunAllPendingInMessageLoop(content::BrowserThread::UI); + EXPECT_EQ(0, static_cast<int>(remove_downloads_.size())); + } + + void ExpectDownloadsRemoved(const HandleSet& handles) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + content::RunAllPendingInMessageLoop(content::BrowserThread::UI); + HandleSet differences; + std::insert_iterator<HandleSet> differences_iter( + differences, differences.begin()); + std::set_difference(handles.begin(), handles.end(), + remove_downloads_.begin(), + remove_downloads_.end(), + differences_iter); + for (HandleSet::const_iterator different = differences.begin(); + different != differences.end(); ++different) { + EXPECT_TRUE(false) << *different; + } + remove_downloads_.clear(); + } + + private: + bool slow_create_download_; + bool fail_create_download_; + base::Closure create_download_callback_; + int handle_counter_; + history::DownloadRow update_download_; + scoped_ptr<InfoVector> expect_query_downloads_; + HandleSet remove_downloads_; + history::DownloadRow create_download_info_; + + DISALLOW_COPY_AND_ASSIGN(FakeHistoryAdapter); +}; + +class DownloadHistoryTest : public testing::Test { + public: + DownloadHistoryTest() + : ui_thread_(content::BrowserThread::UI, &loop_), + manager_(new content::MockDownloadManager()), + history_(NULL), + download_history_(NULL), + manager_observer_(NULL), + item_observer_(NULL), + download_created_index_(0) { + } + virtual ~DownloadHistoryTest() { + STLDeleteElements(&items_); + } + + protected: + virtual void TearDown() OVERRIDE { + download_history_.reset(); + } + + content::MockDownloadManager& manager() { return *manager_.get(); } + content::MockDownloadItem& item(size_t index) { return *items_[index]; } + + void SetManagerObserver( + content::DownloadManager::Observer* manager_observer) { + manager_observer_ = manager_observer; + } + content::DownloadManager::Observer* manager_observer() { + return manager_observer_; + } + void SetItemObserver( + content::DownloadItem::Observer* item_observer) { + item_observer_ = item_observer; + } + content::DownloadItem::Observer* item_observer() { + return item_observer_; + } + + void ExpectWillQueryDownloads(scoped_ptr<InfoVector> infos) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + CHECK(infos.get()); + EXPECT_CALL(manager(), AddObserver(_)).WillOnce(WithArg<0>(Invoke( + this, &DownloadHistoryTest::SetManagerObserver))); + EXPECT_CALL(manager(), RemoveObserver(_)); + download_created_index_ = 0; + for (size_t index = 0; index < infos->size(); ++index) { + EXPECT_CALL(manager(), CreateDownloadItem( + infos->at(index).path, + infos->at(index).url, + infos->at(index).referrer_url, + infos->at(index).start_time, + infos->at(index).end_time, + infos->at(index).received_bytes, + infos->at(index).total_bytes, + infos->at(index).state, + infos->at(index).opened)) + .WillOnce(DoAll( + InvokeWithoutArgs( + this, &DownloadHistoryTest::CallOnDownloadCreatedInOrder), + Return(&item(index)))); + } + EXPECT_CALL(manager(), CheckForHistoryFilesRemoval()); + history_ = new FakeHistoryAdapter(); + history_->ExpectWillQueryDownloads(infos.Pass()); + EXPECT_CALL(*manager_.get(), GetAllDownloads(_)).WillRepeatedly(Return()); + download_history_.reset(new DownloadHistory( + &manager(), scoped_ptr<DownloadHistory::HistoryAdapter>(history_))); + content::RunAllPendingInMessageLoop(content::BrowserThread::UI); + history_->ExpectQueryDownloadsDone(); + } + + void CallOnDownloadCreated(size_t index) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + manager_observer()->OnDownloadCreated(&manager(), &item(index)); + } + + void CallOnDownloadCreatedInOrder() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + // Gmock doesn't appear to support something like InvokeWithTheseArgs. Maybe + // gmock needs to learn about base::Callback. + CallOnDownloadCreated(download_created_index_++); + } + + void set_slow_create_download(bool slow) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + history_->set_slow_create_download(slow); + } + + void FinishCreateDownload() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + history_->FinishCreateDownload(); + } + + void FailCreateDownload() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + history_->FailCreateDownload(); + } + + void ExpectDownloadCreated( + const history::DownloadRow& info) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + history_->ExpectDownloadCreated(info); + } + + void ExpectNoDownloadCreated() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + history_->ExpectNoDownloadCreated(); + } + + void ExpectDownloadUpdated(const history::DownloadRow& info) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + history_->ExpectDownloadUpdated(info); + } + + void ExpectNoDownloadUpdated() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + history_->ExpectNoDownloadUpdated(); + } + + void ExpectNoDownloadsRemoved() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + history_->ExpectNoDownloadsRemoved(); + } + + void ExpectDownloadsRemoved(const HandleSet& handles) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + history_->ExpectDownloadsRemoved(handles); + } + + void InitItem( + int32 id, + const FilePath& path, + const GURL& url, + const GURL& referrer, + const base::Time& start_time, + const base::Time& end_time, + int64 received_bytes, + int64 total_bytes, + content::DownloadItem::DownloadState state, + int64 db_handle, + bool opened, + history::DownloadRow* info) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + int32 index = items_.size(); + NiceMockDownloadItem* mock_item = new NiceMockDownloadItem(); + items_.push_back(mock_item); + + info->path = path; + info->url = url; + info->referrer_url = referrer; + info->start_time = start_time; + info->end_time = end_time; + info->received_bytes = received_bytes; + info->total_bytes = total_bytes; + info->state = state; + info->db_handle = db_handle; + info->opened = opened; + + EXPECT_CALL(item(index), GetId()).WillRepeatedly(Return(id)); + EXPECT_CALL(item(index), GetFullPath()).WillRepeatedly(ReturnRef(path)); + EXPECT_CALL(item(index), GetURL()).WillRepeatedly(ReturnRef(url)); + EXPECT_CALL(item(index), GetMimeType()).WillRepeatedly(Return( + "application/octet-stream")); + EXPECT_CALL(item(index), GetReferrerUrl()).WillRepeatedly(ReturnRef( + referrer)); + EXPECT_CALL(item(index), GetStartTime()).WillRepeatedly(Return(start_time)); + EXPECT_CALL(item(index), GetEndTime()).WillRepeatedly(Return(end_time)); + EXPECT_CALL(item(index), GetReceivedBytes()) + .WillRepeatedly(Return(received_bytes)); + EXPECT_CALL(item(index), GetTotalBytes()).WillRepeatedly(Return( + total_bytes)); + EXPECT_CALL(item(index), GetState()).WillRepeatedly(Return(state)); + EXPECT_CALL(item(index), GetOpened()).WillRepeatedly(Return(opened)); + EXPECT_CALL(item(index), GetTargetDisposition()).WillRepeatedly(Return( + content::DownloadItem::TARGET_DISPOSITION_OVERWRITE)); + EXPECT_CALL(manager(), GetDownload(id)) + .WillRepeatedly(Return(&item(index))); + EXPECT_CALL(item(index), AddObserver(_)).WillOnce(WithArg<0>(Invoke( + this, &DownloadHistoryTest::SetItemObserver))); + EXPECT_CALL(item(index), RemoveObserver(_)); + + std::vector<content::DownloadItem*> items; + for (size_t i = 0; i < items_.size(); ++i) { + items.push_back(&item(i)); + } + EXPECT_CALL(*manager_.get(), GetAllDownloads(_)) + .WillRepeatedly(SetArgPointee<0>(items)); + } + + private: + MessageLoopForUI loop_; + content::TestBrowserThread ui_thread_; + std::vector<NiceMockDownloadItem*> items_; + scoped_refptr<content::MockDownloadManager> manager_; + FakeHistoryAdapter* history_; + scoped_ptr<DownloadHistory> download_history_; + content::DownloadManager::Observer* manager_observer_; + content::DownloadItem::Observer* item_observer_; + size_t download_created_index_; + + DISALLOW_COPY_AND_ASSIGN(DownloadHistoryTest); +}; + +} // anonymous namespace + +// Test loading an item from the database, changing it, saving it back, removing +// it. +TEST_F(DownloadHistoryTest, DownloadHistoryTest_Load) { + // Load a download from history, create the item, OnDownloadCreated, + // OnDownloadUpdated, OnDownloadRemoved. + history::DownloadRow info; + FilePath path(FILE_PATH_LITERAL("/foo/bar.pdf")); + GURL url("http://example.com/bar.pdf"); + GURL referrer("http://example.com/referrer.html"); + InitItem(base::RandInt(0, 1 << 20), + path, + url, + referrer, + (base::Time::Now() - base::TimeDelta::FromMinutes(10)), + (base::Time::Now() - base::TimeDelta::FromMinutes(1)), + 100, + 100, + content::DownloadItem::COMPLETE, + base::RandInt(0, 1 << 20), + false, + &info); + { + scoped_ptr<InfoVector> infos(new InfoVector()); + infos->push_back(info); + ExpectWillQueryDownloads(infos.Pass()); + ExpectNoDownloadCreated(); + } + EXPECT_TRUE(DownloadHistory::IsPersisted(&item(0))); + + // Pretend that something changed on the item. + EXPECT_CALL(item(0), GetOpened()).WillRepeatedly(Return(true)); + item_observer()->OnDownloadUpdated(&item(0)); + info.opened = true; + ExpectDownloadUpdated(info); + + // Pretend that the user removed the item. + HandleSet handles; + handles.insert(info.db_handle); + item_observer()->OnDownloadRemoved(&item(0)); + ExpectDownloadsRemoved(handles); +} + +// Test creating an item, saving it to the database, changing it, saving it +// back, removing it. +TEST_F(DownloadHistoryTest, DownloadHistoryTest_Create) { + // Create a fresh item not from history, OnDownloadCreated, OnDownloadUpdated, + // OnDownloadRemoved. + ExpectWillQueryDownloads(scoped_ptr<InfoVector>(new InfoVector())); + + // Note that db_handle must be -1 at first because it isn't in the db yet. + history::DownloadRow info; + FilePath path(FILE_PATH_LITERAL("/foo/bar.pdf")); + GURL url("http://example.com/bar.pdf"); + GURL referrer("http://example.com/referrer.html"); + InitItem(base::RandInt(0, 1 << 20), + path, + url, + referrer, + (base::Time::Now() - base::TimeDelta::FromMinutes(10)), + (base::Time::Now() - base::TimeDelta::FromMinutes(1)), + 100, + 100, + content::DownloadItem::COMPLETE, + -1, + false, + &info); + + // Pretend the manager just created |item|. + CallOnDownloadCreated(0); + // CreateDownload() always gets db_handle=-1. + ExpectDownloadCreated(info); + info.db_handle = 0; + EXPECT_TRUE(DownloadHistory::IsPersisted(&item(0))); + + // Pretend that something changed on the item. + EXPECT_CALL(item(0), GetOpened()).WillRepeatedly(Return(true)); + item_observer()->OnDownloadUpdated(&item(0)); + info.opened = true; + ExpectDownloadUpdated(info); + + // Pretend that the user removed the item. + HandleSet handles; + handles.insert(info.db_handle); + item_observer()->OnDownloadRemoved(&item(0)); + ExpectDownloadsRemoved(handles); +} + +// Test creating a new item, saving it, removing it by setting it Temporary, +// changing it without saving it back because it's Temporary, clearing +// IsTemporary, saving it back, changing it, saving it back because it isn't +// Temporary anymore. +TEST_F(DownloadHistoryTest, DownloadHistoryTest_Temporary) { + // Create a fresh item not from history, OnDownloadCreated, OnDownloadUpdated, + // OnDownloadRemoved. + ExpectWillQueryDownloads(scoped_ptr<InfoVector>(new InfoVector())); + + // Note that db_handle must be -1 at first because it isn't in the db yet. + history::DownloadRow info; + FilePath path(FILE_PATH_LITERAL("/foo/bar.pdf")); + GURL url("http://example.com/bar.pdf"); + GURL referrer("http://example.com/referrer.html"); + InitItem(base::RandInt(0, 1 << 20), + path, + url, + referrer, + (base::Time::Now() - base::TimeDelta::FromMinutes(10)), + (base::Time::Now() - base::TimeDelta::FromMinutes(1)), + 100, + 100, + content::DownloadItem::COMPLETE, + -1, + false, + &info); + + // Pretend the manager just created |item|. + CallOnDownloadCreated(0); + // CreateDownload() always gets db_handle=-1. + ExpectDownloadCreated(info); + info.db_handle = 0; + EXPECT_TRUE(DownloadHistory::IsPersisted(&item(0))); + + // Pretend the item was marked temporary. DownloadHistory should remove it + // from history and start ignoring it. + EXPECT_CALL(item(0), IsTemporary()).WillRepeatedly(Return(true)); + item_observer()->OnDownloadUpdated(&item(0)); + HandleSet handles; + handles.insert(info.db_handle); + ExpectDownloadsRemoved(handles); + + // Change something that would make DownloadHistory call UpdateDownload if the + // item weren't temporary. + EXPECT_CALL(item(0), GetReceivedBytes()).WillRepeatedly(Return(4200)); + item_observer()->OnDownloadUpdated(&item(0)); + ExpectNoDownloadUpdated(); + + // Changing a temporary item back to a non-temporary item should make + // DownloadHistory call CreateDownload. + EXPECT_CALL(item(0), IsTemporary()).WillRepeatedly(Return(false)); + item_observer()->OnDownloadUpdated(&item(0)); + info.received_bytes = 4200; + info.db_handle = -1; + // CreateDownload() always gets db_handle=-1. + ExpectDownloadCreated(info); + info.db_handle = 1; + EXPECT_TRUE(DownloadHistory::IsPersisted(&item(0))); + + EXPECT_CALL(item(0), GetReceivedBytes()).WillRepeatedly(Return(100)); + item_observer()->OnDownloadUpdated(&item(0)); + info.received_bytes = 100; + ExpectDownloadUpdated(info); +} + +// Test removing downloads while they're still being added. +TEST_F(DownloadHistoryTest, + DownloadHistoryTest_RemoveWhileAdding) { + ExpectWillQueryDownloads(scoped_ptr<InfoVector>(new InfoVector())); + + // Note that db_handle must be -1 at first because it isn't in the db yet. + history::DownloadRow info; + FilePath path(FILE_PATH_LITERAL("/foo/bar.pdf")); + GURL url("http://example.com/bar.pdf"); + GURL referrer("http://example.com/referrer.html"); + InitItem(base::RandInt(0, 1 << 20), + path, + url, + referrer, + (base::Time::Now() - base::TimeDelta::FromMinutes(10)), + (base::Time::Now() - base::TimeDelta::FromMinutes(1)), + 100, + 100, + content::DownloadItem::COMPLETE, + -1, + false, + &info); + + // Instruct CreateDownload() to not callback to DownloadHistory immediately, + // but to wait for FinishCreateDownload(). + set_slow_create_download(true); + + // Pretend the manager just created |item|. + CallOnDownloadCreated(0); + // CreateDownload() always gets db_handle=-1. + ExpectDownloadCreated(info); + info.db_handle = 0; + EXPECT_FALSE(DownloadHistory::IsPersisted(&item(0))); + + // Call OnDownloadRemoved before calling back to DownloadHistory::ItemAdded(). + // Instead of calling RemoveDownloads() immediately, DownloadHistory should + // add the item's id to removed_while_adding_. Then, ItemAdded should + // immediately remove the item's record from history. + item_observer()->OnDownloadRemoved(&item(0)); + EXPECT_CALL(manager(), GetDownload(item(0).GetId())) + .WillRepeatedly(Return(static_cast<content::DownloadItem*>(NULL))); + ExpectNoDownloadsRemoved(); + EXPECT_FALSE(DownloadHistory::IsPersisted(&item(0))); + + // Now callback to DownloadHistory::ItemAdded(), and expect a call to + // RemoveDownloads() for the item that was removed while it was being added. + FinishCreateDownload(); + HandleSet handles; + handles.insert(info.db_handle); + ExpectDownloadsRemoved(handles); + EXPECT_FALSE(DownloadHistory::IsPersisted(&item(0))); +} + +// Test loading multiple items from the database and removing them all. +TEST_F(DownloadHistoryTest, DownloadHistoryTest_Multiple) { + // Load a download from history, create the item, OnDownloadCreated, + // OnDownloadUpdated, OnDownloadRemoved. + history::DownloadRow info0, info1; + FilePath path0(FILE_PATH_LITERAL("/foo/bar.pdf")); + GURL url0("http://example.com/bar.pdf"); + GURL referrer0("http://example.com/referrer.html"); + InitItem(base::RandInt(0, 1 << 10), + path0, + url0, + referrer0, + (base::Time::Now() - base::TimeDelta::FromMinutes(11)), + (base::Time::Now() - base::TimeDelta::FromMinutes(2)), + 100, + 100, + content::DownloadItem::COMPLETE, + base::RandInt(0, 1 << 10), + false, + &info0); + FilePath path1(FILE_PATH_LITERAL("/foo/qux.pdf")); + GURL url1("http://example.com/qux.pdf"); + GURL referrer1("http://example.com/referrer.html"); + InitItem(item(0).GetId() + base::RandInt(1, 1 << 10), + path1, + url1, + referrer1, + (base::Time::Now() - base::TimeDelta::FromMinutes(10)), + (base::Time::Now() - base::TimeDelta::FromMinutes(1)), + 200, + 200, + content::DownloadItem::COMPLETE, + info0.db_handle + base::RandInt(1, 1 << 10), + false, + &info1); + { + scoped_ptr<InfoVector> infos(new InfoVector()); + infos->push_back(info0); + infos->push_back(info1); + ExpectWillQueryDownloads(infos.Pass()); + ExpectNoDownloadCreated(); + } + + EXPECT_TRUE(DownloadHistory::IsPersisted(&item(0))); + EXPECT_TRUE(DownloadHistory::IsPersisted(&item(1))); + + // Pretend that the user removed both items. + HandleSet handles; + handles.insert(info0.db_handle); + handles.insert(info1.db_handle); + item_observer()->OnDownloadRemoved(&item(0)); + item_observer()->OnDownloadRemoved(&item(1)); + ExpectDownloadsRemoved(handles); +} + +// Test what happens when HistoryService/CreateDownload::CreateDownload() fails. +TEST_F(DownloadHistoryTest, DownloadHistoryTest_CreateFailed) { + // Create a fresh item not from history, OnDownloadCreated, OnDownloadUpdated, + // OnDownloadRemoved. + ExpectWillQueryDownloads(scoped_ptr<InfoVector>(new InfoVector())); + + // Note that db_handle must be -1 at first because it isn't in the db yet. + history::DownloadRow info; + FilePath path(FILE_PATH_LITERAL("/foo/bar.pdf")); + GURL url("http://example.com/bar.pdf"); + GURL referrer("http://example.com/referrer.html"); + InitItem(base::RandInt(0, 1 << 20), + path, + url, + referrer, + (base::Time::Now() - base::TimeDelta::FromMinutes(10)), + (base::Time::Now() - base::TimeDelta::FromMinutes(1)), + 100, + 100, + content::DownloadItem::COMPLETE, + -1, + false, + &info); + + FailCreateDownload(); + // Pretend the manager just created |item|. + CallOnDownloadCreated(0); + // CreateDownload() always gets db_handle=-1. + ExpectDownloadCreated(info); + EXPECT_FALSE(DownloadHistory::IsPersisted(&item(0))); + + EXPECT_CALL(item(0), GetReceivedBytes()).WillRepeatedly(Return(100)); + item_observer()->OnDownloadUpdated(&item(0)); + info.received_bytes = 100; + ExpectDownloadCreated(info); + EXPECT_TRUE(DownloadHistory::IsPersisted(&item(0))); +} + +TEST_F(DownloadHistoryTest, + DownloadHistoryTest_UpdateWhileAdding) { + // Create a fresh item not from history, OnDownloadCreated, OnDownloadUpdated, + // OnDownloadRemoved. + ExpectWillQueryDownloads(scoped_ptr<InfoVector>(new InfoVector())); + + // Note that db_handle must be -1 at first because it isn't in the db yet. + history::DownloadRow info; + FilePath path(FILE_PATH_LITERAL("/foo/bar.pdf")); + GURL url("http://example.com/bar.pdf"); + GURL referrer("http://example.com/referrer.html"); + InitItem(base::RandInt(0, 1 << 20), + path, + url, + referrer, + (base::Time::Now() - base::TimeDelta::FromMinutes(10)), + (base::Time::Now() - base::TimeDelta::FromMinutes(1)), + 100, + 100, + content::DownloadItem::COMPLETE, + -1, + false, + &info); + + // Instruct CreateDownload() to not callback to DownloadHistory immediately, + // but to wait for FinishCreateDownload(). + set_slow_create_download(true); + + // Pretend the manager just created |item|. + CallOnDownloadCreated(0); + // CreateDownload() always gets db_handle=-1. + ExpectDownloadCreated(info); + info.db_handle = 0; + EXPECT_FALSE(DownloadHistory::IsPersisted(&item(0))); + + // Pretend that something changed on the item. + EXPECT_CALL(item(0), GetOpened()).WillRepeatedly(Return(true)); + item_observer()->OnDownloadUpdated(&item(0)); + + FinishCreateDownload(); + EXPECT_TRUE(DownloadHistory::IsPersisted(&item(0))); + + // ItemAdded should call OnDownloadUpdated, which should detect that the item + // changed while it was being added and call UpdateDownload immediately. + info.opened = true; + ExpectDownloadUpdated(info); +} diff --git a/chrome/browser/download/download_service.cc b/chrome/browser/download/download_service.cc index 75d2877..dae3bc4 100644 --- a/chrome/browser/download/download_service.cc +++ b/chrome/browser/download/download_service.cc @@ -7,8 +7,11 @@ #include "base/callback.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/download/chrome_download_manager_delegate.h" +#include "chrome/browser/download/download_history.h" #include "chrome/browser/download/download_service_factory.h" #include "chrome/browser/download/download_status_updater.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/net/chrome_net_log.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" @@ -42,6 +45,16 @@ ChromeDownloadManagerDelegate* DownloadService::GetDownloadManagerDelegate() { manager_delegate_->SetDownloadManager(manager); + if (!profile_->IsOffTheRecord()) { + HistoryService* hs = HistoryServiceFactory::GetForProfile( + profile_, Profile::EXPLICIT_ACCESS); + if (hs) + download_history_.reset(new DownloadHistory( + manager, + scoped_ptr<DownloadHistory::HistoryAdapter>( + new DownloadHistory::HistoryAdapter(hs)))); + } + // Include this download manager in the set monitored by the // global status updater. g_browser_process->download_status_updater()->AddManager(manager); @@ -49,6 +62,14 @@ ChromeDownloadManagerDelegate* DownloadService::GetDownloadManagerDelegate() { return manager_delegate_.get(); } +DownloadHistory* DownloadService::GetDownloadHistory() { + if (!download_manager_created_) { + GetDownloadManagerDelegate(); + } + DCHECK(download_manager_created_); + return download_history_.get(); +} + bool DownloadService::HasCreatedDownloadManager() { return download_manager_created_; } @@ -100,4 +121,5 @@ void DownloadService::Shutdown() { BrowserContext::GetDownloadManager(profile_)->Shutdown(); } manager_delegate_ = NULL; + download_history_.reset(); } diff --git a/chrome/browser/download/download_service.h b/chrome/browser/download/download_service.h index 24535f9..1c72b29 100644 --- a/chrome/browser/download/download_service.h +++ b/chrome/browser/download/download_service.h @@ -10,9 +10,11 @@ #include "base/basictypes.h" #include "base/callback_forward.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "chrome/browser/profiles/profile_keyed_service.h" class ChromeDownloadManagerDelegate; +class DownloadHistory; class Profile; namespace content { @@ -28,6 +30,11 @@ class DownloadService : public ProfileKeyedService { // Get the download manager delegate, creating it if it doesn't already exist. ChromeDownloadManagerDelegate* GetDownloadManagerDelegate(); + // Get the interface to the history system. Returns NULL if profile is + // incognito or if the DownloadManager hasn't been created yet or if there is + // no HistoryService for profile. + DownloadHistory* GetDownloadHistory(); + // Has a download manager been created? bool HasCreatedDownloadManager(); @@ -56,6 +63,8 @@ class DownloadService : public ProfileKeyedService { // callbacks. scoped_refptr<ChromeDownloadManagerDelegate> manager_delegate_; + scoped_ptr<DownloadHistory> download_history_; + DISALLOW_COPY_AND_ASSIGN(DownloadService); }; diff --git a/chrome/browser/download/download_status_updater_unittest.cc b/chrome/browser/download/download_status_updater_unittest.cc index 171ec41..06a137d 100644 --- a/chrome/browser/download/download_status_updater_unittest.cc +++ b/chrome/browser/download/download_status_updater_unittest.cc @@ -13,14 +13,14 @@ #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -using ::testing::AtLeast; -using ::testing::Invoke; -using ::testing::Mock; -using ::testing::Return; -using ::testing::SetArgPointee; -using ::testing::StrictMock; -using ::testing::WithArg; -using ::testing::_; +using testing::AtLeast; +using testing::Invoke; +using testing::Mock; +using testing::Return; +using testing::SetArgPointee; +using testing::StrictMock; +using testing::WithArg; +using testing::_; class TestDownloadStatusUpdater : public DownloadStatusUpdater { public: diff --git a/chrome/browser/download/save_page_browsertest.cc b/chrome/browser/download/save_page_browsertest.cc index edc733d..9f2fca4e 100644 --- a/chrome/browser/download/save_page_browsertest.cc +++ b/chrome/browser/download/save_page_browsertest.cc @@ -17,6 +17,7 @@ #include "chrome/browser/download/download_prefs.h" #include "chrome/browser/download/download_service.h" #include "chrome/browser/download/download_service_factory.h" +#include "chrome/browser/history/download_row.h" #include "chrome/browser/net/url_request_mock_util.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" @@ -32,7 +33,6 @@ #include "chrome/test/base/ui_test_utils.h" #include "content/public/browser/download_item.h" #include "content/public/browser/download_manager.h" -#include "content/public/browser/download_persistent_store_info.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/web_contents.h" @@ -50,13 +50,130 @@ using content::BrowserContext; using content::BrowserThread; using content::DownloadItem; using content::DownloadManager; -using content::DownloadPersistentStoreInfo; using content::URLRequestMockHTTPJob; using content::WebContents; +// Waits for an item record in the downloads database to match |filter|. See +// DownloadStoredProperly() below for an example filter. +class DownloadPersistedObserver : public DownloadHistory::Observer { + public: + typedef base::Callback<bool( + DownloadItem* item, + const history::DownloadRow&)> PersistedFilter; + + DownloadPersistedObserver(Profile* profile, const PersistedFilter& filter) + : profile_(profile), + filter_(filter) { + DownloadServiceFactory::GetForProfile(profile_)-> + GetDownloadHistory()->AddObserver(this); + } + + virtual ~DownloadPersistedObserver() { + DownloadService* service = DownloadServiceFactory::GetForProfile(profile_); + if (service && service->GetDownloadHistory()) + service->GetDownloadHistory()->RemoveObserver(this); + } + + bool WaitForPersisted() { + if (persisted_) + return true; + waiting_ = true; + content::RunMessageLoop(); + waiting_ = false; + return persisted_; + } + + virtual void OnDownloadStored(DownloadItem* item, + const history::DownloadRow& info) { + persisted_ = filter_.Run(item, info); + if (persisted_ && waiting_) + MessageLoopForUI::current()->Quit(); + } + + private: + Profile* profile_; + DownloadItem* item_; + PersistedFilter filter_; + bool waiting_; + bool persisted_; + + DISALLOW_COPY_AND_ASSIGN(DownloadPersistedObserver); +}; + +// Waits for an item record to be removed from the downloads database. +class DownloadRemovedObserver : public DownloadPersistedObserver { + public: + DownloadRemovedObserver(Profile* profile, int32 download_id) + : DownloadPersistedObserver(profile, PersistedFilter()), + removed_(false), + waiting_(false), + download_id_(download_id) { + } + virtual ~DownloadRemovedObserver() {} + + bool WaitForRemoved() { + if (removed_) + return true; + waiting_ = true; + content::RunMessageLoop(); + waiting_ = false; + return removed_; + } + + virtual void OnDownloadStored(DownloadItem* item, + const history::DownloadRow& info) { + } + + virtual void OnDownloadsRemoved(const DownloadHistory::IdSet& ids) { + removed_ = ids.find(download_id_) != ids.end(); + if (removed_ && waiting_) + MessageLoopForUI::current()->Quit(); + } + + private: + bool removed_; + bool waiting_; + int32 download_id_; + + DISALLOW_COPY_AND_ASSIGN(DownloadRemovedObserver); +}; + +bool DownloadStoredProperly( + const GURL& expected_url, + const FilePath& expected_path, + int64 num_files, + DownloadItem::DownloadState expected_state, + DownloadItem* item, + const history::DownloadRow& info) { + // This function may be called multiple times for a given test. Returning + // false doesn't necessarily mean that the test has failed or will fail, it + // might just mean that the test hasn't passed yet. + if (info.path != expected_path) { + VLOG(20) << __FUNCTION__ << " " << info.path.value() + << " != " << expected_path.value(); + return false; + } + if (info.url != expected_url) { + VLOG(20) << __FUNCTION__ << " " << info.url.spec() + << " != " << expected_url.spec(); + return false; + } + if ((num_files >= 0) && (info.received_bytes != num_files)) { + VLOG(20) << __FUNCTION__ << " " << num_files + << " != " << info.received_bytes; + return false; + } + if (info.state != expected_state) { + VLOG(20) << __FUNCTION__ << " " << info.state + << " != " << expected_state; + return false; + } + return true; +} + const FilePath::CharType kTestDir[] = FILE_PATH_LITERAL("save_page"); -const char kAppendedExtension[] = +static const char kAppendedExtension[] = #if defined(OS_WIN) ".htm"; #else @@ -99,7 +216,6 @@ class DownloadItemCreatedObserver : public DownloadManager::Observer { } private: - // DownloadManager::Observer virtual void OnDownloadCreated( DownloadManager* manager, DownloadItem* item) OVERRIDE { @@ -124,60 +240,6 @@ class DownloadItemCreatedObserver : public DownloadManager::Observer { DISALLOW_COPY_AND_ASSIGN(DownloadItemCreatedObserver); }; -class DownloadPersistedObserver : public DownloadItem::Observer { - public: - explicit DownloadPersistedObserver(DownloadItem* item) - : waiting_(false), item_(item) { - item->AddObserver(this); - } - - ~DownloadPersistedObserver() { - if (item_) - item_->RemoveObserver(this); - } - - // Wait for download item to get the persisted bit set. - // Note that this class provides no protection against the download - // being destroyed between creation and return of WaitForPersisted(); - // the caller must guarantee that in some other fashion. - void WaitForPersisted() { - // In combination with OnDownloadDestroyed() below, verify the - // above interface contract. - DCHECK(item_); - - if (item_->IsPersisted()) - return; - - waiting_ = true; - content::RunMessageLoop(); - waiting_ = false; - - return; - } - - private: - // DownloadItem::Observer - virtual void OnDownloadUpdated(DownloadItem* item) OVERRIDE { - DCHECK_EQ(item, item_); - - if (waiting_ && item->IsPersisted()) - MessageLoopForUI::current()->Quit(); - } - - virtual void OnDownloadDestroyed(DownloadItem* item) OVERRIDE { - if (item != item_) - return; - - item_->RemoveObserver(this); - item_ = NULL; - } - - bool waiting_; - DownloadItem* item_; - - DISALLOW_COPY_AND_ASSIGN(DownloadPersistedObserver); -}; - class SavePageBrowserTest : public InProcessBrowserTest { public: SavePageBrowserTest() {} @@ -219,7 +281,11 @@ class SavePageBrowserTest : public InProcessBrowserTest { return current_tab; } - bool WaitForSavePackageToFinish(Browser* browser, GURL* url_at_finish) const { + // Returns true if and when there was a single download created, and its url + // is |expected_url|. + bool WaitForSavePackageToFinish( + Browser* browser, + const GURL& expected_url) const { content::WindowedNotificationObserver observer( content::NOTIFICATION_SAVE_PACKAGE_SUCCESSFULLY_FINISHED, content::NotificationService::AllSources()); @@ -241,24 +307,23 @@ class SavePageBrowserTest : public InProcessBrowserTest { return false; DownloadItem* download_item(items[0]); - // Note on synchronization: - // - // For each Save Page As operation, we create a corresponding shell - // DownloadItem to display progress to the user. That DownloadItem - // goes through its own state transitions, including being persisted - // out to the history database, and the download shelf is not shown - // until after the persistence occurs. Save Package completion (and - // marking the DownloadItem as completed) occurs asynchronously from - // persistence. Thus if we want to examine either UI state or DB - // state, we need to wait until both the save package operation is - // complete and the relevant download item has been persisted. - DownloadPersistedObserver(download_item).WaitForPersisted(); - - *url_at_finish = content::Details<DownloadItem>(observer.details()).ptr()-> - GetOriginalUrl(); - return true; + return ((expected_url == download_item->GetOriginalUrl()) && + (expected_url == content::Details<DownloadItem>( + observer.details()).ptr()->GetOriginalUrl())); } + // Note on synchronization: + // + // For each Save Page As operation, we create a corresponding shell + // DownloadItem to display progress to the user. That DownloadItem goes + // through its own state transitions, including being persisted out to the + // history database, and the download shelf is not shown until after the + // persistence occurs. Save Package completion (and marking the DownloadItem + // as completed) occurs asynchronously from persistence. Thus if we want to + // examine either UI state or DB state, we need to wait until both the save + // package operation is complete and the relevant download item has been + // persisted. + DownloadManager* GetDownloadManager() const { DownloadManager* download_manager = BrowserContext::GetDownloadManager(browser()->profile()); @@ -266,92 +331,6 @@ class SavePageBrowserTest : public InProcessBrowserTest { return download_manager; } - void QueryDownloadHistory() { - // Query the history system. - ChromeDownloadManagerDelegate* delegate = - static_cast<ChromeDownloadManagerDelegate*>( - GetDownloadManager()->GetDelegate()); - delegate->download_history()->Load( - base::Bind(&SavePageBrowserTest::OnQueryDownloadEntriesComplete, - base::Unretained(this))); - - // Run message loop until a quit message is sent from - // OnQueryDownloadEntriesComplete(). - content::RunMessageLoop(); - } - - void OnQueryDownloadEntriesComplete( - std::vector<DownloadPersistentStoreInfo>* entries) { - history_entries_ = *entries; - - // Indicate thet we have received the history and can continue. - MessageLoopForUI::current()->Quit(); - } - - struct DownloadPersistentStoreInfoMatch - : public std::unary_function<DownloadPersistentStoreInfo, bool> { - - DownloadPersistentStoreInfoMatch(const GURL& url, - const FilePath& path, - int64 num_files, - DownloadItem::DownloadState state) - : url_(url), - path_(path), - num_files_(num_files), - state_(state) {} - - bool operator() (const DownloadPersistentStoreInfo& info) const { - return info.url == url_ && - info.path == path_ && - // For non-MHTML save packages, received_bytes is actually the - // number of files. - ((num_files_ < 0) || - (info.received_bytes == num_files_)) && - info.total_bytes == 0 && - info.state == state_; - } - - GURL url_; - FilePath path_; - int64 num_files_; - DownloadItem::DownloadState state_; - }; - - void CheckDownloadHistory(const GURL& url, - const FilePath& path, - int64 num_files, - DownloadItem::DownloadState state) { - // Make sure the relevant download item made it into the history. - std::vector<DownloadItem*> downloads; - GetDownloadManager()->GetAllDownloads(&downloads); - ASSERT_EQ(1u, downloads.size()); - - QueryDownloadHistory(); - - std::vector<DownloadPersistentStoreInfo>::iterator found = - std::find_if(history_entries_.begin(), history_entries_.end(), - DownloadPersistentStoreInfoMatch(url, path, num_files, - state)); - - if (found == history_entries_.end()) { - LOG(ERROR) << "Missing url=" << url.spec() - << " path=" << path.value() - << " received=" << num_files - << " state=" << state; - for (size_t index = 0; index < history_entries_.size(); ++index) { - LOG(ERROR) << "History@" << index << ": url=" - << history_entries_[index].url.spec() - << " path=" << history_entries_[index].path.value() - << " received=" << history_entries_[index].received_bytes - << " total=" << history_entries_[index].total_bytes - << " state=" << history_entries_[index].state; - } - EXPECT_TRUE(false); - } - } - - std::vector<DownloadPersistentStoreInfo> history_entries_; - // Path to directory containing test data. FilePath test_dir_; @@ -370,17 +349,14 @@ IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveHTMLOnly) { FilePath full_file_name, dir; GetDestinationPaths("a", &full_file_name, &dir); + DownloadPersistedObserver persisted(browser()->profile(), base::Bind( + &DownloadStoredProperly, url, full_file_name, 1, + DownloadItem::COMPLETE)); ASSERT_TRUE(GetCurrentTab(browser())->SavePage(full_file_name, dir, content::SAVE_PAGE_TYPE_AS_ONLY_HTML)); - - GURL output_url; - ASSERT_TRUE(WaitForSavePackageToFinish(browser(), &output_url)); - EXPECT_EQ(url, output_url); - + ASSERT_TRUE(WaitForSavePackageToFinish(browser(), url)); + persisted.WaitForPersisted(); EXPECT_TRUE(browser()->window()->IsDownloadShelfVisible()); - // a.htm is 1 file. - CheckDownloadHistory(url, full_file_name, 1, DownloadItem::COMPLETE); - EXPECT_TRUE(file_util::PathExists(full_file_name)); EXPECT_FALSE(file_util::PathExists(dir)); EXPECT_TRUE(file_util::ContentsEqual( @@ -398,31 +374,29 @@ IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveHTMLOnlyCancel) { FilePath full_file_name, dir; GetDestinationPaths("a", &full_file_name, &dir); DownloadItemCreatedObserver creation_observer(manager); + DownloadPersistedObserver persisted(browser()->profile(), base::Bind( + &DownloadStoredProperly, url, full_file_name, -1, + DownloadItem::CANCELLED)); + // -1 to disable number of files check; we don't update after cancel, and + // we don't know when the single file completed in relationship to + // the cancel. + ASSERT_TRUE(GetCurrentTab(browser())->SavePage(full_file_name, dir, content::SAVE_PAGE_TYPE_AS_ONLY_HTML)); std::vector<DownloadItem*> items; creation_observer.WaitForDownloadItem(&items); - ASSERT_TRUE(items.size() == 1); + ASSERT_EQ(1UL, items.size()); + ASSERT_EQ(url.spec(), items[0]->GetOriginalUrl().spec()); items[0]->Cancel(true); - // TODO(rdsmith): Fix DII::Cancel() to actually cancel the save package. // Currently it's ignored. - GURL output_url; - ASSERT_TRUE(WaitForSavePackageToFinish(browser(), &output_url)); - EXPECT_EQ(url, output_url); - // -1 to disable number of files check; we don't update after cancel, and - // we don't know when the single file completed in relationship to - // the cancel. - CheckDownloadHistory(url, full_file_name, -1, DownloadItem::CANCELLED); + persisted.WaitForPersisted(); EXPECT_TRUE(browser()->window()->IsDownloadShelfVisible()); - EXPECT_TRUE(file_util::PathExists(full_file_name)); - EXPECT_FALSE(file_util::PathExists(dir)); - EXPECT_TRUE(file_util::ContentsEqual( - test_dir_.Append(FilePath(kTestDir)).Append(FILE_PATH_LITERAL("a.htm")), - full_file_name)); + // TODO(benjhayden): Figure out how to safely wait for SavePackage's finished + // notification, then expect the contents of the downloaded file. } IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveHTMLOnlyTabDestroy) { @@ -459,17 +433,15 @@ IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveViewSourceHTMLOnly) { FilePath full_file_name, dir; GetDestinationPaths("a", &full_file_name, &dir); + DownloadPersistedObserver persisted(browser()->profile(), base::Bind( + &DownloadStoredProperly, actual_page_url, full_file_name, 1, + DownloadItem::COMPLETE)); ASSERT_TRUE(GetCurrentTab(browser())->SavePage(full_file_name, dir, content::SAVE_PAGE_TYPE_AS_ONLY_HTML)); - - GURL output_url; - ASSERT_TRUE(WaitForSavePackageToFinish(browser(), &output_url)); - EXPECT_EQ(actual_page_url, output_url); + ASSERT_TRUE(WaitForSavePackageToFinish(browser(), actual_page_url)); + persisted.WaitForPersisted(); EXPECT_TRUE(browser()->window()->IsDownloadShelfVisible()); - // a.htm is 1 file. - CheckDownloadHistory(actual_page_url, full_file_name, 1, - DownloadItem::COMPLETE); EXPECT_TRUE(file_util::PathExists(full_file_name)); EXPECT_FALSE(file_util::PathExists(dir)); @@ -483,16 +455,15 @@ IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveCompleteHTML) { FilePath full_file_name, dir; GetDestinationPaths("b", &full_file_name, &dir); + DownloadPersistedObserver persisted(browser()->profile(), base::Bind( + &DownloadStoredProperly, url, full_file_name, 3, + DownloadItem::COMPLETE)); ASSERT_TRUE(GetCurrentTab(browser())->SavePage( full_file_name, dir, content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML)); - - GURL output_url; - ASSERT_TRUE(WaitForSavePackageToFinish(browser(), &output_url)); - EXPECT_EQ(url, output_url); + ASSERT_TRUE(WaitForSavePackageToFinish(browser(), url)); + persisted.WaitForPersisted(); EXPECT_TRUE(browser()->window()->IsDownloadShelfVisible()); - // b.htm is 3 files. - CheckDownloadHistory(url, full_file_name, 3, DownloadItem::COMPLETE); EXPECT_TRUE(file_util::PathExists(full_file_name)); EXPECT_TRUE(file_util::PathExists(dir)); @@ -531,9 +502,7 @@ IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, ASSERT_TRUE(GetCurrentTab(incognito)->SavePage( full_file_name, dir, content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML)); - GURL output_url; - WaitForSavePackageToFinish(incognito, &output_url); - EXPECT_EQ(url, output_url); + ASSERT_TRUE(WaitForSavePackageToFinish(incognito, url)); // Confirm download shelf is visible. EXPECT_TRUE(incognito->window()->IsDownloadShelfVisible()); @@ -557,16 +526,16 @@ IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, FileNameFromPageTitle) { std::string("Test page for saving page feature") + kAppendedExtension); FilePath dir = save_dir_.path().AppendASCII( "Test page for saving page feature_files"); + DownloadPersistedObserver persisted(browser()->profile(), base::Bind( + &DownloadStoredProperly, url, full_file_name, 3, + DownloadItem::COMPLETE)); ASSERT_TRUE(GetCurrentTab(browser())->SavePage( full_file_name, dir, content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML)); - GURL output_url; - ASSERT_TRUE(WaitForSavePackageToFinish(browser(), &output_url)); - EXPECT_EQ(url, output_url); + ASSERT_TRUE(WaitForSavePackageToFinish(browser(), url)); + persisted.WaitForPersisted(); EXPECT_TRUE(browser()->window()->IsDownloadShelfVisible()); - // b.htm is 3 files. - CheckDownloadHistory(url, full_file_name, 3, DownloadItem::COMPLETE); EXPECT_TRUE(file_util::PathExists(full_file_name)); EXPECT_TRUE(file_util::PathExists(dir)); @@ -586,25 +555,26 @@ IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, RemoveFromList) { FilePath full_file_name, dir; GetDestinationPaths("a", &full_file_name, &dir); + DownloadPersistedObserver persisted(browser()->profile(), base::Bind( + &DownloadStoredProperly, url, full_file_name, 1, + DownloadItem::COMPLETE)); ASSERT_TRUE(GetCurrentTab(browser())->SavePage(full_file_name, dir, content::SAVE_PAGE_TYPE_AS_ONLY_HTML)); - GURL output_url; - ASSERT_TRUE(WaitForSavePackageToFinish(browser(), &output_url)); - EXPECT_EQ(url, output_url); + ASSERT_TRUE(WaitForSavePackageToFinish(browser(), url)); + persisted.WaitForPersisted(); EXPECT_TRUE(browser()->window()->IsDownloadShelfVisible()); - // a.htm is 1 file. - CheckDownloadHistory(url, full_file_name, 1, DownloadItem::COMPLETE); - EXPECT_EQ(GetDownloadManager()->RemoveAllDownloads(), 1); + DownloadManager* manager(GetDownloadManager()); + std::vector<DownloadItem*> downloads; + manager->GetAllDownloads(&downloads); + ASSERT_EQ(1UL, downloads.size()); + DownloadRemovedObserver removed(browser()->profile(), downloads[0]->GetId()); + + EXPECT_EQ(manager->RemoveAllDownloads(), 1); - // Should not be in history. - QueryDownloadHistory(); - EXPECT_EQ(std::find_if(history_entries_.begin(), history_entries_.end(), - DownloadPersistentStoreInfoMatch( - url, full_file_name, 1, DownloadItem::COMPLETE)), - history_entries_.end()); + removed.WaitForRemoved(); EXPECT_TRUE(file_util::PathExists(full_file_name)); EXPECT_FALSE(file_util::PathExists(dir)); @@ -672,11 +642,12 @@ IN_PROC_BROWSER_TEST_F(SavePageAsMHTMLBrowserTest, SavePageAsMHTML) { #else SavePackageFilePicker::SetShouldPromptUser(false); #endif + DownloadPersistedObserver persisted(browser()->profile(), base::Bind( + &DownloadStoredProperly, url, full_file_name, -1, + DownloadItem::COMPLETE)); chrome::SavePage(browser()); - GURL output_url; - ASSERT_TRUE(WaitForSavePackageToFinish(browser(), &output_url)); - EXPECT_EQ(url, output_url); - CheckDownloadHistory(url, full_file_name, -1, DownloadItem::COMPLETE); + ASSERT_TRUE(WaitForSavePackageToFinish(browser(), url)); + persisted.WaitForPersisted(); EXPECT_TRUE(file_util::PathExists(full_file_name)); int64 actual_file_size = -1; diff --git a/chrome/browser/extensions/api/downloads/downloads_api.cc b/chrome/browser/extensions/api/downloads/downloads_api.cc index fc5ce8a..039ff20 100644 --- a/chrome/browser/extensions/api/downloads/downloads_api.cc +++ b/chrome/browser/extensions/api/downloads/downloads_api.cc @@ -860,9 +860,6 @@ bool DownloadsGetFileIconFunction::RunImpl() { if (!download_item && incognito_manager) download_item = incognito_manager->GetDownload(params->download_id); if (!download_item || download_item->GetTargetFilePath().empty()) { - // The DownloadItem is is added to history when the path is determined. If - // the download is not in history, then we don't have a path / final - // filename and no icon. error_ = download_extension_errors::kInvalidOperationError; return false; } diff --git a/chrome/browser/extensions/api/downloads/downloads_api_unittest.cc b/chrome/browser/extensions/api/downloads/downloads_api_unittest.cc index 7c0117f..e2df57d 100644 --- a/chrome/browser/extensions/api/downloads/downloads_api_unittest.cc +++ b/chrome/browser/extensions/api/downloads/downloads_api_unittest.cc @@ -19,6 +19,7 @@ #include "chrome/browser/extensions/event_names.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_function_test_utils.h" +#include "chrome/browser/history/download_row.h" #include "chrome/browser/net/url_request_mock_util.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" @@ -33,7 +34,6 @@ #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_item.h" #include "content/public/browser/download_manager.h" -#include "content/public/browser/download_persistent_store_info.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" @@ -58,7 +58,6 @@ using content::BrowserContext; using content::BrowserThread; using content::DownloadItem; using content::DownloadManager; -using content::DownloadPersistentStoreInfo; using content::URLRequestSlowDownloadJob; namespace events = extensions::event_names; @@ -357,24 +356,19 @@ class DownloadExtensionTest : public ExtensionApiTest { DownloadManager::DownloadVector* items) { DownloadIdComparator download_id_comparator; base::Time current = base::Time::Now(); - std::vector<DownloadPersistentStoreInfo> entries; - entries.reserve(count); + items->clear(); + GetOnRecordManager()->GetAllDownloads(items); + CHECK_EQ(0, static_cast<int>(items->size())); for (size_t i = 0; i < count; ++i) { - DownloadPersistentStoreInfo entry( + DownloadItem* item = GetOnRecordManager()->CreateDownloadItem( downloads_directory().Append(history_info[i].filename), GURL(), GURL(), // URL, referrer current, current, // start_time, end_time 1, 1, // received_bytes, total_bytes history_info[i].state, // state - i + 1, // db_handle false); // opened - entries.push_back(entry); + items->push_back(item); } - GetOnRecordManager()->OnPersistentStoreQueryComplete(&entries); - GetOnRecordManager()->GetAllDownloads(items); - EXPECT_EQ(count, items->size()); - if (count != items->size()) - return false; // Order by ID so that they are in the order that we created them. std::sort(items->begin(), items->end(), download_id_comparator); @@ -583,6 +577,10 @@ class MockIconExtractorImpl : public DownloadFileIconExtractor { IconURLCallback callback_; }; +bool ItemNotInProgress(DownloadItem* item) { + return !item->IsInProgress(); +} + // Cancels the underlying DownloadItem when the ScopedCancellingItem goes out of // scope. Like a scoped_ptr, but for DownloadItems. class ScopedCancellingItem { @@ -590,6 +588,9 @@ class ScopedCancellingItem { explicit ScopedCancellingItem(DownloadItem* item) : item_(item) {} ~ScopedCancellingItem() { item_->Cancel(true); + content::DownloadUpdatedObserver observer( + item_, base::Bind(&ItemNotInProgress)); + observer.WaitForEvent(); } DownloadItem* operator*() { return item_; } DownloadItem* operator->() { return item_; } @@ -612,6 +613,9 @@ class ScopedItemVectorCanceller { item != items_->end(); ++item) { if ((*item)->IsInProgress()) (*item)->Cancel(true); + content::DownloadUpdatedObserver observer( + (*item), base::Bind(&ItemNotInProgress)); + observer.WaitForEvent(); } } @@ -897,16 +901,13 @@ UIThreadExtensionFunction* MockedGetFileIconFunction( return function.release(); } -bool WaitForPersisted(DownloadItem* item) { - return item->IsPersisted(); -} - // Test downloads.getFileIcon() on in-progress, finished, cancelled and deleted // download items. IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadExtensionTest_FileIcon_Active) { DownloadItem* download_item = CreateSlowTestDownload(); ASSERT_TRUE(download_item); + ASSERT_FALSE(download_item->GetTargetFilePath().empty()); std::string args32(base::StringPrintf("[%d, {\"size\": 32}]", download_item->GetId())); std::string result_string; @@ -943,17 +944,14 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, // Now create another download. download_item = CreateSlowTestDownload(); - args32 = base::StringPrintf("[%d, {\"size\": 32}]", download_item->GetId()); ASSERT_TRUE(download_item); - - // http://crbug.com/154995 - content::DownloadUpdatedObserver persisted( - download_item, base::Bind(&WaitForPersisted)); - persisted.WaitForEvent(); + ASSERT_FALSE(download_item->GetTargetFilePath().empty()); + args32 = base::StringPrintf("[%d, {\"size\": 32}]", download_item->GetId()); // Cancel the download. As long as the download has a target path, we should // be able to query the file icon. download_item->Cancel(true); + ASSERT_FALSE(download_item->GetTargetFilePath().empty()); // Let cleanup complete on the FILE thread. content::RunAllPendingInMessageLoop(BrowserThread::FILE); // Check the path passed to the icon extractor post-cancellation. diff --git a/chrome/browser/history/download_database.cc b/chrome/browser/history/download_database.cc index c988115..73f456f7 100644 --- a/chrome/browser/history/download_database.cc +++ b/chrome/browser/history/download_database.cc @@ -14,16 +14,18 @@ #include "base/time.h" #include "base/utf_string_conversions.h" #include "build/build_config.h" +#include "chrome/browser/history/download_row.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_item.h" -#include "content/public/browser/download_persistent_store_info.h" #include "sql/statement.h" using content::DownloadItem; -using content::DownloadPersistentStoreInfo; namespace history { +// static +const int64 DownloadDatabase::kUninitializedHandle = -1; + namespace { static const char kSchema[] = @@ -42,8 +44,8 @@ static const char kSchema[] = // DownloadItem::DownloadState to change without breaking the database schema. // They guarantee that the values of the |state| field in the database are one // of the values returned by StateToInt, and that the values of the |state| -// field of the DownloadPersistentStoreInfos returned by QueryDownloads() are -// one of the values returned by IntToState(). +// field of the DownloadRows returned by QueryDownloads() are one of the values +// returned by IntToState(). static const int kStateInvalid = -1; static const int kStateInProgress = 0; static const int kStateComplete = 1; @@ -112,15 +114,6 @@ DownloadDatabase::DownloadDatabase() DownloadDatabase::~DownloadDatabase() { } -void DownloadDatabase::CheckThread() { - if (owning_thread_set_) { - DCHECK(owning_thread_ == base::PlatformThread::CurrentId()); - } else { - owning_thread_ = base::PlatformThread::CurrentId(); - owning_thread_set_ = true; - } -} - bool DownloadDatabase::EnsureColumnExists( const std::string& name, const std::string& type) { std::string add_col = "ALTER TABLE downloads ADD COLUMN " + name + " " + type; @@ -137,7 +130,6 @@ bool DownloadDatabase::MigrateDownloadsState() { } bool DownloadDatabase::InitDownloadTable() { - CheckThread(); GetMetaTable().GetValue(kNextDownloadId, &next_id_); if (GetDB().DoesTableExist("downloads")) { return EnsureColumnExists("end_time", "INTEGER NOT NULL DEFAULT 0") && @@ -148,17 +140,15 @@ bool DownloadDatabase::InitDownloadTable() { } bool DownloadDatabase::DropDownloadTable() { - CheckThread(); return GetDB().Execute("DROP TABLE downloads"); } void DownloadDatabase::QueryDownloads( - std::vector<DownloadPersistentStoreInfo>* results) { - CheckThread(); + std::vector<DownloadRow>* results) { results->clear(); if (next_db_handle_ < 1) next_db_handle_ = 1; - std::set<DownloadID> db_handles; + std::set<int64> db_handles; sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, "SELECT id, full_path, url, start_time, received_bytes, " @@ -167,7 +157,7 @@ void DownloadDatabase::QueryDownloads( "ORDER BY start_time")); while (statement.Step()) { - DownloadPersistentStoreInfo info; + DownloadRow info; info.db_handle = statement.ColumnInt64(0); info.path = ColumnFilePath(statement, 1); info.url = GURL(statement.ColumnString(2)); @@ -193,8 +183,7 @@ void DownloadDatabase::QueryDownloads( } } -bool DownloadDatabase::UpdateDownload(const DownloadPersistentStoreInfo& data) { - CheckThread(); +bool DownloadDatabase::UpdateDownload(const DownloadRow& data) { DCHECK(data.db_handle > 0); int state = StateToInt(data.state); if (state == kStateInvalid) { @@ -203,30 +192,20 @@ bool DownloadDatabase::UpdateDownload(const DownloadPersistentStoreInfo& data) { } sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, "UPDATE downloads " - "SET received_bytes=?, state=?, end_time=?, opened=? WHERE id=?")); - statement.BindInt64(0, data.received_bytes); - statement.BindInt(1, state); - statement.BindInt64(2, data.end_time.ToTimeT()); - statement.BindInt(3, (data.opened ? 1 : 0)); - statement.BindInt64(4, data.db_handle); - - return statement.Run(); -} - -bool DownloadDatabase::UpdateDownloadPath(const FilePath& path, - DownloadID db_handle) { - CheckThread(); - DCHECK(db_handle > 0); - sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, - "UPDATE downloads SET full_path=? WHERE id=?")); - BindFilePath(statement, path, 0); - statement.BindInt64(1, db_handle); + "SET full_path=?, received_bytes=?, state=?, end_time=?, total_bytes=?, " + "opened=? WHERE id=?")); + BindFilePath(statement, data.path, 0); + statement.BindInt64(1, data.received_bytes); + statement.BindInt(2, state); + statement.BindInt64(3, data.end_time.ToTimeT()); + statement.BindInt(4, data.total_bytes); + statement.BindInt(5, (data.opened ? 1 : 0)); + statement.BindInt64(6, data.db_handle); return statement.Run(); } bool DownloadDatabase::CleanUpInProgressEntries() { - CheckThread(); sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, "UPDATE downloads SET state=? WHERE state=?")); statement.BindInt(0, kStateCancelled); @@ -236,20 +215,18 @@ bool DownloadDatabase::CleanUpInProgressEntries() { } int64 DownloadDatabase::CreateDownload( - const DownloadPersistentStoreInfo& info) { - CheckThread(); - + const DownloadRow& info) { if (next_db_handle_ == 0) { // This is unlikely. All current known tests and users already call // QueryDownloads() before CreateDownload(). - std::vector<DownloadPersistentStoreInfo> results; + std::vector<DownloadRow> results; QueryDownloads(&results); CHECK_NE(0, next_db_handle_); } int state = StateToInt(info.state); if (state == kStateInvalid) - return false; + return kUninitializedHandle; sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, "INSERT INTO downloads " @@ -275,75 +252,21 @@ int64 DownloadDatabase::CreateDownload( return db_handle; } - return 0; + return kUninitializedHandle; } -void DownloadDatabase::RemoveDownload(DownloadID db_handle) { - CheckThread(); - +void DownloadDatabase::RemoveDownload(int64 handle) { sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, "DELETE FROM downloads WHERE id=?")); - statement.BindInt64(0, db_handle); - + statement.BindInt64(0, handle); statement.Run(); } -bool DownloadDatabase::RemoveDownloadsBetween(base::Time delete_begin, - base::Time delete_end) { - CheckThread(); - time_t start_time = delete_begin.ToTimeT(); - time_t end_time = delete_end.ToTimeT(); - - int num_downloads_deleted = -1; - { - sql::Statement count(GetDB().GetCachedStatement(SQL_FROM_HERE, - "SELECT count(*) FROM downloads WHERE start_time >= ? " - "AND start_time < ? AND (State = ? OR State = ? OR State = ?)")); - count.BindInt64(0, start_time); - count.BindInt64( - 1, - end_time ? end_time : std::numeric_limits<int64>::max()); - count.BindInt(2, kStateComplete); - count.BindInt(3, kStateCancelled); - count.BindInt(4, kStateInterrupted); - if (count.Step()) - num_downloads_deleted = count.ColumnInt(0); - } - - - bool success = false; - base::TimeTicks started_removing = base::TimeTicks::Now(); - { - // This does not use an index. We currently aren't likely to have enough - // downloads where an index by time will give us a lot of benefit. - sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, - "DELETE FROM downloads WHERE start_time >= ? AND start_time < ? " - "AND (State = ? OR State = ? OR State = ?)")); - statement.BindInt64(0, start_time); - statement.BindInt64( - 1, - end_time ? end_time : std::numeric_limits<int64>::max()); - statement.BindInt(2, kStateComplete); - statement.BindInt(3, kStateCancelled); - statement.BindInt(4, kStateInterrupted); - - success = statement.Run(); - } - - base::TimeTicks finished_removing = base::TimeTicks::Now(); - - if (num_downloads_deleted >= 0) { - UMA_HISTOGRAM_COUNTS("Download.DatabaseRemoveDownloadsCount", - num_downloads_deleted); - base::TimeDelta micros = (1000 * (finished_removing - started_removing)); - UMA_HISTOGRAM_TIMES("Download.DatabaseRemoveDownloadsTime", micros); - if (num_downloads_deleted > 0) { - UMA_HISTOGRAM_TIMES("Download.DatabaseRemoveDownloadsTimePerRecord", - (1000 * micros) / num_downloads_deleted); - } - } - - return success; +int DownloadDatabase::CountDownloads() { + sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, + "SELECT count(*) from downloads")); + statement.Step(); + return statement.ColumnInt(0); } } // namespace history diff --git a/chrome/browser/history/download_database.h b/chrome/browser/history/download_database.h index f5e18bd..8cf07ab 100644 --- a/chrome/browser/history/download_database.h +++ b/chrome/browser/history/download_database.h @@ -5,28 +5,29 @@ #ifndef CHROME_BROWSER_HISTORY_DOWNLOAD_DATABASE_H_ #define CHROME_BROWSER_HISTORY_DOWNLOAD_DATABASE_H_ -#include <set> #include <string> +#include <vector> #include "base/threading/platform_thread.h" -#include "chrome/browser/history/history_types.h" #include "sql/meta_table.h" class FilePath; -namespace content { -struct DownloadPersistentStoreInfo; -} - namespace sql { class Connection; } namespace history { +struct DownloadRow; + // Maintains a table of downloads. class DownloadDatabase { public: + // The value of |db_handle| indicating that the associated DownloadItem is not + // yet persisted. + static const int64 kUninitializedHandle; + // Must call InitDownloadTable before using any other functions. DownloadDatabase(); virtual ~DownloadDatabase(); @@ -35,15 +36,12 @@ class DownloadDatabase { // Get all the downloads from the database. void QueryDownloads( - std::vector<content::DownloadPersistentStoreInfo>* results); + std::vector<DownloadRow>* results); // Update the state of one download. Returns true if successful. - // Does not update |url|, |start_time|, |total_bytes|; uses |db_handle| only + // Does not update |url|, |start_time|; uses |db_handle| only // to select the row in the database table to update. - bool UpdateDownload(const content::DownloadPersistentStoreInfo& data); - - // Update the path of one download. Returns true if successful. - bool UpdateDownloadPath(const FilePath& path, DownloadID db_handle); + bool UpdateDownload(const DownloadRow& data); // Fixes state of the download entries. Sometimes entries with IN_PROGRESS // state are not updated during browser shutdown (particularly when crashing). @@ -52,16 +50,12 @@ class DownloadDatabase { bool CleanUpInProgressEntries(); // Create a new database entry for one download and return its primary db id. - int64 CreateDownload(const content::DownloadPersistentStoreInfo& info); + int64 CreateDownload(const DownloadRow& info); - // Remove a download from the database. - void RemoveDownload(DownloadID db_handle); + // Remove |handle| from the database. + void RemoveDownload(int64 handle); - // Remove all completed downloads that started after |remove_begin| - // (inclusive) and before |remove_end|. You may use null Time values - // to do an unbounded delete in either direction. This function ignores - // all downloads that are in progress or are waiting to be cancelled. - bool RemoveDownloadsBetween(base::Time remove_begin, base::Time remove_end); + int CountDownloads(); protected: // Returns the database for the functions in this interface. @@ -83,9 +77,6 @@ class DownloadDatabase { bool DropDownloadTable(); private: - // TODO(rdsmith): Remove after http://crbug.com/96627 has been resolved. - void CheckThread(); - bool EnsureColumnExists(const std::string& name, const std::string& type); bool owning_thread_set_; diff --git a/chrome/browser/history/download_row.cc b/chrome/browser/history/download_row.cc new file mode 100644 index 0000000..d3b553b --- /dev/null +++ b/chrome/browser/history/download_row.cc @@ -0,0 +1,43 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/history/download_row.h" + +namespace history { + +DownloadRow::DownloadRow() + : received_bytes(0), + total_bytes(0), + state(content::DownloadItem::IN_PROGRESS), + db_handle(0), + opened(false) { +} + +DownloadRow::DownloadRow( + const FilePath& path, + const GURL& url, + const GURL& referrer, + const base::Time& start, + const base::Time& end, + int64 received, + int64 total, + content::DownloadItem::DownloadState download_state, + int64 handle, + bool download_opened) + : path(path), + url(url), + referrer_url(referrer), + start_time(start), + end_time(end), + received_bytes(received), + total_bytes(total), + state(download_state), + db_handle(handle), + opened(download_opened) { +} + +DownloadRow::~DownloadRow() { +} + +} // namespace history diff --git a/chrome/browser/history/download_row.h b/chrome/browser/history/download_row.h new file mode 100644 index 0000000..1e36c76 --- /dev/null +++ b/chrome/browser/history/download_row.h @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_HISTORY_DOWNLOAD_ROW_H_ +#define CHROME_BROWSER_HISTORY_DOWNLOAD_ROW_H_ + +#include "base/file_path.h" +#include "base/time.h" +#include "content/public/browser/download_item.h" +#include "googleurl/src/gurl.h" + +namespace history { + +// Contains the information that is stored in the download system's persistent +// store (or refers to it). DownloadHistory uses this to communicate with the +// DownloadDatabase through the HistoryService. +struct DownloadRow { + DownloadRow(); + DownloadRow( + const FilePath& path, + const GURL& url, + const GURL& referrer, + const base::Time& start, + const base::Time& end, + int64 received, + int64 total, + content::DownloadItem::DownloadState download_state, + int64 handle, + bool download_opened); + ~DownloadRow(); + + // The current path to the downloaded file. + // TODO(benjhayden/asanka): Persist the target filename as well. + FilePath path; + + // The URL from which we are downloading. This is the final URL after any + // redirection by the server for |url_chain|. Is not changed by + // UpdateDownload(). + GURL url; + + // The URL that referred us. Is not changed by UpdateDownload(). + GURL referrer_url; + + // The time when the download started. Is not changed by UpdateDownload(). + base::Time start_time; + + // The time when the download completed. + base::Time end_time; + + // The number of bytes received (so far). + int64 received_bytes; + + // The total number of bytes in the download. Is not changed by + // UpdateDownload(). + int64 total_bytes; + + // The current state of the download. + content::DownloadItem::DownloadState state; + + // The handle of the download in the database. Is not changed by + // UpdateDownload(). + int64 db_handle; + + // Whether this download has ever been opened from the browser. + bool opened; +}; + +} // namespace history + +#endif // CHROME_BROWSER_HISTORY_DOWNLOAD_ROW_H_ diff --git a/chrome/browser/history/history.cc b/chrome/browser/history/history.cc index 13def26..ff1aad3 100644 --- a/chrome/browser/history/history.cc +++ b/chrome/browser/history/history.cc @@ -38,6 +38,7 @@ #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/bookmarks/bookmark_model_factory.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/history/download_row.h" #include "chrome/browser/history/history_backend.h" #include "chrome/browser/history/history_notifications.h" #include "chrome/browser/history/history_types.h" @@ -58,7 +59,6 @@ #include "chrome/common/thumbnail_score.h" #include "chrome/common/url_constants.h" #include "content/public/browser/browser_thread.h" -#include "content/public/browser/download_persistent_store_info.h" #include "content/public/browser/notification_service.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" @@ -664,13 +664,12 @@ HistoryService::Handle HistoryService::QueryURL( // Handle creation of a download by creating an entry in the history service's // 'downloads' table. HistoryService::Handle HistoryService::CreateDownload( - int32 id, - const content::DownloadPersistentStoreInfo& create_info, + const history::DownloadRow& create_info, CancelableRequestConsumerBase* consumer, const HistoryService::DownloadCreateCallback& callback) { DCHECK(thread_checker_.CalledOnValidThread()); return Schedule(PRIORITY_NORMAL, &HistoryBackend::CreateDownload, consumer, - new history::DownloadCreateRequest(callback), id, + new history::DownloadCreateRequest(callback), create_info); } @@ -702,32 +701,15 @@ void HistoryService::CleanUpInProgressEntries() { // Handle updates for a particular download. This is a 'fire and forget' // operation, so we don't need to be called back. -void HistoryService::UpdateDownload( - const content::DownloadPersistentStoreInfo& data) { +void HistoryService::UpdateDownload(const history::DownloadRow& data) { DCHECK(thread_checker_.CalledOnValidThread()); ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::UpdateDownload, data); } -void HistoryService::UpdateDownloadPath(const FilePath& path, - int64 db_handle) { - DCHECK(thread_checker_.CalledOnValidThread()); - ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::UpdateDownloadPath, - path, db_handle); -} - -void HistoryService::RemoveDownload(int64 db_handle) { - DCHECK(thread_checker_.CalledOnValidThread()); - ScheduleAndForget(PRIORITY_NORMAL, - &HistoryBackend::RemoveDownload, db_handle); -} - -void HistoryService::RemoveDownloadsBetween(Time remove_begin, - Time remove_end) { +void HistoryService::RemoveDownloads(const std::set<int64>& db_handles) { DCHECK(thread_checker_.CalledOnValidThread()); ScheduleAndForget(PRIORITY_NORMAL, - &HistoryBackend::RemoveDownloadsBetween, - remove_begin, - remove_end); + &HistoryBackend::RemoveDownloads, db_handles); } HistoryService::Handle HistoryService::QueryHistory( diff --git a/chrome/browser/history/history.h b/chrome/browser/history/history.h index b27b809..07fb0b2 100644 --- a/chrome/browser/history/history.h +++ b/chrome/browser/history/history.h @@ -39,31 +39,31 @@ class BookmarkService; class FilePath; class GURL; class HistoryURLProvider; -struct HistoryURLProviderParams; class PageUsageData; class PageUsageRequest; class Profile; +struct HistoryURLProviderParams; namespace base { class Thread; } -namespace content { -struct DownloadPersistentStoreInfo; -} namespace history { -class InMemoryHistoryBackend; -class InMemoryURLIndex; -class InMemoryURLIndexTest; -struct HistoryAddPageArgs; + class HistoryBackend; class HistoryDatabase; -struct HistoryDetails; class HistoryQueryTest; -class VisitFilter; +class InMemoryHistoryBackend; +class InMemoryURLIndex; +class InMemoryURLIndexTest; class URLDatabase; class VisitDatabaseObserver; +class VisitFilter; +struct DownloadRow; +struct HistoryAddPageArgs; +struct HistoryDetails; + } // namespace history namespace sync_pb { @@ -454,15 +454,15 @@ class HistoryService : public CancelableRequestProvider, // Implemented by the caller of 'CreateDownload' below, and is called when the // history service has created a new entry for a download in the history db. - typedef base::Callback<void(int32, int64)> DownloadCreateCallback; + typedef base::Callback<void(int64)> DownloadCreateCallback; - // Begins a history request to create a new persistent entry for a download. - // 'info' contains all the download's creation state, and 'callback' runs - // when the history service request is complete. - Handle CreateDownload(int32 id, - const content::DownloadPersistentStoreInfo& info, - CancelableRequestConsumerBase* consumer, - const DownloadCreateCallback& callback); + // Begins a history request to create a new row for a download. 'info' + // contains all the download's creation state, and 'callback' runs when the + // history service request is complete. + Handle CreateDownload( + const history::DownloadRow& info, + CancelableRequestConsumerBase* consumer, + const DownloadCreateCallback& callback); // Implemented by the caller of 'GetNextDownloadId' below. typedef base::Callback<void(int)> DownloadNextIdCallback; @@ -474,12 +474,12 @@ class HistoryService : public CancelableRequestProvider, // Implemented by the caller of 'QueryDownloads' below, and is called when the // history service has retrieved a list of all download state. The call typedef base::Callback<void( - std::vector<content::DownloadPersistentStoreInfo>*)> + std::vector<history::DownloadRow>*)> DownloadQueryCallback; // Begins a history request to retrieve the state of all downloads in the // history db. 'callback' runs when the history service request is complete, - // at which point 'info' contains an array of DownloadPersistentStoreInfo, one + // at which point 'info' contains an array of history::DownloadRow, one // per download. Handle QueryDownloads(CancelableRequestConsumerBase* consumer, const DownloadQueryCallback& callback); @@ -491,22 +491,11 @@ class HistoryService : public CancelableRequestProvider, // Called to update the history service about the current state of a download. // This is a 'fire and forget' query, so just pass the relevant state info to // the database with no need for a callback. - void UpdateDownload(const content::DownloadPersistentStoreInfo& data); - - // Called to update the history service about the path of a download. - // This is a 'fire and forget' query. - void UpdateDownloadPath(const FilePath& path, int64 db_handle); - - // Permanently remove a download from the history system. This is a 'fire and - // forget' operation. - void RemoveDownload(int64 db_handle); - - // Permanently removes all completed download from the history system within - // the specified range. This function does not delete downloads that are in - // progress or in the process of being cancelled. This is a 'fire and forget' - // operation. You can pass is_null times to get unbounded time in either or - // both directions. - void RemoveDownloadsBetween(base::Time remove_begin, base::Time remove_end); + void UpdateDownload(const history::DownloadRow& data); + + // Permanently remove some downloads from the history system. This is a 'fire + // and forget' operation. + void RemoveDownloads(const std::set<int64>& db_handles); // Visit Segments ------------------------------------------------------------ diff --git a/chrome/browser/history/history_backend.cc b/chrome/browser/history/history_backend.cc index c018628..3f3d33c 100644 --- a/chrome/browser/history/history_backend.cc +++ b/chrome/browser/history/history_backend.cc @@ -23,6 +23,7 @@ #include "chrome/browser/api/bookmarks/bookmark_service.h" #include "chrome/browser/autocomplete/history_url_provider.h" #include "chrome/browser/common/cancelable_request.h" +#include "chrome/browser/history/download_row.h" #include "chrome/browser/history/history_notifications.h" #include "chrome/browser/history/history_publisher.h" #include "chrome/browser/history/in_memory_history_backend.h" @@ -33,7 +34,6 @@ #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/url_constants.h" -#include "content/public/browser/download_persistent_store_info.h" #include "googleurl/src/gurl.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" @@ -1292,40 +1292,52 @@ void HistoryBackend::CleanUpInProgressEntries() { // Update a particular download entry. void HistoryBackend::UpdateDownload( - const content::DownloadPersistentStoreInfo& data) { + const history::DownloadRow& data) { if (db_.get()) db_->UpdateDownload(data); } -// Update the path of a particular download entry. -void HistoryBackend::UpdateDownloadPath(const FilePath& path, - int64 db_handle) { - if (db_.get()) - db_->UpdateDownloadPath(path, db_handle); -} - // Create a new download entry and pass back the db_handle to it. void HistoryBackend::CreateDownload( scoped_refptr<DownloadCreateRequest> request, - int32 id, - const content::DownloadPersistentStoreInfo& history_info) { + const history::DownloadRow& history_info) { int64 db_handle = 0; if (!request->canceled()) { if (db_.get()) db_handle = db_->CreateDownload(history_info); - request->ForwardResult(id, db_handle); + request->ForwardResult(db_handle); } } -void HistoryBackend::RemoveDownload(int64 db_handle) { - if (db_.get()) - db_->RemoveDownload(db_handle); -} - -void HistoryBackend::RemoveDownloadsBetween(const Time remove_begin, - const Time remove_end) { - if (db_.get()) - db_->RemoveDownloadsBetween(remove_begin, remove_end); +void HistoryBackend::RemoveDownloads(const std::set<int64>& handles) { + if (!db_.get()) + return; + int downloads_count_before = db_->CountDownloads(); + base::TimeTicks started_removing = base::TimeTicks::Now(); + // HistoryBackend uses a long-running Transaction that is committed + // periodically, so this loop doesn't actually hit the disk too hard. + for (std::set<int64>::const_iterator it = handles.begin(); + it != handles.end(); ++it) { + db_->RemoveDownload(*it); + } + base::TimeTicks finished_removing = base::TimeTicks::Now(); + int downloads_count_after = db_->CountDownloads(); + int num_downloads_deleted = downloads_count_before - downloads_count_after; + if (num_downloads_deleted >= 0) { + UMA_HISTOGRAM_COUNTS("Download.DatabaseRemoveDownloadsCount", + num_downloads_deleted); + base::TimeDelta micros = (1000 * (finished_removing - started_removing)); + UMA_HISTOGRAM_TIMES("Download.DatabaseRemoveDownloadsTime", micros); + if (num_downloads_deleted > 0) { + UMA_HISTOGRAM_TIMES("Download.DatabaseRemoveDownloadsTimePerRecord", + (1000 * micros) / num_downloads_deleted); + } + } + int num_downloads_not_deleted = handles.size() - num_downloads_deleted; + if (num_downloads_not_deleted >= 0) { + UMA_HISTOGRAM_COUNTS("Download.DatabaseRemoveDownloadsCountNotRemoved", + num_downloads_not_deleted); + } } void HistoryBackend::QueryHistory(scoped_refptr<QueryHistoryRequest> request, diff --git a/chrome/browser/history/history_backend.h b/chrome/browser/history/history_backend.h index 1b16238..626aceb 100644 --- a/chrome/browser/history/history_backend.h +++ b/chrome/browser/history/history_backend.h @@ -29,17 +29,15 @@ class BookmarkService; class TestingProfile; struct ThumbnailScore; -namespace content { -struct DownloadPersistentStoreInfo; -} - namespace history { #if defined(OS_ANDROID) class AndroidProviderBackend; #endif + class CommitLaterTask; class HistoryPublisher; class VisitFilter; +struct DownloadRow; // The maximum number of icons URLs per page which can be stored in the // thumbnail database. @@ -309,15 +307,10 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>, void GetNextDownloadId(scoped_refptr<DownloadNextIdRequest> request); void QueryDownloads(scoped_refptr<DownloadQueryRequest> request); void CleanUpInProgressEntries(); - void UpdateDownload(const content::DownloadPersistentStoreInfo& data); - void UpdateDownloadPath(const FilePath& path, int64 db_handle); + void UpdateDownload(const DownloadRow& data); void CreateDownload(scoped_refptr<DownloadCreateRequest> request, - int32 id, - const content::DownloadPersistentStoreInfo& info); - void RemoveDownload(int64 db_handle); - void RemoveDownloadsBetween(const base::Time remove_begin, - const base::Time remove_end); - void RemoveDownloads(const base::Time remove_end); + const DownloadRow& info); + void RemoveDownloads(const std::set<int64>& db_handles); // Segment usage ------------------------------------------------------------- diff --git a/chrome/browser/history/history_marshaling.h b/chrome/browser/history/history_marshaling.h index 0248115..75d8f92 100644 --- a/chrome/browser/history/history_marshaling.h +++ b/chrome/browser/history/history_marshaling.h @@ -68,7 +68,7 @@ typedef CancelableRequest1<HistoryService::DownloadNextIdCallback, typedef CancelableRequest1<HistoryService::DownloadQueryCallback, - std::vector<content::DownloadPersistentStoreInfo> > + std::vector<DownloadRow> > DownloadQueryRequest; typedef CancelableRequest<HistoryService::DownloadCreateCallback> diff --git a/chrome/browser/history/history_unittest.cc b/chrome/browser/history/history_unittest.cc index d41a022..4146f0a 100644 --- a/chrome/browser/history/history_unittest.cc +++ b/chrome/browser/history/history_unittest.cc @@ -36,6 +36,7 @@ #include "base/string_util.h" #include "base/time.h" #include "base/utf_string_conversions.h" +#include "chrome/browser/history/download_row.h" #include "chrome/browser/history/history.h" #include "chrome/browser/history/history_backend.h" #include "chrome/browser/history/history_database.h" @@ -48,7 +49,6 @@ #include "chrome/common/thumbnail_score.h" #include "chrome/tools/profiles/thumbnail-inl.h" #include "content/public/browser/download_item.h" -#include "content/public/browser/download_persistent_store_info.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "sql/connection.h" @@ -60,7 +60,6 @@ using base::Time; using base::TimeDelta; using content::DownloadItem; -using content::DownloadPersistentStoreInfo; namespace history { class HistoryBackendDBTest; @@ -137,7 +136,7 @@ class HistoryBackendDBTest : public testing::Test { } int64 AddDownload(DownloadItem::DownloadState state, const Time& time) { - DownloadPersistentStoreInfo download( + DownloadRow download( FilePath(FILE_PATH_LITERAL("foo-path")), GURL("foo-url"), GURL(""), @@ -188,86 +187,18 @@ namespace { TEST_F(HistoryBackendDBTest, ClearBrowsingData_Downloads) { CreateBackendAndDatabase(); - Time now = Time::Now(); - TimeDelta one_day = TimeDelta::FromDays(1); - Time month_ago = now - TimeDelta::FromDays(30); - // Initially there should be nothing in the downloads database. - std::vector<DownloadPersistentStoreInfo> downloads; - db_->QueryDownloads(&downloads); - EXPECT_EQ(0U, downloads.size()); - - // Keep track of these as we need to update them later during the test. - DownloadID in_progress; - - // Create one with a 0 time. - EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, Time())); - // Create one for now and +/- 1 day. - EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, now - one_day)); - EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, now)); - EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, now + one_day)); - // Try the other four states. - EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, month_ago)); - EXPECT_NE(0, in_progress = AddDownload(DownloadItem::IN_PROGRESS, month_ago)); - EXPECT_NE(0, AddDownload(DownloadItem::CANCELLED, month_ago)); - EXPECT_NE(0, AddDownload(DownloadItem::INTERRUPTED, month_ago)); - - // Test to see if inserts worked. - db_->QueryDownloads(&downloads); - EXPECT_EQ(8U, downloads.size()); - - // Try removing from current timestamp. This should delete the one in the - // future and one very recent one. - db_->RemoveDownloadsBetween(now, Time()); - db_->QueryDownloads(&downloads); - EXPECT_EQ(6U, downloads.size()); - - // Try removing from two months ago. This should not delete items that are - // 'in progress' or in 'removing' state. - db_->RemoveDownloadsBetween(now - TimeDelta::FromDays(60), Time()); - db_->QueryDownloads(&downloads); - EXPECT_EQ(2U, downloads.size()); - - // Download manager converts to TimeT, which is lossy, so we do the same - // for comparison. - Time month_ago_lossy = Time::FromTimeT(month_ago.ToTimeT()); - - // Make sure the right values remain. - EXPECT_EQ(DownloadItem::COMPLETE, downloads[0].state); - EXPECT_EQ(0, downloads[0].start_time.ToInternalValue()); - EXPECT_EQ(DownloadItem::IN_PROGRESS, downloads[1].state); - EXPECT_EQ(month_ago_lossy.ToInternalValue(), - downloads[1].start_time.ToInternalValue()); - - // Change state so we can delete the downloads. - DownloadPersistentStoreInfo data; - data.received_bytes = 512; - data.state = DownloadItem::COMPLETE; - data.end_time = base::Time::Now(); - data.opened = false; - data.db_handle = in_progress; - EXPECT_TRUE(db_->UpdateDownload(data)); - data.state = DownloadItem::CANCELLED; - EXPECT_TRUE(db_->UpdateDownload(data)); - - // Try removing from Time=0. This should delete all. - db_->RemoveDownloadsBetween(Time(), Time()); + std::vector<DownloadRow> downloads; db_->QueryDownloads(&downloads); EXPECT_EQ(0U, downloads.size()); - // Check removal of downloads stuck in IN_PROGRESS state. - EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, month_ago)); - EXPECT_NE(0, AddDownload(DownloadItem::IN_PROGRESS, month_ago)); - db_->QueryDownloads(&downloads); - EXPECT_EQ(2U, downloads.size()); - db_->RemoveDownloadsBetween(Time(), Time()); - db_->QueryDownloads(&downloads); - // IN_PROGRESS download should remain. It it indicated as "Canceled" - EXPECT_EQ(1U, downloads.size()); - db_->CleanUpInProgressEntries(); + // Add a download, test that it was added, remove it, test that it was + // removed. + DownloadID handle; + EXPECT_NE(0, handle = AddDownload(DownloadItem::COMPLETE, Time())); db_->QueryDownloads(&downloads); EXPECT_EQ(1U, downloads.size()); - db_->RemoveDownloadsBetween(Time(), Time()); + db_->RemoveDownload(handle); db_->QueryDownloads(&downloads); EXPECT_EQ(0U, downloads.size()); } diff --git a/chrome/browser/ui/webui/downloads_dom_handler.cc b/chrome/browser/ui/webui/downloads_dom_handler.cc index 131bcfc..91ff933 100644 --- a/chrome/browser/ui/webui/downloads_dom_handler.cc +++ b/chrome/browser/ui/webui/downloads_dom_handler.cc @@ -200,7 +200,6 @@ DictionaryValue* CreateDownloadItemValue( // Filters out extension downloads and downloads that don't have a filename yet. bool IsDownloadDisplayable(const content::DownloadItem& item) { return (!download_crx_util::IsExtensionDownload(item) && - item.IsPersisted() && !item.IsTemporary() && !item.GetFileNameToReportUser().empty() && !item.GetTargetFilePath().empty()); @@ -328,8 +327,6 @@ void DownloadsDOMHandler::HandleGetDownloads(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_GET_DOWNLOADS); search_text_ = ExtractStringValue(args); SendCurrentDownloads(); - if (main_notifier_.GetManager()) - main_notifier_.GetManager()->CheckForHistoryFilesRemoval(); } void DownloadsDOMHandler::HandleOpenFile(const base::ListValue* args) { @@ -436,10 +433,14 @@ void DownloadsDOMHandler::ScheduleSendCurrentDownloads() { void DownloadsDOMHandler::SendCurrentDownloads() { update_scheduled_ = false; content::DownloadManager::DownloadVector all_items, filtered_items; - if (main_notifier_.GetManager()) + if (main_notifier_.GetManager()) { main_notifier_.GetManager()->GetAllDownloads(&all_items); - if (original_notifier_.get() && original_notifier_->GetManager()) + main_notifier_.GetManager()->CheckForHistoryFilesRemoval(); + } + if (original_notifier_.get() && original_notifier_->GetManager()) { original_notifier_->GetManager()->GetAllDownloads(&all_items); + original_notifier_->GetManager()->CheckForHistoryFilesRemoval(); + } DownloadQuery query; if (!search_text_.empty()) { scoped_ptr<base::Value> query_text(base::Value::CreateStringValue( diff --git a/chrome/browser/ui/webui/downloads_dom_handler_browsertest.cc b/chrome/browser/ui/webui/downloads_dom_handler_browsertest.cc index e2f3daa..374e605 100644 --- a/chrome/browser/ui/webui/downloads_dom_handler_browsertest.cc +++ b/chrome/browser/ui/webui/downloads_dom_handler_browsertest.cc @@ -6,6 +6,7 @@ #include "base/files/scoped_temp_dir.h" #include "base/json/json_reader.h" #include "base/values.h" +#include "chrome/browser/history/download_row.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" @@ -13,7 +14,6 @@ #include "chrome/common/pref_names.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" -#include "content/public/browser/download_persistent_store_info.h" #include "content/public/browser/web_contents.h" namespace { @@ -141,22 +141,16 @@ IN_PROC_BROWSER_TEST_F(DownloadsDOMHandlerTest, GURL url = test_server()->GetURL("files/downloads/image.jpg"); base::Time current(base::Time::Now()); - content::DownloadPersistentStoreInfo population_entries[] = { - content::DownloadPersistentStoreInfo( - FilePath(FILE_PATH_LITERAL("/path/to/file")), - url, - GURL(""), - current - base::TimeDelta::FromMinutes(5), - current, - 128, - 128, - content::DownloadItem::COMPLETE, - 1, - false), - }; - std::vector<content::DownloadPersistentStoreInfo> entries( - population_entries, population_entries + arraysize(population_entries)); - download_manager()->OnPersistentStoreQueryComplete(&entries); + download_manager()->CreateDownloadItem( + FilePath(FILE_PATH_LITERAL("/path/to/file")), + url, + GURL(""), + current - base::TimeDelta::FromMinutes(5), + current, + 128, + 128, + content::DownloadItem::COMPLETE, + false); mddh.WaitForDownloadsList(); ASSERT_EQ(1, static_cast<int>(mddh.downloads_list()->GetSize())); |