summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorbenjhayden@chromium.org <benjhayden@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-20 00:52:08 +0000
committerbenjhayden@chromium.org <benjhayden@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-20 00:52:08 +0000
commit3d95e54652070dacfdb9188c3f6f48111bcc007e (patch)
tree7ad31f4dbdb170fc30cc00ce2b31d76ce146799d /chrome/browser
parent8b9d8f76949307b91f6399d452e41de59f2cbce6 (diff)
downloadchromium_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')
-rw-r--r--chrome/browser/download/chrome_download_manager_delegate.cc98
-rw-r--r--chrome/browser/download/chrome_download_manager_delegate.h33
-rw-r--r--chrome/browser/download/chrome_download_manager_delegate_unittest.cc4
-rw-r--r--chrome/browser/download/download_browsertest.cc138
-rw-r--r--chrome/browser/download/download_history.cc495
-rw-r--r--chrome/browser/download/download_history.h172
-rw-r--r--chrome/browser/download/download_history_unittest.cc741
-rw-r--r--chrome/browser/download/download_service.cc22
-rw-r--r--chrome/browser/download/download_service.h9
-rw-r--r--chrome/browser/download/download_status_updater_unittest.cc16
-rw-r--r--chrome/browser/download/save_page_browsertest.cc411
-rw-r--r--chrome/browser/extensions/api/downloads/downloads_api.cc3
-rw-r--r--chrome/browser/extensions/api/downloads/downloads_api_unittest.cc42
-rw-r--r--chrome/browser/history/download_database.cc137
-rw-r--r--chrome/browser/history/download_database.h37
-rw-r--r--chrome/browser/history/download_row.cc43
-rw-r--r--chrome/browser/history/download_row.h71
-rw-r--r--chrome/browser/history/history.cc30
-rw-r--r--chrome/browser/history/history.h61
-rw-r--r--chrome/browser/history/history_backend.cc54
-rw-r--r--chrome/browser/history/history_backend.h17
-rw-r--r--chrome/browser/history/history_marshaling.h2
-rw-r--r--chrome/browser/history/history_unittest.cc85
-rw-r--r--chrome/browser/ui/webui/downloads_dom_handler.cc11
-rw-r--r--chrome/browser/ui/webui/downloads_dom_handler_browsertest.cc28
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()));