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