OLD | NEW |
| (Empty) |
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 #include "chrome/browser/history/in_memory_url_index.h" | |
6 | |
7 #include "base/files/file_util.h" | |
8 #include "base/strings/utf_string_conversions.h" | |
9 #include "base/trace_event/trace_event.h" | |
10 #include "chrome/browser/history/history_service.h" | |
11 #include "chrome/browser/history/url_index_private_data.h" | |
12 #include "chrome/common/url_constants.h" | |
13 #include "components/history/core/browser/url_database.h" | |
14 #include "content/public/browser/browser_thread.h" | |
15 | |
16 using in_memory_url_index::InMemoryURLIndexCacheItem; | |
17 | |
18 namespace history { | |
19 | |
20 // Called by DoSaveToCacheFile to delete any old cache file at |path| when | |
21 // there is no private data to save. Runs on the FILE thread. | |
22 void DeleteCacheFile(const base::FilePath& path) { | |
23 DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
24 base::DeleteFile(path, false); | |
25 } | |
26 | |
27 // Initializes a whitelist of URL schemes. | |
28 void InitializeSchemeWhitelist(std::set<std::string>* whitelist) { | |
29 DCHECK(whitelist); | |
30 if (!whitelist->empty()) | |
31 return; // Nothing to do, already initialized. | |
32 whitelist->insert(std::string(url::kAboutScheme)); | |
33 whitelist->insert(std::string(content::kChromeUIScheme)); | |
34 whitelist->insert(std::string(url::kFileScheme)); | |
35 whitelist->insert(std::string(url::kFtpScheme)); | |
36 whitelist->insert(std::string(url::kHttpScheme)); | |
37 whitelist->insert(std::string(url::kHttpsScheme)); | |
38 whitelist->insert(std::string(url::kMailToScheme)); | |
39 } | |
40 | |
41 // Restore/SaveCacheObserver --------------------------------------------------- | |
42 | |
43 InMemoryURLIndex::RestoreCacheObserver::~RestoreCacheObserver() {} | |
44 | |
45 InMemoryURLIndex::SaveCacheObserver::~SaveCacheObserver() {} | |
46 | |
47 // RebuildPrivateDataFromHistoryDBTask ----------------------------------------- | |
48 | |
49 InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask:: | |
50 RebuildPrivateDataFromHistoryDBTask( | |
51 InMemoryURLIndex* index, | |
52 const std::string& languages, | |
53 const std::set<std::string>& scheme_whitelist) | |
54 : index_(index), | |
55 languages_(languages), | |
56 scheme_whitelist_(scheme_whitelist), | |
57 succeeded_(false) { | |
58 } | |
59 | |
60 bool InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::RunOnDBThread( | |
61 HistoryBackend* backend, | |
62 HistoryDatabase* db) { | |
63 data_ = URLIndexPrivateData::RebuildFromHistory(db, languages_, | |
64 scheme_whitelist_); | |
65 succeeded_ = data_.get() && !data_->Empty(); | |
66 if (!succeeded_ && data_.get()) | |
67 data_->Clear(); | |
68 return true; | |
69 } | |
70 | |
71 void InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask:: | |
72 DoneRunOnMainThread() { | |
73 index_->DoneRebuidingPrivateDataFromHistoryDB(succeeded_, data_); | |
74 } | |
75 | |
76 InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask:: | |
77 ~RebuildPrivateDataFromHistoryDBTask() { | |
78 } | |
79 | |
80 // InMemoryURLIndex ------------------------------------------------------------ | |
81 | |
82 InMemoryURLIndex::InMemoryURLIndex(HistoryService* history_service, | |
83 const base::FilePath& history_dir, | |
84 const std::string& languages) | |
85 : history_service_(history_service), | |
86 history_dir_(history_dir), | |
87 languages_(languages), | |
88 private_data_(new URLIndexPrivateData), | |
89 restore_cache_observer_(NULL), | |
90 save_cache_observer_(NULL), | |
91 shutdown_(false), | |
92 restored_(false), | |
93 needs_to_be_cached_(false), | |
94 listen_to_history_service_loaded_(false) { | |
95 InitializeSchemeWhitelist(&scheme_whitelist_); | |
96 // TODO(mrossetti): Register for language change notifications. | |
97 if (history_service_) | |
98 history_service_->AddObserver(this); | |
99 } | |
100 | |
101 InMemoryURLIndex::~InMemoryURLIndex() { | |
102 // If there was a history directory (which there won't be for some unit tests) | |
103 // then insure that the cache has already been saved. | |
104 DCHECK(history_dir_.empty() || !needs_to_be_cached_); | |
105 DCHECK(!history_service_); | |
106 DCHECK(shutdown_); | |
107 } | |
108 | |
109 void InMemoryURLIndex::Init() { | |
110 PostRestoreFromCacheFileTask(); | |
111 } | |
112 | |
113 void InMemoryURLIndex::ShutDown() { | |
114 if (history_service_) { | |
115 history_service_->RemoveObserver(this); | |
116 history_service_ = nullptr; | |
117 } | |
118 cache_reader_tracker_.TryCancelAll(); | |
119 shutdown_ = true; | |
120 base::FilePath path; | |
121 if (!GetCacheFilePath(&path)) | |
122 return; | |
123 private_data_tracker_.TryCancelAll(); | |
124 URLIndexPrivateData::WritePrivateDataToCacheFileTask(private_data_, path); | |
125 needs_to_be_cached_ = false; | |
126 } | |
127 | |
128 void InMemoryURLIndex::ClearPrivateData() { | |
129 private_data_->Clear(); | |
130 } | |
131 | |
132 bool InMemoryURLIndex::GetCacheFilePath(base::FilePath* file_path) { | |
133 if (history_dir_.empty()) | |
134 return false; | |
135 *file_path = history_dir_.Append(FILE_PATH_LITERAL("History Provider Cache")); | |
136 return true; | |
137 } | |
138 | |
139 // Querying -------------------------------------------------------------------- | |
140 | |
141 ScoredHistoryMatches InMemoryURLIndex::HistoryItemsForTerms( | |
142 const base::string16& term_string, | |
143 size_t cursor_position, | |
144 size_t max_matches, | |
145 const ScoredHistoryMatch::Builder& builder) { | |
146 return private_data_->HistoryItemsForTerms(term_string, cursor_position, | |
147 max_matches, languages_, builder); | |
148 } | |
149 | |
150 // Updating -------------------------------------------------------------------- | |
151 | |
152 void InMemoryURLIndex::DeleteURL(const GURL& url) { | |
153 private_data_->DeleteURL(url); | |
154 } | |
155 | |
156 void InMemoryURLIndex::OnURLVisited(HistoryService* history_service, | |
157 ui::PageTransition transition, | |
158 const URLRow& row, | |
159 const RedirectList& redirects, | |
160 base::Time visit_time) { | |
161 DCHECK_EQ(history_service_, history_service); | |
162 needs_to_be_cached_ |= private_data_->UpdateURL(history_service_, | |
163 row, | |
164 languages_, | |
165 scheme_whitelist_, | |
166 &private_data_tracker_); | |
167 } | |
168 | |
169 void InMemoryURLIndex::OnURLsModified(HistoryService* history_service, | |
170 const URLRows& changed_urls) { | |
171 DCHECK_EQ(history_service_, history_service); | |
172 for (const auto& row : changed_urls) { | |
173 needs_to_be_cached_ |= private_data_->UpdateURL(history_service_, | |
174 row, | |
175 languages_, | |
176 scheme_whitelist_, | |
177 &private_data_tracker_); | |
178 } | |
179 } | |
180 | |
181 void InMemoryURLIndex::OnURLsDeleted(HistoryService* history_service, | |
182 bool all_history, | |
183 bool expired, | |
184 const URLRows& deleted_rows, | |
185 const std::set<GURL>& favicon_urls) { | |
186 if (all_history) { | |
187 ClearPrivateData(); | |
188 needs_to_be_cached_ = true; | |
189 } else { | |
190 for (const auto& row : deleted_rows) | |
191 needs_to_be_cached_ |= private_data_->DeleteURL(row.url()); | |
192 } | |
193 // If we made changes, destroy the previous cache. Otherwise, if we go | |
194 // through an unclean shutdown (and therefore fail to write a new cache file), | |
195 // when Chrome restarts and we restore from the previous cache, we'll end up | |
196 // searching over URLs that may be deleted. This would be wrong, and | |
197 // surprising to the user who bothered to delete some URLs from his/her | |
198 // history. In this situation, deleting the cache is a better solution than | |
199 // writing a new cache (after deleting the URLs from the in-memory structure) | |
200 // because deleting the cache forces it to be rebuilt from history upon | |
201 // startup. If we instead write a new, updated cache then at the time of next | |
202 // startup (after an unclean shutdown) we will not rebuild the in-memory data | |
203 // structures from history but rather use the cache. This solution is | |
204 // mediocre because this cache may not have the most-recently-visited URLs | |
205 // in it (URLs visited after user deleted some URLs from history), which | |
206 // would be odd and confusing. It's better to force a rebuild. | |
207 base::FilePath path; | |
208 if (needs_to_be_cached_ && GetCacheFilePath(&path)) { | |
209 content::BrowserThread::PostBlockingPoolTask( | |
210 FROM_HERE, base::Bind(DeleteCacheFile, path)); | |
211 } | |
212 } | |
213 | |
214 void InMemoryURLIndex::OnHistoryServiceLoaded(HistoryService* history_service) { | |
215 if (listen_to_history_service_loaded_) | |
216 ScheduleRebuildFromHistory(); | |
217 listen_to_history_service_loaded_ = false; | |
218 } | |
219 | |
220 // Restoring from Cache -------------------------------------------------------- | |
221 | |
222 void InMemoryURLIndex::PostRestoreFromCacheFileTask() { | |
223 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
224 TRACE_EVENT0("browser", "InMemoryURLIndex::PostRestoreFromCacheFileTask"); | |
225 | |
226 base::FilePath path; | |
227 if (!GetCacheFilePath(&path) || shutdown_) { | |
228 restored_ = true; | |
229 if (restore_cache_observer_) | |
230 restore_cache_observer_->OnCacheRestoreFinished(false); | |
231 return; | |
232 } | |
233 | |
234 content::BrowserThread::PostTaskAndReplyWithResult | |
235 <scoped_refptr<URLIndexPrivateData> >( | |
236 content::BrowserThread::FILE, FROM_HERE, | |
237 base::Bind(&URLIndexPrivateData::RestoreFromFile, path, languages_), | |
238 base::Bind(&InMemoryURLIndex::OnCacheLoadDone, AsWeakPtr())); | |
239 } | |
240 | |
241 void InMemoryURLIndex::OnCacheLoadDone( | |
242 scoped_refptr<URLIndexPrivateData> private_data) { | |
243 if (private_data.get() && !private_data->Empty()) { | |
244 private_data_tracker_.TryCancelAll(); | |
245 private_data_ = private_data; | |
246 restored_ = true; | |
247 if (restore_cache_observer_) | |
248 restore_cache_observer_->OnCacheRestoreFinished(true); | |
249 } else if (history_service_) { | |
250 // When unable to restore from the cache file delete the cache file, if | |
251 // it exists, and then rebuild from the history database if it's available, | |
252 // otherwise wait until the history database loaded and then rebuild. | |
253 base::FilePath path; | |
254 if (!GetCacheFilePath(&path) || shutdown_) | |
255 return; | |
256 content::BrowserThread::PostBlockingPoolTask( | |
257 FROM_HERE, base::Bind(DeleteCacheFile, path)); | |
258 if (history_service_->backend_loaded()) { | |
259 ScheduleRebuildFromHistory(); | |
260 } else { | |
261 listen_to_history_service_loaded_ = true; | |
262 } | |
263 } | |
264 } | |
265 | |
266 // Restoring from the History DB ----------------------------------------------- | |
267 | |
268 void InMemoryURLIndex::ScheduleRebuildFromHistory() { | |
269 DCHECK(history_service_); | |
270 history_service_->ScheduleDBTask( | |
271 scoped_ptr<history::HistoryDBTask>( | |
272 new InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask( | |
273 this, languages_, scheme_whitelist_)), | |
274 &cache_reader_tracker_); | |
275 } | |
276 | |
277 void InMemoryURLIndex::DoneRebuidingPrivateDataFromHistoryDB( | |
278 bool succeeded, | |
279 scoped_refptr<URLIndexPrivateData> private_data) { | |
280 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
281 if (succeeded) { | |
282 private_data_tracker_.TryCancelAll(); | |
283 private_data_ = private_data; | |
284 PostSaveToCacheFileTask(); // Cache the newly rebuilt index. | |
285 } else { | |
286 private_data_->Clear(); // Dump the old private data. | |
287 // There is no need to do anything with the cache file as it was deleted | |
288 // when the rebuild from the history operation was kicked off. | |
289 } | |
290 restored_ = true; | |
291 if (restore_cache_observer_) | |
292 restore_cache_observer_->OnCacheRestoreFinished(succeeded); | |
293 } | |
294 | |
295 void InMemoryURLIndex::RebuildFromHistory(HistoryDatabase* history_db) { | |
296 private_data_tracker_.TryCancelAll(); | |
297 private_data_ = URLIndexPrivateData::RebuildFromHistory(history_db, | |
298 languages_, | |
299 scheme_whitelist_); | |
300 } | |
301 | |
302 // Saving to Cache ------------------------------------------------------------- | |
303 | |
304 void InMemoryURLIndex::PostSaveToCacheFileTask() { | |
305 base::FilePath path; | |
306 if (!GetCacheFilePath(&path)) | |
307 return; | |
308 // If there is anything in our private data then make a copy of it and tell | |
309 // it to save itself to a file. | |
310 if (private_data_.get() && !private_data_->Empty()) { | |
311 // Note that ownership of the copy of our private data is passed to the | |
312 // completion closure below. | |
313 scoped_refptr<URLIndexPrivateData> private_data_copy = | |
314 private_data_->Duplicate(); | |
315 content::BrowserThread::PostTaskAndReplyWithResult<bool>( | |
316 content::BrowserThread::FILE, FROM_HERE, | |
317 base::Bind(&URLIndexPrivateData::WritePrivateDataToCacheFileTask, | |
318 private_data_copy, path), | |
319 base::Bind(&InMemoryURLIndex::OnCacheSaveDone, AsWeakPtr())); | |
320 } else { | |
321 // If there is no data in our index then delete any existing cache file. | |
322 content::BrowserThread::PostBlockingPoolTask( | |
323 FROM_HERE, | |
324 base::Bind(DeleteCacheFile, path)); | |
325 } | |
326 } | |
327 | |
328 void InMemoryURLIndex::OnCacheSaveDone(bool succeeded) { | |
329 if (save_cache_observer_) | |
330 save_cache_observer_->OnCacheSaveFinished(succeeded); | |
331 } | |
332 | |
333 } // namespace history | |
OLD | NEW |