root/chrome/browser/ui/app_list/search/mixer.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. RemoveDuplicates
  2. Publish
  3. boost_
  4. Group
  5. AddProvider
  6. FetchResults
  7. results
  8. Init
  9. AddProviderToGroup
  10. MixAndPublish
  11. FetchResults

// 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 "chrome/browser/ui/app_list/search/mixer.h"

#include <algorithm>
#include <set>
#include <string>
#include <vector>

#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
#include "chrome/browser/ui/app_list/search/search_provider.h"

namespace app_list {

namespace {

// Maximum number of results to show.
const size_t kMaxResults = 6;
const size_t kMaxMainGroupResults = 4;
const size_t kMaxWebstoreResults = 2;
const size_t kMaxPeopleResults = 2;

// A value to indicate no max number of results limit.
const size_t kNoMaxResultsLimit = 0;

// Used for sorting and mixing results.
struct SortData {
  SortData()
      : result(NULL),
        score(0.0) {
  }
  SortData(ChromeSearchResult* result, double score)
      : result(result),
        score(score) {
  }

  bool operator<(const SortData& other) const {
    // This data precedes (less than) |other| if it has higher score.
    return score > other.score;
  }

  ChromeSearchResult* result;  // Not owned.
  double score;
};
typedef std::vector<SortData> SortedResults;

// Removes duplicates from |results|.
void RemoveDuplicates(SortedResults* results) {
  SortedResults final;
  final.reserve(results->size());

  std::set<std::string> id_set;
  for (SortedResults::iterator it = results->begin();
       it != results->end();
       ++it) {
    const std::string& id = it->result->id();
    if (id_set.find(id) != id_set.end())
      continue;

    id_set.insert(id);
    final.push_back(*it);
  }

  results->swap(final);
}

// Publishes the given |results| to |ui_results|. Reuse existing ones to avoid
// flickering.
void Publish(const SortedResults& results,
             AppListModel::SearchResults* ui_results) {
  for (size_t i = 0; i < results.size(); ++i) {
    ChromeSearchResult* result = results[i].result;

    ChromeSearchResult* ui_result = i < ui_results->item_count() ?
        static_cast<ChromeSearchResult*>(ui_results->GetItemAt(i)) : NULL;
    if (ui_result && ui_result->id() == result->id()) {
      ui_result->set_title(result->title());
      ui_result->set_title_tags(result->title_tags());
      ui_result->set_details(result->details());
      ui_result->set_details_tags(result->details_tags());
      ui_results->NotifyItemsChanged(i, 1);
    } else {
      if (ui_result)
        ui_results->DeleteAt(i);
      ui_results->AddAt(i, result->Duplicate().release());
    }
  }

  while (ui_results->item_count() > results.size())
    ui_results->DeleteAt(ui_results->item_count() - 1);
}

}  // namespace

// Used to group relevant providers together fox mixing their results.
class Mixer::Group {
 public:
  Group(size_t max_results, double boost)
      : max_results_(max_results),
        boost_(boost) {
  }
  ~Group() {}

  void AddProvider(SearchProvider* provider) {
    providers_.push_back(provider);
  }

  void FetchResults(const KnownResults& known_results) {
    results_.clear();

    for (Providers::const_iterator provider_it = providers_.begin();
         provider_it != providers_.end();
         ++provider_it) {
      for (SearchProvider::Results::const_iterator
               result_it = (*provider_it)->results().begin();
               result_it != (*provider_it)->results().end();
               ++result_it) {
        DCHECK_GE((*result_it)->relevance(), 0.0);
        DCHECK_LE((*result_it)->relevance(), 1.0);
        DCHECK(!(*result_it)->id().empty());

        double boost = boost_;
        KnownResults::const_iterator known_it =
            known_results.find((*result_it)->id());
        if (known_it != known_results.end()) {
          switch (known_it->second) {
            case PERFECT_PRIMARY:
              boost = 4.0;
              break;
            case PREFIX_PRIMARY:
              boost = 3.75;
              break;
            case PERFECT_SECONDARY:
              boost = 3.25;
              break;
            case PREFIX_SECONDARY:
              boost = 3.0;
              break;
            case UNKNOWN_RESULT:
              NOTREACHED() << "Unknown result in KnownResults?";
              break;
          }
        }

        results_.push_back(
            SortData(*result_it, (*result_it)->relevance() + boost));
      }
    }

    std::sort(results_.begin(), results_.end());
    if (max_results_ != kNoMaxResultsLimit && results_.size() > max_results_)
      results_.resize(max_results_);
  }

  const SortedResults& results() const { return results_; }

 private:
  typedef std::vector<SearchProvider*> Providers;
  const size_t max_results_;
  const double boost_;

  Providers providers_;  // Not owned.
  SortedResults results_;

  DISALLOW_COPY_AND_ASSIGN(Group);
};

Mixer::Mixer(AppListModel::SearchResults* ui_results)
    : ui_results_(ui_results) {}
Mixer::~Mixer() {}

void Mixer::Init() {
  groups_.push_back(new Group(kMaxMainGroupResults, 3.0));
  groups_.push_back(new Group(kNoMaxResultsLimit, 2.0));
  groups_.push_back(new Group(kMaxWebstoreResults, 1.0));
  groups_.push_back(new Group(kMaxPeopleResults, 0.0));
}

void Mixer::AddProviderToGroup(GroupId group, SearchProvider* provider) {
  size_t group_index = static_cast<size_t>(group);
  groups_[group_index]->AddProvider(provider);
}

void Mixer::MixAndPublish(const KnownResults& known_results) {
  FetchResults(known_results);

  SortedResults results;
  results.reserve(kMaxResults);

  // Adds main group and web store results first.
  results.insert(results.end(),
                 groups_[MAIN_GROUP]->results().begin(),
                 groups_[MAIN_GROUP]->results().end());
  results.insert(results.end(),
                 groups_[WEBSTORE_GROUP]->results().begin(),
                 groups_[WEBSTORE_GROUP]->results().end());
  results.insert(results.end(),
                 groups_[PEOPLE_GROUP]->results().begin(),
                 groups_[PEOPLE_GROUP]->results().end());

  // Collapse duplicate apps from local and web store.
  RemoveDuplicates(&results);

  DCHECK_GE(kMaxResults, results.size());
  size_t remaining_slots = kMaxResults - results.size();

  // Reserves at least one slot for the omnibox result. If there is no available
  // slot for omnibox results, removes the last one from web store.
  const size_t omnibox_results = groups_[OMNIBOX_GROUP]->results().size();
  if (!remaining_slots && omnibox_results)
    results.pop_back();

  remaining_slots = std::min(kMaxResults - results.size(), omnibox_results);
  results.insert(results.end(),
                 groups_[OMNIBOX_GROUP]->results().begin(),
                 groups_[OMNIBOX_GROUP]->results().begin() + remaining_slots);

  std::sort(results.begin(), results.end());
  RemoveDuplicates(&results);
  if (results.size() > kMaxResults)
    results.resize(kMaxResults);

  Publish(results, ui_results_);
}

void Mixer::FetchResults(const KnownResults& known_results) {
  for (Groups::iterator group_it = groups_.begin();
       group_it != groups_.end();
       ++group_it) {
    (*group_it)->FetchResults(known_results);
  }
}

}  // namespace app_list

/* [<][>][^][v][top][bottom][index][help] */