OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2006-2008 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/engine/syncer_util.h" |
| 6 |
| 7 #include <set> |
| 8 #include <string> |
| 9 #include <vector> |
| 10 |
| 11 #include "chrome/browser/sync/engine/conflict_resolver.h" |
| 12 #include "chrome/browser/sync/engine/syncer_proto_util.h" |
| 13 #include "chrome/browser/sync/engine/syncer_session.h" |
| 14 #include "chrome/browser/sync/engine/syncer_types.h" |
| 15 #include "chrome/browser/sync/engine/syncproto.h" |
| 16 #include "chrome/browser/sync/syncable/directory_manager.h" |
| 17 #include "chrome/browser/sync/syncable/syncable.h" |
| 18 #include "chrome/browser/sync/syncable/syncable_changes_version.h" |
| 19 #include "chrome/browser/sync/util/character_set_converters.h" |
| 20 #include "chrome/browser/sync/util/path_helpers.h" |
| 21 #include "chrome/browser/sync/util/sync_types.h" |
| 22 |
| 23 using syncable::BASE_VERSION; |
| 24 using syncable::BOOKMARK_FAVICON; |
| 25 using syncable::BOOKMARK_URL; |
| 26 using syncable::Blob; |
| 27 using syncable::CHANGES_VERSION; |
| 28 using syncable::CREATE; |
| 29 using syncable::CREATE_NEW_UPDATE_ITEM; |
| 30 using syncable::CTIME; |
| 31 using syncable::ComparePathNames; |
| 32 using syncable::Directory; |
| 33 using syncable::Entry; |
| 34 using syncable::ExtendedAttributeKey; |
| 35 using syncable::GET_BY_HANDLE; |
| 36 using syncable::GET_BY_ID; |
| 37 using syncable::GET_BY_PARENTID_AND_DBNAME; |
| 38 using syncable::ID; |
| 39 using syncable::IS_BOOKMARK_OBJECT; |
| 40 using syncable::IS_DEL; |
| 41 using syncable::IS_DIR; |
| 42 using syncable::IS_UNAPPLIED_UPDATE; |
| 43 using syncable::IS_UNSYNCED; |
| 44 using syncable::Id; |
| 45 using syncable::META_HANDLE; |
| 46 using syncable::MTIME; |
| 47 using syncable::MutableEntry; |
| 48 using syncable::MutableExtendedAttribute; |
| 49 using syncable::NEXT_ID; |
| 50 using syncable::Name; |
| 51 using syncable::PARENT_ID; |
| 52 using syncable::PREV_ID; |
| 53 using syncable::ReadTransaction; |
| 54 using syncable::SERVER_BOOKMARK_FAVICON; |
| 55 using syncable::SERVER_BOOKMARK_URL; |
| 56 using syncable::SERVER_CTIME; |
| 57 using syncable::SERVER_IS_BOOKMARK_OBJECT; |
| 58 using syncable::SERVER_IS_DEL; |
| 59 using syncable::SERVER_IS_DIR; |
| 60 using syncable::SERVER_MTIME; |
| 61 using syncable::SERVER_NAME; |
| 62 using syncable::SERVER_PARENT_ID; |
| 63 using syncable::SERVER_POSITION_IN_PARENT; |
| 64 using syncable::SERVER_VERSION; |
| 65 using syncable::SINGLETON_TAG; |
| 66 using syncable::SYNCER; |
| 67 using syncable::SyncName; |
| 68 using syncable::UNSANITIZED_NAME; |
| 69 using syncable::WriteTransaction; |
| 70 |
| 71 namespace browser_sync { |
| 72 |
| 73 using std::string; |
| 74 using std::vector; |
| 75 |
| 76 // TODO(ncarter): Remove unique-in-parent title support and name conflicts. |
| 77 // static |
| 78 syncable::Id SyncerUtil::GetNameConflictingItemId( |
| 79 syncable::BaseTransaction* trans, |
| 80 const syncable::Id& parent_id, |
| 81 const PathString& server_name) { |
| 82 |
| 83 Entry same_path(trans, GET_BY_PARENTID_AND_DBNAME, parent_id, server_name); |
| 84 if (same_path.good() && !same_path.GetName().HasBeenSanitized()) |
| 85 return same_path.Get(ID); |
| 86 Name doctored_name(server_name); |
| 87 doctored_name.db_value().MakeOSLegal(); |
| 88 if (!doctored_name.HasBeenSanitized()) |
| 89 return syncable::kNullId; |
| 90 Directory::ChildHandles children; |
| 91 trans->directory()->GetChildHandles(trans, parent_id, &children); |
| 92 Directory::ChildHandles::iterator i = children.begin(); |
| 93 while (i != children.end()) { |
| 94 Entry child_entry(trans, GET_BY_HANDLE, *i++); |
| 95 CHECK(child_entry.good()); |
| 96 if (0 == ComparePathNames(child_entry.Get(UNSANITIZED_NAME), server_name)) |
| 97 return child_entry.Get(ID); |
| 98 } |
| 99 return syncable::kNullId; |
| 100 } |
| 101 |
| 102 // Returns the number of unsynced entries. |
| 103 // static |
| 104 int SyncerUtil::GetUnsyncedEntries(syncable::BaseTransaction* trans, |
| 105 vector<int64> *handles) { |
| 106 trans->directory()->GetUnsyncedMetaHandles(trans, handles); |
| 107 LOG_IF(INFO, handles->size() > 0) |
| 108 << "Have " << handles->size() << " unsynced items."; |
| 109 return handles->size(); |
| 110 } |
| 111 |
| 112 // static |
| 113 void SyncerUtil::ChangeEntryIDAndUpdateChildren( |
| 114 syncable::WriteTransaction* trans, |
| 115 syncable::MutableEntry* entry, |
| 116 const syncable::Id& new_id, |
| 117 syncable::Directory::ChildHandles* children) { |
| 118 syncable::Id old_id = entry->Get(ID); |
| 119 if (!entry->Put(ID, new_id)) { |
| 120 Entry old_entry(trans, GET_BY_ID, new_id); |
| 121 CHECK(old_entry.good()); |
| 122 LOG(FATAL) << "Attempt to change ID to " << new_id |
| 123 << " conflicts with existing entry.\n\n" |
| 124 << *entry << "\n\n" << old_entry; |
| 125 } |
| 126 if (entry->Get(IS_DIR)) { |
| 127 // Get all child entries of the old id |
| 128 trans->directory()->GetChildHandles(trans, old_id, children); |
| 129 Directory::ChildHandles::iterator i = children->begin(); |
| 130 while (i != children->end()) { |
| 131 MutableEntry child_entry(trans, GET_BY_HANDLE, *i++); |
| 132 CHECK(child_entry.good()); |
| 133 CHECK(child_entry.Put(PARENT_ID, new_id)); |
| 134 } |
| 135 } |
| 136 // Update Id references on the previous and next nodes in the sibling |
| 137 // order. Do this by reinserting into the linked list; the first |
| 138 // step in PutPredecessor is to Unlink from the existing order, which |
| 139 // will overwrite the stale Id value from the adjacent nodes. |
| 140 if (entry->Get(PREV_ID) == entry->Get(NEXT_ID) && |
| 141 entry->Get(PREV_ID) == old_id) { |
| 142 // We just need a shallow update to |entry|'s fields since it is already |
| 143 // self looped. |
| 144 entry->Put(NEXT_ID, new_id); |
| 145 entry->Put(PREV_ID, new_id); |
| 146 } else { |
| 147 entry->PutPredecessor(entry->Get(PREV_ID)); |
| 148 } |
| 149 } |
| 150 |
| 151 // static |
| 152 void SyncerUtil::ChangeEntryIDAndUpdateChildren( |
| 153 syncable::WriteTransaction* trans, |
| 154 syncable::MutableEntry* entry, |
| 155 const syncable::Id& new_id) { |
| 156 syncable::Directory::ChildHandles children; |
| 157 ChangeEntryIDAndUpdateChildren(trans, entry, new_id, &children); |
| 158 } |
| 159 |
| 160 // static |
| 161 void SyncerUtil::AttemptReuniteLostCommitResponses( |
| 162 syncable::WriteTransaction* trans, |
| 163 const SyncEntity& server_entry, |
| 164 const string& client_id) { |
| 165 // If a commit succeeds, but the response does not come back fast enough |
| 166 // then the syncer might assume that it was never committed. |
| 167 // The server will track the client that sent up the original commit and |
| 168 // return this in a get updates response. When this matches a local |
| 169 // uncommitted item, we must mutate our local item and version to pick up |
| 170 // the committed version of the same item whose commit response was lost. |
| 171 // There is however still a race condition if the server has not |
| 172 // completed the commit by the time the syncer tries to get updates |
| 173 // again. To mitigate this, we need to have the server time out in |
| 174 // a reasonable span, our commit batches have to be small enough |
| 175 // to process within our HTTP response "assumed alive" time. |
| 176 |
| 177 // We need to check if we have a that didn't get its server |
| 178 // id updated correctly. The server sends down a client ID |
| 179 // and a local (negative) id. If we have a entry by that |
| 180 // description, we should update the ID and version to the |
| 181 // server side ones to avoid multiple commits to the same name. |
| 182 if (server_entry.has_originator_cache_guid() && |
| 183 server_entry.originator_cache_guid() == client_id) { |
| 184 syncable::Id server_id = syncable::Id::CreateFromClientString( |
| 185 server_entry.originator_client_item_id()); |
| 186 CHECK(!server_id.ServerKnows()); |
| 187 syncable::MutableEntry local_entry(trans, GET_BY_ID, server_id); |
| 188 |
| 189 // If it exists, then our local client lost a commit response. |
| 190 if (local_entry.good() && !local_entry.Get(IS_DEL)) { |
| 191 int64 old_version = local_entry.Get(BASE_VERSION); |
| 192 int64 new_version = server_entry.version(); |
| 193 CHECK(old_version <= 0); |
| 194 CHECK(new_version > 0); |
| 195 // Otherwise setting the base version could cause a consistency failure. |
| 196 // An entry should never be version 0 and SYNCED. |
| 197 CHECK(local_entry.Get(IS_UNSYNCED)); |
| 198 |
| 199 // just a quick sanity check |
| 200 CHECK(!local_entry.Get(ID).ServerKnows()); |
| 201 |
| 202 LOG(INFO) << "Reuniting lost commit response IDs" << |
| 203 " server id: " << server_entry.id() << " local id: " << |
| 204 local_entry.Get(ID) << " new version: " << new_version; |
| 205 |
| 206 local_entry.Put(BASE_VERSION, new_version); |
| 207 |
| 208 ChangeEntryIDAndUpdateChildren(trans, &local_entry, server_entry.id()); |
| 209 |
| 210 // We need to continue normal processing on this update after we |
| 211 // reunited its ID. |
| 212 } |
| 213 // !local_entry.Good() means we don't have a left behind entry for this |
| 214 // ID. We successfully committed before. In the future we should get rid |
| 215 // of this system and just have client side generated IDs as a whole. |
| 216 } |
| 217 } |
| 218 |
| 219 // static |
| 220 UpdateAttemptResponse SyncerUtil::AttemptToUpdateEntry( |
| 221 syncable::WriteTransaction* const trans, |
| 222 syncable::MutableEntry* const entry, |
| 223 SyncerSession* const session) { |
| 224 |
| 225 syncable::Id conflicting_id; |
| 226 UpdateAttemptResponse result = |
| 227 AttemptToUpdateEntryWithoutMerge(trans, entry, session, |
| 228 &conflicting_id); |
| 229 if (result != NAME_CONFLICT) { |
| 230 return result; |
| 231 } |
| 232 syncable::MutableEntry same_path(trans, syncable::GET_BY_ID, conflicting_id); |
| 233 CHECK(same_path.good()); |
| 234 |
| 235 ConflictResolver* resolver = session->resolver(); |
| 236 |
| 237 if (resolver && |
| 238 resolver->AttemptItemMerge(trans, &same_path, entry)) { |
| 239 return SUCCESS; |
| 240 } |
| 241 LOG(INFO) << "Not updating item, path collision. Update:\n" << *entry |
| 242 << "\nSame Path:\n" << same_path; |
| 243 return CONFLICT; |
| 244 } |
| 245 |
| 246 // static |
| 247 UpdateAttemptResponse SyncerUtil::AttemptToUpdateEntryWithoutMerge( |
| 248 syncable::WriteTransaction* const trans, |
| 249 syncable::MutableEntry* const entry, |
| 250 SyncerSession* const session, syncable::Id* const conflicting_id) { |
| 251 |
| 252 CHECK(entry->good()); |
| 253 if (!entry->Get(IS_UNAPPLIED_UPDATE)) |
| 254 return SUCCESS; // No work to do |
| 255 syncable::Id id = entry->Get(ID); |
| 256 |
| 257 if (entry->Get(IS_UNSYNCED)) { |
| 258 LOG(INFO) << "Skipping update, returning conflict for: " << id |
| 259 << " ; it's unsynced."; |
| 260 return CONFLICT; |
| 261 } |
| 262 if (!entry->Get(SERVER_IS_DEL)) { |
| 263 syncable::Id new_parent = entry->Get(SERVER_PARENT_ID); |
| 264 Entry parent(trans, GET_BY_ID, new_parent); |
| 265 // A note on non-directory parents: |
| 266 // We catch most unfixable tree invariant errors at update receipt time, |
| 267 // however we deal with this case here because we may receive the child |
| 268 // first then the illegal parent. Instead of dealing with it twice in |
| 269 // different ways we deal with it once here to reduce the amount of code and |
| 270 // potential errors. |
| 271 if (!parent.good() || parent.Get(IS_DEL) || !parent.Get(IS_DIR)) { |
| 272 return CONFLICT; |
| 273 } |
| 274 if (entry->Get(PARENT_ID) != new_parent) { |
| 275 if (!entry->Get(IS_DEL) && !IsLegalNewParent(trans, id, new_parent)) { |
| 276 LOG(INFO) << "Not updating item " << id << ", illegal new parent " |
| 277 "(would cause loop)."; |
| 278 return CONFLICT; |
| 279 } |
| 280 } |
| 281 PathString server_name = entry->Get(SERVER_NAME); |
| 282 syncable::Id conflict_id = |
| 283 SyncerUtil::GetNameConflictingItemId(trans, |
| 284 entry->Get(SERVER_PARENT_ID), |
| 285 server_name); |
| 286 if (conflict_id != syncable::kNullId && conflict_id != id) { |
| 287 if (conflicting_id) |
| 288 *conflicting_id = conflict_id; |
| 289 return NAME_CONFLICT; |
| 290 } |
| 291 } else if (entry->Get(IS_DIR)) { |
| 292 Directory::ChildHandles handles; |
| 293 trans->directory()->GetChildHandles(trans, id, &handles); |
| 294 if (!handles.empty()) { |
| 295 // If we have still-existing children, then we need to deal with |
| 296 // them before we can process this change. |
| 297 LOG(INFO) << "Not deleting directory; it's not empty " << *entry; |
| 298 return CONFLICT; |
| 299 } |
| 300 } |
| 301 |
| 302 int64 old_version = entry->Get(BASE_VERSION); |
| 303 SyncerUtil::UpdateLocalDataFromServerData(trans, entry); |
| 304 |
| 305 return SUCCESS; |
| 306 } |
| 307 |
| 308 // Pass in name and checksum because of UTF8 conversion. |
| 309 // static |
| 310 void SyncerUtil::UpdateServerFieldsFromUpdate( |
| 311 MutableEntry* local_entry, |
| 312 const SyncEntity& server_entry, |
| 313 const SyncName& name) { |
| 314 if (server_entry.deleted()) { |
| 315 // The server returns very lightweight replies for deletions, so |
| 316 // we don't clobber a bunch of fields on delete. |
| 317 local_entry->Put(SERVER_IS_DEL, true); |
| 318 local_entry->Put(SERVER_VERSION, |
| 319 std::max(local_entry->Get(SERVER_VERSION), |
| 320 local_entry->Get(BASE_VERSION)) + 1L); |
| 321 local_entry->Put(IS_UNAPPLIED_UPDATE, true); |
| 322 return; |
| 323 } |
| 324 |
| 325 CHECK(local_entry->Get(ID) == server_entry.id()) |
| 326 << "ID Changing not supported here"; |
| 327 local_entry->Put(SERVER_PARENT_ID, server_entry.parent_id()); |
| 328 local_entry->PutServerName(name); |
| 329 local_entry->Put(SERVER_VERSION, server_entry.version()); |
| 330 local_entry->Put(SERVER_CTIME, |
| 331 ServerTimeToClientTime(server_entry.ctime())); |
| 332 local_entry->Put(SERVER_MTIME, |
| 333 ServerTimeToClientTime(server_entry.mtime())); |
| 334 local_entry->Put(SERVER_IS_BOOKMARK_OBJECT, server_entry.has_bookmarkdata()); |
| 335 local_entry->Put(SERVER_IS_DIR, server_entry.IsFolder()); |
| 336 if (server_entry.has_singleton_tag()) { |
| 337 PathString tag; |
| 338 AppendUTF8ToPathString(server_entry.singleton_tag(), &tag); |
| 339 local_entry->Put(SINGLETON_TAG, tag); |
| 340 } |
| 341 if (server_entry.has_bookmarkdata() && !server_entry.deleted()) { |
| 342 const SyncEntity::BookmarkData& bookmark = server_entry.bookmarkdata(); |
| 343 if (bookmark.has_bookmark_url()) { |
| 344 PathString url; |
| 345 AppendUTF8ToPathString(bookmark.bookmark_url(), &url); |
| 346 local_entry->Put(SERVER_BOOKMARK_URL, url); |
| 347 } |
| 348 if (bookmark.has_bookmark_favicon()) { |
| 349 Blob favicon_blob; |
| 350 SyncerProtoUtil::CopyProtoBytesIntoBlob(bookmark.bookmark_favicon(), |
| 351 &favicon_blob); |
| 352 local_entry->Put(SERVER_BOOKMARK_FAVICON, favicon_blob); |
| 353 } |
| 354 } |
| 355 if (server_entry.has_position_in_parent()) { |
| 356 local_entry->Put(SERVER_POSITION_IN_PARENT, |
| 357 server_entry.position_in_parent()); |
| 358 } |
| 359 |
| 360 local_entry->Put(SERVER_IS_DEL, server_entry.deleted()); |
| 361 // We only mark the entry as unapplied if its version is greater than |
| 362 // the local data. If we're processing the update that corresponds to one of |
| 363 // our commit we don't apply it as time differences may occur. |
| 364 if (server_entry.version() > local_entry->Get(BASE_VERSION)) { |
| 365 local_entry->Put(IS_UNAPPLIED_UPDATE, true); |
| 366 } |
| 367 ApplyExtendedAttributes(local_entry, server_entry); |
| 368 } |
| 369 |
| 370 // static |
| 371 void SyncerUtil::ApplyExtendedAttributes( |
| 372 syncable::MutableEntry* local_entry, |
| 373 const SyncEntity& server_entry) { |
| 374 local_entry->DeleteAllExtendedAttributes(local_entry->trans()); |
| 375 if (server_entry.has_extended_attributes()) { |
| 376 const sync_pb::ExtendedAttributes & extended_attributes = |
| 377 server_entry.extended_attributes(); |
| 378 for (int i = 0; i < extended_attributes.extendedattribute_size(); i++) { |
| 379 PathString pathstring_key; |
| 380 AppendUTF8ToPathString( |
| 381 extended_attributes.extendedattribute(i).key(), &pathstring_key); |
| 382 ExtendedAttributeKey key(local_entry->Get(META_HANDLE), pathstring_key); |
| 383 MutableExtendedAttribute local_attribute(local_entry->trans(), |
| 384 CREATE, key); |
| 385 SyncerProtoUtil::CopyProtoBytesIntoBlob( |
| 386 extended_attributes.extendedattribute(i).value(), |
| 387 local_attribute.mutable_value()); |
| 388 } |
| 389 } |
| 390 } |
| 391 |
| 392 // Creates a new Entry iff no Entry exists with the given id. |
| 393 // static |
| 394 void SyncerUtil::CreateNewEntry(syncable::WriteTransaction *trans, |
| 395 const syncable::Id& id) { |
| 396 syncable::MutableEntry entry(trans, syncable::GET_BY_ID, id); |
| 397 if (!entry.good()) { |
| 398 syncable::MutableEntry new_entry(trans, syncable::CREATE_NEW_UPDATE_ITEM, |
| 399 id); |
| 400 } |
| 401 } |
| 402 |
| 403 // static |
| 404 bool SyncerUtil::ServerAndLocalOrdersMatch(syncable::Entry* entry) { |
| 405 // Find the closest up-to-date local sibling by walking the linked list. |
| 406 syncable::Id local_up_to_date_predecessor = entry->Get(PREV_ID); |
| 407 while (!local_up_to_date_predecessor.IsRoot()) { |
| 408 Entry local_prev(entry->trans(), GET_BY_ID, local_up_to_date_predecessor); |
| 409 if (!local_prev.good() || local_prev.Get(IS_DEL)) |
| 410 return false; |
| 411 if (!local_prev.Get(IS_UNAPPLIED_UPDATE) && !local_prev.Get(IS_UNSYNCED)) |
| 412 break; |
| 413 local_up_to_date_predecessor = local_prev.Get(PREV_ID); |
| 414 } |
| 415 // Now find the closest up-to-date sibling in the server order. |
| 416 |
| 417 syncable::Id server_up_to_date_predecessor = |
| 418 ComputePrevIdFromServerPosition(entry->trans(), entry, |
| 419 entry->Get(SERVER_PARENT_ID)); |
| 420 return server_up_to_date_predecessor == local_up_to_date_predecessor; |
| 421 } |
| 422 |
| 423 // static |
| 424 bool SyncerUtil::ServerAndLocalEntriesMatch(syncable::Entry* entry) { |
| 425 if (!ClientAndServerTimeMatch( |
| 426 entry->Get(CTIME), ClientTimeToServerTime(entry->Get(SERVER_CTIME)))) { |
| 427 LOG(WARNING) << "Client and server time mismatch"; |
| 428 return false; |
| 429 } |
| 430 if (entry->Get(IS_DEL) && entry->Get(SERVER_IS_DEL)) |
| 431 return true; |
| 432 // Name should exactly match here. |
| 433 if (!entry->SyncNameMatchesServerName()) { |
| 434 LOG(WARNING) << "Unsanitized name mismatch"; |
| 435 return false; |
| 436 } |
| 437 |
| 438 if (entry->Get(PARENT_ID) != entry->Get(SERVER_PARENT_ID) || |
| 439 entry->Get(IS_DIR) != entry->Get(SERVER_IS_DIR) || |
| 440 entry->Get(IS_DEL) != entry->Get(SERVER_IS_DEL)) { |
| 441 LOG(WARNING) << "Metabit mismatch"; |
| 442 return false; |
| 443 } |
| 444 |
| 445 if (!ServerAndLocalOrdersMatch(entry)) { |
| 446 LOG(WARNING) << "Server/local ordering mismatch"; |
| 447 return false; |
| 448 } |
| 449 |
| 450 if (entry->Get(IS_BOOKMARK_OBJECT)) { |
| 451 if (!entry->Get(IS_DIR)) { |
| 452 if (entry->Get(BOOKMARK_URL) != entry->Get(SERVER_BOOKMARK_URL)) { |
| 453 LOG(WARNING) << "Bookmark URL mismatch"; |
| 454 return false; |
| 455 } |
| 456 } |
| 457 } |
| 458 if (entry->Get(IS_DIR)) |
| 459 return true; |
| 460 // For historical reasons, a folder's MTIME changes when its contents change. |
| 461 // TODO(ncarter): Remove the special casing of MTIME. |
| 462 bool time_match = ClientAndServerTimeMatch(entry->Get(MTIME), |
| 463 ClientTimeToServerTime(entry->Get(SERVER_MTIME))); |
| 464 if (!time_match) { |
| 465 LOG(WARNING) << "Time mismatch"; |
| 466 } |
| 467 return time_match; |
| 468 } |
| 469 |
| 470 // static |
| 471 void SyncerUtil::SplitServerInformationIntoNewEntry( |
| 472 syncable::WriteTransaction* trans, |
| 473 syncable::MutableEntry* entry) { |
| 474 syncable::Id id = entry->Get(ID); |
| 475 ChangeEntryIDAndUpdateChildren(trans, entry, trans->directory()->NextId()); |
| 476 entry->Put(BASE_VERSION, 0); |
| 477 |
| 478 MutableEntry new_entry(trans, CREATE_NEW_UPDATE_ITEM, id); |
| 479 CopyServerFields(entry, &new_entry); |
| 480 ClearServerData(entry); |
| 481 |
| 482 LOG(INFO) << "Splitting server information, local entry: " << *entry << |
| 483 " server entry: " << new_entry; |
| 484 } |
| 485 |
| 486 // This function is called on an entry when we can update the user-facing data |
| 487 // from the server data. |
| 488 // static |
| 489 void SyncerUtil::UpdateLocalDataFromServerData( |
| 490 syncable::WriteTransaction* trans, |
| 491 syncable::MutableEntry* entry) { |
| 492 CHECK(!entry->Get(IS_UNSYNCED)); |
| 493 CHECK(entry->Get(IS_UNAPPLIED_UPDATE)); |
| 494 LOG(INFO) << "Updating entry : " << *entry; |
| 495 entry->Put(IS_BOOKMARK_OBJECT, entry->Get(SERVER_IS_BOOKMARK_OBJECT)); |
| 496 // This strange dance around the IS_DEL flag |
| 497 // avoids problems when setting the name. |
| 498 if (entry->Get(SERVER_IS_DEL)) { |
| 499 entry->Put(IS_DEL, true); |
| 500 } else { |
| 501 Name name = Name::FromSyncName(entry->GetServerName()); |
| 502 name.db_value().MakeOSLegal(); |
| 503 bool was_doctored_before_made_noncolliding = name.HasBeenSanitized(); |
| 504 name.db_value().MakeNoncollidingForEntry(trans, |
| 505 entry->Get(SERVER_PARENT_ID), |
| 506 entry); |
| 507 bool was_doctored = name.HasBeenSanitized(); |
| 508 if (was_doctored) { |
| 509 // If we're changing the name of entry, either its name |
| 510 // should be illegal, or some other entry should have an unsanitized |
| 511 // name. There's should be a CHECK in every code path. |
| 512 Entry blocking_entry(trans, GET_BY_PARENTID_AND_DBNAME, |
| 513 entry->Get(SERVER_PARENT_ID), |
| 514 name.value()); |
| 515 if (blocking_entry.good()) |
| 516 CHECK(blocking_entry.GetName().HasBeenSanitized()); |
| 517 else |
| 518 CHECK(was_doctored_before_made_noncolliding); |
| 519 } |
| 520 CHECK(entry->PutParentIdAndName(entry->Get(SERVER_PARENT_ID), name)) |
| 521 << "Name Clash in UpdateLocalDataFromServerData: " |
| 522 << *entry; |
| 523 CHECK(entry->Put(IS_DEL, false)); |
| 524 Id new_predecessor = ComputePrevIdFromServerPosition(trans, entry, |
| 525 entry->Get(SERVER_PARENT_ID)); |
| 526 CHECK(entry->PutPredecessor(new_predecessor)) |
| 527 << " Illegal predecessor after converting from server position."; |
| 528 } |
| 529 |
| 530 entry->Put(CTIME, entry->Get(SERVER_CTIME)); |
| 531 entry->Put(MTIME, entry->Get(SERVER_MTIME)); |
| 532 entry->Put(BASE_VERSION, entry->Get(SERVER_VERSION)); |
| 533 entry->Put(IS_DIR, entry->Get(SERVER_IS_DIR)); |
| 534 entry->Put(IS_DEL, entry->Get(SERVER_IS_DEL)); |
| 535 entry->Put(BOOKMARK_URL, entry->Get(SERVER_BOOKMARK_URL)); |
| 536 entry->Put(BOOKMARK_FAVICON, entry->Get(SERVER_BOOKMARK_FAVICON)); |
| 537 entry->Put(IS_UNAPPLIED_UPDATE, false); |
| 538 } |
| 539 |
| 540 // static |
| 541 VerifyCommitResult SyncerUtil::ValidateCommitEntry( |
| 542 syncable::MutableEntry* entry) { |
| 543 syncable::Id id = entry->Get(ID); |
| 544 if (id == entry->Get(PARENT_ID)) { |
| 545 CHECK(id.IsRoot()) << "Non-root item is self parenting." << *entry; |
| 546 // If the root becomes unsynced it can cause us problems. |
| 547 LOG(ERROR) << "Root item became unsynced " << *entry; |
| 548 return VERIFY_UNSYNCABLE; |
| 549 } |
| 550 if (entry->IsRoot()) { |
| 551 LOG(ERROR) << "Permanent item became unsynced " << *entry; |
| 552 return VERIFY_UNSYNCABLE; |
| 553 } |
| 554 if (entry->Get(IS_DEL) && !entry->Get(ID).ServerKnows()) { |
| 555 // drop deleted uncommitted entries. |
| 556 return VERIFY_UNSYNCABLE; |
| 557 } |
| 558 return VERIFY_OK; |
| 559 } |
| 560 |
| 561 // static |
| 562 bool SyncerUtil::AddItemThenPredecessors( |
| 563 syncable::BaseTransaction* trans, |
| 564 syncable::Entry* item, |
| 565 syncable::IndexedBitField inclusion_filter, |
| 566 syncable::MetahandleSet* inserted_items, |
| 567 vector<syncable::Id>* commit_ids) { |
| 568 |
| 569 if (!inserted_items->insert(item->Get(META_HANDLE)).second) |
| 570 return false; |
| 571 commit_ids->push_back(item->Get(ID)); |
| 572 if (item->Get(IS_DEL)) |
| 573 return true; // Deleted items have no predecessors. |
| 574 |
| 575 Id prev_id = item->Get(PREV_ID); |
| 576 while (!prev_id.IsRoot()) { |
| 577 Entry prev(trans, GET_BY_ID, prev_id); |
| 578 CHECK(prev.good()) << "Bad id when walking predecessors."; |
| 579 if (!prev.Get(inclusion_filter)) |
| 580 break; |
| 581 if (!inserted_items->insert(prev.Get(META_HANDLE)).second) |
| 582 break; |
| 583 commit_ids->push_back(prev_id); |
| 584 prev_id = prev.Get(PREV_ID); |
| 585 } |
| 586 return true; |
| 587 } |
| 588 |
| 589 // static |
| 590 void SyncerUtil::AddPredecessorsThenItem( |
| 591 syncable::BaseTransaction* trans, |
| 592 syncable::Entry* item, |
| 593 syncable::IndexedBitField inclusion_filter, |
| 594 syncable::MetahandleSet* inserted_items, |
| 595 vector<syncable::Id>* commit_ids) { |
| 596 |
| 597 vector<syncable::Id>::size_type initial_size = commit_ids->size(); |
| 598 if (!AddItemThenPredecessors(trans, item, inclusion_filter, inserted_items, |
| 599 commit_ids)) |
| 600 return; |
| 601 // Reverse what we added to get the correct order. |
| 602 std::reverse(commit_ids->begin() + initial_size, commit_ids->end()); |
| 603 } |
| 604 |
| 605 // TODO(ncarter): This is redundant to some code in GetCommitIdsCommand. Unify |
| 606 // them. |
| 607 // static |
| 608 void SyncerUtil::AddUncommittedParentsAndTheirPredecessors( |
| 609 syncable::BaseTransaction* trans, |
| 610 syncable::MetahandleSet* inserted_items, |
| 611 vector<syncable::Id>* commit_ids, |
| 612 syncable::Id parent_id) { |
| 613 vector<syncable::Id>::size_type intial_commit_ids_size = commit_ids->size(); |
| 614 // Climb the tree adding entries leaf -> root. |
| 615 while (!parent_id.ServerKnows()) { |
| 616 Entry parent(trans, GET_BY_ID, parent_id); |
| 617 CHECK(parent.good()) << "Bad user-only parent in item path."; |
| 618 if (!AddItemThenPredecessors(trans, &parent, IS_UNSYNCED, inserted_items, |
| 619 commit_ids)) |
| 620 break; // Parent was already present in |inserted_items|. |
| 621 parent_id = parent.Get(PARENT_ID); |
| 622 } |
| 623 // Reverse what we added to get the correct order. |
| 624 std::reverse(commit_ids->begin() + intial_commit_ids_size, commit_ids->end()); |
| 625 } |
| 626 |
| 627 // static |
| 628 void SyncerUtil::MarkDeletedChildrenSynced( |
| 629 const syncable::ScopedDirLookup &dir, |
| 630 std::set<syncable::Id>* deleted_folders) { |
| 631 // There's two options here. |
| 632 // 1. Scan deleted unsynced entries looking up their pre-delete tree for any |
| 633 // of the deleted folders. |
| 634 // 2. Take each folder and do a tree walk of all entries underneath it. |
| 635 // #2 has a lower big O cost, but writing code to limit the time spent inside |
| 636 // the transaction during each step is simpler with 1. Changing this decision |
| 637 // may be sensible if this code shows up in profiling. |
| 638 if (deleted_folders->empty()) |
| 639 return; |
| 640 Directory::UnsyncedMetaHandles handles; |
| 641 { |
| 642 ReadTransaction trans(dir, __FILE__, __LINE__); |
| 643 dir->GetUnsyncedMetaHandles(&trans, &handles); |
| 644 } |
| 645 if (handles.empty()) |
| 646 return; |
| 647 Directory::UnsyncedMetaHandles::iterator it; |
| 648 for (it = handles.begin() ; it != handles.end() ; ++it) { |
| 649 // Single transaction / entry we deal with. |
| 650 WriteTransaction trans(dir, SYNCER, __FILE__, __LINE__); |
| 651 MutableEntry entry(&trans, GET_BY_HANDLE, *it); |
| 652 if (!entry.Get(IS_UNSYNCED) || !entry.Get(IS_DEL)) |
| 653 continue; |
| 654 syncable::Id id = entry.Get(PARENT_ID); |
| 655 while (id != trans.root_id()) { |
| 656 if (deleted_folders->find(id) != deleted_folders->end()) { |
| 657 // We've synced the deletion of this deleted entries parent |
| 658 entry.Put(IS_UNSYNCED, false); |
| 659 break; |
| 660 } |
| 661 Entry parent(&trans, GET_BY_ID, id); |
| 662 if (!parent.good() || !parent.Get(IS_DEL)) |
| 663 break; |
| 664 id = parent.Get(PARENT_ID); |
| 665 } |
| 666 } |
| 667 } |
| 668 |
| 669 // static |
| 670 VerifyResult SyncerUtil::VerifyNewEntry( |
| 671 const SyncEntity& entry, |
| 672 syncable::MutableEntry* same_id, |
| 673 const bool deleted) { |
| 674 if (same_id->good()) { |
| 675 // Not a new entry. |
| 676 return VERIFY_UNDECIDED; |
| 677 } |
| 678 if (deleted) { |
| 679 // Deletion of an item we've never seen can be ignored. |
| 680 return VERIFY_SKIP; |
| 681 } |
| 682 |
| 683 return VERIFY_SUCCESS; |
| 684 } |
| 685 |
| 686 // Assumes we have an existing entry; check here for updates that break |
| 687 // consistency rules. |
| 688 // static |
| 689 VerifyResult SyncerUtil::VerifyUpdateConsistency( |
| 690 syncable::WriteTransaction* trans, |
| 691 const SyncEntity& entry, |
| 692 syncable::MutableEntry* same_id, |
| 693 const bool deleted, |
| 694 const bool is_directory, |
| 695 const bool has_bookmark_data) { |
| 696 |
| 697 CHECK(same_id->good()); |
| 698 |
| 699 // If the entry is a delete, we don't really need to worry at this stage. |
| 700 if (deleted) |
| 701 return VERIFY_SUCCESS; |
| 702 |
| 703 if (same_id->Get(SERVER_VERSION) > 0) { |
| 704 // Then we've had an update for this entry before. |
| 705 if (is_directory != same_id->Get(SERVER_IS_DIR) || |
| 706 has_bookmark_data != same_id->Get(SERVER_IS_BOOKMARK_OBJECT)) { |
| 707 if (same_id->Get(IS_DEL)) { // if we've deleted the item, we don't care. |
| 708 return VERIFY_SKIP; |
| 709 } else { |
| 710 LOG(ERROR) << "Server update doesn't agree with previous updates. "; |
| 711 LOG(ERROR) << " Entry: " << *same_id; |
| 712 LOG(ERROR) << " Update: " << SyncEntityDebugString(entry); |
| 713 return VERIFY_FAIL; |
| 714 } |
| 715 } |
| 716 |
| 717 if (!deleted && |
| 718 (same_id->Get(SERVER_IS_DEL) || |
| 719 (!same_id->Get(IS_UNSYNCED) && same_id->Get(IS_DEL) && |
| 720 same_id->Get(BASE_VERSION) > 0))) { |
| 721 // An undelete. The latter case in the above condition is for |
| 722 // when the server does not give us an update following the |
| 723 // commit of a delete, before undeleting. Undeletion is possible |
| 724 // in the server's storage backend, so it's possible on the client, |
| 725 // though not expected to be something that is commonly possible. |
| 726 VerifyResult result = |
| 727 SyncerUtil::VerifyUndelete(trans, entry, same_id); |
| 728 if (VERIFY_UNDECIDED != result) |
| 729 return result; |
| 730 } |
| 731 } |
| 732 if (same_id->Get(BASE_VERSION) > 0) { |
| 733 // We've committed this entry in the past. |
| 734 if (is_directory != same_id->Get(IS_DIR) || |
| 735 has_bookmark_data != same_id->Get(IS_BOOKMARK_OBJECT)) { |
| 736 LOG(ERROR) << "Server update doesn't agree with committed item. "; |
| 737 LOG(ERROR) << " Entry: " << *same_id; |
| 738 LOG(ERROR) << " Update: " << SyncEntityDebugString(entry); |
| 739 return VERIFY_FAIL; |
| 740 } |
| 741 if (same_id->Get(BASE_VERSION) == entry.version() && |
| 742 !same_id->Get(IS_UNSYNCED) && |
| 743 !SyncerProtoUtil::Compare(*same_id, entry)) { |
| 744 // TODO(sync): This constraint needs to be relaxed. For now it's OK to |
| 745 // fail the verification and deal with it when we ApplyUpdates. |
| 746 LOG(ERROR) << "Server update doesn't match local data with same " |
| 747 "version. A bug should be filed. Entry: " << *same_id << |
| 748 "Update: " << SyncEntityDebugString(entry); |
| 749 return VERIFY_FAIL; |
| 750 } |
| 751 if (same_id->Get(SERVER_VERSION) > entry.version()) { |
| 752 LOG(WARNING) << "We've already seen a more recent update from the server"; |
| 753 LOG(WARNING) << " Entry: " << *same_id; |
| 754 LOG(WARNING) << " Update: " << SyncEntityDebugString(entry); |
| 755 return VERIFY_SKIP; |
| 756 } |
| 757 } |
| 758 return VERIFY_SUCCESS; |
| 759 } |
| 760 |
| 761 // Assumes we have an existing entry; verify an update that seems to be |
| 762 // expressing an 'undelete' |
| 763 // static |
| 764 VerifyResult SyncerUtil::VerifyUndelete(syncable::WriteTransaction* trans, |
| 765 const SyncEntity& entry, |
| 766 syncable::MutableEntry* same_id) { |
| 767 CHECK(same_id->good()); |
| 768 LOG(INFO) << "Server update is attempting undelete. " << *same_id |
| 769 << "Update:" << SyncEntityDebugString(entry); |
| 770 // Move the old one aside and start over. It's too tricky to |
| 771 // get the old one back into a state that would pass |
| 772 // CheckTreeInvariants(). |
| 773 if (same_id->Get(IS_DEL)) { |
| 774 same_id->Put(ID, trans->directory()->NextId()); |
| 775 same_id->Put(BASE_VERSION, CHANGES_VERSION); |
| 776 same_id->Put(SERVER_VERSION, 0); |
| 777 return VERIFY_SUCCESS; |
| 778 } |
| 779 if (entry.version() < same_id->Get(SERVER_VERSION)) { |
| 780 LOG(WARNING) << "Update older than current server version for" << |
| 781 *same_id << "Update:" << SyncEntityDebugString(entry); |
| 782 return VERIFY_SUCCESS; // Expected in new sync protocol. |
| 783 } |
| 784 return VERIFY_UNDECIDED; |
| 785 } |
| 786 |
| 787 // static |
| 788 syncable::Id SyncerUtil::ComputePrevIdFromServerPosition( |
| 789 syncable::BaseTransaction* trans, |
| 790 syncable::Entry* update_item, |
| 791 const syncable::Id& parent_id) { |
| 792 const int64 position_in_parent = update_item->Get(SERVER_POSITION_IN_PARENT); |
| 793 |
| 794 // TODO(ncarter): This computation is linear in the number of children, but |
| 795 // we could make it logarithmic if we kept an index on server position. |
| 796 syncable::Id closest_sibling; |
| 797 syncable::Id next_id = trans->directory()->GetFirstChildId(trans, parent_id); |
| 798 while (!next_id.IsRoot()) { |
| 799 syncable::Entry candidate(trans, GET_BY_ID, next_id); |
| 800 if (!candidate.good()) { |
| 801 LOG(WARNING) << "Should not happen"; |
| 802 return closest_sibling; |
| 803 } |
| 804 next_id = candidate.Get(NEXT_ID); |
| 805 |
| 806 // Defensively prevent self-comparison. |
| 807 if (candidate.Get(META_HANDLE) == update_item->Get(META_HANDLE)) { |
| 808 continue; |
| 809 } |
| 810 |
| 811 // Ignore unapplied updates -- they might not even be server-siblings. |
| 812 if (candidate.Get(IS_UNAPPLIED_UPDATE)) { |
| 813 continue; |
| 814 } |
| 815 |
| 816 // Unsynced items don't have a valid server position. |
| 817 if (!candidate.Get(IS_UNSYNCED)) { |
| 818 // If |candidate| is after |update_entry| according to the server |
| 819 // ordering, then we're done. ID is the tiebreaker. |
| 820 if ((candidate.Get(SERVER_POSITION_IN_PARENT) > position_in_parent) || |
| 821 (candidate.Get(SERVER_POSITION_IN_PARENT) == position_in_parent) && |
| 822 (candidate.Get(ID) > update_item->Get(ID))) { |
| 823 return closest_sibling; |
| 824 } |
| 825 } |
| 826 |
| 827 // We can't trust the SERVER_ fields of unsynced items, but they are |
| 828 // potentially legitimate local predecessors. In the case where |
| 829 // |update_item| and an unsynced item wind up in the same insertion |
| 830 // position, we need to choose how to order them. The following check puts |
| 831 // the unapplied update first; removing it would put the unsynced item(s) |
| 832 // first. |
| 833 if (candidate.Get(IS_UNSYNCED)) { |
| 834 continue; |
| 835 } |
| 836 |
| 837 // |update_entry| is considered to be somewhere after |candidate|, so |
| 838 // store it as the upper bound. |
| 839 closest_sibling = candidate.Get(ID); |
| 840 } |
| 841 |
| 842 return closest_sibling; |
| 843 } |
| 844 |
| 845 } // namespace browser_sync |
OLD | NEW |