| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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/content_suggestions_service.h" | 5 #include "components/ntp_snippets/content_suggestions_service.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <iterator> | 8 #include <iterator> |
| 9 | 9 |
| 10 #include "base/bind.h" | 10 #include "base/bind.h" |
| 11 #include "base/strings/string_number_conversions.h" | 11 #include "base/strings/string_number_conversions.h" |
| 12 #include "ui/gfx/image/image.h" | 12 #include "ui/gfx/image/image.h" |
| 13 | 13 |
| 14 namespace ntp_snippets { | 14 namespace ntp_snippets { |
| 15 | 15 |
| 16 bool ContentSuggestionsService::CompareCategoriesByID::operator()( | 16 bool ContentSuggestionsService::CompareCategoriesByID::operator()( |
| 17 const Category& left, | 17 const Category& left, |
| 18 const Category& right) const { | 18 const Category& right) const { |
| 19 return left.id() < right.id(); | 19 return left.id() < right.id(); |
| 20 } | 20 } |
| 21 | 21 |
| 22 ContentSuggestionsService::ContentSuggestionsService(State state) | 22 ContentSuggestionsService::ContentSuggestionsService(State state) |
| 23 : state_(state) {} | 23 : state_(state) {} |
| 24 | 24 |
| 25 ContentSuggestionsService::~ContentSuggestionsService() {} | 25 ContentSuggestionsService::~ContentSuggestionsService() {} |
| 26 | 26 |
| 27 void ContentSuggestionsService::Shutdown() { | 27 void ContentSuggestionsService::Shutdown() { |
| 28 DCHECK(providers_.empty()); | 28 DCHECK(providers_by_category_.empty()); |
| 29 DCHECK(categories_.empty()); | 29 DCHECK(categories_.empty()); |
| 30 DCHECK(suggestions_by_category_.empty()); | 30 DCHECK(suggestions_by_category_.empty()); |
| 31 DCHECK(id_category_map_.empty()); | 31 DCHECK(id_category_map_.empty()); |
| 32 state_ = State::DISABLED; | 32 state_ = State::DISABLED; |
| 33 FOR_EACH_OBSERVER(Observer, observers_, ContentSuggestionsServiceShutdown()); | 33 FOR_EACH_OBSERVER(Observer, observers_, ContentSuggestionsServiceShutdown()); |
| 34 } | 34 } |
| 35 | 35 |
| 36 CategoryStatus ContentSuggestionsService::GetCategoryStatus( | 36 CategoryStatus ContentSuggestionsService::GetCategoryStatus( |
| 37 Category category) const { | 37 Category category) const { |
| 38 if (state_ == State::DISABLED) { | 38 if (state_ == State::DISABLED) { |
| 39 return CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED; | 39 return CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED; |
| 40 } | 40 } |
| 41 | 41 |
| 42 auto iterator = providers_.find(category); | 42 auto iterator = providers_by_category_.find(category); |
| 43 if (iterator == providers_.end()) | 43 if (iterator == providers_by_category_.end()) |
| 44 return CategoryStatus::NOT_PROVIDED; | 44 return CategoryStatus::NOT_PROVIDED; |
| 45 | 45 |
| 46 return iterator->second->GetCategoryStatus(category); | 46 return iterator->second->GetCategoryStatus(category); |
| 47 } | 47 } |
| 48 | 48 |
| 49 const std::vector<ContentSuggestion>& | 49 const std::vector<ContentSuggestion>& |
| 50 ContentSuggestionsService::GetSuggestionsForCategory(Category category) const { | 50 ContentSuggestionsService::GetSuggestionsForCategory(Category category) const { |
| 51 auto iterator = suggestions_by_category_.find(category); | 51 auto iterator = suggestions_by_category_.find(category); |
| 52 if (iterator == suggestions_by_category_.end()) | 52 if (iterator == suggestions_by_category_.end()) |
| 53 return no_suggestions_; | 53 return no_suggestions_; |
| 54 return iterator->second; | 54 return iterator->second; |
| 55 } | 55 } |
| 56 | 56 |
| 57 void ContentSuggestionsService::FetchSuggestionImage( | 57 void ContentSuggestionsService::FetchSuggestionImage( |
| 58 const std::string& suggestion_id, | 58 const std::string& suggestion_id, |
| 59 const ImageFetchedCallback& callback) { | 59 const ImageFetchedCallback& callback) { |
| 60 if (!id_category_map_.count(suggestion_id)) { | 60 if (!id_category_map_.count(suggestion_id)) { |
| 61 LOG(WARNING) << "Requested image for unknown suggestion " << suggestion_id; | 61 LOG(WARNING) << "Requested image for unknown suggestion " << suggestion_id; |
| 62 callback.Run(suggestion_id, gfx::Image()); | 62 callback.Run(suggestion_id, gfx::Image()); |
| 63 return; | 63 return; |
| 64 } | 64 } |
| 65 Category category = id_category_map_.at(suggestion_id); | 65 Category category = id_category_map_.at(suggestion_id); |
| 66 if (!providers_.count(category)) { | 66 if (!providers_by_category_.count(category)) { |
| 67 LOG(WARNING) << "Requested image for suggestion " << suggestion_id | 67 LOG(WARNING) << "Requested image for suggestion " << suggestion_id |
| 68 << " for unavailable category " << category; | 68 << " for unavailable category " << category; |
| 69 callback.Run(suggestion_id, gfx::Image()); | 69 callback.Run(suggestion_id, gfx::Image()); |
| 70 return; | 70 return; |
| 71 } | 71 } |
| 72 providers_[category]->FetchSuggestionImage(suggestion_id, callback); | 72 providers_by_category_[category]->FetchSuggestionImage(suggestion_id, |
| 73 callback); |
| 73 } | 74 } |
| 74 | 75 |
| 75 void ContentSuggestionsService::ClearCachedSuggestionsForDebugging() { | 76 void ContentSuggestionsService::ClearCachedSuggestionsForDebugging() { |
| 76 suggestions_by_category_.clear(); | 77 suggestions_by_category_.clear(); |
| 77 id_category_map_.clear(); | 78 id_category_map_.clear(); |
| 78 for (auto& category_provider_pair : providers_) { | 79 for (auto& category_provider_pair : providers_by_category_) { |
| 79 category_provider_pair.second->ClearCachedSuggestionsForDebugging(); | 80 category_provider_pair.second->ClearCachedSuggestionsForDebugging(); |
| 80 } | 81 } |
| 81 FOR_EACH_OBSERVER(Observer, observers_, OnNewSuggestions()); | 82 FOR_EACH_OBSERVER(Observer, observers_, OnNewSuggestions()); |
| 82 } | 83 } |
| 83 | 84 |
| 84 void ContentSuggestionsService::ClearDismissedSuggestionsForDebugging() { | 85 void ContentSuggestionsService::ClearDismissedSuggestionsForDebugging() { |
| 85 for (auto& category_provider_pair : providers_) { | 86 for (auto& category_provider_pair : providers_by_category_) { |
| 86 category_provider_pair.second->ClearDismissedSuggestionsForDebugging(); | 87 category_provider_pair.second->ClearDismissedSuggestionsForDebugging(); |
| 87 } | 88 } |
| 88 } | 89 } |
| 89 | 90 |
| 90 void ContentSuggestionsService::DismissSuggestion( | 91 void ContentSuggestionsService::DismissSuggestion( |
| 91 const std::string& suggestion_id) { | 92 const std::string& suggestion_id) { |
| 92 if (!id_category_map_.count(suggestion_id)) { | 93 if (!id_category_map_.count(suggestion_id)) { |
| 93 LOG(WARNING) << "Dismissed unknown suggestion " << suggestion_id; | 94 LOG(WARNING) << "Dismissed unknown suggestion " << suggestion_id; |
| 94 return; | 95 return; |
| 95 } | 96 } |
| 96 Category category = id_category_map_.at(suggestion_id); | 97 Category category = id_category_map_.at(suggestion_id); |
| 97 if (!providers_.count(category)) { | 98 if (!providers_by_category_.count(category)) { |
| 98 LOG(WARNING) << "Dismissed suggestion " << suggestion_id | 99 LOG(WARNING) << "Dismissed suggestion " << suggestion_id |
| 99 << " for unavailable category " << category; | 100 << " for unavailable category " << category; |
| 100 return; | 101 return; |
| 101 } | 102 } |
| 102 providers_[category]->DismissSuggestion(suggestion_id); | 103 providers_by_category_[category]->DismissSuggestion(suggestion_id); |
| 103 | 104 |
| 104 // Remove the suggestion locally. | 105 // Remove the suggestion locally. |
| 105 id_category_map_.erase(suggestion_id); | 106 id_category_map_.erase(suggestion_id); |
| 106 std::vector<ContentSuggestion>* suggestions = | 107 std::vector<ContentSuggestion>* suggestions = |
| 107 &suggestions_by_category_[category]; | 108 &suggestions_by_category_[category]; |
| 108 auto position = | 109 auto position = |
| 109 std::find_if(suggestions->begin(), suggestions->end(), | 110 std::find_if(suggestions->begin(), suggestions->end(), |
| 110 [&suggestion_id](const ContentSuggestion& suggestion) { | 111 [&suggestion_id](const ContentSuggestion& suggestion) { |
| 111 return suggestion_id == suggestion.id(); | 112 return suggestion_id == suggestion.id(); |
| 112 }); | 113 }); |
| (...skipping 12 matching lines...) Expand all Loading... |
| 125 observers_.RemoveObserver(observer); | 126 observers_.RemoveObserver(observer); |
| 126 } | 127 } |
| 127 | 128 |
| 128 void ContentSuggestionsService::RegisterProvider( | 129 void ContentSuggestionsService::RegisterProvider( |
| 129 ContentSuggestionsProvider* provider) { | 130 ContentSuggestionsProvider* provider) { |
| 130 // TODO(pke): When NTPSnippetsService is purely a provider, think about | 131 // TODO(pke): When NTPSnippetsService is purely a provider, think about |
| 131 // removing this state check. | 132 // removing this state check. |
| 132 if (state_ == State::DISABLED) | 133 if (state_ == State::DISABLED) |
| 133 return; | 134 return; |
| 134 | 135 |
| 136 provider->SetObserver(this); |
| 135 for (Category category : provider->GetProvidedCategories()) { | 137 for (Category category : provider->GetProvidedCategories()) { |
| 136 DCHECK_EQ(0ul, providers_.count(category)); | 138 DCHECK_NE(CategoryStatus::NOT_PROVIDED, |
| 137 providers_[category] = provider; | 139 provider->GetCategoryStatus(category)); |
| 138 categories_.push_back(category); | 140 RegisterCategoryIfRequired(provider, category); |
| 139 if (IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) { | |
| 140 suggestions_by_category_[category] = std::vector<ContentSuggestion>(); | |
| 141 } | |
| 142 NotifyCategoryStatusChanged(category); | 141 NotifyCategoryStatusChanged(category); |
| 143 } | 142 } |
| 144 std::sort(categories_.begin(), categories_.end(), | |
| 145 [this](const Category& left, const Category& right) { | |
| 146 return category_factory_.CompareCategories(left, right); | |
| 147 }); | |
| 148 provider->SetObserver(this); | |
| 149 } | 143 } |
| 150 | 144 |
| 151 //////////////////////////////////////////////////////////////////////////////// | 145 //////////////////////////////////////////////////////////////////////////////// |
| 152 // Private methods | 146 // Private methods |
| 153 | 147 |
| 154 void ContentSuggestionsService::OnNewSuggestions( | 148 void ContentSuggestionsService::OnNewSuggestions( |
| 155 Category changed_category, | 149 ContentSuggestionsProvider* provider, |
| 150 Category category, |
| 156 std::vector<ContentSuggestion> new_suggestions) { | 151 std::vector<ContentSuggestion> new_suggestions) { |
| 157 DCHECK(IsCategoryRegistered(changed_category)); | 152 if (RegisterCategoryIfRequired(provider, category)) { |
| 153 NotifyCategoryStatusChanged(category); |
| 154 } |
| 158 | 155 |
| 159 for (const ContentSuggestion& suggestion : | 156 for (const ContentSuggestion& suggestion : |
| 160 suggestions_by_category_[changed_category]) { | 157 suggestions_by_category_[category]) { |
| 161 id_category_map_.erase(suggestion.id()); | 158 id_category_map_.erase(suggestion.id()); |
| 162 } | 159 } |
| 163 | 160 |
| 164 for (const ContentSuggestion& suggestion : new_suggestions) { | 161 for (const ContentSuggestion& suggestion : new_suggestions) { |
| 165 id_category_map_.insert(std::make_pair(suggestion.id(), changed_category)); | 162 id_category_map_.insert(std::make_pair(suggestion.id(), category)); |
| 166 } | 163 } |
| 167 | 164 |
| 168 suggestions_by_category_[changed_category] = std::move(new_suggestions); | 165 suggestions_by_category_[category] = std::move(new_suggestions); |
| 169 | 166 |
| 170 FOR_EACH_OBSERVER(Observer, observers_, OnNewSuggestions()); | 167 FOR_EACH_OBSERVER(Observer, observers_, OnNewSuggestions()); |
| 171 } | 168 } |
| 172 | 169 |
| 173 void ContentSuggestionsService::OnCategoryStatusChanged( | 170 void ContentSuggestionsService::OnCategoryStatusChanged( |
| 174 Category changed_category, | 171 ContentSuggestionsProvider* provider, |
| 172 Category category, |
| 175 CategoryStatus new_status) { | 173 CategoryStatus new_status) { |
| 176 if (!IsCategoryStatusAvailable(new_status)) { | 174 if (!IsCategoryStatusAvailable(new_status)) { |
| 177 for (const ContentSuggestion& suggestion : | 175 for (const ContentSuggestion& suggestion : |
| 178 suggestions_by_category_[changed_category]) { | 176 suggestions_by_category_[category]) { |
| 179 id_category_map_.erase(suggestion.id()); | 177 id_category_map_.erase(suggestion.id()); |
| 180 } | 178 } |
| 181 suggestions_by_category_.erase(changed_category); | 179 suggestions_by_category_.erase(category); |
| 182 } | 180 } |
| 183 NotifyCategoryStatusChanged(changed_category); | 181 if (new_status == CategoryStatus::NOT_PROVIDED) { |
| 182 auto providers_it = providers_by_category_.find(category); |
| 183 DCHECK(providers_it != providers_by_category_.end()); |
| 184 DCHECK_EQ(provider, providers_it->second); |
| 185 providers_by_category_.erase(providers_it); |
| 186 categories_.erase( |
| 187 std::find(categories_.begin(), categories_.end(), category)); |
| 188 } else { |
| 189 RegisterCategoryIfRequired(provider, category); |
| 190 DCHECK_EQ(new_status, provider->GetCategoryStatus(category)); |
| 191 } |
| 192 NotifyCategoryStatusChanged(category); |
| 184 } | 193 } |
| 185 | 194 |
| 186 void ContentSuggestionsService::OnProviderShutdown( | 195 void ContentSuggestionsService::OnProviderShutdown( |
| 187 ContentSuggestionsProvider* provider) { | 196 ContentSuggestionsProvider* provider) { |
| 188 for (Category category : provider->GetProvidedCategories()) { | 197 for (Category category : provider->GetProvidedCategories()) { |
| 189 auto iterator = std::find(categories_.begin(), categories_.end(), category); | 198 auto iterator = std::find(categories_.begin(), categories_.end(), category); |
| 190 DCHECK(iterator != categories_.end()); | 199 DCHECK(iterator != categories_.end()); |
| 191 categories_.erase(iterator); | 200 categories_.erase(iterator); |
| 192 for (const ContentSuggestion& suggestion : | 201 for (const ContentSuggestion& suggestion : |
| 193 suggestions_by_category_[category]) { | 202 suggestions_by_category_[category]) { |
| 194 id_category_map_.erase(suggestion.id()); | 203 id_category_map_.erase(suggestion.id()); |
| 195 } | 204 } |
| 196 suggestions_by_category_.erase(category); | 205 suggestions_by_category_.erase(category); |
| 197 providers_.erase(category); | 206 providers_by_category_.erase(category); |
| 198 NotifyCategoryStatusChanged(category); | 207 NotifyCategoryStatusChanged(category); |
| 199 } | 208 } |
| 200 } | 209 } |
| 201 | 210 |
| 202 bool ContentSuggestionsService::IsCategoryRegistered(Category category) const { | 211 bool ContentSuggestionsService::RegisterCategoryIfRequired( |
| 203 return std::find(categories_.begin(), categories_.end(), category) != | 212 ContentSuggestionsProvider* provider, |
| 204 categories_.end(); | 213 Category category) { |
| 214 auto it = providers_by_category_.find(category); |
| 215 if (it != providers_by_category_.end()) { |
| 216 DCHECK_EQ(it->second, provider); |
| 217 return false; |
| 218 } |
| 219 |
| 220 providers_by_category_[category] = provider; |
| 221 categories_.push_back(category); |
| 222 std::sort(categories_.begin(), categories_.end(), |
| 223 [this](const Category& left, const Category& right) { |
| 224 return category_factory_.CompareCategories(left, right); |
| 225 }); |
| 226 if (IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) { |
| 227 suggestions_by_category_.insert( |
| 228 std::make_pair(category, std::vector<ContentSuggestion>())); |
| 229 } |
| 230 return true; |
| 205 } | 231 } |
| 206 | 232 |
| 207 void ContentSuggestionsService::NotifyCategoryStatusChanged(Category category) { | 233 void ContentSuggestionsService::NotifyCategoryStatusChanged(Category category) { |
| 208 FOR_EACH_OBSERVER( | 234 FOR_EACH_OBSERVER( |
| 209 Observer, observers_, | 235 Observer, observers_, |
| 210 OnCategoryStatusChanged(category, GetCategoryStatus(category))); | 236 OnCategoryStatusChanged(category, GetCategoryStatus(category))); |
| 211 } | 237 } |
| 212 | 238 |
| 213 } // namespace ntp_snippets | 239 } // namespace ntp_snippets |
| OLD | NEW |