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. 4 5 #include "chrome/browser/sync/about_sync_util.h" 6 7 #include <string> 8 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" 18 19 using base::DictionaryValue; 20 using base::ListValue; 21 22 const char kIdentityTitle[] = "Identity"; 23 const char kDetailsKey[] = "details"; 24 25 namespace { 26 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 ListValue* AddSection(ListValue* parent_list, 31 const std::string& title) { 32 DictionaryValue* section = new DictionaryValue(); 33 ListValue* section_contents = new 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; 39 } 40 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 ListValue* AddSensitiveSection(ListValue* parent_list, 44 const std::string& title) { 45 DictionaryValue* section = new DictionaryValue(); 46 ListValue* section_contents = new 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; 52 } 53 54 // The following helper classes help manage the about:sync fields which will be 55 // populated in method in ConstructAboutInformation. 56 // 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'. 60 61 class StringSyncStat { 62 public: 63 StringSyncStat(ListValue* section, const std::string& key); 64 void SetValue(const std::string& value); 65 void SetValue(const base::string16& value); 66 67 private: 68 // Owned by the |section| passed in during construction. 69 DictionaryValue* stat_; 70 }; 71 72 StringSyncStat::StringSyncStat(ListValue* section, const std::string& key) { 73 stat_ = new DictionaryValue(); 74 stat_->SetString("stat_name", key); 75 stat_->SetString("stat_value", "Uninitialized"); 76 stat_->SetBoolean("is_valid", false); 77 section->Append(stat_); 78 } 79 80 void StringSyncStat::SetValue(const std::string& value) { 81 stat_->SetString("stat_value", value); 82 stat_->SetBoolean("is_valid", true); 83 } 84 85 void StringSyncStat::SetValue(const base::string16& value) { 86 stat_->SetString("stat_value", value); 87 stat_->SetBoolean("is_valid", true); 88 } 89 90 class BoolSyncStat { 91 public: 92 BoolSyncStat(ListValue* section, const std::string& key); 93 void SetValue(bool value); 94 95 private: 96 // Owned by the |section| passed in during construction. 97 DictionaryValue* stat_; 98 }; 99 100 BoolSyncStat::BoolSyncStat(ListValue* section, const std::string& key) { 101 stat_ = new DictionaryValue(); 102 stat_->SetString("stat_name", key); 103 stat_->SetBoolean("stat_value", false); 104 stat_->SetBoolean("is_valid", false); 105 section->Append(stat_); 106 } 107 108 void BoolSyncStat::SetValue(bool value) { 109 stat_->SetBoolean("stat_value", value); 110 stat_->SetBoolean("is_valid", true); 111 } 112 113 class IntSyncStat { 114 public: 115 IntSyncStat(ListValue* section, const std::string& key); 116 void SetValue(int value); 117 118 private: 119 // Owned by the |section| passed in during construction. 120 DictionaryValue* stat_; 121 }; 122 123 IntSyncStat::IntSyncStat(ListValue* section, const std::string& key) { 124 stat_ = new DictionaryValue(); 125 stat_->SetString("stat_name", key); 126 stat_->SetInteger("stat_value", 0); 127 stat_->SetBoolean("is_valid", false); 128 section->Append(stat_); 129 } 130 131 void IntSyncStat::SetValue(int value) { 132 stat_->SetInteger("stat_value", value); 133 stat_->SetBoolean("is_valid", true); 134 } 135 136 // Returns a string describing the chrome version environment. Version format: 137 // <Build Info> <OS> <Version number> (<Last change>)<channel or "-devel"> 138 // If version information is unavailable, returns "invalid." 139 // TODO(zea): this approximately matches MakeUserAgentForSyncApi in 140 // sync_backend_host.cc. Unify the two if possible. 141 std::string GetVersionString() { 142 // Build a version string that matches MakeUserAgentForSyncApi with the 143 // addition of channel info and proper OS names. 144 chrome::VersionInfo chrome_version; 145 if (!chrome_version.is_valid()) 146 return "invalid"; 147 // GetVersionStringModifier returns empty string for stable channel or 148 // unofficial builds, the channel string otherwise. We want to have "-devel" 149 // for unofficial builds only. 150 std::string version_modifier = 151 chrome::VersionInfo::GetVersionStringModifier(); 152 if (version_modifier.empty()) { 153 if (chrome::VersionInfo::GetChannel() != 154 chrome::VersionInfo::CHANNEL_STABLE) { 155 version_modifier = "-devel"; 156 } 157 } else { 158 version_modifier = " " + version_modifier; 159 } 160 return chrome_version.Name() + " " + chrome_version.OSType() + " " + 161 chrome_version.Version() + " (" + chrome_version.LastChange() + ")" + 162 version_modifier; 163 } 164 165 std::string GetTimeStr(base::Time time, const std::string& default_msg) { 166 std::string time_str; 167 if (time.is_null()) 168 time_str = default_msg; 169 else 170 time_str = syncer::GetTimeDebugString(time); 171 return time_str; 172 } 173 174 std::string GetConnectionStatus( 175 const ProfileSyncService::SyncTokenStatus& status) { 176 std::string message; 177 switch (status.connection_status) { 178 case syncer::CONNECTION_NOT_ATTEMPTED: 179 base::StringAppendF(&message, "not attempted"); 180 break; 181 case syncer::CONNECTION_OK: 182 base::StringAppendF( 183 &message, "OK since %s", 184 GetTimeStr(status.connection_status_update_time, "n/a").c_str()); 185 break; 186 case syncer::CONNECTION_AUTH_ERROR: 187 base::StringAppendF( 188 &message, "auth error since %s", 189 GetTimeStr(status.connection_status_update_time, "n/a").c_str()); 190 break; 191 case syncer::CONNECTION_SERVER_ERROR: 192 base::StringAppendF( 193 &message, "server error since %s", 194 GetTimeStr(status.connection_status_update_time, "n/a").c_str()); 195 break; 196 default: 197 NOTREACHED(); 198 } 199 return message; 200 } 201 202 } // namespace 203 204 namespace sync_ui_util { 205 206 // This function both defines the structure of the message to be returned and 207 // its contents. Most of the message consists of simple fields in about:sync 208 // which are grouped into sections and populated with the help of the SyncStat 209 // classes defined above. 210 scoped_ptr<DictionaryValue> ConstructAboutInformation( 211 ProfileSyncService* service) { 212 scoped_ptr<DictionaryValue> about_info(new DictionaryValue()); 213 ListValue* stats_list = new ListValue(); // 'details': A list of sections. 214 215 // The following lines define the sections and their fields. For each field, 216 // a class is instantiated, which allows us to reference the fields in 217 // 'setter' code later on in this function. 218 ListValue* section_summary = AddSection(stats_list, "Summary"); 219 StringSyncStat summary_string(section_summary, "Summary"); 220 221 ListValue* section_version = AddSection(stats_list, "Version Info"); 222 StringSyncStat client_version(section_version, "Client Version"); 223 StringSyncStat server_url(section_version, "Server URL"); 224 225 ListValue* section_identity = AddSensitiveSection(stats_list, kIdentityTitle); 226 StringSyncStat sync_id(section_identity, "Sync Client ID"); 227 StringSyncStat invalidator_id(section_identity, "Invalidator Client ID"); 228 StringSyncStat username(section_identity, "Username"); 229 230 ListValue* section_credentials = AddSection(stats_list, "Credentials"); 231 StringSyncStat request_token_time(section_credentials, "Requested Token"); 232 StringSyncStat receive_token_time(section_credentials, "Received Token"); 233 StringSyncStat token_request_status(section_credentials, 234 "Token Request Status"); 235 StringSyncStat next_token_request(section_credentials, 236 "Next Token Request"); 237 238 ListValue* section_local = AddSection(stats_list, "Local State"); 239 StringSyncStat server_connection(section_local, 240 "Server Connection"); 241 StringSyncStat last_synced(section_local, "Last Synced"); 242 BoolSyncStat is_setup_complete(section_local, 243 "Sync First-Time Setup Complete"); 244 StringSyncStat backend_initialization(section_local, 245 "Sync Backend Initialization"); 246 BoolSyncStat is_syncing(section_local, "Syncing"); 247 BoolSyncStat is_token_available(section_local, "Sync Token Available"); 248 249 ListValue* section_network = AddSection(stats_list, "Network"); 250 BoolSyncStat is_throttled(section_network, "Throttled"); 251 StringSyncStat retry_time(section_network, "Retry time (maybe stale)"); 252 BoolSyncStat are_notifications_enabled(section_network, 253 "Notifications Enabled"); 254 255 ListValue* section_encryption = AddSection(stats_list, "Encryption"); 256 BoolSyncStat is_using_explicit_passphrase(section_encryption, 257 "Explicit Passphrase"); 258 BoolSyncStat is_passphrase_required(section_encryption, 259 "Passphrase Required"); 260 BoolSyncStat is_cryptographer_ready(section_encryption, 261 "Cryptographer Ready"); 262 BoolSyncStat has_pending_keys(section_encryption, 263 "Cryptographer Has Pending Keys"); 264 StringSyncStat encrypted_types(section_encryption, "Encrypted Types"); 265 BoolSyncStat has_keystore_key(section_encryption, "Has Keystore Key"); 266 StringSyncStat keystore_migration_time(section_encryption, 267 "Keystore Migration Time"); 268 StringSyncStat passphrase_type(section_encryption, 269 "Passphrase Type"); 270 StringSyncStat passphrase_time(section_encryption, 271 "Passphrase Time"); 272 273 ListValue* section_last_session = AddSection( 274 stats_list, "Status from Last Completed Session"); 275 StringSyncStat session_source(section_last_session, "Sync Source"); 276 StringSyncStat get_key_result(section_last_session, "GetKey Step Result"); 277 StringSyncStat download_result(section_last_session, "Download Step Result"); 278 StringSyncStat commit_result(section_last_session, "Commit Step Result"); 279 280 ListValue* section_counters = AddSection(stats_list, "Running Totals"); 281 IntSyncStat notifications_received(section_counters, 282 "Notifications Received"); 283 IntSyncStat empty_get_updates(section_counters, "Cycles Without Updates"); 284 IntSyncStat non_empty_get_updates(section_counters, "Cycles With Updates"); 285 IntSyncStat sync_cycles_without_commits(section_counters, 286 "Cycles Without Commits"); 287 IntSyncStat sync_cycles_with_commits(section_counters, "Cycles With Commits"); 288 IntSyncStat useless_sync_cycles(section_counters, 289 "Cycles Without Commits or Updates"); 290 IntSyncStat useful_sync_cycles(section_counters, 291 "Cycles With Commits or Updates"); 292 IntSyncStat updates_received(section_counters, "Updates Downloaded"); 293 IntSyncStat tombstone_updates(section_counters, "Tombstone Updates"); 294 IntSyncStat reflected_updates(section_counters, "Reflected Updates"); 295 IntSyncStat successful_commits(section_counters, "Successful Commits"); 296 IntSyncStat conflicts_resolved_local_wins(section_counters, 297 "Conflicts Resolved: Client Wins"); 298 IntSyncStat conflicts_resolved_server_wins(section_counters, 299 "Conflicts Resolved: Server Wins"); 300 301 ListValue *section_this_cycle = AddSection(stats_list, 302 "Transient Counters (this cycle)"); 303 IntSyncStat encryption_conflicts(section_this_cycle, "Encryption Conflicts"); 304 IntSyncStat hierarchy_conflicts(section_this_cycle, "Hierarchy Conflicts"); 305 IntSyncStat server_conflicts(section_this_cycle, "Server Conflicts"); 306 IntSyncStat committed_items(section_this_cycle, "Committed Items"); 307 IntSyncStat updates_remaining(section_this_cycle, "Updates Remaining"); 308 309 ListValue* section_that_cycle = AddSection( 310 stats_list, "Transient Counters (last cycle of last completed session)"); 311 IntSyncStat updates_downloaded(section_that_cycle, "Updates Downloaded"); 312 IntSyncStat committed_count(section_that_cycle, "Committed Count"); 313 IntSyncStat entries(section_that_cycle, "Entries"); 314 315 ListValue* section_nudge_info = AddSection( 316 stats_list, "Nudge Source Counters"); 317 IntSyncStat nudge_source_notification( 318 section_nudge_info, "Server Invalidations"); 319 IntSyncStat nudge_source_local(section_nudge_info, "Local Changes"); 320 IntSyncStat nudge_source_local_refresh(section_nudge_info, "Local Refreshes"); 321 322 // This list of sections belongs in the 'details' field of the returned 323 // message. 324 about_info->Set(kDetailsKey, stats_list); 325 326 // Populate all the fields we declared above. 327 client_version.SetValue(GetVersionString()); 328 329 if (!service) { 330 summary_string.SetValue("Sync service does not exist"); 331 return about_info.Pass(); 332 } 333 334 syncer::SyncStatus full_status; 335 bool is_status_valid = service->QueryDetailedSyncStatus(&full_status); 336 bool sync_initialized = service->sync_initialized(); 337 const syncer::sessions::SyncSessionSnapshot& snapshot = 338 sync_initialized ? 339 service->GetLastSessionSnapshot() : 340 syncer::sessions::SyncSessionSnapshot(); 341 342 if (is_status_valid) 343 summary_string.SetValue(service->QuerySyncStatusSummaryString()); 344 345 server_url.SetValue(service->sync_service_url().spec()); 346 347 if (is_status_valid && !full_status.sync_id.empty()) 348 sync_id.SetValue(full_status.sync_id); 349 if (is_status_valid && !full_status.invalidator_client_id.empty()) 350 invalidator_id.SetValue(full_status.invalidator_client_id); 351 if (service->signin()) 352 username.SetValue(service->signin()->GetAuthenticatedUsername()); 353 354 const ProfileSyncService::SyncTokenStatus& token_status = 355 service->GetSyncTokenStatus(); 356 server_connection.SetValue(GetConnectionStatus(token_status)); 357 request_token_time.SetValue(GetTimeStr(token_status.token_request_time, 358 "n/a")); 359 receive_token_time.SetValue(GetTimeStr(token_status.token_receive_time, 360 "n/a")); 361 std::string err = token_status.last_get_token_error.error_message(); 362 token_request_status.SetValue(err.empty() ? "OK" : err); 363 next_token_request.SetValue( 364 GetTimeStr(token_status.next_token_request_time, "not scheduled")); 365 366 last_synced.SetValue(service->GetLastSyncedTimeString()); 367 is_setup_complete.SetValue(service->HasSyncSetupCompleted()); 368 backend_initialization.SetValue( 369 service->GetBackendInitializationStateString()); 370 if (is_status_valid) { 371 is_syncing.SetValue(full_status.syncing); 372 retry_time.SetValue(GetTimeStr(full_status.retry_time, 373 "Scheduler is not in backoff or throttled")); 374 } 375 376 if (snapshot.is_initialized()) 377 is_throttled.SetValue(snapshot.is_silenced()); 378 if (is_status_valid) { 379 are_notifications_enabled.SetValue( 380 full_status.notifications_enabled); 381 } 382 383 if (sync_initialized) { 384 is_using_explicit_passphrase.SetValue( 385 service->IsUsingSecondaryPassphrase()); 386 is_passphrase_required.SetValue(service->IsPassphraseRequired()); 387 passphrase_time.SetValue( 388 GetTimeStr(service->GetExplicitPassphraseTime(), "No Passphrase Time")); 389 } 390 if (is_status_valid) { 391 is_cryptographer_ready.SetValue(full_status.cryptographer_ready); 392 has_pending_keys.SetValue(full_status.crypto_has_pending_keys); 393 encrypted_types.SetValue( 394 ModelTypeSetToString(full_status.encrypted_types)); 395 has_keystore_key.SetValue(full_status.has_keystore_key); 396 keystore_migration_time.SetValue( 397 GetTimeStr(full_status.keystore_migration_time, "Not Migrated")); 398 passphrase_type.SetValue( 399 PassphraseTypeToString(full_status.passphrase_type)); 400 } 401 402 if (snapshot.is_initialized()) { 403 if (snapshot.legacy_updates_source() != 404 sync_pb::GetUpdatesCallerInfo::UNKNOWN) { 405 session_source.SetValue( 406 syncer::GetUpdatesSourceString(snapshot.legacy_updates_source())); 407 } 408 get_key_result.SetValue( 409 GetSyncerErrorString( 410 snapshot.model_neutral_state().last_get_key_result)); 411 download_result.SetValue( 412 GetSyncerErrorString( 413 snapshot.model_neutral_state().last_download_updates_result)); 414 commit_result.SetValue( 415 GetSyncerErrorString( 416 snapshot.model_neutral_state().commit_result)); 417 } 418 419 if (is_status_valid) { 420 notifications_received.SetValue(full_status.notifications_received); 421 empty_get_updates.SetValue(full_status.empty_get_updates); 422 non_empty_get_updates.SetValue(full_status.nonempty_get_updates); 423 sync_cycles_without_commits.SetValue( 424 full_status.sync_cycles_without_commits); 425 sync_cycles_with_commits.SetValue( 426 full_status.sync_cycles_with_commits); 427 useless_sync_cycles.SetValue(full_status.useless_sync_cycles); 428 useful_sync_cycles.SetValue(full_status.useful_sync_cycles); 429 updates_received.SetValue(full_status.updates_received); 430 tombstone_updates.SetValue(full_status.tombstone_updates_received); 431 reflected_updates.SetValue(full_status.reflected_updates_received); 432 successful_commits.SetValue(full_status.num_commits_total); 433 conflicts_resolved_local_wins.SetValue( 434 full_status.num_local_overwrites_total); 435 conflicts_resolved_server_wins.SetValue( 436 full_status.num_server_overwrites_total); 437 } 438 439 if (is_status_valid) { 440 encryption_conflicts.SetValue(full_status.encryption_conflicts); 441 hierarchy_conflicts.SetValue(full_status.hierarchy_conflicts); 442 server_conflicts.SetValue(full_status.server_conflicts); 443 committed_items.SetValue(full_status.committed_count); 444 updates_remaining.SetValue(full_status.updates_available); 445 } 446 447 if (is_status_valid) { 448 nudge_source_notification.SetValue(full_status.nudge_source_notification); 449 nudge_source_local.SetValue(full_status.nudge_source_local); 450 nudge_source_local_refresh.SetValue(full_status.nudge_source_local_refresh); 451 } 452 453 if (snapshot.is_initialized()) { 454 updates_downloaded.SetValue( 455 snapshot.model_neutral_state().num_updates_downloaded_total); 456 committed_count.SetValue( 457 snapshot.model_neutral_state().num_successful_commits); 458 entries.SetValue(snapshot.num_entries()); 459 } 460 461 // The values set from this point onwards do not belong in the 462 // details list. 463 464 // We don't need to check is_status_valid here. 465 // full_status.sync_protocol_error is exported directly from the 466 // ProfileSyncService, even if the backend doesn't exist. 467 const bool actionable_error_detected = 468 full_status.sync_protocol_error.error_type != syncer::UNKNOWN_ERROR && 469 full_status.sync_protocol_error.error_type != syncer::SYNC_SUCCESS; 470 471 about_info->SetBoolean("actionable_error_detected", 472 actionable_error_detected); 473 474 // NOTE: We won't bother showing any of the following values unless 475 // actionable_error_detected is set. 476 477 ListValue* actionable_error = new ListValue(); 478 about_info->Set("actionable_error", actionable_error); 479 480 StringSyncStat error_type(actionable_error, "Error Type"); 481 StringSyncStat action(actionable_error, "Action"); 482 StringSyncStat url(actionable_error, "URL"); 483 StringSyncStat description(actionable_error, "Error Description"); 484 485 if (actionable_error_detected) { 486 error_type.SetValue(syncer::GetSyncErrorTypeString( 487 full_status.sync_protocol_error.error_type)); 488 action.SetValue(syncer::GetClientActionString( 489 full_status.sync_protocol_error.action)); 490 url.SetValue(full_status.sync_protocol_error.url); 491 description.SetValue(full_status.sync_protocol_error.error_description); 492 } 493 494 about_info->SetBoolean("unrecoverable_error_detected", 495 service->HasUnrecoverableError()); 496 497 if (service->HasUnrecoverableError()) { 498 tracked_objects::Location loc(service->unrecoverable_error_location()); 499 std::string location_str; 500 loc.Write(true, true, &location_str); 501 std::string unrecoverable_error_message = 502 "Unrecoverable error detected at " + location_str + 503 ": " + service->unrecoverable_error_message(); 504 about_info->SetString("unrecoverable_error_message", 505 unrecoverable_error_message); 506 } 507 508 about_info->Set("type_status", service->GetTypeStatusMap()); 509 510 return about_info.Pass(); 511 } 512 513 } // namespace sync_ui_util 514