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
23namespace history {
24
25// If a page becomes starred we use this id in place of the real starred id.
26// See note in OnURLsStarred.
27static const StarID kBogusStarredID = 0x0FFFFFFF;
28
29InMemoryHistoryBackend::InMemoryHistoryBackend()
30    : profile_(NULL) {
31}
32
33InMemoryHistoryBackend::~InMemoryHistoryBackend() {
34  if (index_.get())
35    index_->ShutDown();
36}
37
38bool 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
55void 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
80void 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
119void 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
141void 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
169void 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
192bool 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