Index: components/ntp_snippets/content_suggestions_service.cc |
diff --git a/components/ntp_snippets/content_suggestions_service.cc b/components/ntp_snippets/content_suggestions_service.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..cf35d4554007b8d75bfbadbb877c9c6dac8ec5ba |
--- /dev/null |
+++ b/components/ntp_snippets/content_suggestions_service.cc |
@@ -0,0 +1,199 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "components/ntp_snippets/content_suggestions_service.h" |
+ |
+#include <algorithm> |
+#include <iterator> |
+ |
+#include "base/bind.h" |
+#include "base/strings/string_number_conversions.h" |
+#include "ui/gfx/image/image.h" |
+ |
+namespace ntp_snippets { |
+ |
+namespace { |
+ |
+// Helper method get the provider_type from a combined ID. |
tschumann
2016/07/01 09:37:11
nit: let's keep the comment focused on the essenti
Philipp Keck
2016/07/01 13:00:04
Done.
|
+ContentSuggestionsProviderType GetProviderTypeFromCombinedID( |
+ const std::string& id) { |
+ size_t colon_index = id.find(":"); |
+ DCHECK_NE(std::string::npos, colon_index); |
+ int provider_type; |
+ DCHECK(base::StringToInt(id.substr(0, colon_index), &provider_type)); |
+ DCHECK(0 <= provider_type && |
+ provider_type < int(ContentSuggestionsProviderType::COUNT)); |
+ return ContentSuggestionsProviderType(provider_type); |
+} |
+ |
+// Helper method get the original ID from a combined ID. |
+std::string GetIDFromCombinedID(const std::string& id) { |
+ size_t colon_index = id.find(":"); |
+ DCHECK_NE(std::string::npos, colon_index); |
+ return id.substr(colon_index + 1); |
+} |
+ |
+// Helper method to remove all suggestions of the given |provider_type| from |
+// the given |suggestions|. Returns true if anything changed. |
+bool RemoveSuggestionsOfProvider(std::vector<ContentSuggestion>* suggestions, |
+ ContentSuggestionsProviderType provider_type) { |
+ auto remove_from = std::remove_if( |
+ suggestions->begin(), suggestions->end(), |
+ [provider_type](const ContentSuggestion& s) { |
+ return GetProviderTypeFromCombinedID(s.id()) == provider_type; |
+ }); |
+ if (remove_from == suggestions->end()) { |
+ return false; |
+ } |
+ suggestions->erase(remove_from, suggestions->end()); |
+ return true; |
+} |
+ |
+} // namespace |
+ |
+ContentSuggestionsService::ContentSuggestionsService(Enabled enabled) |
+ : enabled_(enabled) {} |
+ |
+ContentSuggestionsService::~ContentSuggestionsService() {} |
+ |
+void ContentSuggestionsService::Shutdown() { |
+ FOR_EACH_OBSERVER(Observer, observers_, ContentSuggestionsServiceShutdown()); |
+ observers_.Clear(); |
+ for (auto& provider : providers_) { |
tschumann
2016/07/01 09:37:11
Too bad we don't have value_view in Chrome (https:
Philipp Keck
2016/07/01 13:00:04
Acknowledged.
|
+ provider.second->SetObserver(nullptr); |
+ } |
+ providers_.clear(); |
+ enabled_ = Enabled::Disable; |
+} |
+ |
+const std::vector<ContentSuggestion>& |
+ContentSuggestionsService::GetSuggestionsForCategory( |
+ ContentSuggestionCategory category) const { |
+ return suggestions_by_category_.at(category); |
+} |
+ |
+void ContentSuggestionsService::FetchSuggestionImage( |
+ const std::string& suggestion_id, |
+ const ImageFetchedCallback& callback) { |
+ ContentSuggestionsProviderType provider_type = |
+ GetProviderTypeFromCombinedID(suggestion_id); |
+ if (providers_.count(provider_type)) { |
+ std::string original_id = GetIDFromCombinedID(suggestion_id); |
+ providers_[provider_type]->FetchSuggestionImage( |
+ original_id, base::Bind(callback, suggestion_id)); |
+ } else { |
+ LOG(WARNING) << "Requested image for suggestion " << suggestion_id |
+ << " for unavailable provider type " << int(provider_type); |
+ callback.Run(suggestion_id, gfx::Image()); |
+ } |
+} |
+ |
+void ContentSuggestionsService::ClearCachedSuggestionsForDebugging() { |
+ categories_.clear(); |
+ suggestions_by_category_.clear(); |
+ for (auto& provider : providers_) { |
+ provider.second->ClearCachedSuggestionsForDebugging(); |
+ } |
+ FOR_EACH_OBSERVER(Observer, observers_, OnSuggestionsChanged()); |
+} |
+ |
+void ContentSuggestionsService::ClearDiscardedSuggestionsForDebugging() { |
+ for (auto& provider : providers_) { |
+ provider.second->ClearDiscardedSuggestionsForDebugging(); |
+ } |
+} |
+ |
+void ContentSuggestionsService::DiscardSuggestion( |
+ const std::string& suggestion_id) { |
+ ContentSuggestionsProviderType provider_type = |
+ GetProviderTypeFromCombinedID(suggestion_id); |
+ std::string original_id = GetIDFromCombinedID(suggestion_id); |
+ |
+ if (providers_.count(provider_type)) { |
+ providers_[provider_type]->DiscardSuggestion(original_id); |
+ |
+ // Remove the suggestion locally. |
+ for (auto& pair : suggestions_by_category_) { |
+ auto position = |
+ std::find_if(pair.second.begin(), pair.second.end(), |
+ [&suggestion_id](const ContentSuggestion& suggestion) { |
+ return suggestion_id == suggestion.id(); |
+ }); |
+ if (position != pair.second.end()) { |
+ pair.second.erase(position); |
+ } |
+ } |
+ } else { |
+ LOG(WARNING) << "Discarded suggestion " << suggestion_id |
+ << " for unavailable provider type " << int(provider_type); |
+ } |
+} |
+ |
+void ContentSuggestionsService::AddObserver(Observer* observer) { |
+ observers_.AddObserver(observer); |
+} |
+ |
+void ContentSuggestionsService::RemoveObserver(Observer* observer) { |
+ observers_.RemoveObserver(observer); |
+} |
+ |
+void ContentSuggestionsService::RegisterProvider( |
+ ContentSuggestionsProvider* provider) { |
+ DCHECK_EQ(0ul, providers_.count(provider->GetProviderType())); |
+ providers_[provider->GetProviderType()] = provider; |
+ provider->SetObserver(this); |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// Private methods |
+ |
+void ContentSuggestionsService::OnSuggestionsChanged( |
+ ContentSuggestionsProviderType provider_type, |
+ ContentSuggestionCategory changed_category, |
+ std::vector<ContentSuggestion> new_suggestions) { |
+ RemoveSuggestionsOfProvider(&suggestions_by_category_[changed_category], |
+ provider_type); |
+ |
+ std::move(new_suggestions.begin(), new_suggestions.end(), |
+ std::back_inserter(suggestions_by_category_[changed_category])); |
+ |
+ // Ensure the category exists exactly if its list of suggestions is nonempty. |
+ auto position = |
+ std::find(categories_.begin(), categories_.end(), changed_category); |
+ if (suggestions_by_category_[changed_category].empty()) { |
+ suggestions_by_category_.erase(changed_category); |
+ if (position != categories_.end()) { |
+ categories_.erase(position); |
+ } |
+ } else { |
+ if (position == categories_.end()) { |
+ // TODO(pke) In the future, make sure that the categories have some useful |
+ // (maybe constant, at least consistent) ordering for the UI. |
+ categories_.emplace_back(changed_category); |
+ } |
+ } |
+ |
+ FOR_EACH_OBSERVER(Observer, observers_, OnSuggestionsChanged()); |
+} |
+ |
+void ContentSuggestionsService::OnProviderShutdown( |
+ ContentSuggestionsProviderType provider_type) { |
+ auto iterator = suggestions_by_category_.begin(); |
+ while (iterator != suggestions_by_category_.end()) { |
+ if (RemoveSuggestionsOfProvider(&iterator->second, provider_type) && |
+ iterator->second.empty()) { |
+ categories_.erase( |
+ std::find(categories_.begin(), categories_.end(), iterator->first)); |
+ iterator = suggestions_by_category_.erase(iterator); |
+ } else { |
+ ++iterator; |
+ } |
+ } |
+ FOR_EACH_OBSERVER(Observer, observers_, OnSuggestionsChanged()); |
+ providers_.erase(provider_type); |
+ // TODO(pke) Notify the UI to do a force refresh in this case, as the |
+ // suggestions must disappear immediately. |
+} |
+ |
+} // namespace ntp_snippets |