// 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 #include #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 HistoryData::GetKnownResults( const std::string& query) const { scoped_ptr 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 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 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