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 "components/omnibox/browser/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 "components/history/core/browser/history_service.h"
11 #include "components/history/core/browser/url_database.h"
12 #include "components/omnibox/browser/url_index_private_data.h"
14 using in_memory_url_index::InMemoryURLIndexCacheItem
;
16 // Initializes a whitelist of URL schemes.
17 void InitializeSchemeWhitelist(
19 const SchemeSet
& client_schemes_to_whitelist
) {
21 if (!whitelist
->empty())
22 return; // Nothing to do, already initialized.
24 whitelist
->insert(client_schemes_to_whitelist
.begin(),
25 client_schemes_to_whitelist
.end());
27 whitelist
->insert(std::string(url::kAboutScheme
));
28 whitelist
->insert(std::string(url::kFileScheme
));
29 whitelist
->insert(std::string(url::kFtpScheme
));
30 whitelist
->insert(std::string(url::kHttpScheme
));
31 whitelist
->insert(std::string(url::kHttpsScheme
));
32 whitelist
->insert(std::string(url::kMailToScheme
));
35 // Restore/SaveCacheObserver ---------------------------------------------------
37 InMemoryURLIndex::RestoreCacheObserver::~RestoreCacheObserver() {
40 InMemoryURLIndex::SaveCacheObserver::~SaveCacheObserver() {
43 // RebuildPrivateDataFromHistoryDBTask -----------------------------------------
45 InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::
46 RebuildPrivateDataFromHistoryDBTask(
47 InMemoryURLIndex
* index
,
48 const std::string
& languages
,
49 const SchemeSet
& scheme_whitelist
)
51 languages_(languages
),
52 scheme_whitelist_(scheme_whitelist
),
56 bool InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::RunOnDBThread(
57 history::HistoryBackend
* backend
,
58 history::HistoryDatabase
* db
) {
59 data_
= URLIndexPrivateData::RebuildFromHistory(db
, languages_
,
61 succeeded_
= data_
.get() && !data_
->Empty();
62 if (!succeeded_
&& data_
.get())
67 void InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::
68 DoneRunOnMainThread() {
69 index_
->DoneRebuidingPrivateDataFromHistoryDB(succeeded_
, data_
);
72 InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::
73 ~RebuildPrivateDataFromHistoryDBTask() {
76 // InMemoryURLIndex ------------------------------------------------------------
78 InMemoryURLIndex::InMemoryURLIndex(
79 bookmarks::BookmarkModel
* bookmark_model
,
80 history::HistoryService
* history_service
,
81 base::SequencedWorkerPool
* worker_pool
,
82 const base::FilePath
& history_dir
,
83 const std::string
& languages
,
84 const SchemeSet
& client_schemes_to_whitelist
)
85 : bookmark_model_(bookmark_model
),
86 history_service_(history_service
),
87 history_dir_(history_dir
),
88 languages_(languages
),
89 private_data_(new URLIndexPrivateData
),
90 restore_cache_observer_(NULL
),
91 save_cache_observer_(NULL
),
93 worker_pool
->GetSequencedTaskRunner(worker_pool
->GetSequenceToken())),
96 needs_to_be_cached_(false),
97 listen_to_history_service_loaded_(false) {
98 InitializeSchemeWhitelist(&scheme_whitelist_
, client_schemes_to_whitelist
);
99 // TODO(mrossetti): Register for language change notifications.
100 if (history_service_
)
101 history_service_
->AddObserver(this);
104 InMemoryURLIndex::~InMemoryURLIndex() {
105 // If there was a history directory (which there won't be for some unit tests)
106 // then insure that the cache has already been saved.
107 DCHECK(history_dir_
.empty() || !needs_to_be_cached_
);
108 DCHECK(!history_service_
);
112 void InMemoryURLIndex::Init() {
113 PostRestoreFromCacheFileTask();
116 void InMemoryURLIndex::ClearPrivateData() {
117 private_data_
->Clear();
120 bool InMemoryURLIndex::GetCacheFilePath(base::FilePath
* file_path
) {
121 if (history_dir_
.empty())
123 *file_path
= history_dir_
.Append(FILE_PATH_LITERAL("History Provider Cache"));
127 // Querying --------------------------------------------------------------------
129 ScoredHistoryMatches
InMemoryURLIndex::HistoryItemsForTerms(
130 const base::string16
& term_string
,
131 size_t cursor_position
,
132 size_t max_matches
) {
133 return private_data_
->HistoryItemsForTerms(
134 term_string
, cursor_position
, max_matches
, languages_
, bookmark_model_
);
137 // Updating --------------------------------------------------------------------
139 void InMemoryURLIndex::DeleteURL(const GURL
& url
) {
140 private_data_
->DeleteURL(url
);
143 void InMemoryURLIndex::OnURLVisited(history::HistoryService
* history_service
,
144 ui::PageTransition transition
,
145 const history::URLRow
& row
,
146 const history::RedirectList
& redirects
,
147 base::Time visit_time
) {
148 DCHECK_EQ(history_service_
, history_service
);
149 needs_to_be_cached_
|= private_data_
->UpdateURL(history_service_
,
153 &private_data_tracker_
);
156 void InMemoryURLIndex::OnURLsModified(history::HistoryService
* history_service
,
157 const history::URLRows
& changed_urls
) {
158 DCHECK_EQ(history_service_
, history_service
);
159 for (const auto& row
: changed_urls
) {
160 needs_to_be_cached_
|= private_data_
->UpdateURL(history_service_
,
164 &private_data_tracker_
);
168 void InMemoryURLIndex::OnURLsDeleted(history::HistoryService
* history_service
,
171 const history::URLRows
& deleted_rows
,
172 const std::set
<GURL
>& favicon_urls
) {
175 needs_to_be_cached_
= true;
177 for (const auto& row
: deleted_rows
)
178 needs_to_be_cached_
|= private_data_
->DeleteURL(row
.url());
180 // If we made changes, destroy the previous cache. Otherwise, if we go
181 // through an unclean shutdown (and therefore fail to write a new cache file),
182 // when Chrome restarts and we restore from the previous cache, we'll end up
183 // searching over URLs that may be deleted. This would be wrong, and
184 // surprising to the user who bothered to delete some URLs from his/her
185 // history. In this situation, deleting the cache is a better solution than
186 // writing a new cache (after deleting the URLs from the in-memory structure)
187 // because deleting the cache forces it to be rebuilt from history upon
188 // startup. If we instead write a new, updated cache then at the time of next
189 // startup (after an unclean shutdown) we will not rebuild the in-memory data
190 // structures from history but rather use the cache. This solution is
191 // mediocre because this cache may not have the most-recently-visited URLs
192 // in it (URLs visited after user deleted some URLs from history), which
193 // would be odd and confusing. It's better to force a rebuild.
195 if (needs_to_be_cached_
&& GetCacheFilePath(&path
))
196 task_runner_
->PostTask(
198 base::Bind(base::IgnoreResult(base::DeleteFile
), path
, false));
201 void InMemoryURLIndex::OnHistoryServiceLoaded(
202 history::HistoryService
* history_service
) {
203 if (listen_to_history_service_loaded_
)
204 ScheduleRebuildFromHistory();
205 listen_to_history_service_loaded_
= false;
208 // Restoring from Cache --------------------------------------------------------
210 void InMemoryURLIndex::PostRestoreFromCacheFileTask() {
211 DCHECK(thread_checker_
.CalledOnValidThread());
212 TRACE_EVENT0("browser", "InMemoryURLIndex::PostRestoreFromCacheFileTask");
215 if (!GetCacheFilePath(&path
) || shutdown_
) {
217 if (restore_cache_observer_
)
218 restore_cache_observer_
->OnCacheRestoreFinished(false);
222 base::PostTaskAndReplyWithResult(
225 base::Bind(&URLIndexPrivateData::RestoreFromFile
, path
, languages_
),
226 base::Bind(&InMemoryURLIndex::OnCacheLoadDone
, AsWeakPtr()));
229 void InMemoryURLIndex::OnCacheLoadDone(
230 scoped_refptr
<URLIndexPrivateData
> private_data
) {
231 if (private_data
.get() && !private_data
->Empty()) {
232 private_data_tracker_
.TryCancelAll();
233 private_data_
= private_data
;
235 if (restore_cache_observer_
)
236 restore_cache_observer_
->OnCacheRestoreFinished(true);
237 } else if (history_service_
) {
238 // When unable to restore from the cache file delete the cache file, if
239 // it exists, and then rebuild from the history database if it's available,
240 // otherwise wait until the history database loaded and then rebuild.
242 if (!GetCacheFilePath(&path
) || shutdown_
)
244 task_runner_
->PostTask(
246 base::Bind(base::IgnoreResult(base::DeleteFile
), path
, false));
247 if (history_service_
->backend_loaded()) {
248 ScheduleRebuildFromHistory();
250 listen_to_history_service_loaded_
= true;
255 // Cleanup ---------------------------------------------------------------------
257 void InMemoryURLIndex::Shutdown() {
258 if (history_service_
) {
259 history_service_
->RemoveObserver(this);
260 history_service_
= nullptr;
262 cache_reader_tracker_
.TryCancelAll();
265 if (!GetCacheFilePath(&path
))
267 private_data_tracker_
.TryCancelAll();
268 task_runner_
->PostTask(
272 &URLIndexPrivateData::WritePrivateDataToCacheFileTask
),
273 private_data_
, path
));
274 needs_to_be_cached_
= false;
277 // Restoring from the History DB -----------------------------------------------
279 void InMemoryURLIndex::ScheduleRebuildFromHistory() {
280 DCHECK(history_service_
);
281 history_service_
->ScheduleDBTask(
282 scoped_ptr
<history::HistoryDBTask
>(
283 new InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask(
284 this, languages_
, scheme_whitelist_
)),
285 &cache_reader_tracker_
);
288 void InMemoryURLIndex::DoneRebuidingPrivateDataFromHistoryDB(
290 scoped_refptr
<URLIndexPrivateData
> private_data
) {
291 DCHECK(thread_checker_
.CalledOnValidThread());
293 private_data_tracker_
.TryCancelAll();
294 private_data_
= private_data
;
295 PostSaveToCacheFileTask(); // Cache the newly rebuilt index.
297 private_data_
->Clear(); // Dump the old private data.
298 // There is no need to do anything with the cache file as it was deleted
299 // when the rebuild from the history operation was kicked off.
302 if (restore_cache_observer_
)
303 restore_cache_observer_
->OnCacheRestoreFinished(succeeded
);
306 void InMemoryURLIndex::RebuildFromHistory(
307 history::HistoryDatabase
* history_db
) {
308 private_data_tracker_
.TryCancelAll();
309 private_data_
= URLIndexPrivateData::RebuildFromHistory(history_db
,
314 // Saving to Cache -------------------------------------------------------------
316 void InMemoryURLIndex::PostSaveToCacheFileTask() {
318 if (!GetCacheFilePath(&path
))
320 // If there is anything in our private data then make a copy of it and tell
321 // it to save itself to a file.
322 if (private_data_
.get() && !private_data_
->Empty()) {
323 // Note that ownership of the copy of our private data is passed to the
324 // completion closure below.
325 scoped_refptr
<URLIndexPrivateData
> private_data_copy
=
326 private_data_
->Duplicate();
327 base::PostTaskAndReplyWithResult(
330 base::Bind(&URLIndexPrivateData::WritePrivateDataToCacheFileTask
,
331 private_data_copy
, path
),
332 base::Bind(&InMemoryURLIndex::OnCacheSaveDone
, AsWeakPtr()));
334 // If there is no data in our index then delete any existing cache file.
335 task_runner_
->PostTask(
337 base::Bind(base::IgnoreResult(base::DeleteFile
), path
, false));
341 void InMemoryURLIndex::OnCacheSaveDone(bool succeeded
) {
342 if (save_cache_observer_
)
343 save_cache_observer_
->OnCacheSaveFinished(succeeded
);