Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(34)

Side by Side Diff: components/ntp_snippets/content_suggestions_service.cc

Issue 2406573002: 📰 Persist category dismissals (Closed)
Patch Set: address comments Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/ntp_snippets/content_suggestions_service.h" 5 #include "components/ntp_snippets/content_suggestions_service.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <iterator> 8 #include <iterator>
9 #include <set> 9 #include <set>
10 #include <utility> 10 #include <utility>
11 11
12 #include "base/bind.h" 12 #include "base/bind.h"
13 #include "base/location.h" 13 #include "base/location.h"
14 #include "base/strings/string_number_conversions.h" 14 #include "base/strings/string_number_conversions.h"
15 #include "base/threading/thread_task_runner_handle.h" 15 #include "base/threading/thread_task_runner_handle.h"
16 #include "base/values.h"
17 #include "components/ntp_snippets/pref_names.h"
18 #include "components/prefs/pref_registry_simple.h"
19 #include "components/prefs/pref_service.h"
16 #include "ui/gfx/image/image.h" 20 #include "ui/gfx/image/image.h"
17 21
18 namespace ntp_snippets { 22 namespace ntp_snippets {
19 23
20 ContentSuggestionsService::ContentSuggestionsService( 24 ContentSuggestionsService::ContentSuggestionsService(
21 State state, 25 State state,
22 history::HistoryService* history_service, 26 history::HistoryService* history_service,
23 PrefService* pref_service) 27 PrefService* pref_service)
24 : state_(state), 28 : state_(state),
25 history_service_observer_(this), 29 history_service_observer_(this),
26 ntp_snippets_service_(nullptr), 30 ntp_snippets_service_(nullptr),
31 pref_service_(pref_service),
27 user_classifier_(pref_service) { 32 user_classifier_(pref_service) {
28 // Can be null in tests. 33 // Can be null in tests.
29 if (history_service) 34 if (history_service)
30 history_service_observer_.Add(history_service); 35 history_service_observer_.Add(history_service);
36
37 RestoreDismissedCategoriesFromPrefs();
31 } 38 }
32 39
33 ContentSuggestionsService::~ContentSuggestionsService() = default; 40 ContentSuggestionsService::~ContentSuggestionsService() = default;
34 41
35 void ContentSuggestionsService::Shutdown() { 42 void ContentSuggestionsService::Shutdown() {
36 ntp_snippets_service_ = nullptr; 43 ntp_snippets_service_ = nullptr;
37 suggestions_by_category_.clear(); 44 suggestions_by_category_.clear();
38 providers_by_category_.clear(); 45 providers_by_category_.clear();
39 categories_.clear(); 46 categories_.clear();
40 providers_.clear(); 47 providers_.clear();
41 state_ = State::DISABLED; 48 state_ = State::DISABLED;
42 FOR_EACH_OBSERVER(Observer, observers_, ContentSuggestionsServiceShutdown()); 49 FOR_EACH_OBSERVER(Observer, observers_, ContentSuggestionsServiceShutdown());
43 } 50 }
44 51
52 // static
53 void ContentSuggestionsService::RegisterProfilePrefs(
54 PrefRegistrySimple* registry) {
55 registry->RegisterListPref(prefs::kDismissedCategories);
56 }
57
45 CategoryStatus ContentSuggestionsService::GetCategoryStatus( 58 CategoryStatus ContentSuggestionsService::GetCategoryStatus(
46 Category category) const { 59 Category category) const {
47 if (state_ == State::DISABLED) { 60 if (state_ == State::DISABLED) {
48 return CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED; 61 return CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED;
49 } 62 }
50 63
51 auto iterator = providers_by_category_.find(category); 64 auto iterator = providers_by_category_.find(category);
52 if (iterator == providers_by_category_.end()) 65 if (iterator == providers_by_category_.end())
53 return CategoryStatus::NOT_PROVIDED; 66 return CategoryStatus::NOT_PROVIDED;
54 67
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
143 DCHECK(removed) << "The dismissed suggestion " << suggestion_id 156 DCHECK(removed) << "The dismissed suggestion " << suggestion_id
144 << " has already been removed. Providers must not call" 157 << " has already been removed. Providers must not call"
145 << " OnNewSuggestions in response to DismissSuggestion."; 158 << " OnNewSuggestions in response to DismissSuggestion.";
146 } 159 }
147 160
148 void ContentSuggestionsService::DismissCategory(Category category) { 161 void ContentSuggestionsService::DismissCategory(Category category) {
149 auto providers_it = providers_by_category_.find(category); 162 auto providers_it = providers_by_category_.find(category);
150 if (providers_it == providers_by_category_.end()) 163 if (providers_it == providers_by_category_.end())
151 return; 164 return;
152 165
153 suggestions_by_category_[category].clear(); 166 ContentSuggestionsProvider* provider = providers_it->second;
154 dismissed_providers_by_category_[providers_it->first] = providers_it->second; 167 UnregisterCategory(category, provider);
155 providers_by_category_.erase(providers_it); 168
156 categories_.erase( 169 dismissed_providers_by_category_[category] = provider;
157 std::find(categories_.begin(), categories_.end(), category)); 170 StoreDismissedCategoriesToPrefs();
158 } 171 }
159 172
160 void ContentSuggestionsService::RestoreDismissedCategories() { 173 void ContentSuggestionsService::RestoreDismissedCategories() {
161 // Make a copy as the original will be modified during iteration. 174 // Make a copy as the original will be modified during iteration.
162 auto dismissed_providers_by_category_copy = dismissed_providers_by_category_; 175 auto dismissed_providers_by_category_copy = dismissed_providers_by_category_;
163 for (const auto& category_provider_pair : 176 for (const auto& category_provider_pair :
164 dismissed_providers_by_category_copy) { 177 dismissed_providers_by_category_copy) {
165 RegisterCategoryIfRequired(category_provider_pair.second, 178 if (category_provider_pair.second) {
166 category_provider_pair.first); 179 RestoreDismissedCategory(category_provider_pair.first);
180 } else {
181 // There is no provider for this category, we just remove the entry.
182 dismissed_providers_by_category_.erase(category_provider_pair.first);
Marc Treib 2016/10/17 08:44:46 I think RestoreDismissedCategory now handles a nul
dgn 2016/10/17 16:41:50 Done.
183 }
167 } 184 }
185 StoreDismissedCategoriesToPrefs();
168 DCHECK(dismissed_providers_by_category_.empty()); 186 DCHECK(dismissed_providers_by_category_.empty());
169 } 187 }
170 188
171 void ContentSuggestionsService::AddObserver(Observer* observer) { 189 void ContentSuggestionsService::AddObserver(Observer* observer) {
172 observers_.AddObserver(observer); 190 observers_.AddObserver(observer);
173 } 191 }
174 192
175 void ContentSuggestionsService::RemoveObserver(Observer* observer) { 193 void ContentSuggestionsService::RemoveObserver(Observer* observer) {
176 observers_.RemoveObserver(observer); 194 observers_.RemoveObserver(observer);
177 } 195 }
178 196
179 void ContentSuggestionsService::RegisterProvider( 197 void ContentSuggestionsService::RegisterProvider(
180 std::unique_ptr<ContentSuggestionsProvider> provider) { 198 std::unique_ptr<ContentSuggestionsProvider> provider) {
181 DCHECK(state_ == State::ENABLED); 199 DCHECK(state_ == State::ENABLED);
182 providers_.push_back(std::move(provider)); 200 providers_.push_back(std::move(provider));
183 } 201 }
184 202
185 //////////////////////////////////////////////////////////////////////////////// 203 ////////////////////////////////////////////////////////////////////////////////
186 // Private methods 204 // Private methods
187 205
188 void ContentSuggestionsService::OnNewSuggestions( 206 void ContentSuggestionsService::OnNewSuggestions(
189 ContentSuggestionsProvider* provider, 207 ContentSuggestionsProvider* provider,
190 Category category, 208 Category category,
191 std::vector<ContentSuggestion> suggestions) { 209 std::vector<ContentSuggestion> suggestions) {
192 if (RegisterCategoryIfRequired(provider, category)) 210 if (TryRegisterProviderForCategory(provider, category)) {
193 NotifyCategoryStatusChanged(category); 211 NotifyCategoryStatusChanged(category);
212 } else if (IsCategoryDismissed(category)) {
213 // The category has been registered as a dismissed one. We need to
214 // check if the dismissal can be cleared now that we received new data.
215 if (suggestions.empty())
216 return;
217
218 RestoreDismissedCategory(category);
219 StoreDismissedCategoriesToPrefs();
220
221 NotifyCategoryStatusChanged(category);
222 }
194 223
195 if (!IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) 224 if (!IsCategoryStatusAvailable(provider->GetCategoryStatus(category)))
196 return; 225 return;
197 226
198 suggestions_by_category_[category] = std::move(suggestions); 227 suggestions_by_category_[category] = std::move(suggestions);
199 228
200 // The positioning of the bookmarks category depends on whether it's empty. 229 // The positioning of the bookmarks category depends on whether it's empty.
201 // TODO(treib): Remove this temporary hack, crbug.com/640568. 230 // TODO(treib): Remove this temporary hack, crbug.com/640568.
202 if (category.IsKnownCategory(KnownCategories::BOOKMARKS)) 231 if (category.IsKnownCategory(KnownCategories::BOOKMARKS))
203 SortCategories(); 232 SortCategories();
204 233
205 FOR_EACH_OBSERVER(Observer, observers_, OnNewSuggestions(category)); 234 FOR_EACH_OBSERVER(Observer, observers_, OnNewSuggestions(category));
206 } 235 }
207 236
208 void ContentSuggestionsService::OnCategoryStatusChanged( 237 void ContentSuggestionsService::OnCategoryStatusChanged(
209 ContentSuggestionsProvider* provider, 238 ContentSuggestionsProvider* provider,
210 Category category, 239 Category category,
211 CategoryStatus new_status) { 240 CategoryStatus new_status) {
212 if (!IsCategoryStatusAvailable(new_status)) { 241 if (!IsCategoryStatusAvailable(new_status)) {
213 suggestions_by_category_.erase(category); 242 suggestions_by_category_.erase(category);
Marc Treib 2016/10/17 08:44:46 I think this can be removed, since there's now ano
dgn 2016/10/17 16:41:50 Done.
214 } 243 }
215 if (new_status == CategoryStatus::NOT_PROVIDED) { 244 if (new_status == CategoryStatus::NOT_PROVIDED) {
216 DCHECK(providers_by_category_.find(category) != 245 UnregisterCategory(category, provider);
217 providers_by_category_.end());
218 DCHECK_EQ(provider, providers_by_category_.find(category)->second);
219 DismissCategory(category);
220 } else { 246 } else {
221 RegisterCategoryIfRequired(provider, category); 247 if (!IsCategoryStatusAvailable(new_status))
248 suggestions_by_category_.erase(category);
249 TryRegisterProviderForCategory(provider, category);
222 DCHECK_EQ(new_status, provider->GetCategoryStatus(category)); 250 DCHECK_EQ(new_status, provider->GetCategoryStatus(category));
223 } 251 }
224 NotifyCategoryStatusChanged(category); 252
253 if (!IsCategoryDismissed(category))
254 NotifyCategoryStatusChanged(category);
225 } 255 }
226 256
227 void ContentSuggestionsService::OnSuggestionInvalidated( 257 void ContentSuggestionsService::OnSuggestionInvalidated(
228 ContentSuggestionsProvider* provider, 258 ContentSuggestionsProvider* provider,
229 const ContentSuggestion::ID& suggestion_id) { 259 const ContentSuggestion::ID& suggestion_id) {
230 RemoveSuggestionByID(suggestion_id); 260 RemoveSuggestionByID(suggestion_id);
231 FOR_EACH_OBSERVER(Observer, observers_, 261 FOR_EACH_OBSERVER(Observer, observers_,
232 OnSuggestionInvalidated(suggestion_id)); 262 OnSuggestionInvalidated(suggestion_id));
233 } 263 }
234 264
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
271 deleted_urls); 301 deleted_urls);
272 ClearHistory(begin, end, filter); 302 ClearHistory(begin, end, filter);
273 } 303 }
274 } 304 }
275 305
276 void ContentSuggestionsService::HistoryServiceBeingDeleted( 306 void ContentSuggestionsService::HistoryServiceBeingDeleted(
277 history::HistoryService* history_service) { 307 history::HistoryService* history_service) {
278 history_service_observer_.RemoveAll(); 308 history_service_observer_.RemoveAll();
279 } 309 }
280 310
281 bool ContentSuggestionsService::RegisterCategoryIfRequired( 311 bool ContentSuggestionsService::TryRegisterProviderForCategory(
282 ContentSuggestionsProvider* provider, 312 ContentSuggestionsProvider* provider,
283 Category category) { 313 Category category) {
284 auto it = providers_by_category_.find(category); 314 auto it = providers_by_category_.find(category);
285 if (it != providers_by_category_.end()) { 315 if (it != providers_by_category_.end()) {
286 DCHECK_EQ(it->second, provider); 316 DCHECK_EQ(it->second, provider);
287 return false; 317 return false;
288 } 318 }
289 319
290 auto dismissed_it = dismissed_providers_by_category_.find(category); 320 auto dismissed_it = dismissed_providers_by_category_.find(category);
291 if (dismissed_it != dismissed_providers_by_category_.end()) { 321 if (dismissed_it != dismissed_providers_by_category_.end()) {
292 DCHECK_EQ(dismissed_it->second, provider); 322 // The initialisation of dismissed categories registers them with |nullptr|
293 dismissed_providers_by_category_.erase(dismissed_it); 323 // for providers, we need to check for that to see if the provider is
324 // already registered or not.
325 if (!dismissed_it->second) {
326 dismissed_it->second = provider;
327 } else {
328 DCHECK_EQ(dismissed_it->second, provider);
329 }
330 return false;
294 } 331 }
295 332
333 RegisterCategory(category, provider);
334 return true;
335 }
336
337 void ContentSuggestionsService::RegisterCategory(
338 Category category,
339 ContentSuggestionsProvider* provider) {
340 DCHECK(!base::ContainsKey(providers_by_category_, category));
341 DCHECK(!IsCategoryDismissed(category));
342
296 providers_by_category_[category] = provider; 343 providers_by_category_[category] = provider;
297 categories_.push_back(category); 344 categories_.push_back(category);
298 SortCategories(); 345 SortCategories();
299 if (IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) { 346 if (IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) {
300 suggestions_by_category_.insert( 347 suggestions_by_category_.insert(
301 std::make_pair(category, std::vector<ContentSuggestion>())); 348 std::make_pair(category, std::vector<ContentSuggestion>()));
302 } 349 }
303 return true; 350 }
351
352 void ContentSuggestionsService::UnregisterCategory(
353 Category category,
354 ContentSuggestionsProvider* provider) {
355 auto providers_it = providers_by_category_.find(category);
356 if (providers_it == providers_by_category_.end()) {
357 DCHECK(IsCategoryDismissed(category));
358 return;
359 }
360
361 DCHECK_EQ(provider, providers_it->second);
362 providers_by_category_.erase(providers_it);
363 categories_.erase(
364 std::find(categories_.begin(), categories_.end(), category));
365 suggestions_by_category_.erase(category);
304 } 366 }
305 367
306 bool ContentSuggestionsService::RemoveSuggestionByID( 368 bool ContentSuggestionsService::RemoveSuggestionByID(
307 const ContentSuggestion::ID& suggestion_id) { 369 const ContentSuggestion::ID& suggestion_id) {
308 std::vector<ContentSuggestion>* suggestions = 370 std::vector<ContentSuggestion>* suggestions =
309 &suggestions_by_category_[suggestion_id.category()]; 371 &suggestions_by_category_[suggestion_id.category()];
310 auto position = 372 auto position =
311 std::find_if(suggestions->begin(), suggestions->end(), 373 std::find_if(suggestions->begin(), suggestions->end(),
312 [&suggestion_id](const ContentSuggestion& suggestion) { 374 [&suggestion_id](const ContentSuggestion& suggestion) {
313 return suggestion_id == suggestion.id(); 375 return suggestion_id == suggestion.id();
(...skipping 29 matching lines...) Expand all
343 if (bookmarks_empty) { 405 if (bookmarks_empty) {
344 if (left.IsKnownCategory(KnownCategories::BOOKMARKS)) 406 if (left.IsKnownCategory(KnownCategories::BOOKMARKS))
345 return false; 407 return false;
346 if (right.IsKnownCategory(KnownCategories::BOOKMARKS)) 408 if (right.IsKnownCategory(KnownCategories::BOOKMARKS))
347 return true; 409 return true;
348 } 410 }
349 return category_factory_.CompareCategories(left, right); 411 return category_factory_.CompareCategories(left, right);
350 }); 412 });
351 } 413 }
352 414
415 bool ContentSuggestionsService::IsCategoryDismissed(Category category) const {
416 return base::ContainsKey(dismissed_providers_by_category_, category);
417 }
418
419 void ContentSuggestionsService::RestoreDismissedCategory(Category category) {
420 auto dismissed_it = dismissed_providers_by_category_.find(category);
421 DCHECK(dismissed_it != dismissed_providers_by_category_.end());
Marc Treib 2016/10/17 08:44:46 DCHECK_NE? Or alternatively, DCHECK(base::Contains
dgn 2016/10/17 16:41:50 Done.
422
423 // Keep the reference to the provider and remove it from the dismissed ones,
424 // because the category registration enforces that it's not dismissed.
425 ContentSuggestionsProvider* provider = dismissed_it->second;
426 dismissed_providers_by_category_.erase(dismissed_it);
427
428 if (provider)
429 RegisterCategory(category, provider);
430 }
431
432 void ContentSuggestionsService::RestoreDismissedCategoriesFromPrefs() {
433 // This must only be called at startup.
434 DCHECK(dismissed_providers_by_category_.empty());
435 DCHECK(providers_by_category_.empty());
436
437 const base::ListValue* list =
438 pref_service_->GetList(prefs::kDismissedCategories);
439 for (const std::unique_ptr<base::Value>& entry : *list) {
440 int id = 0;
441 if (!entry->GetAsInteger(&id)) {
442 DLOG(WARNING) << "Invalid category pref value: " << *entry;
443 continue;
444 }
445
446 // When the provider is registered, it will be stored in this map.
447 dismissed_providers_by_category_[category_factory()->FromIDValue(id)] =
448 nullptr;
449 }
450 }
451
452 void ContentSuggestionsService::StoreDismissedCategoriesToPrefs() {
453 base::ListValue list;
454 for (const auto& category_provider_pair : dismissed_providers_by_category_) {
455 list.AppendInteger(category_provider_pair.first.id());
456 }
457
458 pref_service_->Set(prefs::kDismissedCategories, list);
459 }
460
353 } // namespace ntp_snippets 461 } // namespace ntp_snippets
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698