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/debug/trace_event.h" 8 #include "base/file_util.h" 9 #include "base/strings/utf_string_conversions.h" 10 #include "chrome/browser/bookmarks/bookmark_model_factory.h" 11 #include "chrome/browser/chrome_notification_types.h" 12 #include "chrome/browser/history/history_notifications.h" 13 #include "chrome/browser/history/history_service.h" 14 #include "chrome/browser/history/history_service_factory.h" 15 #include "chrome/browser/history/url_database.h" 16 #include "chrome/browser/history/url_index_private_data.h" 17 #include "chrome/browser/profiles/profile.h" 18 #include "chrome/common/url_constants.h" 19 #include "components/bookmarks/browser/bookmark_model.h" 20 #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 25 using in_memory_url_index::InMemoryURLIndexCacheItem; 26 27 namespace 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. 31 void DeleteCacheFile(const base::FilePath& path) { 32 DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 33 base::DeleteFile(path, false); 34 } 35 36 // Initializes a whitelist of URL schemes. 37 void InitializeSchemeWhitelist(std::set<std::string>* whitelist) { 38 DCHECK(whitelist); 39 if (!whitelist->empty()) 40 return; // Nothing to do, already initialized. 41 whitelist->insert(std::string(url::kAboutScheme)); 42 whitelist->insert(std::string(content::kChromeUIScheme)); 43 whitelist->insert(std::string(url::kFileScheme)); 44 whitelist->insert(std::string(url::kFtpScheme)); 45 whitelist->insert(std::string(url::kHttpScheme)); 46 whitelist->insert(std::string(url::kHttpsScheme)); 47 whitelist->insert(std::string(url::kMailToScheme)); 48 } 49 50 // Restore/SaveCacheObserver --------------------------------------------------- 51 52 InMemoryURLIndex::RestoreCacheObserver::~RestoreCacheObserver() {} 53 54 InMemoryURLIndex::SaveCacheObserver::~SaveCacheObserver() {} 55 56 // RebuildPrivateDataFromHistoryDBTask ----------------------------------------- 57 58 InMemoryURLIndex::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 69 bool InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::RunOnDBThread( 70 HistoryBackend* backend, 71 HistoryDatabase* db) { 72 data_ = URLIndexPrivateData::RebuildFromHistory(db, languages_, 73 scheme_whitelist_); 74 succeeded_ = data_.get() && !data_->Empty(); 75 if (!succeeded_ && data_.get()) 76 data_->Clear(); 77 return true; 78 } 79 80 void InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask:: 81 DoneRunOnMainThread() { 82 index_->DoneRebuidingPrivateDataFromHistoryDB(succeeded_, data_); 83 } 84 85 InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask:: 86 ~RebuildPrivateDataFromHistoryDBTask() { 87 } 88 89 // InMemoryURLIndex ------------------------------------------------------------ 90 91 InMemoryURLIndex::InMemoryURLIndex(Profile* profile, 92 const base::FilePath& history_dir, 93 const std::string& languages, 94 HistoryClient* history_client) 95 : profile_(profile), 96 history_client_(history_client), 97 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. 117 InMemoryURLIndex::InMemoryURLIndex() 118 : profile_(NULL), 119 history_client_(NULL), 120 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 129 InMemoryURLIndex::~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 135 void InMemoryURLIndex::Init() { 136 PostRestoreFromCacheFileTask(); 137 } 138 139 void InMemoryURLIndex::ShutDown() { 140 registrar_.RemoveAll(); 141 cache_reader_consumer_.CancelAllRequests(); 142 shutdown_ = true; 143 base::FilePath path; 144 if (!GetCacheFilePath(&path)) 145 return; 146 private_data_->CancelPendingUpdates(); 147 URLIndexPrivateData::WritePrivateDataToCacheFileTask(private_data_, path); 148 needs_to_be_cached_ = false; 149 } 150 151 void InMemoryURLIndex::ClearPrivateData() { 152 private_data_->Clear(); 153 } 154 155 bool InMemoryURLIndex::GetCacheFilePath(base::FilePath* file_path) { 156 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 164 ScoredHistoryMatches InMemoryURLIndex::HistoryItemsForTerms( 165 const base::string16& term_string, 166 size_t cursor_position, 167 size_t max_matches) { 168 return private_data_->HistoryItemsForTerms( 169 term_string, 170 cursor_position, 171 max_matches, 172 languages_, 173 history_client_); 174 } 175 176 // Updating -------------------------------------------------------------------- 177 178 void InMemoryURLIndex::DeleteURL(const GURL& url) { 179 private_data_->DeleteURL(url); 180 } 181 182 void 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 209 void InMemoryURLIndex::OnURLVisited(const URLVisitedDetails* details) { 210 HistoryService* service = 211 HistoryServiceFactory::GetForProfile(profile_, 212 Profile::EXPLICIT_ACCESS); 213 needs_to_be_cached_ |= private_data_->UpdateURL( 214 service, details->row, languages_, scheme_whitelist_); 215 } 216 217 void InMemoryURLIndex::OnURLsModified(const URLsModifiedDetails* details) { 218 HistoryService* service = 219 HistoryServiceFactory::GetForProfile(profile_, 220 Profile::EXPLICIT_ACCESS); 221 for (URLRows::const_iterator row = details->changed_urls.begin(); 222 row != details->changed_urls.end(); ++row) 223 needs_to_be_cached_ |= 224 private_data_->UpdateURL(service, *row, languages_, scheme_whitelist_); 225 } 226 227 void InMemoryURLIndex::OnURLsDeleted(const URLsDeletedDetails* details) { 228 if (details->all_history) { 229 ClearPrivateData(); 230 needs_to_be_cached_ = true; 231 } else { 232 for (URLRows::const_iterator row = details->rows.begin(); 233 row != details->rows.end(); ++row) 234 needs_to_be_cached_ |= private_data_->DeleteURL(row->url()); 235 } 236 // If we made changes, destroy the previous cache. Otherwise, if we go 237 // through an unclean shutdown (and therefore fail to write a new cache file), 238 // when Chrome restarts and we restore from the previous cache, we'll end up 239 // searching over URLs that may be deleted. This would be wrong, and 240 // surprising to the user who bothered to delete some URLs from his/her 241 // history. In this situation, deleting the cache is a better solution than 242 // writing a new cache (after deleting the URLs from the in-memory structure) 243 // because deleting the cache forces it to be rebuilt from history upon 244 // startup. If we instead write a new, updated cache then at the time of next 245 // startup (after an unclean shutdown) we will not rebuild the in-memory data 246 // structures from history but rather use the cache. This solution is 247 // mediocre because this cache may not have the most-recently-visited URLs 248 // in it (URLs visited after user deleted some URLs from history), which 249 // would be odd and confusing. It's better to force a rebuild. 250 base::FilePath path; 251 if (needs_to_be_cached_ && GetCacheFilePath(&path)) { 252 content::BrowserThread::PostBlockingPoolTask( 253 FROM_HERE, base::Bind(DeleteCacheFile, path)); 254 } 255 } 256 257 // Restoring from Cache -------------------------------------------------------- 258 259 void InMemoryURLIndex::PostRestoreFromCacheFileTask() { 260 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 261 TRACE_EVENT0("browser", "InMemoryURLIndex::PostRestoreFromCacheFileTask"); 262 263 base::FilePath path; 264 if (!GetCacheFilePath(&path) || shutdown_) { 265 restored_ = true; 266 if (restore_cache_observer_) 267 restore_cache_observer_->OnCacheRestoreFinished(false); 268 return; 269 } 270 271 content::BrowserThread::PostTaskAndReplyWithResult 272 <scoped_refptr<URLIndexPrivateData> >( 273 content::BrowserThread::FILE, FROM_HERE, 274 base::Bind(&URLIndexPrivateData::RestoreFromFile, path, languages_), 275 base::Bind(&InMemoryURLIndex::OnCacheLoadDone, AsWeakPtr())); 276 } 277 278 void InMemoryURLIndex::OnCacheLoadDone( 279 scoped_refptr<URLIndexPrivateData> private_data) { 280 if (private_data.get() && !private_data->Empty()) { 281 private_data_ = private_data; 282 restored_ = true; 283 if (restore_cache_observer_) 284 restore_cache_observer_->OnCacheRestoreFinished(true); 285 } else if (profile_) { 286 // When unable to restore from the cache file delete the cache file, if 287 // it exists, and then rebuild from the history database if it's available, 288 // otherwise wait until the history database loaded and then rebuild. 289 base::FilePath path; 290 if (!GetCacheFilePath(&path) || shutdown_) 291 return; 292 content::BrowserThread::PostBlockingPoolTask( 293 FROM_HERE, base::Bind(DeleteCacheFile, path)); 294 HistoryService* service = 295 HistoryServiceFactory::GetForProfileWithoutCreating(profile_); 296 if (service && service->backend_loaded()) { 297 ScheduleRebuildFromHistory(); 298 } else { 299 registrar_.Add(this, chrome::NOTIFICATION_HISTORY_LOADED, 300 content::Source<Profile>(profile_)); 301 } 302 } 303 } 304 305 // Restoring from the History DB ----------------------------------------------- 306 307 void InMemoryURLIndex::ScheduleRebuildFromHistory() { 308 HistoryService* service = 309 HistoryServiceFactory::GetForProfile(profile_, 310 Profile::EXPLICIT_ACCESS); 311 service->ScheduleDBTask( 312 new InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask( 313 this, languages_, scheme_whitelist_), 314 &cache_reader_consumer_); 315 } 316 317 void InMemoryURLIndex::DoneRebuidingPrivateDataFromHistoryDB( 318 bool succeeded, 319 scoped_refptr<URLIndexPrivateData> private_data) { 320 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 321 if (succeeded) { 322 private_data_ = private_data; 323 PostSaveToCacheFileTask(); // Cache the newly rebuilt index. 324 } else { 325 private_data_->Clear(); // Dump the old private data. 326 // There is no need to do anything with the cache file as it was deleted 327 // when the rebuild from the history operation was kicked off. 328 } 329 restored_ = true; 330 if (restore_cache_observer_) 331 restore_cache_observer_->OnCacheRestoreFinished(succeeded); 332 } 333 334 void InMemoryURLIndex::RebuildFromHistory(HistoryDatabase* history_db) { 335 private_data_ = URLIndexPrivateData::RebuildFromHistory(history_db, 336 languages_, 337 scheme_whitelist_); 338 } 339 340 // Saving to Cache ------------------------------------------------------------- 341 342 void InMemoryURLIndex::PostSaveToCacheFileTask() { 343 base::FilePath path; 344 if (!GetCacheFilePath(&path)) 345 return; 346 // If there is anything in our private data then make a copy of it and tell 347 // it to save itself to a file. 348 if (private_data_.get() && !private_data_->Empty()) { 349 // Note that ownership of the copy of our private data is passed to the 350 // completion closure below. 351 scoped_refptr<URLIndexPrivateData> private_data_copy = 352 private_data_->Duplicate(); 353 content::BrowserThread::PostTaskAndReplyWithResult<bool>( 354 content::BrowserThread::FILE, FROM_HERE, 355 base::Bind(&URLIndexPrivateData::WritePrivateDataToCacheFileTask, 356 private_data_copy, path), 357 base::Bind(&InMemoryURLIndex::OnCacheSaveDone, AsWeakPtr())); 358 } else { 359 // If there is no data in our index then delete any existing cache file. 360 content::BrowserThread::PostBlockingPoolTask( 361 FROM_HERE, 362 base::Bind(DeleteCacheFile, path)); 363 } 364 } 365 366 void InMemoryURLIndex::OnCacheSaveDone(bool succeeded) { 367 if (save_cache_observer_) 368 save_cache_observer_->OnCacheSaveFinished(succeeded); 369 } 370 371 } // namespace history 372