Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // DownloadHistory manages persisting DownloadItems to the history service by | |
| 6 // observing a single DownloadManager and all its DownloadItems using an | |
| 7 // AllDownloadItemNotifier. | |
| 8 // | |
| 9 // DownloadHistory decides whether and when to add items to, remove items from, | |
| 10 // and update items in the database. DownloadHistory uses DownloadHistoryData to | |
| 11 // store per-DownloadItem data such as its db_handle, whether the item is being | |
| 12 // added and waiting for its db_handle, and the last DownloadPersistentStoreInfo | |
| 13 // that was passed to the database. When the DownloadManager and its delegate | |
| 14 // (ChromeDownloadManagerDelegate) are initialized, DownloadHistory is created | |
| 15 // and queries the HistoryService. When the HistoryService calls back from | |
| 16 // QueryDownloads() to QueryCallback(), DownloadHistory uses | |
| 17 // DownloadManager::CreateDownloadItem() to inform DownloadManager of these | |
| 18 // persisted DownloadItems. CreateDownloadItem() internally calls | |
| 19 // OnDownloadCreated(), which normally adds items to the database, so | |
| 20 // QueryCallback() uses |loading_db_handle_| to disable adding these items to | |
| 21 // the database as it matches them up with their db_handles. If a download is | |
| 22 // removed via OnDownloadRemoved() while the item is still being added to the | |
| 23 // database, DownloadHistory uses |removed_while_adding_| to remember to remove | |
| 24 // the item when its ItemAdded() callback is called. All callbacks are bound | |
| 25 // with a weak pointer to DownloadHistory to prevent use-after-free bugs. | |
| 26 // ChromeDownloadManagerDelegate owns DownloadHistory, and deletes it in | |
| 27 // Shutdown(), which is called by DownloadManagerImpl::Shutdown() after all | |
| 28 // DownloadItems are destroyed. | |
| 29 // | |
| 30 // Strictly speaking, the weak pointers in the callbacks from the history system | |
| 31 // are redundant with the CancelableRequestConsumer. | |
| 32 // TODO(benjhayden) Use PostTaskAndReply with the weak pointers instead of | |
| 33 // CancelableRequestConsumer. This requires modifying the downloads-related | |
| 34 // portion of the HistoryService interface. | |
| 35 | |
| 5 #include "chrome/browser/download/download_history.h" | 36 #include "chrome/browser/download/download_history.h" |
| 6 | 37 |
| 7 #include "base/logging.h" | 38 #include "base/metrics/histogram.h" |
| 8 #include "chrome/browser/download/download_crx_util.h" | 39 #include "chrome/browser/download/download_crx_util.h" |
| 9 #include "chrome/browser/history/history_marshaling.h" | 40 #include "chrome/browser/history/download_database.h" |
| 10 #include "chrome/browser/history/history_service_factory.h" | 41 #include "chrome/browser/history/download_persistent_store_info.h" |
| 11 #include "chrome/browser/profiles/profile.h" | 42 #include "chrome/browser/history/history.h" |
| 43 #include "content/public/browser/browser_thread.h" | |
| 12 #include "content/public/browser/download_item.h" | 44 #include "content/public/browser/download_item.h" |
| 13 #include "content/public/browser/download_persistent_store_info.h" | 45 #include "content/public/browser/download_manager.h" |
| 14 | 46 |
| 15 using content::DownloadItem; | 47 namespace { |
| 16 using content::DownloadPersistentStoreInfo; | 48 |
| 17 | 49 // Per-DownloadItem data. This information does not belong inside DownloadItem, |
| 18 DownloadHistory::DownloadHistory(Profile* profile) | 50 // and keeping maps in DownloadHistory from DownloadItem to this information is |
| 19 : profile_(profile), | 51 // error-prone and complicated. Unfortunately, DownloadHistory::removing_*_ and |
| 20 next_fake_db_handle_(DownloadItem::kUninitializedHandle - 1) { | 52 // removed_while_adding_ cannot be moved into this class partly because |
| 21 DCHECK(profile); | 53 // DownloadHistoryData is destroyed when DownloadItems are destroyed, and we |
| 22 } | 54 // have no control over when DownloadItems are destroyed. |
| 23 | 55 class DownloadHistoryData : public base::SupportsUserData::Data { |
| 24 DownloadHistory::~DownloadHistory() {} | 56 public: |
| 25 | 57 static DownloadHistoryData* Get(content::DownloadItem* item) { |
| 26 void DownloadHistory::GetNextId( | 58 base::SupportsUserData::Data* data = item->GetUserData(kKey); |
| 27 const HistoryService::DownloadNextIdCallback& callback) { | 59 return (data == NULL) ? NULL : |
| 28 HistoryService* hs = HistoryServiceFactory::GetForProfile( | 60 static_cast<DownloadHistoryData*>(data); |
| 29 profile_, Profile::EXPLICIT_ACCESS); | 61 } |
| 30 if (!hs) | 62 |
| 31 return; | 63 DownloadHistoryData(content::DownloadItem* item, int64 handle) |
| 32 | 64 : is_adding_(false), |
| 33 hs->GetNextDownloadId(&history_consumer_, callback); | 65 db_handle_(handle), |
| 34 } | 66 info_(NULL) { |
| 35 | 67 item->SetUserData(kKey, this); |
| 36 void DownloadHistory::Load( | 68 } |
| 37 const HistoryService::DownloadQueryCallback& callback) { | 69 |
| 38 HistoryService* hs = HistoryServiceFactory::GetForProfile( | 70 virtual ~DownloadHistoryData() { |
| 39 profile_, Profile::EXPLICIT_ACCESS); | 71 } |
| 40 if (!hs) | 72 |
| 41 return; | 73 // Whether this item is currently being added to the database. |
| 42 | 74 bool is_adding() const { return is_adding_; } |
| 43 hs->QueryDownloads(&history_consumer_, callback); | 75 void set_is_adding(bool a) { is_adding_ = a; } |
| 44 | 76 |
| 45 // This is the initial load, so do a cleanup of corrupt in-progress entries. | 77 // Whether this item is already persisted in the database. |
| 46 hs->CleanUpInProgressEntries(); | 78 bool is_persisted() const { |
| 79 return db_handle_ != history::DownloadDatabase::kUninitializedHandle; | |
| 80 } | |
| 81 | |
| 82 int64 db_handle() const { return db_handle_; } | |
| 83 void set_db_handle(int64 h) { db_handle_ = h; } | |
| 84 | |
| 85 // This allows DownloadHistory::OnDownloadUpdated() to see what changed in a | |
| 86 // DownloadItem if anything, in order to prevent writing to the database | |
| 87 // unnecessarily. It is nullified when the item is no longer in progress in | |
| 88 // order to save memory. | |
| 89 DownloadPersistentStoreInfo* info() { return info_.get(); } | |
| 90 void set_info(const DownloadPersistentStoreInfo& i) { | |
| 91 info_.reset(new DownloadPersistentStoreInfo(i)); | |
| 92 } | |
| 93 void clear_info() { | |
| 94 info_.reset(); | |
| 95 } | |
| 96 | |
| 97 private: | |
| 98 static const char kKey[]; | |
| 99 | |
| 100 bool is_adding_; | |
| 101 int64 db_handle_; | |
| 102 scoped_ptr<DownloadPersistentStoreInfo> info_; | |
| 103 | |
| 104 DISALLOW_COPY_AND_ASSIGN(DownloadHistoryData); | |
| 105 }; | |
| 106 | |
| 107 const char DownloadHistoryData::kKey[] = | |
| 108 "DownloadItem DownloadHistoryData"; | |
| 109 | |
| 110 DownloadPersistentStoreInfo GetPersistentStoreInfo( | |
| 111 content::DownloadItem* item) { | |
| 112 DownloadHistoryData* data = DownloadHistoryData::Get(item); | |
| 113 return DownloadPersistentStoreInfo( | |
| 114 item->GetFullPath(), | |
| 115 item->GetURL(), | |
| 116 item->GetReferrerUrl(), | |
| 117 item->GetStartTime(), | |
| 118 item->GetEndTime(), | |
| 119 item->GetReceivedBytes(), | |
| 120 item->GetTotalBytes(), | |
| 121 item->GetState(), | |
| 122 ((data != NULL) ? data->db_handle() | |
| 123 : history::DownloadDatabase::kUninitializedHandle), | |
| 124 item->GetOpened()); | |
| 125 } | |
| 126 | |
| 127 bool ShouldUpdateHistory(const DownloadPersistentStoreInfo* previous, | |
| 128 const DownloadPersistentStoreInfo& current) { | |
| 129 // Ignore url, referrer, start_time, db_handle, which don't change. | |
| 130 return ((previous == NULL) || | |
| 131 (previous->path != current.path) || | |
| 132 (previous->end_time != current.end_time) || | |
| 133 (previous->received_bytes != current.received_bytes) || | |
| 134 (previous->total_bytes != current.total_bytes) || | |
| 135 (previous->state != current.state) || | |
| 136 (previous->opened != current.opened)); | |
| 137 } | |
| 138 | |
| 139 typedef std::vector<DownloadPersistentStoreInfo> InfoVector; | |
| 140 | |
| 141 } // anonymous namespace | |
| 142 | |
| 143 DownloadHistory::Observer::Observer() {} | |
| 144 DownloadHistory::Observer::~Observer() {} | |
| 145 | |
| 146 DownloadHistory::DownloadHistory( | |
| 147 content::DownloadManager* manager, | |
| 148 HistoryService* history) | |
| 149 : ALLOW_THIS_IN_INITIALIZER_LIST(notifier_(manager, this)), | |
| 150 history_(history), | |
| 151 loading_db_handle_(history::DownloadDatabase::kUninitializedHandle), | |
| 152 history_size_(0), | |
| 153 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { | |
| 154 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 155 content::DownloadManager::DownloadVector items; | |
| 156 notifier_.GetManager()->GetAllDownloads(&items); | |
| 157 for (content::DownloadManager::DownloadVector::const_iterator | |
| 158 it = items.begin(); it != items.end(); ++it) { | |
| 159 OnDownloadCreated(notifier_.GetManager(), *it); | |
| 160 } | |
| 161 history_->QueryDownloads(&history_consumer_, base::Bind( | |
| 162 &DownloadHistory::QueryCallback, weak_ptr_factory_.GetWeakPtr())); | |
| 163 } | |
| 164 | |
| 165 DownloadHistory::~DownloadHistory() { | |
| 166 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 167 } | |
| 168 | |
| 169 void DownloadHistory::AddObserver(DownloadHistory::Observer* observer) { | |
| 170 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 171 observers_.AddObserver(observer); | |
| 172 } | |
| 173 | |
| 174 void DownloadHistory::RemoveObserver(DownloadHistory::Observer* observer) { | |
| 175 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 176 observers_.RemoveObserver(observer); | |
| 47 } | 177 } |
| 48 | 178 |
| 49 void DownloadHistory::CheckVisitedReferrerBefore( | 179 void DownloadHistory::CheckVisitedReferrerBefore( |
| 50 int32 download_id, | |
| 51 const GURL& referrer_url, | 180 const GURL& referrer_url, |
| 52 const VisitedBeforeDoneCallback& callback) { | 181 const VisitedBeforeDoneCallback& callback) { |
| 53 if (referrer_url.is_valid()) { | 182 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| 54 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( | 183 if (!referrer_url.is_valid()) { |
| 55 profile_, Profile::EXPLICIT_ACCESS); | 184 callback.Run(false); |
| 56 if (hs) { | 185 return; |
| 57 HistoryService::Handle handle = | 186 } |
| 58 hs->GetVisibleVisitCountToHost(referrer_url, &history_consumer_, | 187 history_->GetVisibleVisitCountToHost( |
| 59 base::Bind(&DownloadHistory::OnGotVisitCountToHost, | 188 referrer_url, &history_consumer_, base::Bind( |
| 60 base::Unretained(this))); | 189 &DownloadHistory::OnGotVisitCountToHost, |
| 61 visited_before_requests_[handle] = callback; | 190 weak_ptr_factory_.GetWeakPtr(), callback)); |
| 62 return; | 191 } |
| 63 } | 192 |
| 64 } | 193 void DownloadHistory::OnGotVisitCountToHost( |
| 65 callback.Run(false); | 194 const VisitedBeforeDoneCallback& callback, |
| 66 } | 195 HistoryService::Handle unused_handle, |
| 67 | 196 bool found_visits, |
| 68 void DownloadHistory::AddEntry( | 197 int count, |
| 69 DownloadItem* download_item, | 198 base::Time first_visit) { |
| 70 const HistoryService::DownloadCreateCallback& callback) { | 199 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| 71 DCHECK(download_item); | |
| 72 // Do not store the download in the history database for a few special cases: | |
| 73 // - incognito mode (that is the point of this mode) | |
| 74 // - extensions (users don't think of extension installation as 'downloading') | |
| 75 // - temporary download, like in drag-and-drop | |
| 76 // - history service is not available (e.g. in tests) | |
| 77 // We have to make sure that these handles don't collide with normal db | |
| 78 // handles, so we use a negative value. Eventually, they could overlap, but | |
| 79 // you'd have to do enough downloading that your ISP would likely stab you in | |
| 80 // the neck first. YMMV. | |
| 81 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( | |
| 82 profile_, Profile::EXPLICIT_ACCESS); | |
| 83 if (download_crx_util::IsExtensionDownload(*download_item) || | |
| 84 download_item->IsTemporary() || !hs) { | |
| 85 callback.Run(download_item->GetId(), GetNextFakeDbHandle()); | |
| 86 return; | |
| 87 } | |
| 88 | |
| 89 int32 id = download_item->GetId(); | |
| 90 DownloadPersistentStoreInfo history_info = | |
| 91 download_item->GetPersistentStoreInfo(); | |
| 92 hs->CreateDownload(id, history_info, &history_consumer_, callback); | |
| 93 } | |
| 94 | |
| 95 void DownloadHistory::UpdateEntry(DownloadItem* download_item) { | |
| 96 // Don't store info in the database if the download was initiated while in | |
| 97 // incognito mode or if it hasn't been initialized in our database table. | |
| 98 if (download_item->GetDbHandle() <= DownloadItem::kUninitializedHandle) | |
| 99 return; | |
| 100 | |
| 101 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( | |
| 102 profile_, Profile::EXPLICIT_ACCESS); | |
| 103 if (!hs) | |
| 104 return; | |
| 105 hs->UpdateDownload(download_item->GetPersistentStoreInfo()); | |
| 106 } | |
| 107 | |
| 108 void DownloadHistory::UpdateDownloadPath(DownloadItem* download_item, | |
| 109 const FilePath& new_path) { | |
| 110 // No update necessary if the download was initiated while in incognito mode. | |
| 111 if (download_item->GetDbHandle() <= DownloadItem::kUninitializedHandle) | |
| 112 return; | |
| 113 | |
| 114 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( | |
| 115 profile_, Profile::EXPLICIT_ACCESS); | |
| 116 if (hs) | |
| 117 hs->UpdateDownloadPath(new_path, download_item->GetDbHandle()); | |
| 118 } | |
| 119 | |
| 120 void DownloadHistory::RemoveEntry(DownloadItem* download_item) { | |
| 121 // No update necessary if the download was initiated while in incognito mode. | |
| 122 if (download_item->GetDbHandle() <= DownloadItem::kUninitializedHandle) | |
| 123 return; | |
| 124 | |
| 125 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( | |
| 126 profile_, Profile::EXPLICIT_ACCESS); | |
| 127 if (hs) | |
| 128 hs->RemoveDownload(download_item->GetDbHandle()); | |
| 129 } | |
| 130 | |
| 131 void DownloadHistory::RemoveEntriesBetween(const base::Time remove_begin, | |
| 132 const base::Time remove_end) { | |
| 133 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( | |
| 134 profile_, Profile::EXPLICIT_ACCESS); | |
| 135 if (hs) | |
| 136 hs->RemoveDownloadsBetween(remove_begin, remove_end); | |
| 137 } | |
| 138 | |
| 139 int64 DownloadHistory::GetNextFakeDbHandle() { | |
| 140 return next_fake_db_handle_--; | |
| 141 } | |
| 142 | |
| 143 void DownloadHistory::OnGotVisitCountToHost(HistoryService::Handle handle, | |
| 144 bool found_visits, | |
| 145 int count, | |
| 146 base::Time first_visit) { | |
| 147 VisitedBeforeRequestsMap::iterator request = | |
| 148 visited_before_requests_.find(handle); | |
| 149 DCHECK(request != visited_before_requests_.end()); | |
| 150 VisitedBeforeDoneCallback callback = request->second; | |
| 151 visited_before_requests_.erase(request); | |
| 152 callback.Run(found_visits && count && | 200 callback.Run(found_visits && count && |
| 153 (first_visit.LocalMidnight() < base::Time::Now().LocalMidnight())); | 201 (first_visit.LocalMidnight() < base::Time::Now().LocalMidnight())); |
| 154 } | 202 } |
| 203 | |
| 204 void DownloadHistory::QueryCallback(InfoVector* infos) { | |
| 205 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 206 // ManagerGoingDown() may have happened before the history loaded. | |
| 207 if (!notifier_.GetManager()) | |
| 208 return; | |
| 209 for (InfoVector::const_iterator it = infos->begin(); | |
| 210 it != infos->end(); ++it) { | |
| 211 // OnDownloadCreated() is called inside DM::CreateDownloadItem(), so set | |
| 212 // loading_db_handle_ to match up the created item with its db_handle. All | |
| 213 // methods run on the UI thread and CreateDownloadItem() is synchronous. | |
| 214 loading_db_handle_ = it->db_handle; | |
| 215 content::DownloadItem* download_item = | |
| 216 notifier_.GetManager()->CreateDownloadItem( | |
| 217 it->path, | |
| 218 it->url, | |
| 219 it->referrer_url, | |
| 220 it->start_time, | |
| 221 it->end_time, | |
| 222 it->received_bytes, | |
| 223 it->total_bytes, | |
| 224 it->state, | |
| 225 it->opened); | |
| 226 DownloadHistoryData* data = DownloadHistoryData::Get(download_item); | |
| 227 | |
| 228 // If this DCHECK fails, then you probably added an Observer that | |
| 229 // synchronously creates a DownloadItem in response to | |
| 230 // DownloadManager::OnDownloadCreated(), and your observer runs before | |
| 231 // DownloadHistory, and DownloadManager creates items synchronously. Just | |
| 232 // bounce your DownloadItem creation off the message loop to flush | |
| 233 // DownloadHistory::OnDownloadCreated. | |
| 234 DCHECK_EQ(it->db_handle, data->db_handle()); | |
| 235 ++history_size_; | |
| 236 } | |
| 237 notifier_.GetManager()->CheckForHistoryFilesRemoval(); | |
| 238 } | |
| 239 | |
| 240 void DownloadHistory::MaybeAddToHistory(content::DownloadItem* item) { | |
| 241 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 242 | |
| 243 int32 download_id = item->GetId(); | |
| 244 DownloadHistoryData* data = DownloadHistoryData::Get(item); | |
| 245 bool removing = (removing_handles_.find(data->db_handle()) != | |
| 246 removing_handles_.end()); | |
| 247 | |
| 248 // TODO(benjhayden): Remove IsTemporary(). | |
| 249 if (download_crx_util::IsExtensionDownload(*item) || | |
| 250 item->IsTemporary() || | |
| 251 data->is_adding() || | |
| 252 data->is_persisted() || | |
| 253 removing) | |
| 254 return; | |
| 255 | |
| 256 data->set_is_adding(true); | |
| 257 if (data->info() == NULL) { | |
| 258 // Keep the info here regardless of whether the item is in progress so that, | |
| 259 // when ItemAdded() calls OnDownloadUpdated(), it can decide whether to | |
| 260 // Update the db and/or clear the info. | |
| 261 data->set_info(GetPersistentStoreInfo(item)); | |
| 262 } | |
| 263 | |
| 264 history_->CreateDownload(*data->info(), &history_consumer_, base::Bind( | |
| 265 &DownloadHistory::ItemAdded, weak_ptr_factory_.GetWeakPtr(), | |
| 266 download_id)); | |
| 267 } | |
| 268 | |
| 269 void DownloadHistory::ItemAdded(int32 download_id, int64 db_handle) { | |
|
asanka
2012/11/02 19:56:57
If CreateDownload() failed, then db_handle == 0. D
benjhayden
2012/11/06 20:01:14
Yes and no.
I made DownloadDatabase::CreateDownloa
| |
| 270 if (removed_while_adding_.find(download_id) != | |
| 271 removed_while_adding_.end()) { | |
| 272 removed_while_adding_.erase(download_id); | |
| 273 ScheduleRemoveDownload(download_id, db_handle); | |
| 274 return; | |
| 275 } | |
| 276 | |
| 277 if (!notifier_.GetManager()) | |
| 278 return; | |
| 279 | |
| 280 content::DownloadItem* item = notifier_.GetManager()->GetDownload( | |
| 281 download_id); | |
| 282 if (!item) { | |
| 283 // This item will have called OnDownloadDestroyed(). If the item should | |
| 284 // have been removed from history, then it would have also called | |
| 285 // OnDownloadRemoved(), which would have put |download_id| in | |
| 286 // removed_while_adding_, handled above. | |
| 287 return; | |
| 288 } | |
| 289 | |
| 290 UMA_HISTOGRAM_CUSTOM_COUNTS("Download.HistorySize2", | |
| 291 history_size_, | |
| 292 0/*min*/, | |
| 293 (1 << 23)/*max*/, | |
| 294 (1 << 7)/*num_buckets*/); | |
| 295 ++history_size_; | |
| 296 | |
| 297 DownloadHistoryData* data = DownloadHistoryData::Get(item); | |
| 298 data->set_is_adding(false); | |
| 299 DCHECK_NE(db_handle, history::DownloadDatabase::kUninitializedHandle); | |
| 300 data->set_db_handle(db_handle); | |
| 301 | |
| 302 // Send to observers the actual DownloadPersistentStoreInfo that was sent to | |
| 303 // the db, plus the db_handle, instead of completely regenerating the | |
| 304 // DownloadPersistentStoreInfo, in order to accurately reflect the contents of | |
| 305 // the database. | |
| 306 data->info()->db_handle = db_handle; | |
| 307 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored( | |
| 308 item, *data->info())); | |
| 309 | |
| 310 // In case the item changed or became temporary while it was being added. | |
| 311 // Don't just update all of the item's observers because we're the only | |
| 312 // observer that can also see db_handle, which is the only thing that | |
| 313 // ItemAdded changed. | |
| 314 OnDownloadUpdated(notifier_.GetManager(), item); | |
| 315 } | |
| 316 | |
| 317 void DownloadHistory::OnDownloadCreated( | |
| 318 content::DownloadManager* manager, content::DownloadItem* item) { | |
| 319 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 320 | |
| 321 // All downloads should pass through OnDownloadCreated exactly once. | |
| 322 CHECK(!DownloadHistoryData::Get(item)); | |
| 323 DownloadHistoryData* data = new DownloadHistoryData(item, loading_db_handle_); | |
| 324 loading_db_handle_ = history::DownloadDatabase::kUninitializedHandle; | |
| 325 if (item->GetState() == content::DownloadItem::IN_PROGRESS) { | |
| 326 data->set_info(GetPersistentStoreInfo(item)); | |
| 327 } | |
| 328 MaybeAddToHistory(item); | |
| 329 } | |
| 330 | |
| 331 void DownloadHistory::OnDownloadUpdated( | |
| 332 content::DownloadManager* manager, content::DownloadItem* item) { | |
| 333 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 334 | |
| 335 DownloadHistoryData* data = DownloadHistoryData::Get(item); | |
| 336 if (!data->is_persisted()) { | |
| 337 MaybeAddToHistory(item); | |
| 338 return; | |
| 339 } | |
| 340 if (item->IsTemporary()) { | |
| 341 OnDownloadRemoved(notifier_.GetManager(), item); | |
| 342 return; | |
| 343 } | |
| 344 | |
| 345 // TODO(asanka): Persist GetTargetFilePath() as well. | |
| 346 DownloadPersistentStoreInfo current_info(GetPersistentStoreInfo(item)); | |
| 347 bool should_update = ShouldUpdateHistory(data->info(), current_info); | |
| 348 UMA_HISTOGRAM_ENUMERATION("Download.HistoryPropagatedUpdate", | |
| 349 should_update, 2); | |
| 350 if (should_update) { | |
| 351 history_->UpdateDownload(current_info); | |
| 352 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored( | |
| 353 item, current_info)); | |
| 354 } | |
| 355 if (item->GetState() == content::DownloadItem::IN_PROGRESS) { | |
| 356 data->set_info(current_info); | |
| 357 } else { | |
| 358 data->clear_info(); | |
| 359 } | |
| 360 } | |
| 361 | |
| 362 void DownloadHistory::OnDownloadOpened( | |
| 363 content::DownloadManager* manager, content::DownloadItem* item) { | |
| 364 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 365 DownloadHistoryData* data = DownloadHistoryData::Get(item); | |
| 366 if (!data->is_persisted()) { | |
| 367 MaybeAddToHistory(item); | |
| 368 return; | |
| 369 } | |
| 370 if (item->IsTemporary()) { | |
| 371 OnDownloadRemoved(manager, item); | |
| 372 return; | |
| 373 } | |
| 374 // Downloads may be opened after they are completed, so don't rely on | |
| 375 // DownloadHistoryData::info(). | |
| 376 | |
| 377 DownloadPersistentStoreInfo current_info(GetPersistentStoreInfo(item)); | |
| 378 history_->UpdateDownload(current_info); | |
| 379 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored(item, current_info)); | |
| 380 } | |
| 381 | |
| 382 void DownloadHistory::OnDownloadRemoved( | |
| 383 content::DownloadManager* manager, content::DownloadItem* item) { | |
| 384 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 385 | |
| 386 DownloadHistoryData* data = DownloadHistoryData::Get(item); | |
| 387 if (!data->is_persisted()) { | |
| 388 if (data->is_adding()) { | |
| 389 // ScheduleRemoveDownload will be called when history_ calls ItemAdded(). | |
| 390 removed_while_adding_.insert(item->GetId()); | |
| 391 } | |
| 392 return; | |
| 393 } | |
| 394 ScheduleRemoveDownload(item->GetId(), data->db_handle()); | |
| 395 data->set_db_handle(history::DownloadDatabase::kUninitializedHandle); | |
| 396 // ItemAdded increments history_size_ only if the item wasn't | |
| 397 // removed_while_adding_, so the next line does not belong in | |
| 398 // ScheduleRemoveDownload(). | |
| 399 --history_size_; | |
| 400 } | |
| 401 | |
| 402 void DownloadHistory::ScheduleRemoveDownload( | |
| 403 int32 download_id, int64 db_handle) { | |
| 404 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 405 | |
| 406 // For database efficiency, batch removals together if they happen all at | |
| 407 // once. | |
| 408 if (removing_handles_.empty()) { | |
| 409 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, | |
| 410 base::Bind(&DownloadHistory::RemoveDownloadsBatch, | |
| 411 weak_ptr_factory_.GetWeakPtr())); | |
| 412 } | |
| 413 removing_handles_.insert(db_handle); | |
| 414 removing_ids_.insert(download_id); | |
| 415 } | |
| 416 | |
| 417 void DownloadHistory::RemoveDownloadsBatch() { | |
| 418 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 419 HandleSet remove_handles; | |
| 420 IdSet remove_ids; | |
| 421 removing_handles_.swap(remove_handles); | |
| 422 removing_ids_.swap(remove_ids); | |
| 423 history_->RemoveDownloads(remove_handles); | |
| 424 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadsRemoved(remove_ids)); | |
| 425 } | |
| OLD | NEW |