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