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/shortcuts_backend.h"
      6 
      7 #include <map>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "base/bind.h"
     12 #include "base/bind_helpers.h"
     13 #include "base/guid.h"
     14 #include "base/i18n/case_conversion.h"
     15 #include "base/strings/string_util.h"
     16 #include "chrome/browser/autocomplete/autocomplete_match.h"
     17 #include "chrome/browser/autocomplete/autocomplete_result.h"
     18 #include "chrome/browser/chrome_notification_types.h"
     19 #include "chrome/browser/history/history_notifications.h"
     20 #include "chrome/browser/history/history_service.h"
     21 #include "chrome/browser/history/shortcuts_database.h"
     22 #include "chrome/browser/omnibox/omnibox_log.h"
     23 #include "chrome/browser/profiles/profile.h"
     24 #include "content/public/browser/browser_thread.h"
     25 #include "content/public/browser/notification_details.h"
     26 #include "content/public/browser/notification_source.h"
     27 
     28 using content::BrowserThread;
     29 
     30 namespace {
     31 
     32 // Takes Match classification vector and removes all matched positions,
     33 // compacting repetitions if necessary.
     34 void StripMatchMarkersFromClassifications(ACMatchClassifications* matches) {
     35   DCHECK(matches);
     36   ACMatchClassifications unmatched;
     37   for (ACMatchClassifications::iterator i = matches->begin();
     38        i != matches->end(); ++i) {
     39     AutocompleteMatch::AddLastClassificationIfNecessary(&unmatched, i->offset,
     40         i->style & ~ACMatchClassification::MATCH);
     41   }
     42   matches->swap(unmatched);
     43 }
     44 
     45 }  // namespace
     46 
     47 namespace history {
     48 
     49 // ShortcutsBackend::Shortcut -------------------------------------------------
     50 
     51 ShortcutsBackend::Shortcut::Shortcut(
     52     const std::string& id,
     53     const string16& text,
     54     const GURL& url,
     55     const string16& contents,
     56     const ACMatchClassifications& contents_class,
     57     const string16& description,
     58     const ACMatchClassifications& description_class,
     59     const base::Time& last_access_time,
     60     int number_of_hits)
     61     : id(id),
     62       text(text),
     63       url(url),
     64       contents(contents),
     65       contents_class(contents_class),
     66       description(description),
     67       description_class(description_class),
     68       last_access_time(last_access_time),
     69       number_of_hits(number_of_hits) {
     70   StripMatchMarkersFromClassifications(&this->contents_class);
     71   StripMatchMarkersFromClassifications(&this->description_class);
     72 }
     73 
     74 ShortcutsBackend::Shortcut::Shortcut()
     75     : last_access_time(base::Time::Now()),
     76       number_of_hits(0) {
     77 }
     78 
     79 ShortcutsBackend::Shortcut::~Shortcut() {
     80 }
     81 
     82 
     83 // ShortcutsBackend -----------------------------------------------------------
     84 
     85 ShortcutsBackend::ShortcutsBackend(Profile* profile, bool suppress_db)
     86     : current_state_(NOT_INITIALIZED),
     87       no_db_access_(suppress_db) {
     88   if (!suppress_db)
     89     db_ = new ShortcutsDatabase(profile);
     90   // |profile| can be NULL in tests.
     91   if (profile) {
     92     notification_registrar_.Add(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL,
     93                                 content::Source<Profile>(profile));
     94     notification_registrar_.Add(this, chrome::NOTIFICATION_HISTORY_URLS_DELETED,
     95                                 content::Source<Profile>(profile));
     96   }
     97 }
     98 
     99 bool ShortcutsBackend::Init() {
    100   if (current_state_ != NOT_INITIALIZED)
    101     return false;
    102 
    103   if (no_db_access_) {
    104     current_state_ = INITIALIZED;
    105     return true;
    106   }
    107 
    108   current_state_ = INITIALIZING;
    109   return BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    110       base::Bind(&ShortcutsBackend::InitInternal, this));
    111 }
    112 
    113 bool ShortcutsBackend::AddShortcut(const Shortcut& shortcut) {
    114   if (!initialized())
    115     return false;
    116   DCHECK(guid_map_.find(shortcut.id) == guid_map_.end());
    117   guid_map_[shortcut.id] = shortcuts_map_.insert(
    118       std::make_pair(base::i18n::ToLower(shortcut.text), shortcut));
    119   FOR_EACH_OBSERVER(ShortcutsBackendObserver, observer_list_,
    120                     OnShortcutsChanged());
    121   return no_db_access_ || BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    122       base::Bind(base::IgnoreResult(&ShortcutsDatabase::AddShortcut),
    123                  db_.get(), shortcut));
    124 }
    125 
    126 bool ShortcutsBackend::UpdateShortcut(const Shortcut& shortcut) {
    127   if (!initialized())
    128     return false;
    129   GuidToShortcutsIteratorMap::iterator it = guid_map_.find(shortcut.id);
    130   if (it != guid_map_.end())
    131     shortcuts_map_.erase(it->second);
    132   guid_map_[shortcut.id] = shortcuts_map_.insert(
    133       std::make_pair(base::i18n::ToLower(shortcut.text), shortcut));
    134   FOR_EACH_OBSERVER(ShortcutsBackendObserver, observer_list_,
    135                     OnShortcutsChanged());
    136   return no_db_access_ || BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    137       base::Bind(base::IgnoreResult(&ShortcutsDatabase::UpdateShortcut),
    138                  db_.get(), shortcut));
    139 }
    140 
    141 bool ShortcutsBackend::DeleteShortcutsWithIds(
    142     const std::vector<std::string>& shortcut_ids) {
    143   if (!initialized())
    144     return false;
    145   for (size_t i = 0; i < shortcut_ids.size(); ++i) {
    146     GuidToShortcutsIteratorMap::iterator it = guid_map_.find(shortcut_ids[i]);
    147     if (it != guid_map_.end()) {
    148       shortcuts_map_.erase(it->second);
    149       guid_map_.erase(it);
    150     }
    151   }
    152   FOR_EACH_OBSERVER(ShortcutsBackendObserver, observer_list_,
    153                     OnShortcutsChanged());
    154   return no_db_access_ || BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    155       base::Bind(base::IgnoreResult(&ShortcutsDatabase::DeleteShortcutsWithIds),
    156                  db_.get(), shortcut_ids));
    157 }
    158 
    159 bool ShortcutsBackend::DeleteShortcutsWithUrl(const GURL& shortcut_url) {
    160   if (!initialized())
    161     return false;
    162   std::vector<std::string> shortcut_ids;
    163   for (GuidToShortcutsIteratorMap::iterator it = guid_map_.begin();
    164        it != guid_map_.end();) {
    165     if (it->second->second.url == shortcut_url) {
    166       shortcut_ids.push_back(it->first);
    167       shortcuts_map_.erase(it->second);
    168       guid_map_.erase(it++);
    169     } else {
    170       ++it;
    171     }
    172   }
    173   FOR_EACH_OBSERVER(ShortcutsBackendObserver, observer_list_,
    174                     OnShortcutsChanged());
    175   return no_db_access_ || BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    176       base::Bind(base::IgnoreResult(&ShortcutsDatabase::DeleteShortcutsWithUrl),
    177                  db_.get(), shortcut_url.spec()));
    178 }
    179 
    180 bool ShortcutsBackend::DeleteAllShortcuts() {
    181   if (!initialized())
    182     return false;
    183   shortcuts_map_.clear();
    184   guid_map_.clear();
    185   FOR_EACH_OBSERVER(ShortcutsBackendObserver, observer_list_,
    186                     OnShortcutsChanged());
    187   return no_db_access_ || BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    188       base::Bind(base::IgnoreResult(&ShortcutsDatabase::DeleteAllShortcuts),
    189                  db_.get()));
    190 }
    191 
    192 ShortcutsBackend::~ShortcutsBackend() {}
    193 
    194 void ShortcutsBackend::InitInternal() {
    195   DCHECK(current_state_ == INITIALIZING);
    196   db_->Init();
    197   ShortcutsDatabase::GuidToShortcutMap shortcuts;
    198   db_->LoadShortcuts(&shortcuts);
    199   temp_shortcuts_map_.reset(new ShortcutMap);
    200   temp_guid_map_.reset(new GuidToShortcutsIteratorMap);
    201   for (ShortcutsDatabase::GuidToShortcutMap::iterator it = shortcuts.begin();
    202        it != shortcuts.end(); ++it) {
    203     (*temp_guid_map_)[it->first] = temp_shortcuts_map_->insert(
    204         std::make_pair(base::i18n::ToLower(it->second.text), it->second));
    205   }
    206   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    207       base::Bind(&ShortcutsBackend::InitCompleted, this));
    208 }
    209 
    210 void ShortcutsBackend::InitCompleted() {
    211   temp_guid_map_->swap(guid_map_);
    212   temp_shortcuts_map_->swap(shortcuts_map_);
    213   temp_shortcuts_map_.reset(NULL);
    214   temp_guid_map_.reset(NULL);
    215   current_state_ = INITIALIZED;
    216   FOR_EACH_OBSERVER(ShortcutsBackendObserver, observer_list_,
    217                     OnShortcutsLoaded());
    218 }
    219 
    220 // content::NotificationObserver:
    221 void ShortcutsBackend::Observe(int type,
    222                                const content::NotificationSource& source,
    223                                const content::NotificationDetails& details) {
    224   if (current_state_ != INITIALIZED)
    225     return;
    226   if (type == chrome::NOTIFICATION_HISTORY_URLS_DELETED) {
    227     if (content::Details<const history::URLsDeletedDetails>(details)->
    228             all_history) {
    229       DeleteAllShortcuts();
    230     }
    231     const URLRows& rows(
    232         content::Details<const history::URLsDeletedDetails>(details)->rows);
    233     std::vector<std::string> shortcut_ids;
    234 
    235     for (GuidToShortcutsIteratorMap::iterator it = guid_map_.begin();
    236          it != guid_map_.end(); ++it) {
    237       if (std::find_if(rows.begin(), rows.end(),
    238                        URLRow::URLRowHasURL(it->second->second.url)) !=
    239           rows.end())
    240         shortcut_ids.push_back(it->first);
    241     }
    242     DeleteShortcutsWithIds(shortcut_ids);
    243     return;
    244   }
    245 
    246   DCHECK(type == chrome::NOTIFICATION_OMNIBOX_OPENED_URL);
    247 
    248   OmniboxLog* log = content::Details<OmniboxLog>(details).ptr();
    249   string16 text_lowercase(base::i18n::ToLower(log->text));
    250 
    251   const AutocompleteMatch& match(log->result.match_at(log->selected_index));
    252   for (ShortcutMap::iterator it = shortcuts_map_.lower_bound(text_lowercase);
    253        it != shortcuts_map_.end() &&
    254            StartsWith(it->first, text_lowercase, true); ++it) {
    255     if (match.destination_url == it->second.url) {
    256       UpdateShortcut(Shortcut(it->second.id, log->text, match.destination_url,
    257           match.contents, match.contents_class, match.description,
    258           match.description_class, base::Time::Now(),
    259           it->second.number_of_hits + 1));
    260       return;
    261     }
    262   }
    263   AddShortcut(Shortcut(base::GenerateGUID(), log->text, match.destination_url,
    264       match.contents, match.contents_class, match.description,
    265       match.description_class, base::Time::Now(), 1));
    266 }
    267 
    268 void ShortcutsBackend::ShutdownOnUIThread() {
    269   DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) ||
    270          BrowserThread::CurrentlyOn(BrowserThread::UI));
    271   notification_registrar_.RemoveAll();
    272 }
    273 
    274 }  // namespace history
    275