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
InMemoryHistoryBackend()29 InMemoryHistoryBackend::InMemoryHistoryBackend()
30 : profile_(NULL) {
31 }
32
~InMemoryHistoryBackend()33 InMemoryHistoryBackend::~InMemoryHistoryBackend() {
34 if (index_.get())
35 index_->ShutDown();
36 }
37
Init(const FilePath & history_filename,const FilePath & history_dir,URLDatabase * db,const std::string & languages)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
AttachToHistoryService(Profile * profile)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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
OnTypedURLsModified(const URLsModifiedDetails & details)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
OnURLsDeleted(const URLsDeletedDetails & details)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
OnKeywordSearchTermUpdated(const KeywordSearchTermDetails & details)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
HasKeyword(const GURL & url)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