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.
5 #include "chrome/browser/autocomplete/in_memory_url_index.h"
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/autocomplete/url_index_private_data.h"
11 #include "chrome/common/url_constants.h"
12 #include "components/history/core/browser/history_service.h"
13 #include "components/history/core/browser/url_database.h"
14 #include "content/public/browser/browser_thread.h"
16 using in_memory_url_index::InMemoryURLIndexCacheItem
;
18 // Called by DoSaveToCacheFile to delete any old cache file at |path| when
19 // there is no private data to save. Runs on the FILE thread.
20 void DeleteCacheFile(const base::FilePath
& path
) {
21 DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
22 base::DeleteFile(path
, false);
25 // Initializes a whitelist of URL schemes.
26 void InitializeSchemeWhitelist(std::set
<std::string
>* whitelist
) {
28 if (!whitelist
->empty())
29 return; // Nothing to do, already initialized.
30 whitelist
->insert(std::string(url::kAboutScheme
));
31 whitelist
->insert(std::string(content::kChromeUIScheme
));
32 whitelist
->insert(std::string(url::kFileScheme
));
33 whitelist
->insert(std::string(url::kFtpScheme
));
34 whitelist
->insert(std::string(url::kHttpScheme
));
35 whitelist
->insert(std::string(url::kHttpsScheme
));
36 whitelist
->insert(std::string(url::kMailToScheme
));
39 // Restore/SaveCacheObserver ---------------------------------------------------
41 InMemoryURLIndex::RestoreCacheObserver::~RestoreCacheObserver() {
44 InMemoryURLIndex::SaveCacheObserver::~SaveCacheObserver() {
47 // RebuildPrivateDataFromHistoryDBTask -----------------------------------------
49 InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::
50 RebuildPrivateDataFromHistoryDBTask(
51 InMemoryURLIndex
* index
,
52 const std::string
& languages
,
53 const std::set
<std::string
>& scheme_whitelist
)
55 languages_(languages
),
56 scheme_whitelist_(scheme_whitelist
),
60 bool InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::RunOnDBThread(
61 history::HistoryBackend
* backend
,
62 history::HistoryDatabase
* db
) {
63 data_
= URLIndexPrivateData::RebuildFromHistory(db
, languages_
,
65 succeeded_
= data_
.get() && !data_
->Empty();
66 if (!succeeded_
&& data_
.get())
71 void InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::
72 DoneRunOnMainThread() {
73 index_
->DoneRebuidingPrivateDataFromHistoryDB(succeeded_
, data_
);
76 InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::
77 ~RebuildPrivateDataFromHistoryDBTask() {
80 // InMemoryURLIndex ------------------------------------------------------------
82 InMemoryURLIndex::InMemoryURLIndex(bookmarks::BookmarkModel
* bookmark_model
,
83 history::HistoryService
* history_service
,
84 const base::FilePath
& history_dir
,
85 const std::string
& languages
)
86 : bookmark_model_(bookmark_model
),
87 history_service_(history_service
),
88 history_dir_(history_dir
),
89 languages_(languages
),
90 private_data_(new URLIndexPrivateData
),
91 restore_cache_observer_(NULL
),
92 save_cache_observer_(NULL
),
95 needs_to_be_cached_(false),
96 listen_to_history_service_loaded_(false) {
97 InitializeSchemeWhitelist(&scheme_whitelist_
);
98 // TODO(mrossetti): Register for language change notifications.
100 history_service_
->AddObserver(this);
103 InMemoryURLIndex::~InMemoryURLIndex() {
104 // If there was a history directory (which there won't be for some unit tests)
105 // then insure that the cache has already been saved.
106 DCHECK(history_dir_
.empty() || !needs_to_be_cached_
);
107 DCHECK(!history_service_
);
111 void InMemoryURLIndex::Init() {
112 PostRestoreFromCacheFileTask();
115 void InMemoryURLIndex::ClearPrivateData() {
116 private_data_
->Clear();
119 bool InMemoryURLIndex::GetCacheFilePath(base::FilePath
* file_path
) {
120 if (history_dir_
.empty())
122 *file_path
= history_dir_
.Append(FILE_PATH_LITERAL("History Provider Cache"));
126 // Querying --------------------------------------------------------------------
128 ScoredHistoryMatches
InMemoryURLIndex::HistoryItemsForTerms(
129 const base::string16
& term_string
,
130 size_t cursor_position
,
131 size_t max_matches
) {
132 return private_data_
->HistoryItemsForTerms(
133 term_string
, cursor_position
, max_matches
, languages_
, bookmark_model_
);
136 // Updating --------------------------------------------------------------------
138 void InMemoryURLIndex::DeleteURL(const GURL
& url
) {
139 private_data_
->DeleteURL(url
);
142 void InMemoryURLIndex::OnURLVisited(history::HistoryService
* history_service
,
143 ui::PageTransition transition
,
144 const history::URLRow
& row
,
145 const history::RedirectList
& redirects
,
146 base::Time visit_time
) {
147 DCHECK_EQ(history_service_
, history_service
);
148 needs_to_be_cached_
|= private_data_
->UpdateURL(history_service_
,
152 &private_data_tracker_
);
155 void InMemoryURLIndex::OnURLsModified(history::HistoryService
* history_service
,
156 const history::URLRows
& changed_urls
) {
157 DCHECK_EQ(history_service_
, history_service
);
158 for (const auto& row
: changed_urls
) {
159 needs_to_be_cached_
|= private_data_
->UpdateURL(history_service_
,
163 &private_data_tracker_
);
167 void InMemoryURLIndex::OnURLsDeleted(history::HistoryService
* history_service
,
170 const history::URLRows
& deleted_rows
,
171 const std::set
<GURL
>& favicon_urls
) {
174 needs_to_be_cached_
= true;
176 for (const auto& row
: deleted_rows
)
177 needs_to_be_cached_
|= private_data_
->DeleteURL(row
.url());
179 // If we made changes, destroy the previous cache. Otherwise, if we go
180 // through an unclean shutdown (and therefore fail to write a new cache file),
181 // when Chrome restarts and we restore from the previous cache, we'll end up
182 // searching over URLs that may be deleted. This would be wrong, and
183 // surprising to the user who bothered to delete some URLs from his/her
184 // history. In this situation, deleting the cache is a better solution than
185 // writing a new cache (after deleting the URLs from the in-memory structure)
186 // because deleting the cache forces it to be rebuilt from history upon
187 // startup. If we instead write a new, updated cache then at the time of next
188 // startup (after an unclean shutdown) we will not rebuild the in-memory data
189 // structures from history but rather use the cache. This solution is
190 // mediocre because this cache may not have the most-recently-visited URLs
191 // in it (URLs visited after user deleted some URLs from history), which
192 // would be odd and confusing. It's better to force a rebuild.
194 if (needs_to_be_cached_
&& GetCacheFilePath(&path
)) {
195 content::BrowserThread::PostBlockingPoolTask(
196 FROM_HERE
, base::Bind(DeleteCacheFile
, path
));
200 void InMemoryURLIndex::OnHistoryServiceLoaded(
201 history::HistoryService
* history_service
) {
202 if (listen_to_history_service_loaded_
)
203 ScheduleRebuildFromHistory();
204 listen_to_history_service_loaded_
= false;
207 // Restoring from Cache --------------------------------------------------------
209 void InMemoryURLIndex::PostRestoreFromCacheFileTask() {
210 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
211 TRACE_EVENT0("browser", "InMemoryURLIndex::PostRestoreFromCacheFileTask");
214 if (!GetCacheFilePath(&path
) || shutdown_
) {
216 if (restore_cache_observer_
)
217 restore_cache_observer_
->OnCacheRestoreFinished(false);
221 content::BrowserThread::PostTaskAndReplyWithResult
222 <scoped_refptr
<URLIndexPrivateData
> >(
223 content::BrowserThread::FILE, FROM_HERE
,
224 base::Bind(&URLIndexPrivateData::RestoreFromFile
, path
, languages_
),
225 base::Bind(&InMemoryURLIndex::OnCacheLoadDone
, AsWeakPtr()));
228 void InMemoryURLIndex::OnCacheLoadDone(
229 scoped_refptr
<URLIndexPrivateData
> private_data
) {
230 if (private_data
.get() && !private_data
->Empty()) {
231 private_data_tracker_
.TryCancelAll();
232 private_data_
= private_data
;
234 if (restore_cache_observer_
)
235 restore_cache_observer_
->OnCacheRestoreFinished(true);
236 } else if (history_service_
) {
237 // When unable to restore from the cache file delete the cache file, if
238 // it exists, and then rebuild from the history database if it's available,
239 // otherwise wait until the history database loaded and then rebuild.
241 if (!GetCacheFilePath(&path
) || shutdown_
)
243 content::BrowserThread::PostBlockingPoolTask(
244 FROM_HERE
, base::Bind(DeleteCacheFile
, path
));
245 if (history_service_
->backend_loaded()) {
246 ScheduleRebuildFromHistory();
248 listen_to_history_service_loaded_
= true;
253 // Cleanup ---------------------------------------------------------------------
255 void InMemoryURLIndex::Shutdown() {
256 if (history_service_
) {
257 history_service_
->RemoveObserver(this);
258 history_service_
= nullptr;
260 cache_reader_tracker_
.TryCancelAll();
263 if (!GetCacheFilePath(&path
))
265 private_data_tracker_
.TryCancelAll();
266 URLIndexPrivateData::WritePrivateDataToCacheFileTask(private_data_
, path
);
267 needs_to_be_cached_
= false;
270 // Restoring from the History DB -----------------------------------------------
272 void InMemoryURLIndex::ScheduleRebuildFromHistory() {
273 DCHECK(history_service_
);
274 history_service_
->ScheduleDBTask(
275 scoped_ptr
<history::HistoryDBTask
>(
276 new InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask(
277 this, languages_
, scheme_whitelist_
)),
278 &cache_reader_tracker_
);
281 void InMemoryURLIndex::DoneRebuidingPrivateDataFromHistoryDB(
283 scoped_refptr
<URLIndexPrivateData
> private_data
) {
284 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
286 private_data_tracker_
.TryCancelAll();
287 private_data_
= private_data
;
288 PostSaveToCacheFileTask(); // Cache the newly rebuilt index.
290 private_data_
->Clear(); // Dump the old private data.
291 // There is no need to do anything with the cache file as it was deleted
292 // when the rebuild from the history operation was kicked off.
295 if (restore_cache_observer_
)
296 restore_cache_observer_
->OnCacheRestoreFinished(succeeded
);
299 void InMemoryURLIndex::RebuildFromHistory(
300 history::HistoryDatabase
* history_db
) {
301 private_data_tracker_
.TryCancelAll();
302 private_data_
= URLIndexPrivateData::RebuildFromHistory(history_db
,
307 // Saving to Cache -------------------------------------------------------------
309 void InMemoryURLIndex::PostSaveToCacheFileTask() {
311 if (!GetCacheFilePath(&path
))
313 // If there is anything in our private data then make a copy of it and tell
314 // it to save itself to a file.
315 if (private_data_
.get() && !private_data_
->Empty()) {
316 // Note that ownership of the copy of our private data is passed to the
317 // completion closure below.
318 scoped_refptr
<URLIndexPrivateData
> private_data_copy
=
319 private_data_
->Duplicate();
320 content::BrowserThread::PostTaskAndReplyWithResult
<bool>(
321 content::BrowserThread::FILE, FROM_HERE
,
322 base::Bind(&URLIndexPrivateData::WritePrivateDataToCacheFileTask
,
323 private_data_copy
, path
),
324 base::Bind(&InMemoryURLIndex::OnCacheSaveDone
, AsWeakPtr()));
326 // If there is no data in our index then delete any existing cache file.
327 content::BrowserThread::PostBlockingPoolTask(
329 base::Bind(DeleteCacheFile
, path
));
333 void InMemoryURLIndex::OnCacheSaveDone(bool succeeded
) {
334 if (save_cache_observer_
)
335 save_cache_observer_
->OnCacheSaveFinished(succeeded
);