| // 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 |