Follow-up for: Disable inline autocompletion for IME in Omnibox by default.
[chromium-blink-merge.git] / sync / engine / conflict_resolver.cc
blobc4814720478fe0e373d2a6d013ef184e6d8f4cf6
1 // Copyright 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.
5 #include "sync/engine/conflict_resolver.h"
7 #include <list>
8 #include <set>
9 #include <string>
11 #include "base/metrics/histogram.h"
12 #include "sync/engine/conflict_util.h"
13 #include "sync/engine/syncer_util.h"
14 #include "sync/sessions/status_controller.h"
15 #include "sync/syncable/directory.h"
16 #include "sync/syncable/mutable_entry.h"
17 #include "sync/syncable/syncable_write_transaction.h"
18 #include "sync/util/cryptographer.h"
20 using std::list;
21 using std::set;
23 namespace syncer {
25 using sessions::StatusController;
26 using syncable::Directory;
27 using syncable::Entry;
28 using syncable::Id;
29 using syncable::MutableEntry;
30 using syncable::WriteTransaction;
32 ConflictResolver::ConflictResolver() {
35 ConflictResolver::~ConflictResolver() {
38 void ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
39 const Id& id,
40 const Cryptographer* cryptographer,
41 StatusController* status) {
42 MutableEntry entry(trans, syncable::GET_BY_ID, id);
43 // Must be good as the entry won't have been cleaned up.
44 CHECK(entry.good());
46 // This function can only resolve simple conflicts. Simple conflicts have
47 // both IS_UNSYNCED and IS_UNAPPLIED_UDPATE set.
48 if (!entry.GetIsUnappliedUpdate() || !entry.GetIsUnsynced()) {
49 // This is very unusual, but it can happen in tests. We may be able to
50 // assert NOTREACHED() here when those tests are updated.
51 return;
54 if (entry.GetIsDel() && entry.GetServerIsDel()) {
55 // we've both deleted it, so lets just drop the need to commit/update this
56 // entry.
57 entry.PutIsUnsynced(false);
58 entry.PutIsUnappliedUpdate(false);
59 // we've made changes, but they won't help syncing progress.
60 // METRIC simple conflict resolved by merge.
61 return;
64 // This logic determines "client wins" vs. "server wins" strategy picking.
65 // By the time we get to this point, we rely on the following to be true:
66 // a) We can decrypt both the local and server data (else we'd be in
67 // conflict encryption and not attempting to resolve).
68 // b) All unsynced changes have been re-encrypted with the default key (
69 // occurs either in AttemptToUpdateEntry, SetEncryptionPassphrase,
70 // SetDecryptionPassphrase, or RefreshEncryption).
71 // c) Base_server_specifics having a valid datatype means that we received
72 // an undecryptable update that only changed specifics, and since then have
73 // not received any further non-specifics-only or decryptable updates.
74 // d) If the server_specifics match specifics, server_specifics are
75 // encrypted with the default key, and all other visible properties match,
76 // then we can safely ignore the local changes as redundant.
77 // e) Otherwise if the base_server_specifics match the server_specifics, no
78 // functional change must have been made server-side (else
79 // base_server_specifics would have been cleared), and we can therefore
80 // safely ignore the server changes as redundant.
81 // f) Otherwise, it's in general safer to ignore local changes, with the
82 // exception of deletion conflicts (choose to undelete) and conflicts
83 // where the non_unique_name or parent don't match.
84 if (!entry.GetServerIsDel()) {
85 // TODO(nick): The current logic is arbitrary; instead, it ought to be made
86 // consistent with the ModelAssociator behavior for a datatype. It would
87 // be nice if we could route this back to ModelAssociator code to pick one
88 // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill)
89 // are easily mergeable.
90 // See http://crbug.com/77339.
91 bool name_matches = entry.GetNonUniqueName() ==
92 entry.GetServerNonUniqueName();
93 bool parent_matches = entry.GetParentId() == entry.GetServerParentId();
94 bool entry_deleted = entry.GetIsDel();
95 // The position check might fail spuriously if one of the positions was
96 // based on a legacy random suffix, rather than a deterministic one based on
97 // originator_cache_guid and originator_item_id. If an item is being
98 // modified regularly, it shouldn't take long for the suffix and position to
99 // be updated, so such false failures shouldn't be a problem for long.
101 // Lucky for us, it's OK to be wrong here. The position_matches check is
102 // allowed to return false negatives, as long as it returns no false
103 // positives.
104 bool position_matches = parent_matches &&
105 entry.GetServerUniquePosition().Equals(entry.GetUniquePosition());
106 const sync_pb::EntitySpecifics& specifics = entry.GetSpecifics();
107 const sync_pb::EntitySpecifics& server_specifics =
108 entry.GetServerSpecifics();
109 const sync_pb::EntitySpecifics& base_server_specifics =
110 entry.GetBaseServerSpecifics();
111 std::string decrypted_specifics, decrypted_server_specifics;
112 bool specifics_match = false;
113 bool server_encrypted_with_default_key = false;
114 if (specifics.has_encrypted()) {
115 DCHECK(cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted()));
116 decrypted_specifics = cryptographer->DecryptToString(
117 specifics.encrypted());
118 } else {
119 decrypted_specifics = specifics.SerializeAsString();
121 if (server_specifics.has_encrypted()) {
122 server_encrypted_with_default_key =
123 cryptographer->CanDecryptUsingDefaultKey(
124 server_specifics.encrypted());
125 decrypted_server_specifics = cryptographer->DecryptToString(
126 server_specifics.encrypted());
127 } else {
128 decrypted_server_specifics = server_specifics.SerializeAsString();
130 if (decrypted_server_specifics == decrypted_specifics &&
131 server_encrypted_with_default_key == specifics.has_encrypted()) {
132 specifics_match = true;
134 bool base_server_specifics_match = false;
135 if (server_specifics.has_encrypted() &&
136 IsRealDataType(GetModelTypeFromSpecifics(base_server_specifics))) {
137 std::string decrypted_base_server_specifics;
138 if (!base_server_specifics.has_encrypted()) {
139 decrypted_base_server_specifics =
140 base_server_specifics.SerializeAsString();
141 } else {
142 decrypted_base_server_specifics = cryptographer->DecryptToString(
143 base_server_specifics.encrypted());
145 if (decrypted_server_specifics == decrypted_base_server_specifics)
146 base_server_specifics_match = true;
149 if (!entry_deleted && name_matches && parent_matches && specifics_match &&
150 position_matches) {
151 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring "
152 << "changes for: " << entry;
153 conflict_util::IgnoreConflict(&entry);
154 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
155 CHANGES_MATCH,
156 CONFLICT_RESOLUTION_SIZE);
157 } else if (base_server_specifics_match) {
158 DVLOG(1) << "Resolving simple conflict, ignoring server encryption "
159 << " changes for: " << entry;
160 status->increment_num_server_overwrites();
161 conflict_util::OverwriteServerChanges(&entry);
162 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
163 IGNORE_ENCRYPTION,
164 CONFLICT_RESOLUTION_SIZE);
165 } else if (entry_deleted || !name_matches || !parent_matches) {
166 // NOTE: The update application logic assumes that conflict resolution
167 // will never result in changes to the local hierarchy. The entry_deleted
168 // and !parent_matches cases here are critical to maintaining that
169 // assumption.
170 conflict_util::OverwriteServerChanges(&entry);
171 status->increment_num_server_overwrites();
172 DVLOG(1) << "Resolving simple conflict, overwriting server changes "
173 << "for: " << entry;
174 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
175 OVERWRITE_SERVER,
176 CONFLICT_RESOLUTION_SIZE);
177 } else {
178 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: "
179 << entry;
180 conflict_util::IgnoreLocalChanges(&entry);
181 status->increment_num_local_overwrites();
182 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
183 OVERWRITE_LOCAL,
184 CONFLICT_RESOLUTION_SIZE);
186 // Now that we've resolved the conflict, clear the prev server
187 // specifics.
188 entry.PutBaseServerSpecifics(sync_pb::EntitySpecifics());
189 } else { // SERVER_IS_DEL is true
190 if (entry.GetIsDir()) {
191 Directory::Metahandles children;
192 trans->directory()->GetChildHandlesById(trans,
193 entry.GetId(),
194 &children);
195 // If a server deleted folder has local contents it should be a hierarchy
196 // conflict. Hierarchy conflicts should not be processed by this
197 // function.
198 DCHECK(children.empty());
201 // The entry is deleted on the server but still exists locally.
202 // We undelete it by overwriting the server's tombstone with the local
203 // data.
204 conflict_util::OverwriteServerChanges(&entry);
205 status->increment_num_server_overwrites();
206 DVLOG(1) << "Resolving simple conflict, undeleting server entry: "
207 << entry;
208 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
209 UNDELETE,
210 CONFLICT_RESOLUTION_SIZE);
214 void ConflictResolver::ResolveConflicts(
215 syncable::WriteTransaction* trans,
216 const Cryptographer* cryptographer,
217 const std::set<syncable::Id>& simple_conflict_ids,
218 sessions::StatusController* status) {
219 // Iterate over simple conflict items.
220 set<Id>::const_iterator it;
221 for (it = simple_conflict_ids.begin();
222 it != simple_conflict_ids.end();
223 ++it) {
224 // We don't resolve conflicts for control types here.
225 Entry conflicting_node(trans, syncable::GET_BY_ID, *it);
226 CHECK(conflicting_node.good());
227 if (IsControlType(
228 GetModelTypeFromSpecifics(conflicting_node.GetSpecifics()))) {
229 continue;
232 ProcessSimpleConflict(trans, *it, cryptographer, status);
234 return;
237 } // namespace syncer