| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE entry. | 3 // found in the LICENSE entry. |
| 4 | 4 |
| 5 #include "chrome/browser/sync/engine/conflict_resolver.h" | 5 #include "chrome/browser/sync/engine/conflict_resolver.h" |
| 6 | 6 |
| 7 #include <map> | 7 #include <map> |
| 8 #include <set> | 8 #include <set> |
| 9 | 9 |
| 10 #include "chrome/browser/sync/engine/syncer.h" | 10 #include "chrome/browser/sync/engine/syncer.h" |
| 11 #include "chrome/browser/sync/engine/syncer_util.h" | 11 #include "chrome/browser/sync/engine/syncer_util.h" |
| 12 #include "chrome/browser/sync/protocol/service_constants.h" | 12 #include "chrome/browser/sync/protocol/service_constants.h" |
| 13 #include "chrome/browser/sync/syncable/directory_manager.h" | 13 #include "chrome/browser/sync/syncable/directory_manager.h" |
| 14 #include "chrome/browser/sync/syncable/syncable.h" | 14 #include "chrome/browser/sync/syncable/syncable.h" |
| 15 #include "chrome/browser/sync/util/character_set_converters.h" | 15 #include "chrome/browser/sync/util/character_set_converters.h" |
| 16 #include "chrome/browser/sync/util/event_sys-inl.h" | 16 #include "chrome/browser/sync/util/event_sys-inl.h" |
| 17 #include "chrome/browser/sync/util/path_helpers.h" | 17 #include "chrome/browser/sync/util/path_helpers.h" |
| 18 | 18 |
| 19 using std::map; | 19 using std::map; |
| 20 using std::set; | 20 using std::set; |
| 21 using syncable::BaseTransaction; | 21 using syncable::BaseTransaction; |
| 22 using syncable::Directory; | 22 using syncable::Directory; |
| 23 using syncable::Entry; | 23 using syncable::Entry; |
| 24 using syncable::Id; | 24 using syncable::Id; |
| 25 using syncable::MutableEntry; | 25 using syncable::MutableEntry; |
| 26 using syncable::Name; | |
| 27 using syncable::ScopedDirLookup; | 26 using syncable::ScopedDirLookup; |
| 28 using syncable::SyncName; | |
| 29 using syncable::WriteTransaction; | 27 using syncable::WriteTransaction; |
| 30 | 28 |
| 31 namespace browser_sync { | 29 namespace browser_sync { |
| 32 | 30 |
| 33 const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8; | 31 const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8; |
| 34 | 32 |
| 35 ConflictResolver::ConflictResolver() { | 33 ConflictResolver::ConflictResolver() { |
| 36 } | 34 } |
| 37 | 35 |
| 38 ConflictResolver::~ConflictResolver() { | 36 ConflictResolver::~ConflictResolver() { |
| 39 } | 37 } |
| 40 | 38 |
| 41 namespace { | |
| 42 // TODO(ncarter): Remove title/path conflicts and the code to resolve them. | |
| 43 // This is historical cruft that seems to be actually reached by some users. | |
| 44 inline PathString GetConflictPathnameBase(PathString base) { | |
| 45 time_t time_since = time(NULL); | |
| 46 struct tm* now = localtime(&time_since); | |
| 47 // Use a fixed format as the locale's format may include '/' characters or | |
| 48 // other illegal characters. | |
| 49 PathString date = IntToPathString(now->tm_year + 1900); | |
| 50 date.append(PSTR("-")); | |
| 51 ++now->tm_mon; // tm_mon is 0-based. | |
| 52 if (now->tm_mon < 10) | |
| 53 date.append(PSTR("0")); | |
| 54 date.append(IntToPathString(now->tm_mon)); | |
| 55 date.append(PSTR("-")); | |
| 56 if (now->tm_mday < 10) | |
| 57 date.append(PSTR("0")); | |
| 58 date.append(IntToPathString(now->tm_mday)); | |
| 59 return base + PSTR(" (Edited on ") + date + PSTR(")"); | |
| 60 } | |
| 61 | |
| 62 // TODO(ncarter): Remove title/path conflicts and the code to resolve them. | |
| 63 Name FindNewName(BaseTransaction* trans, | |
| 64 Id parent_id, | |
| 65 const SyncName& original_name) { | |
| 66 const PathString name = original_name.value(); | |
| 67 // 255 is defined in our spec. | |
| 68 const size_t allowed_length = kSyncProtocolMaxNameLengthBytes; | |
| 69 // TODO(sync): How do we get length on other platforms? The limit is | |
| 70 // checked in java on the server, so it's not the number of glyphs its the | |
| 71 // number of 16 bit characters in the UTF-16 representation. | |
| 72 | |
| 73 // 10 characters for 32 bit numbers + 2 characters for brackets means 12 | |
| 74 // characters should be more than enough for the name. Doubling this ensures | |
| 75 // that we will have enough space. | |
| 76 COMPILE_ASSERT(kSyncProtocolMaxNameLengthBytes >= 24, | |
| 77 maximum_name_too_short); | |
| 78 CHECK(name.length() <= allowed_length); | |
| 79 | |
| 80 if (!Entry(trans, | |
| 81 syncable::GET_BY_PARENTID_AND_DBNAME, | |
| 82 parent_id, | |
| 83 name).good()) | |
| 84 return Name::FromSyncName(original_name); | |
| 85 PathString base = name; | |
| 86 PathString ext; | |
| 87 PathString::size_type ext_index = name.rfind('.'); | |
| 88 if (PathString::npos != ext_index && 0 != ext_index && | |
| 89 name.length() - ext_index < allowed_length / 2) { | |
| 90 base = name.substr(0, ext_index); | |
| 91 ext = name.substr(ext_index); | |
| 92 } | |
| 93 | |
| 94 PathString name_base = GetConflictPathnameBase(base); | |
| 95 if (name_base.length() + ext.length() > allowed_length) { | |
| 96 name_base.resize(allowed_length - ext.length()); | |
| 97 TrimPathStringToValidCharacter(&name_base); | |
| 98 } | |
| 99 PathString new_name = name_base + ext; | |
| 100 int n = 2; | |
| 101 while (Entry(trans, | |
| 102 syncable::GET_BY_PARENTID_AND_DBNAME, | |
| 103 parent_id, | |
| 104 new_name).good()) { | |
| 105 PathString local_ext = PSTR("("); | |
| 106 local_ext.append(IntToPathString(n)); | |
| 107 local_ext.append(PSTR(")")); | |
| 108 local_ext.append(ext); | |
| 109 if (name_base.length() + local_ext.length() > allowed_length) { | |
| 110 name_base.resize(allowed_length - local_ext.length()); | |
| 111 TrimPathStringToValidCharacter(&name_base); | |
| 112 } | |
| 113 new_name = name_base + local_ext; | |
| 114 n++; | |
| 115 } | |
| 116 | |
| 117 CHECK(new_name.length() <= kSyncProtocolMaxNameLengthBytes); | |
| 118 return Name(new_name); | |
| 119 } | |
| 120 | |
| 121 } // namespace | |
| 122 | |
| 123 void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) { | 39 void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) { |
| 124 // An update matches local actions, merge the changes. | 40 // An update matches local actions, merge the changes. |
| 125 // This is a little fishy because we don't actually merge them. | 41 // This is a little fishy because we don't actually merge them. |
| 126 // In the future we should do a 3-way merge. | 42 // In the future we should do a 3-way merge. |
| 127 LOG(INFO) << "Server and local changes match, merging:" << entry; | 43 LOG(INFO) << "Server and local changes match, merging:" << entry; |
| 128 // With IS_UNSYNCED false, changes should be merged. | 44 // With IS_UNSYNCED false, changes should be merged. |
| 129 // METRIC simple conflict resolved by merge. | 45 // METRIC simple conflict resolved by merge. |
| 130 entry->Put(syncable::IS_UNSYNCED, false); | 46 entry->Put(syncable::IS_UNSYNCED, false); |
| 131 } | 47 } |
| 132 | 48 |
| (...skipping 11 matching lines...) Expand all Loading... |
| 144 | 60 |
| 145 ConflictResolver::ProcessSimpleConflictResult | 61 ConflictResolver::ProcessSimpleConflictResult |
| 146 ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, | 62 ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, |
| 147 Id id, | 63 Id id, |
| 148 SyncerSession* session) { | 64 SyncerSession* session) { |
| 149 MutableEntry entry(trans, syncable::GET_BY_ID, id); | 65 MutableEntry entry(trans, syncable::GET_BY_ID, id); |
| 150 // Must be good as the entry won't have been cleaned up. | 66 // Must be good as the entry won't have been cleaned up. |
| 151 CHECK(entry.good()); | 67 CHECK(entry.good()); |
| 152 // If an update fails, locally we have to be in a set or unsynced. We're not | 68 // If an update fails, locally we have to be in a set or unsynced. We're not |
| 153 // in a set here, so we must be unsynced. | 69 // in a set here, so we must be unsynced. |
| 154 if (!entry.Get(syncable::IS_UNSYNCED)) | 70 if (!entry.Get(syncable::IS_UNSYNCED)) { |
| 155 return NO_SYNC_PROGRESS; | 71 return NO_SYNC_PROGRESS; |
| 72 } |
| 73 |
| 156 if (!entry.Get(syncable::IS_UNAPPLIED_UPDATE)) { | 74 if (!entry.Get(syncable::IS_UNAPPLIED_UPDATE)) { |
| 157 if (!entry.Get(syncable::PARENT_ID).ServerKnows()) { | 75 if (!entry.Get(syncable::PARENT_ID).ServerKnows()) { |
| 158 LOG(INFO) << "Item conflicting because its parent not yet committed. " | 76 LOG(INFO) << "Item conflicting because its parent not yet committed. " |
| 159 "Id: "<< id; | 77 "Id: "<< id; |
| 160 } else { | 78 } else { |
| 161 LOG(INFO) << "No set for conflicting entry id " << id << ". There should " | 79 LOG(INFO) << "No set for conflicting entry id " << id << ". There should " |
| 162 "be an update/commit that will fix this soon. This message should " | 80 "be an update/commit that will fix this soon. This message should " |
| 163 "not repeat."; | 81 "not repeat."; |
| 164 } | 82 } |
| 165 return NO_SYNC_PROGRESS; | 83 return NO_SYNC_PROGRESS; |
| 166 } | 84 } |
| 85 |
| 167 if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) { | 86 if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) { |
| 168 // we've both deleted it, so lets just drop the need to commit/update this | 87 // we've both deleted it, so lets just drop the need to commit/update this |
| 169 // entry. | 88 // entry. |
| 170 entry.Put(syncable::IS_UNSYNCED, false); | 89 entry.Put(syncable::IS_UNSYNCED, false); |
| 171 entry.Put(syncable::IS_UNAPPLIED_UPDATE, false); | 90 entry.Put(syncable::IS_UNAPPLIED_UPDATE, false); |
| 172 // we've made changes, but they won't help syncing progress. | 91 // we've made changes, but they won't help syncing progress. |
| 173 // METRIC simple conflict resolved by merge. | 92 // METRIC simple conflict resolved by merge. |
| 174 return NO_SYNC_PROGRESS; | 93 return NO_SYNC_PROGRESS; |
| 175 } | 94 } |
| 176 | 95 |
| 177 if (!entry.Get(syncable::SERVER_IS_DEL)) { | 96 if (!entry.Get(syncable::SERVER_IS_DEL)) { |
| 178 // TODO(chron): Should we check more fields? Since IS_UNSYNCED is | 97 // TODO(chron): Should we check more fields? Since IS_UNSYNCED is |
| 179 // turned on, this is really probably enough as fields will be overwritten. | 98 // turned on, this is really probably enough as fields will be overwritten. |
| 180 // Check if there's no changes. | 99 // Check if there's no changes. |
| 181 | 100 |
| 182 // Verbose but easier to debug. | 101 // Verbose but easier to debug. |
| 183 bool name_matches = entry.SyncNameMatchesServerName(); | 102 bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) == |
| 103 entry.Get(syncable::SERVER_NON_UNIQUE_NAME); |
| 184 bool parent_matches = entry.Get(syncable::PARENT_ID) == | 104 bool parent_matches = entry.Get(syncable::PARENT_ID) == |
| 185 entry.Get(syncable::SERVER_PARENT_ID); | 105 entry.Get(syncable::SERVER_PARENT_ID); |
| 186 bool entry_deleted = entry.Get(syncable::IS_DEL); | 106 bool entry_deleted = entry.Get(syncable::IS_DEL); |
| 187 | 107 |
| 188 if (!entry_deleted && name_matches && parent_matches) { | 108 if (!entry_deleted && name_matches && parent_matches) { |
| 189 LOG(INFO) << "Resolving simple conflict, ignoring local changes for:" | 109 LOG(INFO) << "Resolving simple conflict, ignoring local changes for:" |
| 190 << entry; | 110 << entry; |
| 191 IgnoreLocalChanges(&entry); | 111 IgnoreLocalChanges(&entry); |
| 192 } else { | 112 } else { |
| 193 LOG(INFO) << "Resolving simple conflict, overwriting server" | 113 LOG(INFO) << "Resolving simple conflict, overwriting server" |
| 194 " changes for:" << entry; | 114 " changes for:" << entry; |
| 195 OverwriteServerChanges(trans, &entry); | 115 OverwriteServerChanges(trans, &entry); |
| 196 } | 116 } |
| 197 return SYNC_PROGRESS; | 117 return SYNC_PROGRESS; |
| 198 } else { // SERVER_IS_DEL is true | 118 } else { // SERVER_IS_DEL is true |
| 199 // If a server deleted folder has local contents we should be in a set. | 119 // If a server deleted folder has local contents we should be in a set. |
| 200 if (entry.Get(syncable::IS_DIR)) { | 120 if (entry.Get(syncable::IS_DIR)) { |
| 201 Directory::ChildHandles children; | 121 Directory::ChildHandles children; |
| 202 trans->directory()->GetChildHandles(trans, | 122 trans->directory()->GetChildHandles(trans, |
| 203 entry.Get(syncable::ID), | 123 entry.Get(syncable::ID), |
| 204 &children); | 124 &children); |
| 205 if (0 != children.size()) { | 125 if (0 != children.size()) { |
| 206 LOG(INFO) << "Entry is a server deleted directory with local contents, " | 126 LOG(INFO) << "Entry is a server deleted directory with local contents, " |
| 207 "should be in a set. (race condition)."; | 127 "should be in a set. (race condition)."; |
| 208 return NO_SYNC_PROGRESS; | 128 return NO_SYNC_PROGRESS; |
| 209 } | 129 } |
| 210 } | 130 } |
| 211 // METRIC conflict resolved by entry split; | |
| 212 | 131 |
| 213 // If the entry's deleted on the server, we can have a directory here. | 132 // If the entry's deleted on the server, we can have a directory here. |
| 214 entry.Put(syncable::IS_UNSYNCED, true); | 133 entry.Put(syncable::IS_UNSYNCED, true); |
| 215 | 134 |
| 216 SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry); | 135 SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry); |
| 217 | 136 |
| 218 MutableEntry server_update(trans, syncable::GET_BY_ID, id); | 137 MutableEntry server_update(trans, syncable::GET_BY_ID, id); |
| 219 CHECK(server_update.good()); | 138 CHECK(server_update.good()); |
| 220 CHECK(server_update.Get(syncable::META_HANDLE) != | 139 CHECK(server_update.Get(syncable::META_HANDLE) != |
| 221 entry.Get(syncable::META_HANDLE)) | 140 entry.Get(syncable::META_HANDLE)) |
| 222 << server_update << entry; | 141 << server_update << entry; |
| 223 | 142 |
| 224 return SYNC_PROGRESS; | 143 return SYNC_PROGRESS; |
| 225 } | 144 } |
| 226 } | 145 } |
| 227 | 146 |
| 228 namespace { | |
| 229 | |
| 230 bool NamesCollideWithChildrenOfFolder(BaseTransaction* trans, | |
| 231 const Directory::ChildHandles& children, | |
| 232 Id folder_id) { | |
| 233 Directory::ChildHandles::const_iterator i = children.begin(); | |
| 234 while (i != children.end()) { | |
| 235 Entry child(trans, syncable::GET_BY_HANDLE, *i); | |
| 236 CHECK(child.good()); | |
| 237 if (Entry(trans, | |
| 238 syncable::GET_BY_PARENTID_AND_DBNAME, | |
| 239 folder_id, | |
| 240 child.GetName().db_value()).good()) | |
| 241 return true; | |
| 242 ++i; | |
| 243 } | |
| 244 return false; | |
| 245 } | |
| 246 | |
| 247 void GiveEntryNewName(WriteTransaction* trans, MutableEntry* entry) { | |
| 248 using namespace syncable; | |
| 249 Name new_name = | |
| 250 FindNewName(trans, entry->Get(syncable::PARENT_ID), entry->GetName()); | |
| 251 LOG(INFO) << "Resolving name clash, renaming " << *entry << " to " | |
| 252 << new_name.db_value(); | |
| 253 entry->PutName(new_name); | |
| 254 CHECK(entry->Get(syncable::IS_UNSYNCED)); | |
| 255 } | |
| 256 | |
| 257 } // namespace | |
| 258 | |
| 259 bool ConflictResolver::AttemptItemMerge(WriteTransaction* trans, | |
| 260 MutableEntry* locally_named, | |
| 261 MutableEntry* server_named) { | |
| 262 // To avoid complications we only merge new entries with server entries. | |
| 263 if (locally_named->Get(syncable::IS_DIR) != | |
| 264 server_named->Get(syncable::SERVER_IS_DIR) || | |
| 265 locally_named->Get(syncable::ID).ServerKnows() || | |
| 266 locally_named->Get(syncable::IS_UNAPPLIED_UPDATE) || | |
| 267 server_named->Get(syncable::IS_UNSYNCED)) | |
| 268 return false; | |
| 269 Id local_id = locally_named->Get(syncable::ID); | |
| 270 Id desired_id = server_named->Get(syncable::ID); | |
| 271 if (locally_named->Get(syncable::IS_DIR)) { | |
| 272 // Extra work for directory name clash. We have to make sure we don't have | |
| 273 // clashing child items, and update the parent id the children of the new | |
| 274 // entry. | |
| 275 Directory::ChildHandles children; | |
| 276 trans->directory()->GetChildHandles(trans, local_id, &children); | |
| 277 if (NamesCollideWithChildrenOfFolder(trans, children, desired_id)) | |
| 278 return false; | |
| 279 | |
| 280 LOG(INFO) << "Merging local changes to: " << desired_id << ". " | |
| 281 << *locally_named; | |
| 282 | |
| 283 server_named->Put(syncable::ID, trans->directory()->NextId()); | |
| 284 Directory::ChildHandles::iterator i; | |
| 285 for (i = children.begin() ; i != children.end() ; ++i) { | |
| 286 MutableEntry child_entry(trans, syncable::GET_BY_HANDLE, *i); | |
| 287 CHECK(child_entry.good()); | |
| 288 CHECK(child_entry.Put(syncable::PARENT_ID, desired_id)); | |
| 289 CHECK(child_entry.Put(syncable::IS_UNSYNCED, true)); | |
| 290 Id id = child_entry.Get(syncable::ID); | |
| 291 // We only note new entries for quicker merging next round. | |
| 292 if (!id.ServerKnows()) | |
| 293 children_of_merged_dirs_.insert(id); | |
| 294 } | |
| 295 } else { | |
| 296 if (!server_named->Get(syncable::IS_DEL)) | |
| 297 return false; | |
| 298 } | |
| 299 | |
| 300 LOG(INFO) << "Identical client and server items merging server changes. " << | |
| 301 *locally_named << " server: " << *server_named; | |
| 302 | |
| 303 // Clear server_named's server data and mark it deleted so it goes away | |
| 304 // quietly because it's now identical to a deleted local entry. | |
| 305 // locally_named takes on the ID of the server entry. | |
| 306 server_named->Put(syncable::ID, trans->directory()->NextId()); | |
| 307 locally_named->Put(syncable::ID, desired_id); | |
| 308 locally_named->Put(syncable::IS_UNSYNCED, false); | |
| 309 CopyServerFields(server_named, locally_named); | |
| 310 ClearServerData(server_named); | |
| 311 server_named->Put(syncable::IS_DEL, true); | |
| 312 server_named->Put(syncable::BASE_VERSION, 0); | |
| 313 CHECK(SUCCESS == | |
| 314 SyncerUtil::AttemptToUpdateEntryWithoutMerge( | |
| 315 trans, locally_named, NULL)); | |
| 316 return true; | |
| 317 } | |
| 318 | |
| 319 ConflictResolver::ServerClientNameClashReturn | |
| 320 ConflictResolver::ProcessServerClientNameClash(WriteTransaction* trans, | |
| 321 MutableEntry* locally_named, | |
| 322 MutableEntry* server_named, | |
| 323 SyncerSession* session) { | |
| 324 if (!locally_named->ExistsOnClientBecauseDatabaseNameIsNonEmpty()) | |
| 325 return NO_CLASH; // Locally_named is a server update. | |
| 326 if (locally_named->Get(syncable::IS_DEL) || | |
| 327 server_named->Get(syncable::SERVER_IS_DEL)) { | |
| 328 return NO_CLASH; | |
| 329 } | |
| 330 if (locally_named->Get(syncable::PARENT_ID) != | |
| 331 server_named->Get(syncable::SERVER_PARENT_ID)) { | |
| 332 return NO_CLASH; // Different parents. | |
| 333 } | |
| 334 | |
| 335 PathString name = locally_named->GetSyncNameValue(); | |
| 336 if (0 != syncable::ComparePathNames(name, | |
| 337 server_named->Get(syncable::SERVER_NAME))) { | |
| 338 return NO_CLASH; // Different names. | |
| 339 } | |
| 340 | |
| 341 // First try to merge. | |
| 342 if (AttemptItemMerge(trans, locally_named, server_named)) { | |
| 343 // METRIC conflict resolved by merge | |
| 344 return SOLVED; | |
| 345 } | |
| 346 // We need to rename. | |
| 347 if (!locally_named->Get(syncable::IS_UNSYNCED)) { | |
| 348 LOG(ERROR) << "Locally named part of a name conflict not unsynced?"; | |
| 349 locally_named->Put(syncable::IS_UNSYNCED, true); | |
| 350 } | |
| 351 if (!server_named->Get(syncable::IS_UNAPPLIED_UPDATE)) { | |
| 352 LOG(ERROR) << "Server named part of a name conflict not an update?"; | |
| 353 } | |
| 354 GiveEntryNewName(trans, locally_named); | |
| 355 | |
| 356 // METRIC conflict resolved by rename | |
| 357 return SOLVED; | |
| 358 } | |
| 359 | |
| 360 ConflictResolver::ServerClientNameClashReturn | |
| 361 ConflictResolver::ProcessNameClashesInSet(WriteTransaction* trans, | |
| 362 ConflictSet* conflict_set, | |
| 363 SyncerSession* session) { | |
| 364 ConflictSet::const_iterator i,j; | |
| 365 for (i = conflict_set->begin() ; i != conflict_set->end() ; ++i) { | |
| 366 MutableEntry entryi(trans, syncable::GET_BY_ID, *i); | |
| 367 if (!entryi.Get(syncable::IS_UNSYNCED) && | |
| 368 !entryi.Get(syncable::IS_UNAPPLIED_UPDATE)) | |
| 369 // This set is broken / doesn't make sense, this may be transient. | |
| 370 return BOGUS_SET; | |
| 371 for (j = conflict_set->begin() ; *i != *j ; ++j) { | |
| 372 MutableEntry entryj(trans, syncable::GET_BY_ID, *j); | |
| 373 ServerClientNameClashReturn rv = | |
| 374 ProcessServerClientNameClash(trans, &entryi, &entryj, session); | |
| 375 if (NO_CLASH == rv) | |
| 376 rv = ProcessServerClientNameClash(trans, &entryj, &entryi, session); | |
| 377 if (NO_CLASH != rv) | |
| 378 return rv; | |
| 379 } | |
| 380 } | |
| 381 return NO_CLASH; | |
| 382 } | |
| 383 | |
| 384 ConflictResolver::ConflictSetCountMapKey ConflictResolver::GetSetKey( | 147 ConflictResolver::ConflictSetCountMapKey ConflictResolver::GetSetKey( |
| 385 ConflictSet* set) { | 148 ConflictSet* set) { |
| 386 // TODO(sync): Come up with a better scheme for set hashing. This scheme | 149 // TODO(sync): Come up with a better scheme for set hashing. This scheme |
| 387 // will make debugging easy. | 150 // will make debugging easy. |
| 388 // If this call to sort is removed, we need to add one before we use | 151 // If this call to sort is removed, we need to add one before we use |
| 389 // binary_search in ProcessConflictSet. | 152 // binary_search in ProcessConflictSet. |
| 390 sort(set->begin(), set->end()); | 153 sort(set->begin(), set->end()); |
| 391 std::stringstream rv; | 154 std::stringstream rv; |
| 392 for (ConflictSet::iterator i = set->begin() ; i != set->end() ; ++i ) | 155 for (ConflictSet::iterator i = set->begin() ; i != set->end() ; ++i ) |
| 393 rv << *i << "."; | 156 rv << *i << "."; |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 474 SyncerUtil::ChangeEntryIDAndUpdateChildren(trans, &parent, | 237 SyncerUtil::ChangeEntryIDAndUpdateChildren(trans, &parent, |
| 475 trans->directory()->NextId()); | 238 trans->directory()->NextId()); |
| 476 parent.Put(syncable::BASE_VERSION, 0); | 239 parent.Put(syncable::BASE_VERSION, 0); |
| 477 parent.Put(syncable::IS_UNSYNCED, true); | 240 parent.Put(syncable::IS_UNSYNCED, true); |
| 478 id = parent.Get(syncable::PARENT_ID); | 241 id = parent.Get(syncable::PARENT_ID); |
| 479 // METRIC conflict resolved by recreating dir tree. | 242 // METRIC conflict resolved by recreating dir tree. |
| 480 } | 243 } |
| 481 return true; | 244 return true; |
| 482 } | 245 } |
| 483 | 246 |
| 247 |
| 248 // TODO(chron): needs unit test badly |
| 484 bool AttemptToFixUpdateEntryInDeletedLocalTree(WriteTransaction* trans, | 249 bool AttemptToFixUpdateEntryInDeletedLocalTree(WriteTransaction* trans, |
| 485 ConflictSet* conflict_set, | 250 ConflictSet* conflict_set, |
| 486 const Entry& entry) { | 251 const Entry& entry) { |
| 487 if (!entry.Get(syncable::IS_UNAPPLIED_UPDATE) || | 252 if (!entry.Get(syncable::IS_UNAPPLIED_UPDATE) || |
| 488 entry.Get(syncable::SERVER_IS_DEL)) | 253 entry.Get(syncable::SERVER_IS_DEL)) |
| 489 return false; | 254 return false; |
| 490 Id parent_id = entry.Get(syncable::SERVER_PARENT_ID); | 255 Id parent_id = entry.Get(syncable::SERVER_PARENT_ID); |
| 491 MutableEntry parent(trans, syncable::GET_BY_ID, parent_id); | 256 MutableEntry parent(trans, syncable::GET_BY_ID, parent_id); |
| 492 if (!parent.good() || !parent.Get(syncable::IS_DEL) || | 257 if (!parent.good() || !parent.Get(syncable::IS_DEL) || |
| 493 !binary_search(conflict_set->begin(), conflict_set->end(), parent_id)) { | 258 !binary_search(conflict_set->begin(), conflict_set->end(), parent_id)) { |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 533 // If we find we've been looping we re-root the hierarchy. | 298 // If we find we've been looping we re-root the hierarchy. |
| 534 if (loop_detection < 0) { | 299 if (loop_detection < 0) { |
| 535 if (id == entry.Get(syncable::ID)) | 300 if (id == entry.Get(syncable::ID)) |
| 536 reroot_id = entry.Get(syncable::PARENT_ID); | 301 reroot_id = entry.Get(syncable::PARENT_ID); |
| 537 else | 302 else |
| 538 reroot_id = id; | 303 reroot_id = id; |
| 539 } | 304 } |
| 540 // Now we fix things up by undeleting all the folders in the item's path. | 305 // Now we fix things up by undeleting all the folders in the item's path. |
| 541 id = parent_id; | 306 id = parent_id; |
| 542 while (!id.IsRoot() && id != reroot_id) { | 307 while (!id.IsRoot() && id != reroot_id) { |
| 543 if (!binary_search(conflict_set->begin(), conflict_set->end(), id)) | 308 if (!binary_search(conflict_set->begin(), conflict_set->end(), id)) { |
| 544 break; | 309 break; |
| 310 } |
| 545 MutableEntry entry(trans, syncable::GET_BY_ID, id); | 311 MutableEntry entry(trans, syncable::GET_BY_ID, id); |
| 312 |
| 313 LOG(INFO) << "Undoing our deletion of " << entry |
| 314 << ", will have name " << entry.Get(syncable::NON_UNIQUE_NAME); |
| 315 |
| 546 Id parent_id = entry.Get(syncable::PARENT_ID); | 316 Id parent_id = entry.Get(syncable::PARENT_ID); |
| 547 if (parent_id == reroot_id) | 317 if (parent_id == reroot_id) { |
| 548 parent_id = trans->root_id(); | 318 parent_id = trans->root_id(); |
| 549 Name old_name = entry.GetName(); | 319 } |
| 550 Name new_name = FindNewName(trans, parent_id, old_name); | 320 entry.Put(syncable::PARENT_ID, parent_id); |
| 551 LOG(INFO) << "Undoing our deletion of " << entry << | |
| 552 ", will have name " << new_name.db_value(); | |
| 553 if (new_name != old_name || parent_id != entry.Get(syncable::PARENT_ID)) | |
| 554 CHECK(entry.PutParentIdAndName(parent_id, new_name)); | |
| 555 entry.Put(syncable::IS_DEL, false); | 321 entry.Put(syncable::IS_DEL, false); |
| 556 id = entry.Get(syncable::PARENT_ID); | 322 id = entry.Get(syncable::PARENT_ID); |
| 557 // METRIC conflict resolved by recreating dir tree. | 323 // METRIC conflict resolved by recreating dir tree. |
| 558 } | 324 } |
| 559 return true; | 325 return true; |
| 560 } | 326 } |
| 561 | 327 |
| 562 bool AttemptToFixRemovedDirectoriesWithContent(WriteTransaction* trans, | 328 bool AttemptToFixRemovedDirectoriesWithContent(WriteTransaction* trans, |
| 563 ConflictSet* conflict_set) { | 329 ConflictSet* conflict_set) { |
| 564 ConflictSet::const_iterator i,j; | 330 ConflictSet::const_iterator i,j; |
| 565 for (i = conflict_set->begin() ; i != conflict_set->end() ; ++i) { | 331 for (i = conflict_set->begin() ; i != conflict_set->end() ; ++i) { |
| 566 Entry entry(trans, syncable::GET_BY_ID, *i); | 332 Entry entry(trans, syncable::GET_BY_ID, *i); |
| 567 if (AttemptToFixUnsyncedEntryInDeletedServerTree(trans, | 333 if (AttemptToFixUnsyncedEntryInDeletedServerTree(trans, |
| 568 conflict_set, entry)) { | 334 conflict_set, entry)) { |
| 569 return true; | 335 return true; |
| 570 } | 336 } |
| 571 if (AttemptToFixUpdateEntryInDeletedLocalTree(trans, conflict_set, entry)) | 337 if (AttemptToFixUpdateEntryInDeletedLocalTree(trans, conflict_set, entry)) |
| 572 return true; | 338 return true; |
| 573 } | 339 } |
| 574 return false; | 340 return false; |
| 575 } | 341 } |
| 576 | 342 |
| 577 } // namespace | 343 } // namespace |
| 578 | 344 |
| 345 // TODO(sync): Eliminate conflict sets. They're not necessary. |
| 579 bool ConflictResolver::ProcessConflictSet(WriteTransaction* trans, | 346 bool ConflictResolver::ProcessConflictSet(WriteTransaction* trans, |
| 580 ConflictSet* conflict_set, | 347 ConflictSet* conflict_set, |
| 581 int conflict_count, | 348 int conflict_count, |
| 582 SyncerSession* session) { | 349 SyncerSession* session) { |
| 583 int set_size = conflict_set->size(); | 350 int set_size = conflict_set->size(); |
| 584 if (set_size < 2) { | 351 if (set_size < 2) { |
| 585 LOG(WARNING) << "Skipping conflict set because it has size " << set_size; | 352 LOG(WARNING) << "Skipping conflict set because it has size " << set_size; |
| 586 // We can end up with sets of size one if we have a new item in a set that | 353 // We can end up with sets of size one if we have a new item in a set that |
| 587 // we tried to commit transactionally. This should not be a persistent | 354 // we tried to commit transactionally. This should not be a persistent |
| 588 // situation. | 355 // situation. |
| 589 return false; | 356 return false; |
| 590 } | 357 } |
| 591 if (conflict_count < 3) { | 358 if (conflict_count < 3) { |
| 592 // Avoid resolving sets that could be the result of transient conflicts. | 359 // Avoid resolving sets that could be the result of transient conflicts. |
| 593 // Transient conflicts can occur because the client or server can be | 360 // Transient conflicts can occur because the client or server can be |
| 594 // slightly out of date. | 361 // slightly out of date. |
| 595 return false; | 362 return false; |
| 596 } | 363 } |
| 597 | 364 |
| 598 LOG(INFO) << "Fixing a set containing " << set_size << " items"; | 365 LOG(INFO) << "Fixing a set containing " << set_size << " items"; |
| 599 | 366 |
| 600 ServerClientNameClashReturn rv = ProcessNameClashesInSet(trans, conflict_set, | |
| 601 session); | |
| 602 if (SOLVED == rv) | |
| 603 return true; | |
| 604 if (NO_CLASH != rv) | |
| 605 return false; | |
| 606 | |
| 607 // Fix circular conflicts. | 367 // Fix circular conflicts. |
| 608 if (AttemptToFixCircularConflict(trans, conflict_set)) | 368 if (AttemptToFixCircularConflict(trans, conflict_set)) |
| 609 return true; | 369 return true; |
| 610 // Check for problems involving contents of removed folders. | 370 // Check for problems involving contents of removed folders. |
| 611 if (AttemptToFixRemovedDirectoriesWithContent(trans, conflict_set)) | 371 if (AttemptToFixRemovedDirectoriesWithContent(trans, conflict_set)) |
| 612 return true; | 372 return true; |
| 613 return false; | 373 return false; |
| 614 } | 374 } |
| 615 | 375 |
| 616 template <typename InputIt> | 376 template <typename InputIt> |
| (...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 740 conflict_set_count_map_.erase(i++); | 500 conflict_set_count_map_.erase(i++); |
| 741 // METRIC self resolved conflict sets ++. | 501 // METRIC self resolved conflict sets ++. |
| 742 } else { | 502 } else { |
| 743 ++i; | 503 ++i; |
| 744 } | 504 } |
| 745 } | 505 } |
| 746 return rv; | 506 return rv; |
| 747 } | 507 } |
| 748 | 508 |
| 749 } // namespace browser_sync | 509 } // namespace browser_sync |
| OLD | NEW |