blob: 7d62689036d5bca5a0256ae9b4c7fc2abda83b29 [file] [log] [blame]
// Copyright 2016 The Fuchsia 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 "peridot/bin/suggestion_engine/next_processor.h"
#include "peridot/bin/suggestion_engine/suggestion_engine_helper.h"
namespace modular {
NextProcessor::NextProcessor(std::shared_ptr<SuggestionDebugImpl> debug)
: debug_(debug), processing_(false) {}
NextProcessor::~NextProcessor() = default;
void NextProcessor::RegisterListener(
fidl::InterfaceHandle<fuchsia::modular::NextListener> listener,
const size_t max_results) {
auto listenerPtr = listener.Bind();
// Notify the listener of the current next suggestions
NotifyOfResults(listenerPtr, max_results);
// Save the listener
listeners_.emplace_back(std::move(listenerPtr), max_results);
// Register connection error handler on new listener to remove from list
// if connection drops. This code is mostly borrowed from InterfacePtrSet
fuchsia::modular::NextListenerPtr& nextPtr = listeners_.back().first;
fuchsia::modular::NextListener* pointer = nextPtr.get();
nextPtr.set_error_handler([pointer, this](zx_status_t status) {
auto it = std::find_if(
listeners_.begin(), listeners_.end(),
[pointer](const auto& p) { return (p.first.get() == pointer); });
assert(it != listeners_.end());
listeners_.erase(it);
});
}
void NextProcessor::RegisterInterruptionListener(
fidl::InterfaceHandle<fuchsia::modular::InterruptionListener> listener) {
interruptions_processor_.RegisterListener(std::move(listener));
}
void NextProcessor::AddProposal(const std::string& component_url,
fuchsia::modular::Proposal proposal) {
AddProposal(component_url, "" /* preloaded_story_id */, std::move(proposal));
}
void NextProcessor::AddProposal(const std::string& component_url,
const std::string& preloaded_story_id,
fuchsia::modular::Proposal proposal) {
NotifyOfProcessingChange(true);
// The component_url and proposal ID form a unique identifier for a proposal.
// If one already exists, remove it before adding the new one.
RemoveProposal(component_url, proposal.id);
auto prototype = CreateSuggestionPrototype(
&prototypes_, component_url, preloaded_story_id, std::move(proposal));
if (prototype->proposal.listener) {
prototype->bound_listener = prototype->proposal.listener.Bind();
prototype->bound_listener.set_error_handler(
[this, proposal_id = prototype->proposal.id,
component_url](zx_status_t status) {
RemoveProposal(component_url, proposal_id);
});
}
auto ranked_suggestion = RankedSuggestion::New(prototype);
// TODO(miguelfrde): Make NextProcessor not depend on InterruptionsProcessor.
if (interruptions_processor_.MaybeInterrupt(*ranked_suggestion)) {
ranked_suggestion->interrupting = true;
debug_->OnInterrupt(prototype);
}
suggestions_.AddSuggestion(std::move(ranked_suggestion));
UpdateRanking();
}
void NextProcessor::RemoveProposal(const std::string& component_url,
const std::string& proposal_id) {
const auto key = std::make_pair(component_url, proposal_id);
auto toRemove = prototypes_.find(key);
if (toRemove != prototypes_.end()) {
// can't erase right off the bat because the prototype must remain valid
// until removed from the ranked list
RemoveProposalFromList(component_url, proposal_id);
prototypes_.erase(toRemove);
}
}
SuggestionPrototype* NextProcessor::GetSuggestion(
const std::string& component_url, const std::string& proposal_id) const {
RankedSuggestion* ranked_suggestion =
suggestions_.GetSuggestion(component_url, proposal_id);
if (ranked_suggestion) {
return ranked_suggestion->prototype;
}
return nullptr;
}
void NextProcessor::RemoveProposalFromList(const std::string& component_url,
const std::string& proposal_id) {
NotifyOfProcessingChange(true);
if (suggestions_.RemoveProposal(component_url, proposal_id)) {
UpdateRanking();
}
}
void NextProcessor::SetActiveFilters(
std::vector<std::unique_ptr<SuggestionActiveFilter>>&& active_filters) {
suggestions_.SetActiveFilters(std::move(active_filters));
}
void NextProcessor::SetPassiveFilters(
std::vector<std::unique_ptr<SuggestionPassiveFilter>>&& passive_filters) {
suggestions_.SetPassiveFilters(std::move(passive_filters));
}
void NextProcessor::SetRanker(std::unique_ptr<Ranker> ranker) {
suggestions_.SetRanker(std::move(ranker));
}
void NextProcessor::SetInterruptionDecisionPolicy(
std::unique_ptr<DecisionPolicy> decision_policy) {
interruptions_processor_.SetDecisionPolicy(std::move(decision_policy));
}
RankedSuggestion* NextProcessor::GetSuggestion(
const std::string& suggestion_id) const {
return suggestions_.GetSuggestion(suggestion_id);
}
void NextProcessor::UpdateRanking() {
suggestions_.Refresh();
NotifyAllOfResults();
debug_->OnNextUpdate(&suggestions_);
NotifyOfProcessingChange(false);
}
void NextProcessor::NotifyAllOfResults() {
for (const auto& it : listeners_) {
if (it.first) {
NotifyOfResults(it.first, it.second);
}
}
}
void NextProcessor::NotifyOfProcessingChange(const bool processing) {
if (processing_ != processing) {
processing_ = processing;
// Notify all listeners that the processing state has changed
for (const auto& it : listeners_) {
if (it.first) {
it.first->OnProcessingChange(processing_);
}
}
}
}
void NextProcessor::NotifyOfResults(
const fuchsia::modular::NextListenerPtr& listener,
const size_t max_results) {
const auto& suggestion_vector = suggestions_.Get();
std::vector<fuchsia::modular::Suggestion> window;
for (size_t i = 0;
window.size() < max_results && i < suggestion_vector.size(); i++) {
if (!suggestion_vector[i]->hidden) {
window.push_back(CreateSuggestion(*suggestion_vector[i]));
}
}
listener->OnNextResults(std::move(window));
}
} // namespace modular