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