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 #include "chrome/browser/download/download_history.h" | 5 #include "chrome/browser/download/download_history.h" |
6 | 6 |
7 #include "base/logging.h" | 7 #include "base/metrics/histogram.h" |
8 #include "chrome/browser/download/download_crx_util.h" | 8 #include "chrome/browser/cancelable_request.h" |
9 #include "chrome/browser/history/history_marshaling.h" | 9 #include "content/public/browser/browser_thread.h" |
10 #include "chrome/browser/history/history_service_factory.h" | |
11 #include "chrome/browser/profiles/profile.h" | |
12 #include "content/public/browser/download_item.h" | 10 #include "content/public/browser/download_item.h" |
11 #include "content/public/browser/download_manager.h" | |
13 #include "content/public/browser/download_persistent_store_info.h" | 12 #include "content/public/browser/download_persistent_store_info.h" |
14 | 13 |
14 using content::BrowserThread; | |
15 using content::DownloadItem; | 15 using content::DownloadItem; |
16 using content::DownloadManager; | |
16 using content::DownloadPersistentStoreInfo; | 17 using content::DownloadPersistentStoreInfo; |
17 | 18 |
18 DownloadHistory::DownloadHistory(Profile* profile) | 19 namespace { |
19 : profile_(profile), | 20 |
20 next_fake_db_handle_(DownloadItem::kUninitializedHandle - 1) { | 21 class DownloadHistoryData : public base::SupportsUserData::Data { |
21 DCHECK(profile); | 22 public: |
22 } | 23 static const int64 kUninitializedHandle = -1; |
23 | 24 |
24 DownloadHistory::~DownloadHistory() {} | 25 static DownloadHistoryData* Get(DownloadItem* item) { |
25 | 26 base::SupportsUserData::Data* data = item->GetUserData(kKey); |
26 void DownloadHistory::GetNextId( | 27 return (data == NULL) ? NULL : |
27 const HistoryService::DownloadNextIdCallback& callback) { | 28 static_cast<DownloadHistoryData*>(data); |
28 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( | 29 } |
29 profile_, Profile::EXPLICIT_ACCESS); | 30 |
30 if (!hs) | 31 explicit DownloadHistoryData(DownloadItem* item) |
31 return; | 32 : is_persisted_(false), |
32 | 33 db_handle_(kUninitializedHandle) { |
33 hs->GetNextDownloadId(&history_consumer_, callback); | 34 item->SetUserData(kKey, this); |
35 } | |
36 | |
37 virtual ~DownloadHistoryData() { | |
38 } | |
39 | |
40 bool is_persisted() const { return is_persisted_; } | |
41 void set_is_persisted(bool p) { is_persisted_ = p; } | |
42 | |
43 // TODO(benjhayden) Merge db_handle with DownloadItem::GetId(), then this | |
44 // class can turn into a simple bool. | |
45 int64 db_handle() const { return db_handle_; } | |
46 void set_db_handle(int64 h) { db_handle_ = h; } | |
47 | |
48 private: | |
49 static const char kKey[]; | |
50 | |
51 bool is_persisted_; | |
52 int64 db_handle_; | |
53 | |
54 DISALLOW_COPY_AND_ASSIGN(DownloadHistoryData); | |
55 }; | |
56 | |
57 const char DownloadHistoryData::kKey[] = | |
58 "DownloadItem DownloadHistoryData"; | |
59 | |
60 DownloadPersistentStoreInfo GetPersistentStoreInfo(DownloadItem* item) { | |
61 DownloadHistoryData* data = DownloadHistoryData::Get(item); | |
62 int64 db_handle = ((data != NULL) ? | |
63 data->db_handle() : | |
64 DownloadHistoryData::kUninitializedHandle); | |
65 return DownloadPersistentStoreInfo( | |
66 item->GetFullPath(), | |
67 item->GetURL(), | |
68 item->GetReferrerUrl(), | |
69 item->GetStartTime(), | |
70 item->GetEndTime(), | |
71 item->GetReceivedBytes(), | |
72 item->GetTotalBytes(), | |
73 item->GetState(), | |
74 db_handle, | |
75 item->GetOpened()); | |
76 } | |
77 | |
78 } // anonymous namespace | |
79 | |
80 DownloadHistory::DownloadHistory( | |
81 DownloadManager* manager, | |
82 HistoryServiceDownloadInterface* history) | |
83 : manager_(manager), | |
84 history_(history), | |
85 history_size_(0) { | |
86 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
87 DCHECK(manager_); | |
88 DCHECK(history_); | |
89 manager_->AddObserver(this); | |
90 } | |
91 | |
92 DownloadHistory::~DownloadHistory() { | |
93 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
94 history_->OnDownloadHistoryDestroyed(); | |
95 if (manager_) | |
96 manager_->RemoveObserver(this); | |
34 } | 97 } |
35 | 98 |
36 void DownloadHistory::Load( | 99 void DownloadHistory::Load( |
37 const HistoryService::DownloadQueryCallback& callback) { | 100 const HistoryService::DownloadQueryCallback& callback) { |
38 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( | 101 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
39 profile_, Profile::EXPLICIT_ACCESS); | 102 history_->QueryDownloads(base::Bind( |
40 if (!hs) | 103 &DownloadHistory::LoadCallback, AsWeakPtr(), callback)); |
41 return; | 104 } |
42 | 105 |
43 hs->QueryDownloads(&history_consumer_, callback); | 106 void DownloadHistory::LoadCallback( |
44 | 107 const HistoryService::DownloadQueryCallback& callback, |
45 // This is the initial load, so do a cleanup of corrupt in-progress entries. | 108 DownloadHistory::InfoVector* infos) { |
46 hs->CleanUpInProgressEntries(); | 109 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
110 loaded_infos_.clear(); | |
111 loaded_infos_.resize(infos->size()); | |
112 std::copy(infos->begin(), infos->end(), loaded_infos_.begin()); | |
113 callback.Run(infos); | |
47 } | 114 } |
48 | 115 |
49 void DownloadHistory::CheckVisitedReferrerBefore( | 116 void DownloadHistory::CheckVisitedReferrerBefore( |
50 int32 download_id, | 117 int32 download_id, |
51 const GURL& referrer_url, | 118 const GURL& referrer_url, |
52 const VisitedBeforeDoneCallback& callback) { | 119 const VisitedBeforeDoneCallback& callback) { |
120 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
53 if (referrer_url.is_valid()) { | 121 if (referrer_url.is_valid()) { |
54 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( | 122 HistoryService::Handle handle = history_->GetVisibleVisitCountToHost( |
55 profile_, Profile::EXPLICIT_ACCESS); | 123 referrer_url, |
56 if (hs) { | 124 base::Bind(&DownloadHistory::OnGotVisitCountToHost, |
57 HistoryService::Handle handle = | 125 AsWeakPtr())); |
58 hs->GetVisibleVisitCountToHost(referrer_url, &history_consumer_, | 126 visited_before_requests_[handle] = callback; |
59 base::Bind(&DownloadHistory::OnGotVisitCountToHost, | 127 return; |
60 base::Unretained(this))); | |
61 visited_before_requests_[handle] = callback; | |
62 return; | |
63 } | |
64 } | 128 } |
65 callback.Run(false); | 129 callback.Run(false); |
66 } | 130 } |
67 | 131 |
68 void DownloadHistory::AddEntry( | 132 void DownloadHistory::OnGotVisitCountToHost( |
69 DownloadItem* download_item, | 133 HistoryService::Handle handle, |
70 const HistoryService::DownloadCreateCallback& callback) { | 134 bool found_visits, |
71 DCHECK(download_item); | 135 int count, |
72 // Do not store the download in the history database for a few special cases: | 136 base::Time first_visit) { |
73 // - incognito mode (that is the point of this mode) | 137 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
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_item->IsOtr() || | |
84 download_crx_util::IsExtensionDownload(*download_item) || | |
85 download_item->IsTemporary() || !hs) { | |
86 callback.Run(download_item->GetId(), GetNextFakeDbHandle()); | |
87 return; | |
88 } | |
89 | |
90 int32 id = download_item->GetId(); | |
91 DownloadPersistentStoreInfo history_info = | |
92 download_item->GetPersistentStoreInfo(); | |
93 hs->CreateDownload(id, history_info, &history_consumer_, callback); | |
94 } | |
95 | |
96 void DownloadHistory::UpdateEntry(DownloadItem* download_item) { | |
97 // Don't store info in the database if the download was initiated while in | |
98 // incognito mode or if it hasn't been initialized in our database table. | |
99 if (download_item->GetDbHandle() <= DownloadItem::kUninitializedHandle) | |
100 return; | |
101 | |
102 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( | |
103 profile_, Profile::EXPLICIT_ACCESS); | |
104 if (!hs) | |
105 return; | |
106 hs->UpdateDownload(download_item->GetPersistentStoreInfo()); | |
107 } | |
108 | |
109 void DownloadHistory::UpdateDownloadPath(DownloadItem* download_item, | |
110 const FilePath& new_path) { | |
111 // No update necessary if the download was initiated while in incognito mode. | |
112 if (download_item->GetDbHandle() <= DownloadItem::kUninitializedHandle) | |
113 return; | |
114 | |
115 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( | |
116 profile_, Profile::EXPLICIT_ACCESS); | |
117 if (hs) | |
118 hs->UpdateDownloadPath(new_path, download_item->GetDbHandle()); | |
119 } | |
120 | |
121 void DownloadHistory::RemoveEntry(DownloadItem* download_item) { | |
122 // No update necessary if the download was initiated while in incognito mode. | |
123 if (download_item->GetDbHandle() <= DownloadItem::kUninitializedHandle) | |
124 return; | |
125 | |
126 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( | |
127 profile_, Profile::EXPLICIT_ACCESS); | |
128 if (hs) | |
129 hs->RemoveDownload(download_item->GetDbHandle()); | |
130 } | |
131 | |
132 void DownloadHistory::RemoveEntriesBetween(const base::Time remove_begin, | |
133 const base::Time remove_end) { | |
134 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( | |
135 profile_, Profile::EXPLICIT_ACCESS); | |
136 if (hs) | |
137 hs->RemoveDownloadsBetween(remove_begin, remove_end); | |
138 } | |
139 | |
140 int64 DownloadHistory::GetNextFakeDbHandle() { | |
141 return next_fake_db_handle_--; | |
142 } | |
143 | |
144 void DownloadHistory::OnGotVisitCountToHost(HistoryService::Handle handle, | |
145 bool found_visits, | |
146 int count, | |
147 base::Time first_visit) { | |
148 VisitedBeforeRequestsMap::iterator request = | 138 VisitedBeforeRequestsMap::iterator request = |
149 visited_before_requests_.find(handle); | 139 visited_before_requests_.find(handle); |
150 DCHECK(request != visited_before_requests_.end()); | 140 DCHECK(request != visited_before_requests_.end()); |
151 VisitedBeforeDoneCallback callback = request->second; | 141 VisitedBeforeDoneCallback callback = request->second; |
152 visited_before_requests_.erase(request); | 142 visited_before_requests_.erase(request); |
153 callback.Run(found_visits && count && | 143 callback.Run(found_visits && count && |
154 (first_visit.LocalMidnight() < base::Time::Now().LocalMidnight())); | 144 (first_visit.LocalMidnight() < base::Time::Now().LocalMidnight())); |
155 } | 145 } |
146 | |
147 void DownloadHistory::OnDownloadCreated( | |
148 DownloadManager* manager, DownloadItem* item) { | |
149 DCHECK(manager); | |
150 DCHECK(manager_); | |
151 DCHECK_EQ(manager_, manager); | |
152 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
153 | |
154 // Observe even temporary downloads in case they are marked not temporary. | |
155 item->AddObserver(this); | |
156 DownloadPersistentStoreInfo info = GetPersistentStoreInfo(item); | |
157 infos_[item->GetId()] = info; | |
158 // All downloads should pass through OnDownloadCreated exactly once. | |
159 DCHECK(!DownloadHistoryData::Get(item)); | |
160 DownloadHistoryData* data = new DownloadHistoryData(item); | |
161 | |
162 // Try to match up |item| with |loaded_infos_| by all fields except | |
163 // |db_handle|, because DownloadItem doesn't know its |db_handle|. | |
Randy Smith (Not in Mondays)
2012/08/02 22:47:56
Refactor load path into DM so that the DH creates
benjhayden
2012/08/14 21:31:47
Done.
| |
164 // Theoretically, DownloadManager will run through infos in order, so this | |
165 // loop should either match the first info or else loaded_infos_ should be | |
166 // empty, but we shouldn't rely on that. This matching mechanism relies on | |
167 // DownloadItem reflecting its info accurately, without "fixing" any fields | |
168 // except |state|, which is changed from IN_PROGRESS to CANCELLED. | |
169 for (InfoVector::iterator it = loaded_infos_.begin(); | |
170 it != loaded_infos_.end(); ++it) { | |
171 LOG(INFO) << "occam " << __FUNCTION__ | |
172 << " " << data->is_persisted() << " " << data->db_handle() | |
173 << " " << info.db_handle << " " << it->db_handle | |
174 << " " << info.path.value() << " " << it->path.value() | |
175 << " " << info.url.spec() << " " << it->url.spec() | |
176 << " " << info.referrer_url.spec() << " " << it->referrer_url.spec () | |
177 << " " << info.start_time.ToTimeT() << " " << it->start_time.ToTim eT() | |
178 << " " << info.end_time.ToTimeT() << " " << it->end_time.ToTimeT() | |
179 << " " << info.received_bytes << " " << it->received_bytes | |
180 << " " << info.total_bytes << " " << it->total_bytes | |
181 << " " << info.state << " " << it->state | |
182 << " " << info.opened << " " << it->opened; | |
183 if ((info.path == it->path) && | |
184 (info.url == it->url) && | |
185 (info.referrer_url == it->referrer_url) && | |
186 (info.start_time == it->start_time) && | |
187 (info.end_time == it->end_time) && | |
188 (info.received_bytes == it->received_bytes) && | |
189 (info.total_bytes == it->total_bytes) && | |
190 ((it->state == DownloadItem::IN_PROGRESS) || | |
191 (info.state == it->state)) && | |
192 (info.opened == it->opened)) { | |
193 loaded_infos_.erase(it); | |
194 data->set_is_persisted(true); | |
195 data->set_db_handle(it->db_handle); | |
196 break; | |
197 } | |
198 } | |
199 | |
200 MaybeAddToHistory(item); | |
201 } | |
202 | |
203 void DownloadHistory::ManagerGoingDown(DownloadManager* manager) { | |
204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
205 DCHECK_EQ(manager_, manager); | |
206 manager_->RemoveObserver(this); | |
207 manager_ = NULL; | |
208 } | |
209 | |
210 void DownloadHistory::MaybeAddToHistory(DownloadItem* item) { | |
211 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
212 int32 download_id = item->GetId(); | |
213 DownloadHistoryData* data = DownloadHistoryData::Get(item); | |
214 bool removing = (removing_.find(data->db_handle()) != removing_.end()); | |
215 bool already_adding = (adding_.find(download_id) != adding_.end()); | |
216 LOG(INFO) << "occam " << __FUNCTION__ << " " << data->is_persisted() << " " << item->IsTemporary() << " " << removing << " " << already_adding; | |
217 if (!data->is_persisted() && | |
218 !item->IsTemporary() && | |
219 !removing && | |
220 !already_adding) { | |
221 adding_.insert(download_id); | |
222 history_->CreateDownload( | |
223 download_id, | |
224 infos_[download_id], | |
225 base::Bind(&DownloadHistory::ItemAdded, AsWeakPtr())); | |
226 } | |
227 } | |
228 | |
229 void DownloadHistory::ItemAdded(int32 download_id, int64 db_handle) { | |
230 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
231 | |
232 adding_.erase(download_id); | |
233 | |
234 if (!manager_) | |
235 return; | |
236 | |
237 DownloadItem* item = manager_->GetDownload(download_id); | |
238 LOG(INFO) << "occam " << __FUNCTION__ << " " << item << " " << db_handle; | |
239 if (!item) { | |
240 // We should have caught OnDownloadDestroyed(). If the item should have | |
241 // been removed from history, then OnDownloadRemoved() would have been | |
242 // called, so don't manually call it here. | |
243 DCHECK(infos_.find(download_id) == infos_.end()); | |
244 return; | |
245 } | |
246 | |
247 UMA_HISTOGRAM_CUSTOM_COUNTS("Download.HistorySize2", | |
248 history_size_, | |
249 0/*min*/, | |
250 (1 << 23)/*max*/, | |
251 (1 << 7)/*num_buckets*/); | |
252 ++history_size_; | |
253 | |
254 DownloadHistoryData* data = DownloadHistoryData::Get(item); | |
255 data->set_is_persisted(true); | |
256 data->set_db_handle(db_handle); | |
Randy Smith (Not in Mondays)
2012/08/02 22:47:56
Maybe also store the last DownloadPersistentStateI
benjhayden
2012/08/14 21:31:47
Done.
| |
257 | |
258 // In case the item changed or became temporary while it was being added. | |
259 // Don't just UpdateObservers() because we're the only observer that can also | |
260 // see is_persisted/db_handle, which is the only thing that we changed. | |
261 OnDownloadUpdated(item); | |
262 } | |
263 | |
264 void DownloadHistory::OnDownloadUpdated(DownloadItem* item) { | |
265 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
266 if (!manager_) | |
267 return; | |
268 | |
269 DownloadHistoryData* data = DownloadHistoryData::Get(item); | |
270 if ((data == NULL) || | |
271 !data->is_persisted()) { | |
272 MaybeAddToHistory(item); | |
273 return; | |
274 } | |
275 | |
276 if (item->IsTemporary()) { | |
277 OnDownloadRemoved(item); | |
278 return; | |
279 } | |
280 | |
281 // TODO(asanka): Persist GetTargetFilePath() as well. | |
282 DownloadPersistentStoreInfo current_info = GetPersistentStoreInfo(item); | |
283 InfoMap::const_iterator previous_info = infos_.find(item->GetId()); | |
284 if ((previous_info == infos_.end()) || | |
285 (previous_info->second.end_time != current_info.end_time) || | |
286 (previous_info->second.received_bytes != current_info.received_bytes) || | |
287 (previous_info->second.state != current_info.state) || | |
288 (previous_info->second.opened != current_info.opened)) { | |
289 history_->UpdateDownload(current_info); | |
290 } | |
291 infos_[item->GetId()] = current_info; | |
292 } | |
293 | |
294 void DownloadHistory::OnDownloadRemoved(DownloadItem* item) { | |
295 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
296 | |
297 DownloadHistoryData* data = DownloadHistoryData::Get(item); | |
298 if ((data == NULL) || | |
299 !data->is_persisted() || | |
300 (data->db_handle() <= DownloadHistoryData::kUninitializedHandle)) | |
301 return; | |
302 | |
303 // For database efficiency, batch removals together if they happen all at | |
304 // once. | |
305 if (removing_.empty()) { | |
306 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
307 base::Bind(&DownloadHistory::RemoveDownloadsBatch, AsWeakPtr())); | |
308 } | |
309 removing_.insert(data->db_handle()); | |
310 data->set_db_handle(DownloadHistoryData::kUninitializedHandle); | |
311 data->set_is_persisted(false); | |
312 } | |
313 | |
314 void DownloadHistory::RemoveDownloadsBatch() { | |
315 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
316 HandleSet remove_handles; | |
317 removing_.swap(remove_handles); | |
318 history_->RemoveDownloads(remove_handles); | |
319 --history_size_; | |
320 } | |
321 | |
322 void DownloadHistory::OnDownloadDestroyed(DownloadItem* item) { | |
323 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
324 infos_.erase(item->GetId()); | |
325 item->RemoveObserver(this); | |
326 } | |
OLD | NEW |