OLD | NEW |
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/remote/ntp_snippets_service.h" | 5 #include "components/ntp_snippets/remote/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 175 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
186 snippets_fetcher_(std::move(snippets_fetcher)), | 186 snippets_fetcher_(std::move(snippets_fetcher)), |
187 image_fetcher_(std::move(image_fetcher)), | 187 image_fetcher_(std::move(image_fetcher)), |
188 image_decoder_(std::move(image_decoder)), | 188 image_decoder_(std::move(image_decoder)), |
189 database_(std::move(database)), | 189 database_(std::move(database)), |
190 snippets_status_service_(std::move(status_service)), | 190 snippets_status_service_(std::move(status_service)), |
191 fetch_when_ready_(false), | 191 fetch_when_ready_(false), |
192 nuke_when_initialized_(false), | 192 nuke_when_initialized_(false), |
193 thumbnail_requests_throttler_( | 193 thumbnail_requests_throttler_( |
194 pref_service, | 194 pref_service, |
195 RequestThrottler::RequestType::CONTENT_SUGGESTION_THUMBNAIL) { | 195 RequestThrottler::RequestType::CONTENT_SUGGESTION_THUMBNAIL) { |
196 // Articles category always exists; others will be added as needed. | 196 RestoreCategoriesFromPrefs(); |
197 categories_[articles_category_] = CategoryContent(); | 197 // The articles category always exists. Add it if we didn't get it from prefs. |
198 categories_[articles_category_].localized_title = | 198 // TODO(treib): Rethink this. |
199 l10n_util::GetStringUTF16(IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER); | 199 if (!base::ContainsKey(categories_, articles_category_)) { |
200 observer->OnCategoryStatusChanged(this, articles_category_, | 200 categories_[articles_category_] = CategoryContent(); |
201 categories_[articles_category_].status); | 201 categories_[articles_category_].localized_title = |
| 202 l10n_util::GetStringUTF16(IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER); |
| 203 } |
| 204 // Tell the observer about all the categories. |
| 205 for (const auto& entry : categories_) { |
| 206 observer->OnCategoryStatusChanged(this, entry.first, entry.second.status); |
| 207 } |
| 208 |
202 if (database_->IsErrorState()) { | 209 if (database_->IsErrorState()) { |
203 EnterState(State::ERROR_OCCURRED); | 210 EnterState(State::ERROR_OCCURRED); |
204 UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR); | 211 UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR); |
205 return; | 212 return; |
206 } | 213 } |
207 | 214 |
208 database_->SetErrorCallback(base::Bind(&NTPSnippetsService::OnDatabaseError, | 215 database_->SetErrorCallback(base::Bind(&NTPSnippetsService::OnDatabaseError, |
209 base::Unretained(this))); | 216 base::Unretained(this))); |
210 | 217 |
211 // We transition to other states while finalizing the initialization, when the | 218 // We transition to other states while finalizing the initialization, when the |
212 // database is done loading. | 219 // database is done loading. |
213 database_->LoadSnippets(base::Bind(&NTPSnippetsService::OnDatabaseLoaded, | 220 database_->LoadSnippets(base::Bind(&NTPSnippetsService::OnDatabaseLoaded, |
214 base::Unretained(this))); | 221 base::Unretained(this))); |
215 } | 222 } |
216 | 223 |
217 NTPSnippetsService::~NTPSnippetsService() = default; | 224 NTPSnippetsService::~NTPSnippetsService() = default; |
218 | 225 |
219 // static | 226 // static |
220 void NTPSnippetsService::RegisterProfilePrefs(PrefRegistrySimple* registry) { | 227 void NTPSnippetsService::RegisterProfilePrefs(PrefRegistrySimple* registry) { |
221 registry->RegisterListPref(prefs::kSnippetHosts); //TODO remove | 228 // TODO(treib): Add cleanup logic for prefs::kSnippetHosts, then remove it |
| 229 // completely after M56. |
| 230 registry->RegisterListPref(prefs::kSnippetHosts); |
| 231 registry->RegisterListPref(prefs::kRemoteSuggestionCategories); |
222 registry->RegisterInt64Pref(prefs::kSnippetBackgroundFetchingIntervalWifi, 0); | 232 registry->RegisterInt64Pref(prefs::kSnippetBackgroundFetchingIntervalWifi, 0); |
223 registry->RegisterInt64Pref(prefs::kSnippetBackgroundFetchingIntervalFallback, | 233 registry->RegisterInt64Pref(prefs::kSnippetBackgroundFetchingIntervalFallback, |
224 0); | 234 0); |
225 | 235 |
226 NTPSnippetsStatusService::RegisterProfilePrefs(registry); | 236 NTPSnippetsStatusService::RegisterProfilePrefs(registry); |
227 } | 237 } |
228 | 238 |
229 void NTPSnippetsService::FetchSnippets(bool interactive_request) { | 239 void NTPSnippetsService::FetchSnippets(bool interactive_request) { |
230 if (ready()) | 240 if (ready()) |
231 FetchSnippetsFromHosts(std::set<std::string>(), interactive_request); | 241 FetchSnippetsFromHosts(std::set<std::string>(), interactive_request); |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
292 if (state_ != State::NOT_INITED || force) { | 302 if (state_ != State::NOT_INITED || force) { |
293 scheduler_->Unschedule(); | 303 scheduler_->Unschedule(); |
294 pref_service_->ClearPref(prefs::kSnippetBackgroundFetchingIntervalWifi); | 304 pref_service_->ClearPref(prefs::kSnippetBackgroundFetchingIntervalWifi); |
295 pref_service_->ClearPref( | 305 pref_service_->ClearPref( |
296 prefs::kSnippetBackgroundFetchingIntervalFallback); | 306 prefs::kSnippetBackgroundFetchingIntervalFallback); |
297 } | 307 } |
298 } | 308 } |
299 } | 309 } |
300 | 310 |
301 CategoryStatus NTPSnippetsService::GetCategoryStatus(Category category) { | 311 CategoryStatus NTPSnippetsService::GetCategoryStatus(Category category) { |
302 DCHECK(categories_.find(category) != categories_.end()); | 312 DCHECK(base::ContainsKey(categories_, category)); |
303 return categories_[category].status; | 313 return categories_[category].status; |
304 } | 314 } |
305 | 315 |
306 CategoryInfo NTPSnippetsService::GetCategoryInfo(Category category) { | 316 CategoryInfo NTPSnippetsService::GetCategoryInfo(Category category) { |
307 DCHECK(categories_.find(category) != categories_.end()); | 317 DCHECK(base::ContainsKey(categories_, category)); |
308 const CategoryContent& content = categories_[category]; | 318 const CategoryContent& content = categories_[category]; |
309 return CategoryInfo(content.localized_title, | 319 return CategoryInfo(content.localized_title, |
310 ContentSuggestionsCardLayout::FULL_CARD, | 320 ContentSuggestionsCardLayout::FULL_CARD, |
311 /* has_more_button */ false, | 321 /* has_more_button */ false, |
312 /* show_if_empty */ true); | 322 /* show_if_empty */ true); |
313 } | 323 } |
314 | 324 |
315 void NTPSnippetsService::DismissSuggestion( | 325 void NTPSnippetsService::DismissSuggestion( |
316 const ContentSuggestion::ID& suggestion_id) { | 326 const ContentSuggestion::ID& suggestion_id) { |
317 if (!ready()) | 327 if (!ready()) |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
356 if (!ready()) | 366 if (!ready()) |
357 nuke_when_initialized_ = true; | 367 nuke_when_initialized_ = true; |
358 else | 368 else |
359 NukeAllSnippets(); | 369 NukeAllSnippets(); |
360 } | 370 } |
361 | 371 |
362 void NTPSnippetsService::ClearCachedSuggestions(Category category) { | 372 void NTPSnippetsService::ClearCachedSuggestions(Category category) { |
363 if (!initialized()) | 373 if (!initialized()) |
364 return; | 374 return; |
365 | 375 |
366 if (categories_.find(category) == categories_.end()) | 376 if (!base::ContainsKey(categories_, category)) |
367 return; | 377 return; |
368 CategoryContent* content = &categories_[category]; | 378 CategoryContent* content = &categories_[category]; |
369 if (content->snippets.empty()) | 379 if (content->snippets.empty()) |
370 return; | 380 return; |
371 | 381 |
372 if (category == articles_category_) { | 382 if (category == articles_category_) { |
373 database_->DeleteSnippets(GetSnippetIDVector(content->snippets)); | 383 database_->DeleteSnippets(GetSnippetIDVector(content->snippets)); |
374 database_->DeleteImages(GetSnippetIDVector(content->snippets)); | 384 database_->DeleteImages(GetSnippetIDVector(content->snippets)); |
375 } | 385 } |
376 content->snippets.clear(); | 386 content->snippets.clear(); |
377 | 387 |
378 NotifyNewSuggestions(); | 388 NotifyNewSuggestions(); |
379 } | 389 } |
380 | 390 |
381 void NTPSnippetsService::GetDismissedSuggestionsForDebugging( | 391 void NTPSnippetsService::GetDismissedSuggestionsForDebugging( |
382 Category category, | 392 Category category, |
383 const DismissedSuggestionsCallback& callback) { | 393 const DismissedSuggestionsCallback& callback) { |
384 DCHECK(categories_.find(category) != categories_.end()); | 394 DCHECK(base::ContainsKey(categories_, category)); |
385 | 395 |
386 std::vector<ContentSuggestion> result; | 396 std::vector<ContentSuggestion> result; |
387 const CategoryContent& content = categories_[category]; | 397 const CategoryContent& content = categories_[category]; |
388 for (const std::unique_ptr<NTPSnippet>& snippet : content.dismissed) { | 398 for (const std::unique_ptr<NTPSnippet>& snippet : content.dismissed) { |
389 if (!snippet->is_complete()) | 399 if (!snippet->is_complete()) |
390 continue; | 400 continue; |
391 ContentSuggestion suggestion(category, snippet->id(), | 401 ContentSuggestion suggestion(category, snippet->id(), |
392 snippet->best_source().url); | 402 snippet->best_source().url); |
393 suggestion.set_amp_url(snippet->best_source().amp_url); | 403 suggestion.set_amp_url(snippet->best_source().amp_url); |
394 suggestion.set_title(base::UTF8ToUTF16(snippet->title())); | 404 suggestion.set_title(base::UTF8ToUTF16(snippet->title())); |
395 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet())); | 405 suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet())); |
396 suggestion.set_publish_date(snippet->publish_date()); | 406 suggestion.set_publish_date(snippet->publish_date()); |
397 suggestion.set_publisher_name( | 407 suggestion.set_publisher_name( |
398 base::UTF8ToUTF16(snippet->best_source().publisher_name)); | 408 base::UTF8ToUTF16(snippet->best_source().publisher_name)); |
399 suggestion.set_score(snippet->score()); | 409 suggestion.set_score(snippet->score()); |
400 result.emplace_back(std::move(suggestion)); | 410 result.emplace_back(std::move(suggestion)); |
401 } | 411 } |
402 callback.Run(std::move(result)); | 412 callback.Run(std::move(result)); |
403 } | 413 } |
404 | 414 |
405 void NTPSnippetsService::ClearDismissedSuggestionsForDebugging( | 415 void NTPSnippetsService::ClearDismissedSuggestionsForDebugging( |
406 Category category) { | 416 Category category) { |
407 DCHECK(categories_.find(category) != categories_.end()); | 417 DCHECK(base::ContainsKey(categories_, category)); |
408 | 418 |
409 if (!initialized()) | 419 if (!initialized()) |
410 return; | 420 return; |
411 | 421 |
412 CategoryContent* content = &categories_[category]; | 422 CategoryContent* content = &categories_[category]; |
413 if (content->dismissed.empty()) | 423 if (content->dismissed.empty()) |
414 return; | 424 return; |
415 | 425 |
416 if (category == articles_category_) { | 426 if (category == articles_category_) { |
417 // The image got already deleted when the suggestion was dismissed. | 427 // The image got already deleted when the suggestion was dismissed. |
418 database_->DeleteSnippets(GetSnippetIDVector(content->dismissed)); | 428 database_->DeleteSnippets(GetSnippetIDVector(content->dismissed)); |
419 } | 429 } |
420 content->dismissed.clear(); | 430 content->dismissed.clear(); |
421 } | 431 } |
422 | 432 |
423 // static | 433 // static |
424 int NTPSnippetsService::GetMaxSnippetCountForTesting() { | 434 int NTPSnippetsService::GetMaxSnippetCountForTesting() { |
425 return kMaxSnippetCount; | 435 return kMaxSnippetCount; |
426 } | 436 } |
427 | 437 |
428 //////////////////////////////////////////////////////////////////////////////// | 438 //////////////////////////////////////////////////////////////////////////////// |
429 // Private methods | 439 // Private methods |
430 | 440 |
431 GURL NTPSnippetsService::FindSnippetImageUrl( | 441 GURL NTPSnippetsService::FindSnippetImageUrl( |
432 const ContentSuggestion::ID& suggestion_id) const { | 442 const ContentSuggestion::ID& suggestion_id) const { |
433 DCHECK(categories_.find(suggestion_id.category()) != categories_.end()); | 443 DCHECK(base::ContainsKey(categories_, suggestion_id.category())); |
434 | 444 |
435 const CategoryContent& content = categories_.at(suggestion_id.category()); | 445 const CategoryContent& content = categories_.at(suggestion_id.category()); |
436 const NTPSnippet* snippet = | 446 const NTPSnippet* snippet = |
437 content.FindSnippet(suggestion_id.id_within_category()); | 447 content.FindSnippet(suggestion_id.id_within_category()); |
438 if (!snippet) | 448 if (!snippet) |
439 return GURL(); | 449 return GURL(); |
440 return snippet->salient_image_url(); | 450 return snippet->salient_image_url(); |
441 } | 451 } |
442 | 452 |
443 // image_fetcher::ImageFetcherDelegate implementation. | 453 // image_fetcher::ImageFetcherDelegate implementation. |
(...skipping 16 matching lines...) Expand all Loading... |
460 | 470 |
461 // Only cache the data in the DB, the actual serving is done in the callback | 471 // Only cache the data in the DB, the actual serving is done in the callback |
462 // provided to |image_fetcher_| (OnSnippetImageDecodedFromNetwork()). | 472 // provided to |image_fetcher_| (OnSnippetImageDecodedFromNetwork()). |
463 database_->SaveImage(id_within_category, image_data); | 473 database_->SaveImage(id_within_category, image_data); |
464 } | 474 } |
465 | 475 |
466 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { | 476 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { |
467 if (state_ == State::ERROR_OCCURRED) | 477 if (state_ == State::ERROR_OCCURRED) |
468 return; | 478 return; |
469 DCHECK(state_ == State::NOT_INITED); | 479 DCHECK(state_ == State::NOT_INITED); |
470 DCHECK_EQ(1u, categories_.size()); // Only articles category, so far. | 480 DCHECK(base::ContainsKey(categories_, articles_category_)); |
471 DCHECK(categories_.find(articles_category_) != categories_.end()); | |
472 | 481 |
473 // TODO(sfiera): support non-article categories in database. | 482 // TODO(treib): Support non-article categories in database. crbug.com/653476 |
474 CategoryContent* content = &categories_[articles_category_]; | 483 CategoryContent* content = &categories_[articles_category_]; |
475 for (std::unique_ptr<NTPSnippet>& snippet : snippets) { | 484 for (std::unique_ptr<NTPSnippet>& snippet : snippets) { |
476 if (snippet->is_dismissed()) | 485 if (snippet->is_dismissed()) |
477 content->dismissed.emplace_back(std::move(snippet)); | 486 content->dismissed.emplace_back(std::move(snippet)); |
478 else | 487 else |
479 content->snippets.emplace_back(std::move(snippet)); | 488 content->snippets.emplace_back(std::move(snippet)); |
480 } | 489 } |
481 | 490 |
482 std::sort(content->snippets.begin(), content->snippets.end(), | 491 std::sort(content->snippets.begin(), content->snippets.end(), |
483 [](const std::unique_ptr<NTPSnippet>& lhs, | 492 [](const std::unique_ptr<NTPSnippet>& lhs, |
(...skipping 12 matching lines...) Expand all Loading... |
496 void NTPSnippetsService::OnDatabaseError() { | 505 void NTPSnippetsService::OnDatabaseError() { |
497 EnterState(State::ERROR_OCCURRED); | 506 EnterState(State::ERROR_OCCURRED); |
498 UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR); | 507 UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR); |
499 } | 508 } |
500 | 509 |
501 void NTPSnippetsService::OnFetchFinished( | 510 void NTPSnippetsService::OnFetchFinished( |
502 NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories) { | 511 NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories) { |
503 if (!ready()) | 512 if (!ready()) |
504 return; | 513 return; |
505 | 514 |
| 515 // Mark all categories as not provided by the server in the latest fetch. The |
| 516 // ones we got will be marked again below. |
506 for (auto& item : categories_) { | 517 for (auto& item : categories_) { |
507 CategoryContent* content = &item.second; | 518 CategoryContent* content = &item.second; |
508 content->provided_by_server = false; | 519 content->provided_by_server = false; |
509 } | 520 } |
510 | 521 |
511 // Clear up expired dismissed snippets before we use them to filter new ones. | 522 // Clear up expired dismissed snippets before we use them to filter new ones. |
512 ClearExpiredDismissedSnippets(); | 523 ClearExpiredDismissedSnippets(); |
513 | 524 |
514 // If snippets were fetched successfully, update our |categories_| from each | 525 // If snippets were fetched successfully, update our |categories_| from each |
515 // category provided by the server. | 526 // category provided by the server. |
(...skipping 22 matching lines...) Expand all Loading... |
538 if (category == articles_category_) { | 549 if (category == articles_category_) { |
539 UMA_HISTOGRAM_SPARSE_SLOWLY( | 550 UMA_HISTOGRAM_SPARSE_SLOWLY( |
540 "NewTabPage.Snippets.NumArticlesFetched", | 551 "NewTabPage.Snippets.NumArticlesFetched", |
541 std::min(fetched_category.snippets.size(), | 552 std::min(fetched_category.snippets.size(), |
542 static_cast<size_t>(kMaxSnippetCount + 1))); | 553 static_cast<size_t>(kMaxSnippetCount + 1))); |
543 } | 554 } |
544 ReplaceSnippets(category, std::move(fetched_category.snippets)); | 555 ReplaceSnippets(category, std::move(fetched_category.snippets)); |
545 } | 556 } |
546 } | 557 } |
547 | 558 |
| 559 // We might have gotten new categories (or updated the titles of existing |
| 560 // ones), so update the pref. |
| 561 StoreCategoriesToPrefs(); |
| 562 |
548 for (const auto& item : categories_) { | 563 for (const auto& item : categories_) { |
549 Category category = item.first; | 564 Category category = item.first; |
550 UpdateCategoryStatus(category, CategoryStatus::AVAILABLE); | 565 UpdateCategoryStatus(category, CategoryStatus::AVAILABLE); |
551 } | 566 } |
552 | 567 |
553 // TODO(sfiera): equivalent metrics for non-articles. | 568 // TODO(sfiera): equivalent metrics for non-articles. |
554 const CategoryContent& content = categories_[articles_category_]; | 569 const CategoryContent& content = categories_[articles_category_]; |
555 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles", | 570 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles", |
556 content.snippets.size()); | 571 content.snippets.size()); |
557 if (content.snippets.empty() && !content.dismissed.empty()) { | 572 if (content.snippets.empty() && !content.dismissed.empty()) { |
558 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded", | 573 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded", |
559 content.dismissed.size()); | 574 content.dismissed.size()); |
560 } | 575 } |
561 | 576 |
562 // TODO(sfiera): notify only when a category changed above. | 577 // TODO(sfiera): notify only when a category changed above. |
563 NotifyNewSuggestions(); | 578 NotifyNewSuggestions(); |
564 | 579 |
565 // Reschedule after a successful fetch. This resets all currently scheduled | 580 // Reschedule after a successful fetch. This resets all currently scheduled |
566 // fetches, to make sure the fallback interval triggers only if no wifi fetch | 581 // fetches, to make sure the fallback interval triggers only if no wifi fetch |
567 // succeeded, and also that we don't do a background fetch immediately after | 582 // succeeded, and also that we don't do a background fetch immediately after |
568 // a user-initiated one. | 583 // a user-initiated one. |
569 if (fetched_categories) | 584 if (fetched_categories) |
570 RescheduleFetching(true); | 585 RescheduleFetching(true); |
571 } | 586 } |
572 | 587 |
573 void NTPSnippetsService::ArchiveSnippets(Category category, | 588 void NTPSnippetsService::ArchiveSnippets(Category category, |
574 NTPSnippet::PtrVector* to_archive) { | 589 NTPSnippet::PtrVector* to_archive) { |
575 CategoryContent* content = &categories_[category]; | 590 CategoryContent* content = &categories_[category]; |
576 | 591 |
577 // TODO(sfiera): handle DB for non-articles too. | 592 // TODO(treib): Handle DB for non-articles too. crbug.com/653476 |
578 if (category == articles_category_) { | 593 if (category == articles_category_) { |
579 database_->DeleteSnippets(GetSnippetIDVector(*to_archive)); | 594 database_->DeleteSnippets(GetSnippetIDVector(*to_archive)); |
580 // Do not delete the thumbnail images as they are still handy on open NTPs. | 595 // Do not delete the thumbnail images as they are still handy on open NTPs. |
581 } | 596 } |
582 | 597 |
583 // Archive previous snippets - move them at the beginning of the list. | 598 // Archive previous snippets - move them at the beginning of the list. |
584 content->archived.insert(content->archived.begin(), | 599 content->archived.insert(content->archived.begin(), |
585 std::make_move_iterator(to_archive->begin()), | 600 std::make_move_iterator(to_archive->begin()), |
586 std::make_move_iterator(to_archive->end())); | 601 std::make_move_iterator(to_archive->end())); |
587 Compact(to_archive); | 602 Compact(to_archive); |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
685 if (content->snippets.empty() && content->dismissed.empty() && | 700 if (content->snippets.empty() && content->dismissed.empty() && |
686 category != articles_category_ && !content->provided_by_server) { | 701 category != articles_category_ && !content->provided_by_server) { |
687 categories_to_erase.push_back(category); | 702 categories_to_erase.push_back(category); |
688 } | 703 } |
689 } | 704 } |
690 | 705 |
691 for (Category category : categories_to_erase) { | 706 for (Category category : categories_to_erase) { |
692 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED); | 707 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED); |
693 categories_.erase(category); | 708 categories_.erase(category); |
694 } | 709 } |
| 710 |
| 711 StoreCategoriesToPrefs(); |
695 } | 712 } |
696 | 713 |
697 void NTPSnippetsService::ClearOrphanedImages() { | 714 void NTPSnippetsService::ClearOrphanedImages() { |
698 auto alive_snippets = base::MakeUnique<std::set<std::string>>(); | 715 auto alive_snippets = base::MakeUnique<std::set<std::string>>(); |
699 for (const auto& snippet_ptr : categories_[articles_category_].snippets) { | 716 for (const auto& snippet_ptr : categories_[articles_category_].snippets) { |
700 alive_snippets->insert(snippet_ptr->id()); | 717 alive_snippets->insert(snippet_ptr->id()); |
701 } | 718 } |
702 for (const auto& snippet_ptr : categories_[articles_category_].dismissed) { | 719 for (const auto& snippet_ptr : categories_[articles_category_].dismissed) { |
703 alive_snippets->insert(snippet_ptr->id()); | 720 alive_snippets->insert(snippet_ptr->id()); |
704 } | 721 } |
(...skipping 14 matching lines...) Expand all Loading... |
719 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED); | 736 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED); |
720 | 737 |
721 // Remove the category entirely; it may or may not reappear. | 738 // Remove the category entirely; it may or may not reappear. |
722 if (category != articles_category_) | 739 if (category != articles_category_) |
723 categories_to_erase.push_back(category); | 740 categories_to_erase.push_back(category); |
724 } | 741 } |
725 | 742 |
726 for (Category category : categories_to_erase) { | 743 for (Category category : categories_to_erase) { |
727 categories_.erase(category); | 744 categories_.erase(category); |
728 } | 745 } |
| 746 |
| 747 StoreCategoriesToPrefs(); |
729 } | 748 } |
730 | 749 |
731 void NTPSnippetsService::OnSnippetImageFetchedFromDatabase( | 750 void NTPSnippetsService::OnSnippetImageFetchedFromDatabase( |
732 const ImageFetchedCallback& callback, | 751 const ImageFetchedCallback& callback, |
733 const ContentSuggestion::ID& suggestion_id, | 752 const ContentSuggestion::ID& suggestion_id, |
734 std::string data) { | 753 std::string data) { |
735 // |image_decoder_| is null in tests. | 754 // |image_decoder_| is null in tests. |
736 if (image_decoder_ && !data.empty()) { | 755 if (image_decoder_ && !data.empty()) { |
737 image_decoder_->DecodeImage( | 756 image_decoder_->DecodeImage( |
738 data, base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromDatabase, | 757 data, base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromDatabase, |
(...skipping 16 matching lines...) Expand all Loading... |
755 | 774 |
756 // If decoding the image failed, delete the DB entry. | 775 // If decoding the image failed, delete the DB entry. |
757 database_->DeleteImage(suggestion_id.id_within_category()); | 776 database_->DeleteImage(suggestion_id.id_within_category()); |
758 | 777 |
759 FetchSnippetImageFromNetwork(suggestion_id, callback); | 778 FetchSnippetImageFromNetwork(suggestion_id, callback); |
760 } | 779 } |
761 | 780 |
762 void NTPSnippetsService::FetchSnippetImageFromNetwork( | 781 void NTPSnippetsService::FetchSnippetImageFromNetwork( |
763 const ContentSuggestion::ID& suggestion_id, | 782 const ContentSuggestion::ID& suggestion_id, |
764 const ImageFetchedCallback& callback) { | 783 const ImageFetchedCallback& callback) { |
765 if (categories_.find(suggestion_id.category()) == categories_.end()) { | 784 if (!base::ContainsKey(categories_, suggestion_id.category())) { |
766 OnSnippetImageDecodedFromNetwork( | 785 OnSnippetImageDecodedFromNetwork( |
767 callback, suggestion_id.id_within_category(), gfx::Image()); | 786 callback, suggestion_id.id_within_category(), gfx::Image()); |
768 return; | 787 return; |
769 } | 788 } |
770 | 789 |
771 GURL image_url = FindSnippetImageUrl(suggestion_id); | 790 GURL image_url = FindSnippetImageUrl(suggestion_id); |
772 | 791 |
773 if (image_url.is_empty() || | 792 if (image_url.is_empty() || |
774 !thumbnail_requests_throttler_.DemandQuotaForRequest( | 793 !thumbnail_requests_throttler_.DemandQuotaForRequest( |
775 /*interactive_request=*/true)) { | 794 /*interactive_request=*/true)) { |
(...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
960 } | 979 } |
961 | 980 |
962 DVLOG(1) << "NotifyNewSuggestions(): " << result.size() | 981 DVLOG(1) << "NotifyNewSuggestions(): " << result.size() |
963 << " items in category " << category; | 982 << " items in category " << category; |
964 observer()->OnNewSuggestions(this, category, std::move(result)); | 983 observer()->OnNewSuggestions(this, category, std::move(result)); |
965 } | 984 } |
966 } | 985 } |
967 | 986 |
968 void NTPSnippetsService::UpdateCategoryStatus(Category category, | 987 void NTPSnippetsService::UpdateCategoryStatus(Category category, |
969 CategoryStatus status) { | 988 CategoryStatus status) { |
970 DCHECK(categories_.find(category) != categories_.end()); | 989 DCHECK(base::ContainsKey(categories_, category)); |
971 CategoryContent& content = categories_[category]; | 990 CategoryContent& content = categories_[category]; |
972 if (status == content.status) | 991 if (status == content.status) |
973 return; | 992 return; |
974 | 993 |
975 DVLOG(1) << "UpdateCategoryStatus(): " << category.id() << ": " | 994 DVLOG(1) << "UpdateCategoryStatus(): " << category.id() << ": " |
976 << static_cast<int>(content.status) << " -> " | 995 << static_cast<int>(content.status) << " -> " |
977 << static_cast<int>(status); | 996 << static_cast<int>(status); |
978 content.status = status; | 997 content.status = status; |
979 observer()->OnCategoryStatusChanged(this, category, content.status); | 998 observer()->OnCategoryStatusChanged(this, category, content.status); |
980 } | 999 } |
(...skipping 19 matching lines...) Expand all Loading... |
1000 archived.begin(), archived.end(), | 1019 archived.begin(), archived.end(), |
1001 [&id_within_category](const std::unique_ptr<NTPSnippet>& snippet) { | 1020 [&id_within_category](const std::unique_ptr<NTPSnippet>& snippet) { |
1002 return snippet->id() == id_within_category; | 1021 return snippet->id() == id_within_category; |
1003 }); | 1022 }); |
1004 if (it != archived.end()) | 1023 if (it != archived.end()) |
1005 return it->get(); | 1024 return it->get(); |
1006 | 1025 |
1007 return nullptr; | 1026 return nullptr; |
1008 } | 1027 } |
1009 | 1028 |
| 1029 namespace { |
| 1030 |
| 1031 const char kCategoryContentId[] = "id"; |
| 1032 const char kCategoryContentTitle[] = "title"; |
| 1033 const char kCategoryContentProvidedByServer[] = "provided_by_server"; |
| 1034 |
| 1035 } // namespace |
| 1036 |
| 1037 void NTPSnippetsService::RestoreCategoriesFromPrefs() { |
| 1038 // This must only be called at startup, before there are any categories. |
| 1039 DCHECK(categories_.empty()); |
| 1040 |
| 1041 const base::ListValue* list = |
| 1042 pref_service_->GetList(prefs::kRemoteSuggestionCategories); |
| 1043 for (const std::unique_ptr<base::Value>& entry : *list) { |
| 1044 const base::DictionaryValue* dict = nullptr; |
| 1045 if (!entry->GetAsDictionary(&dict)) { |
| 1046 DLOG(WARNING) << "Invalid category pref value: " << *entry; |
| 1047 continue; |
| 1048 } |
| 1049 int id = 0; |
| 1050 if (!dict->GetInteger(kCategoryContentId, &id)) { |
| 1051 DLOG(WARNING) << "Invalid category pref value, missing '" |
| 1052 << kCategoryContentId << "': " << *entry; |
| 1053 continue; |
| 1054 } |
| 1055 base::string16 title; |
| 1056 if (!dict->GetString(kCategoryContentTitle, &title)) { |
| 1057 DLOG(WARNING) << "Invalid category pref value, missing '" |
| 1058 << kCategoryContentTitle << "': " << *entry; |
| 1059 continue; |
| 1060 } |
| 1061 bool provided_by_server = false; |
| 1062 if (!dict->GetBoolean(kCategoryContentProvidedByServer, |
| 1063 &provided_by_server)) { |
| 1064 DLOG(WARNING) << "Invalid category pref value, missing '" |
| 1065 << kCategoryContentProvidedByServer << "': " << *entry; |
| 1066 continue; |
| 1067 } |
| 1068 |
| 1069 Category category = category_factory()->FromIDValue(id); |
| 1070 categories_[category] = CategoryContent(); |
| 1071 categories_[category].localized_title = title; |
| 1072 categories_[category].provided_by_server = provided_by_server; |
| 1073 } |
| 1074 } |
| 1075 |
| 1076 void NTPSnippetsService::StoreCategoriesToPrefs() { |
| 1077 // Collect all the CategoryContents. |
| 1078 std::vector<std::pair<Category, const CategoryContent*>> to_store; |
| 1079 for (const auto& entry : categories_) |
| 1080 to_store.emplace_back(entry.first, &entry.second); |
| 1081 // Sort them into the proper category order. |
| 1082 std::sort(to_store.begin(), to_store.end(), |
| 1083 [this](const std::pair<Category, const CategoryContent*>& left, |
| 1084 const std::pair<Category, const CategoryContent*>& right) { |
| 1085 return category_factory()->CompareCategories(left.first, |
| 1086 right.first); |
| 1087 }); |
| 1088 // Convert the relevant info into a base::ListValue for storage. |
| 1089 base::ListValue list; |
| 1090 for (const auto& entry : to_store) { |
| 1091 Category category = entry.first; |
| 1092 const base::string16& title = entry.second->localized_title; |
| 1093 bool provided_by_server = entry.second->provided_by_server; |
| 1094 auto dict = base::MakeUnique<base::DictionaryValue>(); |
| 1095 dict->SetInteger(kCategoryContentId, category.id()); |
| 1096 dict->SetString(kCategoryContentTitle, title); |
| 1097 dict->SetBoolean(kCategoryContentProvidedByServer, provided_by_server); |
| 1098 list.Append(std::move(dict)); |
| 1099 } |
| 1100 // Finally, store the result in the pref service. |
| 1101 pref_service_->Set(prefs::kRemoteSuggestionCategories, list); |
| 1102 } |
| 1103 |
1010 NTPSnippetsService::CategoryContent::CategoryContent() = default; | 1104 NTPSnippetsService::CategoryContent::CategoryContent() = default; |
1011 NTPSnippetsService::CategoryContent::CategoryContent(CategoryContent&&) = | 1105 NTPSnippetsService::CategoryContent::CategoryContent(CategoryContent&&) = |
1012 default; | 1106 default; |
1013 NTPSnippetsService::CategoryContent::~CategoryContent() = default; | 1107 NTPSnippetsService::CategoryContent::~CategoryContent() = default; |
1014 NTPSnippetsService::CategoryContent& NTPSnippetsService::CategoryContent:: | 1108 NTPSnippetsService::CategoryContent& NTPSnippetsService::CategoryContent:: |
1015 operator=(CategoryContent&&) = default; | 1109 operator=(CategoryContent&&) = default; |
1016 | 1110 |
1017 } // namespace ntp_snippets | 1111 } // namespace ntp_snippets |
OLD | NEW |