Chromium Code Reviews| 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..602956076bc72cae4410b2363c003e1195ce4694 |
| --- /dev/null |
| +++ b/components/ntp_snippets/content_suggestions_service.cc |
| @@ -0,0 +1,194 @@ |
| +// 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 "ui/gfx/image/image.h" |
| + |
| +namespace ntp_snippets { |
| + |
| +namespace { |
| + |
| +// Helper method get the provider_type from a combined ID. |
| +ContentSuggestionsProviderType GetProviderTypeFromCombinedID(std::string id) { |
|
Marc Treib
2016/06/30 10:36:54
const std::string&
tschumann
2016/06/30 10:55:19
do we already have move semantics in chrome?
I'd g
Marc Treib
2016/06/30 11:34:23
We do, but there's no reason to pass in ownership
Philipp Keck
2016/06/30 17:14:08
Done.
|
| + auto colon_index = id.find(":"); |
|
Marc Treib
2016/06/30 10:36:54
size_t please - if you say "auto", then it kinda l
Philipp Keck
2016/06/30 17:14:08
Done.
|
| + DCHECK_NE(std::string::npos, colon_index); |
| + int provider_type = std::stoi(id.substr(0, colon_index)); |
|
Marc Treib
2016/06/30 10:36:54
What happens if this fails to parse as an int?
std
Philipp Keck
2016/06/30 17:14:08
Done.
|
| + return ContentSuggestionsProviderType(provider_type); |
|
Marc Treib
2016/06/30 10:36:54
What if the int isn't a valid provider type? (Shou
tschumann
2016/06/30 10:55:19
use the most restrictive cast, i.e. static_cast<Co
Marc Treib
2016/06/30 11:34:23
AFAIK that will actually do the exact same thing.
tschumann
2016/06/30 11:58:11
in this particular case, c-style cast does the sam
Marc Treib
2016/06/30 13:36:12
IMO, the out-of-range case is one that should *nev
Philipp Keck
2016/06/30 17:14:08
I agree with Marc, and equally the case when the s
Marc Treib
2016/07/01 09:15:32
I find "create a ContentSuggestionsProviderType ou
|
| +} |
| + |
| +// Helper method get the original ID from a combined ID. |
| +std::string GetIDFromCombinedID(std::string id) { |
| + auto colon_index = id.find(":"); |
| + DCHECK_NE(std::string::npos, colon_index); |
| + return id.substr(colon_index + 1); |
| +} |
|
Marc Treib
2016/06/30 10:36:54
I think it'd be nice if the "combine" and "extract
tschumann
2016/06/30 11:58:11
+1
They should live together but not on the Conten
Philipp Keck
2016/06/30 17:14:08
I had the same dilemma. ContentSuggestion is defin
Marc Treib
2016/07/01 09:15:32
Naaah.
Philipp Keck
2016/07/01 13:00:03
Ok. So should I do that? Btw this is also one of t
Marc Treib
2016/07/01 13:29:17
How exactly would that solve it? We'll still have
Philipp Keck
2016/07/01 13:45:11
Yes, but it could be public logic because categori
|
| + |
| +// 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; |
| + } else { |
|
Marc Treib
2016/06/30 10:36:54
No "else" after "return"
Philipp Keck
2016/06/30 17:14:08
I changed it. Though I personally like the shape o
Marc Treib
2016/07/01 09:15:32
I agree, in the second example, the benefit is cle
Philipp Keck
2016/07/01 13:00:03
Done.
|
| + suggestions->erase(remove_from, suggestions->end()); |
| + return true; |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| +ContentSuggestionsService::ContentSuggestionsService(bool enabled) |
| + : enabled_(enabled) {} |
| + |
| +ContentSuggestionsService::~ContentSuggestionsService() {} |
| + |
| +void ContentSuggestionsService::Shutdown() { |
| + FOR_EACH_OBSERVER(Observer, observers_, ContentSuggestionsServiceShutdown()); |
| + observers_.Clear(); |
|
Marc Treib
2016/06/30 10:36:54
Hm. Since the observers register themselves, they
tschumann
2016/06/30 11:58:11
+1
Philipp Keck
2016/06/30 17:14:08
Marc's precondition is not perfectly true: Observe
Marc Treib
2016/07/01 09:15:32
But the observers need a reference to the service
Philipp Keck
2016/07/01 13:00:04
The Unregistering would need to be triggered from
Marc Treib
2016/07/01 13:29:17
Hm, I think the categories should be registered "a
Philipp Keck
2016/07/01 13:45:11
I agree about the static categories, when conflict
Marc Treib
2016/07/01 15:08:14
DCHECK should be fine; I don't actually expect any
|
| + for (auto& provider : providers_) { |
| + provider.second->SetObserver(nullptr); |
| + } |
| + providers_.clear(); |
| + enabled_ = false; |
|
Marc Treib
2016/06/30 10:36:54
Also clear the cached suggestions, just to be sure
Philipp Keck
2016/06/30 17:14:08
Yes, see previous comment.
Marc Treib
2016/07/01 09:15:32
Hmm?
Philipp Keck
2016/07/01 13:00:04
Depending on how we change the shutdown, I would a
Marc Treib
2016/07/01 15:08:14
Acknowledged.
|
| +} |
| + |
| +const std::vector<ContentSuggestion>& |
| +ContentSuggestionsService::GetSuggestionsForCategory( |
| + ContentSuggestionCategory category) const { |
| + return suggestions_by_category_.at(category); |
|
Marc Treib
2016/06/30 10:36:54
Hm, so this assumes that it'll never be called for
tschumann
2016/06/30 10:55:19
hmmm... is there anything guaranteeing that we hav
Philipp Keck
2016/06/30 17:14:08
In the obvious, simple Android implementation, thi
Marc Treib
2016/07/01 09:15:32
"no list" isn't an option if you return a referenc
Philipp Keck
2016/07/01 13:00:04
Changed to empty list.
|
| +} |
| + |
| +void ContentSuggestionsService::FetchSuggestionImage( |
| + const std::string& suggestion_id, |
| + const ImageFetchedCallback& callback) { |
| + ContentSuggestionsProviderType provider_type = |
| + GetProviderTypeFromCombinedID(suggestion_id); |
| + std::string original_id = GetIDFromCombinedID(suggestion_id); |
|
Marc Treib
2016/06/30 10:36:54
nit: move this into the "if"
Philipp Keck
2016/06/30 17:14:08
Done.
|
| + if (providers_.count(provider_type)) { |
| + 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::ClearCachedSuggestions() { |
| + categories_.clear(); |
| + suggestions_by_category_.clear(); |
| + for (auto& provider : providers_) { |
| + provider.second->ClearCachedSuggestions(); |
| + } |
| + FOR_EACH_OBSERVER(Observer, observers_, OnSuggestionsChanged()); |
| +} |
| + |
| +void ContentSuggestionsService::ClearDiscardedSuggestions() { |
| + for (auto& provider : providers_) { |
| + provider.second->ClearDiscardedSuggestions(); |
| + } |
| +} |
| + |
| +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_) { |
|
Marc Treib
2016/06/30 10:36:54
Can't you get the category from the suggestions, r
Philipp Keck
2016/06/30 17:14:08
Then I would need to include the category in the D
Marc Treib
2016/07/01 09:15:32
Ah, right, sorry. Carry on :)
Philipp Keck
2016/07/01 13:00:04
It might be part of the ID, if we enforce that the
Marc Treib
2016/07/01 13:29:17
Yup, SGTM.
|
| + auto position = |
| + std::find_if(pair.second.begin(), pair.second.end(), |
| + [suggestion_id](const ContentSuggestion& suggestion) { |
|
Marc Treib
2016/06/30 10:36:54
You could capture this by reference, to save a str
Philipp Keck
2016/06/30 17:14:08
Done.
|
| + return suggestion_id == suggestion.id(); |
| + }); |
| + if (position != pair.second.end()) { |
| + pair.second.erase(position); |
|
Marc Treib
2016/07/01 09:15:32
Add a break? Once we're removed the suggestion, th
Philipp Keck
2016/07/01 13:00:03
Done.
|
| + } |
| + } |
| + } else { |
|
Marc Treib
2016/07/01 09:15:32
nit: Turning the if around might make things a bit
Philipp Keck
2016/07/01 13:00:04
Done.
|
| + LOG(WARNING) << "Discarded suggestion " << suggestion_id |
| + << " for unavailable provider type " |
| + << static_cast<base::underlying_type< |
| + ContentSuggestionsProviderType>::type>(provider_type); |
|
Marc Treib
2016/06/30 10:36:54
int(provider_type)
tschumann
2016/06/30 11:58:11
i'd vote for implicit_cast<int> as it's the least
Philipp Keck
2016/06/30 17:14:08
Wasn't sure where to import the implicit_cast from
Marc Treib
2016/07/01 09:15:32
implicit_cast isn't a standard C++ feature, and do
tschumann
2016/07/01 09:55:59
=) argh... spent too much time in google3 it seems
Philipp Keck
2016/07/01 13:00:04
It's "int(provider_type)" now? See discussion abov
|
| + } |
| +} |
| + |
| +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) Useful ordering of categories. |
|
tschumann
2016/06/30 10:55:19
can you explain this TODO() in a bit more detail?
Philipp Keck
2016/06/30 17:14:08
Done.
|
| + 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); |
| +} |
| + |
| +} // namespace ntp_snippets |