| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 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 "sync/engine/get_commit_ids.h" | |
| 6 | |
| 7 #include <stddef.h> | |
| 8 #include <stdint.h> | |
| 9 | |
| 10 #include <set> | |
| 11 #include <vector> | |
| 12 | |
| 13 #include "base/macros.h" | |
| 14 #include "sync/engine/syncer_util.h" | |
| 15 #include "sync/syncable/directory.h" | |
| 16 #include "sync/syncable/entry.h" | |
| 17 #include "sync/syncable/nigori_handler.h" | |
| 18 #include "sync/syncable/nigori_util.h" | |
| 19 #include "sync/syncable/syncable_base_transaction.h" | |
| 20 #include "sync/syncable/syncable_util.h" | |
| 21 #include "sync/util/cryptographer.h" | |
| 22 | |
| 23 using std::set; | |
| 24 using std::vector; | |
| 25 | |
| 26 namespace syncer { | |
| 27 | |
| 28 namespace { | |
| 29 | |
| 30 // Forward-declare some helper functions. This gives us more options for | |
| 31 // ordering the function defintions within this file. | |
| 32 | |
| 33 // Filters |unsynced_handles| to remove all entries that do not belong to the | |
| 34 // specified |requested_types|, or are not eligible for a commit at this time. | |
| 35 void FilterUnreadyEntries( | |
| 36 syncable::BaseTransaction* trans, | |
| 37 ModelTypeSet requested_types, | |
| 38 ModelTypeSet encrypted_types, | |
| 39 bool passphrase_missing, | |
| 40 const syncable::Directory::Metahandles& unsynced_handles, | |
| 41 std::set<int64_t>* ready_unsynced_set); | |
| 42 | |
| 43 // Given a set of commit metahandles that are ready for commit | |
| 44 // (|ready_unsynced_set|), sorts these into commit order and places up to | |
| 45 // |max_entries| of them in the output parameter |out|. | |
| 46 // | |
| 47 // See the header file for an explanation of commit ordering. | |
| 48 void OrderCommitIds(syncable::BaseTransaction* trans, | |
| 49 size_t max_entries, | |
| 50 const std::set<int64_t>& ready_unsynced_set, | |
| 51 std::vector<int64_t>* out); | |
| 52 | |
| 53 } // namespace | |
| 54 | |
| 55 void GetCommitIdsForType( | |
| 56 syncable::BaseTransaction* trans, | |
| 57 ModelType type, | |
| 58 size_t max_entries, | |
| 59 syncable::Directory::Metahandles* out) { | |
| 60 syncable::Directory* dir = trans->directory(); | |
| 61 | |
| 62 // Gather the full set of unsynced items and store it in the session. They | |
| 63 // are not in the correct order for commit. | |
| 64 std::set<int64_t> ready_unsynced_set; | |
| 65 syncable::Directory::Metahandles all_unsynced_handles; | |
| 66 GetUnsyncedEntries(trans, &all_unsynced_handles); | |
| 67 | |
| 68 ModelTypeSet encrypted_types; | |
| 69 bool passphrase_missing = false; | |
| 70 Cryptographer* cryptographer = dir->GetCryptographer(trans); | |
| 71 if (cryptographer) { | |
| 72 encrypted_types = dir->GetNigoriHandler()->GetEncryptedTypes(trans); | |
| 73 passphrase_missing = cryptographer->has_pending_keys(); | |
| 74 } | |
| 75 | |
| 76 // We filter out all unready entries from the set of unsynced handles. This | |
| 77 // new set of ready and unsynced items is then what we use to determine what | |
| 78 // is a candidate for commit. The caller is responsible for ensuring that no | |
| 79 // throttled types are included among the requested_types. | |
| 80 FilterUnreadyEntries(trans, | |
| 81 ModelTypeSet(type), | |
| 82 encrypted_types, | |
| 83 passphrase_missing, | |
| 84 all_unsynced_handles, | |
| 85 &ready_unsynced_set); | |
| 86 | |
| 87 OrderCommitIds(trans, max_entries, ready_unsynced_set, out); | |
| 88 | |
| 89 for (size_t i = 0; i < out->size(); i++) { | |
| 90 DVLOG(1) << "Debug commit batch result:" << (*out)[i]; | |
| 91 } | |
| 92 } | |
| 93 | |
| 94 namespace { | |
| 95 | |
| 96 bool IsEntryInConflict(const syncable::Entry& entry) { | |
| 97 if (entry.GetIsUnsynced() && | |
| 98 entry.GetServerVersion() > 0 && | |
| 99 (entry.GetServerVersion() > entry.GetBaseVersion())) { | |
| 100 // The local and server versions don't match. The item must be in | |
| 101 // conflict, so there's no point in attempting to commit. | |
| 102 DCHECK(entry.GetIsUnappliedUpdate()); | |
| 103 DVLOG(1) << "Excluding entry from commit due to version mismatch " | |
| 104 << entry; | |
| 105 return true; | |
| 106 } | |
| 107 return false; | |
| 108 } | |
| 109 | |
| 110 // Return true if this entry has any attachments that haven't yet been uploaded | |
| 111 // to the server. | |
| 112 bool HasAttachmentNotOnServer(const syncable::Entry& entry) { | |
| 113 const sync_pb::AttachmentMetadata& metadata = entry.GetAttachmentMetadata(); | |
| 114 for (int i = 0; i < metadata.record_size(); ++i) { | |
| 115 if (!metadata.record(i).is_on_server()) { | |
| 116 return true; | |
| 117 } | |
| 118 } | |
| 119 return false; | |
| 120 } | |
| 121 | |
| 122 // An entry may not commit if any are true: | |
| 123 // 1. It requires encryption (either the type is encrypted but a passphrase | |
| 124 // is missing from the cryptographer, or the entry itself wasn't properly | |
| 125 // encrypted). | |
| 126 // 2. It's type is currently throttled. | |
| 127 // 3. It's a delete but has not been committed. | |
| 128 bool MayEntryCommit(ModelTypeSet requested_types, | |
| 129 ModelTypeSet encrypted_types, | |
| 130 bool passphrase_missing, | |
| 131 const syncable::Entry& entry) { | |
| 132 DCHECK(entry.GetIsUnsynced()); | |
| 133 | |
| 134 const ModelType type = entry.GetModelType(); | |
| 135 // We special case the nigori node because even though it is considered an | |
| 136 // "encrypted type", not all nigori node changes require valid encryption | |
| 137 // (ex: sync_tabs). | |
| 138 if ((type != NIGORI) && encrypted_types.Has(type) && | |
| 139 (passphrase_missing || | |
| 140 syncable::EntryNeedsEncryption(encrypted_types, entry))) { | |
| 141 // This entry requires encryption but is not properly encrypted (possibly | |
| 142 // due to the cryptographer not being initialized or the user hasn't | |
| 143 // provided the most recent passphrase). | |
| 144 DVLOG(1) << "Excluding entry from commit due to lack of encryption " | |
| 145 << entry; | |
| 146 return false; | |
| 147 } | |
| 148 | |
| 149 // Ignore it if it's not in our set of requested types. | |
| 150 if (!requested_types.Has(type)) | |
| 151 return false; | |
| 152 | |
| 153 if (entry.GetIsDel() && !entry.GetId().ServerKnows()) { | |
| 154 // New clients (following the resolution of crbug.com/125381) should not | |
| 155 // create such items. Old clients may have left some in the database | |
| 156 // (crbug.com/132905), but we should now be cleaning them on startup. | |
| 157 NOTREACHED() << "Found deleted and unsynced local item: " << entry; | |
| 158 return false; | |
| 159 } | |
| 160 | |
| 161 // Extra validity checks. | |
| 162 syncable::Id id = entry.GetId(); | |
| 163 if (id == entry.GetParentId()) { | |
| 164 CHECK(id.IsRoot()) << "Non-root item is self parenting." << entry; | |
| 165 // If the root becomes unsynced it can cause us problems. | |
| 166 NOTREACHED() << "Root item became unsynced " << entry; | |
| 167 return false; | |
| 168 } | |
| 169 | |
| 170 if (entry.IsRoot()) { | |
| 171 NOTREACHED() << "Permanent item became unsynced " << entry; | |
| 172 return false; | |
| 173 } | |
| 174 | |
| 175 if (HasAttachmentNotOnServer(entry)) { | |
| 176 // This entry is not ready to be sent to the server because it has one or | |
| 177 // more attachments that have not yet been uploaded to the server. The idea | |
| 178 // here is avoid propagating an entry with dangling attachment references. | |
| 179 return false; | |
| 180 } | |
| 181 | |
| 182 DVLOG(2) << "Entry is ready for commit: " << entry; | |
| 183 return true; | |
| 184 } | |
| 185 | |
| 186 bool SupportsHierarchy(const syncable::Entry& item) { | |
| 187 // Types with explicit server supported hierarchy only. | |
| 188 return IsTypeWithServerGeneratedRoot(item.GetModelType()); | |
| 189 } | |
| 190 | |
| 191 // Excludes ancestors of deleted conflicted items from | |
| 192 // |ready_unsynced_set|. | |
| 193 void ExcludeDeletedAncestors( | |
| 194 syncable::BaseTransaction* trans, | |
| 195 const std::vector<int64_t>& deleted_conflicted_items, | |
| 196 std::set<int64_t>* ready_unsynced_set) { | |
| 197 for (auto iter = deleted_conflicted_items.begin(); | |
| 198 iter != deleted_conflicted_items.end(); ++iter) { | |
| 199 syncable::Entry item(trans, syncable::GET_BY_HANDLE, *iter); | |
| 200 syncable::Id parent_id = item.GetParentId(); | |
| 201 DCHECK(!parent_id.IsNull()); | |
| 202 | |
| 203 while (!parent_id.IsRoot()) { | |
| 204 syncable::Entry parent(trans, syncable::GET_BY_ID, parent_id); | |
| 205 CHECK(parent.good()) << "Bad user-only parent in item path."; | |
| 206 int64_t handle = parent.GetMetahandle(); | |
| 207 | |
| 208 if (!parent.GetIsDel()) | |
| 209 break; | |
| 210 | |
| 211 auto ready_iter = ready_unsynced_set->find(handle); | |
| 212 if (ready_iter == ready_unsynced_set->end()) | |
| 213 break; | |
| 214 | |
| 215 // Remove this entry from |ready_unsynced_set|. | |
| 216 ready_unsynced_set->erase(ready_iter); | |
| 217 parent_id = parent.GetParentId(); | |
| 218 } | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 // Iterates over children of items from |conflicted_items| list that are in | |
| 223 // |ready_unsynced_set|, exludes them from |ready_unsynced_set| and adds them | |
| 224 // to |excluded_items| list. | |
| 225 void ExcludeChildren(syncable::BaseTransaction* trans, | |
| 226 const std::vector<int64_t>& conflicted_items, | |
| 227 std::vector<int64_t>* excluded_items, | |
| 228 std::set<int64_t>* ready_unsynced_set) { | |
| 229 for (auto iter = conflicted_items.begin(); iter != conflicted_items.end(); | |
| 230 ++iter) { | |
| 231 syncable::Entry entry(trans, syncable::GET_BY_HANDLE, *iter); | |
| 232 | |
| 233 if (!entry.GetIsDir() || entry.GetIsDel()) | |
| 234 continue; | |
| 235 | |
| 236 std::vector<int64_t> children; | |
| 237 entry.GetChildHandles(&children); | |
| 238 | |
| 239 for (std::vector<int64_t>::const_iterator child_iter = children.begin(); | |
| 240 child_iter != children.end(); ++child_iter) { | |
| 241 // Collect all child handles that are in |ready_unsynced_set|. | |
| 242 int64_t child_handle = *child_iter; | |
| 243 auto ready_iter = ready_unsynced_set->find(child_handle); | |
| 244 if (ready_iter != ready_unsynced_set->end()) { | |
| 245 // Remove this entry from |ready_unsynced_set| and add it | |
| 246 // to |excluded_items|. | |
| 247 ready_unsynced_set->erase(ready_iter); | |
| 248 excluded_items->push_back(child_handle); | |
| 249 } | |
| 250 } | |
| 251 } | |
| 252 } | |
| 253 | |
| 254 // Filters |unsynced_handles| to remove all entries that do not belong to the | |
| 255 // specified |requested_types|, or are not eligible for a commit at this time. | |
| 256 void FilterUnreadyEntries( | |
| 257 syncable::BaseTransaction* trans, | |
| 258 ModelTypeSet requested_types, | |
| 259 ModelTypeSet encrypted_types, | |
| 260 bool passphrase_missing, | |
| 261 const syncable::Directory::Metahandles& unsynced_handles, | |
| 262 std::set<int64_t>* ready_unsynced_set) { | |
| 263 std::vector<int64_t> deleted_conflicted_items; | |
| 264 std::vector<int64_t> conflicted_items; | |
| 265 | |
| 266 // Go over all unsynced handles, filter the ones that might be committed based | |
| 267 // on type / encryption, then based on whether they are in conflict add them | |
| 268 // to either |ready_unsynced_set| or one of the conflicted lists. | |
| 269 for (auto iter = unsynced_handles.begin(); iter != unsynced_handles.end(); | |
| 270 ++iter) { | |
| 271 syncable::Entry entry(trans, syncable::GET_BY_HANDLE, *iter); | |
| 272 // TODO(maniscalco): While we check if entry is ready to be committed, we | |
| 273 // also need to check that all of its ancestors (parents, transitive) are | |
| 274 // ready to be committed. Once attachments can prevent an entry from being | |
| 275 // committable, this method must ensure all ancestors are ready for commit | |
| 276 // (bug 356273). | |
| 277 if (MayEntryCommit(requested_types, encrypted_types, passphrase_missing, | |
| 278 entry)) { | |
| 279 if (IsEntryInConflict(entry)) { | |
| 280 // Conflicting hierarchical entries might prevent their ancestors or | |
| 281 // descendants from being committed. | |
| 282 if (SupportsHierarchy(entry)) { | |
| 283 if (entry.GetIsDel()) { | |
| 284 deleted_conflicted_items.push_back(*iter); | |
| 285 } else if (entry.GetIsDir()) { | |
| 286 // Populate the initial version of |conflicted_items| with folder | |
| 287 // items that are in conflict. | |
| 288 conflicted_items.push_back(*iter); | |
| 289 } | |
| 290 } | |
| 291 } else { | |
| 292 ready_unsynced_set->insert(*iter); | |
| 293 } | |
| 294 } | |
| 295 } | |
| 296 | |
| 297 // If there are any deleted conflicted entries, remove their deleted ancestors | |
| 298 // from |ready_unsynced_set| as well. | |
| 299 ExcludeDeletedAncestors(trans, deleted_conflicted_items, ready_unsynced_set); | |
| 300 | |
| 301 // Starting with conflicted_items containing conflicted folders go down and | |
| 302 // exclude all descendants from |ready_unsynced_set|. | |
| 303 while (!conflicted_items.empty()) { | |
| 304 std::vector<int64_t> new_list; | |
| 305 ExcludeChildren(trans, conflicted_items, &new_list, ready_unsynced_set); | |
| 306 conflicted_items.swap(new_list); | |
| 307 } | |
| 308 } | |
| 309 | |
| 310 // This class helps to implement OrderCommitIds(). Its members track the | |
| 311 // progress of a traversal while its methods extend it. It can return early if | |
| 312 // the traversal reaches the desired size before the full traversal is complete. | |
| 313 class Traversal { | |
| 314 public: | |
| 315 Traversal(syncable::BaseTransaction* trans, | |
| 316 int64_t max_entries, | |
| 317 syncable::Directory::Metahandles* out); | |
| 318 ~Traversal(); | |
| 319 | |
| 320 // First step of traversal building. Adds non-deleted items in order. | |
| 321 void AddCreatesAndMoves(const std::set<int64_t>& ready_unsynced_set); | |
| 322 | |
| 323 // Second step of traverals building. Appends deleted items. | |
| 324 void AddDeletes(const std::set<int64_t>& ready_unsynced_set); | |
| 325 | |
| 326 private: | |
| 327 // The following functions do not modify the traversal directly. They return | |
| 328 // their results in the |result| vector instead. | |
| 329 void AddUncommittedParents(const std::set<int64_t>& ready_unsynced_set, | |
| 330 const syncable::Entry& item, | |
| 331 syncable::Directory::Metahandles* result) const; | |
| 332 | |
| 333 bool TryAddItem(const std::set<int64_t>& ready_unsynced_set, | |
| 334 const syncable::Entry& item, | |
| 335 syncable::Directory::Metahandles* result) const; | |
| 336 | |
| 337 void AddDeletedParents(const std::set<int64_t>& ready_unsynced_set, | |
| 338 const syncable::Entry& item, | |
| 339 const syncable::Directory::Metahandles& traversed, | |
| 340 syncable::Directory::Metahandles* result) const; | |
| 341 | |
| 342 // Returns true if we've collected enough items. | |
| 343 bool IsFull() const; | |
| 344 | |
| 345 // Returns true if the specified handle is already in the traversal. | |
| 346 bool HaveItem(int64_t handle) const; | |
| 347 | |
| 348 // Adds the specified handles to the traversal. | |
| 349 void AppendManyToTraversal(const syncable::Directory::Metahandles& handles); | |
| 350 | |
| 351 // Adds the specifed handle to the traversal. | |
| 352 void AppendToTraversal(int64_t handle); | |
| 353 | |
| 354 syncable::Directory::Metahandles* out_; | |
| 355 std::set<int64_t> added_handles_; | |
| 356 const size_t max_entries_; | |
| 357 syncable::BaseTransaction* trans_; | |
| 358 | |
| 359 DISALLOW_COPY_AND_ASSIGN(Traversal); | |
| 360 }; | |
| 361 | |
| 362 Traversal::Traversal(syncable::BaseTransaction* trans, | |
| 363 int64_t max_entries, | |
| 364 syncable::Directory::Metahandles* out) | |
| 365 : out_(out), max_entries_(max_entries), trans_(trans) {} | |
| 366 | |
| 367 Traversal::~Traversal() {} | |
| 368 | |
| 369 void Traversal::AddUncommittedParents( | |
| 370 const std::set<int64_t>& ready_unsynced_set, | |
| 371 const syncable::Entry& item, | |
| 372 syncable::Directory::Metahandles* result) const { | |
| 373 DCHECK(SupportsHierarchy(item)); | |
| 374 syncable::Directory::Metahandles dependencies; | |
| 375 syncable::Id parent_id = item.GetParentId(); | |
| 376 | |
| 377 // Climb the tree adding entries leaf -> root. | |
| 378 while (!parent_id.ServerKnows()) { | |
| 379 syncable::Entry parent(trans_, syncable::GET_BY_ID, parent_id); | |
| 380 CHECK(parent.good()) << "Bad user-only parent in item path."; | |
| 381 int64_t handle = parent.GetMetahandle(); | |
| 382 if (HaveItem(handle)) { | |
| 383 // We've already added this parent (and therefore all of its parents). | |
| 384 // We can return early. | |
| 385 break; | |
| 386 } | |
| 387 | |
| 388 if (!TryAddItem(ready_unsynced_set, parent, &dependencies)) { | |
| 389 // The parent isn't in |ready_unsynced_set|. | |
| 390 break; | |
| 391 } | |
| 392 | |
| 393 parent_id = parent.GetParentId(); | |
| 394 } | |
| 395 | |
| 396 // Reverse what we added to get the correct order. | |
| 397 result->insert(result->end(), dependencies.rbegin(), dependencies.rend()); | |
| 398 } | |
| 399 | |
| 400 // Adds the given item to the list if it is unsynced and ready for commit. | |
| 401 bool Traversal::TryAddItem(const std::set<int64_t>& ready_unsynced_set, | |
| 402 const syncable::Entry& item, | |
| 403 syncable::Directory::Metahandles* result) const { | |
| 404 DCHECK(item.GetIsUnsynced()); | |
| 405 int64_t item_handle = item.GetMetahandle(); | |
| 406 if (ready_unsynced_set.count(item_handle) != 0) { | |
| 407 result->push_back(item_handle); | |
| 408 return true; | |
| 409 } | |
| 410 return false; | |
| 411 } | |
| 412 | |
| 413 // Traverses the tree from bottom to top, adding the deleted parents of the | |
| 414 // given |item|. Stops traversing if it encounters a non-deleted node, or | |
| 415 // a node that was already listed in the |traversed| list. | |
| 416 // | |
| 417 // The result list is reversed before it is returned, so the resulting | |
| 418 // traversal is in top to bottom order. Also note that this function appends | |
| 419 // to the result list without clearing it. | |
| 420 void Traversal::AddDeletedParents( | |
| 421 const std::set<int64_t>& ready_unsynced_set, | |
| 422 const syncable::Entry& item, | |
| 423 const syncable::Directory::Metahandles& traversed, | |
| 424 syncable::Directory::Metahandles* result) const { | |
| 425 DCHECK(SupportsHierarchy(item)); | |
| 426 syncable::Directory::Metahandles dependencies; | |
| 427 syncable::Id parent_id = item.GetParentId(); | |
| 428 | |
| 429 // Climb the tree adding entries leaf -> root. | |
| 430 while (!parent_id.IsRoot()) { | |
| 431 syncable::Entry parent(trans_, syncable::GET_BY_ID, parent_id); | |
| 432 | |
| 433 if (!parent.good()) { | |
| 434 // This is valid because the parent could have gone away a long time ago | |
| 435 // | |
| 436 // Consider the case where a folder is server-unknown and locally | |
| 437 // deleted, and has a child that is server-known, deleted, and unsynced. | |
| 438 // The parent could be dropped from memory at any time, but its child | |
| 439 // needs to be committed first. | |
| 440 break; | |
| 441 } | |
| 442 int64_t handle = parent.GetMetahandle(); | |
| 443 if (!parent.GetIsUnsynced()) { | |
| 444 // In some rare cases, our parent can be both deleted and unsynced. | |
| 445 // (ie. the server-unknown parent case). | |
| 446 break; | |
| 447 } | |
| 448 if (!parent.GetIsDel()) { | |
| 449 // We're not intersted in non-deleted parents. | |
| 450 break; | |
| 451 } | |
| 452 if (std::find(traversed.begin(), traversed.end(), handle) != | |
| 453 traversed.end()) { | |
| 454 // We've already added this parent (and therefore all of its parents). | |
| 455 // We can return early. | |
| 456 break; | |
| 457 } | |
| 458 | |
| 459 if (!TryAddItem(ready_unsynced_set, parent, &dependencies)) { | |
| 460 // The parent isn't in ready_unsynced_set. | |
| 461 break; | |
| 462 } | |
| 463 | |
| 464 parent_id = parent.GetParentId(); | |
| 465 } | |
| 466 | |
| 467 // Reverse what we added to get the correct order. | |
| 468 result->insert(result->end(), dependencies.rbegin(), dependencies.rend()); | |
| 469 } | |
| 470 | |
| 471 bool Traversal::IsFull() const { | |
| 472 return out_->size() >= max_entries_; | |
| 473 } | |
| 474 | |
| 475 bool Traversal::HaveItem(int64_t handle) const { | |
| 476 return added_handles_.find(handle) != added_handles_.end(); | |
| 477 } | |
| 478 | |
| 479 void Traversal::AppendManyToTraversal( | |
| 480 const syncable::Directory::Metahandles& handles) { | |
| 481 out_->insert(out_->end(), handles.begin(), handles.end()); | |
| 482 added_handles_.insert(handles.begin(), handles.end()); | |
| 483 } | |
| 484 | |
| 485 void Traversal::AppendToTraversal(int64_t metahandle) { | |
| 486 out_->push_back(metahandle); | |
| 487 added_handles_.insert(metahandle); | |
| 488 } | |
| 489 | |
| 490 void Traversal::AddCreatesAndMoves( | |
| 491 const std::set<int64_t>& ready_unsynced_set) { | |
| 492 // Add moves and creates, and prepend their uncommitted parents. | |
| 493 for (std::set<int64_t>::const_iterator iter = ready_unsynced_set.begin(); | |
| 494 !IsFull() && iter != ready_unsynced_set.end(); ++iter) { | |
| 495 int64_t metahandle = *iter; | |
| 496 if (HaveItem(metahandle)) | |
| 497 continue; | |
| 498 | |
| 499 syncable::Entry entry(trans_, | |
| 500 syncable::GET_BY_HANDLE, | |
| 501 metahandle); | |
| 502 if (!entry.GetIsDel()) { | |
| 503 if (SupportsHierarchy(entry)) { | |
| 504 // We only commit an item + its dependencies if it and all its | |
| 505 // dependencies are not in conflict. | |
| 506 syncable::Directory::Metahandles item_dependencies; | |
| 507 AddUncommittedParents(ready_unsynced_set, entry, &item_dependencies); | |
| 508 TryAddItem(ready_unsynced_set, entry, &item_dependencies); | |
| 509 AppendManyToTraversal(item_dependencies); | |
| 510 } else { | |
| 511 // No hierarchy dependencies, just commit the item itself. | |
| 512 AppendToTraversal(metahandle); | |
| 513 } | |
| 514 } | |
| 515 } | |
| 516 | |
| 517 // It's possible that we overcommitted while trying to expand dependent | |
| 518 // items. If so, truncate the set down to the allowed size. | |
| 519 if (out_->size() > max_entries_) | |
| 520 out_->resize(max_entries_); | |
| 521 } | |
| 522 | |
| 523 void Traversal::AddDeletes(const std::set<int64_t>& ready_unsynced_set) { | |
| 524 syncable::Directory::Metahandles deletion_list; | |
| 525 | |
| 526 // Note: we iterate over all the unsynced set, regardless of the max size. | |
| 527 // The max size is only enforced after the top-to-bottom order has been | |
| 528 // reversed, in order to ensure children are always deleted before parents. | |
| 529 for (std::set<int64_t>::const_iterator iter = ready_unsynced_set.begin(); | |
| 530 iter != ready_unsynced_set.end(); ++iter) { | |
| 531 int64_t metahandle = *iter; | |
| 532 | |
| 533 if (HaveItem(metahandle)) | |
| 534 continue; | |
| 535 | |
| 536 if (std::find(deletion_list.begin(), deletion_list.end(), metahandle) != | |
| 537 deletion_list.end()) { | |
| 538 continue; | |
| 539 } | |
| 540 | |
| 541 syncable::Entry entry(trans_, syncable::GET_BY_HANDLE, | |
| 542 metahandle); | |
| 543 | |
| 544 if (entry.GetIsDel()) { | |
| 545 if (SupportsHierarchy(entry)) { | |
| 546 syncable::Directory::Metahandles parents; | |
| 547 AddDeletedParents(ready_unsynced_set, entry, deletion_list, &parents); | |
| 548 // Append parents and chilren in top to bottom order. | |
| 549 deletion_list.insert(deletion_list.end(), parents.begin(), | |
| 550 parents.end()); | |
| 551 } | |
| 552 deletion_list.push_back(metahandle); | |
| 553 } | |
| 554 } | |
| 555 | |
| 556 // We've been gathering deletions in top to bottom order. Now we reverse the | |
| 557 // order as we prepare the list. | |
| 558 std::reverse(deletion_list.begin(), deletion_list.end()); | |
| 559 AppendManyToTraversal(deletion_list); | |
| 560 | |
| 561 // It's possible that we overcommitted while trying to expand dependent | |
| 562 // items. If so, truncate the set down to the allowed size. | |
| 563 if (out_->size() > max_entries_) | |
| 564 out_->resize(max_entries_); | |
| 565 } | |
| 566 | |
| 567 void OrderCommitIds(syncable::BaseTransaction* trans, | |
| 568 size_t max_entries, | |
| 569 const std::set<int64_t>& ready_unsynced_set, | |
| 570 syncable::Directory::Metahandles* out) { | |
| 571 // Commits follow these rules: | |
| 572 // 1. Moves or creates are preceded by needed folder creates, from | |
| 573 // root to leaf. | |
| 574 // 2. Moves/Creates before deletes. | |
| 575 // 3. Deletes, collapsed. | |
| 576 // We commit deleted moves under deleted items as moves when collapsing | |
| 577 // delete trees. | |
| 578 | |
| 579 Traversal traversal(trans, max_entries, out); | |
| 580 | |
| 581 // Add moves and creates, and prepend their uncommitted parents. | |
| 582 traversal.AddCreatesAndMoves(ready_unsynced_set); | |
| 583 | |
| 584 // Add all deletes. | |
| 585 traversal.AddDeletes(ready_unsynced_set); | |
| 586 } | |
| 587 | |
| 588 } // namespace | |
| 589 | |
| 590 } // namespace syncer | |
| OLD | NEW |