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

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

Issue 2255783002: Support server categories in NTPSnippetsService. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix after rebase Created 4 years, 3 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 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 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/ntp_snippets_service.h" 5 #include "components/ntp_snippets/ntp_snippets_service.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <iterator> 8 #include <iterator>
9 #include <utility> 9 #include <utility>
10 10
(...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after
195 SuggestionsService* suggestions_service, 195 SuggestionsService* suggestions_service,
196 const std::string& application_language_code, 196 const std::string& application_language_code,
197 NTPSnippetsScheduler* scheduler, 197 NTPSnippetsScheduler* scheduler,
198 std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher, 198 std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher,
199 std::unique_ptr<ImageFetcher> image_fetcher, 199 std::unique_ptr<ImageFetcher> image_fetcher,
200 std::unique_ptr<ImageDecoder> image_decoder, 200 std::unique_ptr<ImageDecoder> image_decoder,
201 std::unique_ptr<NTPSnippetsDatabase> database, 201 std::unique_ptr<NTPSnippetsDatabase> database,
202 std::unique_ptr<NTPSnippetsStatusService> status_service) 202 std::unique_ptr<NTPSnippetsStatusService> status_service)
203 : ContentSuggestionsProvider(observer, category_factory), 203 : ContentSuggestionsProvider(observer, category_factory),
204 state_(State::NOT_INITED), 204 state_(State::NOT_INITED),
205 category_status_(CategoryStatus::INITIALIZING),
206 pref_service_(pref_service), 205 pref_service_(pref_service),
207 suggestions_service_(suggestions_service), 206 suggestions_service_(suggestions_service),
207 articles_category_(
208 category_factory->FromKnownCategory(KnownCategories::ARTICLES)),
208 application_language_code_(application_language_code), 209 application_language_code_(application_language_code),
209 scheduler_(scheduler), 210 scheduler_(scheduler),
210 history_service_observer_(this), 211 history_service_observer_(this),
211 snippets_fetcher_(std::move(snippets_fetcher)), 212 snippets_fetcher_(std::move(snippets_fetcher)),
212 image_fetcher_(std::move(image_fetcher)), 213 image_fetcher_(std::move(image_fetcher)),
213 image_decoder_(std::move(image_decoder)), 214 image_decoder_(std::move(image_decoder)),
214 database_(std::move(database)), 215 database_(std::move(database)),
215 snippets_status_service_(std::move(status_service)), 216 snippets_status_service_(std::move(status_service)),
216 fetch_after_load_(false), 217 fetch_after_load_(false),
217 nuke_after_load_(false), 218 nuke_after_load_(false),
218 provided_category_(
219 category_factory->FromKnownCategory(KnownCategories::ARTICLES)),
220 thumbnail_requests_throttler_( 219 thumbnail_requests_throttler_(
221 pref_service, 220 pref_service,
222 RequestThrottler::RequestType::CONTENT_SUGGESTION_THUMBNAIL) { 221 RequestThrottler::RequestType::CONTENT_SUGGESTION_THUMBNAIL) {
223 observer->OnCategoryStatusChanged(this, provided_category_, category_status_); 222 // Articles category always exists; others will be added as needed.
223 categories_[articles_category_] = CategoryContent();
224 observer->OnCategoryStatusChanged(this, articles_category_,
225 categories_[articles_category_].status);
224 if (database_->IsErrorState()) { 226 if (database_->IsErrorState()) {
225 EnterState(State::ERROR_OCCURRED, CategoryStatus::LOADING_ERROR); 227 EnterState(State::ERROR_OCCURRED);
228 UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR);
226 return; 229 return;
227 } 230 }
228 231
229 // Can be null in tests. 232 // Can be null in tests.
230 if (history_service) 233 if (history_service)
231 history_service_observer_.Add(history_service); 234 history_service_observer_.Add(history_service);
232 235
233 database_->SetErrorCallback(base::Bind(&NTPSnippetsService::OnDatabaseError, 236 database_->SetErrorCallback(base::Bind(&NTPSnippetsService::OnDatabaseError,
234 base::Unretained(this))); 237 base::Unretained(this)));
235 238
(...skipping 19 matching lines...) Expand all
255 else 258 else
256 fetch_after_load_ = true; 259 fetch_after_load_ = true;
257 } 260 }
258 261
259 void NTPSnippetsService::FetchSnippetsFromHosts( 262 void NTPSnippetsService::FetchSnippetsFromHosts(
260 const std::set<std::string>& hosts, 263 const std::set<std::string>& hosts,
261 bool interactive_request) { 264 bool interactive_request) {
262 if (!ready()) 265 if (!ready())
263 return; 266 return;
264 267
265 if (snippets_.empty()) 268 // Empty categories are marked as loading; others are unchanged.
266 UpdateCategoryStatus(CategoryStatus::AVAILABLE_LOADING); 269 for (const auto& item : categories_) {
270 Category category = item.first;
271 const CategoryContent& content = item.second;
272 if (content.snippets.empty())
273 UpdateCategoryStatus(category, CategoryStatus::AVAILABLE_LOADING);
274 }
267 275
268 std::set<std::string> excluded_ids; 276 std::set<std::string> excluded_ids;
269 for (const auto& snippet : dismissed_snippets_) { 277 for (const auto& item : categories_) {
270 excluded_ids.insert(snippet->id()); 278 const CategoryContent& content = item.second;
279 for (const auto& snippet : content.dismissed)
280 excluded_ids.insert(snippet->id());
271 } 281 }
272 snippets_fetcher_->FetchSnippetsFromHosts(hosts, application_language_code_, 282 snippets_fetcher_->FetchSnippetsFromHosts(hosts, application_language_code_,
273 excluded_ids, kMaxSnippetCount, 283 excluded_ids, kMaxSnippetCount,
274 interactive_request); 284 interactive_request);
275 } 285 }
276 286
277 void NTPSnippetsService::RescheduleFetching() { 287 void NTPSnippetsService::RescheduleFetching() {
278 // The scheduler only exists on Android so far, it's null on other platforms. 288 // The scheduler only exists on Android so far, it's null on other platforms.
279 if (!scheduler_) 289 if (!scheduler_)
280 return; 290 return;
281 291
282 if (ready()) { 292 if (ready()) {
283 base::Time now = base::Time::Now(); 293 base::Time now = base::Time::Now();
284 scheduler_->Schedule( 294 scheduler_->Schedule(
285 GetFetchingIntervalWifiCharging(), GetFetchingIntervalWifi(now), 295 GetFetchingIntervalWifiCharging(), GetFetchingIntervalWifi(now),
286 GetFetchingIntervalFallback(), GetRescheduleTime(now)); 296 GetFetchingIntervalFallback(), GetRescheduleTime(now));
287 } else { 297 } else {
288 scheduler_->Unschedule(); 298 scheduler_->Unschedule();
289 } 299 }
290 } 300 }
291 301
292 CategoryStatus NTPSnippetsService::GetCategoryStatus(Category category) { 302 CategoryStatus NTPSnippetsService::GetCategoryStatus(Category category) {
293 DCHECK(category.IsKnownCategory(KnownCategories::ARTICLES)); 303 DCHECK(categories_.find(category) != categories_.end());
294 return category_status_; 304 return categories_[category].status;
295 } 305 }
296 306
297 CategoryInfo NTPSnippetsService::GetCategoryInfo(Category category) { 307 CategoryInfo NTPSnippetsService::GetCategoryInfo(Category category) {
308 DCHECK(categories_.find(category) != categories_.end());
309 // TODO(sfiera): pass back titles for server categories.
298 return CategoryInfo( 310 return CategoryInfo(
299 l10n_util::GetStringUTF16(IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER), 311 l10n_util::GetStringUTF16(IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER),
300 ContentSuggestionsCardLayout::FULL_CARD, 312 ContentSuggestionsCardLayout::FULL_CARD,
301 /* has_more_button */ false, 313 /* has_more_button */ false,
302 /* show_if_empty */ true); 314 /* show_if_empty */ true);
303 } 315 }
304 316
305 void NTPSnippetsService::DismissSuggestion(const std::string& suggestion_id) { 317 void NTPSnippetsService::DismissSuggestion(const std::string& suggestion_id) {
306 if (!ready()) 318 if (!ready())
307 return; 319 return;
308 320
321 Category category = GetCategoryFromUniqueID(suggestion_id);
309 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); 322 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id);
310 323
324 DCHECK(categories_.find(category) != categories_.end());
325
326 CategoryContent* content = &categories_[category];
311 auto it = 327 auto it =
312 std::find_if(snippets_.begin(), snippets_.end(), 328 std::find_if(content->snippets.begin(), content->snippets.end(),
313 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { 329 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) {
314 return snippet->id() == snippet_id; 330 return snippet->id() == snippet_id;
315 }); 331 });
316 if (it == snippets_.end()) 332 if (it == content->snippets.end())
317 return; 333 return;
318 334
319 (*it)->set_dismissed(true); 335 (*it)->set_dismissed(true);
320 336
321 database_->SaveSnippet(**it); 337 database_->SaveSnippet(**it);
322 database_->DeleteImage((*it)->id()); 338 database_->DeleteImage(snippet_id);
323 339
324 dismissed_snippets_.push_back(std::move(*it)); 340 content->dismissed.push_back(std::move(*it));
325 snippets_.erase(it); 341 content->snippets.erase(it);
326 } 342 }
327 343
328 void NTPSnippetsService::FetchSuggestionImage( 344 void NTPSnippetsService::FetchSuggestionImage(
329 const std::string& suggestion_id, 345 const std::string& suggestion_id,
330 const ImageFetchedCallback& callback) { 346 const ImageFetchedCallback& callback) {
331 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); 347 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id);
332 database_->LoadImage( 348 database_->LoadImage(
333 snippet_id, 349 snippet_id,
334 base::Bind(&NTPSnippetsService::OnSnippetImageFetchedFromDatabase, 350 base::Bind(&NTPSnippetsService::OnSnippetImageFetchedFromDatabase,
335 base::Unretained(this), callback, snippet_id)); 351 base::Unretained(this), callback, suggestion_id));
336 } 352 }
337 353
338 void NTPSnippetsService::ClearCachedSuggestions(Category category) { 354 void NTPSnippetsService::ClearCachedSuggestions(Category category) {
339 DCHECK_EQ(category, provided_category_);
340 if (!initialized()) 355 if (!initialized())
341 return; 356 return;
342 357
343 if (snippets_.empty()) 358 if (categories_.find(category) == categories_.end())
359 return;
360 CategoryContent* content = &categories_[category];
361 if (content->snippets.empty())
344 return; 362 return;
345 363
346 database_->DeleteSnippets(snippets_); 364 if (category == articles_category_)
347 snippets_.clear(); 365 database_->DeleteSnippets(content->snippets);
366 content->snippets.clear();
348 367
349 NotifyNewSuggestions(); 368 NotifyNewSuggestions();
350 } 369 }
351 370
352 void NTPSnippetsService::GetDismissedSuggestionsForDebugging( 371 void NTPSnippetsService::GetDismissedSuggestionsForDebugging(
353 Category category, 372 Category category,
354 const DismissedSuggestionsCallback& callback) { 373 const DismissedSuggestionsCallback& callback) {
355 DCHECK_EQ(category, provided_category_); 374 DCHECK(categories_.find(category) != categories_.end());
375
356 std::vector<ContentSuggestion> result; 376 std::vector<ContentSuggestion> result;
357 for (const std::unique_ptr<NTPSnippet>& snippet : dismissed_snippets_) { 377 const CategoryContent& content = categories_[category];
378 for (const std::unique_ptr<NTPSnippet>& snippet : content.dismissed) {
358 if (!snippet->is_complete()) 379 if (!snippet->is_complete())
359 continue; 380 continue;
360 ContentSuggestion suggestion( 381 ContentSuggestion suggestion(MakeUniqueID(category, snippet->id()),
361 MakeUniqueID(provided_category_, snippet->id()), 382 snippet->best_source().url);
362 snippet->best_source().url);
363 suggestion.set_amp_url(snippet->best_source().amp_url); 383 suggestion.set_amp_url(snippet->best_source().amp_url);
364 suggestion.set_title(base::UTF8ToUTF16(snippet->title())); 384 suggestion.set_title(base::UTF8ToUTF16(snippet->title()));
365 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet())); 385 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet()));
366 suggestion.set_publish_date(snippet->publish_date()); 386 suggestion.set_publish_date(snippet->publish_date());
367 suggestion.set_publisher_name( 387 suggestion.set_publisher_name(
368 base::UTF8ToUTF16(snippet->best_source().publisher_name)); 388 base::UTF8ToUTF16(snippet->best_source().publisher_name));
369 suggestion.set_score(snippet->score()); 389 suggestion.set_score(snippet->score());
370 result.emplace_back(std::move(suggestion)); 390 result.emplace_back(std::move(suggestion));
371 } 391 }
372 callback.Run(std::move(result)); 392 callback.Run(std::move(result));
373 } 393 }
374 394
375 void NTPSnippetsService::ClearDismissedSuggestionsForDebugging( 395 void NTPSnippetsService::ClearDismissedSuggestionsForDebugging(
376 Category category) { 396 Category category) {
377 DCHECK_EQ(category, provided_category_); 397 DCHECK(categories_.find(category) != categories_.end());
398
378 if (!initialized()) 399 if (!initialized())
379 return; 400 return;
380 401
381 if (dismissed_snippets_.empty()) 402 CategoryContent* content = &categories_[category];
403 if (content->dismissed.empty())
382 return; 404 return;
383 405
384 database_->DeleteSnippets(dismissed_snippets_); 406 if (category == articles_category_)
385 dismissed_snippets_.clear(); 407 database_->DeleteSnippets(content->dismissed);
408 content->dismissed.clear();
386 } 409 }
387 410
388 std::set<std::string> NTPSnippetsService::GetSuggestionsHosts() const { 411 std::set<std::string> NTPSnippetsService::GetSuggestionsHosts() const {
389 // |suggestions_service_| can be null in tests. 412 // |suggestions_service_| can be null in tests.
390 if (!suggestions_service_) 413 if (!suggestions_service_)
391 return std::set<std::string>(); 414 return std::set<std::string>();
392 415
393 // TODO(treib): This should just call GetSnippetHostsFromPrefs. 416 // TODO(treib): This should just call GetSnippetHostsFromPrefs.
394 return GetSuggestionsHostsImpl( 417 return GetSuggestionsHostsImpl(
395 suggestions_service_->GetSuggestionsDataFromCache()); 418 suggestions_service_->GetSuggestionsDataFromCache());
(...skipping 23 matching lines...) Expand all
419 else 442 else
420 NukeAllSnippets(); 443 NukeAllSnippets();
421 } 444 }
422 445
423 void NTPSnippetsService::HistoryServiceBeingDeleted( 446 void NTPSnippetsService::HistoryServiceBeingDeleted(
424 history::HistoryService* history_service) { 447 history::HistoryService* history_service) {
425 history_service_observer_.RemoveAll(); 448 history_service_observer_.RemoveAll();
426 } 449 }
427 450
428 // image_fetcher::ImageFetcherDelegate implementation. 451 // image_fetcher::ImageFetcherDelegate implementation.
429 void NTPSnippetsService::OnImageDataFetched(const std::string& snippet_id, 452 void NTPSnippetsService::OnImageDataFetched(const std::string& suggestion_id,
430 const std::string& image_data) { 453 const std::string& image_data) {
431 if (image_data.empty()) 454 if (image_data.empty())
432 return; 455 return;
433 456
457 Category category = GetCategoryFromUniqueID(suggestion_id);
458 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id);
459
460 auto category_it = categories_.find(category);
461 if (category_it == categories_.end())
462 return;
463
464 const CategoryContent& content = category_it->second;
465
434 // Only save the image if the corresponding snippet still exists. 466 // Only save the image if the corresponding snippet still exists.
435 auto it = 467 auto it =
436 std::find_if(snippets_.begin(), snippets_.end(), 468 std::find_if(content.snippets.begin(), content.snippets.end(),
437 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { 469 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) {
438 return snippet->id() == snippet_id; 470 return snippet->id() == snippet_id;
439 }); 471 });
440 if (it == snippets_.end()) 472 if (it == content.snippets.end())
441 return; 473 return;
442 474
443 database_->SaveImage(snippet_id, image_data); 475 database_->SaveImage(snippet_id, image_data);
444 } 476 }
445 477
446 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { 478 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) {
447 if (state_ == State::ERROR_OCCURRED) 479 if (state_ == State::ERROR_OCCURRED)
448 return; 480 return;
449 DCHECK(state_ == State::NOT_INITED); 481 DCHECK(state_ == State::NOT_INITED);
450 DCHECK(snippets_.empty()); 482 DCHECK(categories_.size() == 1); // Only articles category, so far.
451 DCHECK(dismissed_snippets_.empty()); 483 DCHECK(categories_.find(articles_category_) != categories_.end());
484
485 // TODO(sfiera): support non-article categories in database.
486 CategoryContent* content = &categories_[articles_category_];
452 for (std::unique_ptr<NTPSnippet>& snippet : snippets) { 487 for (std::unique_ptr<NTPSnippet>& snippet : snippets) {
453 if (snippet->is_dismissed()) 488 if (snippet->is_dismissed())
454 dismissed_snippets_.emplace_back(std::move(snippet)); 489 content->dismissed.emplace_back(std::move(snippet));
455 else 490 else
456 snippets_.emplace_back(std::move(snippet)); 491 content->snippets.emplace_back(std::move(snippet));
457 } 492 }
458 std::sort(snippets_.begin(), snippets_.end(), 493
494 std::sort(content->snippets.begin(), content->snippets.end(),
459 [](const std::unique_ptr<NTPSnippet>& lhs, 495 [](const std::unique_ptr<NTPSnippet>& lhs,
460 const std::unique_ptr<NTPSnippet>& rhs) { 496 const std::unique_ptr<NTPSnippet>& rhs) {
461 return lhs->score() > rhs->score(); 497 return lhs->score() > rhs->score();
462 }); 498 });
463 499
464 ClearExpiredSnippets(); 500 ClearExpiredSnippets();
465 FinishInitialization(); 501 FinishInitialization();
466 } 502 }
467 503
468 void NTPSnippetsService::OnDatabaseError() { 504 void NTPSnippetsService::OnDatabaseError() {
469 EnterState(State::ERROR_OCCURRED, CategoryStatus::LOADING_ERROR); 505 EnterState(State::ERROR_OCCURRED);
506 UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR);
470 } 507 }
471 508
472 // TODO(dgn): name clash between content suggestions and suggestions hosts. 509 // TODO(dgn): name clash between content suggestions and suggestions hosts.
473 // method name should be changed. 510 // method name should be changed.
474 void NTPSnippetsService::OnSuggestionsChanged( 511 void NTPSnippetsService::OnSuggestionsChanged(
475 const SuggestionsProfile& suggestions) { 512 const SuggestionsProfile& suggestions) {
476 DCHECK(initialized()); 513 DCHECK(initialized());
477 514
478 std::set<std::string> hosts = GetSuggestionsHostsImpl(suggestions); 515 std::set<std::string> hosts = GetSuggestionsHostsImpl(suggestions);
479 if (hosts == GetSnippetHostsFromPrefs()) 516 if (hosts == GetSnippetHostsFromPrefs())
480 return; 517 return;
481 518
482 // Remove existing snippets that aren't in the suggestions anymore. 519 // Remove existing snippets that aren't in the suggestions anymore.
520 //
483 // TODO(treib,maybelle): If there is another source with an allowed host, 521 // TODO(treib,maybelle): If there is another source with an allowed host,
484 // then we should fall back to that. 522 // then we should fall back to that.
523 //
524 // TODO(sfiera): determine when non-article categories should restrict hosts,
525 // and apply the same logic to them here. Maybe never?
526 //
485 // First, move them over into |to_delete|. 527 // First, move them over into |to_delete|.
528 CategoryContent* content = &categories_[articles_category_];
486 NTPSnippet::PtrVector to_delete; 529 NTPSnippet::PtrVector to_delete;
487 for (std::unique_ptr<NTPSnippet>& snippet : snippets_) { 530 for (std::unique_ptr<NTPSnippet>& snippet : content->snippets) {
488 if (!hosts.count(snippet->best_source().url.host())) 531 if (!hosts.count(snippet->best_source().url.host()))
489 to_delete.emplace_back(std::move(snippet)); 532 to_delete.emplace_back(std::move(snippet));
490 } 533 }
491 Compact(&snippets_); 534 Compact(&content->snippets);
492 // Then delete the removed snippets from the database. 535 // Then delete the removed snippets from the database.
493 database_->DeleteSnippets(to_delete); 536 database_->DeleteSnippets(to_delete);
494 537
495 StoreSnippetHostsToPrefs(hosts); 538 StoreSnippetHostsToPrefs(hosts);
496 539
497 // We removed some suggestions, so we want to let the client know about that. 540 // We removed some suggestions, so we want to let the client know about that.
498 // The fetch might take a long time or not complete so we don't want to wait 541 // The fetch might take a long time or not complete so we don't want to wait
499 // for its callback. 542 // for its callback.
500 NotifyNewSuggestions(); 543 NotifyNewSuggestions();
501 544
502 FetchSnippetsFromHosts(hosts, /*force_request=*/false); 545 FetchSnippetsFromHosts(hosts, /*force_request=*/false);
503 } 546 }
504 547
505 void NTPSnippetsService::OnFetchFinished( 548 void NTPSnippetsService::OnFetchFinished(
506 NTPSnippetsFetcher::OptionalSnippets snippets) { 549 NTPSnippetsFetcher::OptionalSnippets snippets) {
507 if (!ready()) 550 if (!ready())
508 return; 551 return;
509 552
510 DCHECK(category_status_ == CategoryStatus::AVAILABLE || 553 for (auto& item : categories_) {
511 category_status_ == CategoryStatus::AVAILABLE_LOADING); 554 CategoryContent* content = &item.second;
512 555 content->provided_by_server = false;
513 // TODO(sfiera): support more than just the provided_category_ ARTICLES.
514 if (snippets && (snippets->find(provided_category_) != snippets->end())) {
515 // Sparse histogram used because the number of snippets is small (bound by
516 // kMaxSnippetCount).
517 DCHECK_LE(snippets->size(), static_cast<size_t>(kMaxSnippetCount));
518 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticlesFetched",
519 (*snippets)[provided_category_].size());
520 MergeSnippets(std::move((*snippets)[provided_category_]));
521 } 556 }
522 557
558 // If snippets were fetched successfully, update our |categories_| from each
559 // category provided by the server.
560 if (snippets) {
561 for (std::pair<const Category, NTPSnippet::PtrVector>& item : *snippets) {
562 Category category = item.first;
563 NTPSnippet::PtrVector& new_snippets = item.second;
564
565 DCHECK_LE(snippets->size(), static_cast<size_t>(kMaxSnippetCount));
566 // TODO(sfiera): histograms for server categories.
567 // Sparse histogram used because the number of snippets is small (bound by
568 // kMaxSnippetCount).
569 if (category == articles_category_) {
570 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticlesFetched",
571 new_snippets.size());
572 }
573
574 MergeSnippets(category, std::move(new_snippets));
575
576 // If there are more snippets than we want to show, delete the extra ones.
577 CategoryContent* content = &categories_[category];
578 content->provided_by_server = true;
579 if (content->snippets.size() > kMaxSnippetCount) {
580 NTPSnippet::PtrVector to_delete(
581 std::make_move_iterator(content->snippets.begin() +
582 kMaxSnippetCount),
583 std::make_move_iterator(content->snippets.end()));
584 content->snippets.resize(kMaxSnippetCount);
585 if (category == articles_category_)
586 database_->DeleteSnippets(to_delete);
587 }
588 }
589 }
590
591 // Trigger expiration. This probably won't expire any current snippets (old
592 // ones should have already been expired by the timer, and new ones shouldn't
593 // have expired yet), but it will update the timer for the next run.
523 ClearExpiredSnippets(); 594 ClearExpiredSnippets();
524 595
525 // If there are more snippets than we want to show, delete the extra ones. 596 for (const auto& item : categories_) {
526 if (snippets_.size() > kMaxSnippetCount) { 597 Category category = item.first;
527 NTPSnippet::PtrVector to_delete( 598 UpdateCategoryStatus(category, CategoryStatus::AVAILABLE);
528 std::make_move_iterator(snippets_.begin() + kMaxSnippetCount),
529 std::make_move_iterator(snippets_.end()));
530 snippets_.resize(kMaxSnippetCount);
531 database_->DeleteSnippets(to_delete);
532 } 599 }
533 600
601 // TODO(sfiera): equivalent metrics for non-articles.
602 const CategoryContent& content = categories_[articles_category_];
534 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles", 603 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles",
535 snippets_.size()); 604 content.snippets.size());
536 if (snippets_.empty() && !dismissed_snippets_.empty()) { 605 if (content.snippets.empty() && !content.dismissed.empty()) {
537 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded", 606 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded",
538 dismissed_snippets_.size()); 607 content.dismissed.size());
539 } 608 }
540 609
541 UpdateCategoryStatus(CategoryStatus::AVAILABLE); 610 // TODO(sfiera): notify only when a category changed above.
542 NotifyNewSuggestions(); 611 NotifyNewSuggestions();
543 } 612 }
544 613
545 void NTPSnippetsService::MergeSnippets(NTPSnippet::PtrVector new_snippets) { 614 void NTPSnippetsService::MergeSnippets(Category category,
615 NTPSnippet::PtrVector new_snippets) {
546 DCHECK(ready()); 616 DCHECK(ready());
617 CategoryContent* content = &categories_[category];
547 618
548 // Remove new snippets that we already have, or that have been dismissed. 619 // Remove new snippets that we already have, or that have been dismissed.
549 std::set<std::string> old_snippet_ids; 620 std::set<std::string> old_snippet_ids;
550 InsertAllIDs(dismissed_snippets_, &old_snippet_ids); 621 InsertAllIDs(content->dismissed, &old_snippet_ids);
551 InsertAllIDs(snippets_, &old_snippet_ids); 622 InsertAllIDs(content->snippets, &old_snippet_ids);
552 new_snippets.erase( 623 new_snippets.erase(
553 std::remove_if( 624 std::remove_if(
554 new_snippets.begin(), new_snippets.end(), 625 new_snippets.begin(), new_snippets.end(),
555 [&old_snippet_ids](const std::unique_ptr<NTPSnippet>& snippet) { 626 [&old_snippet_ids](const std::unique_ptr<NTPSnippet>& snippet) {
556 if (old_snippet_ids.count(snippet->id())) 627 if (old_snippet_ids.count(snippet->id()))
557 return true; 628 return true;
558 for (const SnippetSource& source : snippet->sources()) { 629 for (const SnippetSource& source : snippet->sources()) {
559 if (old_snippet_ids.count(source.url.spec())) 630 if (old_snippet_ids.count(source.url.spec()))
560 return true; 631 return true;
561 } 632 }
(...skipping 27 matching lines...) Expand all
589 new_snippets.end()); 660 new_snippets.end());
590 int num_snippets_dismissed = num_new_snippets - new_snippets.size(); 661 int num_snippets_dismissed = num_new_snippets - new_snippets.size();
591 UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch", 662 UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch",
592 num_snippets_dismissed > 0); 663 num_snippets_dismissed > 0);
593 if (num_snippets_dismissed > 0) { 664 if (num_snippets_dismissed > 0) {
594 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets", 665 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets",
595 num_snippets_dismissed); 666 num_snippets_dismissed);
596 } 667 }
597 } 668 }
598 669
599 // Save the new snippets to the DB. 670 // Save new articles to the DB.
600 database_->SaveSnippets(new_snippets); 671 // TODO(sfiera): save non-articles to DB too.
672 if (category == articles_category_)
673 database_->SaveSnippets(new_snippets);
601 674
602 // Insert the new snippets at the front. 675 // Insert the new snippets at the front.
603 snippets_.insert(snippets_.begin(), 676 content->snippets.insert(content->snippets.begin(),
604 std::make_move_iterator(new_snippets.begin()), 677 std::make_move_iterator(new_snippets.begin()),
605 std::make_move_iterator(new_snippets.end())); 678 std::make_move_iterator(new_snippets.end()));
606 } 679 }
607 680
608 std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const { 681 std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const {
609 std::set<std::string> hosts; 682 std::set<std::string> hosts;
610 const base::ListValue* list = pref_service_->GetList(prefs::kSnippetHosts); 683 const base::ListValue* list = pref_service_->GetList(prefs::kSnippetHosts);
611 for (const auto& value : *list) { 684 for (const auto& value : *list) {
612 std::string str; 685 std::string str;
613 bool success = value->GetAsString(&str); 686 bool success = value->GetAsString(&str);
614 DCHECK(success) << "Failed to parse snippet host from prefs"; 687 DCHECK(success) << "Failed to parse snippet host from prefs";
615 hosts.insert(std::move(str)); 688 hosts.insert(std::move(str));
616 } 689 }
617 return hosts; 690 return hosts;
618 } 691 }
619 692
620 void NTPSnippetsService::StoreSnippetHostsToPrefs( 693 void NTPSnippetsService::StoreSnippetHostsToPrefs(
621 const std::set<std::string>& hosts) { 694 const std::set<std::string>& hosts) {
622 base::ListValue list; 695 base::ListValue list;
623 for (const std::string& host : hosts) 696 for (const std::string& host : hosts)
624 list.AppendString(host); 697 list.AppendString(host);
625 pref_service_->Set(prefs::kSnippetHosts, list); 698 pref_service_->Set(prefs::kSnippetHosts, list);
626 } 699 }
627 700
628 void NTPSnippetsService::ClearExpiredSnippets() { 701 void NTPSnippetsService::ClearExpiredSnippets() {
629 base::Time expiry = base::Time::Now(); 702 std::vector<Category> categories_to_erase;
630 703
631 // Move expired snippets over into |to_delete|. 704 const base::Time expiry = base::Time::Now();
632 NTPSnippet::PtrVector to_delete; 705 base::Time next_expiry = base::Time::Max();
633 for (std::unique_ptr<NTPSnippet>& snippet : snippets_) { 706
634 if (snippet->expiry_date() <= expiry) 707 for (auto& item : categories_) {
635 to_delete.emplace_back(std::move(snippet)); 708 Category category = item.first;
709 CategoryContent* content = &item.second;
710
711 // Move expired snippets over into |to_delete|.
712 NTPSnippet::PtrVector to_delete;
713 for (std::unique_ptr<NTPSnippet>& snippet : content->snippets) {
714 if (snippet->expiry_date() <= expiry)
715 to_delete.emplace_back(std::move(snippet));
716 }
717 Compact(&content->snippets);
718
719 // Move expired dismissed snippets over into |to_delete| as well.
720 for (std::unique_ptr<NTPSnippet>& snippet : content->dismissed) {
721 if (snippet->expiry_date() <= expiry)
722 to_delete.emplace_back(std::move(snippet));
723 }
724 Compact(&content->dismissed);
725
726 // Finally, actually delete the removed snippets from the DB.
727 if (category == articles_category_)
728 database_->DeleteSnippets(to_delete);
729
730 if (content->snippets.empty() && content->dismissed.empty()) {
731 if ((category != articles_category_) && !content->provided_by_server)
732 categories_to_erase.push_back(category);
733 continue;
734 }
735
736 for (const auto& snippet : content->snippets) {
737 if (snippet->expiry_date() < next_expiry)
738 next_expiry = snippet->expiry_date();
739 }
740 for (const auto& snippet : content->dismissed) {
741 if (snippet->expiry_date() < next_expiry)
742 next_expiry = snippet->expiry_date();
743 }
636 } 744 }
637 Compact(&snippets_);
638 745
639 // Move expired dismissed snippets over into |to_delete| as well. 746 for (Category category : categories_to_erase) {
640 for (std::unique_ptr<NTPSnippet>& snippet : dismissed_snippets_) { 747 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
641 if (snippet->expiry_date() <= expiry) 748 categories_.erase(category);
642 to_delete.emplace_back(std::move(snippet));
643 } 749 }
644 Compact(&dismissed_snippets_);
645 750
646 // Finally, actually delete the removed snippets from the DB. 751 // Unless there are no snippets left, schedule a timer for the next expiry.
647 database_->DeleteSnippets(to_delete); 752 DCHECK_GT(next_expiry, expiry);
648 753 if (next_expiry < base::Time::Max()) {
649 // If there are any snippets left, schedule a timer for the next expiry. 754 expiry_timer_.Start(FROM_HERE, next_expiry - expiry,
tschumann 2016/08/25 16:15:21 i wonder if we should add some leeway here for the
650 if (snippets_.empty() && dismissed_snippets_.empty()) 755 base::Bind(&NTPSnippetsService::ClearExpiredSnippets,
651 return; 756 base::Unretained(this)));
652
653 base::Time next_expiry = base::Time::Max();
654 for (const auto& snippet : snippets_) {
655 if (snippet->expiry_date() < next_expiry)
656 next_expiry = snippet->expiry_date();
657 } 757 }
658 for (const auto& snippet : dismissed_snippets_) {
659 if (snippet->expiry_date() < next_expiry)
660 next_expiry = snippet->expiry_date();
661 }
662 DCHECK_GT(next_expiry, expiry);
663 expiry_timer_.Start(FROM_HERE, next_expiry - expiry,
664 base::Bind(&NTPSnippetsService::ClearExpiredSnippets,
665 base::Unretained(this)));
666 } 758 }
667 759
668 void NTPSnippetsService::NukeAllSnippets() { 760 void NTPSnippetsService::NukeAllSnippets() {
669 ClearCachedSuggestions(provided_category_); 761 std::vector<Category> categories_to_erase;
670 ClearDismissedSuggestionsForDebugging(provided_category_);
671 762
672 // Temporarily enter an "explicitly disabled" state, so that any open UIs 763 // Empty the ARTICLES category and remove all others, since they may or may
673 // will clear the suggestions too. 764 // not be personalized.
674 if (category_status_ != CategoryStatus::CATEGORY_EXPLICITLY_DISABLED) { 765 for (const auto& item : categories_) {
675 CategoryStatus old_category_status = category_status_; 766 Category category = item.first;
676 UpdateCategoryStatus(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED); 767
677 UpdateCategoryStatus(old_category_status); 768 ClearCachedSuggestions(category);
769 ClearDismissedSuggestionsForDebugging(category);
770
771 if (category == articles_category_) {
772 // Temporarily enter an "explicitly disabled" state, so that any open UIs
773 // will clear the suggestions too.
774 CategoryContent& content = categories_[category];
775 if (content.status != CategoryStatus::CATEGORY_EXPLICITLY_DISABLED) {
776 CategoryStatus old_category_status = content.status;
777 UpdateCategoryStatus(category,
778 CategoryStatus::CATEGORY_EXPLICITLY_DISABLED);
779 UpdateCategoryStatus(category, old_category_status);
780 }
781 } else {
782 // Remove other categories entirely; they may or may not reappear.
783 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
784 categories_to_erase.push_back(category);
785 }
786 }
787
788 for (Category category : categories_to_erase) {
789 categories_.erase(category);
678 } 790 }
679 } 791 }
680 792
681 void NTPSnippetsService::OnSnippetImageFetchedFromDatabase( 793 void NTPSnippetsService::OnSnippetImageFetchedFromDatabase(
682 const ImageFetchedCallback& callback, 794 const ImageFetchedCallback& callback,
683 const std::string& snippet_id, 795 const std::string& suggestion_id,
684 std::string data) { 796 std::string data) {
685 // |image_decoder_| is null in tests. 797 // |image_decoder_| is null in tests.
686 if (image_decoder_ && !data.empty()) { 798 if (image_decoder_ && !data.empty()) {
687 image_decoder_->DecodeImage( 799 image_decoder_->DecodeImage(
688 std::move(data), 800 std::move(data),
689 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromDatabase, 801 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromDatabase,
690 base::Unretained(this), callback, snippet_id)); 802 base::Unretained(this), callback, suggestion_id));
691 return; 803 return;
692 } 804 }
693 805
694 // Fetching from the DB failed; start a network fetch. 806 // Fetching from the DB failed; start a network fetch.
695 FetchSnippetImageFromNetwork(snippet_id, callback); 807 FetchSnippetImageFromNetwork(suggestion_id, callback);
696 } 808 }
697 809
698 void NTPSnippetsService::OnSnippetImageDecodedFromDatabase( 810 void NTPSnippetsService::OnSnippetImageDecodedFromDatabase(
699 const ImageFetchedCallback& callback, 811 const ImageFetchedCallback& callback,
700 const std::string& snippet_id, 812 const std::string& suggestion_id,
701 const gfx::Image& image) { 813 const gfx::Image& image) {
702 if (!image.IsEmpty()) { 814 if (!image.IsEmpty()) {
703 callback.Run(MakeUniqueID(provided_category_, snippet_id), image); 815 callback.Run(suggestion_id, image);
704 return; 816 return;
705 } 817 }
706 818
707 // If decoding the image failed, delete the DB entry. 819 // If decoding the image failed, delete the DB entry.
820 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id);
708 database_->DeleteImage(snippet_id); 821 database_->DeleteImage(snippet_id);
709 822
710 FetchSnippetImageFromNetwork(snippet_id, callback); 823 FetchSnippetImageFromNetwork(suggestion_id, callback);
711 } 824 }
712 825
713 void NTPSnippetsService::FetchSnippetImageFromNetwork( 826 void NTPSnippetsService::FetchSnippetImageFromNetwork(
714 const std::string& snippet_id, 827 const std::string& suggestion_id,
715 const ImageFetchedCallback& callback) { 828 const ImageFetchedCallback& callback) {
829 Category category = GetCategoryFromUniqueID(suggestion_id);
830 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id);
831
832 auto category_it = categories_.find(category);
833 if (category_it == categories_.end()) {
834 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image());
835 return;
836 }
837
838 const CategoryContent& content = category_it->second;
716 auto it = 839 auto it =
717 std::find_if(snippets_.begin(), snippets_.end(), 840 std::find_if(content.snippets.begin(), content.snippets.end(),
718 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { 841 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) {
719 return snippet->id() == snippet_id; 842 return snippet->id() == snippet_id;
720 }); 843 });
721 844
722 if (it == snippets_.end() || 845 if (it == content.snippets.end() ||
723 !thumbnail_requests_throttler_.DemandQuotaForRequest( 846 !thumbnail_requests_throttler_.DemandQuotaForRequest(
724 /*interactive_request=*/true)) { 847 /*interactive_request=*/true)) {
725 // Return an empty image. Directly, this is never synchronous with the 848 // Return an empty image. Directly, this is never synchronous with the
726 // original FetchSuggestionImage() call - an asynchronous database query has 849 // original FetchSuggestionImage() call - an asynchronous database query has
727 // happened in the meantime. 850 // happened in the meantime.
728 OnSnippetImageDecodedFromNetwork(callback, snippet_id, gfx::Image()); 851 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image());
729 return; 852 return;
730 } 853 }
731 854
732 const NTPSnippet& snippet = *it->get(); 855 const NTPSnippet& snippet = *it->get();
733 856
734 // TODO(jkrcal): We probably should rename OnImageDataFetched() to 857 // TODO(jkrcal): We probably should rename OnImageDataFetched() to
735 // CacheImageData(). This would document that this is actually independent 858 // CacheImageData(). This would document that this is actually independent
736 // from the individual fetch-flow. 859 // from the individual fetch-flow.
737 // The image fetcher calls OnImageDataFetched() with the raw data (this object 860 // The image fetcher calls OnImageDataFetched() with the raw data (this object
738 // is an ImageFetcherDelegate) and then also 861 // is an ImageFetcherDelegate) and then also
739 // OnSnippetImageDecodedFromNetwork() after the raw data gets decoded. 862 // OnSnippetImageDecodedFromNetwork() after the raw data gets decoded.
740 image_fetcher_->StartOrQueueNetworkRequest( 863 image_fetcher_->StartOrQueueNetworkRequest(
741 snippet.id(), snippet.salient_image_url(), 864 suggestion_id, snippet.salient_image_url(),
742 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork, 865 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork,
743 base::Unretained(this), callback)); 866 base::Unretained(this), callback));
744 } 867 }
745 868
746 void NTPSnippetsService::OnSnippetImageDecodedFromNetwork( 869 void NTPSnippetsService::OnSnippetImageDecodedFromNetwork(
747 const ImageFetchedCallback& callback, 870 const ImageFetchedCallback& callback,
748 const std::string& snippet_id, 871 const std::string& suggestion_id,
749 const gfx::Image& image) { 872 const gfx::Image& image) {
750 callback.Run(MakeUniqueID(provided_category_, snippet_id), image); 873 callback.Run(suggestion_id, image);
751 } 874 }
752 875
753 void NTPSnippetsService::EnterStateEnabled(bool fetch_snippets) { 876 void NTPSnippetsService::EnterStateEnabled(bool fetch_snippets) {
754 if (fetch_snippets) 877 if (fetch_snippets)
755 FetchSnippets(/*force_request=*/false); 878 FetchSnippets(/*force_request=*/false);
756 879
757 // FetchSnippets should set the status to |AVAILABLE_LOADING| if relevant, 880 // FetchSnippets should set the status to |AVAILABLE_LOADING| if relevant,
758 // otherwise we transition to |AVAILABLE| here. 881 // otherwise we transition to |AVAILABLE| here.
759 if (category_status_ != CategoryStatus::AVAILABLE_LOADING) 882 if (categories_[articles_category_].status !=
760 UpdateCategoryStatus(CategoryStatus::AVAILABLE); 883 CategoryStatus::AVAILABLE_LOADING) {
884 UpdateCategoryStatus(articles_category_, CategoryStatus::AVAILABLE);
885 }
761 886
762 // If host restrictions are enabled, register for host list updates. 887 // If host restrictions are enabled, register for host list updates.
763 // |suggestions_service_| can be null in tests. 888 // |suggestions_service_| can be null in tests.
764 if (snippets_fetcher_->UsesHostRestrictions() && suggestions_service_) { 889 if (snippets_fetcher_->UsesHostRestrictions() && suggestions_service_) {
765 suggestions_service_subscription_ = 890 suggestions_service_subscription_ =
766 suggestions_service_->AddCallback(base::Bind( 891 suggestions_service_->AddCallback(base::Bind(
767 &NTPSnippetsService::OnSuggestionsChanged, base::Unretained(this))); 892 &NTPSnippetsService::OnSuggestionsChanged, base::Unretained(this)));
768 } 893 }
769 894
770 RescheduleFetching(); 895 RescheduleFetching();
771 } 896 }
772 897
773 void NTPSnippetsService::EnterStateDisabled() { 898 void NTPSnippetsService::EnterStateDisabled() {
774 ClearCachedSuggestions(provided_category_); 899 std::vector<Category> categories_to_erase;
775 ClearDismissedSuggestionsForDebugging(provided_category_); 900
901 // Empty the ARTICLES category and remove all others, since they may or may
902 // not be personalized.
903 for (const auto& item : categories_) {
904 Category category = item.first;
905 ClearCachedSuggestions(category);
906 ClearDismissedSuggestionsForDebugging(category);
907 if (category != articles_category_) {
908 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED);
909 categories_to_erase.push_back(category);
910 }
911 }
912
913 for (Category category : categories_to_erase) {
914 categories_.erase(category);
915 }
776 916
777 expiry_timer_.Stop(); 917 expiry_timer_.Stop();
778 suggestions_service_subscription_.reset(); 918 suggestions_service_subscription_.reset();
779 RescheduleFetching(); 919 RescheduleFetching();
780 } 920 }
781 921
782 void NTPSnippetsService::EnterStateError() { 922 void NTPSnippetsService::EnterStateError() {
783 expiry_timer_.Stop(); 923 expiry_timer_.Stop();
784 suggestions_service_subscription_.reset(); 924 suggestions_service_subscription_.reset();
785 RescheduleFetching(); 925 RescheduleFetching();
(...skipping 24 matching lines...) Expand all
810 // Always notify here even if we got nothing from the database, because we 950 // Always notify here even if we got nothing from the database, because we
811 // don't know how long the fetch will take or if it will even complete. 951 // don't know how long the fetch will take or if it will even complete.
812 NotifyNewSuggestions(); 952 NotifyNewSuggestions();
813 } 953 }
814 954
815 void NTPSnippetsService::OnDisabledReasonChanged( 955 void NTPSnippetsService::OnDisabledReasonChanged(
816 DisabledReason disabled_reason) { 956 DisabledReason disabled_reason) {
817 switch (disabled_reason) { 957 switch (disabled_reason) {
818 case DisabledReason::NONE: 958 case DisabledReason::NONE:
819 // Do not change the status. That will be done in EnterStateEnabled() 959 // Do not change the status. That will be done in EnterStateEnabled()
820 EnterState(State::READY, category_status_); 960 EnterState(State::READY);
821 break; 961 break;
822 962
823 case DisabledReason::EXPLICITLY_DISABLED: 963 case DisabledReason::EXPLICITLY_DISABLED:
824 EnterState(State::DISABLED, CategoryStatus::CATEGORY_EXPLICITLY_DISABLED); 964 EnterState(State::DISABLED);
965 UpdateAllCategoryStatus(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED);
825 break; 966 break;
826 967
827 case DisabledReason::SIGNED_OUT: 968 case DisabledReason::SIGNED_OUT:
828 EnterState(State::DISABLED, CategoryStatus::SIGNED_OUT); 969 EnterState(State::DISABLED);
970 UpdateAllCategoryStatus(CategoryStatus::SIGNED_OUT);
829 break; 971 break;
830 } 972 }
831 } 973 }
832 974
833 void NTPSnippetsService::EnterState(State state, CategoryStatus status) { 975 void NTPSnippetsService::EnterState(State state) {
834 UpdateCategoryStatus(status);
835
836 if (state == state_) 976 if (state == state_)
837 return; 977 return;
838 978
839 switch (state) { 979 switch (state) {
840 case State::NOT_INITED: 980 case State::NOT_INITED:
841 // Initial state, it should not be possible to get back there. 981 // Initial state, it should not be possible to get back there.
842 NOTREACHED(); 982 NOTREACHED();
843 return; 983 return;
844 984
845 case State::READY: { 985 case State::READY: {
846 DCHECK(state_ == State::NOT_INITED || state_ == State::DISABLED); 986 DCHECK(state_ == State::NOT_INITED || state_ == State::DISABLED);
847 987
848 bool fetch_snippets = snippets_.empty() || fetch_after_load_; 988 bool fetch_snippets =
989 categories_[articles_category_].snippets.empty() || fetch_after_load_;
849 DVLOG(1) << "Entering state: READY"; 990 DVLOG(1) << "Entering state: READY";
850 state_ = State::READY; 991 state_ = State::READY;
851 fetch_after_load_ = false; 992 fetch_after_load_ = false;
852 EnterStateEnabled(fetch_snippets); 993 EnterStateEnabled(fetch_snippets);
853 return; 994 return;
854 } 995 }
855 996
856 case State::DISABLED: 997 case State::DISABLED:
857 DCHECK(state_ == State::NOT_INITED || state_ == State::READY); 998 DCHECK(state_ == State::NOT_INITED || state_ == State::READY);
858 999
859 DVLOG(1) << "Entering state: DISABLED"; 1000 DVLOG(1) << "Entering state: DISABLED";
860 state_ = State::DISABLED; 1001 state_ = State::DISABLED;
861 EnterStateDisabled(); 1002 EnterStateDisabled();
862 return; 1003 return;
863 1004
864 case State::ERROR_OCCURRED: 1005 case State::ERROR_OCCURRED:
865 DVLOG(1) << "Entering state: ERROR_OCCURRED"; 1006 DVLOG(1) << "Entering state: ERROR_OCCURRED";
866 state_ = State::ERROR_OCCURRED; 1007 state_ = State::ERROR_OCCURRED;
867 EnterStateError(); 1008 EnterStateError();
868 return; 1009 return;
869 } 1010 }
870 } 1011 }
871 1012
872 void NTPSnippetsService::NotifyNewSuggestions() { 1013 void NTPSnippetsService::NotifyNewSuggestions() {
873 std::vector<ContentSuggestion> result; 1014 for (const auto& item : categories_) {
874 for (const std::unique_ptr<NTPSnippet>& snippet : snippets_) { 1015 Category category = item.first;
875 if (!snippet->is_complete()) 1016 const CategoryContent& content = item.second;
876 continue; 1017
877 ContentSuggestion suggestion( 1018 std::vector<ContentSuggestion> result;
878 MakeUniqueID(provided_category_, snippet->id()), 1019 for (const std::unique_ptr<NTPSnippet>& snippet : content.snippets) {
879 snippet->best_source().url); 1020 // TODO(sfiera): if a snippet is not going to be displayed, move it
880 suggestion.set_amp_url(snippet->best_source().amp_url); 1021 // directly to content.dismissed on fetch. Otherwise, we might prune
881 suggestion.set_title(base::UTF8ToUTF16(snippet->title())); 1022 // other snippets to get down to kMaxSnippetCount, only to hide one of the
882 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet())); 1023 // incomplete ones we kept.
883 suggestion.set_publish_date(snippet->publish_date()); 1024 if (!snippet->is_complete())
884 suggestion.set_publisher_name( 1025 continue;
885 base::UTF8ToUTF16(snippet->best_source().publisher_name)); 1026 ContentSuggestion suggestion(MakeUniqueID(category, snippet->id()),
886 suggestion.set_score(snippet->score()); 1027 snippet->best_source().url);
887 result.emplace_back(std::move(suggestion)); 1028 suggestion.set_amp_url(snippet->best_source().amp_url);
1029 suggestion.set_title(base::UTF8ToUTF16(snippet->title()));
1030 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet()));
1031 suggestion.set_publish_date(snippet->publish_date());
1032 suggestion.set_publisher_name(
1033 base::UTF8ToUTF16(snippet->best_source().publisher_name));
1034 suggestion.set_score(snippet->score());
1035 result.emplace_back(std::move(suggestion));
1036 }
1037
1038 DVLOG(1) << "NotifyNewSuggestions(): " << result.size()
1039 << " items in category " << category;
1040 observer()->OnNewSuggestions(this, category, std::move(result));
888 } 1041 }
889 observer()->OnNewSuggestions(this, provided_category_, std::move(result));
890 } 1042 }
891 1043
892 void NTPSnippetsService::UpdateCategoryStatus(CategoryStatus status) { 1044 void NTPSnippetsService::UpdateCategoryStatus(Category category,
893 if (status == category_status_) 1045 CategoryStatus status) {
1046 DCHECK(categories_.find(category) != categories_.end());
1047 CategoryContent& content = categories_[category];
1048 if (status == content.status)
894 return; 1049 return;
895 1050
896 category_status_ = status; 1051 DVLOG(1) << "UpdateCategoryStatus(): " << category.id() << ": "
897 observer()->OnCategoryStatusChanged(this, provided_category_, 1052 << static_cast<int>(content.status) << " -> "
898 category_status_); 1053 << static_cast<int>(status);
1054 content.status = status;
1055 observer()->OnCategoryStatusChanged(this, category, content.status);
899 } 1056 }
900 1057
1058 void NTPSnippetsService::UpdateAllCategoryStatus(CategoryStatus status) {
1059 for (const auto& category : categories_) {
1060 UpdateCategoryStatus(category.first, status);
1061 }
1062 }
1063
1064 NTPSnippetsService::CategoryContent::CategoryContent() = default;
1065 NTPSnippetsService::CategoryContent::CategoryContent(CategoryContent&&) =
1066 default;
1067 NTPSnippetsService::CategoryContent::~CategoryContent() = default;
1068 NTPSnippetsService::CategoryContent& NTPSnippetsService::CategoryContent::
1069 operator=(CategoryContent&&) = default;
1070
901 } // namespace ntp_snippets 1071 } // namespace ntp_snippets
OLDNEW
« no previous file with comments | « components/ntp_snippets/ntp_snippets_service.h ('k') | components/ntp_snippets/ntp_snippets_service_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698