Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / enhanced_bookmarks / bookmark_server_cluster_service.cc
blobd58bf5c6f68d7243367e90121d20931622be0c45
1 // Copyright 2014 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.
5 #include "components/enhanced_bookmarks/bookmark_server_cluster_service.h"
7 #include "base/json/json_reader.h"
8 #include "base/json/json_writer.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/values.h"
12 #include "components/bookmarks/browser/bookmark_model.h"
13 #include "components/enhanced_bookmarks/enhanced_bookmark_model.h"
14 #include "components/enhanced_bookmarks/enhanced_bookmark_utils.h"
15 #include "components/enhanced_bookmarks/pref_names.h"
16 #include "components/enhanced_bookmarks/proto/cluster.pb.h"
17 #include "components/pref_registry/pref_registry_syncable.h"
18 #include "components/signin/core/browser/signin_manager.h"
19 #include "components/sync_driver/sync_service.h"
20 #include "net/base/url_util.h"
21 #include "net/url_request/url_fetcher.h"
22 #include "net/url_request/url_request_context_getter.h"
24 using bookmarks::BookmarkNode;
26 namespace {
27 const char kClusterUrl[] = "https://www.google.com/stars/cluster";
28 const int kPrefServiceVersion = 1;
29 const char kPrefServiceVersionKey[] = "version";
30 const char kPrefServiceDataKey[] = "data";
31 const char kAuthIdKey[] = "auth_id";
32 } // namespace
34 namespace enhanced_bookmarks {
36 BookmarkServerClusterService::BookmarkServerClusterService(
37 const std::string& application_language_code,
38 scoped_refptr<net::URLRequestContextGetter> request_context_getter,
39 ProfileOAuth2TokenService* token_service,
40 SigninManagerBase* signin_manager,
41 enhanced_bookmarks::EnhancedBookmarkModel* enhanced_bookmark_model,
42 sync_driver::SyncService* sync_service,
43 PrefService* pref_service)
44 : BookmarkServerService(request_context_getter,
45 token_service,
46 signin_manager,
47 enhanced_bookmark_model),
48 application_language_code_(application_language_code),
49 sync_service_(sync_service),
50 pref_service_(pref_service),
51 sync_refresh_skipped_(false),
52 refreshes_needed_(0) {
53 LoadModel();
55 if (model_->loaded())
56 TriggerTokenRequest(false);
58 GetSigninManager()->AddObserver(this);
59 if (sync_service_)
60 sync_service_->AddObserver(this);
63 BookmarkServerClusterService::~BookmarkServerClusterService() {
66 void BookmarkServerClusterService::Shutdown() {
67 if (sync_service_)
68 sync_service_->RemoveObserver(this);
69 GetSigninManager()->RemoveObserver(this);
72 const std::vector<const BookmarkNode*>
73 BookmarkServerClusterService::BookmarksForClusterNamed(
74 const std::string& cluster_name) const {
75 std::vector<const BookmarkNode*> results;
77 ClusterMap::const_iterator cluster_it = cluster_data_.find(cluster_name);
78 if (cluster_it == cluster_data_.end())
79 return results;
81 for (auto& star_id : cluster_it->second) {
82 const BookmarkNode* bookmark = BookmarkForRemoteId(star_id);
83 if (bookmark)
84 results.push_back(bookmark);
86 return results;
89 const std::vector<std::string>
90 BookmarkServerClusterService::ClustersForBookmark(
91 const BookmarkNode* bookmark) const {
92 const std::string& star_id = RemoteIDForBookmark(bookmark);
94 // TODO(noyau): if this turns out to be a perf bottleneck this may be improved
95 // by storing a reverse map from id to cluster.
96 std::vector<std::string> clusters;
97 for (auto& pair : cluster_data_) {
98 const std::vector<std::string>& stars_ids = pair.second;
99 if (std::find(stars_ids.begin(), stars_ids.end(), star_id) !=
100 stars_ids.end())
101 clusters.push_back(pair.first);
103 return clusters;
106 const std::vector<std::string> BookmarkServerClusterService::GetClusters()
107 const {
108 std::vector<std::string> cluster_names;
110 for (auto& pair : cluster_data_) {
111 for (auto& star_id : pair.second) {
112 const BookmarkNode* bookmark = BookmarkForRemoteId(star_id);
113 if (bookmark) {
114 // Only add clusters that have children.
115 cluster_names.push_back(pair.first);
116 break;
121 return cluster_names;
124 void BookmarkServerClusterService::AddObserver(
125 enhanced_bookmarks::BookmarkServerServiceObserver* observer) {
126 BookmarkServerService::AddObserver(observer);
127 if (sync_refresh_skipped_) {
128 TriggerTokenRequest(false);
129 sync_refresh_skipped_ = true;
133 // static
134 void BookmarkServerClusterService::RegisterPrefs(
135 user_prefs::PrefRegistrySyncable* registry) {
136 registry->RegisterDictionaryPref(prefs::kBookmarkClusters);
139 scoped_ptr<net::URLFetcher> BookmarkServerClusterService::CreateFetcher() {
140 // Add the necessary arguments to the URI.
141 GURL url(kClusterUrl);
142 url = net::AppendQueryParameter(url, "output", "proto");
144 // Append language.
145 if (!application_language_code_.empty())
146 url = net::AppendQueryParameter(url, "hl", application_language_code_);
148 url = net::AppendQueryParameter(url, "v", model_->GetVersionString());
150 // Build the URLFetcher to perform the request.
151 scoped_ptr<net::URLFetcher> url_fetcher =
152 net::URLFetcher::Create(url, net::URLFetcher::POST, this);
154 // Binary encode a basic request proto.
155 image_collections::ClusterRequest request_proto;
156 request_proto.set_cluster_all(true);
158 std::string proto_output;
159 bool result = request_proto.SerializePartialToString(&proto_output);
160 DCHECK(result);
162 url_fetcher->SetUploadData("application/octet-stream", proto_output);
163 return url_fetcher;
166 bool BookmarkServerClusterService::ProcessResponse(const std::string& response,
167 bool* should_notify) {
168 DCHECK(*should_notify);
169 image_collections::ClusterResponse response_proto;
170 bool result = response_proto.ParseFromString(response);
171 if (!result)
172 return false; // Not formatted properly.
174 ClusterMap new_cluster_data;
175 for (const auto& cluster : response_proto.clusters()) {
176 const std::string& title = cluster.title();
177 if (title.empty())
178 continue;
179 std::vector<std::string> stars_ids;
180 for (auto& doc : cluster.docs()) {
181 if (!doc.empty())
182 stars_ids.push_back(doc);
184 if (stars_ids.size())
185 new_cluster_data[title] = stars_ids;
188 if (new_cluster_data.size() == cluster_data_.size() &&
189 std::equal(new_cluster_data.begin(),
190 new_cluster_data.end(),
191 cluster_data_.begin())) {
192 *should_notify = false;
193 } else {
194 SwapModel(&new_cluster_data);
196 return true;
199 void BookmarkServerClusterService::CleanAfterFailure() {
200 if (cluster_data_.empty())
201 return;
203 ClusterMap empty;
204 SwapModel(&empty);
207 void BookmarkServerClusterService::EnhancedBookmarkModelLoaded() {
208 TriggerTokenRequest(false);
211 void BookmarkServerClusterService::EnhancedBookmarkAdded(
212 const BookmarkNode* node) {
213 InvalidateCache();
216 void BookmarkServerClusterService::EnhancedBookmarkRemoved(
217 const BookmarkNode* node) {
218 // It is possible to remove the entries from the map here, but as those are
219 // filtered in ClustersForBookmark() this is not strictly necessary.
220 InvalidateCache();
223 void BookmarkServerClusterService::EnhancedBookmarkNodeChanged(
224 const BookmarkNode* node) {
225 InvalidateCache();
228 void BookmarkServerClusterService::EnhancedBookmarkAllUserNodesRemoved() {
229 if (!cluster_data_.empty()) {
230 ClusterMap empty;
231 SwapModel(&empty);
235 void BookmarkServerClusterService::EnhancedBookmarkRemoteIdChanged(
236 const BookmarkNode* node,
237 const std::string& old_remote_id,
238 const std::string& remote_id) {
239 std::vector<std::string> clusters;
240 for (auto& pair : cluster_data_) {
241 std::vector<std::string>& stars_ids = pair.second;
242 std::replace(stars_ids.begin(), stars_ids.end(), old_remote_id, remote_id);
246 void BookmarkServerClusterService::GoogleSignedOut(
247 const std::string& account_id,
248 const std::string& username) {
249 if (!cluster_data_.empty()) {
250 ClusterMap empty;
251 SwapModel(&empty);
255 void BookmarkServerClusterService::SwapModel(ClusterMap* cluster_map) {
256 cluster_data_.swap(*cluster_map);
257 const std::string& auth_id = GetSigninManager()->GetAuthenticatedAccountId();
258 scoped_ptr<base::DictionaryValue> dictionary(
259 Serialize(cluster_data_, auth_id));
260 pref_service_->Set(prefs::kBookmarkClusters, *dictionary);
263 void BookmarkServerClusterService::LoadModel() {
264 const base::DictionaryValue* dictionary =
265 pref_service_->GetDictionary(prefs::kBookmarkClusters);
266 const std::string& auth_id = GetSigninManager()->GetAuthenticatedAccountId();
268 ClusterMap loaded_data;
269 bool result = BookmarkServerClusterService::Deserialize(
270 *dictionary, auth_id, &loaded_data);
271 if (result)
272 cluster_data_.swap(loaded_data);
275 void BookmarkServerClusterService::OnStateChanged() {
276 // Do nothing.
279 void BookmarkServerClusterService::OnSyncCycleCompleted() {
280 // The stars cluster API relies on the information in chrome-sync. Sending a
281 // cluster request immediately after a bookmark is changed from the bookmark
282 // observer notification will yield the wrong results. The request must be
283 // delayed until the sync cycle has completed.
284 // Note that we will be skipping calling this cluster API if there is no
285 // observer attached, because calling that is meaningless without UI to show.
286 // We also will avoid requesting for clusters if the bookmark data hasn't
287 // changed.
288 if (refreshes_needed_ > 0) {
289 DCHECK(model_->loaded());
290 if (observers_.might_have_observers()) {
291 TriggerTokenRequest(false);
292 sync_refresh_skipped_ = false;
293 } else {
294 sync_refresh_skipped_ = true;
296 --refreshes_needed_;
300 void BookmarkServerClusterService::InvalidateCache() {
301 // Bookmark changes can happen locally or via sync. It is difficult to
302 // determine if a given SyncCycle contains all the local modifications.
304 // Consider the following sequence:
305 // 1. SyncCycleBeginning (bookmark version:1)
306 // 2. Bookmarks mutate locally (bookmark version:2)
307 // 3. SyncCycleCompleted (bookmark version:1)
309 // In this case, the bookmarks modified locally won't be sent to the server
310 // until the next SyncCycleCompleted. Since we can't accurately determine
311 // if a bookmark change has been sent on a SyncCycleCompleted, we're always
312 // assuming that we need to wait for 2 sync cycles.
313 refreshes_needed_ = 2;
317 // Serialization.
319 // static
320 scoped_ptr<base::DictionaryValue> BookmarkServerClusterService::Serialize(
321 const ClusterMap& cluster_map,
322 const std::string& auth_id) {
323 // Create a list of all clusters. For each cluster, make another list. The
324 // first element in the list is the key (cluster name). All subsequent
325 // elements are stars ids.
326 scoped_ptr<base::ListValue> all_clusters(new base::ListValue);
327 for (auto& pair : cluster_map) {
328 scoped_ptr<base::ListValue> cluster(new base::ListValue);
329 cluster->AppendString(pair.first);
330 cluster->AppendStrings(pair.second);
331 all_clusters->Append(cluster.release());
334 // The dictionary that will be serialized has two fields: a version field and
335 // a data field.
336 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue);
337 data->SetInteger(kPrefServiceVersionKey, kPrefServiceVersion);
338 data->Set(kPrefServiceDataKey, all_clusters.release());
339 data->SetString(kAuthIdKey, auth_id);
341 return data.Pass();
344 // static
345 bool BookmarkServerClusterService::Deserialize(
346 const base::DictionaryValue& value,
347 const std::string& auth_id,
348 ClusterMap* out_map) {
349 ClusterMap output;
351 // Check version.
352 int version;
353 if (!value.GetInteger(kPrefServiceVersionKey, &version))
354 return false;
355 if (version != kPrefServiceVersion)
356 return false;
358 // Check auth id.
359 std::string id;
360 if (!value.GetString(kAuthIdKey, &id))
361 return false;
362 if (id != auth_id)
363 return false;
365 const base::ListValue* all_clusters = NULL;
366 if (!value.GetList(kPrefServiceDataKey, &all_clusters))
367 return false;
369 for (size_t index = 0; index < all_clusters->GetSize(); ++index) {
370 const base::ListValue* cluster = NULL;
371 if (!all_clusters->GetList(index, &cluster))
372 return false;
373 if (cluster->GetSize() < 1)
374 return false;
375 std::string key;
376 if (!cluster->GetString(0, &key))
377 return false;
378 std::vector<std::string> stars_ids;
379 for (size_t index = 1; index < cluster->GetSize(); ++index) {
380 std::string stars_id;
381 if (!cluster->GetString(index, &stars_id))
382 return false;
383 stars_ids.push_back(stars_id);
385 output.insert(std::make_pair(key, stars_ids));
387 out_map->swap(output);
388 return true;
391 } // namespace enhanced_bookmarks