Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / download / download_history.cc
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   extensions::DownloadedByExtension* by_ext =
106       extensions::DownloadedByExtension::Get(item);
107   if (by_ext) {
108     by_ext_id = by_ext->id();
109     by_ext_name = by_ext->name();
110   }
111 #endif
112
113   return history::DownloadRow(
114       item->GetFullPath(),
115       item->GetTargetFilePath(),
116       item->GetUrlChain(),
117       item->GetReferrerUrl(),
118       item->GetStartTime(),
119       item->GetEndTime(),
120       item->GetETag(),
121       item->GetLastModifiedTime(),
122       item->GetReceivedBytes(),
123       item->GetTotalBytes(),
124       item->GetState(),
125       item->GetDangerType(),
126       item->GetLastReason(),
127       item->GetId(),
128       item->GetOpened(),
129       by_ext_id,
130       by_ext_name);
131 }
132
133 bool ShouldUpdateHistory(const history::DownloadRow* previous,
134                          const history::DownloadRow& current) {
135   // Ignore url, referrer, start_time, id, which don't change.
136   return ((previous == NULL) ||
137           (previous->current_path != current.current_path) ||
138           (previous->target_path != current.target_path) ||
139           (previous->end_time != current.end_time) ||
140           (previous->received_bytes != current.received_bytes) ||
141           (previous->total_bytes != current.total_bytes) ||
142           (previous->etag != current.etag) ||
143           (previous->last_modified != current.last_modified) ||
144           (previous->state != current.state) ||
145           (previous->danger_type != current.danger_type) ||
146           (previous->interrupt_reason != current.interrupt_reason) ||
147           (previous->opened != current.opened) ||
148           (previous->by_ext_id != current.by_ext_id) ||
149           (previous->by_ext_name != current.by_ext_name));
150 }
151
152 typedef std::vector<history::DownloadRow> InfoVector;
153
154 }  // anonymous namespace
155
156 DownloadHistory::HistoryAdapter::HistoryAdapter(HistoryService* history)
157   : history_(history) {
158 }
159 DownloadHistory::HistoryAdapter::~HistoryAdapter() {}
160
161 void DownloadHistory::HistoryAdapter::QueryDownloads(
162     const HistoryService::DownloadQueryCallback& callback) {
163   history_->QueryDownloads(callback);
164 }
165
166 void DownloadHistory::HistoryAdapter::CreateDownload(
167     const history::DownloadRow& info,
168     const HistoryService::DownloadCreateCallback& callback) {
169   history_->CreateDownload(info, callback);
170 }
171
172 void DownloadHistory::HistoryAdapter::UpdateDownload(
173     const history::DownloadRow& data) {
174   history_->UpdateDownload(data);
175 }
176
177 void DownloadHistory::HistoryAdapter::RemoveDownloads(
178     const std::set<uint32>& ids) {
179   history_->RemoveDownloads(ids);
180 }
181
182
183 DownloadHistory::Observer::Observer() {}
184 DownloadHistory::Observer::~Observer() {}
185
186 bool DownloadHistory::IsPersisted(content::DownloadItem* item) {
187   DownloadHistoryData* data = DownloadHistoryData::Get(item);
188   return data && (data->state() == DownloadHistoryData::PERSISTED);
189 }
190
191 DownloadHistory::DownloadHistory(content::DownloadManager* manager,
192                                  scoped_ptr<HistoryAdapter> history)
193   : notifier_(manager, this),
194     history_(history.Pass()),
195     loading_id_(content::DownloadItem::kInvalidId),
196     history_size_(0),
197     weak_ptr_factory_(this) {
198   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
199   content::DownloadManager::DownloadVector items;
200   notifier_.GetManager()->GetAllDownloads(&items);
201   for (content::DownloadManager::DownloadVector::const_iterator
202        it = items.begin(); it != items.end(); ++it) {
203     OnDownloadCreated(notifier_.GetManager(), *it);
204   }
205   history_->QueryDownloads(base::Bind(
206       &DownloadHistory::QueryCallback, weak_ptr_factory_.GetWeakPtr()));
207 }
208
209 DownloadHistory::~DownloadHistory() {
210   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
211   FOR_EACH_OBSERVER(Observer, observers_, OnDownloadHistoryDestroyed());
212   observers_.Clear();
213 }
214
215 void DownloadHistory::AddObserver(DownloadHistory::Observer* observer) {
216   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
217   observers_.AddObserver(observer);
218 }
219
220 void DownloadHistory::RemoveObserver(DownloadHistory::Observer* observer) {
221   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
222   observers_.RemoveObserver(observer);
223 }
224
225 void DownloadHistory::QueryCallback(scoped_ptr<InfoVector> infos) {
226   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
227   // ManagerGoingDown() may have happened before the history loaded.
228   if (!notifier_.GetManager())
229     return;
230   for (InfoVector::const_iterator it = infos->begin();
231        it != infos->end(); ++it) {
232     loading_id_ = it->id;
233     content::DownloadItem* item = notifier_.GetManager()->CreateDownloadItem(
234         loading_id_,
235         it->current_path,
236         it->target_path,
237         it->url_chain,
238         it->referrer_url,
239         it->start_time,
240         it->end_time,
241         it->etag,
242         it->last_modified,
243         it->received_bytes,
244         it->total_bytes,
245         it->state,
246         it->danger_type,
247         it->interrupt_reason,
248         it->opened);
249 #if !defined(OS_ANDROID)
250     if (!it->by_ext_id.empty() && !it->by_ext_name.empty()) {
251       new extensions::DownloadedByExtension(
252           item, it->by_ext_id, it->by_ext_name);
253       item->UpdateObservers();
254     }
255 #endif
256     DCHECK_EQ(DownloadHistoryData::Get(item)->state(),
257               DownloadHistoryData::PERSISTED);
258     ++history_size_;
259   }
260   notifier_.GetManager()->CheckForHistoryFilesRemoval();
261 }
262
263 void DownloadHistory::MaybeAddToHistory(content::DownloadItem* item) {
264   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
265
266   uint32 download_id = item->GetId();
267   DownloadHistoryData* data = DownloadHistoryData::Get(item);
268   bool removing = removing_ids_.find(download_id) != removing_ids_.end();
269
270   // TODO(benjhayden): Remove IsTemporary().
271   if (download_crx_util::IsExtensionDownload(*item) ||
272       item->IsTemporary() ||
273       (data->state() != DownloadHistoryData::NOT_PERSISTED) ||
274       removing)
275     return;
276
277   data->SetState(DownloadHistoryData::PERSISTING);
278   if (data->info() == NULL) {
279     // Keep the info here regardless of whether the item is in progress so that,
280     // when ItemAdded() calls OnDownloadUpdated(), it can decide whether to
281     // Update the db and/or clear the info.
282     data->set_info(GetDownloadRow(item));
283   }
284
285   history_->CreateDownload(*data->info(), base::Bind(
286       &DownloadHistory::ItemAdded, weak_ptr_factory_.GetWeakPtr(),
287       download_id));
288   FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored(
289       item, *data->info()));
290 }
291
292 void DownloadHistory::ItemAdded(uint32 download_id, bool success) {
293   if (removed_while_adding_.find(download_id) !=
294       removed_while_adding_.end()) {
295     removed_while_adding_.erase(download_id);
296     if (success)
297       ScheduleRemoveDownload(download_id);
298     return;
299   }
300
301   if (!notifier_.GetManager())
302     return;
303
304   content::DownloadItem* item = notifier_.GetManager()->GetDownload(
305       download_id);
306   if (!item) {
307     // This item will have called OnDownloadDestroyed().  If the item should
308     // have been removed from history, then it would have also called
309     // OnDownloadRemoved(), which would have put |download_id| in
310     // removed_while_adding_, handled above.
311     return;
312   }
313
314   DownloadHistoryData* data = DownloadHistoryData::Get(item);
315
316   // The sql INSERT statement failed. Avoid an infinite loop: don't
317   // automatically retry. Retry adding the next time the item is updated by
318   // resetting the state to NOT_PERSISTED.
319   if (!success) {
320     DVLOG(20) << __FUNCTION__ << " INSERT failed id=" << download_id;
321     data->SetState(DownloadHistoryData::NOT_PERSISTED);
322     return;
323   }
324   data->SetState(DownloadHistoryData::PERSISTED);
325
326   UMA_HISTOGRAM_CUSTOM_COUNTS("Download.HistorySize2",
327                               history_size_,
328                               0/*min*/,
329                               (1 << 23)/*max*/,
330                               (1 << 7)/*num_buckets*/);
331   ++history_size_;
332
333   // In case the item changed or became temporary while it was being added.
334   // Don't just update all of the item's observers because we're the only
335   // observer that can also see data->state(), which is the only thing that
336   // ItemAdded() changed.
337   OnDownloadUpdated(notifier_.GetManager(), item);
338 }
339
340 void DownloadHistory::OnDownloadCreated(
341     content::DownloadManager* manager, content::DownloadItem* item) {
342   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
343
344   // All downloads should pass through OnDownloadCreated exactly once.
345   CHECK(!DownloadHistoryData::Get(item));
346   DownloadHistoryData* data = new DownloadHistoryData(item);
347   if (item->GetId() == loading_id_) {
348     data->SetState(DownloadHistoryData::PERSISTED);
349     loading_id_ = content::DownloadItem::kInvalidId;
350   }
351   if (item->GetState() == content::DownloadItem::IN_PROGRESS) {
352     data->set_info(GetDownloadRow(item));
353   }
354   MaybeAddToHistory(item);
355 }
356
357 void DownloadHistory::OnDownloadUpdated(
358     content::DownloadManager* manager, content::DownloadItem* item) {
359   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
360
361   DownloadHistoryData* data = DownloadHistoryData::Get(item);
362   if (data->state() == DownloadHistoryData::NOT_PERSISTED) {
363     MaybeAddToHistory(item);
364     return;
365   }
366   if (item->IsTemporary()) {
367     OnDownloadRemoved(notifier_.GetManager(), item);
368     return;
369   }
370
371   history::DownloadRow current_info(GetDownloadRow(item));
372   bool should_update = ShouldUpdateHistory(data->info(), current_info);
373   UMA_HISTOGRAM_ENUMERATION("Download.HistoryPropagatedUpdate",
374                             should_update, 2);
375   if (should_update) {
376     history_->UpdateDownload(current_info);
377     FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored(
378         item, current_info));
379   }
380   if (item->GetState() == content::DownloadItem::IN_PROGRESS) {
381     data->set_info(current_info);
382   } else {
383     data->clear_info();
384   }
385 }
386
387 void DownloadHistory::OnDownloadOpened(
388     content::DownloadManager* manager, content::DownloadItem* item) {
389   OnDownloadUpdated(manager, item);
390 }
391
392 void DownloadHistory::OnDownloadRemoved(
393     content::DownloadManager* manager, content::DownloadItem* item) {
394   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
395
396   DownloadHistoryData* data = DownloadHistoryData::Get(item);
397   if (data->state() != DownloadHistoryData::PERSISTED) {
398     if (data->state() == DownloadHistoryData::PERSISTING) {
399       // ScheduleRemoveDownload will be called when history_ calls ItemAdded().
400       removed_while_adding_.insert(item->GetId());
401     }
402     return;
403   }
404   ScheduleRemoveDownload(item->GetId());
405   // This is important: another OnDownloadRemoved() handler could do something
406   // that synchronously fires an OnDownloadUpdated().
407   data->SetState(DownloadHistoryData::NOT_PERSISTED);
408   // ItemAdded increments history_size_ only if the item wasn't
409   // removed_while_adding_, so the next line does not belong in
410   // ScheduleRemoveDownload().
411   --history_size_;
412 }
413
414 void DownloadHistory::ScheduleRemoveDownload(uint32 download_id) {
415   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
416
417   // For database efficiency, batch removals together if they happen all at
418   // once.
419   if (removing_ids_.empty()) {
420     content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
421         base::Bind(&DownloadHistory::RemoveDownloadsBatch,
422                    weak_ptr_factory_.GetWeakPtr()));
423   }
424   removing_ids_.insert(download_id);
425 }
426
427 void DownloadHistory::RemoveDownloadsBatch() {
428   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
429   IdSet remove_ids;
430   removing_ids_.swap(remove_ids);
431   history_->RemoveDownloads(remove_ids);
432   FOR_EACH_OBSERVER(Observer, observers_, OnDownloadsRemoved(remove_ids));
433 }