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 3 // found in the LICENSE file. 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 whether the item is persisted or being 12 // persisted, and the last history::DownloadRow that was passed to the database. 13 // When the DownloadManager and its delegate (ChromeDownloadManagerDelegate) are 14 // initialized, DownloadHistory is created and queries the HistoryService. When 15 // the HistoryService calls back from QueryDownloads() to QueryCallback(), 16 // DownloadHistory uses DownloadManager::CreateDownloadItem() to inform 17 // DownloadManager of these persisted DownloadItems. CreateDownloadItem() 18 // internally calls OnDownloadCreated(), which normally adds items to the 19 // database, so QueryCallback() uses |loading_id_| to disable adding these items 20 // to the database. If a download is removed via OnDownloadRemoved() while the 21 // item is still being added to the database, DownloadHistory uses 22 // |removed_while_adding_| to remember to remove the item when its ItemAdded() 23 // callback is called. All callbacks are bound with a weak pointer to 24 // DownloadHistory to prevent use-after-free bugs. 25 // ChromeDownloadManagerDelegate owns DownloadHistory, and deletes it in 26 // Shutdown(), which is called by DownloadManagerImpl::Shutdown() after all 27 // DownloadItems are destroyed. 28 29 #include "chrome/browser/download/download_history.h" 30 31 #include "base/metrics/histogram.h" 32 #include "chrome/browser/download/download_crx_util.h" 33 #include "chrome/browser/history/download_database.h" 34 #include "chrome/browser/history/download_row.h" 35 #include "chrome/browser/history/history_service.h" 36 #include "content/public/browser/browser_thread.h" 37 #include "content/public/browser/download_item.h" 38 #include "content/public/browser/download_manager.h" 39 40 #if !defined(OS_ANDROID) 41 #include "chrome/browser/extensions/api/downloads/downloads_api.h" 42 #endif 43 44 namespace { 45 46 // Per-DownloadItem data. This information does not belong inside DownloadItem, 47 // and keeping maps in DownloadHistory from DownloadItem to this information is 48 // error-prone and complicated. Unfortunately, DownloadHistory::removing_*_ and 49 // removed_while_adding_ cannot be moved into this class partly because 50 // DownloadHistoryData is destroyed when DownloadItems are destroyed, and we 51 // have no control over when DownloadItems are destroyed. 52 class DownloadHistoryData : public base::SupportsUserData::Data { 53 public: 54 enum PersistenceState { 55 NOT_PERSISTED, 56 PERSISTING, 57 PERSISTED, 58 }; 59 60 static DownloadHistoryData* Get(content::DownloadItem* item) { 61 base::SupportsUserData::Data* data = item->GetUserData(kKey); 62 return (data == NULL) ? NULL : 63 static_cast<DownloadHistoryData*>(data); 64 } 65 66 explicit DownloadHistoryData(content::DownloadItem* item) 67 : state_(NOT_PERSISTED) { 68 item->SetUserData(kKey, this); 69 } 70 71 virtual ~DownloadHistoryData() { 72 } 73 74 PersistenceState state() const { return state_; } 75 void SetState(PersistenceState s) { state_ = s; } 76 77 // This allows DownloadHistory::OnDownloadUpdated() to see what changed in a 78 // DownloadItem if anything, in order to prevent writing to the database 79 // unnecessarily. It is nullified when the item is no longer in progress in 80 // order to save memory. 81 history::DownloadRow* info() { return info_.get(); } 82 void set_info(const history::DownloadRow& i) { 83 info_.reset(new history::DownloadRow(i)); 84 } 85 void clear_info() { 86 info_.reset(); 87 } 88 89 private: 90 static const char kKey[]; 91 92 PersistenceState state_; 93 scoped_ptr<history::DownloadRow> info_; 94 95 DISALLOW_COPY_AND_ASSIGN(DownloadHistoryData); 96 }; 97 98 const char DownloadHistoryData::kKey[] = 99 "DownloadItem DownloadHistoryData"; 100 101 history::DownloadRow GetDownloadRow( 102 content::DownloadItem* item) { 103 std::string by_ext_id, by_ext_name; 104 #if !defined(OS_ANDROID) 105 DownloadedByExtension* by_ext = DownloadedByExtension::Get(item); 106 if (by_ext) { 107 by_ext_id = by_ext->id(); 108 by_ext_name = by_ext->name(); 109 } 110 #endif 111 112 return history::DownloadRow( 113 item->GetFullPath(), 114 item->GetTargetFilePath(), 115 item->GetUrlChain(), 116 item->GetReferrerUrl(), 117 item->GetStartTime(), 118 item->GetEndTime(), 119 item->GetETag(), 120 item->GetLastModifiedTime(), 121 item->GetReceivedBytes(), 122 item->GetTotalBytes(), 123 item->GetState(), 124 item->GetDangerType(), 125 item->GetLastReason(), 126 item->GetId(), 127 item->GetOpened(), 128 by_ext_id, 129 by_ext_name); 130 } 131 132 bool ShouldUpdateHistory(const history::DownloadRow* previous, 133 const history::DownloadRow& current) { 134 // Ignore url, referrer, start_time, id, which don't change. 135 return ((previous == NULL) || 136 (previous->current_path != current.current_path) || 137 (previous->target_path != current.target_path) || 138 (previous->end_time != current.end_time) || 139 (previous->received_bytes != current.received_bytes) || 140 (previous->total_bytes != current.total_bytes) || 141 (previous->etag != current.etag) || 142 (previous->last_modified != current.last_modified) || 143 (previous->state != current.state) || 144 (previous->danger_type != current.danger_type) || 145 (previous->interrupt_reason != current.interrupt_reason) || 146 (previous->opened != current.opened) || 147 (previous->by_ext_id != current.by_ext_id) || 148 (previous->by_ext_name != current.by_ext_name)); 149 } 150 151 typedef std::vector<history::DownloadRow> InfoVector; 152 153 } // anonymous namespace 154 155 DownloadHistory::HistoryAdapter::HistoryAdapter(HistoryService* history) 156 : history_(history) { 157 } 158 DownloadHistory::HistoryAdapter::~HistoryAdapter() {} 159 160 void DownloadHistory::HistoryAdapter::QueryDownloads( 161 const HistoryService::DownloadQueryCallback& callback) { 162 history_->QueryDownloads(callback); 163 } 164 165 void DownloadHistory::HistoryAdapter::CreateDownload( 166 const history::DownloadRow& info, 167 const HistoryService::DownloadCreateCallback& callback) { 168 history_->CreateDownload(info, callback); 169 } 170 171 void DownloadHistory::HistoryAdapter::UpdateDownload( 172 const history::DownloadRow& data) { 173 history_->UpdateDownload(data); 174 } 175 176 void DownloadHistory::HistoryAdapter::RemoveDownloads( 177 const std::set<uint32>& ids) { 178 history_->RemoveDownloads(ids); 179 } 180 181 182 DownloadHistory::Observer::Observer() {} 183 DownloadHistory::Observer::~Observer() {} 184 185 bool DownloadHistory::IsPersisted(content::DownloadItem* item) { 186 DownloadHistoryData* data = DownloadHistoryData::Get(item); 187 return data && (data->state() == DownloadHistoryData::PERSISTED); 188 } 189 190 DownloadHistory::DownloadHistory(content::DownloadManager* manager, 191 scoped_ptr<HistoryAdapter> history) 192 : notifier_(manager, this), 193 history_(history.Pass()), 194 loading_id_(content::DownloadItem::kInvalidId), 195 history_size_(0), 196 weak_ptr_factory_(this) { 197 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 198 content::DownloadManager::DownloadVector items; 199 notifier_.GetManager()->GetAllDownloads(&items); 200 for (content::DownloadManager::DownloadVector::const_iterator 201 it = items.begin(); it != items.end(); ++it) { 202 OnDownloadCreated(notifier_.GetManager(), *it); 203 } 204 history_->QueryDownloads(base::Bind( 205 &DownloadHistory::QueryCallback, weak_ptr_factory_.GetWeakPtr())); 206 } 207 208 DownloadHistory::~DownloadHistory() { 209 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 210 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadHistoryDestroyed()); 211 observers_.Clear(); 212 } 213 214 void DownloadHistory::AddObserver(DownloadHistory::Observer* observer) { 215 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 216 observers_.AddObserver(observer); 217 } 218 219 void DownloadHistory::RemoveObserver(DownloadHistory::Observer* observer) { 220 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 221 observers_.RemoveObserver(observer); 222 } 223 224 void DownloadHistory::QueryCallback(scoped_ptr<InfoVector> infos) { 225 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 226 // ManagerGoingDown() may have happened before the history loaded. 227 if (!notifier_.GetManager()) 228 return; 229 for (InfoVector::const_iterator it = infos->begin(); 230 it != infos->end(); ++it) { 231 loading_id_ = it->id; 232 content::DownloadItem* item = notifier_.GetManager()->CreateDownloadItem( 233 loading_id_, 234 it->current_path, 235 it->target_path, 236 it->url_chain, 237 it->referrer_url, 238 it->start_time, 239 it->end_time, 240 it->etag, 241 it->last_modified, 242 it->received_bytes, 243 it->total_bytes, 244 it->state, 245 it->danger_type, 246 it->interrupt_reason, 247 it->opened); 248 #if !defined(OS_ANDROID) 249 if (!it->by_ext_id.empty() && !it->by_ext_name.empty()) { 250 new DownloadedByExtension(item, it->by_ext_id, it->by_ext_name); 251 item->UpdateObservers(); 252 } 253 #endif 254 DCHECK_EQ(DownloadHistoryData::Get(item)->state(), 255 DownloadHistoryData::PERSISTED); 256 ++history_size_; 257 } 258 notifier_.GetManager()->CheckForHistoryFilesRemoval(); 259 } 260 261 void DownloadHistory::MaybeAddToHistory(content::DownloadItem* item) { 262 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 263 264 uint32 download_id = item->GetId(); 265 DownloadHistoryData* data = DownloadHistoryData::Get(item); 266 bool removing = removing_ids_.find(download_id) != removing_ids_.end(); 267 268 // TODO(benjhayden): Remove IsTemporary(). 269 if (download_crx_util::IsExtensionDownload(*item) || 270 item->IsTemporary() || 271 (data->state() != DownloadHistoryData::NOT_PERSISTED) || 272 removing) 273 return; 274 275 data->SetState(DownloadHistoryData::PERSISTING); 276 if (data->info() == NULL) { 277 // Keep the info here regardless of whether the item is in progress so that, 278 // when ItemAdded() calls OnDownloadUpdated(), it can decide whether to 279 // Update the db and/or clear the info. 280 data->set_info(GetDownloadRow(item)); 281 } 282 283 history_->CreateDownload(*data->info(), base::Bind( 284 &DownloadHistory::ItemAdded, weak_ptr_factory_.GetWeakPtr(), 285 download_id)); 286 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored( 287 item, *data->info())); 288 } 289 290 void DownloadHistory::ItemAdded(uint32 download_id, bool success) { 291 if (removed_while_adding_.find(download_id) != 292 removed_while_adding_.end()) { 293 removed_while_adding_.erase(download_id); 294 if (success) 295 ScheduleRemoveDownload(download_id); 296 return; 297 } 298 299 if (!notifier_.GetManager()) 300 return; 301 302 content::DownloadItem* item = notifier_.GetManager()->GetDownload( 303 download_id); 304 if (!item) { 305 // This item will have called OnDownloadDestroyed(). If the item should 306 // have been removed from history, then it would have also called 307 // OnDownloadRemoved(), which would have put |download_id| in 308 // removed_while_adding_, handled above. 309 return; 310 } 311 312 DownloadHistoryData* data = DownloadHistoryData::Get(item); 313 314 // The sql INSERT statement failed. Avoid an infinite loop: don't 315 // automatically retry. Retry adding the next time the item is updated by 316 // resetting the state to NOT_PERSISTED. 317 if (!success) { 318 DVLOG(20) << __FUNCTION__ << " INSERT failed id=" << download_id; 319 data->SetState(DownloadHistoryData::NOT_PERSISTED); 320 return; 321 } 322 data->SetState(DownloadHistoryData::PERSISTED); 323 324 UMA_HISTOGRAM_CUSTOM_COUNTS("Download.HistorySize2", 325 history_size_, 326 0/*min*/, 327 (1 << 23)/*max*/, 328 (1 << 7)/*num_buckets*/); 329 ++history_size_; 330 331 // In case the item changed or became temporary while it was being added. 332 // Don't just update all of the item's observers because we're the only 333 // observer that can also see data->state(), which is the only thing that 334 // ItemAdded() changed. 335 OnDownloadUpdated(notifier_.GetManager(), item); 336 } 337 338 void DownloadHistory::OnDownloadCreated( 339 content::DownloadManager* manager, content::DownloadItem* item) { 340 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 341 342 // All downloads should pass through OnDownloadCreated exactly once. 343 CHECK(!DownloadHistoryData::Get(item)); 344 DownloadHistoryData* data = new DownloadHistoryData(item); 345 if (item->GetId() == loading_id_) { 346 data->SetState(DownloadHistoryData::PERSISTED); 347 loading_id_ = content::DownloadItem::kInvalidId; 348 } 349 if (item->GetState() == content::DownloadItem::IN_PROGRESS) { 350 data->set_info(GetDownloadRow(item)); 351 } 352 MaybeAddToHistory(item); 353 } 354 355 void DownloadHistory::OnDownloadUpdated( 356 content::DownloadManager* manager, content::DownloadItem* item) { 357 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 358 359 DownloadHistoryData* data = DownloadHistoryData::Get(item); 360 if (data->state() == DownloadHistoryData::NOT_PERSISTED) { 361 MaybeAddToHistory(item); 362 return; 363 } 364 if (item->IsTemporary()) { 365 OnDownloadRemoved(notifier_.GetManager(), item); 366 return; 367 } 368 369 history::DownloadRow current_info(GetDownloadRow(item)); 370 bool should_update = ShouldUpdateHistory(data->info(), current_info); 371 UMA_HISTOGRAM_ENUMERATION("Download.HistoryPropagatedUpdate", 372 should_update, 2); 373 if (should_update) { 374 history_->UpdateDownload(current_info); 375 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored( 376 item, current_info)); 377 } 378 if (item->GetState() == content::DownloadItem::IN_PROGRESS) { 379 data->set_info(current_info); 380 } else { 381 data->clear_info(); 382 } 383 } 384 385 void DownloadHistory::OnDownloadOpened( 386 content::DownloadManager* manager, content::DownloadItem* item) { 387 OnDownloadUpdated(manager, item); 388 } 389 390 void DownloadHistory::OnDownloadRemoved( 391 content::DownloadManager* manager, content::DownloadItem* item) { 392 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 393 394 DownloadHistoryData* data = DownloadHistoryData::Get(item); 395 if (data->state() != DownloadHistoryData::PERSISTED) { 396 if (data->state() == DownloadHistoryData::PERSISTING) { 397 // ScheduleRemoveDownload will be called when history_ calls ItemAdded(). 398 removed_while_adding_.insert(item->GetId()); 399 } 400 return; 401 } 402 ScheduleRemoveDownload(item->GetId()); 403 // This is important: another OnDownloadRemoved() handler could do something 404 // that synchronously fires an OnDownloadUpdated(). 405 data->SetState(DownloadHistoryData::NOT_PERSISTED); 406 // ItemAdded increments history_size_ only if the item wasn't 407 // removed_while_adding_, so the next line does not belong in 408 // ScheduleRemoveDownload(). 409 --history_size_; 410 } 411 412 void DownloadHistory::ScheduleRemoveDownload(uint32 download_id) { 413 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 414 415 // For database efficiency, batch removals together if they happen all at 416 // once. 417 if (removing_ids_.empty()) { 418 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 419 base::Bind(&DownloadHistory::RemoveDownloadsBatch, 420 weak_ptr_factory_.GetWeakPtr())); 421 } 422 removing_ids_.insert(download_id); 423 } 424 425 void DownloadHistory::RemoveDownloadsBatch() { 426 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 427 IdSet remove_ids; 428 removing_ids_.swap(remove_ids); 429 history_->RemoveDownloads(remove_ids); 430 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadsRemoved(remove_ids)); 431 } 432