1 // Copyright (c) 2011 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_history_backend.h" 6 7 #include <set> 8 #include <vector> 9 10 #include "base/command_line.h" 11 #include "base/time.h" 12 #include "base/utf_string_conversions.h" 13 #include "chrome/browser/browser_process.h" 14 #include "chrome/browser/history/history_notifications.h" 15 #include "chrome/browser/history/in_memory_database.h" 16 #include "chrome/browser/history/in_memory_url_index.h" 17 #include "chrome/browser/history/url_database.h" 18 #include "chrome/browser/profiles/profile.h" 19 #include "chrome/common/chrome_switches.h" 20 #include "content/common/notification_details.h" 21 #include "content/common/notification_source.h" 22 23 namespace history { 24 25 // If a page becomes starred we use this id in place of the real starred id. 26 // See note in OnURLsStarred. 27 static const StarID kBogusStarredID = 0x0FFFFFFF; 28 29 InMemoryHistoryBackend::InMemoryHistoryBackend() 30 : profile_(NULL) { 31 } 32 33 InMemoryHistoryBackend::~InMemoryHistoryBackend() { 34 if (index_.get()) 35 index_->ShutDown(); 36 } 37 38 bool InMemoryHistoryBackend::Init(const FilePath& history_filename, 39 const FilePath& history_dir, 40 URLDatabase* db, 41 const std::string& languages) { 42 db_.reset(new InMemoryDatabase); 43 bool success = db_->InitFromDisk(history_filename); 44 if (CommandLine::ForCurrentProcess()->HasSwitch( 45 switches::kEnableHistoryQuickProvider) && 46 !CommandLine::ForCurrentProcess()->HasSwitch( 47 switches::kDisableHistoryQuickProvider)) { 48 index_.reset(new InMemoryURLIndex(history_dir)); 49 50 index_->Init(db, languages); 51 } 52 return success; 53 } 54 55 void InMemoryHistoryBackend::AttachToHistoryService(Profile* profile) { 56 if (!db_.get()) { 57 NOTREACHED(); 58 return; 59 } 60 61 profile_ = profile; 62 63 // TODO(evanm): this is currently necessitated by generate_profile, which 64 // runs without a browser process. generate_profile should really create 65 // a browser process, at which point this check can then be nuked. 66 if (!g_browser_process) 67 return; 68 69 // Register for the notifications we care about. 70 // We only want notifications for the associated profile. 71 Source<Profile> source(profile_); 72 registrar_.Add(this, NotificationType::HISTORY_URL_VISITED, source); 73 registrar_.Add(this, NotificationType::HISTORY_TYPED_URLS_MODIFIED, source); 74 registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED, source); 75 registrar_.Add(this, NotificationType::HISTORY_KEYWORD_SEARCH_TERM_UPDATED, 76 source); 77 registrar_.Add(this, NotificationType::TEMPLATE_URL_REMOVED, source); 78 } 79 80 void InMemoryHistoryBackend::Observe(NotificationType type, 81 const NotificationSource& source, 82 const NotificationDetails& details) { 83 switch (type.value) { 84 case NotificationType::HISTORY_URL_VISITED: { 85 Details<history::URLVisitedDetails> visited_details(details); 86 PageTransition::Type primary_type = 87 PageTransition::StripQualifier(visited_details->transition); 88 if (visited_details->row.typed_count() > 0 || 89 primary_type == PageTransition::KEYWORD || 90 HasKeyword(visited_details->row.url())) { 91 URLsModifiedDetails modified_details; 92 modified_details.changed_urls.push_back(visited_details->row); 93 OnTypedURLsModified(modified_details); 94 } 95 break; 96 } 97 case NotificationType::HISTORY_KEYWORD_SEARCH_TERM_UPDATED: 98 OnKeywordSearchTermUpdated( 99 *Details<history::KeywordSearchTermDetails>(details).ptr()); 100 break; 101 case NotificationType::HISTORY_TYPED_URLS_MODIFIED: 102 OnTypedURLsModified( 103 *Details<history::URLsModifiedDetails>(details).ptr()); 104 break; 105 case NotificationType::HISTORY_URLS_DELETED: 106 OnURLsDeleted(*Details<history::URLsDeletedDetails>(details).ptr()); 107 break; 108 case NotificationType::TEMPLATE_URL_REMOVED: 109 db_->DeleteAllSearchTermsForKeyword( 110 *(Details<TemplateURLID>(details).ptr())); 111 break; 112 default: 113 // For simplicity, the unit tests send us all notifications, even when 114 // we haven't registered for them, so don't assert here. 115 break; 116 } 117 } 118 119 void InMemoryHistoryBackend::OnTypedURLsModified( 120 const URLsModifiedDetails& details) { 121 DCHECK(db_.get()); 122 123 // Add or update the URLs. 124 // 125 // TODO(brettw) currently the rows in the in-memory database don't match the 126 // IDs in the main database. This sucks. Instead of Add and Remove, we should 127 // have Sync(), which would take the ID if it's given and add it. 128 std::vector<history::URLRow>::const_iterator i; 129 for (i = details.changed_urls.begin(); 130 i != details.changed_urls.end(); i++) { 131 URLID id = db_->GetRowForURL(i->url(), NULL); 132 if (id) 133 db_->UpdateURLRow(id, *i); 134 else 135 id = db_->AddURL(*i); 136 if (index_.get()) 137 index_->UpdateURL(id, *i); 138 } 139 } 140 141 void InMemoryHistoryBackend::OnURLsDeleted(const URLsDeletedDetails& details) { 142 DCHECK(db_.get()); 143 144 if (details.all_history) { 145 // When all history is deleted, the individual URLs won't be listed. Just 146 // create a new database to quickly clear everything out. 147 db_.reset(new InMemoryDatabase); 148 if (!db_->InitFromScratch()) 149 db_.reset(); 150 if (index_.get()) 151 index_->ReloadFromHistory(db_.get(), true); 152 return; 153 } 154 155 // Delete all matching URLs in our database. 156 for (std::set<GURL>::const_iterator i = details.urls.begin(); 157 i != details.urls.end(); ++i) { 158 URLID id = db_->GetRowForURL(*i, NULL); 159 if (id) { 160 // We typically won't have most of them since we only have a subset of 161 // history, so ignore errors. 162 db_->DeleteURLRow(id); 163 if (index_.get()) 164 index_->DeleteURL(id); 165 } 166 } 167 } 168 169 void InMemoryHistoryBackend::OnKeywordSearchTermUpdated( 170 const KeywordSearchTermDetails& details) { 171 // The url won't exist for new search terms (as the user hasn't typed it), so 172 // we force it to be added. If we end up adding a URL it won't be 173 // autocompleted as the typed count is 0. 174 URLRow url_row; 175 URLID url_id; 176 if (!db_->GetRowForURL(details.url, &url_row)) { 177 // Because this row won't have a typed count the title and other stuff 178 // doesn't matter. If the user ends up typing the url we'll update the title 179 // in OnTypedURLsModified. 180 URLRow new_row(details.url); 181 new_row.set_last_visit(base::Time::Now()); 182 url_id = db_->AddURL(new_row); 183 if (!url_id) 184 return; // Error adding. 185 } else { 186 url_id = url_row.id(); 187 } 188 189 db_->SetKeywordSearchTermsForURL(url_id, details.keyword_id, details.term); 190 } 191 192 bool InMemoryHistoryBackend::HasKeyword(const GURL& url) { 193 URLID id = db_->GetRowForURL(url, NULL); 194 if (!id) 195 return false; 196 197 return db_->GetKeywordSearchTermRow(id, NULL); 198 } 199 200 } // namespace history 201