Index: components/ntp_snippets/ntp_snippets_service.cc |
diff --git a/components/ntp_snippets/ntp_snippets_service.cc b/components/ntp_snippets/ntp_snippets_service.cc |
index 1bb40f4ddc8c77e9919548ea6352b347552144cc..fb955cd0894cb2f080f8fe33b3a33f0c850593d5 100644 |
--- a/components/ntp_snippets/ntp_snippets_service.cc |
+++ b/components/ntp_snippets/ntp_snippets_service.cc |
@@ -50,6 +50,9 @@ namespace { |
// histograms with COUNTS() if this number increases beyond 50. |
const int kMaxSnippetCount = 10; |
+// Number of archived snippets we keep around in memory. |
+const int kMaxArchivedSnippetCount = 200; |
+ |
// Default values for snippets fetching intervals - once per day only. |
const int kDefaultFetchingIntervalWifiSeconds = 0; |
const int kDefaultFetchingIntervalFallbackSeconds = 24 * 60 * 60; |
@@ -118,13 +121,48 @@ std::set<std::string> GetSuggestionsHostsImpl( |
return hosts; |
} |
-void InsertAllIDs(const NTPSnippet::PtrVector& snippets, |
- std::set<std::string>* ids) { |
+std::set<std::string> GetAllIDs(const NTPSnippet::PtrVector& snippets) { |
+ std::set<std::string> ids; |
for (const std::unique_ptr<NTPSnippet>& snippet : snippets) { |
- ids->insert(snippet->id()); |
+ ids.insert(snippet->id()); |
for (const SnippetSource& source : snippet->sources()) |
- ids->insert(source.url.spec()); |
+ ids.insert(source.url.spec()); |
} |
+ return ids; |
+} |
+ |
+std::set<std::string> GetMainIDs(const NTPSnippet::PtrVector& snippets) { |
+ std::set<std::string> ids; |
+ for (const std::unique_ptr<NTPSnippet>& snippet : snippets) |
+ ids.insert(snippet->id()); |
+ return ids; |
+} |
+ |
+bool IsSnippetInSet(const std::unique_ptr<NTPSnippet>& snippet, |
+ const std::set<std::string>& ids, |
+ bool match_all_ids) { |
+ if (ids.count(snippet->id())) |
+ return true; |
+ if (!match_all_ids) |
+ return false; |
+ for (const SnippetSource& source : snippet->sources()) { |
+ if (ids.count(source.url.spec())) |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+void EraseMatchingSnippets(NTPSnippet::PtrVector* snippets, |
+ const std::set<std::string>& matching_ids, |
+ bool match_all_ids) { |
+ snippets->erase( |
+ std::remove_if(snippets->begin(), snippets->end(), |
+ [&matching_ids, match_all_ids]( |
+ const std::unique_ptr<NTPSnippet>& snippet) { |
+ return IsSnippetInSet(snippet, matching_ids, |
+ match_all_ids); |
+ }), |
+ snippets->end()); |
} |
void Compact(NTPSnippet::PtrVector* snippets) { |
@@ -323,8 +361,10 @@ void NTPSnippetsService::ClearCachedSuggestions(Category category) { |
if (content->snippets.empty()) |
return; |
- if (category == articles_category_) |
+ if (category == articles_category_) { |
database_->DeleteSnippets(content->snippets); |
+ database_->DeleteImages(content->snippets); |
+ } |
content->snippets.clear(); |
NotifyNewSuggestions(); |
@@ -365,8 +405,10 @@ void NTPSnippetsService::ClearDismissedSuggestionsForDebugging( |
if (content->dismissed.empty()) |
return; |
- if (category == articles_category_) |
+ if (category == articles_category_) { |
+ // The image got already deleted when the suggestion was dismissed. |
database_->DeleteSnippets(content->dismissed); |
+ } |
content->dismissed.clear(); |
} |
@@ -388,6 +430,31 @@ int NTPSnippetsService::GetMaxSnippetCountForTesting() { |
//////////////////////////////////////////////////////////////////////////////// |
// Private methods |
+GURL NTPSnippetsService::FindSnippetImageUrl( |
+ Category category, |
+ const std::string& snippet_id) const { |
+ DCHECK(categories_.find(category) != categories_.end()); |
+ |
+ const CategoryContent& content = categories_.at(category); |
+ // Search for the snippet in current and archived snippets. |
+ auto it = |
+ std::find_if(content.snippets.begin(), content.snippets.end(), |
+ [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { |
+ return snippet->id() == snippet_id; |
+ }); |
+ if (it != content.snippets.end()) |
+ return (*it)->salient_image_url(); |
+ |
+ it = std::find_if(content.archived.begin(), content.archived.end(), |
+ [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { |
+ return snippet->id() == snippet_id; |
+ }); |
+ if (it != content.archived.end()) |
+ return (*it)->salient_image_url(); |
+ |
+ return GURL(); |
+} |
+ |
// image_fetcher::ImageFetcherDelegate implementation. |
void NTPSnippetsService::OnImageDataFetched(const std::string& suggestion_id, |
const std::string& image_data) { |
@@ -397,21 +464,15 @@ void NTPSnippetsService::OnImageDataFetched(const std::string& suggestion_id, |
Category category = GetCategoryFromUniqueID(suggestion_id); |
std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); |
- auto category_it = categories_.find(category); |
- if (category_it == categories_.end()) |
+ if (categories_.find(category) == categories_.end()) |
return; |
- const CategoryContent& content = category_it->second; |
- |
// Only save the image if the corresponding snippet still exists. |
- auto it = |
- std::find_if(content.snippets.begin(), content.snippets.end(), |
- [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { |
- return snippet->id() == snippet_id; |
- }); |
- if (it == content.snippets.end()) |
+ if (FindSnippetImageUrl(category, snippet_id).is_empty()) |
return; |
+ // Only cache the data in the DB, the actual serving is done in the callback |
+ // provided to |image_fetcher_| (OnSnippetImageDecodedFromNetwork()). |
database_->SaveImage(snippet_id, image_data); |
} |
@@ -419,7 +480,7 @@ void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { |
if (state_ == State::ERROR_OCCURRED) |
return; |
DCHECK(state_ == State::NOT_INITED); |
- DCHECK(categories_.size() == 1); // Only articles category, so far. |
+ DCHECK_EQ(1u, categories_.size()); // Only articles category, so far. |
DCHECK(categories_.find(articles_category_) != categories_.end()); |
// TODO(sfiera): support non-article categories in database. |
@@ -437,7 +498,8 @@ void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { |
return lhs->score() > rhs->score(); |
}); |
- ClearExpiredSnippets(); |
+ ClearExpiredDismissedSnippets(); |
+ ClearOrphanedImages(); |
FinishInitialization(); |
} |
@@ -472,8 +534,7 @@ void NTPSnippetsService::OnSuggestionsChanged( |
to_delete.emplace_back(std::move(snippet)); |
} |
Compact(&content->snippets); |
- // Then delete the removed snippets from the database. |
- database_->DeleteSnippets(to_delete); |
+ ArchiveSnippets(articles_category_, &to_delete); |
StoreSnippetHostsToPrefs(hosts); |
@@ -495,9 +556,14 @@ void NTPSnippetsService::OnFetchFinished( |
content->provided_by_server = false; |
} |
+ // Clear up expired dismissed snippets before we use them to filter new ones. |
+ ClearExpiredDismissedSnippets(); |
+ |
// If snippets were fetched successfully, update our |categories_| from each |
// category provided by the server. |
if (snippets) { |
+ // TODO(jkrcal): A bit hard to understand with so many variables called |
+ // "*categor*". Isn't here some room for simplification? |
for (NTPSnippetsFetcher::FetchedCategory& fetched_category : *snippets) { |
Category category = fetched_category.category; |
@@ -508,6 +574,7 @@ void NTPSnippetsService::OnFetchFinished( |
categories_[category].localized_title = |
fetched_category.localized_title; |
} |
+ categories_[category].provided_by_server = true; |
DCHECK_LE(snippets->size(), static_cast<size_t>(kMaxSnippetCount)); |
// TODO(sfiera): histograms for server categories. |
@@ -518,28 +585,10 @@ void NTPSnippetsService::OnFetchFinished( |
fetched_category.snippets.size()); |
} |
- MergeSnippets(category, std::move(fetched_category.snippets)); |
- |
- // If there are more snippets than we want to show, delete the extra ones. |
- CategoryContent* content = &categories_[category]; |
- content->provided_by_server = true; |
- if (content->snippets.size() > kMaxSnippetCount) { |
- NTPSnippet::PtrVector to_delete( |
- std::make_move_iterator(content->snippets.begin() + |
- kMaxSnippetCount), |
- std::make_move_iterator(content->snippets.end())); |
- content->snippets.resize(kMaxSnippetCount); |
- if (category == articles_category_) |
- database_->DeleteSnippets(to_delete); |
- } |
+ ReplaceSnippets(category, std::move(fetched_category.snippets)); |
} |
} |
- // Trigger expiration. This probably won't expire any current snippets (old |
- // ones should have already been expired by the timer, and new ones shouldn't |
- // have expired yet), but it will update the timer for the next run. |
- ClearExpiredSnippets(); |
- |
for (const auto& item : categories_) { |
Category category = item.first; |
UpdateCategoryStatus(category, CategoryStatus::AVAILABLE); |
@@ -565,28 +614,43 @@ void NTPSnippetsService::OnFetchFinished( |
RescheduleFetching(); |
} |
-void NTPSnippetsService::MergeSnippets(Category category, |
- NTPSnippet::PtrVector new_snippets) { |
+void NTPSnippetsService::ArchiveSnippets(Category category, |
+ NTPSnippet::PtrVector* to_archive) { |
+ CategoryContent* content = &categories_[category]; |
+ |
+ // TODO(sfiera): handle DB for non-articles too. |
+ if (category == articles_category_) { |
+ database_->DeleteSnippets(*to_archive); |
+ // Do not delete the thumbnail images as they are still handy on open NTPs. |
+ } |
+ |
+ // Archive previous snippets - move them at the beginning of the list. |
+ content->archived.insert(content->archived.begin(), |
+ std::make_move_iterator(to_archive->begin()), |
+ std::make_move_iterator(to_archive->end())); |
+ Compact(to_archive); |
+ |
+ // If there are more archived snippets than we want to keep, delete the |
+ // oldest ones by their fetch time (which are always in the back). |
+ if (content->archived.size() > kMaxArchivedSnippetCount) { |
+ NTPSnippet::PtrVector to_delete( |
+ std::make_move_iterator(content->archived.begin() + |
+ kMaxArchivedSnippetCount), |
+ std::make_move_iterator(content->archived.end())); |
+ content->archived.resize(kMaxArchivedSnippetCount); |
+ if (category == articles_category_) |
+ database_->DeleteImages(to_delete); |
+ } |
+} |
+ |
+void NTPSnippetsService::ReplaceSnippets(Category category, |
+ NTPSnippet::PtrVector new_snippets) { |
DCHECK(ready()); |
CategoryContent* content = &categories_[category]; |
- // Remove new snippets that we already have, or that have been dismissed. |
- std::set<std::string> old_snippet_ids; |
- InsertAllIDs(content->dismissed, &old_snippet_ids); |
- InsertAllIDs(content->snippets, &old_snippet_ids); |
- new_snippets.erase( |
- std::remove_if( |
- new_snippets.begin(), new_snippets.end(), |
- [&old_snippet_ids](const std::unique_ptr<NTPSnippet>& snippet) { |
- if (old_snippet_ids.count(snippet->id())) |
- return true; |
- for (const SnippetSource& source : snippet->sources()) { |
- if (old_snippet_ids.count(source.url.spec())) |
- return true; |
- } |
- return false; |
- }), |
- new_snippets.end()); |
+ // Remove new snippets that have been dismissed. |
+ EraseMatchingSnippets(&new_snippets, GetAllIDs(content->dismissed), |
+ /*match_all_ids=*/true); |
// Fill in default publish/expiry dates where required. |
for (std::unique_ptr<NTPSnippet>& snippet : new_snippets) { |
@@ -621,15 +685,24 @@ void NTPSnippetsService::MergeSnippets(Category category, |
} |
} |
- // Save new articles to the DB. |
- // TODO(sfiera): save non-articles to DB too. |
- if (category == articles_category_) |
+ // Do not touch the current set of snippets if the newly fetched one is empty. |
+ if (new_snippets.empty()) |
+ return; |
+ |
+ // Remove current snippets that have been fetched again. We do not need to |
+ // archive those as they will be in the new current set. |
+ EraseMatchingSnippets(&content->snippets, GetMainIDs(new_snippets), |
+ /*match_all_ids=*/false); |
+ |
+ ArchiveSnippets(category, &content->snippets); |
+ |
+ // TODO(sfiera): handle DB for non-articles too. |
+ if (category == articles_category_) { |
+ // Save new articles to the DB. |
database_->SaveSnippets(new_snippets); |
+ } |
- // Insert the new snippets at the front. |
- content->snippets.insert(content->snippets.begin(), |
- std::make_move_iterator(new_snippets.begin()), |
- std::make_move_iterator(new_snippets.end())); |
+ content->snippets = std::move(new_snippets); |
} |
std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const { |
@@ -652,48 +725,32 @@ void NTPSnippetsService::StoreSnippetHostsToPrefs( |
pref_service_->Set(prefs::kSnippetHosts, list); |
} |
-void NTPSnippetsService::ClearExpiredSnippets() { |
+void NTPSnippetsService::ClearExpiredDismissedSnippets() { |
std::vector<Category> categories_to_erase; |
- const base::Time expiry = base::Time::Now(); |
- base::Time next_expiry = base::Time::Max(); |
+ const base::Time now = base::Time::Now(); |
for (auto& item : categories_) { |
Category category = item.first; |
CategoryContent* content = &item.second; |
- // Move expired snippets over into |to_delete|. |
NTPSnippet::PtrVector to_delete; |
- for (std::unique_ptr<NTPSnippet>& snippet : content->snippets) { |
- if (snippet->expiry_date() <= expiry) |
- to_delete.emplace_back(std::move(snippet)); |
- } |
- Compact(&content->snippets); |
- |
- // Move expired dismissed snippets over into |to_delete| as well. |
+ // Move expired dismissed snippets over into |to_delete|. |
for (std::unique_ptr<NTPSnippet>& snippet : content->dismissed) { |
- if (snippet->expiry_date() <= expiry) |
+ if (snippet->expiry_date() <= now) |
to_delete.emplace_back(std::move(snippet)); |
} |
Compact(&content->dismissed); |
- // Finally, actually delete the removed snippets from the DB. |
- if (category == articles_category_) |
+ // Delete the removed article suggestions from the DB. |
+ if (category == articles_category_) { |
+ // The image got already deleted when the suggestion was dismissed. |
database_->DeleteSnippets(to_delete); |
- |
- if (content->snippets.empty() && content->dismissed.empty()) { |
- if ((category != articles_category_) && !content->provided_by_server) |
- categories_to_erase.push_back(category); |
- continue; |
} |
- for (const auto& snippet : content->snippets) { |
- if (snippet->expiry_date() < next_expiry) |
- next_expiry = snippet->expiry_date(); |
- } |
- for (const auto& snippet : content->dismissed) { |
- if (snippet->expiry_date() < next_expiry) |
- next_expiry = snippet->expiry_date(); |
+ if (content->snippets.empty() && content->dismissed.empty() && |
+ category != articles_category_ && !content->provided_by_server) { |
+ categories_to_erase.push_back(category); |
} |
} |
@@ -701,14 +758,10 @@ void NTPSnippetsService::ClearExpiredSnippets() { |
UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED); |
categories_.erase(category); |
} |
+} |
- // Unless there are no snippets left, schedule a timer for the next expiry. |
- DCHECK_GT(next_expiry, expiry); |
- if (next_expiry < base::Time::Max()) { |
- expiry_timer_.Start(FROM_HERE, next_expiry - expiry, |
- base::Bind(&NTPSnippetsService::ClearExpiredSnippets, |
- base::Unretained(this))); |
- } |
+void NTPSnippetsService::ClearOrphanedImages() { |
+ // TODO(jkrcal): Implement. crbug.com/649009 |
} |
void NTPSnippetsService::NukeAllSnippets() { |
@@ -783,20 +836,14 @@ void NTPSnippetsService::FetchSnippetImageFromNetwork( |
Category category = GetCategoryFromUniqueID(suggestion_id); |
std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); |
- auto category_it = categories_.find(category); |
- if (category_it == categories_.end()) { |
+ if (categories_.find(category) == categories_.end()) { |
OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image()); |
return; |
} |
- const CategoryContent& content = category_it->second; |
- auto it = |
- std::find_if(content.snippets.begin(), content.snippets.end(), |
- [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { |
- return snippet->id() == snippet_id; |
- }); |
+ GURL image_url = FindSnippetImageUrl(category, snippet_id); |
- if (it == content.snippets.end() || |
+ if (image_url.is_empty() || |
!thumbnail_requests_throttler_.DemandQuotaForRequest( |
/*interactive_request=*/true)) { |
// Return an empty image. Directly, this is never synchronous with the |
@@ -806,16 +853,8 @@ void NTPSnippetsService::FetchSnippetImageFromNetwork( |
return; |
} |
- const NTPSnippet& snippet = *it->get(); |
- |
- // TODO(jkrcal): We probably should rename OnImageDataFetched() to |
- // CacheImageData(). This would document that this is actually independent |
- // from the individual fetch-flow. |
- // The image fetcher calls OnImageDataFetched() with the raw data (this object |
- // is an ImageFetcherDelegate) and then also |
- // OnSnippetImageDecodedFromNetwork() after the raw data gets decoded. |
image_fetcher_->StartOrQueueNetworkRequest( |
- suggestion_id, snippet.salient_image_url(), |
+ suggestion_id, image_url, |
base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork, |
base::Unretained(this), callback)); |
} |
@@ -862,12 +901,10 @@ void NTPSnippetsService::EnterStateReady() { |
void NTPSnippetsService::EnterStateDisabled() { |
NukeAllSnippets(); |
- expiry_timer_.Stop(); |
suggestions_service_subscription_.reset(); |
} |
void NTPSnippetsService::EnterStateError() { |
- expiry_timer_.Stop(); |
suggestions_service_subscription_.reset(); |
snippets_status_service_.reset(); |
} |