url_index_private_data.cc revision 010d83a9304c5a91596085d917d248abff47903a
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/url_index_private_data.h"
6
7#include <algorithm>
8#include <functional>
9#include <iterator>
10#include <limits>
11#include <numeric>
12#include <string>
13#include <vector>
14
15#include "base/basictypes.h"
16#include "base/file_util.h"
17#include "base/i18n/break_iterator.h"
18#include "base/i18n/case_conversion.h"
19#include "base/metrics/histogram.h"
20#include "base/strings/string_util.h"
21#include "base/strings/utf_string_conversions.h"
22#include "base/time/time.h"
23#include "chrome/browser/autocomplete/autocomplete_provider.h"
24#include "chrome/browser/autocomplete/url_prefix.h"
25#include "chrome/browser/history/history_database.h"
26#include "chrome/browser/history/history_db_task.h"
27#include "chrome/browser/history/history_service.h"
28#include "chrome/browser/history/in_memory_url_index.h"
29#include "components/bookmarks/core/browser/bookmark_service.h"
30#include "components/bookmarks/core/browser/bookmark_utils.h"
31#include "net/base/net_util.h"
32
33#if defined(USE_SYSTEM_PROTOBUF)
34#include <google/protobuf/repeated_field.h>
35#else
36#include "third_party/protobuf/src/google/protobuf/repeated_field.h"
37#endif
38
39using google::protobuf::RepeatedField;
40using google::protobuf::RepeatedPtrField;
41using in_memory_url_index::InMemoryURLIndexCacheItem;
42
43namespace {
44static const size_t kMaxVisitsToStoreInCache = 10u;
45}  // anonymous namespace
46
47namespace history {
48
49typedef imui::InMemoryURLIndexCacheItem_WordListItem WordListItem;
50typedef imui::InMemoryURLIndexCacheItem_WordMapItem_WordMapEntry WordMapEntry;
51typedef imui::InMemoryURLIndexCacheItem_WordMapItem WordMapItem;
52typedef imui::InMemoryURLIndexCacheItem_CharWordMapItem CharWordMapItem;
53typedef imui::InMemoryURLIndexCacheItem_CharWordMapItem_CharWordMapEntry
54    CharWordMapEntry;
55typedef imui::InMemoryURLIndexCacheItem_WordIDHistoryMapItem
56    WordIDHistoryMapItem;
57typedef imui::
58    InMemoryURLIndexCacheItem_WordIDHistoryMapItem_WordIDHistoryMapEntry
59    WordIDHistoryMapEntry;
60typedef imui::InMemoryURLIndexCacheItem_HistoryInfoMapItem HistoryInfoMapItem;
61typedef imui::InMemoryURLIndexCacheItem_HistoryInfoMapItem_HistoryInfoMapEntry
62    HistoryInfoMapEntry;
63typedef imui::
64    InMemoryURLIndexCacheItem_HistoryInfoMapItem_HistoryInfoMapEntry_VisitInfo
65    HistoryInfoMapEntry_VisitInfo;
66typedef imui::InMemoryURLIndexCacheItem_WordStartsMapItem WordStartsMapItem;
67typedef imui::InMemoryURLIndexCacheItem_WordStartsMapItem_WordStartsMapEntry
68    WordStartsMapEntry;
69
70
71// Algorithm Functions ---------------------------------------------------------
72
73// Comparison function for sorting search terms by descending length.
74bool LengthGreater(const base::string16& string_a,
75                   const base::string16& string_b) {
76  return string_a.length() > string_b.length();
77}
78
79
80// UpdateRecentVisitsFromHistoryDBTask -----------------------------------------
81
82// HistoryDBTask used to update the recent visit data for a particular
83// row from the history database.
84class UpdateRecentVisitsFromHistoryDBTask : public HistoryDBTask {
85 public:
86  explicit UpdateRecentVisitsFromHistoryDBTask(
87      URLIndexPrivateData* private_data,
88      URLID url_id);
89
90  virtual bool RunOnDBThread(HistoryBackend* backend,
91                             history::HistoryDatabase* db) OVERRIDE;
92  virtual void DoneRunOnMainThread() OVERRIDE;
93
94 private:
95  virtual ~UpdateRecentVisitsFromHistoryDBTask();
96
97  // The URLIndexPrivateData that gets updated after the historyDB
98  // task returns.
99  URLIndexPrivateData* private_data_;
100  // The ID of the URL to get visits for and then update.
101  URLID url_id_;
102  // Whether fetching the recent visits for the URL succeeded.
103  bool succeeded_;
104  // The awaited data that's shown to private_data_ for it to copy and
105  // store.
106  VisitVector recent_visits_;
107
108  DISALLOW_COPY_AND_ASSIGN(UpdateRecentVisitsFromHistoryDBTask);
109};
110
111UpdateRecentVisitsFromHistoryDBTask::UpdateRecentVisitsFromHistoryDBTask(
112    URLIndexPrivateData* private_data,
113    URLID url_id)
114    : private_data_(private_data),
115      url_id_(url_id),
116      succeeded_(false) {
117}
118
119bool UpdateRecentVisitsFromHistoryDBTask::RunOnDBThread(
120    HistoryBackend* backend,
121    HistoryDatabase* db) {
122  // Make sure the private data is going to get as many recent visits as
123  // ScoredHistoryMatch::GetFrecency() hopes to use.
124  DCHECK_GE(kMaxVisitsToStoreInCache, ScoredHistoryMatch::kMaxVisitsToScore);
125  succeeded_ = db->GetMostRecentVisitsForURL(url_id_,
126                                             kMaxVisitsToStoreInCache,
127                                             &recent_visits_);
128  if (!succeeded_)
129    recent_visits_.clear();
130  return true;  // Always claim to be done; do not retry failures.
131}
132
133void UpdateRecentVisitsFromHistoryDBTask::DoneRunOnMainThread() {
134  if (succeeded_)
135    private_data_->UpdateRecentVisits(url_id_, recent_visits_);
136}
137
138UpdateRecentVisitsFromHistoryDBTask::~UpdateRecentVisitsFromHistoryDBTask() {
139}
140
141
142// URLIndexPrivateData ---------------------------------------------------------
143
144URLIndexPrivateData::URLIndexPrivateData()
145    : restored_cache_version_(0),
146      saved_cache_version_(kCurrentCacheFileVersion),
147      pre_filter_item_count_(0),
148      post_filter_item_count_(0),
149      post_scoring_item_count_(0) {
150}
151
152ScoredHistoryMatches URLIndexPrivateData::HistoryItemsForTerms(
153    base::string16 search_string,
154    size_t cursor_position,
155    const std::string& languages,
156    BookmarkService* bookmark_service) {
157  // If cursor position is set and useful (not at either end of the
158  // string), allow the search string to be broken at cursor position.
159  // We do this by pretending there's a space where the cursor is.
160  if ((cursor_position != base::string16::npos) &&
161      (cursor_position < search_string.length()) &&
162      (cursor_position > 0)) {
163    search_string.insert(cursor_position, base::ASCIIToUTF16(" "));
164  }
165  pre_filter_item_count_ = 0;
166  post_filter_item_count_ = 0;
167  post_scoring_item_count_ = 0;
168  // The search string we receive may contain escaped characters. For reducing
169  // the index we need individual, lower-cased words, ignoring escapings. For
170  // the final filtering we need whitespace separated substrings possibly
171  // containing escaped characters.
172  base::string16 lower_raw_string(base::i18n::ToLower(search_string));
173  base::string16 lower_unescaped_string =
174      net::UnescapeURLComponent(lower_raw_string,
175          net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS);
176  // Extract individual 'words' (as opposed to 'terms'; see below) from the
177  // search string. When the user types "colspec=ID%20Mstone Release" we get
178  // four 'words': "colspec", "id", "mstone" and "release".
179  String16Vector lower_words(
180      history::String16VectorFromString16(lower_unescaped_string, false, NULL));
181  ScoredHistoryMatches scored_items;
182
183  // Do nothing if we have indexed no words (probably because we've not been
184  // initialized yet) or the search string has no words.
185  if (word_list_.empty() || lower_words.empty()) {
186    search_term_cache_.clear();  // Invalidate the term cache.
187    return scored_items;
188  }
189
190  // Reset used_ flags for search_term_cache_. We use a basic mark-and-sweep
191  // approach.
192  ResetSearchTermCache();
193
194  HistoryIDSet history_id_set = HistoryIDSetFromWords(lower_words);
195
196  // Trim the candidate pool if it is large. Note that we do not filter out
197  // items that do not contain the search terms as proper substrings -- doing
198  // so is the performance-costly operation we are trying to avoid in order
199  // to maintain omnibox responsiveness.
200  const size_t kItemsToScoreLimit = 500;
201  pre_filter_item_count_ = history_id_set.size();
202  // If we trim the results set we do not want to cache the results for next
203  // time as the user's ultimately desired result could easily be eliminated
204  // in this early rough filter.
205  bool was_trimmed = (pre_filter_item_count_ > kItemsToScoreLimit);
206  if (was_trimmed) {
207    HistoryIDVector history_ids;
208    std::copy(history_id_set.begin(), history_id_set.end(),
209              std::back_inserter(history_ids));
210    // Trim down the set by sorting by typed-count, visit-count, and last
211    // visit.
212    HistoryItemFactorGreater
213        item_factor_functor(history_info_map_);
214    std::partial_sort(history_ids.begin(),
215                      history_ids.begin() + kItemsToScoreLimit,
216                      history_ids.end(),
217                      item_factor_functor);
218    history_id_set.clear();
219    std::copy(history_ids.begin(), history_ids.begin() + kItemsToScoreLimit,
220              std::inserter(history_id_set, history_id_set.end()));
221    post_filter_item_count_ = history_id_set.size();
222  }
223
224  // Pass over all of the candidates filtering out any without a proper
225  // substring match, inserting those which pass in order by score. Note that
226  // in this step we are using the raw search string complete with escaped
227  // URL elements. When the user has specifically typed something akin to
228  // "sort=pri&colspec=ID%20Mstone%20Release" we want to make sure that that
229  // specific substring appears in the URL or page title.
230
231  // We call these 'terms' (as opposed to 'words'; see above) as in this case
232  // we only want to break up the search string on 'true' whitespace rather than
233  // escaped whitespace. When the user types "colspec=ID%20Mstone Release" we
234  // get two 'terms': "colspec=id%20mstone" and "release".
235  history::String16Vector lower_raw_terms;
236  if (Tokenize(lower_raw_string, base::kWhitespaceUTF16,
237               &lower_raw_terms) == 0) {
238    // Don't score matches when there are no terms to score against.  (It's
239    // possible that the word break iterater that extracts words to search
240    // for in the database allows some whitespace "words" whereas Tokenize
241    // excludes a long list of whitespace.)  One could write a scoring
242    // function that gives a reasonable order to matches when there
243    // are no terms (i.e., all the words are some form of whitespace),
244    // but this is such a rare edge case that it's not worth the time.
245    return scored_items;
246  }
247  scored_items = std::for_each(history_id_set.begin(), history_id_set.end(),
248      AddHistoryMatch(*this, languages, bookmark_service, lower_raw_string,
249                      lower_raw_terms, base::Time::Now())).ScoredMatches();
250
251  // Select and sort only the top kMaxMatches results.
252  if (scored_items.size() > AutocompleteProvider::kMaxMatches) {
253    std::partial_sort(scored_items.begin(),
254                      scored_items.begin() +
255                          AutocompleteProvider::kMaxMatches,
256                      scored_items.end(),
257                      ScoredHistoryMatch::MatchScoreGreater);
258      scored_items.resize(AutocompleteProvider::kMaxMatches);
259  } else {
260    std::sort(scored_items.begin(), scored_items.end(),
261              ScoredHistoryMatch::MatchScoreGreater);
262  }
263  post_scoring_item_count_ = scored_items.size();
264
265  if (was_trimmed) {
266    search_term_cache_.clear();  // Invalidate the term cache.
267  } else {
268    // Remove any stale SearchTermCacheItems.
269    for (SearchTermCacheMap::iterator cache_iter = search_term_cache_.begin();
270         cache_iter != search_term_cache_.end(); ) {
271      if (!cache_iter->second.used_)
272        search_term_cache_.erase(cache_iter++);
273      else
274        ++cache_iter;
275    }
276  }
277
278  return scored_items;
279}
280
281bool URLIndexPrivateData::UpdateURL(
282    HistoryService* history_service,
283    const URLRow& row,
284    const std::string& languages,
285    const std::set<std::string>& scheme_whitelist) {
286  // The row may or may not already be in our index. If it is not already
287  // indexed and it qualifies then it gets indexed. If it is already
288  // indexed and still qualifies then it gets updated, otherwise it
289  // is deleted from the index.
290  bool row_was_updated = false;
291  URLID row_id = row.id();
292  HistoryInfoMap::iterator row_pos = history_info_map_.find(row_id);
293  if (row_pos == history_info_map_.end()) {
294    // This new row should be indexed if it qualifies.
295    URLRow new_row(row);
296    new_row.set_id(row_id);
297    row_was_updated = RowQualifiesAsSignificant(new_row, base::Time()) &&
298        IndexRow(NULL, history_service, new_row, languages, scheme_whitelist);
299  } else if (RowQualifiesAsSignificant(row, base::Time())) {
300    // This indexed row still qualifies and will be re-indexed.
301    // The url won't have changed but the title, visit count, etc.
302    // might have changed.
303    URLRow& row_to_update = row_pos->second.url_row;
304    bool title_updated = row_to_update.title() != row.title();
305    if (row_to_update.visit_count() != row.visit_count() ||
306        row_to_update.typed_count() != row.typed_count() ||
307        row_to_update.last_visit() != row.last_visit() || title_updated) {
308      row_to_update.set_visit_count(row.visit_count());
309      row_to_update.set_typed_count(row.typed_count());
310      row_to_update.set_last_visit(row.last_visit());
311      // If something appears to have changed, update the recent visits
312      // information.
313      ScheduleUpdateRecentVisits(history_service, row_id);
314      // While the URL is guaranteed to remain stable, the title may have
315      // changed. If so, then update the index with the changed words.
316      if (title_updated) {
317        // Clear all words associated with this row and re-index both the
318        // URL and title.
319        RemoveRowWordsFromIndex(row_to_update);
320        row_to_update.set_title(row.title());
321        RowWordStarts word_starts;
322        AddRowWordsToIndex(row_to_update, &word_starts, languages);
323        word_starts_map_[row_id] = word_starts;
324      }
325      row_was_updated = true;
326    }
327  } else {
328    // This indexed row no longer qualifies and will be de-indexed by
329    // clearing all words associated with this row.
330    RemoveRowFromIndex(row);
331    row_was_updated = true;
332  }
333  if (row_was_updated)
334    search_term_cache_.clear();  // This invalidates the cache.
335  return row_was_updated;
336}
337
338void URLIndexPrivateData::UpdateRecentVisits(
339    URLID url_id,
340    const VisitVector& recent_visits) {
341  HistoryInfoMap::iterator row_pos = history_info_map_.find(url_id);
342  if (row_pos != history_info_map_.end()) {
343    VisitInfoVector* visits = &row_pos->second.visits;
344    visits->clear();
345    const size_t size =
346        std::min(recent_visits.size(), kMaxVisitsToStoreInCache);
347    visits->reserve(size);
348    for (size_t i = 0; i < size; i++) {
349      // Copy from the VisitVector the only fields visits needs.
350      visits->push_back(std::make_pair(recent_visits[i].visit_time,
351                                       recent_visits[i].transition));
352    }
353  }
354  // Else: Oddly, the URL doesn't seem to exist in the private index.
355  // Ignore this update.  This can happen if, for instance, the user
356  // removes the URL from URLIndexPrivateData before the historyDB call
357  // returns.
358}
359
360void URLIndexPrivateData::ScheduleUpdateRecentVisits(
361    HistoryService* history_service,
362    URLID url_id) {
363  history_service->ScheduleDBTask(
364      new UpdateRecentVisitsFromHistoryDBTask(this, url_id),
365      &recent_visits_consumer_);
366}
367
368// Helper functor for DeleteURL.
369class HistoryInfoMapItemHasURL {
370 public:
371  explicit HistoryInfoMapItemHasURL(const GURL& url): url_(url) {}
372
373  bool operator()(const std::pair<const HistoryID, HistoryInfoMapValue>& item) {
374    return item.second.url_row.url() == url_;
375  }
376
377 private:
378  const GURL& url_;
379};
380
381bool URLIndexPrivateData::DeleteURL(const GURL& url) {
382  // Find the matching entry in the history_info_map_.
383  HistoryInfoMap::iterator pos = std::find_if(
384      history_info_map_.begin(),
385      history_info_map_.end(),
386      HistoryInfoMapItemHasURL(url));
387  if (pos == history_info_map_.end())
388    return false;
389  RemoveRowFromIndex(pos->second.url_row);
390  search_term_cache_.clear();  // This invalidates the cache.
391  return true;
392}
393
394// static
395scoped_refptr<URLIndexPrivateData> URLIndexPrivateData::RestoreFromFile(
396    const base::FilePath& file_path,
397    const std::string& languages) {
398  base::TimeTicks beginning_time = base::TimeTicks::Now();
399  if (!base::PathExists(file_path))
400    return NULL;
401  std::string data;
402  // If there is no cache file then simply give up. This will cause us to
403  // attempt to rebuild from the history database.
404  if (!base::ReadFileToString(file_path, &data))
405    return NULL;
406
407  scoped_refptr<URLIndexPrivateData> restored_data(new URLIndexPrivateData);
408  InMemoryURLIndexCacheItem index_cache;
409  if (!index_cache.ParseFromArray(data.c_str(), data.size())) {
410    LOG(WARNING) << "Failed to parse URLIndexPrivateData cache data read from "
411                 << file_path.value();
412    return restored_data;
413  }
414
415  if (!restored_data->RestorePrivateData(index_cache, languages))
416    return NULL;
417
418  UMA_HISTOGRAM_TIMES("History.InMemoryURLIndexRestoreCacheTime",
419                      base::TimeTicks::Now() - beginning_time);
420  UMA_HISTOGRAM_COUNTS("History.InMemoryURLHistoryItems",
421                       restored_data->history_id_word_map_.size());
422  UMA_HISTOGRAM_COUNTS("History.InMemoryURLCacheSize", data.size());
423  UMA_HISTOGRAM_COUNTS_10000("History.InMemoryURLWords",
424                             restored_data->word_map_.size());
425  UMA_HISTOGRAM_COUNTS_10000("History.InMemoryURLChars",
426                             restored_data->char_word_map_.size());
427  if (restored_data->Empty())
428    return NULL;  // 'No data' is the same as a failed reload.
429  return restored_data;
430}
431
432// static
433scoped_refptr<URLIndexPrivateData> URLIndexPrivateData::RebuildFromHistory(
434    HistoryDatabase* history_db,
435    const std::string& languages,
436    const std::set<std::string>& scheme_whitelist) {
437  if (!history_db)
438    return NULL;
439
440  base::TimeTicks beginning_time = base::TimeTicks::Now();
441
442  scoped_refptr<URLIndexPrivateData>
443      rebuilt_data(new URLIndexPrivateData);
444  URLDatabase::URLEnumerator history_enum;
445  if (!history_db->InitURLEnumeratorForSignificant(&history_enum))
446    return NULL;
447  rebuilt_data->last_time_rebuilt_from_history_ = base::Time::Now();
448  for (URLRow row; history_enum.GetNextURL(&row); ) {
449    rebuilt_data->IndexRow(history_db, NULL, row, languages,
450                           scheme_whitelist);
451  }
452
453  UMA_HISTOGRAM_TIMES("History.InMemoryURLIndexingTime",
454                      base::TimeTicks::Now() - beginning_time);
455  UMA_HISTOGRAM_COUNTS("History.InMemoryURLHistoryItems",
456                       rebuilt_data->history_id_word_map_.size());
457  UMA_HISTOGRAM_COUNTS_10000("History.InMemoryURLWords",
458                             rebuilt_data->word_map_.size());
459  UMA_HISTOGRAM_COUNTS_10000("History.InMemoryURLChars",
460                             rebuilt_data->char_word_map_.size());
461  return rebuilt_data;
462}
463
464// static
465bool URLIndexPrivateData::WritePrivateDataToCacheFileTask(
466    scoped_refptr<URLIndexPrivateData> private_data,
467    const base::FilePath& file_path) {
468  DCHECK(private_data.get());
469  DCHECK(!file_path.empty());
470  return private_data->SaveToFile(file_path);
471}
472
473void URLIndexPrivateData::CancelPendingUpdates() {
474  recent_visits_consumer_.CancelAllRequests();
475}
476
477scoped_refptr<URLIndexPrivateData> URLIndexPrivateData::Duplicate() const {
478  scoped_refptr<URLIndexPrivateData> data_copy = new URLIndexPrivateData;
479  data_copy->last_time_rebuilt_from_history_ = last_time_rebuilt_from_history_;
480  data_copy->word_list_ = word_list_;
481  data_copy->available_words_ = available_words_;
482  data_copy->word_map_ = word_map_;
483  data_copy->char_word_map_ = char_word_map_;
484  data_copy->word_id_history_map_ = word_id_history_map_;
485  data_copy->history_id_word_map_ = history_id_word_map_;
486  data_copy->history_info_map_ = history_info_map_;
487  data_copy->word_starts_map_ = word_starts_map_;
488  return data_copy;
489  // Not copied:
490  //    search_term_cache_
491  //    pre_filter_item_count_
492  //    post_filter_item_count_
493  //    post_scoring_item_count_
494};
495
496bool URLIndexPrivateData::Empty() const {
497  return history_info_map_.empty();
498}
499
500void URLIndexPrivateData::Clear() {
501  last_time_rebuilt_from_history_ = base::Time();
502  word_list_.clear();
503  available_words_.clear();
504  word_map_.clear();
505  char_word_map_.clear();
506  word_id_history_map_.clear();
507  history_id_word_map_.clear();
508  history_info_map_.clear();
509  word_starts_map_.clear();
510}
511
512URLIndexPrivateData::~URLIndexPrivateData() {}
513
514HistoryIDSet URLIndexPrivateData::HistoryIDSetFromWords(
515    const String16Vector& unsorted_words) {
516  // Break the terms down into individual terms (words), get the candidate
517  // set for each term, and intersect each to get a final candidate list.
518  // Note that a single 'term' from the user's perspective might be
519  // a string like "http://www.somewebsite.com" which, from our perspective,
520  // is four words: 'http', 'www', 'somewebsite', and 'com'.
521  HistoryIDSet history_id_set;
522  String16Vector words(unsorted_words);
523  // Sort the words into the longest first as such are likely to narrow down
524  // the results quicker. Also, single character words are the most expensive
525  // to process so save them for last.
526  std::sort(words.begin(), words.end(), LengthGreater);
527  for (String16Vector::iterator iter = words.begin(); iter != words.end();
528       ++iter) {
529    base::string16 uni_word = *iter;
530    HistoryIDSet term_history_set = HistoryIDsForTerm(uni_word);
531    if (term_history_set.empty()) {
532      history_id_set.clear();
533      break;
534    }
535    if (iter == words.begin()) {
536      history_id_set.swap(term_history_set);
537    } else {
538      HistoryIDSet new_history_id_set;
539      std::set_intersection(history_id_set.begin(), history_id_set.end(),
540                            term_history_set.begin(), term_history_set.end(),
541                            std::inserter(new_history_id_set,
542                                          new_history_id_set.begin()));
543      history_id_set.swap(new_history_id_set);
544    }
545  }
546  return history_id_set;
547}
548
549HistoryIDSet URLIndexPrivateData::HistoryIDsForTerm(
550    const base::string16& term) {
551  if (term.empty())
552    return HistoryIDSet();
553
554  // TODO(mrossetti): Consider optimizing for very common terms such as
555  // 'http[s]', 'www', 'com', etc. Or collect the top 100 more frequently
556  // occuring words in the user's searches.
557
558  size_t term_length = term.length();
559  WordIDSet word_id_set;
560  if (term_length > 1) {
561    // See if this term or a prefix thereof is present in the cache.
562    SearchTermCacheMap::iterator best_prefix(search_term_cache_.end());
563    for (SearchTermCacheMap::iterator cache_iter = search_term_cache_.begin();
564         cache_iter != search_term_cache_.end(); ++cache_iter) {
565      if (StartsWith(term, cache_iter->first, false) &&
566          (best_prefix == search_term_cache_.end() ||
567           cache_iter->first.length() > best_prefix->first.length()))
568        best_prefix = cache_iter;
569    }
570
571    // If a prefix was found then determine the leftover characters to be used
572    // for further refining the results from that prefix.
573    Char16Set prefix_chars;
574    base::string16 leftovers(term);
575    if (best_prefix != search_term_cache_.end()) {
576      // If the prefix is an exact match for the term then grab the cached
577      // results and we're done.
578      size_t prefix_length = best_prefix->first.length();
579      if (prefix_length == term_length) {
580        best_prefix->second.used_ = true;
581        return best_prefix->second.history_id_set_;
582      }
583
584      // Otherwise we have a handy starting point.
585      // If there are no history results for this prefix then we can bail early
586      // as there will be no history results for the full term.
587      if (best_prefix->second.history_id_set_.empty()) {
588        search_term_cache_[term] = SearchTermCacheItem();
589        return HistoryIDSet();
590      }
591      word_id_set = best_prefix->second.word_id_set_;
592      prefix_chars = Char16SetFromString16(best_prefix->first);
593      leftovers = term.substr(prefix_length);
594    }
595
596    // Filter for each remaining, unique character in the term.
597    Char16Set leftover_chars = Char16SetFromString16(leftovers);
598    Char16Set unique_chars =
599        base::STLSetDifference<Char16Set>(leftover_chars, prefix_chars);
600
601    // Reduce the word set with any leftover, unprocessed characters.
602    if (!unique_chars.empty()) {
603      WordIDSet leftover_set(WordIDSetForTermChars(unique_chars));
604      // We might come up empty on the leftovers.
605      if (leftover_set.empty()) {
606        search_term_cache_[term] = SearchTermCacheItem();
607        return HistoryIDSet();
608      }
609      // Or there may not have been a prefix from which to start.
610      if (prefix_chars.empty()) {
611        word_id_set.swap(leftover_set);
612      } else {
613        WordIDSet new_word_id_set;
614        std::set_intersection(word_id_set.begin(), word_id_set.end(),
615                              leftover_set.begin(), leftover_set.end(),
616                              std::inserter(new_word_id_set,
617                                            new_word_id_set.begin()));
618        word_id_set.swap(new_word_id_set);
619      }
620    }
621
622    // We must filter the word list because the resulting word set surely
623    // contains words which do not have the search term as a proper subset.
624    for (WordIDSet::iterator word_set_iter = word_id_set.begin();
625         word_set_iter != word_id_set.end(); ) {
626      if (word_list_[*word_set_iter].find(term) == base::string16::npos)
627        word_id_set.erase(word_set_iter++);
628      else
629        ++word_set_iter;
630    }
631  } else {
632    word_id_set = WordIDSetForTermChars(Char16SetFromString16(term));
633  }
634
635  // If any words resulted then we can compose a set of history IDs by unioning
636  // the sets from each word.
637  HistoryIDSet history_id_set;
638  if (!word_id_set.empty()) {
639    for (WordIDSet::iterator word_id_iter = word_id_set.begin();
640         word_id_iter != word_id_set.end(); ++word_id_iter) {
641      WordID word_id = *word_id_iter;
642      WordIDHistoryMap::iterator word_iter = word_id_history_map_.find(word_id);
643      if (word_iter != word_id_history_map_.end()) {
644        HistoryIDSet& word_history_id_set(word_iter->second);
645        history_id_set.insert(word_history_id_set.begin(),
646                              word_history_id_set.end());
647      }
648    }
649  }
650
651  // Record a new cache entry for this word if the term is longer than
652  // a single character.
653  if (term_length > 1)
654    search_term_cache_[term] = SearchTermCacheItem(word_id_set, history_id_set);
655
656  return history_id_set;
657}
658
659WordIDSet URLIndexPrivateData::WordIDSetForTermChars(
660    const Char16Set& term_chars) {
661  WordIDSet word_id_set;
662  for (Char16Set::const_iterator c_iter = term_chars.begin();
663       c_iter != term_chars.end(); ++c_iter) {
664    CharWordIDMap::iterator char_iter = char_word_map_.find(*c_iter);
665    if (char_iter == char_word_map_.end()) {
666      // A character was not found so there are no matching results: bail.
667      word_id_set.clear();
668      break;
669    }
670    WordIDSet& char_word_id_set(char_iter->second);
671    // It is possible for there to no longer be any words associated with
672    // a particular character. Give up in that case.
673    if (char_word_id_set.empty()) {
674      word_id_set.clear();
675      break;
676    }
677
678    if (c_iter == term_chars.begin()) {
679      // First character results becomes base set of results.
680      word_id_set = char_word_id_set;
681    } else {
682      // Subsequent character results get intersected in.
683      WordIDSet new_word_id_set;
684      std::set_intersection(word_id_set.begin(), word_id_set.end(),
685                            char_word_id_set.begin(), char_word_id_set.end(),
686                            std::inserter(new_word_id_set,
687                                          new_word_id_set.begin()));
688      word_id_set.swap(new_word_id_set);
689    }
690  }
691  return word_id_set;
692}
693
694bool URLIndexPrivateData::IndexRow(
695    HistoryDatabase* history_db,
696    HistoryService* history_service,
697    const URLRow& row,
698    const std::string& languages,
699    const std::set<std::string>& scheme_whitelist) {
700  const GURL& gurl(row.url());
701
702  // Index only URLs with a whitelisted scheme.
703  if (!URLSchemeIsWhitelisted(gurl, scheme_whitelist))
704    return false;
705
706  URLID row_id = row.id();
707  // Strip out username and password before saving and indexing.
708  base::string16 url(net::FormatUrl(gurl, languages,
709      net::kFormatUrlOmitUsernamePassword,
710      net::UnescapeRule::NONE,
711      NULL, NULL, NULL));
712
713  HistoryID history_id = static_cast<HistoryID>(row_id);
714  DCHECK_LT(history_id, std::numeric_limits<HistoryID>::max());
715
716  // Add the row for quick lookup in the history info store.
717  URLRow new_row(GURL(url), row_id);
718  new_row.set_visit_count(row.visit_count());
719  new_row.set_typed_count(row.typed_count());
720  new_row.set_last_visit(row.last_visit());
721  new_row.set_title(row.title());
722  history_info_map_[history_id].url_row = new_row;
723
724  // Index the words contained in the URL and title of the row.
725  RowWordStarts word_starts;
726  AddRowWordsToIndex(new_row, &word_starts, languages);
727  word_starts_map_[history_id] = word_starts;
728
729  // Update the recent visits information or schedule the update
730  // as appropriate.
731  if (history_db) {
732    // We'd like to check that we're on the history DB thread.
733    // However, unittest code actually calls this on the UI thread.
734    // So we don't do any thread checks.
735    VisitVector recent_visits;
736    // Make sure the private data is going to get as many recent visits as
737    // ScoredHistoryMatch::GetFrecency() hopes to use.
738    DCHECK_GE(kMaxVisitsToStoreInCache, ScoredHistoryMatch::kMaxVisitsToScore);
739    if (history_db->GetMostRecentVisitsForURL(row_id,
740                                              kMaxVisitsToStoreInCache,
741                                              &recent_visits))
742      UpdateRecentVisits(row_id, recent_visits);
743  } else {
744    DCHECK(history_service);
745    ScheduleUpdateRecentVisits(history_service, row_id);
746  }
747
748  return true;
749}
750
751void URLIndexPrivateData::AddRowWordsToIndex(const URLRow& row,
752                                             RowWordStarts* word_starts,
753                                             const std::string& languages) {
754  HistoryID history_id = static_cast<HistoryID>(row.id());
755  // Split URL into individual, unique words then add in the title words.
756  const GURL& gurl(row.url());
757  const base::string16& url =
758      bookmark_utils::CleanUpUrlForMatching(gurl, languages, NULL);
759  String16Set url_words = String16SetFromString16(url,
760      word_starts ? &word_starts->url_word_starts_ : NULL);
761  const base::string16& title =
762      bookmark_utils::CleanUpTitleForMatching(row.title());
763  String16Set title_words = String16SetFromString16(title,
764      word_starts ? &word_starts->title_word_starts_ : NULL);
765  String16Set words;
766  std::set_union(url_words.begin(), url_words.end(),
767                 title_words.begin(), title_words.end(),
768                 std::insert_iterator<String16Set>(words, words.begin()));
769  for (String16Set::iterator word_iter = words.begin();
770       word_iter != words.end(); ++word_iter)
771    AddWordToIndex(*word_iter, history_id);
772
773  search_term_cache_.clear();  // Invalidate the term cache.
774}
775
776void URLIndexPrivateData::AddWordToIndex(const base::string16& term,
777                                         HistoryID history_id) {
778  WordMap::iterator word_pos = word_map_.find(term);
779  if (word_pos != word_map_.end())
780    UpdateWordHistory(word_pos->second, history_id);
781  else
782    AddWordHistory(term, history_id);
783}
784
785void URLIndexPrivateData::AddWordHistory(const base::string16& term,
786                                         HistoryID history_id) {
787  WordID word_id = word_list_.size();
788  if (available_words_.empty()) {
789    word_list_.push_back(term);
790  } else {
791    word_id = *(available_words_.begin());
792    word_list_[word_id] = term;
793    available_words_.erase(word_id);
794  }
795  word_map_[term] = word_id;
796
797  HistoryIDSet history_id_set;
798  history_id_set.insert(history_id);
799  word_id_history_map_[word_id] = history_id_set;
800  AddToHistoryIDWordMap(history_id, word_id);
801
802  // For each character in the newly added word (i.e. a word that is not
803  // already in the word index), add the word to the character index.
804  Char16Set characters = Char16SetFromString16(term);
805  for (Char16Set::iterator uni_char_iter = characters.begin();
806       uni_char_iter != characters.end(); ++uni_char_iter) {
807    base::char16 uni_char = *uni_char_iter;
808    CharWordIDMap::iterator char_iter = char_word_map_.find(uni_char);
809    if (char_iter != char_word_map_.end()) {
810      // Update existing entry in the char/word index.
811      WordIDSet& word_id_set(char_iter->second);
812      word_id_set.insert(word_id);
813    } else {
814      // Create a new entry in the char/word index.
815      WordIDSet word_id_set;
816      word_id_set.insert(word_id);
817      char_word_map_[uni_char] = word_id_set;
818    }
819  }
820}
821
822void URLIndexPrivateData::UpdateWordHistory(WordID word_id,
823                                            HistoryID history_id) {
824  WordIDHistoryMap::iterator history_pos = word_id_history_map_.find(word_id);
825  DCHECK(history_pos != word_id_history_map_.end());
826  HistoryIDSet& history_id_set(history_pos->second);
827  history_id_set.insert(history_id);
828  AddToHistoryIDWordMap(history_id, word_id);
829}
830
831void URLIndexPrivateData::AddToHistoryIDWordMap(HistoryID history_id,
832                                                WordID word_id) {
833  HistoryIDWordMap::iterator iter = history_id_word_map_.find(history_id);
834  if (iter != history_id_word_map_.end()) {
835    WordIDSet& word_id_set(iter->second);
836    word_id_set.insert(word_id);
837  } else {
838    WordIDSet word_id_set;
839    word_id_set.insert(word_id);
840    history_id_word_map_[history_id] = word_id_set;
841  }
842}
843
844void URLIndexPrivateData::RemoveRowFromIndex(const URLRow& row) {
845  RemoveRowWordsFromIndex(row);
846  HistoryID history_id = static_cast<HistoryID>(row.id());
847  history_info_map_.erase(history_id);
848  word_starts_map_.erase(history_id);
849}
850
851void URLIndexPrivateData::RemoveRowWordsFromIndex(const URLRow& row) {
852  // Remove the entries in history_id_word_map_ and word_id_history_map_ for
853  // this row.
854  HistoryID history_id = static_cast<HistoryID>(row.id());
855  WordIDSet word_id_set = history_id_word_map_[history_id];
856  history_id_word_map_.erase(history_id);
857
858  // Reconcile any changes to word usage.
859  for (WordIDSet::iterator word_id_iter = word_id_set.begin();
860       word_id_iter != word_id_set.end(); ++word_id_iter) {
861    WordID word_id = *word_id_iter;
862    word_id_history_map_[word_id].erase(history_id);
863    if (!word_id_history_map_[word_id].empty())
864      continue;  // The word is still in use.
865
866    // The word is no longer in use. Reconcile any changes to character usage.
867    base::string16 word = word_list_[word_id];
868    Char16Set characters = Char16SetFromString16(word);
869    for (Char16Set::iterator uni_char_iter = characters.begin();
870         uni_char_iter != characters.end(); ++uni_char_iter) {
871      base::char16 uni_char = *uni_char_iter;
872      char_word_map_[uni_char].erase(word_id);
873      if (char_word_map_[uni_char].empty())
874        char_word_map_.erase(uni_char);  // No longer in use.
875    }
876
877    // Complete the removal of references to the word.
878    word_id_history_map_.erase(word_id);
879    word_map_.erase(word);
880    word_list_[word_id] = base::string16();
881    available_words_.insert(word_id);
882  }
883}
884
885void URLIndexPrivateData::ResetSearchTermCache() {
886  for (SearchTermCacheMap::iterator iter = search_term_cache_.begin();
887       iter != search_term_cache_.end(); ++iter)
888    iter->second.used_ = false;
889}
890
891bool URLIndexPrivateData::SaveToFile(const base::FilePath& file_path) {
892  base::TimeTicks beginning_time = base::TimeTicks::Now();
893  InMemoryURLIndexCacheItem index_cache;
894  SavePrivateData(&index_cache);
895  std::string data;
896  if (!index_cache.SerializeToString(&data)) {
897    LOG(WARNING) << "Failed to serialize the InMemoryURLIndex cache.";
898    return false;
899  }
900
901  int size = data.size();
902  if (base::WriteFile(file_path, data.c_str(), size) != size) {
903    LOG(WARNING) << "Failed to write " << file_path.value();
904    return false;
905  }
906  UMA_HISTOGRAM_TIMES("History.InMemoryURLIndexSaveCacheTime",
907                      base::TimeTicks::Now() - beginning_time);
908  return true;
909}
910
911void URLIndexPrivateData::SavePrivateData(
912    InMemoryURLIndexCacheItem* cache) const {
913  DCHECK(cache);
914  cache->set_last_rebuild_timestamp(
915      last_time_rebuilt_from_history_.ToInternalValue());
916  cache->set_version(saved_cache_version_);
917  // history_item_count_ is no longer used but rather than change the protobuf
918  // definition use a placeholder. This will go away with the switch to SQLite.
919  cache->set_history_item_count(0);
920  SaveWordList(cache);
921  SaveWordMap(cache);
922  SaveCharWordMap(cache);
923  SaveWordIDHistoryMap(cache);
924  SaveHistoryInfoMap(cache);
925  SaveWordStartsMap(cache);
926}
927
928void URLIndexPrivateData::SaveWordList(InMemoryURLIndexCacheItem* cache) const {
929  if (word_list_.empty())
930    return;
931  WordListItem* list_item = cache->mutable_word_list();
932  list_item->set_word_count(word_list_.size());
933  for (String16Vector::const_iterator iter = word_list_.begin();
934       iter != word_list_.end(); ++iter)
935    list_item->add_word(base::UTF16ToUTF8(*iter));
936}
937
938void URLIndexPrivateData::SaveWordMap(InMemoryURLIndexCacheItem* cache) const {
939  if (word_map_.empty())
940    return;
941  WordMapItem* map_item = cache->mutable_word_map();
942  map_item->set_item_count(word_map_.size());
943  for (WordMap::const_iterator iter = word_map_.begin();
944       iter != word_map_.end(); ++iter) {
945    WordMapEntry* map_entry = map_item->add_word_map_entry();
946    map_entry->set_word(base::UTF16ToUTF8(iter->first));
947    map_entry->set_word_id(iter->second);
948  }
949}
950
951void URLIndexPrivateData::SaveCharWordMap(
952    InMemoryURLIndexCacheItem* cache) const {
953  if (char_word_map_.empty())
954    return;
955  CharWordMapItem* map_item = cache->mutable_char_word_map();
956  map_item->set_item_count(char_word_map_.size());
957  for (CharWordIDMap::const_iterator iter = char_word_map_.begin();
958       iter != char_word_map_.end(); ++iter) {
959    CharWordMapEntry* map_entry = map_item->add_char_word_map_entry();
960    map_entry->set_char_16(iter->first);
961    const WordIDSet& word_id_set(iter->second);
962    map_entry->set_item_count(word_id_set.size());
963    for (WordIDSet::const_iterator set_iter = word_id_set.begin();
964         set_iter != word_id_set.end(); ++set_iter)
965      map_entry->add_word_id(*set_iter);
966  }
967}
968
969void URLIndexPrivateData::SaveWordIDHistoryMap(
970    InMemoryURLIndexCacheItem* cache) const {
971  if (word_id_history_map_.empty())
972    return;
973  WordIDHistoryMapItem* map_item = cache->mutable_word_id_history_map();
974  map_item->set_item_count(word_id_history_map_.size());
975  for (WordIDHistoryMap::const_iterator iter = word_id_history_map_.begin();
976       iter != word_id_history_map_.end(); ++iter) {
977    WordIDHistoryMapEntry* map_entry =
978        map_item->add_word_id_history_map_entry();
979    map_entry->set_word_id(iter->first);
980    const HistoryIDSet& history_id_set(iter->second);
981    map_entry->set_item_count(history_id_set.size());
982    for (HistoryIDSet::const_iterator set_iter = history_id_set.begin();
983         set_iter != history_id_set.end(); ++set_iter)
984      map_entry->add_history_id(*set_iter);
985  }
986}
987
988void URLIndexPrivateData::SaveHistoryInfoMap(
989    InMemoryURLIndexCacheItem* cache) const {
990  if (history_info_map_.empty())
991    return;
992  HistoryInfoMapItem* map_item = cache->mutable_history_info_map();
993  map_item->set_item_count(history_info_map_.size());
994  for (HistoryInfoMap::const_iterator iter = history_info_map_.begin();
995       iter != history_info_map_.end(); ++iter) {
996    HistoryInfoMapEntry* map_entry = map_item->add_history_info_map_entry();
997    map_entry->set_history_id(iter->first);
998    const URLRow& url_row(iter->second.url_row);
999    // Note: We only save information that contributes to the index so there
1000    // is no need to save search_term_cache_ (not persistent).
1001    map_entry->set_visit_count(url_row.visit_count());
1002    map_entry->set_typed_count(url_row.typed_count());
1003    map_entry->set_last_visit(url_row.last_visit().ToInternalValue());
1004    map_entry->set_url(url_row.url().spec());
1005    map_entry->set_title(base::UTF16ToUTF8(url_row.title()));
1006    const VisitInfoVector& visits(iter->second.visits);
1007    for (VisitInfoVector::const_iterator visit_iter = visits.begin();
1008         visit_iter != visits.end(); ++visit_iter) {
1009      HistoryInfoMapEntry_VisitInfo* visit_info = map_entry->add_visits();
1010      visit_info->set_visit_time(visit_iter->first.ToInternalValue());
1011      visit_info->set_transition_type(visit_iter->second);
1012    }
1013  }
1014}
1015
1016void URLIndexPrivateData::SaveWordStartsMap(
1017    InMemoryURLIndexCacheItem* cache) const {
1018  if (word_starts_map_.empty())
1019    return;
1020  // For unit testing: Enable saving of the cache as an earlier version to
1021  // allow testing of cache file upgrading in ReadFromFile().
1022  // TODO(mrossetti): Instead of intruding on production code with this kind of
1023  // test harness, save a copy of an older version cache with known results.
1024  // Implement this when switching the caching over to SQLite.
1025  if (saved_cache_version_ < 1)
1026    return;
1027
1028  WordStartsMapItem* map_item = cache->mutable_word_starts_map();
1029  map_item->set_item_count(word_starts_map_.size());
1030  for (WordStartsMap::const_iterator iter = word_starts_map_.begin();
1031       iter != word_starts_map_.end(); ++iter) {
1032    WordStartsMapEntry* map_entry = map_item->add_word_starts_map_entry();
1033    map_entry->set_history_id(iter->first);
1034    const RowWordStarts& word_starts(iter->second);
1035    for (WordStarts::const_iterator i = word_starts.url_word_starts_.begin();
1036         i != word_starts.url_word_starts_.end(); ++i)
1037      map_entry->add_url_word_starts(*i);
1038    for (WordStarts::const_iterator i = word_starts.title_word_starts_.begin();
1039         i != word_starts.title_word_starts_.end(); ++i)
1040      map_entry->add_title_word_starts(*i);
1041  }
1042}
1043
1044bool URLIndexPrivateData::RestorePrivateData(
1045    const InMemoryURLIndexCacheItem& cache,
1046    const std::string& languages) {
1047  last_time_rebuilt_from_history_ =
1048      base::Time::FromInternalValue(cache.last_rebuild_timestamp());
1049  const base::TimeDelta rebuilt_ago =
1050      base::Time::Now() - last_time_rebuilt_from_history_;
1051  if ((rebuilt_ago > base::TimeDelta::FromDays(7)) ||
1052      (rebuilt_ago < base::TimeDelta::FromDays(-1))) {
1053    // Cache is more than a week old or, somehow, from some time in the future.
1054    // It's probably a good time to rebuild the index from history to
1055    // allow synced entries to now appear, expired entries to disappear, etc.
1056    // Allow one day in the future to make the cache not rebuild on simple
1057    // system clock changes such as time zone changes.
1058    return false;
1059  }
1060  if (cache.has_version()) {
1061    if (cache.version() < kCurrentCacheFileVersion) {
1062      // Don't try to restore an old format cache file.  (This will cause
1063      // the InMemoryURLIndex to schedule rebuilding the URLIndexPrivateData
1064      // from history.)
1065      return false;
1066    }
1067    restored_cache_version_ = cache.version();
1068  }
1069  return RestoreWordList(cache) && RestoreWordMap(cache) &&
1070      RestoreCharWordMap(cache) && RestoreWordIDHistoryMap(cache) &&
1071      RestoreHistoryInfoMap(cache) && RestoreWordStartsMap(cache, languages);
1072}
1073
1074bool URLIndexPrivateData::RestoreWordList(
1075    const InMemoryURLIndexCacheItem& cache) {
1076  if (!cache.has_word_list())
1077    return false;
1078  const WordListItem& list_item(cache.word_list());
1079  uint32 expected_item_count = list_item.word_count();
1080  uint32 actual_item_count = list_item.word_size();
1081  if (actual_item_count == 0 || actual_item_count != expected_item_count)
1082    return false;
1083  const RepeatedPtrField<std::string>& words(list_item.word());
1084  for (RepeatedPtrField<std::string>::const_iterator iter = words.begin();
1085       iter != words.end(); ++iter)
1086    word_list_.push_back(base::UTF8ToUTF16(*iter));
1087  return true;
1088}
1089
1090bool URLIndexPrivateData::RestoreWordMap(
1091    const InMemoryURLIndexCacheItem& cache) {
1092  if (!cache.has_word_map())
1093    return false;
1094  const WordMapItem& list_item(cache.word_map());
1095  uint32 expected_item_count = list_item.item_count();
1096  uint32 actual_item_count = list_item.word_map_entry_size();
1097  if (actual_item_count == 0 || actual_item_count != expected_item_count)
1098    return false;
1099  const RepeatedPtrField<WordMapEntry>& entries(list_item.word_map_entry());
1100  for (RepeatedPtrField<WordMapEntry>::const_iterator iter = entries.begin();
1101       iter != entries.end(); ++iter)
1102    word_map_[base::UTF8ToUTF16(iter->word())] = iter->word_id();
1103  return true;
1104}
1105
1106bool URLIndexPrivateData::RestoreCharWordMap(
1107    const InMemoryURLIndexCacheItem& cache) {
1108  if (!cache.has_char_word_map())
1109    return false;
1110  const CharWordMapItem& list_item(cache.char_word_map());
1111  uint32 expected_item_count = list_item.item_count();
1112  uint32 actual_item_count = list_item.char_word_map_entry_size();
1113  if (actual_item_count == 0 || actual_item_count != expected_item_count)
1114    return false;
1115  const RepeatedPtrField<CharWordMapEntry>&
1116      entries(list_item.char_word_map_entry());
1117  for (RepeatedPtrField<CharWordMapEntry>::const_iterator iter =
1118       entries.begin(); iter != entries.end(); ++iter) {
1119    expected_item_count = iter->item_count();
1120    actual_item_count = iter->word_id_size();
1121    if (actual_item_count == 0 || actual_item_count != expected_item_count)
1122      return false;
1123    base::char16 uni_char = static_cast<base::char16>(iter->char_16());
1124    WordIDSet word_id_set;
1125    const RepeatedField<int32>& word_ids(iter->word_id());
1126    for (RepeatedField<int32>::const_iterator jiter = word_ids.begin();
1127         jiter != word_ids.end(); ++jiter)
1128      word_id_set.insert(*jiter);
1129    char_word_map_[uni_char] = word_id_set;
1130  }
1131  return true;
1132}
1133
1134bool URLIndexPrivateData::RestoreWordIDHistoryMap(
1135    const InMemoryURLIndexCacheItem& cache) {
1136  if (!cache.has_word_id_history_map())
1137    return false;
1138  const WordIDHistoryMapItem& list_item(cache.word_id_history_map());
1139  uint32 expected_item_count = list_item.item_count();
1140  uint32 actual_item_count = list_item.word_id_history_map_entry_size();
1141  if (actual_item_count == 0 || actual_item_count != expected_item_count)
1142    return false;
1143  const RepeatedPtrField<WordIDHistoryMapEntry>&
1144      entries(list_item.word_id_history_map_entry());
1145  for (RepeatedPtrField<WordIDHistoryMapEntry>::const_iterator iter =
1146       entries.begin(); iter != entries.end(); ++iter) {
1147    expected_item_count = iter->item_count();
1148    actual_item_count = iter->history_id_size();
1149    if (actual_item_count == 0 || actual_item_count != expected_item_count)
1150      return false;
1151    WordID word_id = iter->word_id();
1152    HistoryIDSet history_id_set;
1153    const RepeatedField<int64>& history_ids(iter->history_id());
1154    for (RepeatedField<int64>::const_iterator jiter = history_ids.begin();
1155         jiter != history_ids.end(); ++jiter) {
1156      history_id_set.insert(*jiter);
1157      AddToHistoryIDWordMap(*jiter, word_id);
1158    }
1159    word_id_history_map_[word_id] = history_id_set;
1160  }
1161  return true;
1162}
1163
1164bool URLIndexPrivateData::RestoreHistoryInfoMap(
1165    const InMemoryURLIndexCacheItem& cache) {
1166  if (!cache.has_history_info_map())
1167    return false;
1168  const HistoryInfoMapItem& list_item(cache.history_info_map());
1169  uint32 expected_item_count = list_item.item_count();
1170  uint32 actual_item_count = list_item.history_info_map_entry_size();
1171  if (actual_item_count == 0 || actual_item_count != expected_item_count)
1172    return false;
1173  const RepeatedPtrField<HistoryInfoMapEntry>&
1174      entries(list_item.history_info_map_entry());
1175  for (RepeatedPtrField<HistoryInfoMapEntry>::const_iterator iter =
1176       entries.begin(); iter != entries.end(); ++iter) {
1177    HistoryID history_id = iter->history_id();
1178    GURL url(iter->url());
1179    URLRow url_row(url, history_id);
1180    url_row.set_visit_count(iter->visit_count());
1181    url_row.set_typed_count(iter->typed_count());
1182    url_row.set_last_visit(base::Time::FromInternalValue(iter->last_visit()));
1183    if (iter->has_title()) {
1184      base::string16 title(base::UTF8ToUTF16(iter->title()));
1185      url_row.set_title(title);
1186    }
1187    history_info_map_[history_id].url_row = url_row;
1188
1189    // Restore visits list.
1190    VisitInfoVector visits;
1191    visits.reserve(iter->visits_size());
1192    for (int i = 0; i < iter->visits_size(); ++i) {
1193      visits.push_back(std::make_pair(
1194          base::Time::FromInternalValue(iter->visits(i).visit_time()),
1195          static_cast<content::PageTransition>(iter->visits(i).
1196                                               transition_type())));
1197    }
1198    history_info_map_[history_id].visits = visits;
1199  }
1200  return true;
1201}
1202
1203bool URLIndexPrivateData::RestoreWordStartsMap(
1204    const InMemoryURLIndexCacheItem& cache,
1205    const std::string& languages) {
1206  // Note that this function must be called after RestoreHistoryInfoMap() has
1207  // been run as the word starts may have to be recalculated from the urls and
1208  // page titles.
1209  if (cache.has_word_starts_map()) {
1210    const WordStartsMapItem& list_item(cache.word_starts_map());
1211    uint32 expected_item_count = list_item.item_count();
1212    uint32 actual_item_count = list_item.word_starts_map_entry_size();
1213    if (actual_item_count == 0 || actual_item_count != expected_item_count)
1214      return false;
1215    const RepeatedPtrField<WordStartsMapEntry>&
1216        entries(list_item.word_starts_map_entry());
1217    for (RepeatedPtrField<WordStartsMapEntry>::const_iterator iter =
1218         entries.begin(); iter != entries.end(); ++iter) {
1219      HistoryID history_id = iter->history_id();
1220      RowWordStarts word_starts;
1221      // Restore the URL word starts.
1222      const RepeatedField<int32>& url_starts(iter->url_word_starts());
1223      for (RepeatedField<int32>::const_iterator jiter = url_starts.begin();
1224           jiter != url_starts.end(); ++jiter)
1225        word_starts.url_word_starts_.push_back(*jiter);
1226      // Restore the page title word starts.
1227      const RepeatedField<int32>& title_starts(iter->title_word_starts());
1228      for (RepeatedField<int32>::const_iterator jiter = title_starts.begin();
1229           jiter != title_starts.end(); ++jiter)
1230        word_starts.title_word_starts_.push_back(*jiter);
1231      word_starts_map_[history_id] = word_starts;
1232    }
1233  } else {
1234    // Since the cache did not contain any word starts we must rebuild then from
1235    // the URL and page titles.
1236    for (HistoryInfoMap::const_iterator iter = history_info_map_.begin();
1237         iter != history_info_map_.end(); ++iter) {
1238      RowWordStarts word_starts;
1239      const URLRow& row(iter->second.url_row);
1240      const base::string16& url = bookmark_utils::CleanUpUrlForMatching(
1241          row.url(), languages, NULL);
1242      String16VectorFromString16(url, false, &word_starts.url_word_starts_);
1243      const base::string16& title =
1244          bookmark_utils::CleanUpTitleForMatching(row.title());
1245      String16VectorFromString16(title, false, &word_starts.title_word_starts_);
1246      word_starts_map_[iter->first] = word_starts;
1247    }
1248  }
1249  return true;
1250}
1251
1252// static
1253bool URLIndexPrivateData::URLSchemeIsWhitelisted(
1254    const GURL& gurl,
1255    const std::set<std::string>& whitelist) {
1256  return whitelist.find(gurl.scheme()) != whitelist.end();
1257}
1258
1259
1260// SearchTermCacheItem ---------------------------------------------------------
1261
1262URLIndexPrivateData::SearchTermCacheItem::SearchTermCacheItem(
1263    const WordIDSet& word_id_set,
1264    const HistoryIDSet& history_id_set)
1265    : word_id_set_(word_id_set),
1266      history_id_set_(history_id_set),
1267      used_(true) {}
1268
1269URLIndexPrivateData::SearchTermCacheItem::SearchTermCacheItem()
1270    : used_(true) {}
1271
1272URLIndexPrivateData::SearchTermCacheItem::~SearchTermCacheItem() {}
1273
1274
1275// URLIndexPrivateData::AddHistoryMatch ----------------------------------------
1276
1277URLIndexPrivateData::AddHistoryMatch::AddHistoryMatch(
1278    const URLIndexPrivateData& private_data,
1279    const std::string& languages,
1280    BookmarkService* bookmark_service,
1281    const base::string16& lower_string,
1282    const String16Vector& lower_terms,
1283    const base::Time now)
1284  : private_data_(private_data),
1285    languages_(languages),
1286    bookmark_service_(bookmark_service),
1287    lower_string_(lower_string),
1288    lower_terms_(lower_terms),
1289    now_(now) {
1290  // Calculate offsets for each term.  For instance, the offset for
1291  // ".net" should be 1, indicating that the actual word-part of the term
1292  // starts at offset 1.
1293  lower_terms_to_word_starts_offsets_.resize(lower_terms_.size(), 0u);
1294  for (size_t i = 0; i < lower_terms_.size(); ++i) {
1295    base::i18n::BreakIterator iter(lower_terms_[i],
1296                                   base::i18n::BreakIterator::BREAK_WORD);
1297    // If the iterator doesn't work, assume an offset of 0.
1298    if (!iter.Init())
1299      continue;
1300    // Find the first word start.
1301    while (iter.Advance() && !iter.IsWord()) {}
1302    if (iter.IsWord())
1303      lower_terms_to_word_starts_offsets_[i] = iter.prev();
1304    // Else: the iterator didn't find a word break.  Assume an offset of 0.
1305  }
1306}
1307
1308URLIndexPrivateData::AddHistoryMatch::~AddHistoryMatch() {}
1309
1310void URLIndexPrivateData::AddHistoryMatch::operator()(
1311    const HistoryID history_id) {
1312  HistoryInfoMap::const_iterator hist_pos =
1313      private_data_.history_info_map_.find(history_id);
1314  if (hist_pos != private_data_.history_info_map_.end()) {
1315    const URLRow& hist_item = hist_pos->second.url_row;
1316    const VisitInfoVector& visits = hist_pos->second.visits;
1317    WordStartsMap::const_iterator starts_pos =
1318        private_data_.word_starts_map_.find(history_id);
1319    DCHECK(starts_pos != private_data_.word_starts_map_.end());
1320    ScoredHistoryMatch match(hist_item, visits, languages_, lower_string_,
1321                             lower_terms_, lower_terms_to_word_starts_offsets_,
1322                             starts_pos->second, now_, bookmark_service_);
1323    if (match.raw_score() > 0)
1324      scored_matches_.push_back(match);
1325  }
1326}
1327
1328
1329// URLIndexPrivateData::HistoryItemFactorGreater -------------------------------
1330
1331URLIndexPrivateData::HistoryItemFactorGreater::HistoryItemFactorGreater(
1332    const HistoryInfoMap& history_info_map)
1333    : history_info_map_(history_info_map) {
1334}
1335
1336URLIndexPrivateData::HistoryItemFactorGreater::~HistoryItemFactorGreater() {}
1337
1338bool URLIndexPrivateData::HistoryItemFactorGreater::operator()(
1339    const HistoryID h1,
1340    const HistoryID h2) {
1341  HistoryInfoMap::const_iterator entry1(history_info_map_.find(h1));
1342  if (entry1 == history_info_map_.end())
1343    return false;
1344  HistoryInfoMap::const_iterator entry2(history_info_map_.find(h2));
1345  if (entry2 == history_info_map_.end())
1346    return true;
1347  const URLRow& r1(entry1->second.url_row);
1348  const URLRow& r2(entry2->second.url_row);
1349  // First cut: typed count, visit count, recency.
1350  // TODO(mrossetti): This is too simplistic. Consider an approach which ranks
1351  // recently visited (within the last 12/24 hours) as highly important. Get
1352  // input from mpearson.
1353  if (r1.typed_count() != r2.typed_count())
1354    return (r1.typed_count() > r2.typed_count());
1355  if (r1.visit_count() != r2.visit_count())
1356    return (r1.visit_count() > r2.visit_count());
1357  return (r1.last_visit() > r2.last_visit());
1358}
1359
1360}  // namespace history
1361