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/ntp_snippets_service.h" | 5 #include "components/ntp_snippets/ntp_snippets_service.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <iterator> | 8 #include <iterator> |
9 #include <utility> | 9 #include <utility> |
10 | 10 |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
43 using suggestions::SuggestionsService; | 43 using suggestions::SuggestionsService; |
44 | 44 |
45 namespace ntp_snippets { | 45 namespace ntp_snippets { |
46 | 46 |
47 namespace { | 47 namespace { |
48 | 48 |
49 // Number of snippets requested to the server. Consider replacing sparse UMA | 49 // Number of snippets requested to the server. Consider replacing sparse UMA |
50 // histograms with COUNTS() if this number increases beyond 50. | 50 // histograms with COUNTS() if this number increases beyond 50. |
51 const int kMaxSnippetCount = 10; | 51 const int kMaxSnippetCount = 10; |
52 | 52 |
53 // Number of archived snippets we keep around in memory. | |
54 const int kMaxArchivedSnippetCount = 200; | |
55 | |
53 // Default values for snippets fetching intervals - once per day only. | 56 // Default values for snippets fetching intervals - once per day only. |
54 const int kDefaultFetchingIntervalWifiChargingSeconds = 0; | 57 const int kDefaultFetchingIntervalWifiChargingSeconds = 0; |
55 const int kDefaultFetchingIntervalWifiSeconds = 0; | 58 const int kDefaultFetchingIntervalWifiSeconds = 0; |
56 const int kDefaultFetchingIntervalFallbackSeconds = 24 * 60 * 60; | 59 const int kDefaultFetchingIntervalFallbackSeconds = 24 * 60 * 60; |
57 | 60 |
58 // Variation parameters than can override the default fetching intervals. | 61 // Variation parameters than can override the default fetching intervals. |
59 const char kFetchingIntervalWifiChargingParamName[] = | 62 const char kFetchingIntervalWifiChargingParamName[] = |
60 "fetching_interval_wifi_charging_seconds"; | 63 "fetching_interval_wifi_charging_seconds"; |
61 const char kFetchingIntervalWifiParamName[] = | 64 const char kFetchingIntervalWifiParamName[] = |
62 "fetching_interval_wifi_seconds"; | 65 "fetching_interval_wifi_seconds"; |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
170 | 173 |
171 void InsertAllIDs(const NTPSnippet::PtrVector& snippets, | 174 void InsertAllIDs(const NTPSnippet::PtrVector& snippets, |
172 std::set<std::string>* ids) { | 175 std::set<std::string>* ids) { |
173 for (const std::unique_ptr<NTPSnippet>& snippet : snippets) { | 176 for (const std::unique_ptr<NTPSnippet>& snippet : snippets) { |
174 ids->insert(snippet->id()); | 177 ids->insert(snippet->id()); |
175 for (const SnippetSource& source : snippet->sources()) | 178 for (const SnippetSource& source : snippet->sources()) |
176 ids->insert(source.url.spec()); | 179 ids->insert(source.url.spec()); |
177 } | 180 } |
178 } | 181 } |
179 | 182 |
183 bool IsSnippetInSet(const std::unique_ptr<NTPSnippet>& snippet, | |
184 const std::set<std::string>& ids) { | |
185 if (ids.count(snippet->id())) | |
186 return true; | |
187 for (const SnippetSource& source : snippet->sources()) { | |
188 if (ids.count(source.url.spec())) | |
189 return true; | |
190 } | |
191 return false; | |
192 } | |
193 | |
194 void EraseMatchingSnippets( | |
195 NTPSnippet::PtrVector* snippets, | |
Marc Treib
2016/09/22 11:27:06
nit: This can go on the previous line
jkrcal
2016/09/22 12:50:05
Done.
| |
196 const NTPSnippet::PtrVector& matching) { | |
197 std::set<std::string> matching_ids; | |
198 InsertAllIDs(matching, &matching_ids); | |
199 snippets->erase( | |
200 std::remove_if( | |
201 snippets->begin(), snippets->end(), | |
202 [&matching_ids](const std::unique_ptr<NTPSnippet>& snippet) { | |
203 return IsSnippetInSet(snippet, matching_ids); | |
204 }), | |
205 snippets->end()); | |
206 } | |
207 | |
180 void Compact(NTPSnippet::PtrVector* snippets) { | 208 void Compact(NTPSnippet::PtrVector* snippets) { |
181 snippets->erase( | 209 snippets->erase( |
182 std::remove_if( | 210 std::remove_if( |
183 snippets->begin(), snippets->end(), | 211 snippets->begin(), snippets->end(), |
184 [](const std::unique_ptr<NTPSnippet>& snippet) { return !snippet; }), | 212 [](const std::unique_ptr<NTPSnippet>& snippet) { return !snippet; }), |
185 snippets->end()); | 213 snippets->end()); |
186 } | 214 } |
187 | 215 |
188 } // namespace | 216 } // namespace |
189 | 217 |
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
362 void NTPSnippetsService::ClearCachedSuggestions(Category category) { | 390 void NTPSnippetsService::ClearCachedSuggestions(Category category) { |
363 if (!initialized()) | 391 if (!initialized()) |
364 return; | 392 return; |
365 | 393 |
366 if (categories_.find(category) == categories_.end()) | 394 if (categories_.find(category) == categories_.end()) |
367 return; | 395 return; |
368 CategoryContent* content = &categories_[category]; | 396 CategoryContent* content = &categories_[category]; |
369 if (content->snippets.empty()) | 397 if (content->snippets.empty()) |
370 return; | 398 return; |
371 | 399 |
372 if (category == articles_category_) | 400 if (category == articles_category_) { |
373 database_->DeleteSnippets(content->snippets); | 401 database_->DeleteSnippets(content->snippets); |
402 database_->DeleteImages(content->snippets); | |
403 } | |
374 content->snippets.clear(); | 404 content->snippets.clear(); |
375 | 405 |
376 NotifyNewSuggestions(); | 406 NotifyNewSuggestions(); |
377 } | 407 } |
378 | 408 |
379 void NTPSnippetsService::GetDismissedSuggestionsForDebugging( | 409 void NTPSnippetsService::GetDismissedSuggestionsForDebugging( |
380 Category category, | 410 Category category, |
381 const DismissedSuggestionsCallback& callback) { | 411 const DismissedSuggestionsCallback& callback) { |
382 DCHECK(categories_.find(category) != categories_.end()); | 412 DCHECK(categories_.find(category) != categories_.end()); |
383 | 413 |
(...skipping 20 matching lines...) Expand all Loading... | |
404 Category category) { | 434 Category category) { |
405 DCHECK(categories_.find(category) != categories_.end()); | 435 DCHECK(categories_.find(category) != categories_.end()); |
406 | 436 |
407 if (!initialized()) | 437 if (!initialized()) |
408 return; | 438 return; |
409 | 439 |
410 CategoryContent* content = &categories_[category]; | 440 CategoryContent* content = &categories_[category]; |
411 if (content->dismissed.empty()) | 441 if (content->dismissed.empty()) |
412 return; | 442 return; |
413 | 443 |
414 if (category == articles_category_) | 444 if (category == articles_category_) { |
445 // Image gets deleted when each snippet gets dismissed. | |
Marc Treib
2016/09/22 11:27:06
nit: This sounds like something that will happen i
jkrcal
2016/09/22 12:50:05
Done.
| |
415 database_->DeleteSnippets(content->dismissed); | 446 database_->DeleteSnippets(content->dismissed); |
447 } | |
416 content->dismissed.clear(); | 448 content->dismissed.clear(); |
417 } | 449 } |
418 | 450 |
419 std::set<std::string> NTPSnippetsService::GetSuggestionsHosts() const { | 451 std::set<std::string> NTPSnippetsService::GetSuggestionsHosts() const { |
420 // |suggestions_service_| can be null in tests. | 452 // |suggestions_service_| can be null in tests. |
421 if (!suggestions_service_) | 453 if (!suggestions_service_) |
422 return std::set<std::string>(); | 454 return std::set<std::string>(); |
423 | 455 |
424 // TODO(treib): This should just call GetSnippetHostsFromPrefs. | 456 // TODO(treib): This should just call GetSnippetHostsFromPrefs. |
425 return GetSuggestionsHostsImpl( | 457 return GetSuggestionsHostsImpl( |
426 suggestions_service_->GetSuggestionsDataFromCache()); | 458 suggestions_service_->GetSuggestionsDataFromCache()); |
427 } | 459 } |
428 | 460 |
429 // static | 461 // static |
430 int NTPSnippetsService::GetMaxSnippetCountForTesting() { | 462 int NTPSnippetsService::GetMaxSnippetCountForTesting() { |
431 return kMaxSnippetCount; | 463 return kMaxSnippetCount; |
432 } | 464 } |
433 | 465 |
434 //////////////////////////////////////////////////////////////////////////////// | 466 //////////////////////////////////////////////////////////////////////////////// |
435 // Private methods | 467 // Private methods |
436 | 468 |
469 GURL NTPSnippetsService::FindSnippetImageUrl( | |
470 Category category, | |
471 const std::string& snippet_id) const { | |
472 DCHECK(categories_.find(category) != categories_.end()); | |
473 | |
474 const CategoryContent& content = categories_.at(category); | |
475 // Search for the snippet in current and archived snippets. | |
476 auto it = | |
477 std::find_if(content.snippets.begin(), content.snippets.end(), | |
478 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { | |
479 return snippet->id() == snippet_id; | |
480 }); | |
481 if (it != content.snippets.end()) | |
482 return (*it)->salient_image_url(); | |
483 | |
484 it = std::find_if(content.archived.begin(), content.archived.end(), | |
485 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { | |
486 return snippet->id() == snippet_id; | |
487 }); | |
488 if (it != content.archived.end()) | |
489 return (*it)->salient_image_url(); | |
490 | |
491 return GURL(); | |
492 } | |
493 | |
437 // image_fetcher::ImageFetcherDelegate implementation. | 494 // image_fetcher::ImageFetcherDelegate implementation. |
438 void NTPSnippetsService::OnImageDataFetched(const std::string& suggestion_id, | 495 void NTPSnippetsService::OnImageDataFetched(const std::string& suggestion_id, |
439 const std::string& image_data) { | 496 const std::string& image_data) { |
440 if (image_data.empty()) | 497 if (image_data.empty()) |
441 return; | 498 return; |
442 | 499 |
443 Category category = GetCategoryFromUniqueID(suggestion_id); | 500 Category category = GetCategoryFromUniqueID(suggestion_id); |
444 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); | 501 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); |
445 | 502 |
446 auto category_it = categories_.find(category); | 503 if (categories_.find(category) == categories_.end()) |
447 if (category_it == categories_.end()) | |
448 return; | 504 return; |
449 | 505 |
450 const CategoryContent& content = category_it->second; | |
451 | |
452 // Only save the image if the corresponding snippet still exists. | 506 // Only save the image if the corresponding snippet still exists. |
453 auto it = | 507 if (FindSnippetImageUrl(category, snippet_id).is_empty()) |
454 std::find_if(content.snippets.begin(), content.snippets.end(), | |
455 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { | |
456 return snippet->id() == snippet_id; | |
457 }); | |
458 if (it == content.snippets.end()) | |
459 return; | 508 return; |
460 | 509 |
510 // Only cache the data in the DB, the actual serving is done in the callback | |
511 // provided to |image_fetcher_| (OnSnippetImageDecodedFromNetwork()). | |
461 database_->SaveImage(snippet_id, image_data); | 512 database_->SaveImage(snippet_id, image_data); |
462 } | 513 } |
463 | 514 |
464 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { | 515 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { |
465 if (state_ == State::ERROR_OCCURRED) | 516 if (state_ == State::ERROR_OCCURRED) |
466 return; | 517 return; |
467 DCHECK(state_ == State::NOT_INITED); | 518 DCHECK(state_ == State::NOT_INITED); |
468 DCHECK(categories_.size() == 1); // Only articles category, so far. | 519 DCHECK_EQ(1u, categories_.size()); // Only articles category, so far. |
469 DCHECK(categories_.find(articles_category_) != categories_.end()); | 520 DCHECK(categories_.find(articles_category_) != categories_.end()); |
470 | 521 |
471 // TODO(sfiera): support non-article categories in database. | 522 // TODO(sfiera): support non-article categories in database. |
472 CategoryContent* content = &categories_[articles_category_]; | 523 CategoryContent* content = &categories_[articles_category_]; |
473 for (std::unique_ptr<NTPSnippet>& snippet : snippets) { | 524 for (std::unique_ptr<NTPSnippet>& snippet : snippets) { |
474 if (snippet->is_dismissed()) | 525 if (snippet->is_dismissed()) |
475 content->dismissed.emplace_back(std::move(snippet)); | 526 content->dismissed.emplace_back(std::move(snippet)); |
476 else | 527 else |
477 content->snippets.emplace_back(std::move(snippet)); | 528 content->snippets.emplace_back(std::move(snippet)); |
478 } | 529 } |
479 | 530 |
480 std::sort(content->snippets.begin(), content->snippets.end(), | 531 std::sort(content->snippets.begin(), content->snippets.end(), |
481 [](const std::unique_ptr<NTPSnippet>& lhs, | 532 [](const std::unique_ptr<NTPSnippet>& lhs, |
482 const std::unique_ptr<NTPSnippet>& rhs) { | 533 const std::unique_ptr<NTPSnippet>& rhs) { |
483 return lhs->score() > rhs->score(); | 534 return lhs->score() > rhs->score(); |
484 }); | 535 }); |
485 | 536 |
486 ClearExpiredSnippets(); | 537 ClearExpiredDismissedSnippets(); |
538 ClearOrphanedImages(); | |
487 FinishInitialization(); | 539 FinishInitialization(); |
488 } | 540 } |
489 | 541 |
490 void NTPSnippetsService::OnDatabaseError() { | 542 void NTPSnippetsService::OnDatabaseError() { |
491 EnterState(State::ERROR_OCCURRED); | 543 EnterState(State::ERROR_OCCURRED); |
492 UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR); | 544 UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR); |
493 } | 545 } |
494 | 546 |
495 // TODO(dgn): name clash between content suggestions and suggestions hosts. | 547 // TODO(dgn): name clash between content suggestions and suggestions hosts. |
496 // method name should be changed. | 548 // method name should be changed. |
(...skipping 14 matching lines...) Expand all Loading... | |
511 // and apply the same logic to them here. Maybe never? | 563 // and apply the same logic to them here. Maybe never? |
512 // | 564 // |
513 // First, move them over into |to_delete|. | 565 // First, move them over into |to_delete|. |
514 CategoryContent* content = &categories_[articles_category_]; | 566 CategoryContent* content = &categories_[articles_category_]; |
515 NTPSnippet::PtrVector to_delete; | 567 NTPSnippet::PtrVector to_delete; |
516 for (std::unique_ptr<NTPSnippet>& snippet : content->snippets) { | 568 for (std::unique_ptr<NTPSnippet>& snippet : content->snippets) { |
517 if (!hosts.count(snippet->best_source().url.host())) | 569 if (!hosts.count(snippet->best_source().url.host())) |
518 to_delete.emplace_back(std::move(snippet)); | 570 to_delete.emplace_back(std::move(snippet)); |
519 } | 571 } |
520 Compact(&content->snippets); | 572 Compact(&content->snippets); |
521 // Then delete the removed snippets from the database. | 573 ArchiveSnippets(articles_category_, &to_delete); |
522 database_->DeleteSnippets(to_delete); | |
523 | 574 |
524 StoreSnippetHostsToPrefs(hosts); | 575 StoreSnippetHostsToPrefs(hosts); |
525 | 576 |
526 // We removed some suggestions, so we want to let the client know about that. | 577 // We removed some suggestions, so we want to let the client know about that. |
527 // The fetch might take a long time or not complete so we don't want to wait | 578 // The fetch might take a long time or not complete so we don't want to wait |
528 // for its callback. | 579 // for its callback. |
529 NotifyNewSuggestions(); | 580 NotifyNewSuggestions(); |
530 | 581 |
531 FetchSnippetsFromHosts(hosts, /*force_request=*/false); | 582 FetchSnippetsFromHosts(hosts, /*force_request=*/false); |
532 } | 583 } |
533 | 584 |
534 void NTPSnippetsService::OnFetchFinished( | 585 void NTPSnippetsService::OnFetchFinished( |
535 NTPSnippetsFetcher::OptionalSnippets snippets) { | 586 NTPSnippetsFetcher::OptionalSnippets snippets) { |
536 if (!ready()) | 587 if (!ready()) |
537 return; | 588 return; |
538 | 589 |
539 for (auto& item : categories_) { | 590 for (auto& item : categories_) { |
540 CategoryContent* content = &item.second; | 591 CategoryContent* content = &item.second; |
541 content->provided_by_server = false; | 592 content->provided_by_server = false; |
542 } | 593 } |
543 | 594 |
595 // Clear up expired dismissed snippets before we use them to filter new ones. | |
596 ClearExpiredDismissedSnippets(); | |
597 | |
544 // If snippets were fetched successfully, update our |categories_| from each | 598 // If snippets were fetched successfully, update our |categories_| from each |
545 // category provided by the server. | 599 // category provided by the server. |
546 if (snippets) { | 600 if (snippets) { |
601 // TODO(jkrcal): A bit hard to understand with so many variables called | |
602 // "*categor*". Isn't here some room for simplification? | |
547 for (NTPSnippetsFetcher::FetchedCategory& fetched_category : *snippets) { | 603 for (NTPSnippetsFetcher::FetchedCategory& fetched_category : *snippets) { |
548 Category category = fetched_category.category; | 604 Category category = fetched_category.category; |
549 | 605 |
550 // TODO(sfiera): Avoid hard-coding articles category checks in so many | 606 // TODO(sfiera): Avoid hard-coding articles category checks in so many |
551 // places. | 607 // places. |
552 if (category != articles_category_) { | 608 if (category != articles_category_) { |
553 // Only update titles from server-side provided categories. | 609 // Only update titles from server-side provided categories. |
554 categories_[category].localized_title = | 610 categories_[category].localized_title = |
555 fetched_category.localized_title; | 611 fetched_category.localized_title; |
556 } | 612 } |
613 categories_[category].provided_by_server = true; | |
557 | 614 |
558 DCHECK_LE(snippets->size(), static_cast<size_t>(kMaxSnippetCount)); | 615 DCHECK_LE(snippets->size(), static_cast<size_t>(kMaxSnippetCount)); |
559 // TODO(sfiera): histograms for server categories. | 616 // TODO(sfiera): histograms for server categories. |
560 // Sparse histogram used because the number of snippets is small (bound by | 617 // Sparse histogram used because the number of snippets is small (bound by |
561 // kMaxSnippetCount). | 618 // kMaxSnippetCount). |
562 if (category == articles_category_) { | 619 if (category == articles_category_) { |
563 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticlesFetched", | 620 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticlesFetched", |
564 fetched_category.snippets.size()); | 621 fetched_category.snippets.size()); |
565 } | 622 } |
566 | 623 |
567 MergeSnippets(category, std::move(fetched_category.snippets)); | 624 ReplaceSnippets(category, std::move(fetched_category.snippets)); |
568 | |
569 // If there are more snippets than we want to show, delete the extra ones. | |
570 CategoryContent* content = &categories_[category]; | |
571 content->provided_by_server = true; | |
572 if (content->snippets.size() > kMaxSnippetCount) { | |
573 NTPSnippet::PtrVector to_delete( | |
574 std::make_move_iterator(content->snippets.begin() + | |
575 kMaxSnippetCount), | |
576 std::make_move_iterator(content->snippets.end())); | |
577 content->snippets.resize(kMaxSnippetCount); | |
578 if (category == articles_category_) | |
579 database_->DeleteSnippets(to_delete); | |
580 } | |
581 } | 625 } |
582 } | 626 } |
583 | 627 |
584 // Trigger expiration. This probably won't expire any current snippets (old | |
585 // ones should have already been expired by the timer, and new ones shouldn't | |
586 // have expired yet), but it will update the timer for the next run. | |
587 ClearExpiredSnippets(); | |
588 | |
589 for (const auto& item : categories_) { | 628 for (const auto& item : categories_) { |
590 Category category = item.first; | 629 Category category = item.first; |
591 UpdateCategoryStatus(category, CategoryStatus::AVAILABLE); | 630 UpdateCategoryStatus(category, CategoryStatus::AVAILABLE); |
592 } | 631 } |
593 | 632 |
594 // TODO(sfiera): equivalent metrics for non-articles. | 633 // TODO(sfiera): equivalent metrics for non-articles. |
595 const CategoryContent& content = categories_[articles_category_]; | 634 const CategoryContent& content = categories_[articles_category_]; |
596 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles", | 635 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles", |
597 content.snippets.size()); | 636 content.snippets.size()); |
598 if (content.snippets.empty() && !content.dismissed.empty()) { | 637 if (content.snippets.empty() && !content.dismissed.empty()) { |
599 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded", | 638 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded", |
600 content.dismissed.size()); | 639 content.dismissed.size()); |
601 } | 640 } |
602 | 641 |
603 // TODO(sfiera): notify only when a category changed above. | 642 // TODO(sfiera): notify only when a category changed above. |
604 NotifyNewSuggestions(); | 643 NotifyNewSuggestions(); |
605 } | 644 } |
606 | 645 |
607 void NTPSnippetsService::MergeSnippets(Category category, | 646 void NTPSnippetsService::ArchiveSnippets(Category category, |
608 NTPSnippet::PtrVector new_snippets) { | 647 NTPSnippet::PtrVector* to_archive) { |
648 CategoryContent* content = &categories_[category]; | |
649 | |
650 // TODO(sfiera): handle DB for non-articles too. | |
651 if (category == articles_category_) { | |
652 database_->DeleteSnippets(*to_archive); | |
653 // TODO(jkrcal): remove when orphaned images are deleted at start. | |
654 // crbug.com/649009 | |
655 database_->DeleteImages(*to_archive); | |
Marc Treib
2016/09/22 11:27:06
Hm. So when there is an open NTP that has a snippe
jkrcal
2016/09/22 12:50:05
Okay :) Removed the line.
| |
656 } | |
657 | |
658 // Archive previous snippets - move them at the beginning of the list. | |
659 content->archived.insert(content->archived.begin(), | |
660 std::make_move_iterator(to_archive->begin()), | |
661 std::make_move_iterator(to_archive->end())); | |
662 Compact(to_archive); | |
663 | |
664 // If there are more archived snippets than we want to keep, delete the | |
665 // oldest ones by their fetch time (which are always in the back). | |
666 if (content->archived.size() > kMaxArchivedSnippetCount) { | |
667 NTPSnippet::PtrVector to_delete( | |
668 std::make_move_iterator(content->archived.begin() + | |
669 kMaxArchivedSnippetCount), | |
670 std::make_move_iterator(content->archived.end())); | |
671 content->archived.resize(kMaxArchivedSnippetCount); | |
672 if (category == articles_category_) | |
673 database_->DeleteImages(to_delete); | |
674 } | |
675 } | |
676 | |
677 void NTPSnippetsService::ReplaceSnippets(Category category, | |
678 NTPSnippet::PtrVector new_snippets) { | |
609 DCHECK(ready()); | 679 DCHECK(ready()); |
610 CategoryContent* content = &categories_[category]; | 680 CategoryContent* content = &categories_[category]; |
611 | 681 |
612 // Remove new snippets that we already have, or that have been dismissed. | 682 // Remove new snippets that have been dismissed. |
613 std::set<std::string> old_snippet_ids; | 683 EraseMatchingSnippets(&new_snippets, content->dismissed); |
614 InsertAllIDs(content->dismissed, &old_snippet_ids); | |
615 InsertAllIDs(content->snippets, &old_snippet_ids); | |
616 new_snippets.erase( | |
617 std::remove_if( | |
618 new_snippets.begin(), new_snippets.end(), | |
619 [&old_snippet_ids](const std::unique_ptr<NTPSnippet>& snippet) { | |
620 if (old_snippet_ids.count(snippet->id())) | |
621 return true; | |
622 for (const SnippetSource& source : snippet->sources()) { | |
623 if (old_snippet_ids.count(source.url.spec())) | |
624 return true; | |
625 } | |
626 return false; | |
627 }), | |
628 new_snippets.end()); | |
629 | 684 |
630 // Fill in default publish/expiry dates where required. | 685 // Fill in default publish/expiry dates where required. |
631 for (std::unique_ptr<NTPSnippet>& snippet : new_snippets) { | 686 for (std::unique_ptr<NTPSnippet>& snippet : new_snippets) { |
632 if (snippet->publish_date().is_null()) | 687 if (snippet->publish_date().is_null()) |
633 snippet->set_publish_date(base::Time::Now()); | 688 snippet->set_publish_date(base::Time::Now()); |
634 if (snippet->expiry_date().is_null()) { | 689 if (snippet->expiry_date().is_null()) { |
635 snippet->set_expiry_date( | 690 snippet->set_expiry_date( |
636 snippet->publish_date() + | 691 snippet->publish_date() + |
637 base::TimeDelta::FromMinutes(kDefaultExpiryTimeMins)); | 692 base::TimeDelta::FromMinutes(kDefaultExpiryTimeMins)); |
638 } | 693 } |
(...skipping 14 matching lines...) Expand all Loading... | |
653 new_snippets.end()); | 708 new_snippets.end()); |
654 int num_snippets_dismissed = num_new_snippets - new_snippets.size(); | 709 int num_snippets_dismissed = num_new_snippets - new_snippets.size(); |
655 UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch", | 710 UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch", |
656 num_snippets_dismissed > 0); | 711 num_snippets_dismissed > 0); |
657 if (num_snippets_dismissed > 0) { | 712 if (num_snippets_dismissed > 0) { |
658 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets", | 713 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets", |
659 num_snippets_dismissed); | 714 num_snippets_dismissed); |
660 } | 715 } |
661 } | 716 } |
662 | 717 |
663 // Save new articles to the DB. | 718 if (new_snippets.empty()) |
664 // TODO(sfiera): save non-articles to DB too. | 719 return; |
665 if (category == articles_category_) | 720 |
721 // Remove current snippets that have been fetched again. We do not need to | |
722 // archive those as they will be in the new current set. | |
723 EraseMatchingSnippets(&content->snippets, new_snippets); | |
Marc Treib
2016/09/22 11:27:06
Hm. For this purpose, should we only check the pri
jkrcal
2016/09/22 12:50:05
Done.
| |
724 | |
725 ArchiveSnippets(category, &content->snippets); | |
726 | |
727 // TODO(sfiera): handle DB for non-articles too. | |
728 if (category == articles_category_) { | |
729 // Save new articles to the DB. | |
666 database_->SaveSnippets(new_snippets); | 730 database_->SaveSnippets(new_snippets); |
731 } | |
667 | 732 |
668 // Insert the new snippets at the front. | 733 // Insert new snippets. |
669 content->snippets.insert(content->snippets.begin(), | 734 content->snippets.insert(content->snippets.begin(), |
670 std::make_move_iterator(new_snippets.begin()), | 735 std::make_move_iterator(new_snippets.begin()), |
671 std::make_move_iterator(new_snippets.end())); | 736 std::make_move_iterator(new_snippets.end())); |
Marc Treib
2016/09/22 11:27:06
Would
content->snippets = std::move(new_snippets);
jkrcal
2016/09/22 12:50:05
Done.
| |
672 } | 737 } |
673 | 738 |
674 std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const { | 739 std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const { |
675 std::set<std::string> hosts; | 740 std::set<std::string> hosts; |
676 const base::ListValue* list = pref_service_->GetList(prefs::kSnippetHosts); | 741 const base::ListValue* list = pref_service_->GetList(prefs::kSnippetHosts); |
677 for (const auto& value : *list) { | 742 for (const auto& value : *list) { |
678 std::string str; | 743 std::string str; |
679 bool success = value->GetAsString(&str); | 744 bool success = value->GetAsString(&str); |
680 DCHECK(success) << "Failed to parse snippet host from prefs"; | 745 DCHECK(success) << "Failed to parse snippet host from prefs"; |
681 hosts.insert(std::move(str)); | 746 hosts.insert(std::move(str)); |
682 } | 747 } |
683 return hosts; | 748 return hosts; |
684 } | 749 } |
685 | 750 |
686 void NTPSnippetsService::StoreSnippetHostsToPrefs( | 751 void NTPSnippetsService::StoreSnippetHostsToPrefs( |
687 const std::set<std::string>& hosts) { | 752 const std::set<std::string>& hosts) { |
688 base::ListValue list; | 753 base::ListValue list; |
689 for (const std::string& host : hosts) | 754 for (const std::string& host : hosts) |
690 list.AppendString(host); | 755 list.AppendString(host); |
691 pref_service_->Set(prefs::kSnippetHosts, list); | 756 pref_service_->Set(prefs::kSnippetHosts, list); |
692 } | 757 } |
693 | 758 |
694 void NTPSnippetsService::ClearExpiredSnippets() { | 759 void NTPSnippetsService::ClearExpiredDismissedSnippets() { |
695 std::vector<Category> categories_to_erase; | 760 std::vector<Category> categories_to_erase; |
696 | 761 |
697 const base::Time expiry = base::Time::Now(); | 762 const base::Time now = base::Time::Now(); |
698 base::Time next_expiry = base::Time::Max(); | |
699 | 763 |
700 for (auto& item : categories_) { | 764 for (auto& item : categories_) { |
701 Category category = item.first; | 765 Category category = item.first; |
702 CategoryContent* content = &item.second; | 766 CategoryContent* content = &item.second; |
703 | 767 |
704 // Move expired snippets over into |to_delete|. | |
705 NTPSnippet::PtrVector to_delete; | 768 NTPSnippet::PtrVector to_delete; |
706 for (std::unique_ptr<NTPSnippet>& snippet : content->snippets) { | 769 // Move expired dismissed snippets over into |to_delete|. |
707 if (snippet->expiry_date() <= expiry) | |
708 to_delete.emplace_back(std::move(snippet)); | |
709 } | |
710 Compact(&content->snippets); | |
711 | |
712 // Move expired dismissed snippets over into |to_delete| as well. | |
713 for (std::unique_ptr<NTPSnippet>& snippet : content->dismissed) { | 770 for (std::unique_ptr<NTPSnippet>& snippet : content->dismissed) { |
714 if (snippet->expiry_date() <= expiry) | 771 if (snippet->expiry_date() <= now) |
715 to_delete.emplace_back(std::move(snippet)); | 772 to_delete.emplace_back(std::move(snippet)); |
716 } | 773 } |
717 Compact(&content->dismissed); | 774 Compact(&content->dismissed); |
718 | 775 |
719 // Finally, actually delete the removed snippets from the DB. | 776 // Delete the removed article suggestions from the DB. |
720 if (category == articles_category_) | 777 if (category == articles_category_) { |
778 // Image gets deleted when each snippet gets dismissed. | |
Marc Treib
2016/09/22 11:27:06
Same as the identical comment above :)
jkrcal
2016/09/22 12:50:05
Done.
| |
721 database_->DeleteSnippets(to_delete); | 779 database_->DeleteSnippets(to_delete); |
780 } | |
722 | 781 |
723 if (content->snippets.empty() && content->dismissed.empty()) { | 782 if (content->snippets.empty() && content->dismissed.empty()) { |
724 if ((category != articles_category_) && !content->provided_by_server) | 783 if ((category != articles_category_) && !content->provided_by_server) |
Marc Treib
2016/09/22 11:27:06
nit: Now you could merge the two "if"s
jkrcal
2016/09/22 12:50:05
Done.
| |
725 categories_to_erase.push_back(category); | 784 categories_to_erase.push_back(category); |
726 continue; | |
727 } | |
728 | |
729 for (const auto& snippet : content->snippets) { | |
730 if (snippet->expiry_date() < next_expiry) | |
731 next_expiry = snippet->expiry_date(); | |
732 } | |
733 for (const auto& snippet : content->dismissed) { | |
734 if (snippet->expiry_date() < next_expiry) | |
735 next_expiry = snippet->expiry_date(); | |
736 } | 785 } |
737 } | 786 } |
738 | 787 |
739 for (Category category : categories_to_erase) { | 788 for (Category category : categories_to_erase) { |
740 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED); | 789 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED); |
741 categories_.erase(category); | 790 categories_.erase(category); |
742 } | 791 } |
792 } | |
743 | 793 |
744 // Unless there are no snippets left, schedule a timer for the next expiry. | 794 void NTPSnippetsService::ClearOrphanedImages() { |
745 DCHECK_GT(next_expiry, expiry); | 795 // TODO(jkrcal): Implement. crbug.com/649009 |
746 if (next_expiry < base::Time::Max()) { | |
747 expiry_timer_.Start(FROM_HERE, next_expiry - expiry, | |
748 base::Bind(&NTPSnippetsService::ClearExpiredSnippets, | |
749 base::Unretained(this))); | |
750 } | |
751 } | 796 } |
752 | 797 |
753 void NTPSnippetsService::NukeAllSnippets() { | 798 void NTPSnippetsService::NukeAllSnippets() { |
754 std::vector<Category> categories_to_erase; | 799 std::vector<Category> categories_to_erase; |
755 | 800 |
756 // Empty the ARTICLES category and remove all others, since they may or may | 801 // Empty the ARTICLES category and remove all others, since they may or may |
757 // not be personalized. | 802 // not be personalized. |
758 for (const auto& item : categories_) { | 803 for (const auto& item : categories_) { |
759 Category category = item.first; | 804 Category category = item.first; |
760 | 805 |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
815 | 860 |
816 FetchSnippetImageFromNetwork(suggestion_id, callback); | 861 FetchSnippetImageFromNetwork(suggestion_id, callback); |
817 } | 862 } |
818 | 863 |
819 void NTPSnippetsService::FetchSnippetImageFromNetwork( | 864 void NTPSnippetsService::FetchSnippetImageFromNetwork( |
820 const std::string& suggestion_id, | 865 const std::string& suggestion_id, |
821 const ImageFetchedCallback& callback) { | 866 const ImageFetchedCallback& callback) { |
822 Category category = GetCategoryFromUniqueID(suggestion_id); | 867 Category category = GetCategoryFromUniqueID(suggestion_id); |
823 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); | 868 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); |
824 | 869 |
825 auto category_it = categories_.find(category); | 870 if (categories_.find(category) == categories_.end()) { |
826 if (category_it == categories_.end()) { | |
827 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image()); | 871 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image()); |
828 return; | 872 return; |
829 } | 873 } |
830 | 874 |
831 const CategoryContent& content = category_it->second; | 875 GURL image_url = FindSnippetImageUrl(category, snippet_id); |
832 auto it = | |
833 std::find_if(content.snippets.begin(), content.snippets.end(), | |
834 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { | |
835 return snippet->id() == snippet_id; | |
836 }); | |
837 | 876 |
838 if (it == content.snippets.end() || | 877 if (image_url.is_empty() || |
839 !thumbnail_requests_throttler_.DemandQuotaForRequest( | 878 !thumbnail_requests_throttler_.DemandQuotaForRequest( |
840 /*interactive_request=*/true)) { | 879 /*interactive_request=*/true)) { |
841 // Return an empty image. Directly, this is never synchronous with the | 880 // Return an empty image. Directly, this is never synchronous with the |
842 // original FetchSuggestionImage() call - an asynchronous database query has | 881 // original FetchSuggestionImage() call - an asynchronous database query has |
843 // happened in the meantime. | 882 // happened in the meantime. |
844 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image()); | 883 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image()); |
845 return; | 884 return; |
846 } | 885 } |
847 | 886 |
848 const NTPSnippet& snippet = *it->get(); | |
849 | |
850 // TODO(jkrcal): We probably should rename OnImageDataFetched() to | |
851 // CacheImageData(). This would document that this is actually independent | |
852 // from the individual fetch-flow. | |
853 // The image fetcher calls OnImageDataFetched() with the raw data (this object | |
854 // is an ImageFetcherDelegate) and then also | |
855 // OnSnippetImageDecodedFromNetwork() after the raw data gets decoded. | |
856 image_fetcher_->StartOrQueueNetworkRequest( | 887 image_fetcher_->StartOrQueueNetworkRequest( |
857 suggestion_id, snippet.salient_image_url(), | 888 suggestion_id, image_url, |
858 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork, | 889 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork, |
859 base::Unretained(this), callback)); | 890 base::Unretained(this), callback)); |
860 } | 891 } |
861 | 892 |
862 void NTPSnippetsService::OnSnippetImageDecodedFromNetwork( | 893 void NTPSnippetsService::OnSnippetImageDecodedFromNetwork( |
863 const ImageFetchedCallback& callback, | 894 const ImageFetchedCallback& callback, |
864 const std::string& suggestion_id, | 895 const std::string& suggestion_id, |
865 const gfx::Image& image) { | 896 const gfx::Image& image) { |
866 callback.Run(image); | 897 callback.Run(image); |
867 } | 898 } |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
900 if (category != articles_category_) { | 931 if (category != articles_category_) { |
901 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED); | 932 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED); |
902 categories_to_erase.push_back(category); | 933 categories_to_erase.push_back(category); |
903 } | 934 } |
904 } | 935 } |
905 | 936 |
906 for (Category category : categories_to_erase) { | 937 for (Category category : categories_to_erase) { |
907 categories_.erase(category); | 938 categories_.erase(category); |
908 } | 939 } |
909 | 940 |
910 expiry_timer_.Stop(); | |
911 suggestions_service_subscription_.reset(); | 941 suggestions_service_subscription_.reset(); |
912 RescheduleFetching(); | 942 RescheduleFetching(); |
913 } | 943 } |
914 | 944 |
915 void NTPSnippetsService::EnterStateError() { | 945 void NTPSnippetsService::EnterStateError() { |
916 expiry_timer_.Stop(); | |
917 suggestions_service_subscription_.reset(); | 946 suggestions_service_subscription_.reset(); |
918 RescheduleFetching(); | 947 RescheduleFetching(); |
919 snippets_status_service_.reset(); | 948 snippets_status_service_.reset(); |
920 } | 949 } |
921 | 950 |
922 void NTPSnippetsService::FinishInitialization() { | 951 void NTPSnippetsService::FinishInitialization() { |
923 if (nuke_after_load_) { | 952 if (nuke_after_load_) { |
924 NukeAllSnippets(); | 953 NukeAllSnippets(); |
925 nuke_after_load_ = false; | 954 nuke_after_load_ = false; |
926 } | 955 } |
(...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1061 } | 1090 } |
1062 | 1091 |
1063 NTPSnippetsService::CategoryContent::CategoryContent() = default; | 1092 NTPSnippetsService::CategoryContent::CategoryContent() = default; |
1064 NTPSnippetsService::CategoryContent::CategoryContent(CategoryContent&&) = | 1093 NTPSnippetsService::CategoryContent::CategoryContent(CategoryContent&&) = |
1065 default; | 1094 default; |
1066 NTPSnippetsService::CategoryContent::~CategoryContent() = default; | 1095 NTPSnippetsService::CategoryContent::~CategoryContent() = default; |
1067 NTPSnippetsService::CategoryContent& NTPSnippetsService::CategoryContent:: | 1096 NTPSnippetsService::CategoryContent& NTPSnippetsService::CategoryContent:: |
1068 operator=(CategoryContent&&) = default; | 1097 operator=(CategoryContent&&) = default; |
1069 | 1098 |
1070 } // namespace ntp_snippets | 1099 } // namespace ntp_snippets |
OLD | NEW |