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_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