Home | History | Annotate | Download | only in sync
      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/sync/profile_sync_service.h"
     13 #include "chrome/common/chrome_version_info.h"
     14 #include "components/signin/core/browser/signin_manager.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 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;
     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 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;
     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(base::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   base::DictionaryValue* stat_;
     70 };
     71 
     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_);
     79 }
     80 
     81 void StringSyncStat::SetValue(const std::string& value) {
     82   stat_->SetString("stat_value", value);
     83   stat_->SetBoolean("is_valid", true);
     84 }
     85 
     86 void StringSyncStat::SetValue(const base::string16& value) {
     87   stat_->SetString("stat_value", value);
     88   stat_->SetBoolean("is_valid", true);
     89 }
     90 
     91 class BoolSyncStat {
     92  public:
     93   BoolSyncStat(base::ListValue* section, const std::string& key);
     94   void SetValue(bool value);
     95 
     96  private:
     97   // Owned by the |section| passed in during construction.
     98   base::DictionaryValue* stat_;
     99 };
    100 
    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_);
    107 }
    108 
    109 void BoolSyncStat::SetValue(bool value) {
    110   stat_->SetBoolean("stat_value", value);
    111   stat_->SetBoolean("is_valid", true);
    112 }
    113 
    114 class IntSyncStat {
    115  public:
    116   IntSyncStat(base::ListValue* section, const std::string& key);
    117   void SetValue(int value);
    118 
    119  private:
    120   // Owned by the |section| passed in during construction.
    121   base::DictionaryValue* stat_;
    122 };
    123 
    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_);
    130 }
    131 
    132 void IntSyncStat::SetValue(int value) {
    133   stat_->SetInteger("stat_value", value);
    134   stat_->SetBoolean("is_valid", true);
    135 }
    136 
    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())
    147     return "invalid";
    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";
    157     }
    158   } else {
    159     version_modifier = " " + version_modifier;
    160   }
    161   return chrome_version.Name() + " " + chrome_version.OSType() + " " +
    162       chrome_version.Version() + " (" + chrome_version.LastChange() + ")" +
    163       version_modifier;
    164 }
    165 
    166 std::string GetTimeStr(base::Time time, const std::string& default_msg) {
    167   std::string time_str;
    168   if (time.is_null())
    169     time_str = default_msg;
    170   else
    171     time_str = syncer::GetTimeDebugString(time);
    172   return time_str;
    173 }
    174 
    175 std::string GetConnectionStatus(
    176     const ProfileSyncService::SyncTokenStatus& status) {
    177   std::string message;
    178   switch (status.connection_status) {
    179     case syncer::CONNECTION_NOT_ATTEMPTED:
    180       base::StringAppendF(&message, "not attempted");
    181       break;
    182     case syncer::CONNECTION_OK:
    183       base::StringAppendF(
    184           &message, "OK since %s",
    185           GetTimeStr(status.connection_status_update_time, "n/a").c_str());
    186       break;
    187     case syncer::CONNECTION_AUTH_ERROR:
    188       base::StringAppendF(
    189           &message, "auth error since %s",
    190           GetTimeStr(status.connection_status_update_time, "n/a").c_str());
    191       break;
    192     case syncer::CONNECTION_SERVER_ERROR:
    193       base::StringAppendF(
    194           &message, "server error since %s",
    195           GetTimeStr(status.connection_status_update_time, "n/a").c_str());
    196       break;
    197     default:
    198       NOTREACHED();
    199   }
    200   return message;
    201 }
    202 
    203 }  // namespace
    204 
    205 namespace sync_ui_util {
    206 
    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());
    214 
    215   // 'details': A list of sections.
    216   base::ListValue* stats_list = new base::ListValue();
    217 
    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");
    223 
    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");
    227 
    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");
    233 
    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");
    241 
    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 
    252   base::ListValue* section_network = AddSection(stats_list, "Network");
    253   BoolSyncStat is_throttled(section_network, "Throttled");
    254   StringSyncStat retry_time(section_network, "Retry time (maybe stale)");
    255   BoolSyncStat are_notifications_enabled(section_network,
    256                                          "Notifications Enabled");
    257 
    258   base::ListValue* section_encryption = AddSection(stats_list, "Encryption");
    259   BoolSyncStat is_using_explicit_passphrase(section_encryption,
    260                                             "Explicit Passphrase");
    261   BoolSyncStat is_passphrase_required(section_encryption,
    262                                       "Passphrase Required");
    263   BoolSyncStat is_cryptographer_ready(section_encryption,
    264                                       "Cryptographer Ready");
    265   BoolSyncStat has_pending_keys(section_encryption,
    266                                 "Cryptographer Has Pending Keys");
    267   StringSyncStat encrypted_types(section_encryption, "Encrypted Types");
    268   BoolSyncStat has_keystore_key(section_encryption, "Has Keystore Key");
    269   StringSyncStat keystore_migration_time(section_encryption,
    270                                          "Keystore Migration Time");
    271   StringSyncStat passphrase_type(section_encryption,
    272                                  "Passphrase Type");
    273   StringSyncStat passphrase_time(section_encryption,
    274                                  "Passphrase Time");
    275 
    276   base::ListValue* section_last_session = AddSection(
    277       stats_list, "Status from Last Completed Session");
    278   StringSyncStat session_source(section_last_session, "Sync Source");
    279   StringSyncStat get_key_result(section_last_session, "GetKey Step Result");
    280   StringSyncStat download_result(section_last_session, "Download Step Result");
    281   StringSyncStat commit_result(section_last_session, "Commit Step Result");
    282 
    283   base::ListValue* section_counters = AddSection(stats_list, "Running Totals");
    284   IntSyncStat notifications_received(section_counters,
    285                                      "Notifications Received");
    286   IntSyncStat updates_received(section_counters, "Updates Downloaded");
    287   IntSyncStat tombstone_updates(section_counters, "Tombstone Updates");
    288   IntSyncStat reflected_updates(section_counters, "Reflected Updates");
    289   IntSyncStat successful_commits(section_counters, "Successful Commits");
    290   IntSyncStat conflicts_resolved_local_wins(section_counters,
    291                                      "Conflicts Resolved: Client Wins");
    292   IntSyncStat conflicts_resolved_server_wins(section_counters,
    293                                       "Conflicts Resolved: Server Wins");
    294 
    295   base::ListValue *section_this_cycle = AddSection(stats_list,
    296                                              "Transient Counters (this cycle)");
    297   IntSyncStat encryption_conflicts(section_this_cycle, "Encryption Conflicts");
    298   IntSyncStat hierarchy_conflicts(section_this_cycle, "Hierarchy Conflicts");
    299   IntSyncStat server_conflicts(section_this_cycle, "Server Conflicts");
    300   IntSyncStat committed_items(section_this_cycle, "Committed Items");
    301 
    302   base::ListValue* section_that_cycle = AddSection(
    303       stats_list, "Transient Counters (last cycle of last completed session)");
    304   IntSyncStat updates_downloaded(section_that_cycle, "Updates Downloaded");
    305   IntSyncStat committed_count(section_that_cycle, "Committed Count");
    306   IntSyncStat entries(section_that_cycle, "Entries");
    307 
    308   base::ListValue* section_nudge_info = AddSection(
    309       stats_list, "Nudge Source Counters");
    310   IntSyncStat nudge_source_notification(
    311       section_nudge_info, "Server Invalidations");
    312   IntSyncStat nudge_source_local(section_nudge_info, "Local Changes");
    313   IntSyncStat nudge_source_local_refresh(section_nudge_info, "Local Refreshes");
    314 
    315   // This list of sections belongs in the 'details' field of the returned
    316   // message.
    317   about_info->Set(kDetailsKey, stats_list);
    318 
    319   // Populate all the fields we declared above.
    320   client_version.SetValue(GetVersionString());
    321 
    322   if (!service) {
    323     summary_string.SetValue("Sync service does not exist");
    324     return about_info.Pass();
    325   }
    326 
    327   syncer::SyncStatus full_status;
    328   bool is_status_valid = service->QueryDetailedSyncStatus(&full_status);
    329   bool sync_initialized = service->sync_initialized();
    330   const syncer::sessions::SyncSessionSnapshot& snapshot =
    331       sync_initialized ?
    332       service->GetLastSessionSnapshot() :
    333       syncer::sessions::SyncSessionSnapshot();
    334 
    335   if (is_status_valid)
    336     summary_string.SetValue(service->QuerySyncStatusSummaryString());
    337 
    338   server_url.SetValue(service->sync_service_url().spec());
    339 
    340   if (is_status_valid && !full_status.sync_id.empty())
    341     sync_id.SetValue(full_status.sync_id);
    342   if (is_status_valid && !full_status.invalidator_client_id.empty())
    343     invalidator_id.SetValue(full_status.invalidator_client_id);
    344   if (service->signin())
    345     username.SetValue(service->signin()->GetAuthenticatedUsername());
    346 
    347   const ProfileSyncService::SyncTokenStatus& token_status =
    348       service->GetSyncTokenStatus();
    349   server_connection.SetValue(GetConnectionStatus(token_status));
    350   request_token_time.SetValue(GetTimeStr(token_status.token_request_time,
    351                                          "n/a"));
    352   receive_token_time.SetValue(GetTimeStr(token_status.token_receive_time,
    353                                          "n/a"));
    354   std::string err = token_status.last_get_token_error.error_message();
    355   token_request_status.SetValue(err.empty() ? "OK" : err);
    356   next_token_request.SetValue(
    357       GetTimeStr(token_status.next_token_request_time, "not scheduled"));
    358 
    359   last_synced.SetValue(service->GetLastSyncedTimeString());
    360   is_setup_complete.SetValue(service->HasSyncSetupCompleted());
    361   backend_initialization.SetValue(
    362       service->GetBackendInitializationStateString());
    363   if (is_status_valid) {
    364     is_syncing.SetValue(full_status.syncing);
    365     retry_time.SetValue(GetTimeStr(full_status.retry_time,
    366         "Scheduler is not in backoff or throttled"));
    367   }
    368 
    369   if (snapshot.is_initialized())
    370     is_throttled.SetValue(snapshot.is_silenced());
    371   if (is_status_valid) {
    372     are_notifications_enabled.SetValue(
    373         full_status.notifications_enabled);
    374   }
    375 
    376   if (sync_initialized) {
    377     is_using_explicit_passphrase.SetValue(
    378         service->IsUsingSecondaryPassphrase());
    379     is_passphrase_required.SetValue(service->IsPassphraseRequired());
    380     passphrase_time.SetValue(
    381         GetTimeStr(service->GetExplicitPassphraseTime(), "No Passphrase Time"));
    382   }
    383   if (is_status_valid) {
    384     is_cryptographer_ready.SetValue(full_status.cryptographer_ready);
    385     has_pending_keys.SetValue(full_status.crypto_has_pending_keys);
    386     encrypted_types.SetValue(
    387         ModelTypeSetToString(full_status.encrypted_types));
    388     has_keystore_key.SetValue(full_status.has_keystore_key);
    389     keystore_migration_time.SetValue(
    390         GetTimeStr(full_status.keystore_migration_time, "Not Migrated"));
    391     passphrase_type.SetValue(
    392         PassphraseTypeToString(full_status.passphrase_type));
    393   }
    394 
    395   if (snapshot.is_initialized()) {
    396     if (snapshot.legacy_updates_source() !=
    397         sync_pb::GetUpdatesCallerInfo::UNKNOWN) {
    398       session_source.SetValue(
    399           syncer::GetUpdatesSourceString(snapshot.legacy_updates_source()));
    400     }
    401     get_key_result.SetValue(
    402         GetSyncerErrorString(
    403             snapshot.model_neutral_state().last_get_key_result));
    404     download_result.SetValue(
    405         GetSyncerErrorString(
    406             snapshot.model_neutral_state().last_download_updates_result));
    407     commit_result.SetValue(
    408         GetSyncerErrorString(
    409             snapshot.model_neutral_state().commit_result));
    410   }
    411 
    412   if (is_status_valid) {
    413     notifications_received.SetValue(full_status.notifications_received);
    414     updates_received.SetValue(full_status.updates_received);
    415     tombstone_updates.SetValue(full_status.tombstone_updates_received);
    416     reflected_updates.SetValue(full_status.reflected_updates_received);
    417     successful_commits.SetValue(full_status.num_commits_total);
    418     conflicts_resolved_local_wins.SetValue(
    419         full_status.num_local_overwrites_total);
    420     conflicts_resolved_server_wins.SetValue(
    421         full_status.num_server_overwrites_total);
    422   }
    423 
    424   if (is_status_valid) {
    425     encryption_conflicts.SetValue(full_status.encryption_conflicts);
    426     hierarchy_conflicts.SetValue(full_status.hierarchy_conflicts);
    427     server_conflicts.SetValue(full_status.server_conflicts);
    428     committed_items.SetValue(full_status.committed_count);
    429   }
    430 
    431   if (is_status_valid) {
    432     nudge_source_notification.SetValue(full_status.nudge_source_notification);
    433     nudge_source_local.SetValue(full_status.nudge_source_local);
    434     nudge_source_local_refresh.SetValue(full_status.nudge_source_local_refresh);
    435   }
    436 
    437   if (snapshot.is_initialized()) {
    438     updates_downloaded.SetValue(
    439         snapshot.model_neutral_state().num_updates_downloaded_total);
    440     committed_count.SetValue(
    441         snapshot.model_neutral_state().num_successful_commits);
    442     entries.SetValue(snapshot.num_entries());
    443   }
    444 
    445   // The values set from this point onwards do not belong in the
    446   // details list.
    447 
    448   // We don't need to check is_status_valid here.
    449   // full_status.sync_protocol_error is exported directly from the
    450   // ProfileSyncService, even if the backend doesn't exist.
    451   const bool actionable_error_detected =
    452       full_status.sync_protocol_error.error_type != syncer::UNKNOWN_ERROR &&
    453       full_status.sync_protocol_error.error_type != syncer::SYNC_SUCCESS;
    454 
    455   about_info->SetBoolean("actionable_error_detected",
    456                          actionable_error_detected);
    457 
    458   // NOTE: We won't bother showing any of the following values unless
    459   // actionable_error_detected is set.
    460 
    461   base::ListValue* actionable_error = new base::ListValue();
    462   about_info->Set("actionable_error", actionable_error);
    463 
    464   StringSyncStat error_type(actionable_error, "Error Type");
    465   StringSyncStat action(actionable_error, "Action");
    466   StringSyncStat url(actionable_error, "URL");
    467   StringSyncStat description(actionable_error, "Error Description");
    468 
    469   if (actionable_error_detected) {
    470     error_type.SetValue(syncer::GetSyncErrorTypeString(
    471             full_status.sync_protocol_error.error_type));
    472     action.SetValue(syncer::GetClientActionString(
    473             full_status.sync_protocol_error.action));
    474     url.SetValue(full_status.sync_protocol_error.url);
    475     description.SetValue(full_status.sync_protocol_error.error_description);
    476   }
    477 
    478   about_info->SetBoolean("unrecoverable_error_detected",
    479                          service->HasUnrecoverableError());
    480 
    481   if (service->HasUnrecoverableError()) {
    482     tracked_objects::Location loc(service->unrecoverable_error_location());
    483     std::string location_str;
    484     loc.Write(true, true, &location_str);
    485     std::string unrecoverable_error_message =
    486         "Unrecoverable error detected at " + location_str +
    487         ": " + service->unrecoverable_error_message();
    488     about_info->SetString("unrecoverable_error_message",
    489                        unrecoverable_error_message);
    490   }
    491 
    492   about_info->Set("type_status", service->GetTypeStatusMap());
    493 
    494   return about_info.Pass();
    495 }
    496 
    497 }  // namespace sync_ui_util
    498