blob: 636d60f70167ca3faabd71e885b8dbe956722216 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/app_list/search/history_data.h"
#include <algorithm>
#include <vector>
#include "base/bind.h"
#include "ui/app_list/search/history_data_observer.h"
#include "ui/app_list/search/history_data_store.h"
namespace app_list {
namespace {
// A struct used to sort query entries by time.
struct EntrySortData {
EntrySortData() : query(NULL), update_time(NULL) {}
EntrySortData(const std::string* query, const base::Time* update_time)
: query(query), update_time(update_time) {}
const std::string* query;
const base::Time* update_time;
};
bool EntrySortByTimeAscending(const EntrySortData& entry1,
const EntrySortData& entry2) {
return *entry1.update_time < *entry2.update_time;
}
} // namespace
HistoryData::Data::Data() {
}
HistoryData::Data::~Data() {
}
HistoryData::HistoryData(HistoryDataStore* store,
size_t max_primary,
size_t max_secondary)
: store_(store), max_primary_(max_primary), max_secondary_(max_secondary) {
store_->Load(base::Bind(&HistoryData::OnStoreLoaded, AsWeakPtr()));
}
HistoryData::~HistoryData() {
}
void HistoryData::Add(const std::string& query, const std::string& result_id) {
Associations::iterator assoc_it = associations_.find(query);
// Creates a primary association if query is seen for the first time.
if (assoc_it == associations_.end()) {
Data& data = associations_[query];
data.primary = result_id;
data.update_time = base::Time::Now();
store_->SetPrimary(query, result_id);
store_->SetUpdateTime(query, data.update_time);
TrimEntries();
return;
}
Data& data = assoc_it->second;
data.update_time = base::Time::Now();
store_->SetUpdateTime(query, data.update_time);
SecondaryDeque& secondary = data.secondary;
if (!secondary.empty() && secondary.back() == result_id) {
// Nothing to do if the last secondary is the current primary.
if (data.primary == result_id)
return;
// If |result_id| is the last (the most recent) secondary and it's not the
// current primary, promote it and demote the primary.
secondary.pop_back();
secondary.push_back(data.primary);
data.primary = result_id;
store_->SetPrimary(query, result_id);
store_->SetSecondary(query, secondary);
return;
}
// Otherwise, append to secondary list.
SecondaryDeque::iterator secondary_it =
std::find(secondary.begin(), secondary.end(), result_id);
if (secondary_it != secondary.end())
secondary.erase(secondary_it);
secondary.push_back(result_id);
if (secondary.size() > max_secondary_)
secondary.pop_front();
store_->SetSecondary(query, secondary);
}
scoped_ptr<KnownResults> HistoryData::GetKnownResults(
const std::string& query) const {
scoped_ptr<KnownResults> results(new KnownResults);
for (Associations::const_iterator assoc_it = associations_.lower_bound(query);
assoc_it != associations_.end();
++assoc_it) {
// Break out of the loop if |query| is no longer a prefix.
if (assoc_it->first.size() < query.size() ||
strncmp(assoc_it->first.c_str(), query.c_str(), query.length()) != 0) {
break;
}
const bool perfect = assoc_it->first == query;
// Primary match should always be returned.
(*results)[assoc_it->second.primary] =
perfect ? PERFECT_PRIMARY : PREFIX_PRIMARY;
const KnownResultType secondary_type =
perfect ? PERFECT_SECONDARY : PREFIX_SECONDARY;
const HistoryData::SecondaryDeque& secondary = assoc_it->second.secondary;
for (HistoryData::SecondaryDeque::const_iterator secondary_it =
secondary.begin();
secondary_it != secondary.end();
++secondary_it) {
const std::string& secondary_result_id = (*secondary_it);
// Secondary match only gets added if there there is no primary match.
if (results->find(secondary_result_id) != results->end())
continue;
(*results)[secondary_result_id] = secondary_type;
}
}
return results.Pass();
}
void HistoryData::AddObserver(HistoryDataObserver* observer) {
observers_.AddObserver(observer);
}
void HistoryData::RemoveObserver(HistoryDataObserver* observer) {
observers_.RemoveObserver(observer);
}
void HistoryData::OnStoreLoaded(scoped_ptr<Associations> loaded_data) {
if (loaded_data)
loaded_data->swap(associations_);
FOR_EACH_OBSERVER(
HistoryDataObserver, observers_, OnHistoryDataLoadedFromStore());
}
void HistoryData::TrimEntries() {
if (associations_.size() <= max_primary_)
return;
std::vector<EntrySortData> entries;
for (Associations::const_iterator it = associations_.begin();
it != associations_.end();
++it) {
entries.push_back(EntrySortData(&it->first, &it->second.update_time));
}
const size_t entries_to_remove = associations_.size() - max_primary_;
std::partial_sort(entries.begin(),
entries.begin() + entries_to_remove,
entries.end(),
&EntrySortByTimeAscending);
for (size_t i = 0; i < entries_to_remove; ++i) {
const std::string& query = *entries[i].query;
store_->Delete(query);
associations_.erase(query);
}
}
} // namespace app_list