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/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