Home | History | Annotate | Download | only in history
      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