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.h" 11 #include "chrome/browser/bookmarks/bookmark_model_factory.h" 12 #include "chrome/browser/bookmarks/bookmark_service.h" 13 #include "chrome/browser/chrome_notification_types.h" 14 #include "chrome/browser/history/history_notifications.h" 15 #include "chrome/browser/history/history_service.h" 16 #include "chrome/browser/history/history_service_factory.h" 17 #include "chrome/browser/history/url_database.h" 18 #include "chrome/browser/history/url_index_private_data.h" 19 #include "chrome/browser/profiles/profile.h" 20 #include "chrome/common/url_constants.h" 21 #include "content/public/browser/browser_thread.h" 22 #include "content/public/browser/notification_details.h" 23 #include "content/public/browser/notification_service.h" 24 #include "content/public/browser/notification_source.h" 25 26 using in_memory_url_index::InMemoryURLIndexCacheItem; 27 28 namespace history { 29 30 // Called by DoSaveToCacheFile to delete any old cache file at |path| when 31 // there is no private data to save. Runs on the FILE thread. 32 void DeleteCacheFile(const base::FilePath& path) { 33 DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 34 base::DeleteFile(path, false); 35 } 36 37 // Initializes a whitelist of URL schemes. 38 void InitializeSchemeWhitelist(std::set<std::string>* whitelist) { 39 DCHECK(whitelist); 40 if (!whitelist->empty()) 41 return; // Nothing to do, already initialized. 42 whitelist->insert(std::string(chrome::kAboutScheme)); 43 whitelist->insert(std::string(chrome::kChromeUIScheme)); 44 whitelist->insert(std::string(chrome::kFileScheme)); 45 whitelist->insert(std::string(chrome::kFtpScheme)); 46 whitelist->insert(std::string(chrome::kHttpScheme)); 47 whitelist->insert(std::string(chrome::kHttpsScheme)); 48 whitelist->insert(std::string(content::kMailToScheme)); 49 } 50 51 // Restore/SaveCacheObserver --------------------------------------------------- 52 53 InMemoryURLIndex::RestoreCacheObserver::~RestoreCacheObserver() {} 54 55 InMemoryURLIndex::SaveCacheObserver::~SaveCacheObserver() {} 56 57 // RebuildPrivateDataFromHistoryDBTask ----------------------------------------- 58 59 InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask:: 60 RebuildPrivateDataFromHistoryDBTask( 61 InMemoryURLIndex* index, 62 const std::string& languages, 63 const std::set<std::string>& scheme_whitelist) 64 : index_(index), 65 languages_(languages), 66 scheme_whitelist_(scheme_whitelist), 67 succeeded_(false) { 68 } 69 70 bool InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::RunOnDBThread( 71 HistoryBackend* backend, 72 HistoryDatabase* db) { 73 data_ = URLIndexPrivateData::RebuildFromHistory(db, languages_, 74 scheme_whitelist_); 75 succeeded_ = data_.get() && !data_->Empty(); 76 if (!succeeded_ && data_.get()) 77 data_->Clear(); 78 return true; 79 } 80 81 void InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask:: 82 DoneRunOnMainThread() { 83 index_->DoneRebuidingPrivateDataFromHistoryDB(succeeded_, data_); 84 } 85 86 InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask:: 87 ~RebuildPrivateDataFromHistoryDBTask() { 88 } 89 90 // InMemoryURLIndex ------------------------------------------------------------ 91 92 InMemoryURLIndex::InMemoryURLIndex(Profile* profile, 93 const base::FilePath& history_dir, 94 const std::string& languages) 95 : profile_(profile), 96 history_dir_(history_dir), 97 languages_(languages), 98 private_data_(new URLIndexPrivateData), 99 restore_cache_observer_(NULL), 100 save_cache_observer_(NULL), 101 shutdown_(false), 102 restored_(false), 103 needs_to_be_cached_(false) { 104 InitializeSchemeWhitelist(&scheme_whitelist_); 105 if (profile) { 106 // TODO(mrossetti): Register for language change notifications. 107 content::Source<Profile> source(profile); 108 registrar_.Add(this, chrome::NOTIFICATION_HISTORY_URL_VISITED, source); 109 registrar_.Add(this, chrome::NOTIFICATION_HISTORY_URLS_MODIFIED, 110 source); 111 registrar_.Add(this, chrome::NOTIFICATION_HISTORY_URLS_DELETED, source); 112 } 113 } 114 115 // Called only by unit tests. 116 InMemoryURLIndex::InMemoryURLIndex() 117 : profile_(NULL), 118 private_data_(new URLIndexPrivateData), 119 restore_cache_observer_(NULL), 120 save_cache_observer_(NULL), 121 shutdown_(false), 122 restored_(false), 123 needs_to_be_cached_(false) { 124 InitializeSchemeWhitelist(&scheme_whitelist_); 125 } 126 127 InMemoryURLIndex::~InMemoryURLIndex() { 128 // If there was a history directory (which there won't be for some unit tests) 129 // then insure that the cache has already been saved. 130 DCHECK(history_dir_.empty() || !needs_to_be_cached_); 131 } 132 133 void InMemoryURLIndex::Init() { 134 PostRestoreFromCacheFileTask(); 135 } 136 137 void InMemoryURLIndex::ShutDown() { 138 registrar_.RemoveAll(); 139 cache_reader_consumer_.CancelAllRequests(); 140 shutdown_ = true; 141 base::FilePath path; 142 if (!GetCacheFilePath(&path)) 143 return; 144 private_data_->CancelPendingUpdates(); 145 URLIndexPrivateData::WritePrivateDataToCacheFileTask(private_data_, path); 146 needs_to_be_cached_ = false; 147 } 148 149 void InMemoryURLIndex::ClearPrivateData() { 150 private_data_->Clear(); 151 } 152 153 bool InMemoryURLIndex::GetCacheFilePath(base::FilePath* file_path) { 154 if (history_dir_.empty()) 155 return false; 156 *file_path = history_dir_.Append(FILE_PATH_LITERAL("History Provider Cache")); 157 return true; 158 } 159 160 // Querying -------------------------------------------------------------------- 161 162 ScoredHistoryMatches InMemoryURLIndex::HistoryItemsForTerms( 163 const string16& term_string, 164 size_t cursor_position) { 165 return private_data_->HistoryItemsForTerms( 166 term_string, 167 cursor_position, 168 languages_, 169 BookmarkModelFactory::GetForProfile(profile_)); 170 } 171 172 // Updating -------------------------------------------------------------------- 173 174 void InMemoryURLIndex::DeleteURL(const GURL& url) { 175 private_data_->DeleteURL(url); 176 } 177 178 void InMemoryURLIndex::Observe(int notification_type, 179 const content::NotificationSource& source, 180 const content::NotificationDetails& details) { 181 switch (notification_type) { 182 case chrome::NOTIFICATION_HISTORY_URL_VISITED: 183 OnURLVisited(content::Details<URLVisitedDetails>(details).ptr()); 184 break; 185 case chrome::NOTIFICATION_HISTORY_URLS_MODIFIED: 186 OnURLsModified( 187 content::Details<history::URLsModifiedDetails>(details).ptr()); 188 break; 189 case chrome::NOTIFICATION_HISTORY_URLS_DELETED: 190 OnURLsDeleted( 191 content::Details<history::URLsDeletedDetails>(details).ptr()); 192 break; 193 case chrome::NOTIFICATION_HISTORY_LOADED: 194 registrar_.Remove(this, chrome::NOTIFICATION_HISTORY_LOADED, 195 content::Source<Profile>(profile_)); 196 ScheduleRebuildFromHistory(); 197 break; 198 default: 199 // For simplicity, the unit tests send us all notifications, even when 200 // we haven't registered for them, so don't assert here. 201 break; 202 } 203 } 204 205 void InMemoryURLIndex::OnURLVisited(const URLVisitedDetails* details) { 206 HistoryService* service = 207 HistoryServiceFactory::GetForProfile(profile_, 208 Profile::EXPLICIT_ACCESS); 209 needs_to_be_cached_ |= private_data_->UpdateURL( 210 service, details->row, languages_, scheme_whitelist_); 211 } 212 213 void InMemoryURLIndex::OnURLsModified(const URLsModifiedDetails* details) { 214 HistoryService* service = 215 HistoryServiceFactory::GetForProfile(profile_, 216 Profile::EXPLICIT_ACCESS); 217 for (URLRows::const_iterator row = details->changed_urls.begin(); 218 row != details->changed_urls.end(); ++row) 219 needs_to_be_cached_ |= 220 private_data_->UpdateURL(service, *row, languages_, scheme_whitelist_); 221 } 222 223 void InMemoryURLIndex::OnURLsDeleted(const URLsDeletedDetails* details) { 224 if (details->all_history) { 225 ClearPrivateData(); 226 needs_to_be_cached_ = true; 227 } else { 228 for (URLRows::const_iterator row = details->rows.begin(); 229 row != details->rows.end(); ++row) 230 needs_to_be_cached_ |= private_data_->DeleteURL(row->url()); 231 } 232 } 233 234 // Restoring from Cache -------------------------------------------------------- 235 236 void InMemoryURLIndex::PostRestoreFromCacheFileTask() { 237 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 238 TRACE_EVENT0("browser", "InMemoryURLIndex::PostRestoreFromCacheFileTask"); 239 240 base::FilePath path; 241 if (!GetCacheFilePath(&path) || shutdown_) { 242 restored_ = true; 243 if (restore_cache_observer_) 244 restore_cache_observer_->OnCacheRestoreFinished(false); 245 return; 246 } 247 248 content::BrowserThread::PostTaskAndReplyWithResult 249 <scoped_refptr<URLIndexPrivateData> >( 250 content::BrowserThread::FILE, FROM_HERE, 251 base::Bind(&URLIndexPrivateData::RestoreFromFile, path, languages_), 252 base::Bind(&InMemoryURLIndex::OnCacheLoadDone, AsWeakPtr())); 253 } 254 255 void InMemoryURLIndex::OnCacheLoadDone( 256 scoped_refptr<URLIndexPrivateData> private_data) { 257 if (private_data.get() && !private_data->Empty()) { 258 private_data_ = private_data; 259 restored_ = true; 260 if (restore_cache_observer_) 261 restore_cache_observer_->OnCacheRestoreFinished(true); 262 } else if (profile_) { 263 // When unable to restore from the cache file delete the cache file, if 264 // it exists, and then rebuild from the history database if it's available, 265 // otherwise wait until the history database loaded and then rebuild. 266 base::FilePath path; 267 if (!GetCacheFilePath(&path) || shutdown_) 268 return; 269 content::BrowserThread::PostBlockingPoolTask( 270 FROM_HERE, base::Bind(DeleteCacheFile, path)); 271 HistoryService* service = 272 HistoryServiceFactory::GetForProfileWithoutCreating(profile_); 273 if (service && service->backend_loaded()) { 274 ScheduleRebuildFromHistory(); 275 } else { 276 registrar_.Add(this, chrome::NOTIFICATION_HISTORY_LOADED, 277 content::Source<Profile>(profile_)); 278 } 279 } 280 } 281 282 // Restoring from the History DB ----------------------------------------------- 283 284 void InMemoryURLIndex::ScheduleRebuildFromHistory() { 285 HistoryService* service = 286 HistoryServiceFactory::GetForProfile(profile_, 287 Profile::EXPLICIT_ACCESS); 288 service->ScheduleDBTask( 289 new InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask( 290 this, languages_, scheme_whitelist_), 291 &cache_reader_consumer_); 292 } 293 294 void InMemoryURLIndex::DoneRebuidingPrivateDataFromHistoryDB( 295 bool succeeded, 296 scoped_refptr<URLIndexPrivateData> private_data) { 297 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 298 if (succeeded) { 299 private_data_ = private_data; 300 PostSaveToCacheFileTask(); // Cache the newly rebuilt index. 301 } else { 302 private_data_->Clear(); // Dump the old private data. 303 // There is no need to do anything with the cache file as it was deleted 304 // when the rebuild from the history operation was kicked off. 305 } 306 restored_ = true; 307 if (restore_cache_observer_) 308 restore_cache_observer_->OnCacheRestoreFinished(succeeded); 309 } 310 311 void InMemoryURLIndex::RebuildFromHistory(HistoryDatabase* history_db) { 312 private_data_ = URLIndexPrivateData::RebuildFromHistory(history_db, 313 languages_, 314 scheme_whitelist_); 315 } 316 317 // Saving to Cache ------------------------------------------------------------- 318 319 void InMemoryURLIndex::PostSaveToCacheFileTask() { 320 base::FilePath path; 321 if (!GetCacheFilePath(&path)) 322 return; 323 // If there is anything in our private data then make a copy of it and tell 324 // it to save itself to a file. 325 if (private_data_.get() && !private_data_->Empty()) { 326 // Note that ownership of the copy of our private data is passed to the 327 // completion closure below. 328 scoped_refptr<URLIndexPrivateData> private_data_copy = 329 private_data_->Duplicate(); 330 content::BrowserThread::PostTaskAndReplyWithResult<bool>( 331 content::BrowserThread::FILE, FROM_HERE, 332 base::Bind(&URLIndexPrivateData::WritePrivateDataToCacheFileTask, 333 private_data_copy, path), 334 base::Bind(&InMemoryURLIndex::OnCacheSaveDone, AsWeakPtr())); 335 } else { 336 // If there is no data in our index then delete any existing cache file. 337 content::BrowserThread::PostBlockingPoolTask( 338 FROM_HERE, 339 base::Bind(DeleteCacheFile, path)); 340 } 341 } 342 343 void InMemoryURLIndex::OnCacheSaveDone(bool succeeded) { 344 if (save_cache_observer_) 345 save_cache_observer_->OnCacheSaveFinished(succeeded); 346 } 347 348 } // namespace history 349