Chromium Code Reviews| 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 282c5d9765c0bd9ab31306e02dc9bb735ae968bb..7f6f9420fe85047c0e4937cb98b49f166cff7b86 100644 |
| --- a/components/ntp_snippets/ntp_snippets_service.cc |
| +++ b/components/ntp_snippets/ntp_snippets_service.cc |
| @@ -200,9 +200,10 @@ NTPSnippetsService::NTPSnippetsService( |
| std::unique_ptr<NTPSnippetsStatusService> status_service) |
| : ContentSuggestionsProvider(observer, category_factory), |
| state_(State::NOT_INITED), |
| - category_status_(CategoryStatus::INITIALIZING), |
| pref_service_(pref_service), |
| suggestions_service_(suggestions_service), |
| + articles_category_( |
| + category_factory->FromKnownCategory(KnownCategories::ARTICLES)), |
| application_language_code_(application_language_code), |
| scheduler_(scheduler), |
| snippets_fetcher_(std::move(snippets_fetcher)), |
| @@ -211,14 +212,16 @@ NTPSnippetsService::NTPSnippetsService( |
| database_(std::move(database)), |
| snippets_status_service_(std::move(status_service)), |
| fetch_after_load_(false), |
| - provided_category_( |
| - category_factory->FromKnownCategory(KnownCategories::ARTICLES)), |
| thumbnail_requests_throttler_( |
| pref_service, |
| RequestThrottler::RequestType::CONTENT_SUGGESTION_THUMBNAIL) { |
| - observer->OnCategoryStatusChanged(this, provided_category_, category_status_); |
| + // Articles category always exists; others will be added as needed. |
| + categories_.emplace(articles_category_, CategoryContent()); |
|
Marc Treib
2016/08/22 15:06:46
map::emplace isn't available everywhere yet, see h
sfiera
2016/08/24 14:35:56
Done.
|
| + observer->OnCategoryStatusChanged(this, articles_category_, |
| + CategoryStatus::INITIALIZING); |
|
Marc Treib
2016/08/22 15:06:47
Use the actual value from categories_, so the defa
sfiera
2016/08/24 14:35:56
Done.
|
| if (database_->IsErrorState()) { |
| - EnterState(State::ERROR_OCCURRED, CategoryStatus::LOADING_ERROR); |
| + EnterState(State::ERROR_OCCURRED); |
| + UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR); |
| return; |
| } |
| @@ -254,8 +257,11 @@ void NTPSnippetsService::FetchSnippetsFromHosts( |
| if (!ready()) |
| return; |
| - if (snippets_.empty()) |
| - UpdateCategoryStatus(CategoryStatus::AVAILABLE_LOADING); |
| + // Empty categories are marked as loading; others are unchanged. |
| + for (const auto& category : categories_) { |
|
Marc Treib
2016/08/22 15:06:47
Use the actual type? auto isn't clear here, since
sfiera
2016/08/24 14:35:56
I've switched this to what I do in most places, wh
|
| + if (category.second.snippets.empty()) |
| + UpdateCategoryStatus(category.first, CategoryStatus::AVAILABLE_LOADING); |
| + } |
| snippets_fetcher_->FetchSnippetsFromHosts( |
| hosts, application_language_code_, kMaxSnippetCount, interactive_request); |
| @@ -277,11 +283,13 @@ void NTPSnippetsService::RescheduleFetching() { |
| } |
| CategoryStatus NTPSnippetsService::GetCategoryStatus(Category category) { |
| - DCHECK(category.IsKnownCategory(KnownCategories::ARTICLES)); |
| - return category_status_; |
| + DCHECK(categories_.find(category) != categories_.end()); |
| + return categories_[category].status; |
| } |
| CategoryInfo NTPSnippetsService::GetCategoryInfo(Category category) { |
| + DCHECK(categories_.find(category) != categories_.end()); |
| + // TODO(sfiera): pass back localized category titles. |
|
Marc Treib
2016/08/22 15:06:47
nit: mention that this refers to non-articles cate
sfiera
2016/08/24 14:35:56
Changed.
|
| return CategoryInfo( |
| l10n_util::GetStringUTF16(IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER), |
| ContentSuggestionsCardLayout::FULL_CARD, |
| @@ -293,14 +301,18 @@ void NTPSnippetsService::DismissSuggestion(const std::string& suggestion_id) { |
| if (!ready()) |
| return; |
| + Category category_id = GetCategoryFromUniqueID(suggestion_id); |
|
Marc Treib
2016/08/22 15:06:47
Just "category" please (that's what it's called ev
sfiera
2016/08/24 14:35:56
Done.
|
| std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); |
| + DCHECK(categories_.find(category_id) != categories_.end()); |
| + |
| + CategoryContent* category = &categories_[category_id]; |
|
Marc Treib
2016/08/22 15:06:46
call this something else, like "content"
sfiera
2016/08/24 14:35:56
Done.
|
| auto it = |
| - std::find_if(snippets_.begin(), snippets_.end(), |
| + std::find_if(category->snippets.begin(), category->snippets.end(), |
| [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { |
| return snippet->id() == snippet_id; |
| }); |
| - if (it == snippets_.end()) |
| + if (it == category->snippets.end()) |
| return; |
| (*it)->set_dismissed(true); |
| @@ -308,44 +320,48 @@ void NTPSnippetsService::DismissSuggestion(const std::string& suggestion_id) { |
| database_->SaveSnippet(**it); |
| database_->DeleteImage((*it)->id()); |
| - dismissed_snippets_.push_back(std::move(*it)); |
| - snippets_.erase(it); |
| + category->dismissed.push_back(std::move(*it)); |
| + category->snippets.erase(it); |
| } |
| void NTPSnippetsService::FetchSuggestionImage( |
| const std::string& suggestion_id, |
| const ImageFetchedCallback& callback) { |
| - std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); |
| database_->LoadImage( |
| - snippet_id, |
| + suggestion_id, |
| base::Bind(&NTPSnippetsService::OnSnippetImageFetchedFromDatabase, |
| - base::Unretained(this), callback, snippet_id)); |
| + base::Unretained(this), callback, suggestion_id)); |
| } |
| -void NTPSnippetsService::ClearCachedSuggestionsForDebugging(Category category) { |
| - DCHECK_EQ(category, provided_category_); |
| +void NTPSnippetsService::ClearCachedSuggestionsForDebugging( |
| + Category category_id) { |
| if (!initialized()) |
| return; |
| - if (snippets_.empty()) |
| + if (categories_.find(category_id) == categories_.end()) |
| + return; |
| + CategoryContent* category = &categories_[category_id]; |
| + if (category->snippets.empty()) |
| return; |
| - database_->DeleteSnippets(snippets_); |
| - snippets_.clear(); |
| + if (category_id == articles_category_) |
| + database_->DeleteSnippets(category->snippets); |
| + category->snippets.clear(); |
| NotifyNewSuggestions(); |
| } |
| std::vector<ContentSuggestion> |
| -NTPSnippetsService::GetDismissedSuggestionsForDebugging(Category category) { |
| - DCHECK_EQ(category, provided_category_); |
| +NTPSnippetsService::GetDismissedSuggestionsForDebugging(Category category_id) { |
| + DCHECK(categories_.find(category_id) != categories_.end()); |
| + |
| std::vector<ContentSuggestion> result; |
| - for (const std::unique_ptr<NTPSnippet>& snippet : dismissed_snippets_) { |
| + const CategoryContent& category = categories_[category_id]; |
| + for (const std::unique_ptr<NTPSnippet>& snippet : category.dismissed) { |
| if (!snippet->is_complete()) |
| continue; |
| - ContentSuggestion suggestion( |
| - MakeUniqueID(provided_category_, snippet->id()), |
| - snippet->best_source().url); |
| + ContentSuggestion suggestion(MakeUniqueID(category_id, snippet->id()), |
| + snippet->best_source().url); |
| suggestion.set_amp_url(snippet->best_source().amp_url); |
| suggestion.set_title(base::UTF8ToUTF16(snippet->title())); |
| suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet())); |
| @@ -359,16 +375,19 @@ NTPSnippetsService::GetDismissedSuggestionsForDebugging(Category category) { |
| } |
| void NTPSnippetsService::ClearDismissedSuggestionsForDebugging( |
| - Category category) { |
| - DCHECK_EQ(category, provided_category_); |
| + Category category_id) { |
| + DCHECK(categories_.find(category_id) != categories_.end()); |
| + |
| if (!initialized()) |
| return; |
| - if (dismissed_snippets_.empty()) |
| + CategoryContent* category = &categories_[category_id]; |
| + if (category->dismissed.empty()) |
| return; |
| - database_->DeleteSnippets(dismissed_snippets_); |
| - dismissed_snippets_.clear(); |
| + if (category_id == articles_category_) |
| + database_->DeleteSnippets(category->dismissed); |
| + category->dismissed.clear(); |
| } |
| std::set<std::string> NTPSnippetsService::GetSuggestionsHosts() const { |
| @@ -390,47 +409,61 @@ int NTPSnippetsService::GetMaxSnippetCountForTesting() { |
| // Private methods |
| // image_fetcher::ImageFetcherDelegate implementation. |
| -void NTPSnippetsService::OnImageDataFetched(const std::string& snippet_id, |
| +void NTPSnippetsService::OnImageDataFetched(const std::string& suggestion_id, |
| const std::string& image_data) { |
| if (image_data.empty()) |
| return; |
| + Category category_id = GetCategoryFromUniqueID(suggestion_id); |
| + std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); |
| + |
| + auto category_it = categories_.find(category_id); |
| + if (category_it == categories_.end()) |
| + return; |
| + |
| + const CategoryContent& category = category_it->second; |
| + |
| // Only save the image if the corresponding snippet still exists. |
| auto it = |
| - std::find_if(snippets_.begin(), snippets_.end(), |
| + std::find_if(category.snippets.begin(), category.snippets.end(), |
| [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { |
| return snippet->id() == snippet_id; |
| }); |
| - if (it == snippets_.end()) |
| + if (it == category.snippets.end()) |
| return; |
| - database_->SaveImage(snippet_id, image_data); |
| + database_->SaveImage(suggestion_id, image_data); |
| } |
| void NTPSnippetsService::OnDatabaseLoaded(NTPSnippet::PtrVector snippets) { |
| if (state_ == State::ERROR_OCCURRED) |
| return; |
| DCHECK(state_ == State::NOT_INITED); |
| - DCHECK(snippets_.empty()); |
| - DCHECK(dismissed_snippets_.empty()); |
| + DCHECK(categories_.size() == 1); // Only articles category, so far. |
| + DCHECK(categories_.find(articles_category_) != categories_.end()); |
| + |
| + // TODO(sfiera): support non-article categories in database. |
| + CategoryContent* category = &categories_[articles_category_]; |
| for (std::unique_ptr<NTPSnippet>& snippet : snippets) { |
| if (snippet->is_dismissed()) |
| - dismissed_snippets_.emplace_back(std::move(snippet)); |
| + category->dismissed.emplace_back(std::move(snippet)); |
| else |
| - snippets_.emplace_back(std::move(snippet)); |
| + category->snippets.emplace_back(std::move(snippet)); |
| } |
| - std::sort(snippets_.begin(), snippets_.end(), |
| + |
| + std::sort(category->snippets.begin(), category->snippets.end(), |
| [](const std::unique_ptr<NTPSnippet>& lhs, |
| const std::unique_ptr<NTPSnippet>& rhs) { |
| return lhs->score() > rhs->score(); |
| }); |
| - ClearExpiredSnippets(); |
| + ClearExpiredSnippets(articles_category_); |
| FinishInitialization(); |
| } |
| void NTPSnippetsService::OnDatabaseError() { |
| - EnterState(State::ERROR_OCCURRED, CategoryStatus::LOADING_ERROR); |
| + EnterState(State::ERROR_OCCURRED); |
| + UpdateAllCategoryStatus(CategoryStatus::LOADING_ERROR); |
| } |
| // TODO(dgn): name clash between content suggestions and suggestions hosts. |
| @@ -444,15 +477,20 @@ void NTPSnippetsService::OnSuggestionsChanged( |
| return; |
| // Remove existing snippets that aren't in the suggestions anymore. |
| + // |
| // TODO(treib,maybelle): If there is another source with an allowed host, |
| // then we should fall back to that. |
| // First, move them over into |to_delete|. |
|
Marc Treib
2016/08/22 15:06:46
This line isn't part of the TODO; move it to the e
sfiera
2016/08/24 14:35:56
Done.
|
| + // |
| + // TODO(sfiera): determine when non-article categories should restrict hosts, |
| + // and apply the same logic to them here. Maybe never? |
|
Marc Treib
2016/08/22 15:06:47
Eh, since host restricts are likely going away any
sfiera
2016/08/24 14:35:56
Acknowledged.
|
| + CategoryContent* category = &categories_[articles_category_]; |
| NTPSnippet::PtrVector to_delete; |
| - for (std::unique_ptr<NTPSnippet>& snippet : snippets_) { |
| + for (std::unique_ptr<NTPSnippet>& snippet : category->snippets) { |
| if (!hosts.count(snippet->best_source().url.host())) |
| to_delete.emplace_back(std::move(snippet)); |
| } |
| - Compact(&snippets_); |
| + Compact(&category->snippets); |
| // Then delete the removed snippets from the database. |
| database_->DeleteSnippets(to_delete); |
| @@ -471,48 +509,84 @@ void NTPSnippetsService::OnFetchFinished( |
| if (!ready()) |
| return; |
| - DCHECK(category_status_ == CategoryStatus::AVAILABLE || |
| - category_status_ == CategoryStatus::AVAILABLE_LOADING); |
| + // If snippets were fetched successfully, update our |categories_| from each |
| + // source and report the category as AVAILABLE. |
| + if (snippets) { |
| + for (std::pair<const Category, NTPSnippet::PtrVector>& item : *snippets) { |
| + Category category_id = item.first; |
| + NTPSnippet::PtrVector& new_snippets = item.second; |
| + // Sparse histogram used because the number of snippets is small (bound by |
| + // kMaxSnippetCount). |
| + DCHECK_LE(snippets->size(), static_cast<size_t>(kMaxSnippetCount)); |
| + // TODO(sfiera): per-category histograms |
| + if (category_id == articles_category_) |
|
Marc Treib
2016/08/22 15:06:47
Braces please
sfiera
2016/08/24 14:35:56
Done.
|
| + UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticlesFetched", |
| + new_snippets.size()); |
| - // TODO(sfiera): support more than just the provided_category_ ARTICLES. |
| - if (snippets && (snippets->find(provided_category_) != snippets->end())) { |
| - // Sparse histogram used because the number of snippets is small (bound by |
| - // kMaxSnippetCount). |
| - DCHECK_LE(snippets->size(), static_cast<size_t>(kMaxSnippetCount)); |
| - UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticlesFetched", |
| - (*snippets)[provided_category_].size()); |
| - MergeSnippets(std::move((*snippets)[provided_category_])); |
| + MergeSnippets(category_id, std::move(new_snippets)); |
| + ClearExpiredSnippets(category_id); |
| + // If there are more snippets than we want to show, delete the extra ones. |
| + CategoryContent* category = &categories_[category_id]; |
| + if (category->snippets.size() > kMaxSnippetCount) { |
| + NTPSnippet::PtrVector to_delete( |
| + std::make_move_iterator(category->snippets.begin() + |
| + kMaxSnippetCount), |
| + std::make_move_iterator(category->snippets.end())); |
| + category->snippets.resize(kMaxSnippetCount); |
| + database_->DeleteSnippets(to_delete); |
| + } |
| + |
| + // Report category as available, even if empty. It is provided by the |
| + // service, and is no longer loading. |
| + UpdateCategoryStatus(category_id, CategoryStatus::AVAILABLE); |
| + } |
| } |
| - ClearExpiredSnippets(); |
| + // Go back and expire articles out of categories that weren't updated. If |
| + // |snippets| was not provided due to an error, that's all categories. |
| + std::vector<Category> categories_to_remove_; |
| + for (auto& item : categories_) { |
| + Category category_id = item.first; |
| + CategoryContent& category = item.second; |
| + if (!snippets || (snippets->find(category_id) == snippets->end())) |
| + continue; // skip anything we processed in the first loop. |
|
Marc Treib
2016/08/22 15:06:46
Why?
I think the main purpose of calling ClearExpi
sfiera
2016/08/24 14:35:56
I'd sort of misunderstood ClearExpiredSnippets(),
|
| + |
| + ClearExpiredSnippets(category_id); |
| + if (category.snippets.empty() && (category_id != articles_category_)) { |
| + categories_to_remove_.push_back(category_id); |
| + } |
| + } |
| - // If there are more snippets than we want to show, delete the extra ones. |
| - if (snippets_.size() > kMaxSnippetCount) { |
| - NTPSnippet::PtrVector to_delete( |
| - std::make_move_iterator(snippets_.begin() + kMaxSnippetCount), |
| - std::make_move_iterator(snippets_.end())); |
| - snippets_.resize(kMaxSnippetCount); |
| - database_->DeleteSnippets(to_delete); |
| + // If a category (other than ARTICLES) is now empty, and it was not present in |
| + // |snippets|, then report that the snippets service no longer provides that |
| + // category. If the category is now |empty| but it was in |snippets|, then we |
|
Marc Treib
2016/08/22 15:06:46
nit: no pipes around empty
sfiera
2016/08/24 14:35:57
Done.
|
| + // will continue to report it as AVAILABLE. |
| + for (Category category : categories_to_remove_) { |
| + UpdateCategoryStatus(category, CategoryStatus::NOT_PROVIDED); |
| + categories_.erase(category); |
| } |
| + // TODO(sfiera): equivalent metrics for non-articles. |
| + const CategoryContent& category = categories_[articles_category_]; |
| UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.NumArticles", |
| - snippets_.size()); |
| - if (snippets_.empty() && !dismissed_snippets_.empty()) { |
| + category.snippets.size()); |
| + if (category.snippets.empty() && !category.dismissed.empty()) { |
| UMA_HISTOGRAM_COUNTS("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded", |
| - dismissed_snippets_.size()); |
| + category.dismissed.size()); |
| } |
| - UpdateCategoryStatus(CategoryStatus::AVAILABLE); |
| NotifyNewSuggestions(); |
| } |
| -void NTPSnippetsService::MergeSnippets(NTPSnippet::PtrVector new_snippets) { |
| +void NTPSnippetsService::MergeSnippets(Category category_id, |
| + NTPSnippet::PtrVector new_snippets) { |
| DCHECK(ready()); |
| + CategoryContent* category = &categories_[category_id]; |
| // Remove new snippets that we already have, or that have been dismissed. |
| std::set<std::string> old_snippet_ids; |
| - InsertAllIDs(dismissed_snippets_, &old_snippet_ids); |
| - InsertAllIDs(snippets_, &old_snippet_ids); |
| + InsertAllIDs(category->dismissed, &old_snippet_ids); |
| + InsertAllIDs(category->snippets, &old_snippet_ids); |
| new_snippets.erase( |
| std::remove_if( |
| new_snippets.begin(), new_snippets.end(), |
| @@ -560,13 +634,15 @@ void NTPSnippetsService::MergeSnippets(NTPSnippet::PtrVector new_snippets) { |
| } |
| } |
| - // Save the new snippets to the DB. |
| - database_->SaveSnippets(new_snippets); |
| + // Save new articles to the DB. |
| + // TODO(sfiera): save non-articles to DB too. |
| + if (category_id == articles_category_) |
| + database_->SaveSnippets(new_snippets); |
| // Insert the new snippets at the front. |
| - snippets_.insert(snippets_.begin(), |
| - std::make_move_iterator(new_snippets.begin()), |
| - std::make_move_iterator(new_snippets.end())); |
| + category->snippets.insert(category->snippets.begin(), |
| + std::make_move_iterator(new_snippets.begin()), |
| + std::make_move_iterator(new_snippets.end())); |
| } |
| std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const { |
| @@ -589,94 +665,109 @@ void NTPSnippetsService::StoreSnippetHostsToPrefs( |
| pref_service_->Set(prefs::kSnippetHosts, list); |
| } |
| -void NTPSnippetsService::ClearExpiredSnippets() { |
| +void NTPSnippetsService::ClearExpiredSnippets(Category category_id) { |
| + DCHECK(categories_.find(category_id) != categories_.end()); |
| + CategoryContent* category = &categories_[category_id]; |
| + |
| base::Time expiry = base::Time::Now(); |
| // Move expired snippets over into |to_delete|. |
| NTPSnippet::PtrVector to_delete; |
| - for (std::unique_ptr<NTPSnippet>& snippet : snippets_) { |
| + for (std::unique_ptr<NTPSnippet>& snippet : category->snippets) { |
| if (snippet->expiry_date() <= expiry) |
| to_delete.emplace_back(std::move(snippet)); |
| } |
| - Compact(&snippets_); |
| + Compact(&category->snippets); |
| // Move expired dismissed snippets over into |to_delete| as well. |
| - for (std::unique_ptr<NTPSnippet>& snippet : dismissed_snippets_) { |
| + for (std::unique_ptr<NTPSnippet>& snippet : category->dismissed) { |
| if (snippet->expiry_date() <= expiry) |
| to_delete.emplace_back(std::move(snippet)); |
| } |
| - Compact(&dismissed_snippets_); |
| + Compact(&category->dismissed); |
| // Finally, actually delete the removed snippets from the DB. |
| - database_->DeleteSnippets(to_delete); |
| + if (category_id == articles_category_) |
| + database_->DeleteSnippets(to_delete); |
| - // If there are any snippets left, schedule a timer for the next expiry. |
| - if (snippets_.empty() && dismissed_snippets_.empty()) |
| + // Unless there are no snippets left, schedule a timer for the next expiry. |
| + if (category->snippets.empty() && category->dismissed.empty()) |
| return; |
| base::Time next_expiry = base::Time::Max(); |
| - for (const auto& snippet : snippets_) { |
| + for (const auto& snippet : category->snippets) { |
| if (snippet->expiry_date() < next_expiry) |
| next_expiry = snippet->expiry_date(); |
| } |
| - for (const auto& snippet : dismissed_snippets_) { |
| + for (const auto& snippet : category->dismissed) { |
| if (snippet->expiry_date() < next_expiry) |
| next_expiry = snippet->expiry_date(); |
| } |
| + |
| DCHECK_GT(next_expiry, expiry); |
| expiry_timer_.Start(FROM_HERE, next_expiry - expiry, |
| base::Bind(&NTPSnippetsService::ClearExpiredSnippets, |
| - base::Unretained(this))); |
| + base::Unretained(this), category_id)); |
| } |
| void NTPSnippetsService::OnSnippetImageFetchedFromDatabase( |
| const ImageFetchedCallback& callback, |
| - const std::string& snippet_id, |
| + const std::string& suggestion_id, |
| std::string data) { |
| // |image_decoder_| is null in tests. |
| if (image_decoder_ && !data.empty()) { |
| image_decoder_->DecodeImage( |
| std::move(data), |
| base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromDatabase, |
| - base::Unretained(this), callback, snippet_id)); |
| + base::Unretained(this), callback, suggestion_id)); |
| return; |
| } |
| // Fetching from the DB failed; start a network fetch. |
| - FetchSnippetImageFromNetwork(snippet_id, callback); |
| + FetchSnippetImageFromNetwork(suggestion_id, callback); |
| } |
| void NTPSnippetsService::OnSnippetImageDecodedFromDatabase( |
| const ImageFetchedCallback& callback, |
| - const std::string& snippet_id, |
| + const std::string& suggestion_id, |
| const gfx::Image& image) { |
| if (!image.IsEmpty()) { |
| - callback.Run(MakeUniqueID(provided_category_, snippet_id), image); |
| + callback.Run(suggestion_id, image); |
| return; |
| } |
| // If decoding the image failed, delete the DB entry. |
| - database_->DeleteImage(snippet_id); |
| + database_->DeleteImage(suggestion_id); |
| - FetchSnippetImageFromNetwork(snippet_id, callback); |
| + FetchSnippetImageFromNetwork(suggestion_id, callback); |
| } |
| void NTPSnippetsService::FetchSnippetImageFromNetwork( |
| - const std::string& snippet_id, |
| + const std::string& suggestion_id, |
| const ImageFetchedCallback& callback) { |
| + Category category_id = GetCategoryFromUniqueID(suggestion_id); |
| + std::string snippet_id = GetWithinCategoryIDFromUniqueID(suggestion_id); |
| + |
| + auto category_it = categories_.find(category_id); |
| + if (category_it == categories_.end()) { |
| + callback.Run(suggestion_id, gfx::Image()); |
| + return; |
| + } |
| + |
| + const CategoryContent& category = category_it->second; |
| auto it = |
| - std::find_if(snippets_.begin(), snippets_.end(), |
| + std::find_if(category.snippets.begin(), category.snippets.end(), |
| [&snippet_id](const std::unique_ptr<NTPSnippet>& snippet) { |
| return snippet->id() == snippet_id; |
| }); |
| - if (it == snippets_.end() || |
| + if (it == category.snippets.end() || |
| !thumbnail_requests_throttler_.DemandQuotaForRequest( |
| /*interactive_request=*/true)) { |
| // Return an empty image. Directly, this is never synchronous with the |
| // original FetchSuggestionImage() call - an asynchronous database query has |
| // happened in the meantime. |
| - OnSnippetImageDecodedFromNetwork(callback, snippet_id, gfx::Image()); |
| + OnSnippetImageDecodedFromNetwork(callback, suggestion_id, gfx::Image()); |
| return; |
| } |
| @@ -689,16 +780,16 @@ void NTPSnippetsService::FetchSnippetImageFromNetwork( |
| // is an ImageFetcherDelegate) and then also |
| // OnSnippetImageDecodedFromNetwork() after the raw data gets decoded. |
| image_fetcher_->StartOrQueueNetworkRequest( |
| - snippet.id(), snippet.salient_image_url(), |
| + suggestion_id, snippet.salient_image_url(), |
| base::Bind(&NTPSnippetsService::OnSnippetImageDecodedFromNetwork, |
| base::Unretained(this), callback)); |
| } |
| void NTPSnippetsService::OnSnippetImageDecodedFromNetwork( |
| const ImageFetchedCallback& callback, |
| - const std::string& snippet_id, |
| + const std::string& suggestion_id, |
| const gfx::Image& image) { |
| - callback.Run(MakeUniqueID(provided_category_, snippet_id), image); |
| + callback.Run(suggestion_id, image); |
| } |
| void NTPSnippetsService::EnterStateEnabled(bool fetch_snippets) { |
| @@ -707,8 +798,9 @@ void NTPSnippetsService::EnterStateEnabled(bool fetch_snippets) { |
| // FetchSnippets should set the status to |AVAILABLE_LOADING| if relevant, |
| // otherwise we transition to |AVAILABLE| here. |
| - if (category_status_ != CategoryStatus::AVAILABLE_LOADING) |
| - UpdateCategoryStatus(CategoryStatus::AVAILABLE); |
| + if (categories_[articles_category_].status != |
| + CategoryStatus::AVAILABLE_LOADING) |
|
Marc Treib
2016/08/22 15:06:46
Braces please
sfiera
2016/08/24 14:35:57
Done.
|
| + UpdateCategoryStatus(articles_category_, CategoryStatus::AVAILABLE); |
| // If host restrictions are enabled, register for host list updates. |
| // |suggestions_service_| can be null in tests. |
| @@ -722,8 +814,22 @@ void NTPSnippetsService::EnterStateEnabled(bool fetch_snippets) { |
| } |
| void NTPSnippetsService::EnterStateDisabled() { |
| - ClearCachedSuggestionsForDebugging(provided_category_); |
| - ClearDismissedSuggestionsForDebugging(provided_category_); |
| + std::vector<Category> category_list; |
| + for (const auto& item : categories_) { |
| + Category category_id = item.first; |
| + category_list.push_back(category_id); |
| + } |
| + |
| + // Empty the ARTICLES category and remove all others, since they may or may |
| + // not be personalized. |
| + for (Category category_id : category_list) { |
| + ClearCachedSuggestionsForDebugging(category_id); |
| + ClearDismissedSuggestionsForDebugging(category_id); |
| + if (category_id != articles_category_) { |
| + UpdateCategoryStatus(category_id, CategoryStatus::NOT_PROVIDED); |
| + categories_.erase(category_id); |
| + } |
| + } |
| expiry_timer_.Stop(); |
| suggestions_service_subscription_.reset(); |
| @@ -763,22 +869,22 @@ void NTPSnippetsService::OnDisabledReasonChanged( |
| switch (disabled_reason) { |
| case DisabledReason::NONE: |
| // Do not change the status. That will be done in EnterStateEnabled() |
| - EnterState(State::READY, category_status_); |
| + EnterState(State::READY); |
| break; |
| case DisabledReason::EXPLICITLY_DISABLED: |
| - EnterState(State::DISABLED, CategoryStatus::CATEGORY_EXPLICITLY_DISABLED); |
| + EnterState(State::DISABLED); |
| + UpdateAllCategoryStatus(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED); |
| break; |
| case DisabledReason::SIGNED_OUT: |
| - EnterState(State::DISABLED, CategoryStatus::SIGNED_OUT); |
| + EnterState(State::DISABLED); |
| + UpdateAllCategoryStatus(CategoryStatus::SIGNED_OUT); |
| break; |
| } |
| } |
| -void NTPSnippetsService::EnterState(State state, CategoryStatus status) { |
| - UpdateCategoryStatus(status); |
| - |
| +void NTPSnippetsService::EnterState(State state) { |
| if (state == state_) |
| return; |
| @@ -791,7 +897,10 @@ void NTPSnippetsService::EnterState(State state, CategoryStatus status) { |
| case State::READY: { |
| DCHECK(state_ == State::NOT_INITED || state_ == State::DISABLED); |
| - bool fetch_snippets = snippets_.empty() || fetch_after_load_; |
| + // TODO(sfiera): always fetch, because there might be server-side |
| + // categories we're interested in? |
| + bool fetch_snippets = |
| + categories_[articles_category_].snippets.empty() || fetch_after_load_; |
| DVLOG(1) << "Entering state: READY"; |
| state_ = State::READY; |
| fetch_after_load_ = false; |
| @@ -816,32 +925,50 @@ void NTPSnippetsService::EnterState(State state, CategoryStatus status) { |
| } |
| void NTPSnippetsService::NotifyNewSuggestions() { |
| - std::vector<ContentSuggestion> result; |
| - for (const std::unique_ptr<NTPSnippet>& snippet : snippets_) { |
| - if (!snippet->is_complete()) |
| - continue; |
| - ContentSuggestion suggestion( |
| - MakeUniqueID(provided_category_, snippet->id()), |
| - snippet->best_source().url); |
| - suggestion.set_amp_url(snippet->best_source().amp_url); |
| - suggestion.set_title(base::UTF8ToUTF16(snippet->title())); |
| - suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet())); |
| - suggestion.set_publish_date(snippet->publish_date()); |
| - suggestion.set_publisher_name( |
| - base::UTF8ToUTF16(snippet->best_source().publisher_name)); |
| - suggestion.set_score(snippet->score()); |
| - result.emplace_back(std::move(suggestion)); |
| + for (auto& item : categories_) { |
| + Category category_id = item.first; |
| + CategoryContent& category = item.second; |
| + |
| + std::vector<ContentSuggestion> result; |
| + for (const std::unique_ptr<NTPSnippet>& snippet : category.snippets) { |
| + // TODO(sfiera): if a snippet is not going to be displayed, move it |
| + // directly to category.dismissed on fetch. Otherwise, we might prune |
| + // other snippets to get down to kMaxSnippetCount, only to hide one of the |
| + // incomplete ones we kept. |
| + if (!snippet->is_complete()) |
| + continue; |
| + ContentSuggestion suggestion(MakeUniqueID(category_id, snippet->id()), |
| + snippet->best_source().url); |
| + suggestion.set_amp_url(snippet->best_source().amp_url); |
| + suggestion.set_title(base::UTF8ToUTF16(snippet->title())); |
| + suggestion.set_snippet_text(base::UTF8ToUTF16(snippet->snippet())); |
| + suggestion.set_publish_date(snippet->publish_date()); |
| + suggestion.set_publisher_name( |
| + base::UTF8ToUTF16(snippet->best_source().publisher_name)); |
| + suggestion.set_score(snippet->score()); |
| + result.emplace_back(std::move(suggestion)); |
| + } |
| + // TODO(sfiera): detect when a category is unchanged and don't notify the |
| + // observer in that case. |
| + observer()->OnNewSuggestions(this, category_id, std::move(result)); |
| } |
| - observer()->OnNewSuggestions(this, provided_category_, std::move(result)); |
| } |
| -void NTPSnippetsService::UpdateCategoryStatus(CategoryStatus status) { |
| - if (status == category_status_) |
| +void NTPSnippetsService::UpdateCategoryStatus(Category category_id, |
| + CategoryStatus status) { |
| + DCHECK(categories_.find(category_id) != categories_.end()); |
| + CategoryContent& category = categories_[category_id]; |
| + if (status == category.status) |
| return; |
| - category_status_ = status; |
| - observer()->OnCategoryStatusChanged(this, provided_category_, |
| - category_status_); |
| + category.status = status; |
| + observer()->OnCategoryStatusChanged(this, category_id, category.status); |
| +} |
| + |
| +void NTPSnippetsService::UpdateAllCategoryStatus(CategoryStatus status) { |
| + for (const auto& category : categories_) { |
| + UpdateCategoryStatus(category.first, status); |
| + } |
| } |
| } // namespace ntp_snippets |