preference_model_associator.cc revision dc0f95d653279beabeb9817299e2902918ba123e
1// Copyright (c) 2010 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/values.h"
10#include "base/utf_string_conversions.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/json_value_serializer.h"
18#include "chrome/common/notification_service.h"
19#include "chrome/common/pref_names.h"
20#include "content/browser/browser_thread.h"
21
22namespace browser_sync {
23
24PreferenceModelAssociator::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
41PreferenceModelAssociator::~PreferenceModelAssociator() {
42  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
43}
44
45bool 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
114bool 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    InitPrefNodeAndAssociate(&trans, root, pref);
138  }
139  return true;
140}
141
142bool PreferenceModelAssociator::DisassociateModels() {
143  id_map_.clear();
144  id_map_inverse_.clear();
145  return true;
146}
147
148bool PreferenceModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
149  DCHECK(has_nodes);
150  *has_nodes = false;
151  int64 preferences_sync_id;
152  if (!GetSyncIdForTaggedNode(kPreferencesTag, &preferences_sync_id)) {
153    LOG(ERROR) << "Server did not create the top-level preferences node. We "
154               << "might be running against an out-of-date server.";
155    return false;
156  }
157  sync_api::ReadTransaction trans(sync_service_->GetUserShare());
158
159  sync_api::ReadNode preferences_node(&trans);
160  if (!preferences_node.InitByIdLookup(preferences_sync_id)) {
161    LOG(ERROR) << "Server did not create the top-level preferences node. We "
162               << "might be running against an out-of-date server.";
163    return false;
164  }
165
166  // The sync model has user created nodes if the preferences folder has any
167  // children.
168  *has_nodes = sync_api::kInvalidId != preferences_node.GetFirstChildId();
169  return true;
170}
171
172const PrefService::Preference*
173PreferenceModelAssociator::GetChromeNodeFromSyncId(int64 sync_id) {
174  return NULL;
175}
176
177bool PreferenceModelAssociator::InitSyncNodeFromChromeId(
178    const std::string& node_id,
179    sync_api::BaseNode* sync_node) {
180  return false;
181}
182
183int64 PreferenceModelAssociator::GetSyncIdFromChromeId(
184    const std::string& preference_name) {
185  PreferenceNameToSyncIdMap::const_iterator iter =
186      id_map_.find(preference_name);
187  return iter == id_map_.end() ? sync_api::kInvalidId : iter->second;
188}
189
190void PreferenceModelAssociator::Associate(
191    const PrefService::Preference* preference, int64 sync_id) {
192  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
193  DCHECK_NE(sync_api::kInvalidId, sync_id);
194  DCHECK(id_map_.find(preference->name()) == id_map_.end());
195  DCHECK(id_map_inverse_.find(sync_id) == id_map_inverse_.end());
196  id_map_[preference->name()] = sync_id;
197  id_map_inverse_[sync_id] = preference->name();
198}
199
200void PreferenceModelAssociator::Disassociate(int64 sync_id) {
201  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
202  SyncIdToPreferenceNameMap::iterator iter = id_map_inverse_.find(sync_id);
203  if (iter == id_map_inverse_.end())
204    return;
205  id_map_.erase(iter->second);
206  id_map_inverse_.erase(iter);
207}
208
209bool PreferenceModelAssociator::GetSyncIdForTaggedNode(const std::string& tag,
210                                                       int64* sync_id) {
211  sync_api::ReadTransaction trans(sync_service_->GetUserShare());
212  sync_api::ReadNode sync_node(&trans);
213  if (!sync_node.InitByTagLookup(tag.c_str()))
214    return false;
215  *sync_id = sync_node.GetId();
216  return true;
217}
218
219Value* PreferenceModelAssociator::MergePreference(
220    const PrefService::Preference& local_pref,
221    const Value& server_value) {
222  const std::string& name(local_pref.name());
223  if (name == prefs::kURLsToRestoreOnStartup ||
224      name == prefs::kDesktopNotificationAllowedOrigins ||
225      name == prefs::kDesktopNotificationDeniedOrigins) {
226    return MergeListValues(*local_pref.GetValue(), server_value);
227  }
228
229  if (name == prefs::kContentSettingsPatterns ||
230      name == prefs::kGeolocationContentSettings) {
231    return MergeDictionaryValues(*local_pref.GetValue(), server_value);
232  }
233
234  // If this is not a specially handled preference, server wins.
235  return server_value.DeepCopy();
236}
237
238bool PreferenceModelAssociator::WritePreferenceToNode(
239    const std::string& name,
240    const Value& value,
241    sync_api::WriteNode* node) {
242  std::string serialized;
243  JSONStringValueSerializer json(&serialized);
244  if (!json.Serialize(value)) {
245    LOG(ERROR) << "Failed to serialize preference value.";
246    return false;
247  }
248
249  sync_pb::PreferenceSpecifics preference;
250  preference.set_name(name);
251  preference.set_value(serialized);
252  node->SetPreferenceSpecifics(preference);
253  // TODO(viettrungluu): eliminate conversion (it's temporary)
254  node->SetTitle(UTF8ToWide(name));
255  return true;
256}
257
258Value* PreferenceModelAssociator::MergeListValues(const Value& from_value,
259                                                  const Value& to_value) {
260  if (from_value.GetType() == Value::TYPE_NULL)
261    return to_value.DeepCopy();
262  if (to_value.GetType() == Value::TYPE_NULL)
263    return from_value.DeepCopy();
264
265  DCHECK(from_value.GetType() == Value::TYPE_LIST);
266  DCHECK(to_value.GetType() == Value::TYPE_LIST);
267  const ListValue& from_list_value = static_cast<const ListValue&>(from_value);
268  const ListValue& to_list_value = static_cast<const ListValue&>(to_value);
269  ListValue* result = to_list_value.DeepCopy();
270
271  for (ListValue::const_iterator i = from_list_value.begin();
272       i != from_list_value.end(); ++i) {
273    Value* value = (*i)->DeepCopy();
274    if (!result->AppendIfNotPresent(value))
275      delete value;
276  }
277  return result;
278}
279
280Value* 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
317void 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}  // namespace browser_sync
330