Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "components/ntp_snippets/content_suggestions_service.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <iterator> | |
| 9 | |
| 10 #include "base/bind.h" | |
| 11 #include "base/strings/string_number_conversions.h" | |
| 12 #include "ui/gfx/image/image.h" | |
| 13 | |
| 14 namespace ntp_snippets { | |
| 15 | |
| 16 namespace { | |
| 17 | |
| 18 // 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.
| |
| 19 ContentSuggestionsProviderType GetProviderTypeFromCombinedID( | |
| 20 const std::string& id) { | |
| 21 size_t colon_index = id.find(":"); | |
| 22 DCHECK_NE(std::string::npos, colon_index); | |
| 23 int provider_type; | |
| 24 DCHECK(base::StringToInt(id.substr(0, colon_index), &provider_type)); | |
| 25 DCHECK(0 <= provider_type && | |
| 26 provider_type < int(ContentSuggestionsProviderType::COUNT)); | |
| 27 return ContentSuggestionsProviderType(provider_type); | |
| 28 } | |
| 29 | |
| 30 // Helper method get the original ID from a combined ID. | |
| 31 std::string GetIDFromCombinedID(const std::string& id) { | |
| 32 size_t colon_index = id.find(":"); | |
| 33 DCHECK_NE(std::string::npos, colon_index); | |
| 34 return id.substr(colon_index + 1); | |
| 35 } | |
| 36 | |
| 37 // Helper method to remove all suggestions of the given |provider_type| from | |
| 38 // the given |suggestions|. Returns true if anything changed. | |
| 39 bool RemoveSuggestionsOfProvider(std::vector<ContentSuggestion>* suggestions, | |
| 40 ContentSuggestionsProviderType provider_type) { | |
| 41 auto remove_from = std::remove_if( | |
| 42 suggestions->begin(), suggestions->end(), | |
| 43 [provider_type](const ContentSuggestion& s) { | |
| 44 return GetProviderTypeFromCombinedID(s.id()) == provider_type; | |
| 45 }); | |
| 46 if (remove_from == suggestions->end()) { | |
| 47 return false; | |
| 48 } | |
| 49 suggestions->erase(remove_from, suggestions->end()); | |
| 50 return true; | |
| 51 } | |
| 52 | |
| 53 } // namespace | |
| 54 | |
| 55 ContentSuggestionsService::ContentSuggestionsService(Enabled enabled) | |
| 56 : enabled_(enabled) {} | |
| 57 | |
| 58 ContentSuggestionsService::~ContentSuggestionsService() {} | |
| 59 | |
| 60 void ContentSuggestionsService::Shutdown() { | |
| 61 FOR_EACH_OBSERVER(Observer, observers_, ContentSuggestionsServiceShutdown()); | |
| 62 observers_.Clear(); | |
| 63 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.
| |
| 64 provider.second->SetObserver(nullptr); | |
| 65 } | |
| 66 providers_.clear(); | |
| 67 enabled_ = Enabled::Disable; | |
| 68 } | |
| 69 | |
| 70 const std::vector<ContentSuggestion>& | |
| 71 ContentSuggestionsService::GetSuggestionsForCategory( | |
| 72 ContentSuggestionCategory category) const { | |
| 73 return suggestions_by_category_.at(category); | |
| 74 } | |
| 75 | |
| 76 void ContentSuggestionsService::FetchSuggestionImage( | |
| 77 const std::string& suggestion_id, | |
| 78 const ImageFetchedCallback& callback) { | |
| 79 ContentSuggestionsProviderType provider_type = | |
| 80 GetProviderTypeFromCombinedID(suggestion_id); | |
| 81 if (providers_.count(provider_type)) { | |
| 82 std::string original_id = GetIDFromCombinedID(suggestion_id); | |
| 83 providers_[provider_type]->FetchSuggestionImage( | |
| 84 original_id, base::Bind(callback, suggestion_id)); | |
| 85 } else { | |
| 86 LOG(WARNING) << "Requested image for suggestion " << suggestion_id | |
| 87 << " for unavailable provider type " << int(provider_type); | |
| 88 callback.Run(suggestion_id, gfx::Image()); | |
| 89 } | |
| 90 } | |
| 91 | |
| 92 void ContentSuggestionsService::ClearCachedSuggestionsForDebugging() { | |
| 93 categories_.clear(); | |
| 94 suggestions_by_category_.clear(); | |
| 95 for (auto& provider : providers_) { | |
| 96 provider.second->ClearCachedSuggestionsForDebugging(); | |
| 97 } | |
| 98 FOR_EACH_OBSERVER(Observer, observers_, OnSuggestionsChanged()); | |
| 99 } | |
| 100 | |
| 101 void ContentSuggestionsService::ClearDiscardedSuggestionsForDebugging() { | |
| 102 for (auto& provider : providers_) { | |
| 103 provider.second->ClearDiscardedSuggestionsForDebugging(); | |
| 104 } | |
| 105 } | |
| 106 | |
| 107 void ContentSuggestionsService::DiscardSuggestion( | |
| 108 const std::string& suggestion_id) { | |
| 109 ContentSuggestionsProviderType provider_type = | |
| 110 GetProviderTypeFromCombinedID(suggestion_id); | |
| 111 std::string original_id = GetIDFromCombinedID(suggestion_id); | |
| 112 | |
| 113 if (providers_.count(provider_type)) { | |
| 114 providers_[provider_type]->DiscardSuggestion(original_id); | |
| 115 | |
| 116 // Remove the suggestion locally. | |
| 117 for (auto& pair : suggestions_by_category_) { | |
| 118 auto position = | |
| 119 std::find_if(pair.second.begin(), pair.second.end(), | |
| 120 [&suggestion_id](const ContentSuggestion& suggestion) { | |
| 121 return suggestion_id == suggestion.id(); | |
| 122 }); | |
| 123 if (position != pair.second.end()) { | |
| 124 pair.second.erase(position); | |
| 125 } | |
| 126 } | |
| 127 } else { | |
| 128 LOG(WARNING) << "Discarded suggestion " << suggestion_id | |
| 129 << " for unavailable provider type " << int(provider_type); | |
| 130 } | |
| 131 } | |
| 132 | |
| 133 void ContentSuggestionsService::AddObserver(Observer* observer) { | |
| 134 observers_.AddObserver(observer); | |
| 135 } | |
| 136 | |
| 137 void ContentSuggestionsService::RemoveObserver(Observer* observer) { | |
| 138 observers_.RemoveObserver(observer); | |
| 139 } | |
| 140 | |
| 141 void ContentSuggestionsService::RegisterProvider( | |
| 142 ContentSuggestionsProvider* provider) { | |
| 143 DCHECK_EQ(0ul, providers_.count(provider->GetProviderType())); | |
| 144 providers_[provider->GetProviderType()] = provider; | |
| 145 provider->SetObserver(this); | |
| 146 } | |
| 147 | |
| 148 //////////////////////////////////////////////////////////////////////////////// | |
| 149 // Private methods | |
| 150 | |
| 151 void ContentSuggestionsService::OnSuggestionsChanged( | |
| 152 ContentSuggestionsProviderType provider_type, | |
| 153 ContentSuggestionCategory changed_category, | |
| 154 std::vector<ContentSuggestion> new_suggestions) { | |
| 155 RemoveSuggestionsOfProvider(&suggestions_by_category_[changed_category], | |
| 156 provider_type); | |
| 157 | |
| 158 std::move(new_suggestions.begin(), new_suggestions.end(), | |
| 159 std::back_inserter(suggestions_by_category_[changed_category])); | |
| 160 | |
| 161 // Ensure the category exists exactly if its list of suggestions is nonempty. | |
| 162 auto position = | |
| 163 std::find(categories_.begin(), categories_.end(), changed_category); | |
| 164 if (suggestions_by_category_[changed_category].empty()) { | |
| 165 suggestions_by_category_.erase(changed_category); | |
| 166 if (position != categories_.end()) { | |
| 167 categories_.erase(position); | |
| 168 } | |
| 169 } else { | |
| 170 if (position == categories_.end()) { | |
| 171 // TODO(pke) In the future, make sure that the categories have some useful | |
| 172 // (maybe constant, at least consistent) ordering for the UI. | |
| 173 categories_.emplace_back(changed_category); | |
| 174 } | |
| 175 } | |
| 176 | |
| 177 FOR_EACH_OBSERVER(Observer, observers_, OnSuggestionsChanged()); | |
| 178 } | |
| 179 | |
| 180 void ContentSuggestionsService::OnProviderShutdown( | |
| 181 ContentSuggestionsProviderType provider_type) { | |
| 182 auto iterator = suggestions_by_category_.begin(); | |
| 183 while (iterator != suggestions_by_category_.end()) { | |
| 184 if (RemoveSuggestionsOfProvider(&iterator->second, provider_type) && | |
| 185 iterator->second.empty()) { | |
| 186 categories_.erase( | |
| 187 std::find(categories_.begin(), categories_.end(), iterator->first)); | |
| 188 iterator = suggestions_by_category_.erase(iterator); | |
| 189 } else { | |
| 190 ++iterator; | |
| 191 } | |
| 192 } | |
| 193 FOR_EACH_OBSERVER(Observer, observers_, OnSuggestionsChanged()); | |
| 194 providers_.erase(provider_type); | |
| 195 // TODO(pke) Notify the UI to do a force refresh in this case, as the | |
| 196 // suggestions must disappear immediately. | |
| 197 } | |
| 198 | |
| 199 } // namespace ntp_snippets | |
| OLD | NEW |