1 // Copyright (c) 2012 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 "chrome/browser/sync/about_sync_util.h"
9 #include "base/strings/string16.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/values.h"
12 #include "chrome/browser/signin/signin_manager.h"
13 #include "chrome/browser/sync/profile_sync_service.h"
14 #include "chrome/common/chrome_version_info.h"
15 #include "sync/api/time.h"
16 #include "sync/internal_api/public/util/sync_string_conversions.h"
17 #include "sync/protocol/proto_enum_conversions.h"
19 using base::DictionaryValue
;
20 using base::ListValue
;
22 const char kIdentityTitle
[] = "Identity";
23 const char kDetailsKey
[] = "details";
27 // Creates a 'section' for display on about:sync, consisting of a title and a
28 // list of fields. Returns a pointer to the new section. Note that
29 // |parent_list|, not the caller, owns the newly added section.
30 base::ListValue
* AddSection(base::ListValue
* parent_list
,
31 const std::string
& title
) {
32 base::DictionaryValue
* section
= new base::DictionaryValue();
33 base::ListValue
* section_contents
= new base::ListValue();
34 section
->SetString("title", title
);
35 section
->Set("data", section_contents
);
36 section
->SetBoolean("is_sensitive", false);
37 parent_list
->Append(section
);
38 return section_contents
;
41 // Same as AddSection, but for data that should be elided when dumped into text
42 // form and posted in a public forum (e.g. unique identifiers).
43 base::ListValue
* AddSensitiveSection(base::ListValue
* parent_list
,
44 const std::string
& title
) {
45 base::DictionaryValue
* section
= new base::DictionaryValue();
46 base::ListValue
* section_contents
= new base::ListValue();
47 section
->SetString("title", title
);
48 section
->Set("data", section_contents
);
49 section
->SetBoolean("is_sensitive", true);
50 parent_list
->Append(section
);
51 return section_contents
;
54 // The following helper classes help manage the about:sync fields which will be
55 // populated in method in ConstructAboutInformation.
57 // Each instance of one of thse classes indicates a field in about:sync. Each
58 // field will be serialized to a DictionaryValue with entries for 'stat_name',
59 // 'stat_value' and 'is_valid'.
61 class StringSyncStat
{
63 StringSyncStat(base::ListValue
* section
, const std::string
& key
);
64 void SetValue(const std::string
& value
);
65 void SetValue(const base::string16
& value
);
68 // Owned by the |section| passed in during construction.
69 base::DictionaryValue
* stat_
;
72 StringSyncStat::StringSyncStat(base::ListValue
* section
,
73 const std::string
& key
) {
74 stat_
= new base::DictionaryValue();
75 stat_
->SetString("stat_name", key
);
76 stat_
->SetString("stat_value", "Uninitialized");
77 stat_
->SetBoolean("is_valid", false);
78 section
->Append(stat_
);
81 void StringSyncStat::SetValue(const std::string
& value
) {
82 stat_
->SetString("stat_value", value
);
83 stat_
->SetBoolean("is_valid", true);
86 void StringSyncStat::SetValue(const base::string16
& value
) {
87 stat_
->SetString("stat_value", value
);
88 stat_
->SetBoolean("is_valid", true);
93 BoolSyncStat(base::ListValue
* section
, const std::string
& key
);
94 void SetValue(bool value
);
97 // Owned by the |section| passed in during construction.
98 base::DictionaryValue
* stat_
;
101 BoolSyncStat::BoolSyncStat(base::ListValue
* section
, const std::string
& key
) {
102 stat_
= new base::DictionaryValue();
103 stat_
->SetString("stat_name", key
);
104 stat_
->SetBoolean("stat_value", false);
105 stat_
->SetBoolean("is_valid", false);
106 section
->Append(stat_
);
109 void BoolSyncStat::SetValue(bool value
) {
110 stat_
->SetBoolean("stat_value", value
);
111 stat_
->SetBoolean("is_valid", true);
116 IntSyncStat(base::ListValue
* section
, const std::string
& key
);
117 void SetValue(int value
);
120 // Owned by the |section| passed in during construction.
121 base::DictionaryValue
* stat_
;
124 IntSyncStat::IntSyncStat(base::ListValue
* section
, const std::string
& key
) {
125 stat_
= new base::DictionaryValue();
126 stat_
->SetString("stat_name", key
);
127 stat_
->SetInteger("stat_value", 0);
128 stat_
->SetBoolean("is_valid", false);
129 section
->Append(stat_
);
132 void IntSyncStat::SetValue(int value
) {
133 stat_
->SetInteger("stat_value", value
);
134 stat_
->SetBoolean("is_valid", true);
137 // Returns a string describing the chrome version environment. Version format:
138 // <Build Info> <OS> <Version number> (<Last change>)<channel or "-devel">
139 // If version information is unavailable, returns "invalid."
140 // TODO(zea): this approximately matches MakeUserAgentForSyncApi in
141 // sync_backend_host.cc. Unify the two if possible.
142 std::string
GetVersionString() {
143 // Build a version string that matches MakeUserAgentForSyncApi with the
144 // addition of channel info and proper OS names.
145 chrome::VersionInfo chrome_version
;
146 if (!chrome_version
.is_valid())
148 // GetVersionStringModifier returns empty string for stable channel or
149 // unofficial builds, the channel string otherwise. We want to have "-devel"
150 // for unofficial builds only.
151 std::string version_modifier
=
152 chrome::VersionInfo::GetVersionStringModifier();
153 if (version_modifier
.empty()) {
154 if (chrome::VersionInfo::GetChannel() !=
155 chrome::VersionInfo::CHANNEL_STABLE
) {
156 version_modifier
= "-devel";
159 version_modifier
= " " + version_modifier
;
161 return chrome_version
.Name() + " " + chrome_version
.OSType() + " " +
162 chrome_version
.Version() + " (" + chrome_version
.LastChange() + ")" +
166 std::string
GetTimeStr(base::Time time
, const std::string
& default_msg
) {
167 std::string time_str
;
169 time_str
= default_msg
;
171 time_str
= syncer::GetTimeDebugString(time
);
175 std::string
GetConnectionStatus(
176 const ProfileSyncService::SyncTokenStatus
& status
) {
178 switch (status
.connection_status
) {
179 case syncer::CONNECTION_NOT_ATTEMPTED
:
180 base::StringAppendF(&message
, "not attempted");
182 case syncer::CONNECTION_OK
:
184 &message
, "OK since %s",
185 GetTimeStr(status
.connection_status_update_time
, "n/a").c_str());
187 case syncer::CONNECTION_AUTH_ERROR
:
189 &message
, "auth error since %s",
190 GetTimeStr(status
.connection_status_update_time
, "n/a").c_str());
192 case syncer::CONNECTION_SERVER_ERROR
:
194 &message
, "server error since %s",
195 GetTimeStr(status
.connection_status_update_time
, "n/a").c_str());
205 namespace sync_ui_util
{
207 // This function both defines the structure of the message to be returned and
208 // its contents. Most of the message consists of simple fields in about:sync
209 // which are grouped into sections and populated with the help of the SyncStat
210 // classes defined above.
211 scoped_ptr
<base::DictionaryValue
> ConstructAboutInformation(
212 ProfileSyncService
* service
) {
213 scoped_ptr
<base::DictionaryValue
> about_info(new base::DictionaryValue());
215 // 'details': A list of sections.
216 base::ListValue
* stats_list
= new base::ListValue();
218 // The following lines define the sections and their fields. For each field,
219 // a class is instantiated, which allows us to reference the fields in
220 // 'setter' code later on in this function.
221 base::ListValue
* section_summary
= AddSection(stats_list
, "Summary");
222 StringSyncStat
summary_string(section_summary
, "Summary");
224 base::ListValue
* section_version
= AddSection(stats_list
, "Version Info");
225 StringSyncStat
client_version(section_version
, "Client Version");
226 StringSyncStat
server_url(section_version
, "Server URL");
228 base::ListValue
* section_identity
=
229 AddSensitiveSection(stats_list
, kIdentityTitle
);
230 StringSyncStat
sync_id(section_identity
, "Sync Client ID");
231 StringSyncStat
invalidator_id(section_identity
, "Invalidator Client ID");
232 StringSyncStat
username(section_identity
, "Username");
234 base::ListValue
* section_credentials
= AddSection(stats_list
, "Credentials");
235 StringSyncStat
request_token_time(section_credentials
, "Requested Token");
236 StringSyncStat
receive_token_time(section_credentials
, "Received Token");
237 StringSyncStat
token_request_status(section_credentials
,
238 "Token Request Status");
239 StringSyncStat
next_token_request(section_credentials
,
240 "Next Token Request");
242 base::ListValue
* section_local
= AddSection(stats_list
, "Local State");
243 StringSyncStat
server_connection(section_local
,
244 "Server Connection");
245 StringSyncStat
last_synced(section_local
, "Last Synced");
246 BoolSyncStat
is_setup_complete(section_local
,
247 "Sync First-Time Setup Complete");
248 StringSyncStat
backend_initialization(section_local
,
249 "Sync Backend Initialization");
250 BoolSyncStat
is_syncing(section_local
, "Syncing");
251 BoolSyncStat
is_token_available(section_local
, "Sync Token Available");
253 base::ListValue
* section_network
= AddSection(stats_list
, "Network");
254 BoolSyncStat
is_throttled(section_network
, "Throttled");
255 StringSyncStat
retry_time(section_network
, "Retry time (maybe stale)");
256 BoolSyncStat
are_notifications_enabled(section_network
,
257 "Notifications Enabled");
259 base::ListValue
* section_encryption
= AddSection(stats_list
, "Encryption");
260 BoolSyncStat
is_using_explicit_passphrase(section_encryption
,
261 "Explicit Passphrase");
262 BoolSyncStat
is_passphrase_required(section_encryption
,
263 "Passphrase Required");
264 BoolSyncStat
is_cryptographer_ready(section_encryption
,
265 "Cryptographer Ready");
266 BoolSyncStat
has_pending_keys(section_encryption
,
267 "Cryptographer Has Pending Keys");
268 StringSyncStat
encrypted_types(section_encryption
, "Encrypted Types");
269 BoolSyncStat
has_keystore_key(section_encryption
, "Has Keystore Key");
270 StringSyncStat
keystore_migration_time(section_encryption
,
271 "Keystore Migration Time");
272 StringSyncStat
passphrase_type(section_encryption
,
274 StringSyncStat
passphrase_time(section_encryption
,
277 base::ListValue
* section_last_session
= AddSection(
278 stats_list
, "Status from Last Completed Session");
279 StringSyncStat
session_source(section_last_session
, "Sync Source");
280 StringSyncStat
get_key_result(section_last_session
, "GetKey Step Result");
281 StringSyncStat
download_result(section_last_session
, "Download Step Result");
282 StringSyncStat
commit_result(section_last_session
, "Commit Step Result");
284 base::ListValue
* section_counters
= AddSection(stats_list
, "Running Totals");
285 IntSyncStat
notifications_received(section_counters
,
286 "Notifications Received");
287 IntSyncStat
empty_get_updates(section_counters
, "Cycles Without Updates");
288 IntSyncStat
non_empty_get_updates(section_counters
, "Cycles With Updates");
289 IntSyncStat
sync_cycles_without_commits(section_counters
,
290 "Cycles Without Commits");
291 IntSyncStat
sync_cycles_with_commits(section_counters
, "Cycles With Commits");
292 IntSyncStat
useless_sync_cycles(section_counters
,
293 "Cycles Without Commits or Updates");
294 IntSyncStat
useful_sync_cycles(section_counters
,
295 "Cycles With Commits or Updates");
296 IntSyncStat
updates_received(section_counters
, "Updates Downloaded");
297 IntSyncStat
tombstone_updates(section_counters
, "Tombstone Updates");
298 IntSyncStat
reflected_updates(section_counters
, "Reflected Updates");
299 IntSyncStat
successful_commits(section_counters
, "Successful Commits");
300 IntSyncStat
conflicts_resolved_local_wins(section_counters
,
301 "Conflicts Resolved: Client Wins");
302 IntSyncStat
conflicts_resolved_server_wins(section_counters
,
303 "Conflicts Resolved: Server Wins");
305 base::ListValue
*section_this_cycle
= AddSection(stats_list
,
306 "Transient Counters (this cycle)");
307 IntSyncStat
encryption_conflicts(section_this_cycle
, "Encryption Conflicts");
308 IntSyncStat
hierarchy_conflicts(section_this_cycle
, "Hierarchy Conflicts");
309 IntSyncStat
server_conflicts(section_this_cycle
, "Server Conflicts");
310 IntSyncStat
committed_items(section_this_cycle
, "Committed Items");
311 IntSyncStat
updates_remaining(section_this_cycle
, "Updates Remaining");
313 base::ListValue
* section_that_cycle
= AddSection(
314 stats_list
, "Transient Counters (last cycle of last completed session)");
315 IntSyncStat
updates_downloaded(section_that_cycle
, "Updates Downloaded");
316 IntSyncStat
committed_count(section_that_cycle
, "Committed Count");
317 IntSyncStat
entries(section_that_cycle
, "Entries");
319 base::ListValue
* section_nudge_info
= AddSection(
320 stats_list
, "Nudge Source Counters");
321 IntSyncStat
nudge_source_notification(
322 section_nudge_info
, "Server Invalidations");
323 IntSyncStat
nudge_source_local(section_nudge_info
, "Local Changes");
324 IntSyncStat
nudge_source_local_refresh(section_nudge_info
, "Local Refreshes");
326 // This list of sections belongs in the 'details' field of the returned
328 about_info
->Set(kDetailsKey
, stats_list
);
330 // Populate all the fields we declared above.
331 client_version
.SetValue(GetVersionString());
334 summary_string
.SetValue("Sync service does not exist");
335 return about_info
.Pass();
338 syncer::SyncStatus full_status
;
339 bool is_status_valid
= service
->QueryDetailedSyncStatus(&full_status
);
340 bool sync_initialized
= service
->sync_initialized();
341 const syncer::sessions::SyncSessionSnapshot
& snapshot
=
343 service
->GetLastSessionSnapshot() :
344 syncer::sessions::SyncSessionSnapshot();
347 summary_string
.SetValue(service
->QuerySyncStatusSummaryString());
349 server_url
.SetValue(service
->sync_service_url().spec());
351 if (is_status_valid
&& !full_status
.sync_id
.empty())
352 sync_id
.SetValue(full_status
.sync_id
);
353 if (is_status_valid
&& !full_status
.invalidator_client_id
.empty())
354 invalidator_id
.SetValue(full_status
.invalidator_client_id
);
355 if (service
->signin())
356 username
.SetValue(service
->signin()->GetAuthenticatedUsername());
358 const ProfileSyncService::SyncTokenStatus
& token_status
=
359 service
->GetSyncTokenStatus();
360 server_connection
.SetValue(GetConnectionStatus(token_status
));
361 request_token_time
.SetValue(GetTimeStr(token_status
.token_request_time
,
363 receive_token_time
.SetValue(GetTimeStr(token_status
.token_receive_time
,
365 std::string err
= token_status
.last_get_token_error
.error_message();
366 token_request_status
.SetValue(err
.empty() ? "OK" : err
);
367 next_token_request
.SetValue(
368 GetTimeStr(token_status
.next_token_request_time
, "not scheduled"));
370 last_synced
.SetValue(service
->GetLastSyncedTimeString());
371 is_setup_complete
.SetValue(service
->HasSyncSetupCompleted());
372 backend_initialization
.SetValue(
373 service
->GetBackendInitializationStateString());
374 if (is_status_valid
) {
375 is_syncing
.SetValue(full_status
.syncing
);
376 retry_time
.SetValue(GetTimeStr(full_status
.retry_time
,
377 "Scheduler is not in backoff or throttled"));
380 if (snapshot
.is_initialized())
381 is_throttled
.SetValue(snapshot
.is_silenced());
382 if (is_status_valid
) {
383 are_notifications_enabled
.SetValue(
384 full_status
.notifications_enabled
);
387 if (sync_initialized
) {
388 is_using_explicit_passphrase
.SetValue(
389 service
->IsUsingSecondaryPassphrase());
390 is_passphrase_required
.SetValue(service
->IsPassphraseRequired());
391 passphrase_time
.SetValue(
392 GetTimeStr(service
->GetExplicitPassphraseTime(), "No Passphrase Time"));
394 if (is_status_valid
) {
395 is_cryptographer_ready
.SetValue(full_status
.cryptographer_ready
);
396 has_pending_keys
.SetValue(full_status
.crypto_has_pending_keys
);
397 encrypted_types
.SetValue(
398 ModelTypeSetToString(full_status
.encrypted_types
));
399 has_keystore_key
.SetValue(full_status
.has_keystore_key
);
400 keystore_migration_time
.SetValue(
401 GetTimeStr(full_status
.keystore_migration_time
, "Not Migrated"));
402 passphrase_type
.SetValue(
403 PassphraseTypeToString(full_status
.passphrase_type
));
406 if (snapshot
.is_initialized()) {
407 if (snapshot
.legacy_updates_source() !=
408 sync_pb::GetUpdatesCallerInfo::UNKNOWN
) {
409 session_source
.SetValue(
410 syncer::GetUpdatesSourceString(snapshot
.legacy_updates_source()));
412 get_key_result
.SetValue(
413 GetSyncerErrorString(
414 snapshot
.model_neutral_state().last_get_key_result
));
415 download_result
.SetValue(
416 GetSyncerErrorString(
417 snapshot
.model_neutral_state().last_download_updates_result
));
418 commit_result
.SetValue(
419 GetSyncerErrorString(
420 snapshot
.model_neutral_state().commit_result
));
423 if (is_status_valid
) {
424 notifications_received
.SetValue(full_status
.notifications_received
);
425 empty_get_updates
.SetValue(full_status
.empty_get_updates
);
426 non_empty_get_updates
.SetValue(full_status
.nonempty_get_updates
);
427 sync_cycles_without_commits
.SetValue(
428 full_status
.sync_cycles_without_commits
);
429 sync_cycles_with_commits
.SetValue(
430 full_status
.sync_cycles_with_commits
);
431 useless_sync_cycles
.SetValue(full_status
.useless_sync_cycles
);
432 useful_sync_cycles
.SetValue(full_status
.useful_sync_cycles
);
433 updates_received
.SetValue(full_status
.updates_received
);
434 tombstone_updates
.SetValue(full_status
.tombstone_updates_received
);
435 reflected_updates
.SetValue(full_status
.reflected_updates_received
);
436 successful_commits
.SetValue(full_status
.num_commits_total
);
437 conflicts_resolved_local_wins
.SetValue(
438 full_status
.num_local_overwrites_total
);
439 conflicts_resolved_server_wins
.SetValue(
440 full_status
.num_server_overwrites_total
);
443 if (is_status_valid
) {
444 encryption_conflicts
.SetValue(full_status
.encryption_conflicts
);
445 hierarchy_conflicts
.SetValue(full_status
.hierarchy_conflicts
);
446 server_conflicts
.SetValue(full_status
.server_conflicts
);
447 committed_items
.SetValue(full_status
.committed_count
);
448 updates_remaining
.SetValue(full_status
.updates_available
);
451 if (is_status_valid
) {
452 nudge_source_notification
.SetValue(full_status
.nudge_source_notification
);
453 nudge_source_local
.SetValue(full_status
.nudge_source_local
);
454 nudge_source_local_refresh
.SetValue(full_status
.nudge_source_local_refresh
);
457 if (snapshot
.is_initialized()) {
458 updates_downloaded
.SetValue(
459 snapshot
.model_neutral_state().num_updates_downloaded_total
);
460 committed_count
.SetValue(
461 snapshot
.model_neutral_state().num_successful_commits
);
462 entries
.SetValue(snapshot
.num_entries());
465 // The values set from this point onwards do not belong in the
468 // We don't need to check is_status_valid here.
469 // full_status.sync_protocol_error is exported directly from the
470 // ProfileSyncService, even if the backend doesn't exist.
471 const bool actionable_error_detected
=
472 full_status
.sync_protocol_error
.error_type
!= syncer::UNKNOWN_ERROR
&&
473 full_status
.sync_protocol_error
.error_type
!= syncer::SYNC_SUCCESS
;
475 about_info
->SetBoolean("actionable_error_detected",
476 actionable_error_detected
);
478 // NOTE: We won't bother showing any of the following values unless
479 // actionable_error_detected is set.
481 base::ListValue
* actionable_error
= new base::ListValue();
482 about_info
->Set("actionable_error", actionable_error
);
484 StringSyncStat
error_type(actionable_error
, "Error Type");
485 StringSyncStat
action(actionable_error
, "Action");
486 StringSyncStat
url(actionable_error
, "URL");
487 StringSyncStat
description(actionable_error
, "Error Description");
489 if (actionable_error_detected
) {
490 error_type
.SetValue(syncer::GetSyncErrorTypeString(
491 full_status
.sync_protocol_error
.error_type
));
492 action
.SetValue(syncer::GetClientActionString(
493 full_status
.sync_protocol_error
.action
));
494 url
.SetValue(full_status
.sync_protocol_error
.url
);
495 description
.SetValue(full_status
.sync_protocol_error
.error_description
);
498 about_info
->SetBoolean("unrecoverable_error_detected",
499 service
->HasUnrecoverableError());
501 if (service
->HasUnrecoverableError()) {
502 tracked_objects::Location
loc(service
->unrecoverable_error_location());
503 std::string location_str
;
504 loc
.Write(true, true, &location_str
);
505 std::string unrecoverable_error_message
=
506 "Unrecoverable error detected at " + location_str
+
507 ": " + service
->unrecoverable_error_message();
508 about_info
->SetString("unrecoverable_error_message",
509 unrecoverable_error_message
);
512 about_info
->Set("type_status", service
->GetTypeStatusMap());
514 return about_info
.Pass();
517 } // namespace sync_ui_util