Commit 81b1765d authored by Min Qin's avatar Min Qin Committed by Commit Bot

Import in-progress downloads when history DB is loaded

The logic in loading the download history is quite complicated today.
When DownloadManagerImpl is created, it first loads the download from
in-progress manager. And then it starts to load the downloads from
History DB. That's causing several issues:
1. Due to the mismatch between history DB and in-progress DB, DownloadManagerImpl
need to resolve the conflicts when history DB is loaded. Normally it just
takes the information from in-progress DB. However, due to the latency during
database commit, history DB might contain fresher information than the
in-progress DB if the download is in a terminating state (Cancelled/Completed).
And that's causing crbug/898859 as an extra OnDownloadCreated() call is called
for those terminating downloads.

2. DownloadHistory used to call CreateDownloadItem() and expect a reentrant
OnDownloadCreated() call when loading history downloads. However, this may
no longer happen as OnDownloadCreated() may have already been called when
loading the in-progress DB. So DownloadHistory have to handle the case that
there is no reentrant call, which makes the code hard to understand.

3. DownloadHistory might get a OnDownloadCreated() call even before history
is loaded. And some of the downloads might have already existed in the
history DB. This will cause a failed sql insert LOG in the console. Although
the behavior is expected, it is not quite clean.

This CL fixes all above issue by loading the in-progress DB records and the
history DB records at the same time. Once CreateDownloadItem() is called,
DownloadManagerImpl will find the corresponding in-progress download and return
it to DownloadHistory, and calling OnDownloadCreated() at the same time.

BUG=898859

Change-Id: I0f1da0ecea99e72e8c5507ea005c56261a46f3cd
Reviewed-on: https://chromium-review.googlesource.com/c/1318692Reviewed-by: default avatarXing Liu <xingliu@chromium.org>
Commit-Queue: Min Qin <qinmin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#605900}
parent 0df82fcd
...@@ -229,6 +229,9 @@ DownloadManagerService::RetriveInProgressDownloadManager( ...@@ -229,6 +229,9 @@ DownloadManagerService::RetriveInProgressDownloadManager(
content::BrowserContext* context) { content::BrowserContext* context) {
if (in_progress_manager_) { if (in_progress_manager_) {
DCHECK(!context->IsOffTheRecord()); DCHECK(!context->IsOffTheRecord());
// Set |is_pending_downloads_loaded_| to false so that we need to wait for
// download history to initialize before performing new download actions.
is_pending_downloads_loaded_ = false;
return in_progress_manager_.release(); return in_progress_manager_.release();
} }
return nullptr; return nullptr;
...@@ -562,6 +565,10 @@ void DownloadManagerService::CreateInProgressDownloadManager() { ...@@ -562,6 +565,10 @@ void DownloadManagerService::CreateInProgressDownloadManager() {
} }
void DownloadManagerService::OnPendingDownloadsLoaded() { void DownloadManagerService::OnPendingDownloadsLoaded() {
// If |in_progress_manager_| is null, wait for DownloadHistory to initialize
// before performing any pending actions.
if (!in_progress_manager_ && !is_history_query_complete_)
return;
is_pending_downloads_loaded_ = true; is_pending_downloads_loaded_ = true;
for (auto iter = pending_actions_.begin(); iter != pending_actions_.end(); for (auto iter = pending_actions_.begin(); iter != pending_actions_.end();
++iter) { ++iter) {
......
...@@ -319,8 +319,7 @@ void DownloadHistory::LoadHistoryDownloads(std::unique_ptr<InfoVector> infos) { ...@@ -319,8 +319,7 @@ void DownloadHistory::LoadHistoryDownloads(std::unique_ptr<InfoVector> infos) {
ScheduleRemoveDownload(it->id); ScheduleRemoveDownload(it->id);
continue; continue;
} }
if (item->GetId() == loading_id_) DCHECK_EQ(download::DownloadItem::kInvalidId, loading_id_);
OnDownloadRestoredFromHistory(item);
// The download might have been completed or cancelled without informing // The download might have been completed or cancelled without informing
// history DB. If this is the case, populate the new state back to history // history DB. If this is the case, populate the new state back to history
......
...@@ -209,12 +209,9 @@ class DownloadHistoryTest : public testing::Test { ...@@ -209,12 +209,9 @@ class DownloadHistoryTest : public testing::Test {
return manager_observer_; return manager_observer_;
} }
// Creates the DownloadHistory. If |call_on_download_created| is false, // Creates the DownloadHistory. If |return_null_item| is true, |manager_|
// DownloadHistory::OnDownloadCreated() will not be called by |manager_|. // will return nullptr on CreateDownloadItem() call,
// If |return_null_item| is true, |manager_| will return nullptr on
// CreateDownloadItem() call,
void CreateDownloadHistory(std::unique_ptr<InfoVector> infos, void CreateDownloadHistory(std::unique_ptr<InfoVector> infos,
bool call_on_download_created = true,
bool return_null_item = false) { bool return_null_item = false) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
CHECK(infos.get()); CHECK(infos.get());
...@@ -235,28 +232,20 @@ class DownloadHistoryTest : public testing::Test { ...@@ -235,28 +232,20 @@ class DownloadHistoryTest : public testing::Test {
history::ToContentDownloadInterruptReason(row.interrupt_reason), history::ToContentDownloadInterruptReason(row.interrupt_reason),
row.opened, row.last_access_time, row.transient, row.opened, row.last_access_time, row.transient,
history::ToContentReceivedSlices(row.download_slice_info)); history::ToContentReceivedSlices(row.download_slice_info));
if (call_on_download_created) { if (return_null_item) {
EXPECT_CALL(manager(), MockCreateDownloadItem(adapter))
.WillOnce(Return(nullptr));
} else {
EXPECT_CALL(manager(), MockCreateDownloadItem(adapter)) EXPECT_CALL(manager(), MockCreateDownloadItem(adapter))
.WillOnce(DoAll( .WillOnce(DoAll(
InvokeWithoutArgs( InvokeWithoutArgs(
this, &DownloadHistoryTest::CallOnDownloadCreatedInOrder), this, &DownloadHistoryTest::CallOnDownloadCreatedInOrder),
Return(&item(index)))); Return(&item(index))));
} else {
download::DownloadItem* download =
return_null_item ? nullptr : &item(index);
EXPECT_CALL(manager(), MockCreateDownloadItem(adapter))
.WillOnce(Return(download));
} }
} }
history_ = new FakeHistoryAdapter(); history_ = new FakeHistoryAdapter();
history_->ExpectWillQueryDownloads(std::move(infos)); history_->ExpectWillQueryDownloads(std::move(infos));
if (call_on_download_created) { EXPECT_CALL(manager(), GetAllDownloads(_)).WillRepeatedly(Return());
EXPECT_CALL(manager(), GetAllDownloads(_)).WillRepeatedly(Return());
} else {
EXPECT_CALL(manager(), GetAllDownloads(_))
.WillRepeatedly(
WithArg<0>(Invoke(this, &DownloadHistoryTest::AddAllDownloads)));
}
download_history_.reset(new DownloadHistory( download_history_.reset(new DownloadHistory(
&manager(), &manager(),
std::unique_ptr<DownloadHistory::HistoryAdapter>(history_))); std::unique_ptr<DownloadHistory::HistoryAdapter>(history_)));
...@@ -892,7 +881,7 @@ TEST_F(DownloadHistoryTest, CreateHistoryItemInDownloadDB) { ...@@ -892,7 +881,7 @@ TEST_F(DownloadHistoryTest, CreateHistoryItemInDownloadDB) {
EXPECT_CALL(item(0), GetReceivedBytes()).WillRepeatedly(Return(50)); EXPECT_CALL(item(0), GetReceivedBytes()).WillRepeatedly(Return(50));
std::unique_ptr<InfoVector> infos(new InfoVector()); std::unique_ptr<InfoVector> infos(new InfoVector());
infos->push_back(info); infos->push_back(info);
CreateDownloadHistory(std::move(infos), false); CreateDownloadHistory(std::move(infos));
EXPECT_TRUE(DownloadHistory::IsPersisted(&item(0))); EXPECT_TRUE(DownloadHistory::IsPersisted(&item(0)));
// Modify the item, it should not trigger any updates. // Modify the item, it should not trigger any updates.
...@@ -924,7 +913,7 @@ TEST_F(DownloadHistoryTest, RemoveClearedItemFromHistory) { ...@@ -924,7 +913,7 @@ TEST_F(DownloadHistoryTest, RemoveClearedItemFromHistory) {
std::unique_ptr<InfoVector> infos(new InfoVector()); std::unique_ptr<InfoVector> infos(new InfoVector());
infos->push_back(info); infos->push_back(info);
CreateDownloadHistory(std::move(infos), false, true); CreateDownloadHistory(std::move(infos), true);
// The download should be removed from history afterwards. // The download should be removed from history afterwards.
IdSet ids; IdSet ids;
......
...@@ -548,15 +548,11 @@ void DownloadManagerImpl::OnInProgressDownloadManagerInitialized() { ...@@ -548,15 +548,11 @@ void DownloadManagerImpl::OnInProgressDownloadManagerInitialized() {
in_progress_downloads = in_progress_manager_->TakeInProgressDownloads(); in_progress_downloads = in_progress_manager_->TakeInProgressDownloads();
uint32_t max_id = download::DownloadItem::kInvalidId; uint32_t max_id = download::DownloadItem::kInvalidId;
for (auto& download : in_progress_downloads) { for (auto& download : in_progress_downloads) {
DCHECK(!base::ContainsKey(downloads_by_guid_, download->GetGuid())); uint32_t id = download->GetId();
// If this id is not unique, drop this download, this may happen due to a if (base::ContainsKey(in_progress_downloads_, id)) {
// previous history DB failure. See http://crbug.com/898859.
// TODO(qinmin): remove the downloaded files too if wasn't completed.
if (base::ContainsKey(downloads_, download->GetId())) {
in_progress_manager_->RemoveInProgressDownload(download->GetGuid()); in_progress_manager_->RemoveInProgressDownload(download->GetGuid());
continue; continue;
} }
uint32_t id = download->GetId();
if (id > max_id) if (id > max_id)
max_id = id; max_id = id;
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
...@@ -568,14 +564,7 @@ void DownloadManagerImpl::OnInProgressDownloadManagerInitialized() { ...@@ -568,14 +564,7 @@ void DownloadManagerImpl::OnInProgressDownloadManagerInitialized() {
continue; continue;
} }
#endif // defined(OS_ANDROID) #endif // defined(OS_ANDROID)
DownloadItemUtils::AttachInfo(download.get(), GetBrowserContext(), nullptr); in_progress_downloads_[id] = std::move(download);
download::DownloadItemImpl* item = download.get();
item->SetDelegate(this);
downloads_by_guid_[download->GetGuid()] = item;
downloads_[id] = std::move(download);
for (auto& observer : observers_)
observer.OnDownloadCreated(this, item);
DVLOG(20) << __func__ << "() download = " << item->DebugString(true);
} }
PostInitialization(DOWNLOAD_INITIALIZATION_DEPENDENCY_IN_PROGRESS_CACHE); PostInitialization(DOWNLOAD_INITIALIZATION_DEPENDENCY_IN_PROGRESS_CACHE);
SetNextId(max_id + 1); SetNextId(max_id + 1);
...@@ -724,11 +713,7 @@ void DownloadManagerImpl::CreateSavePackageDownloadItemWithId( ...@@ -724,11 +713,7 @@ void DownloadManagerImpl::CreateSavePackageDownloadItemWithId(
DownloadItemUtils::AttachInfo(download_item, GetBrowserContext(), DownloadItemUtils::AttachInfo(download_item, GetBrowserContext(),
WebContentsImpl::FromRenderFrameHostID( WebContentsImpl::FromRenderFrameHostID(
render_process_id, render_frame_id)); render_process_id, render_frame_id));
downloads_[download_item->GetId()] = base::WrapUnique(download_item); OnDownloadCreated(base::WrapUnique(download_item));
DCHECK(!base::ContainsKey(downloads_by_guid_, download_item->GetGuid()));
downloads_by_guid_[download_item->GetGuid()] = download_item;
for (auto& observer : observers_)
observer.OnDownloadCreated(this, download_item);
if (!item_created.is_null()) if (!item_created.is_null())
item_created.Run(download_item); item_created.Run(download_item);
} }
...@@ -964,6 +949,9 @@ download::DownloadItem* DownloadManagerImpl::CreateDownloadItem( ...@@ -964,6 +949,9 @@ download::DownloadItem* DownloadManagerImpl::CreateDownloadItem(
base::Time last_access_time, base::Time last_access_time,
bool transient, bool transient,
const std::vector<download::DownloadItem::ReceivedSlice>& received_slices) { const std::vector<download::DownloadItem::ReceivedSlice>& received_slices) {
// Retrive the in-progress download if it exists. Notice that this also
// removes it from |in_progress_downloads_|.
auto download = RetrieveInProgressDownload(id);
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
// On Android, there is no way to interact with cancelled or non-resumable // On Android, there is no way to interact with cancelled or non-resumable
// download. Simply returning null and don't store them in this class to // download. Simply returning null and don't store them in this class to
...@@ -977,29 +965,44 @@ download::DownloadItem* DownloadManagerImpl::CreateDownloadItem( ...@@ -977,29 +965,44 @@ download::DownloadItem* DownloadManagerImpl::CreateDownloadItem(
return nullptr; return nullptr;
} }
#endif #endif
if (base::ContainsKey(downloads_, id)) { if (download) {
// If a completed or cancelled download item is already in the history db, // If a completed or cancelled download item is already in the history db,
// remove it from the in-progress db. // remove it from the in-progress db.
if (state == download::DownloadItem::COMPLETE || if (state == download::DownloadItem::COMPLETE ||
state == download::DownloadItem::CANCELLED) { state == download::DownloadItem::CANCELLED) {
in_progress_manager_->RemoveInProgressDownload(guid); in_progress_manager_->RemoveInProgressDownload(guid);
} else { // Destroy |download| as we can create from the history instead.
return downloads_[id].get(); download.reset();
} }
} }
download::DownloadItemImpl* item = item_factory_->CreatePersistedItem( download::DownloadItemImpl* item = download.get();
this, guid, id, current_path, target_path, url_chain, referrer_url, if (item) {
site_url, tab_url, tab_refererr_url, mime_type, original_mime_type, item->SetDelegate(this);
start_time, end_time, etag, last_modified, received_bytes, total_bytes, DownloadItemUtils::AttachInfo(item, GetBrowserContext(), nullptr);
hash, state, danger_type, interrupt_reason, opened, last_access_time, OnDownloadCreated(std::move(download));
transient, received_slices); } else {
DownloadItemUtils::AttachInfo(item, GetBrowserContext(), nullptr); item = item_factory_->CreatePersistedItem(
downloads_[id] = base::WrapUnique(item); this, guid, id, current_path, target_path, url_chain, referrer_url,
downloads_by_guid_[guid] = item; site_url, tab_url, tab_refererr_url, mime_type, original_mime_type,
start_time, end_time, etag, last_modified, received_bytes, total_bytes,
hash, state, danger_type, interrupt_reason, opened, last_access_time,
transient, received_slices);
DownloadItemUtils::AttachInfo(item, GetBrowserContext(), nullptr);
OnDownloadCreated(base::WrapUnique(item));
}
return item;
}
void DownloadManagerImpl::OnDownloadCreated(
std::unique_ptr<download::DownloadItemImpl> download) {
DCHECK(!base::ContainsKey(downloads_, download->GetId()));
DCHECK(!base::ContainsKey(downloads_by_guid_, download->GetGuid()));
download::DownloadItemImpl* item = download.get();
downloads_[item->GetId()] = std::move(download);
downloads_by_guid_[item->GetGuid()] = item;
for (auto& observer : observers_) for (auto& observer : observers_)
observer.OnDownloadCreated(this, item); observer.OnDownloadCreated(this, item);
DVLOG(20) << __func__ << "() download = " << item->DebugString(true); DVLOG(20) << __func__ << "() download = " << item->DebugString(true);
return item;
} }
void DownloadManagerImpl::PostInitialization( void DownloadManagerImpl::PostInitialization(
...@@ -1030,7 +1033,9 @@ void DownloadManagerImpl::PostInitialization( ...@@ -1030,7 +1033,9 @@ void DownloadManagerImpl::PostInitialization(
// cache are initialized. // cache are initialized.
initialized_ = history_db_initialized_ && in_progress_cache_initialized_; initialized_ = history_db_initialized_ && in_progress_cache_initialized_;
if (initialized_) { if (!initialized_)
return;
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
for (const auto& guid : cleared_download_guids_on_startup_) for (const auto& guid : cleared_download_guids_on_startup_)
in_progress_manager_->RemoveInProgressDownload(guid); in_progress_manager_->RemoveInProgressDownload(guid);
...@@ -1046,9 +1051,19 @@ void DownloadManagerImpl::PostInitialization( ...@@ -1046,9 +1051,19 @@ void DownloadManagerImpl::PostInitialization(
interrupted_download_cleared_from_history_); interrupted_download_cleared_from_history_);
} }
#endif #endif
// If there are still downloads in |in_progress_downloads_|, import them
// now.
for (auto& download : in_progress_downloads_) {
auto item = std::move(download.second);
item->SetDelegate(this);
DownloadItemUtils::AttachInfo(item.get(), GetBrowserContext(), nullptr);
OnDownloadCreated(std::move(item));
}
in_progress_downloads_.clear();
for (auto& observer : observers_) for (auto& observer : observers_)
observer.OnManagerInitialized(); observer.OnManagerInitialized();
}
} }
bool DownloadManagerImpl::IsManagerInitialized() const { bool DownloadManagerImpl::IsManagerInitialized() const {
...@@ -1344,4 +1359,14 @@ bool DownloadManagerImpl::ShouldClearDownloadFromDB( ...@@ -1344,4 +1359,14 @@ bool DownloadManagerImpl::ShouldClearDownloadFromDB(
} }
#endif // defined(OS_ANDROID) #endif // defined(OS_ANDROID)
std::unique_ptr<download::DownloadItemImpl>
DownloadManagerImpl::RetrieveInProgressDownload(uint32_t id) {
if (base::ContainsKey(in_progress_downloads_, id)) {
auto download = std::move(in_progress_downloads_[id]);
in_progress_downloads_.erase(id);
return download;
}
return nullptr;
}
} // namespace content } // namespace content
...@@ -292,6 +292,13 @@ class CONTENT_EXPORT DownloadManagerImpl ...@@ -292,6 +292,13 @@ class CONTENT_EXPORT DownloadManagerImpl
// Whether |next_download_id_| is initialized. // Whether |next_download_id_| is initialized.
bool IsNextIdInitialized() const; bool IsNextIdInitialized() const;
// Called when a new download is created.
void OnDownloadCreated(std::unique_ptr<download::DownloadItemImpl> download);
// Retrieves a download from |in_progress_downloads_|.
std::unique_ptr<download::DownloadItemImpl> RetrieveInProgressDownload(
uint32_t id);
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
// Check whether a download should be cleared from history. On Android, // Check whether a download should be cleared from history. On Android,
// cancelled and non-resumable interrupted download will be cleaned up to // cancelled and non-resumable interrupted download will be cleaned up to
...@@ -367,6 +374,11 @@ class CONTENT_EXPORT DownloadManagerImpl ...@@ -367,6 +374,11 @@ class CONTENT_EXPORT DownloadManagerImpl
int cancelled_download_cleared_from_history_; int cancelled_download_cleared_from_history_;
int interrupted_download_cleared_from_history_; int interrupted_download_cleared_from_history_;
// In progress downloads returned by |in_progress_manager_| that are not yet
// added to |downloads_|.
std::unordered_map<uint32_t, std::unique_ptr<download::DownloadItemImpl>>
in_progress_downloads_;
// Callbacks to run once download ID is determined. // Callbacks to run once download ID is determined.
using IdCallbackVector = std::vector<std::unique_ptr<GetNextIdCallback>>; using IdCallbackVector = std::vector<std::unique_ptr<GetNextIdCallback>>;
IdCallbackVector id_callbacks_; IdCallbackVector id_callbacks_;
......
...@@ -521,6 +521,11 @@ class DownloadManagerTest : public testing::Test { ...@@ -521,6 +521,11 @@ class DownloadManagerTest : public testing::Test {
download_manager_->OnInProgressDownloadManagerInitialized(); download_manager_->OnInProgressDownloadManagerInitialized();
} }
void OnHistoryDBInitialized() {
download_manager_->PostInitialization(
DownloadManager::DOWNLOAD_INITIALIZATION_DEPENDENCY_HISTORY_DB);
}
void SetInProgressDownloadManager( void SetInProgressDownloadManager(
std::unique_ptr<download::InProgressDownloadManager> manager) { std::unique_ptr<download::InProgressDownloadManager> manager) {
download_manager_->in_progress_manager_ = std::move(manager); download_manager_->in_progress_manager_ = std::move(manager);
...@@ -746,8 +751,10 @@ TEST_F(DownloadManagerTest, OnInProgressDownloadsLoaded) { ...@@ -746,8 +751,10 @@ TEST_F(DownloadManagerTest, OnInProgressDownloadsLoaded) {
EXPECT_CALL(GetMockObserver(), OnDownloadCreated(download_manager_.get(), _)) EXPECT_CALL(GetMockObserver(), OnDownloadCreated(download_manager_.get(), _))
.WillOnce(Return()); .WillOnce(Return());
OnInProgressDownloadManagerInitialized(); OnInProgressDownloadManagerInitialized();
ASSERT_TRUE(download_manager_->GetDownloadByGuid(kGuid)); ASSERT_FALSE(download_manager_->GetDownloadByGuid(kGuid));
OnHistoryDBInitialized();
ASSERT_TRUE(download_manager_->GetDownloadByGuid(kGuid));
download::DownloadItem* download = download::DownloadItem* download =
download_manager_->GetDownloadByGuid(kGuid); download_manager_->GetDownloadByGuid(kGuid);
download->Remove(); download->Remove();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment