blob: 71244d31aa872a73964387b66f3dc625abd915f7 [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00007#include "base/debug/trace_event.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +00008#include "base/file_util.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01009#include "base/strings/utf_string_conversions.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000010#include "chrome/browser/bookmarks/bookmark_model_factory.h"
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010011#include "chrome/browser/chrome_notification_types.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000012#include "chrome/browser/history/history_notifications.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000013#include "chrome/browser/history/history_service.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000014#include "chrome/browser/history/history_service_factory.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000015#include "chrome/browser/history/url_index_private_data.h"
16#include "chrome/browser/profiles/profile.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000017#include "chrome/common/url_constants.h"
Torne (Richard Coles)cedac222014-06-03 10:58:34 +010018#include "components/bookmarks/browser/bookmark_model.h"
Ben Murdoch116680a2014-07-20 18:25:52 -070019#include "components/history/core/browser/url_database.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000020#include "content/public/browser/browser_thread.h"
21#include "content/public/browser/notification_details.h"
22#include "content/public/browser/notification_service.h"
23#include "content/public/browser/notification_source.h"
24
25using in_memory_url_index::InMemoryURLIndexCacheItem;
26
27namespace history {
28
29// Called by DoSaveToCacheFile to delete any old cache file at |path| when
30// there is no private data to save. Runs on the FILE thread.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000031void DeleteCacheFile(const base::FilePath& path) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +000032 DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010033 base::DeleteFile(path, false);
Torne (Richard Coles)58218062012-11-14 11:43:16 +000034}
35
36// Initializes a whitelist of URL schemes.
37void InitializeSchemeWhitelist(std::set<std::string>* whitelist) {
38 DCHECK(whitelist);
39 if (!whitelist->empty())
40 return; // Nothing to do, already initialized.
Torne (Richard Coles)f8ee7882014-06-20 14:52:04 +010041 whitelist->insert(std::string(url::kAboutScheme));
Torne (Richard Coles)5d1f7b12014-02-21 12:16:55 +000042 whitelist->insert(std::string(content::kChromeUIScheme));
Torne (Richard Coles)cedac222014-06-03 10:58:34 +010043 whitelist->insert(std::string(url::kFileScheme));
44 whitelist->insert(std::string(url::kFtpScheme));
Torne (Richard Coles)010d83a2014-05-14 12:12:37 +010045 whitelist->insert(std::string(url::kHttpScheme));
46 whitelist->insert(std::string(url::kHttpsScheme));
Torne (Richard Coles)cedac222014-06-03 10:58:34 +010047 whitelist->insert(std::string(url::kMailToScheme));
Torne (Richard Coles)58218062012-11-14 11:43:16 +000048}
49
50// Restore/SaveCacheObserver ---------------------------------------------------
51
52InMemoryURLIndex::RestoreCacheObserver::~RestoreCacheObserver() {}
53
54InMemoryURLIndex::SaveCacheObserver::~SaveCacheObserver() {}
55
56// RebuildPrivateDataFromHistoryDBTask -----------------------------------------
57
58InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::
59 RebuildPrivateDataFromHistoryDBTask(
60 InMemoryURLIndex* index,
61 const std::string& languages,
62 const std::set<std::string>& scheme_whitelist)
63 : index_(index),
64 languages_(languages),
65 scheme_whitelist_(scheme_whitelist),
66 succeeded_(false) {
67}
68
69bool InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::RunOnDBThread(
70 HistoryBackend* backend,
71 HistoryDatabase* db) {
72 data_ = URLIndexPrivateData::RebuildFromHistory(db, languages_,
73 scheme_whitelist_);
74 succeeded_ = data_.get() && !data_->Empty();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010075 if (!succeeded_ && data_.get())
Torne (Richard Coles)58218062012-11-14 11:43:16 +000076 data_->Clear();
77 return true;
78}
79
80void InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::
81 DoneRunOnMainThread() {
82 index_->DoneRebuidingPrivateDataFromHistoryDB(succeeded_, data_);
83}
84
85InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::
86 ~RebuildPrivateDataFromHistoryDBTask() {
87}
88
89// InMemoryURLIndex ------------------------------------------------------------
90
91InMemoryURLIndex::InMemoryURLIndex(Profile* profile,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000092 const base::FilePath& history_dir,
Torne (Richard Coles)46d4c2b2014-06-09 12:00:27 +010093 const std::string& languages,
94 HistoryClient* history_client)
Torne (Richard Coles)58218062012-11-14 11:43:16 +000095 : profile_(profile),
Torne (Richard Coles)46d4c2b2014-06-09 12:00:27 +010096 history_client_(history_client),
Torne (Richard Coles)58218062012-11-14 11:43:16 +000097 history_dir_(history_dir),
98 languages_(languages),
99 private_data_(new URLIndexPrivateData),
100 restore_cache_observer_(NULL),
101 save_cache_observer_(NULL),
102 shutdown_(false),
103 restored_(false),
104 needs_to_be_cached_(false) {
105 InitializeSchemeWhitelist(&scheme_whitelist_);
106 if (profile) {
107 // TODO(mrossetti): Register for language change notifications.
108 content::Source<Profile> source(profile);
109 registrar_.Add(this, chrome::NOTIFICATION_HISTORY_URL_VISITED, source);
110 registrar_.Add(this, chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
111 source);
112 registrar_.Add(this, chrome::NOTIFICATION_HISTORY_URLS_DELETED, source);
113 }
114}
115
116// Called only by unit tests.
117InMemoryURLIndex::InMemoryURLIndex()
118 : profile_(NULL),
Torne (Richard Coles)46d4c2b2014-06-09 12:00:27 +0100119 history_client_(NULL),
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000120 private_data_(new URLIndexPrivateData),
121 restore_cache_observer_(NULL),
122 save_cache_observer_(NULL),
123 shutdown_(false),
124 restored_(false),
125 needs_to_be_cached_(false) {
126 InitializeSchemeWhitelist(&scheme_whitelist_);
127}
128
129InMemoryURLIndex::~InMemoryURLIndex() {
130 // If there was a history directory (which there won't be for some unit tests)
131 // then insure that the cache has already been saved.
132 DCHECK(history_dir_.empty() || !needs_to_be_cached_);
133}
134
135void InMemoryURLIndex::Init() {
136 PostRestoreFromCacheFileTask();
137}
138
139void InMemoryURLIndex::ShutDown() {
140 registrar_.RemoveAll();
Ben Murdoch116680a2014-07-20 18:25:52 -0700141 cache_reader_tracker_.TryCancelAll();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000142 shutdown_ = true;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000143 base::FilePath path;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000144 if (!GetCacheFilePath(&path))
145 return;
Ben Murdoch116680a2014-07-20 18:25:52 -0700146 private_data_tracker_.TryCancelAll();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000147 URLIndexPrivateData::WritePrivateDataToCacheFileTask(private_data_, path);
148 needs_to_be_cached_ = false;
149}
150
151void InMemoryURLIndex::ClearPrivateData() {
152 private_data_->Clear();
153}
154
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000155bool InMemoryURLIndex::GetCacheFilePath(base::FilePath* file_path) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000156 if (history_dir_.empty())
157 return false;
158 *file_path = history_dir_.Append(FILE_PATH_LITERAL("History Provider Cache"));
159 return true;
160}
161
162// Querying --------------------------------------------------------------------
163
164ScoredHistoryMatches InMemoryURLIndex::HistoryItemsForTerms(
Torne (Richard Coles)a3f6a492013-12-18 16:25:09 +0000165 const base::string16& term_string,
Torne (Richard Coles)cedac222014-06-03 10:58:34 +0100166 size_t cursor_position,
167 size_t max_matches) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000168 return private_data_->HistoryItemsForTerms(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000169 term_string,
170 cursor_position,
Torne (Richard Coles)cedac222014-06-03 10:58:34 +0100171 max_matches,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000172 languages_,
Torne (Richard Coles)46d4c2b2014-06-09 12:00:27 +0100173 history_client_);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000174}
175
176// Updating --------------------------------------------------------------------
177
178void InMemoryURLIndex::DeleteURL(const GURL& url) {
179 private_data_->DeleteURL(url);
180}
181
182void InMemoryURLIndex::Observe(int notification_type,
183 const content::NotificationSource& source,
184 const content::NotificationDetails& details) {
185 switch (notification_type) {
186 case chrome::NOTIFICATION_HISTORY_URL_VISITED:
187 OnURLVisited(content::Details<URLVisitedDetails>(details).ptr());
188 break;
189 case chrome::NOTIFICATION_HISTORY_URLS_MODIFIED:
190 OnURLsModified(
191 content::Details<history::URLsModifiedDetails>(details).ptr());
192 break;
193 case chrome::NOTIFICATION_HISTORY_URLS_DELETED:
194 OnURLsDeleted(
195 content::Details<history::URLsDeletedDetails>(details).ptr());
196 break;
197 case chrome::NOTIFICATION_HISTORY_LOADED:
198 registrar_.Remove(this, chrome::NOTIFICATION_HISTORY_LOADED,
199 content::Source<Profile>(profile_));
200 ScheduleRebuildFromHistory();
201 break;
202 default:
203 // For simplicity, the unit tests send us all notifications, even when
204 // we haven't registered for them, so don't assert here.
205 break;
206 }
207}
208
209void InMemoryURLIndex::OnURLVisited(const URLVisitedDetails* details) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000210 HistoryService* service =
211 HistoryServiceFactory::GetForProfile(profile_,
212 Profile::EXPLICIT_ACCESS);
Ben Murdoch116680a2014-07-20 18:25:52 -0700213 needs_to_be_cached_ |= private_data_->UpdateURL(service,
214 details->row,
215 languages_,
216 scheme_whitelist_,
217 &private_data_tracker_);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000218}
219
220void InMemoryURLIndex::OnURLsModified(const URLsModifiedDetails* details) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000221 HistoryService* service =
222 HistoryServiceFactory::GetForProfile(profile_,
223 Profile::EXPLICIT_ACCESS);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000224 for (URLRows::const_iterator row = details->changed_urls.begin();
Ben Murdoch116680a2014-07-20 18:25:52 -0700225 row != details->changed_urls.end();
226 ++row) {
227 needs_to_be_cached_ |= private_data_->UpdateURL(
228 service, *row, languages_, scheme_whitelist_, &private_data_tracker_);
229 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000230}
231
232void InMemoryURLIndex::OnURLsDeleted(const URLsDeletedDetails* details) {
233 if (details->all_history) {
234 ClearPrivateData();
235 needs_to_be_cached_ = true;
236 } else {
237 for (URLRows::const_iterator row = details->rows.begin();
238 row != details->rows.end(); ++row)
239 needs_to_be_cached_ |= private_data_->DeleteURL(row->url());
240 }
Torne (Richard Coles)6e8cce62014-08-19 13:00:08 +0100241 // If we made changes, destroy the previous cache. Otherwise, if we go
242 // through an unclean shutdown (and therefore fail to write a new cache file),
243 // when Chrome restarts and we restore from the previous cache, we'll end up
244 // searching over URLs that may be deleted. This would be wrong, and
245 // surprising to the user who bothered to delete some URLs from his/her
246 // history. In this situation, deleting the cache is a better solution than
247 // writing a new cache (after deleting the URLs from the in-memory structure)
248 // because deleting the cache forces it to be rebuilt from history upon
249 // startup. If we instead write a new, updated cache then at the time of next
250 // startup (after an unclean shutdown) we will not rebuild the in-memory data
251 // structures from history but rather use the cache. This solution is
252 // mediocre because this cache may not have the most-recently-visited URLs
253 // in it (URLs visited after user deleted some URLs from history), which
254 // would be odd and confusing. It's better to force a rebuild.
255 base::FilePath path;
256 if (needs_to_be_cached_ && GetCacheFilePath(&path)) {
257 content::BrowserThread::PostBlockingPoolTask(
258 FROM_HERE, base::Bind(DeleteCacheFile, path));
259 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000260}
261
262// Restoring from Cache --------------------------------------------------------
263
264void InMemoryURLIndex::PostRestoreFromCacheFileTask() {
265 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000266 TRACE_EVENT0("browser", "InMemoryURLIndex::PostRestoreFromCacheFileTask");
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000267
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000268 base::FilePath path;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000269 if (!GetCacheFilePath(&path) || shutdown_) {
270 restored_ = true;
271 if (restore_cache_observer_)
272 restore_cache_observer_->OnCacheRestoreFinished(false);
273 return;
274 }
275
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000276 content::BrowserThread::PostTaskAndReplyWithResult
277 <scoped_refptr<URLIndexPrivateData> >(
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000278 content::BrowserThread::FILE, FROM_HERE,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000279 base::Bind(&URLIndexPrivateData::RestoreFromFile, path, languages_),
280 base::Bind(&InMemoryURLIndex::OnCacheLoadDone, AsWeakPtr()));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000281}
282
283void InMemoryURLIndex::OnCacheLoadDone(
284 scoped_refptr<URLIndexPrivateData> private_data) {
285 if (private_data.get() && !private_data->Empty()) {
Ben Murdoch116680a2014-07-20 18:25:52 -0700286 private_data_tracker_.TryCancelAll();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000287 private_data_ = private_data;
288 restored_ = true;
289 if (restore_cache_observer_)
290 restore_cache_observer_->OnCacheRestoreFinished(true);
291 } else if (profile_) {
292 // When unable to restore from the cache file delete the cache file, if
293 // it exists, and then rebuild from the history database if it's available,
294 // otherwise wait until the history database loaded and then rebuild.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000295 base::FilePath path;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000296 if (!GetCacheFilePath(&path) || shutdown_)
297 return;
298 content::BrowserThread::PostBlockingPoolTask(
299 FROM_HERE, base::Bind(DeleteCacheFile, path));
300 HistoryService* service =
301 HistoryServiceFactory::GetForProfileWithoutCreating(profile_);
302 if (service && service->backend_loaded()) {
303 ScheduleRebuildFromHistory();
304 } else {
305 registrar_.Add(this, chrome::NOTIFICATION_HISTORY_LOADED,
306 content::Source<Profile>(profile_));
307 }
308 }
309}
310
311// Restoring from the History DB -----------------------------------------------
312
313void InMemoryURLIndex::ScheduleRebuildFromHistory() {
314 HistoryService* service =
315 HistoryServiceFactory::GetForProfile(profile_,
316 Profile::EXPLICIT_ACCESS);
317 service->ScheduleDBTask(
Torne (Richard Coles)5f1c9432014-08-12 13:47:38 +0100318 scoped_ptr<history::HistoryDBTask>(
319 new InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask(
320 this, languages_, scheme_whitelist_)),
Ben Murdoch116680a2014-07-20 18:25:52 -0700321 &cache_reader_tracker_);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000322}
323
324void InMemoryURLIndex::DoneRebuidingPrivateDataFromHistoryDB(
325 bool succeeded,
326 scoped_refptr<URLIndexPrivateData> private_data) {
327 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
328 if (succeeded) {
Ben Murdoch116680a2014-07-20 18:25:52 -0700329 private_data_tracker_.TryCancelAll();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000330 private_data_ = private_data;
331 PostSaveToCacheFileTask(); // Cache the newly rebuilt index.
332 } else {
333 private_data_->Clear(); // Dump the old private data.
334 // There is no need to do anything with the cache file as it was deleted
335 // when the rebuild from the history operation was kicked off.
336 }
337 restored_ = true;
338 if (restore_cache_observer_)
339 restore_cache_observer_->OnCacheRestoreFinished(succeeded);
340}
341
342void InMemoryURLIndex::RebuildFromHistory(HistoryDatabase* history_db) {
Ben Murdoch116680a2014-07-20 18:25:52 -0700343 private_data_tracker_.TryCancelAll();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000344 private_data_ = URLIndexPrivateData::RebuildFromHistory(history_db,
345 languages_,
346 scheme_whitelist_);
347}
348
349// Saving to Cache -------------------------------------------------------------
350
351void InMemoryURLIndex::PostSaveToCacheFileTask() {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000352 base::FilePath path;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000353 if (!GetCacheFilePath(&path))
354 return;
355 // If there is anything in our private data then make a copy of it and tell
356 // it to save itself to a file.
357 if (private_data_.get() && !private_data_->Empty()) {
358 // Note that ownership of the copy of our private data is passed to the
359 // completion closure below.
360 scoped_refptr<URLIndexPrivateData> private_data_copy =
361 private_data_->Duplicate();
362 content::BrowserThread::PostTaskAndReplyWithResult<bool>(
363 content::BrowserThread::FILE, FROM_HERE,
364 base::Bind(&URLIndexPrivateData::WritePrivateDataToCacheFileTask,
365 private_data_copy, path),
366 base::Bind(&InMemoryURLIndex::OnCacheSaveDone, AsWeakPtr()));
367 } else {
368 // If there is no data in our index then delete any existing cache file.
369 content::BrowserThread::PostBlockingPoolTask(
370 FROM_HERE,
371 base::Bind(DeleteCacheFile, path));
372 }
373}
374
375void InMemoryURLIndex::OnCacheSaveDone(bool succeeded) {
376 if (save_cache_observer_)
377 save_cache_observer_->OnCacheSaveFinished(succeeded);
378}
379
380} // namespace history