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
;
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";
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
,
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) {
56 TriggerTokenRequest(false);
58 GetSigninManager()->AddObserver(this);
60 sync_service_
->AddObserver(this);
63 BookmarkServerClusterService::~BookmarkServerClusterService() {
66 void BookmarkServerClusterService::Shutdown() {
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())
81 for (auto& star_id
: cluster_it
->second
) {
82 const BookmarkNode
* bookmark
= BookmarkForRemoteId(star_id
);
84 results
.push_back(bookmark
);
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
) !=
101 clusters
.push_back(pair
.first
);
106 const std::vector
<std::string
> BookmarkServerClusterService::GetClusters()
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
);
114 // Only add clusters that have children.
115 cluster_names
.push_back(pair
.first
);
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;
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");
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
);
162 url_fetcher
->SetUploadData("application/octet-stream", proto_output
);
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
);
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();
179 std::vector
<std::string
> stars_ids
;
180 for (auto& doc
: cluster
.docs()) {
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;
194 SwapModel(&new_cluster_data
);
199 void BookmarkServerClusterService::CleanAfterFailure() {
200 if (cluster_data_
.empty())
207 void BookmarkServerClusterService::EnhancedBookmarkModelLoaded() {
208 TriggerTokenRequest(false);
211 void BookmarkServerClusterService::EnhancedBookmarkAdded(
212 const BookmarkNode
* node
) {
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.
223 void BookmarkServerClusterService::EnhancedBookmarkNodeChanged(
224 const BookmarkNode
* node
) {
228 void BookmarkServerClusterService::EnhancedBookmarkAllUserNodesRemoved() {
229 if (!cluster_data_
.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()) {
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
);
272 cluster_data_
.swap(loaded_data
);
275 void BookmarkServerClusterService::OnStateChanged() {
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
288 if (refreshes_needed_
> 0) {
289 DCHECK(model_
->loaded());
290 if (observers_
.might_have_observers()) {
291 TriggerTokenRequest(false);
292 sync_refresh_skipped_
= false;
294 sync_refresh_skipped_
= true;
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;
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
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
);
345 bool BookmarkServerClusterService::Deserialize(
346 const base::DictionaryValue
& value
,
347 const std::string
& auth_id
,
348 ClusterMap
* out_map
) {
353 if (!value
.GetInteger(kPrefServiceVersionKey
, &version
))
355 if (version
!= kPrefServiceVersion
)
360 if (!value
.GetString(kAuthIdKey
, &id
))
365 const base::ListValue
* all_clusters
= NULL
;
366 if (!value
.GetList(kPrefServiceDataKey
, &all_clusters
))
369 for (size_t index
= 0; index
< all_clusters
->GetSize(); ++index
) {
370 const base::ListValue
* cluster
= NULL
;
371 if (!all_clusters
->GetList(index
, &cluster
))
373 if (cluster
->GetSize() < 1)
376 if (!cluster
->GetString(0, &key
))
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
))
383 stars_ids
.push_back(stars_id
);
385 output
.insert(std::make_pair(key
, stars_ids
));
387 out_map
->swap(output
);
391 } // namespace enhanced_bookmarks