Chromium Code Reviews| 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 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 161 std::set<std::string> hosts; | 164 std::set<std::string> hosts; |
| 162 for (int i = 0; i < suggestions.suggestions_size(); ++i) { | 165 for (int i = 0; i < suggestions.suggestions_size(); ++i) { |
| 163 const ChromeSuggestion& suggestion = suggestions.suggestions(i); | 166 const ChromeSuggestion& suggestion = suggestions.suggestions(i); |
| 164 GURL url(suggestion.url()); | 167 GURL url(suggestion.url()); |
| 165 if (url.is_valid()) | 168 if (url.is_valid()) |
| 166 hosts.insert(url.host()); | 169 hosts.insert(url.host()); |
| 167 } | 170 } |
| 168 return hosts; | 171 return hosts; |
| 169 } | 172 } |
| 170 | 173 |
| 171 void InsertAllIDs(const NTPSnippet::PtrVector& snippets, | 174 std::set<std::string> GetAllIDs(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 } |
| 181 return ids; | |
| 182 } | |
| 183 | |
| 184 std::set<std::string> GetMainIDs(const NTPSnippet::PtrVector& snippets) { | |
| 185 std::set<std::string> ids; | |
| 186 for (const std::unique_ptr<NTPSnippet>& snippet : snippets) | |
| 187 ids.insert(snippet->id()); | |
| 188 return ids; | |
| 189 } | |
| 190 | |
| 191 bool IsSnippetInSet(const std::unique_ptr<NTPSnippet>& snippet, | |
| 192 const std::set<std::string>& ids, | |
| 193 bool match_all_ids) { | |
| 194 if (ids.count(snippet->id())) | |
| 195 return true; | |
| 196 if (!match_all_ids) | |
| 197 return false; | |
| 198 for (const SnippetSource& source : snippet->sources()) { | |
| 199 if (ids.count(source.url.spec())) | |
| 200 return true; | |
| 201 } | |
| 202 return false; | |
| 203 } | |
| 204 | |
| 205 void EraseMatchingSnippets(NTPSnippet::PtrVector* snippets, | |
| 206 const std::set<std::string>& matching_ids, | |
|
Marc Treib
2016/09/22 13:05:29
optional: You could also pass in the snippets to m
jkrcal
2016/09/22 14:07:58
I think the current way it is even clearer on the
| |
| 207 bool match_all_ids) { | |
| 208 snippets->erase( | |
| 209 std::remove_if(snippets->begin(), snippets->end(), | |
| 210 [&matching_ids, match_all_ids]( | |
| 211 const std::unique_ptr<NTPSnippet>& snippet) { | |
| 212 return IsSnippetInSet(snippet, matching_ids, | |
| 213 match_all_ids); | |
| 214 }), | |
| 215 snippets->end()); | |
| 178 } | 216 } |
| 179 | 217 |
| 180 void Compact(NTPSnippet::PtrVector* snippets) { | 218 void Compact(NTPSnippet::PtrVector* snippets) { |
| 181 snippets->erase( | 219 snippets->erase( |
| 182 std::remove_if( | 220 std::remove_if( |
| 183 snippets->begin(), snippets->end(), | 221 snippets->begin(), snippets->end(), |
| 184 [](const std::unique_ptr<NTPSnippet>& snippet) { return !snippet; }), | 222 [](const std::unique_ptr<NTPSnippet>& snippet) { return !snippet; }), |
| 185 snippets->end()); | 223 snippets->end()); |
| 186 } | 224 } |
| 187 | 225 |
| (...skipping 174 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 362 void NTPSnippetsService::ClearCachedSuggestions(Category category) { | 400 void NTPSnippetsService::ClearCachedSuggestions(Category category) { |
| 363 if (!initialized()) | 401 if (!initialized()) |
| 364 return; | 402 return; |
| 365 | 403 |
| 366 if (categories_.find(category) == categories_.end()) | 404 if (categories_.find(category) == categories_.end()) |
| 367 return; | 405 return; |
| 368 CategoryContent* content = &categories_[category]; | 406 CategoryContent* content = &categories_[category]; |
| 369 if (content->snippets.empty()) | 407 if (content->snippets.empty()) |
| 370 return; | 408 return; |
| 371 | 409 |
| 372 if (category == articles_category_) | 410 if (category == articles_category_) { |
| 373 database_->DeleteSnippets(content->snippets); | 411 database_->DeleteSnippets(content->snippets); |
| 412 database_->DeleteImages(content->snippets); | |
| 413 } | |
| 374 content->snippets.clear(); | 414 content->snippets.clear(); |
| 375 | 415 |
| 376 NotifyNewSuggestions(); | 416 NotifyNewSuggestions(); |
| 377 } | 417 } |
| 378 | 418 |
| 379 void NTPSnippetsService::GetDismissedSuggestionsForDebugging( | 419 void NTPSnippetsService::GetDismissedSuggestionsForDebugging( |
| 380 Category category, | 420 Category category, |
| 381 const DismissedSuggestionsCallback& callback) { | 421 const DismissedSuggestionsCallback& callback) { |
| 382 DCHECK(categories_.find(category) != categories_.end()); | 422 DCHECK(categories_.find(category) != categories_.end()); |
| 383 | 423 |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 404 Category category) { | 444 Category category) { |
| 405 DCHECK(categories_.find(category) != categories_.end()); | 445 DCHECK(categories_.find(category) != categories_.end()); |
| 406 | 446 |
| 407 if (!initialized()) | 447 if (!initialized()) |
| 408 return; | 448 return; |
| 409 | 449 |
| 410 CategoryContent* content = &categories_[category]; | 450 CategoryContent* content = &categories_[category]; |
| 411 if (content->dismissed.empty()) | 451 if (content->dismissed.empty()) |
| 412 return; | 452 return; |
| 413 | 453 |
| 414 if (category == articles_category_) | 454 if (category == articles_category_) { |
| 455 // The image got already deleted when the suggestion was dismissed. | |
| 415 database_->DeleteSnippets(content->dismissed); | 456 database_->DeleteSnippets(content->dismissed); |
| 457 } | |
| 416 content->dismissed.clear(); | 458 content->dismissed.clear(); |
| 417 } | 459 } |
| 418 | 460 |
| 419 std::set<std::string> NTPSnippetsService::GetSuggestionsHosts() const { | 461 std::set<std::string> NTPSnippetsService::GetSuggestionsHosts() const { |
| 420 // |suggestions_service_| can be null in tests. | 462 // |suggestions_service_| can be null in tests. |
| 421 if (!suggestions_service_) | 463 if (!suggestions_service_) |
| 422 return std::set<std::string>(); | 464 return std::set<std::string>(); |
| 423 | 465 |
| 424 // TODO(treib): This should just call GetSnippetHostsFromPrefs. | 466 // TODO(treib): This should just call GetSnippetHostsFromPrefs. |
| 425 return GetSuggestionsHostsImpl( | 467 return GetSuggestionsHostsImpl( |
| 426 suggestions_service_->GetSuggestionsDataFromCache()); | 468 suggestions_service_->GetSuggestionsDataFromCache()); |
| 427 } | 469 } |
| 428 | 470 |
| 429 // static | 471 // static |
| 430 int NTPSnippetsService::GetMaxSnippetCountForTesting() { | 472 int NTPSnippetsService::GetMaxSnippetCountForTesting() { |
| 431 return kMaxSnippetCount; | 473 return kMaxSnippetCount; |
| 432 } | 474 } |
| 433 | 475 |
| 434 //////////////////////////////////////////////////////////////////////////////// | 476 //////////////////////////////////////////////////////////////////////////////// |
| 435 // Private methods | 477 // Private methods |
| 436 | 478 |
| 479 GURL NTPSnippetsService::FindSnippetImageUrl( | |
| 480 Category category, | |
| 481 const std::string& snippet_id) const { | |
| 482 DCHECK(categories_.find(category) != categories_.end()); | |
| 483 | |
| 484 const CategoryContent& content = categories_.at(category); | |
| 485 // Search for the snippet in current and archived snippets. | |
| 486 auto it = | |
| 487 std::find_if(content.snippets.begin(), content.snippets.end(), | |
| 488 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { | |
| 489 return snippet->id() == snippet_id; | |
| 490 }); | |
| 491 if (it != content.snippets.end()) | |
| 492 return (*it)->salient_image_url(); | |
| 493 | |
| 494 it = std::find_if(content.archived.begin(), content.archived.end(), | |
| 495 [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { | |
| 496 return snippet->id() == snippet_id; | |
| 497 }); | |
| 498 if (it != content.archived.end()) | |
| 499 return (*it)->salient_image_url(); | |
| 500 | |
| 501 return GURL(); | |
| 502 } | |
| 503 | |
| 437 // image_fetcher::ImageFetcherDelegate implementation. | 504 // image_fetcher::ImageFetcherDelegate implementation. |
| 438 void NTPSnippetsService::OnImageDataFetched(const std::string& suggestion_id, | 505 void NTPSnippetsService::OnImageDataFetched(const std::string& suggestion_id, |
| 439 const std::string& image_data) { | 506 const std::string& image_data) { |
| 440 if (image_data.empty()) | 507 if (image_data.empty()) |
| 441 return; | 508 return; |
| 442 | 509 |
| 443 Category category = GetCategoryFromUniqueID(suggestion_id); | 510 Category category = GetCategoryFromUniqueID(suggestion_id); |
| 444 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); | 511 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); |
| 445 | 512 |
| 446 auto category_it = categories_.find(category); | 513 if (categories_.find(category) == categories_.end()) |
| 447 if (category_it == categories_.end()) | |
| 448 return; | 514 return; |
| 449 | 515 |
| 450 const CategoryContent& content = category_it->second; | |
| 451 | |
| 452 // Only save the image if the corresponding snippet still exists. | 516 // Only save the image if the corresponding snippet still exists. |
| 453 auto it = | 517 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; | 518 return; |
| 460 | 519 |
| 520 // Only cache the data in the DB, the actual serving is done in the callback | |
| 521 // provided to |image_fetcher_| (OnSnippetImageDecodedFromNetwork()). | |
| 461 database_->SaveImage(snippet_id, image_data); | 522 database_->SaveImage(snippet_id, image_data); |
| 462 } | 523 } |
| 463 | 524 |
| 464 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { | 525 void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { |
| 465 if (state_ == State::ERROR_OCCURRED) | 526 if (state_ == State::ERROR_OCCURRED) |
| 466 return; | 527 return; |
| 467 DCHECK(state_ == State::NOT_INITED); | 528 DCHECK(state_ == State::NOT_INITED); |
| 468 DCHECK(categories_.size() == 1); // Only articles category, so far. | 529 DCHECK_EQ(1u, categories_.size()); // Only articles category, so far. |
| 469 DCHECK(categories_.find(articles_category_) != categories_.end()); | 530 DCHECK(categories_.find(articles_category_) != categories_.end()); |
| 470 | 531 |
| 471 // TODO(sfiera): support non-article categories in database. | 532 // TODO(sfiera): support non-article categories in database. |
| 472 CategoryContent* content = &categories_[articles_category_]; | 533 CategoryContent* content = &categories_[articles_category_]; |
| 473 for (std::unique_ptr<NTPSnippet>& snippet : snippets) { | 534 for (std::unique_ptr<NTPSnippet>& snippet : snippets) { |
| 474 if (snippet->is_dismissed()) | 535 if (snippet->is_dismissed()) |
| 475 content->dismissed.emplace_back(std::move(snippet)); | 536 content->dismissed.emplace_back(std::move(snippet)); |
| 476 else | 537 else |
| 477 content->snippets.emplace_back(std::move(snippet)); | 538 content->snippets.emplace_back(std::move(snippet)); |
| 478 } | 539 } |
| 479 | 540 |
| 480 std::sort(content->snippets.begin(), content->snippets.end(), | 541 std::sort(content->snippets.begin(), content->snippets.end(), |
| 481 [](const std::unique_ptr<NTPSnippet>& lhs, | 542 [](const std::unique_ptr<NTPSnippet>& lhs, |
| 482 const std::unique_ptr<NTPSnippet>& rhs) { | 543 const std::unique_ptr<NTPSnippet>& rhs) { |
| 483 return lhs->score() > rhs->score(); | 544 return lhs->score() > rhs->score(); |
| 484 }); | 545 }); |
| 485 | 546 |
| 486 ClearExpiredSnippets(); | 547 ClearExpiredDismissedSnippets(); |
| 548 ClearOrphanedImages(); | |
| 487 FinishInitialization(); | 549 FinishInitialization(); |
| 488 } | 550 } |
| 489 | 551 |
| 490 void NTPSnippetsService::OnDatabaseError() { | 552 void NTPSnippetsService::OnDatabaseError() { |
| 491 EnterState(State::ERROR_OCCURRED); | 553 EnterState(State::ERROR_OCCURRED); |
| 492 UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR); | 554 UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR); |
| 493 } | 555 } |
| 494 | 556 |
| 495 // TODO(dgn): name clash between content suggestions and suggestions hosts. | 557 // TODO(dgn): name clash between content suggestions and suggestions hosts. |
| 496 // method name should be changed. | 558 // method name should be changed. |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 511 // and apply the same logic to them here. Maybe never? | 573 // and apply the same logic to them here. Maybe never? |
| 512 // | 574 // |
| 513 // First, move them over into |to_delete|. | 575 // First, move them over into |to_delete|. |
| 514 CategoryContent* content = &categories_[articles_category_]; | 576 CategoryContent* content = &categories_[articles_category_]; |
| 515 NTPSnippet::PtrVector to_delete; | 577 NTPSnippet::PtrVector to_delete; |
| 516 for (std::unique_ptr<NTPSnippet>& snippet : content->snippets) { | 578 for (std::unique_ptr<NTPSnippet>& snippet : content->snippets) { |
| 517 if (!hosts.count(snippet->best_source().url.host())) | 579 if (!hosts.count(snippet->best_source().url.host())) |
| 518 to_delete.emplace_back(std::move(snippet)); | 580 to_delete.emplace_back(std::move(snippet)); |
| 519 } | 581 } |
| 520 Compact(&content->snippets); | 582 Compact(&content->snippets); |
| 521 // Then delete the removed snippets from the database. | 583 ArchiveSnippets(articles_category_, &to_delete); |
| 522 database_->DeleteSnippets(to_delete); | |
| 523 | 584 |
| 524 StoreSnippetHostsToPrefs(hosts); | 585 StoreSnippetHostsToPrefs(hosts); |
| 525 | 586 |
| 526 // We removed some suggestions, so we want to let the client know about that. | 587 // 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 | 588 // The fetch might take a long time or not complete so we don't want to wait |
| 528 // for its callback. | 589 // for its callback. |
| 529 NotifyNewSuggestions(); | 590 NotifyNewSuggestions(); |
| 530 | 591 |
| 531 FetchSnippetsFromHosts(hosts, /*force_request=*/false); | 592 FetchSnippetsFromHosts(hosts, /*force_request=*/false); |
| 532 } | 593 } |
| 533 | 594 |
| 534 void NTPSnippetsService::OnFetchFinished( | 595 void NTPSnippetsService::OnFetchFinished( |
| 535 NTPSnippetsFetcher::OptionalSnippets snippets) { | 596 NTPSnippetsFetcher::OptionalSnippets snippets) { |
| 536 if (!ready()) | 597 if (!ready()) |
| 537 return; | 598 return; |
| 538 | 599 |
| 539 for (auto& item : categories_) { | 600 for (auto& item : categories_) { |
| 540 CategoryContent* content = &item.second; | 601 CategoryContent* content = &item.second; |
| 541 content->provided_by_server = false; | 602 content->provided_by_server = false; |
| 542 } | 603 } |
| 543 | 604 |
| 605 // Clear up expired dismissed snippets before we use them to filter new ones. | |
| 606 ClearExpiredDismissedSnippets(); | |
| 607 | |
| 544 // If snippets were fetched successfully, update our |categories_| from each | 608 // If snippets were fetched successfully, update our |categories_| from each |
| 545 // category provided by the server. | 609 // category provided by the server. |
| 546 if (snippets) { | 610 if (snippets) { |
| 611 // TODO(jkrcal): A bit hard to understand with so many variables called | |
| 612 // "*categor*". Isn't here some room for simplification? | |
| 547 for (NTPSnippetsFetcher::FetchedCategory& fetched_category : *snippets) { | 613 for (NTPSnippetsFetcher::FetchedCategory& fetched_category : *snippets) { |
| 548 Category category = fetched_category.category; | 614 Category category = fetched_category.category; |
| 549 | 615 |
| 550 // TODO(sfiera): Avoid hard-coding articles category checks in so many | 616 // TODO(sfiera): Avoid hard-coding articles category checks in so many |
| 551 // places. | 617 // places. |
| 552 if (category != articles_category_) { | 618 if (category != articles_category_) { |
| 553 // Only update titles from server-side provided categories. | 619 // Only update titles from server-side provided categories. |
| 554 categories_[category].localized_title = | 620 categories_[category].localized_title = |
| 555 fetched_category.localized_title; | 621 fetched_category.localized_title; |
| 556 } | 622 } |
| 623 categories_[category].provided_by_server = true; | |
| 557 | 624 |
| 558 DCHECK_LE(snippets->size(), static_cast<size_t>(kMaxSnippetCount)); | 625 DCHECK_LE(snippets->size(), static_cast<size_t>(kMaxSnippetCount)); |
| 559 // TODO(sfiera): histograms for server categories. | 626 // TODO(sfiera): histograms for server categories. |
| 560 // Sparse histogram used because the number of snippets is small (bound by | 627 // Sparse histogram used because the number of snippets is small (bound by |
| 561 // kMaxSnippetCount). | 628 // kMaxSnippetCount). |
| 562 if (category == articles_category_) { | 629 if (category == articles_category_) { |
| 563 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticlesFetched", | 630 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticlesFetched", |
| 564 fetched_category.snippets.size()); | 631 fetched_category.snippets.size()); |
| 565 } | 632 } |
| 566 | 633 |
| 567 MergeSnippets(category, std::move(fetched_category.snippets)); | 634 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 } | 635 } |
| 582 } | 636 } |
| 583 | 637 |
| 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_) { | 638 for (const auto& item : categories_) { |
| 590 Category category = item.first; | 639 Category category = item.first; |
| 591 UpdateCategoryStatus(category, CategoryStatus::AVAILABLE); | 640 UpdateCategoryStatus(category, CategoryStatus::AVAILABLE); |
| 592 } | 641 } |
| 593 | 642 |
| 594 // TODO(sfiera): equivalent metrics for non-articles. | 643 // TODO(sfiera): equivalent metrics for non-articles. |
| 595 const CategoryContent& content = categories_[articles_category_]; | 644 const CategoryContent& content = categories_[articles_category_]; |
| 596 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles", | 645 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles", |
| 597 content.snippets.size()); | 646 content.snippets.size()); |
| 598 if (content.snippets.empty() && !content.dismissed.empty()) { | 647 if (content.snippets.empty() && !content.dismissed.empty()) { |
| 599 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded", | 648 UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded", |
| 600 content.dismissed.size()); | 649 content.dismissed.size()); |
| 601 } | 650 } |
| 602 | 651 |
| 603 // TODO(sfiera): notify only when a category changed above. | 652 // TODO(sfiera): notify only when a category changed above. |
| 604 NotifyNewSuggestions(); | 653 NotifyNewSuggestions(); |
| 605 } | 654 } |
| 606 | 655 |
| 607 void NTPSnippetsService::MergeSnippets(Category category, | 656 void NTPSnippetsService::ArchiveSnippets(Category category, |
| 608 NTPSnippet::PtrVector new_snippets) { | 657 NTPSnippet::PtrVector* to_archive) { |
| 658 CategoryContent* content = &categories_[category]; | |
| 659 | |
| 660 // TODO(sfiera): handle DB for non-articles too. | |
| 661 if (category == articles_category_) { | |
| 662 database_->DeleteSnippets(*to_archive); | |
| 663 // Do not delete the thumbnail images as they are still handy on open NTPs. | |
| 664 } | |
| 665 | |
| 666 // Archive previous snippets - move them at the beginning of the list. | |
| 667 content->archived.insert(content->archived.begin(), | |
| 668 std::make_move_iterator(to_archive->begin()), | |
| 669 std::make_move_iterator(to_archive->end())); | |
| 670 Compact(to_archive); | |
| 671 | |
| 672 // If there are more archived snippets than we want to keep, delete the | |
| 673 // oldest ones by their fetch time (which are always in the back). | |
| 674 if (content->archived.size() > kMaxArchivedSnippetCount) { | |
| 675 NTPSnippet::PtrVector to_delete( | |
| 676 std::make_move_iterator(content->archived.begin() + | |
| 677 kMaxArchivedSnippetCount), | |
| 678 std::make_move_iterator(content->archived.end())); | |
| 679 content->archived.resize(kMaxArchivedSnippetCount); | |
| 680 if (category == articles_category_) | |
| 681 database_->DeleteImages(to_delete); | |
| 682 } | |
| 683 } | |
| 684 | |
| 685 void NTPSnippetsService::ReplaceSnippets(Category category, | |
| 686 NTPSnippet::PtrVector new_snippets) { | |
| 609 DCHECK(ready()); | 687 DCHECK(ready()); |
| 610 CategoryContent* content = &categories_[category]; | 688 CategoryContent* content = &categories_[category]; |
| 611 | 689 |
| 612 // Remove new snippets that we already have, or that have been dismissed. | 690 // Remove new snippets that have been dismissed. |
| 613 std::set<std::string> old_snippet_ids; | 691 EraseMatchingSnippets(&new_snippets, GetAllIDs(content->dismissed), |
| 614 InsertAllIDs(content->dismissed, &old_snippet_ids); | 692 /*match_all_ids=*/true); |
| 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 | 693 |
| 630 // Fill in default publish/expiry dates where required. | 694 // Fill in default publish/expiry dates where required. |
| 631 for (std::unique_ptr<NTPSnippet>& snippet : new_snippets) { | 695 for (std::unique_ptr<NTPSnippet>& snippet : new_snippets) { |
| 632 if (snippet->publish_date().is_null()) | 696 if (snippet->publish_date().is_null()) |
| 633 snippet->set_publish_date(base::Time::Now()); | 697 snippet->set_publish_date(base::Time::Now()); |
| 634 if (snippet->expiry_date().is_null()) { | 698 if (snippet->expiry_date().is_null()) { |
| 635 snippet->set_expiry_date( | 699 snippet->set_expiry_date( |
| 636 snippet->publish_date() + | 700 snippet->publish_date() + |
| 637 base::TimeDelta::FromMinutes(kDefaultExpiryTimeMins)); | 701 base::TimeDelta::FromMinutes(kDefaultExpiryTimeMins)); |
| 638 } | 702 } |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 653 new_snippets.end()); | 717 new_snippets.end()); |
| 654 int num_snippets_dismissed = num_new_snippets - new_snippets.size(); | 718 int num_snippets_dismissed = num_new_snippets - new_snippets.size(); |
| 655 UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch", | 719 UMA_HISTOGRAM_BOOLEAN("NewTabPage.Snippets.IncompleteSnippetsAfterFetch", |
| 656 num_snippets_dismissed > 0); | 720 num_snippets_dismissed > 0); |
| 657 if (num_snippets_dismissed > 0) { | 721 if (num_snippets_dismissed > 0) { |
| 658 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets", | 722 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumIncompleteSnippets", |
| 659 num_snippets_dismissed); | 723 num_snippets_dismissed); |
| 660 } | 724 } |
| 661 } | 725 } |
| 662 | 726 |
| 663 // Save new articles to the DB. | 727 // Do not touch the current set of snippets if the newly fetched one is empty. |
| 664 // TODO(sfiera): save non-articles to DB too. | 728 if (new_snippets.empty()) |
| 665 if (category == articles_category_) | 729 return; |
| 730 | |
| 731 // Remove current snippets that have been fetched again. We do not need to | |
| 732 // archive those as they will be in the new current set. | |
| 733 EraseMatchingSnippets(&content->snippets, GetMainIDs(new_snippets), | |
| 734 /*match_all_ids=*/false); | |
| 735 | |
| 736 ArchiveSnippets(category, &content->snippets); | |
| 737 | |
| 738 // TODO(sfiera): handle DB for non-articles too. | |
| 739 if (category == articles_category_) { | |
| 740 // Save new articles to the DB. | |
| 666 database_->SaveSnippets(new_snippets); | 741 database_->SaveSnippets(new_snippets); |
| 742 } | |
| 667 | 743 |
| 668 // Insert the new snippets at the front. | 744 // Insert new snippets. |
|
Marc Treib
2016/09/22 13:05:29
nitty nit: "Insert" isn't correct anymore. I'd jus
jkrcal
2016/09/22 14:07:58
Done.
| |
| 669 content->snippets.insert(content->snippets.begin(), | 745 content->snippets = std::move(new_snippets); |
| 670 std::make_move_iterator(new_snippets.begin()), | |
| 671 std::make_move_iterator(new_snippets.end())); | |
| 672 } | 746 } |
| 673 | 747 |
| 674 std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const { | 748 std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const { |
| 675 std::set<std::string> hosts; | 749 std::set<std::string> hosts; |
| 676 const base::ListValue* list = pref_service_->GetList(prefs::kSnippetHosts); | 750 const base::ListValue* list = pref_service_->GetList(prefs::kSnippetHosts); |
| 677 for (const auto& value : *list) { | 751 for (const auto& value : *list) { |
| 678 std::string str; | 752 std::string str; |
| 679 bool success = value->GetAsString(&str); | 753 bool success = value->GetAsString(&str); |
| 680 DCHECK(success) << "Failed to parse snippet host from prefs"; | 754 DCHECK(success) << "Failed to parse snippet host from prefs"; |
| 681 hosts.insert(std::move(str)); | 755 hosts.insert(std::move(str)); |
| 682 } | 756 } |
| 683 return hosts; | 757 return hosts; |
| 684 } | 758 } |
| 685 | 759 |
| 686 void NTPSnippetsService::StoreSnippetHostsToPrefs( | 760 void NTPSnippetsService::StoreSnippetHostsToPrefs( |
| 687 const std::set<std::string>& hosts) { | 761 const std::set<std::string>& hosts) { |
| 688 base::ListValue list; | 762 base::ListValue list; |
| 689 for (const std::string& host : hosts) | 763 for (const std::string& host : hosts) |
| 690 list.AppendString(host); | 764 list.AppendString(host); |
| 691 pref_service_->Set(prefs::kSnippetHosts, list); | 765 pref_service_->Set(prefs::kSnippetHosts, list); |
| 692 } | 766 } |
| 693 | 767 |
| 694 void NTPSnippetsService::ClearExpiredSnippets() { | 768 void NTPSnippetsService::ClearExpiredDismissedSnippets() { |
| 695 std::vector<Category> categories_to_erase; | 769 std::vector<Category> categories_to_erase; |
| 696 | 770 |
| 697 const base::Time expiry = base::Time::Now(); | 771 const base::Time now = base::Time::Now(); |
| 698 base::Time next_expiry = base::Time::Max(); | |
| 699 | 772 |
| 700 for (auto& item : categories_) { | 773 for (auto& item : categories_) { |
| 701 Category category = item.first; | 774 Category category = item.first; |
| 702 CategoryContent* content = &item.second; | 775 CategoryContent* content = &item.second; |
| 703 | 776 |
| 704 // Move expired snippets over into |to_delete|. | |
| 705 NTPSnippet::PtrVector to_delete; | 777 NTPSnippet::PtrVector to_delete; |
| 706 for (std::unique_ptr<NTPSnippet>& snippet : content->snippets) { | 778 // 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) { | 779 for (std::unique_ptr<NTPSnippet>& snippet : content->dismissed) { |
| 714 if (snippet->expiry_date() <= expiry) | 780 if (snippet->expiry_date() <= now) |
| 715 to_delete.emplace_back(std::move(snippet)); | 781 to_delete.emplace_back(std::move(snippet)); |
| 716 } | 782 } |
| 717 Compact(&content->dismissed); | 783 Compact(&content->dismissed); |
| 718 | 784 |
| 719 // Finally, actually delete the removed snippets from the DB. | 785 // Delete the removed article suggestions from the DB. |
| 720 if (category == articles_category_) | 786 if (category == articles_category_) { |
| 787 // The image got already deleted when the suggestion was dismissed. | |
| 721 database_->DeleteSnippets(to_delete); | 788 database_->DeleteSnippets(to_delete); |
| 722 | |
| 723 if (content->snippets.empty() && content->dismissed.empty()) { | |
| 724 if ((category != articles_category_) && !content->provided_by_server) | |
| 725 categories_to_erase.push_back(category); | |
| 726 continue; | |
| 727 } | 789 } |
| 728 | 790 |
| 729 for (const auto& snippet : content->snippets) { | 791 if (content->snippets.empty() && content->dismissed.empty() && |
| 730 if (snippet->expiry_date() < next_expiry) | 792 category != articles_category_ && !content->provided_by_server) { |
| 731 next_expiry = snippet->expiry_date(); | 793 categories_to_erase.push_back(category); |
| 732 } | |
| 733 for (const auto& snippet : content->dismissed) { | |
| 734 if (snippet->expiry_date() < next_expiry) | |
| 735 next_expiry = snippet->expiry_date(); | |
| 736 } | 794 } |
| 737 } | 795 } |
| 738 | 796 |
| 739 for (Category category : categories_to_erase) { | 797 for (Category category : categories_to_erase) { |
| 740 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED); | 798 UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED); |
| 741 categories_.erase(category); | 799 categories_.erase(category); |
| 742 } | 800 } |
| 801 } | |
| 743 | 802 |
| 744 // Unless there are no snippets left, schedule a timer for the next expiry. | 803 void NTPSnippetsService::ClearOrphanedImages() { |
| 745 DCHECK_GT(next_expiry, expiry); | 804 // 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 } | 805 } |
| 752 | 806 |
| 753 void NTPSnippetsService::NukeAllSnippets() { | 807 void NTPSnippetsService::NukeAllSnippets() { |
| 754 std::vector<Category> categories_to_erase; | 808 std::vector<Category> categories_to_erase; |
| 755 | 809 |
| 756 // Empty the ARTICLES category and remove all others, since they may or may | 810 // Empty the ARTICLES category and remove all others, since they may or may |
| 757 // not be personalized. | 811 // not be personalized. |
| 758 for (const auto& item : categories_) { | 812 for (const auto& item : categories_) { |
| 759 Category category = item.first; | 813 Category category = item.first; |
| 760 | 814 |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 815 | 869 |
| 816 FetchSnippetImageFromNetwork(suggestion_id, callback); | 870 FetchSnippetImageFromNetwork(suggestion_id, callback); |
| 817 } | 871 } |
| 818 | 872 |
| 819 void NTPSnippetsService::FetchSnippetImageFromNetwork( | 873 void NTPSnippetsService::FetchSnippetImageFromNetwork( |
| 820 const std::string& suggestion_id, | 874 const std::string& suggestion_id, |
| 821 const ImageFetchedCallback& callback) { | 875 const ImageFetchedCallback& callback) { |
| 822 Category category = GetCategoryFromUniqueID(suggestion_id); | 876 Category category = GetCategoryFromUniqueID(suggestion_id); |
| 823 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); | 877 std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); |
| 824 | 878 |
| 825 auto category_it = categories_.find(category); | 879 if (categories_.find(category) == categories_.end()) { |
| 826 if (category_it == categories_.end()) { | |
| 827 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image()); | 880 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image()); |
| 828 return; | 881 return; |
| 829 } | 882 } |
| 830 | 883 |
| 831 const CategoryContent& content = category_it->second; | 884 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 | 885 |
| 838 if (it == content.snippets.end() || | 886 if (image_url.is_empty() || |
| 839 !thumbnail_requests_throttler_.DemandQuotaForRequest( | 887 !thumbnail_requests_throttler_.DemandQuotaForRequest( |
| 840 /*interactive_request=*/true)) { | 888 /*interactive_request=*/true)) { |
| 841 // Return an empty image. Directly, this is never synchronous with the | 889 // Return an empty image. Directly, this is never synchronous with the |
| 842 // original FetchSuggestionImage() call - an asynchronous database query has | 890 // original FetchSuggestionImage() call - an asynchronous database query has |
| 843 // happened in the meantime. | 891 // happened in the meantime. |
| 844 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image()); | 892 OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image()); |
| 845 return; | 893 return; |
| 846 } | 894 } |
| 847 | 895 |
| 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( | 896 image_fetcher_->StartOrQueueNetworkRequest( |
| 857 suggestion_id, snippet.salient_image_url(), | 897 suggestion_id, image_url, |
| 858 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork, | 898 base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork, |
| 859 base::Unretained(this), callback)); | 899 base::Unretained(this), callback)); |
| 860 } | 900 } |
| 861 | 901 |
| 862 void NTPSnippetsService::OnSnippetImageDecodedFromNetwork( | 902 void NTPSnippetsService::OnSnippetImageDecodedFromNetwork( |
| 863 const ImageFetchedCallback& callback, | 903 const ImageFetchedCallback& callback, |
| 864 const std::string& suggestion_id, | 904 const std::string& suggestion_id, |
| 865 const gfx::Image& image) { | 905 const gfx::Image& image) { |
| 866 callback.Run(image); | 906 callback.Run(image); |
| 867 } | 907 } |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 883 suggestions_service_subscription_ = | 923 suggestions_service_subscription_ = |
| 884 suggestions_service_->AddCallback(base::Bind( | 924 suggestions_service_->AddCallback(base::Bind( |
| 885 &NTPSnippetsService::OnSuggestionsChanged, base::Unretained(this))); | 925 &NTPSnippetsService::OnSuggestionsChanged, base::Unretained(this))); |
| 886 } | 926 } |
| 887 | 927 |
| 888 RescheduleFetching(); | 928 RescheduleFetching(); |
| 889 } | 929 } |
| 890 | 930 |
| 891 void NTPSnippetsService::EnterStateDisabled() { | 931 void NTPSnippetsService::EnterStateDisabled() { |
| 892 NukeAllSnippets(); | 932 NukeAllSnippets(); |
| 893 expiry_timer_.Stop(); | |
| 894 suggestions_service_subscription_.reset(); | 933 suggestions_service_subscription_.reset(); |
| 895 RescheduleFetching(); | 934 RescheduleFetching(); |
| 896 } | 935 } |
| 897 | 936 |
| 898 void NTPSnippetsService::EnterStateError() { | 937 void NTPSnippetsService::EnterStateError() { |
| 899 expiry_timer_.Stop(); | |
| 900 suggestions_service_subscription_.reset(); | 938 suggestions_service_subscription_.reset(); |
| 901 RescheduleFetching(); | 939 RescheduleFetching(); |
| 902 snippets_status_service_.reset(); | 940 snippets_status_service_.reset(); |
| 903 } | 941 } |
| 904 | 942 |
| 905 void NTPSnippetsService::FinishInitialization() { | 943 void NTPSnippetsService::FinishInitialization() { |
| 906 if (nuke_after_load_) { | 944 if (nuke_after_load_) { |
| 907 NukeAllSnippets(); | 945 NukeAllSnippets(); |
| 908 nuke_after_load_ = false; | 946 nuke_after_load_ = false; |
| 909 } | 947 } |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1044 } | 1082 } |
| 1045 | 1083 |
| 1046 NTPSnippetsService::CategoryContent::CategoryContent() = default; | 1084 NTPSnippetsService::CategoryContent::CategoryContent() = default; |
| 1047 NTPSnippetsService::CategoryContent::CategoryContent(CategoryContent&&) = | 1085 NTPSnippetsService::CategoryContent::CategoryContent(CategoryContent&&) = |
| 1048 default; | 1086 default; |
| 1049 NTPSnippetsService::CategoryContent::~CategoryContent() = default; | 1087 NTPSnippetsService::CategoryContent::~CategoryContent() = default; |
| 1050 NTPSnippetsService::CategoryContent& NTPSnippetsService::CategoryContent:: | 1088 NTPSnippetsService::CategoryContent& NTPSnippetsService::CategoryContent:: |
| 1051 operator=(CategoryContent&&) = default; | 1089 operator=(CategoryContent&&) = default; |
| 1052 | 1090 |
| 1053 } // namespace ntp_snippets | 1091 } // namespace ntp_snippets |
| OLD | NEW |