blob: 934928607776f56543562bc3d330f4c9054d0a5c [file] [log] [blame]
pke6dbb90af2016-07-08 14:00:461// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/ntp_snippets/content_suggestions_service.h"
6
7#include <algorithm>
8#include <iterator>
9
10#include "base/bind.h"
11#include "base/strings/string_number_conversions.h"
12#include "ui/gfx/image/image.h"
13
14namespace ntp_snippets {
15
pke31b07942016-08-01 11:35:0216bool ContentSuggestionsService::CompareCategoriesByID::operator()(
pke9c5095ac2016-08-01 13:53:1217 const Category& left,
18 const Category& right) const {
pke31b07942016-08-01 11:35:0219 return left.id() < right.id();
20}
21
pke6dbb90af2016-07-08 14:00:4622ContentSuggestionsService::ContentSuggestionsService(State state)
23 : state_(state) {}
24
25ContentSuggestionsService::~ContentSuggestionsService() {}
26
27void ContentSuggestionsService::Shutdown() {
28 DCHECK(providers_.empty());
29 DCHECK(categories_.empty());
30 DCHECK(suggestions_by_category_.empty());
31 DCHECK(id_category_map_.empty());
32 state_ = State::DISABLED;
33 FOR_EACH_OBSERVER(Observer, observers_, ContentSuggestionsServiceShutdown());
34}
35
pke9c5095ac2016-08-01 13:53:1236CategoryStatus ContentSuggestionsService::GetCategoryStatus(
37 Category category) const {
pke6dbb90af2016-07-08 14:00:4638 if (state_ == State::DISABLED) {
pke9c5095ac2016-08-01 13:53:1239 return CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED;
pke6dbb90af2016-07-08 14:00:4640 }
41
42 auto iterator = providers_.find(category);
43 if (iterator == providers_.end())
pke9c5095ac2016-08-01 13:53:1244 return CategoryStatus::NOT_PROVIDED;
pke6dbb90af2016-07-08 14:00:4645
46 return iterator->second->GetCategoryStatus(category);
47}
48
49const std::vector<ContentSuggestion>&
pke9c5095ac2016-08-01 13:53:1250ContentSuggestionsService::GetSuggestionsForCategory(Category category) const {
pke6dbb90af2016-07-08 14:00:4651 auto iterator = suggestions_by_category_.find(category);
52 if (iterator == suggestions_by_category_.end())
53 return no_suggestions_;
54 return iterator->second;
55}
56
57void ContentSuggestionsService::FetchSuggestionImage(
58 const std::string& suggestion_id,
59 const ImageFetchedCallback& callback) {
60 if (!id_category_map_.count(suggestion_id)) {
61 LOG(WARNING) << "Requested image for unknown suggestion " << suggestion_id;
62 callback.Run(suggestion_id, gfx::Image());
63 return;
64 }
pke9c5095ac2016-08-01 13:53:1265 Category category = id_category_map_.at(suggestion_id);
pke6dbb90af2016-07-08 14:00:4666 if (!providers_.count(category)) {
67 LOG(WARNING) << "Requested image for suggestion " << suggestion_id
pke31b07942016-08-01 11:35:0268 << " for unavailable category " << category;
pke6dbb90af2016-07-08 14:00:4669 callback.Run(suggestion_id, gfx::Image());
70 return;
71 }
72 providers_[category]->FetchSuggestionImage(suggestion_id, callback);
73}
74
75void ContentSuggestionsService::ClearCachedSuggestionsForDebugging() {
76 suggestions_by_category_.clear();
77 id_category_map_.clear();
78 for (auto& category_provider_pair : providers_) {
79 category_provider_pair.second->ClearCachedSuggestionsForDebugging();
80 }
81 FOR_EACH_OBSERVER(Observer, observers_, OnNewSuggestions());
82}
83
pke2646c95b2016-07-25 12:18:4484void ContentSuggestionsService::ClearDismissedSuggestionsForDebugging() {
pke6dbb90af2016-07-08 14:00:4685 for (auto& category_provider_pair : providers_) {
pke2646c95b2016-07-25 12:18:4486 category_provider_pair.second->ClearDismissedSuggestionsForDebugging();
pke6dbb90af2016-07-08 14:00:4687 }
88}
89
pke2646c95b2016-07-25 12:18:4490void ContentSuggestionsService::DismissSuggestion(
pke6dbb90af2016-07-08 14:00:4691 const std::string& suggestion_id) {
92 if (!id_category_map_.count(suggestion_id)) {
pke2646c95b2016-07-25 12:18:4493 LOG(WARNING) << "Dismissed unknown suggestion " << suggestion_id;
pke6dbb90af2016-07-08 14:00:4694 return;
95 }
pke9c5095ac2016-08-01 13:53:1296 Category category = id_category_map_.at(suggestion_id);
pke6dbb90af2016-07-08 14:00:4697 if (!providers_.count(category)) {
pke2646c95b2016-07-25 12:18:4498 LOG(WARNING) << "Dismissed suggestion " << suggestion_id
pke31b07942016-08-01 11:35:0299 << " for unavailable category " << category;
pke6dbb90af2016-07-08 14:00:46100 return;
101 }
pke2646c95b2016-07-25 12:18:44102 providers_[category]->DismissSuggestion(suggestion_id);
pke6dbb90af2016-07-08 14:00:46103
104 // Remove the suggestion locally.
105 id_category_map_.erase(suggestion_id);
106 std::vector<ContentSuggestion>* suggestions =
107 &suggestions_by_category_[category];
108 auto position =
109 std::find_if(suggestions->begin(), suggestions->end(),
110 [&suggestion_id](const ContentSuggestion& suggestion) {
111 return suggestion_id == suggestion.id();
112 });
pke27632ab2016-07-25 10:20:25113 DCHECK(position != suggestions->end())
pke2646c95b2016-07-25 12:18:44114 << "The dismissed suggestion " << suggestion_id
pke27632ab2016-07-25 10:20:25115 << " has already been removed. Providers must not call OnNewSuggestions"
pke2646c95b2016-07-25 12:18:44116 " in response to DismissSuggestion.";
pke6dbb90af2016-07-08 14:00:46117 suggestions->erase(position);
118}
119
120void ContentSuggestionsService::AddObserver(Observer* observer) {
121 observers_.AddObserver(observer);
122}
123
124void ContentSuggestionsService::RemoveObserver(Observer* observer) {
125 observers_.RemoveObserver(observer);
126}
127
128void ContentSuggestionsService::RegisterProvider(
129 ContentSuggestionsProvider* provider) {
130 // TODO(pke): When NTPSnippetsService is purely a provider, think about
131 // removing this state check.
132 if (state_ == State::DISABLED)
133 return;
134
pke9c5095ac2016-08-01 13:53:12135 for (Category category : provider->GetProvidedCategories()) {
pke6dbb90af2016-07-08 14:00:46136 DCHECK_EQ(0ul, providers_.count(category));
137 providers_[category] = provider;
pke6dbb90af2016-07-08 14:00:46138 categories_.push_back(category);
pke9c5095ac2016-08-01 13:53:12139 if (IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) {
pke6dbb90af2016-07-08 14:00:46140 suggestions_by_category_[category] = std::vector<ContentSuggestion>();
141 }
142 NotifyCategoryStatusChanged(category);
143 }
pke31b07942016-08-01 11:35:02144 std::sort(categories_.begin(), categories_.end(),
pke9c5095ac2016-08-01 13:53:12145 [this](const Category& left, const Category& right) {
pke31b07942016-08-01 11:35:02146 return category_factory_.CompareCategories(left, right);
147 });
pke6dbb90af2016-07-08 14:00:46148 provider->SetObserver(this);
149}
150
151////////////////////////////////////////////////////////////////////////////////
152// Private methods
153
154void ContentSuggestionsService::OnNewSuggestions(
pke9c5095ac2016-08-01 13:53:12155 Category changed_category,
pke6dbb90af2016-07-08 14:00:46156 std::vector<ContentSuggestion> new_suggestions) {
157 DCHECK(IsCategoryRegistered(changed_category));
158
159 for (const ContentSuggestion& suggestion :
160 suggestions_by_category_[changed_category]) {
161 id_category_map_.erase(suggestion.id());
162 }
163
164 for (const ContentSuggestion& suggestion : new_suggestions) {
pke31b07942016-08-01 11:35:02165 id_category_map_.insert(std::make_pair(suggestion.id(), changed_category));
pke6dbb90af2016-07-08 14:00:46166 }
167
168 suggestions_by_category_[changed_category] = std::move(new_suggestions);
169
170 FOR_EACH_OBSERVER(Observer, observers_, OnNewSuggestions());
171}
172
173void ContentSuggestionsService::OnCategoryStatusChanged(
pke9c5095ac2016-08-01 13:53:12174 Category changed_category,
175 CategoryStatus new_status) {
176 if (!IsCategoryStatusAvailable(new_status)) {
pke6dbb90af2016-07-08 14:00:46177 for (const ContentSuggestion& suggestion :
178 suggestions_by_category_[changed_category]) {
179 id_category_map_.erase(suggestion.id());
180 }
181 suggestions_by_category_.erase(changed_category);
182 }
183 NotifyCategoryStatusChanged(changed_category);
184}
185
186void ContentSuggestionsService::OnProviderShutdown(
187 ContentSuggestionsProvider* provider) {
pke9c5095ac2016-08-01 13:53:12188 for (Category category : provider->GetProvidedCategories()) {
pke6dbb90af2016-07-08 14:00:46189 auto iterator = std::find(categories_.begin(), categories_.end(), category);
190 DCHECK(iterator != categories_.end());
191 categories_.erase(iterator);
192 for (const ContentSuggestion& suggestion :
193 suggestions_by_category_[category]) {
194 id_category_map_.erase(suggestion.id());
195 }
196 suggestions_by_category_.erase(category);
197 providers_.erase(category);
198 NotifyCategoryStatusChanged(category);
199 }
200}
201
pke9c5095ac2016-08-01 13:53:12202bool ContentSuggestionsService::IsCategoryRegistered(Category category) const {
pke6dbb90af2016-07-08 14:00:46203 return std::find(categories_.begin(), categories_.end(), category) !=
204 categories_.end();
205}
206
pke9c5095ac2016-08-01 13:53:12207void ContentSuggestionsService::NotifyCategoryStatusChanged(Category category) {
pke6dbb90af2016-07-08 14:00:46208 FOR_EACH_OBSERVER(
209 Observer, observers_,
210 OnCategoryStatusChanged(category, GetCategoryStatus(category)));
211}
212
213} // namespace ntp_snippets