Home | History | Annotate | Download | only in glue
      1 // Copyright (c) 2011 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/glue/preference_model_associator.h"
      6 
      7 #include "base/json/json_reader.h"
      8 #include "base/logging.h"
      9 #include "base/utf_string_conversions.h"
     10 #include "base/values.h"
     11 #include "chrome/browser/prefs/pref_service.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/sync/engine/syncapi.h"
     14 #include "chrome/browser/sync/glue/synchronized_preferences.h"
     15 #include "chrome/browser/sync/profile_sync_service.h"
     16 #include "chrome/browser/sync/protocol/preference_specifics.pb.h"
     17 #include "chrome/common/pref_names.h"
     18 #include "content/browser/browser_thread.h"
     19 #include "content/common/json_value_serializer.h"
     20 #include "content/common/notification_service.h"
     21 
     22 namespace browser_sync {
     23 
     24 PreferenceModelAssociator::PreferenceModelAssociator(
     25     ProfileSyncService* sync_service)
     26     : sync_service_(sync_service),
     27       preferences_node_id_(sync_api::kInvalidId) {
     28   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     29   DCHECK(sync_service_);
     30 
     31   // Add the list of kSynchronizedPreferences to our local
     32   // synced_preferences set, taking care to filter out any preferences
     33   // that are not registered.
     34   PrefService* pref_service = sync_service_->profile()->GetPrefs();
     35   for (size_t i = 0; i < arraysize(kSynchronizedPreferences); ++i) {
     36     if (pref_service->FindPreference(kSynchronizedPreferences[i]))
     37       synced_preferences_.insert(kSynchronizedPreferences[i]);
     38   }
     39 }
     40 
     41 PreferenceModelAssociator::~PreferenceModelAssociator() {
     42   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     43 }
     44 
     45 bool PreferenceModelAssociator::InitPrefNodeAndAssociate(
     46     sync_api::WriteTransaction* trans,
     47     const sync_api::BaseNode& root,
     48     const PrefService::Preference* pref) {
     49   DCHECK(pref);
     50 
     51   PrefService* pref_service = sync_service_->profile()->GetPrefs();
     52   base::JSONReader reader;
     53   std::string tag = pref->name();
     54   sync_api::WriteNode node(trans);
     55   if (node.InitByClientTagLookup(syncable::PREFERENCES, tag)) {
     56     // The server has a value for the preference.
     57     const sync_pb::PreferenceSpecifics& preference(
     58         node.GetPreferenceSpecifics());
     59     DCHECK_EQ(tag, preference.name());
     60 
     61     if (pref->IsUserModifiable()) {
     62       scoped_ptr<Value> value(
     63           reader.JsonToValue(preference.value(), false, false));
     64       std::string pref_name = preference.name();
     65       if (!value.get()) {
     66         LOG(ERROR) << "Failed to deserialize preference value: "
     67                    << reader.GetErrorMessage();
     68         return false;
     69       }
     70 
     71       // Merge the server value of this preference with the local value.
     72       scoped_ptr<Value> new_value(MergePreference(*pref, *value));
     73 
     74       // Update the local preference based on what we got from the
     75       // sync server.
     76       if (new_value->IsType(Value::TYPE_NULL)) {
     77         pref_service->ClearPref(pref_name.c_str());
     78       } else if (!new_value->IsType(pref->GetType())) {
     79         LOG(WARNING) << "Synced value for " << preference.name()
     80                      << " is of type " << new_value->GetType()
     81                      << " which doesn't match pref type " << pref->GetType();
     82       } else if (!pref->GetValue()->Equals(new_value.get())) {
     83         pref_service->Set(pref_name.c_str(), *new_value);
     84       }
     85 
     86       AfterUpdateOperations(pref_name);
     87 
     88       // If the merge resulted in an updated value, write it back to
     89       // the sync node.
     90       if (!value->Equals(new_value.get()) &&
     91           !WritePreferenceToNode(pref->name(), *new_value, &node))
     92         return false;
     93     }
     94     Associate(pref, node.GetId());
     95   } else if (pref->IsUserControlled()) {
     96     // The server doesn't have a value, but we have a user-controlled value,
     97     // so we push it to the server.
     98     sync_api::WriteNode write_node(trans);
     99     if (!write_node.InitUniqueByCreation(syncable::PREFERENCES, root, tag)) {
    100       LOG(ERROR) << "Failed to create preference sync node.";
    101       return false;
    102     }
    103 
    104     // Update the sync node with the local value for this preference.
    105     if (!WritePreferenceToNode(pref->name(), *pref->GetValue(), &write_node))
    106       return false;
    107 
    108     Associate(pref, write_node.GetId());
    109   }
    110 
    111   return true;
    112 }
    113 
    114 bool PreferenceModelAssociator::AssociateModels() {
    115   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    116   PrefService* pref_service = sync_service_->profile()->GetPrefs();
    117 
    118   int64 root_id;
    119   if (!GetSyncIdForTaggedNode(kPreferencesTag, &root_id)) {
    120     LOG(ERROR) << "Server did not create the top-level preferences node. We "
    121                << "might be running against an out-of-date server.";
    122     return false;
    123   }
    124 
    125   sync_api::WriteTransaction trans(sync_service_->GetUserShare());
    126   sync_api::ReadNode root(&trans);
    127   if (!root.InitByIdLookup(root_id)) {
    128     LOG(ERROR) << "Server did not create the top-level preferences node. We "
    129                << "might be running against an out-of-date server.";
    130     return false;
    131   }
    132 
    133   for (std::set<std::string>::iterator it = synced_preferences_.begin();
    134        it != synced_preferences_.end(); ++it) {
    135     const PrefService::Preference* pref =
    136         pref_service->FindPreference((*it).c_str());
    137     DCHECK(pref);
    138     InitPrefNodeAndAssociate(&trans, root, pref);
    139   }
    140   return true;
    141 }
    142 
    143 bool PreferenceModelAssociator::DisassociateModels() {
    144   id_map_.clear();
    145   id_map_inverse_.clear();
    146   return true;
    147 }
    148 
    149 bool PreferenceModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
    150   DCHECK(has_nodes);
    151   *has_nodes = false;
    152   int64 preferences_sync_id;
    153   if (!GetSyncIdForTaggedNode(kPreferencesTag, &preferences_sync_id)) {
    154     LOG(ERROR) << "Server did not create the top-level preferences node. We "
    155                << "might be running against an out-of-date server.";
    156     return false;
    157   }
    158   sync_api::ReadTransaction trans(sync_service_->GetUserShare());
    159 
    160   sync_api::ReadNode preferences_node(&trans);
    161   if (!preferences_node.InitByIdLookup(preferences_sync_id)) {
    162     LOG(ERROR) << "Server did not create the top-level preferences node. We "
    163                << "might be running against an out-of-date server.";
    164     return false;
    165   }
    166 
    167   // The sync model has user created nodes if the preferences folder has any
    168   // children.
    169   *has_nodes = sync_api::kInvalidId != preferences_node.GetFirstChildId();
    170   return true;
    171 }
    172 
    173 const PrefService::Preference*
    174 PreferenceModelAssociator::GetChromeNodeFromSyncId(int64 sync_id) {
    175   return NULL;
    176 }
    177 
    178 bool PreferenceModelAssociator::InitSyncNodeFromChromeId(
    179     const std::string& node_id,
    180     sync_api::BaseNode* sync_node) {
    181   return false;
    182 }
    183 
    184 int64 PreferenceModelAssociator::GetSyncIdFromChromeId(
    185     const std::string& preference_name) {
    186   PreferenceNameToSyncIdMap::const_iterator iter =
    187       id_map_.find(preference_name);
    188   return iter == id_map_.end() ? sync_api::kInvalidId : iter->second;
    189 }
    190 
    191 void PreferenceModelAssociator::Associate(
    192     const PrefService::Preference* preference, int64 sync_id) {
    193   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    194   DCHECK_NE(sync_api::kInvalidId, sync_id);
    195   DCHECK(id_map_.find(preference->name()) == id_map_.end());
    196   DCHECK(id_map_inverse_.find(sync_id) == id_map_inverse_.end());
    197   id_map_[preference->name()] = sync_id;
    198   id_map_inverse_[sync_id] = preference->name();
    199 }
    200 
    201 void PreferenceModelAssociator::Disassociate(int64 sync_id) {
    202   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    203   SyncIdToPreferenceNameMap::iterator iter = id_map_inverse_.find(sync_id);
    204   if (iter == id_map_inverse_.end())
    205     return;
    206   id_map_.erase(iter->second);
    207   id_map_inverse_.erase(iter);
    208 }
    209 
    210 bool PreferenceModelAssociator::GetSyncIdForTaggedNode(const std::string& tag,
    211                                                        int64* sync_id) {
    212   sync_api::ReadTransaction trans(sync_service_->GetUserShare());
    213   sync_api::ReadNode sync_node(&trans);
    214   if (!sync_node.InitByTagLookup(tag.c_str()))
    215     return false;
    216   *sync_id = sync_node.GetId();
    217   return true;
    218 }
    219 
    220 Value* PreferenceModelAssociator::MergePreference(
    221     const PrefService::Preference& local_pref,
    222     const Value& server_value) {
    223   const std::string& name(local_pref.name());
    224   if (name == prefs::kURLsToRestoreOnStartup ||
    225       name == prefs::kDesktopNotificationAllowedOrigins ||
    226       name == prefs::kDesktopNotificationDeniedOrigins) {
    227     return MergeListValues(*local_pref.GetValue(), server_value);
    228   }
    229 
    230   if (name == prefs::kContentSettingsPatterns ||
    231       name == prefs::kGeolocationContentSettings) {
    232     return MergeDictionaryValues(*local_pref.GetValue(), server_value);
    233   }
    234 
    235   // If this is not a specially handled preference, server wins.
    236   return server_value.DeepCopy();
    237 }
    238 
    239 bool PreferenceModelAssociator::WritePreferenceToNode(
    240     const std::string& name,
    241     const Value& value,
    242     sync_api::WriteNode* node) {
    243   std::string serialized;
    244   JSONStringValueSerializer json(&serialized);
    245   if (!json.Serialize(value)) {
    246     LOG(ERROR) << "Failed to serialize preference value.";
    247     return false;
    248   }
    249 
    250   sync_pb::PreferenceSpecifics preference;
    251   preference.set_name(name);
    252   preference.set_value(serialized);
    253   node->SetPreferenceSpecifics(preference);
    254   // TODO(viettrungluu): eliminate conversion (it's temporary)
    255   node->SetTitle(UTF8ToWide(name));
    256   return true;
    257 }
    258 
    259 Value* PreferenceModelAssociator::MergeListValues(const Value& from_value,
    260                                                   const Value& to_value) {
    261   if (from_value.GetType() == Value::TYPE_NULL)
    262     return to_value.DeepCopy();
    263   if (to_value.GetType() == Value::TYPE_NULL)
    264     return from_value.DeepCopy();
    265 
    266   DCHECK(from_value.GetType() == Value::TYPE_LIST);
    267   DCHECK(to_value.GetType() == Value::TYPE_LIST);
    268   const ListValue& from_list_value = static_cast<const ListValue&>(from_value);
    269   const ListValue& to_list_value = static_cast<const ListValue&>(to_value);
    270   ListValue* result = to_list_value.DeepCopy();
    271 
    272   for (ListValue::const_iterator i = from_list_value.begin();
    273        i != from_list_value.end(); ++i) {
    274     Value* value = (*i)->DeepCopy();
    275     result->AppendIfNotPresent(value);
    276   }
    277   return result;
    278 }
    279 
    280 Value* PreferenceModelAssociator::MergeDictionaryValues(
    281     const Value& from_value,
    282     const Value& to_value) {
    283   if (from_value.GetType() == Value::TYPE_NULL)
    284     return to_value.DeepCopy();
    285   if (to_value.GetType() == Value::TYPE_NULL)
    286     return from_value.DeepCopy();
    287 
    288   DCHECK(from_value.GetType() == Value::TYPE_DICTIONARY);
    289   DCHECK(to_value.GetType() == Value::TYPE_DICTIONARY);
    290   const DictionaryValue& from_dict_value =
    291       static_cast<const DictionaryValue&>(from_value);
    292   const DictionaryValue& to_dict_value =
    293       static_cast<const DictionaryValue&>(to_value);
    294   DictionaryValue* result = to_dict_value.DeepCopy();
    295 
    296   for (DictionaryValue::key_iterator key = from_dict_value.begin_keys();
    297        key != from_dict_value.end_keys(); ++key) {
    298     Value* from_value;
    299     bool success = from_dict_value.GetWithoutPathExpansion(*key, &from_value);
    300     DCHECK(success);
    301 
    302     Value* to_key_value;
    303     if (result->GetWithoutPathExpansion(*key, &to_key_value)) {
    304       if (to_key_value->GetType() == Value::TYPE_DICTIONARY) {
    305         Value* merged_value = MergeDictionaryValues(*from_value, *to_key_value);
    306         result->SetWithoutPathExpansion(*key, merged_value);
    307       }
    308       // Note that for all other types we want to preserve the "to"
    309       // values so we do nothing here.
    310     } else {
    311       result->SetWithoutPathExpansion(*key, from_value->DeepCopy());
    312     }
    313   }
    314   return result;
    315 }
    316 
    317 void PreferenceModelAssociator::AfterUpdateOperations(
    318     const std::string& pref_name) {
    319   // The bookmark bar visibility preference requires a special
    320   // notification to update the UI.
    321   if (0 == pref_name.compare(prefs::kShowBookmarkBar)) {
    322     NotificationService::current()->Notify(
    323         NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED,
    324         Source<PreferenceModelAssociator>(this),
    325         NotificationService::NoDetails());
    326   }
    327 }
    328 
    329 bool PreferenceModelAssociator::CryptoReadyIfNecessary() {
    330   // We only access the cryptographer while holding a transaction.
    331   sync_api::ReadTransaction trans(sync_service_->GetUserShare());
    332   syncable::ModelTypeSet encrypted_types;
    333   sync_service_->GetEncryptedDataTypes(&encrypted_types);
    334   return encrypted_types.count(syncable::PREFERENCES) == 0 ||
    335          sync_service_->IsCryptographerReady(&trans);
    336 }
    337 
    338 }  // namespace browser_sync
    339