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