[SyncFS] Build indexes from FileTracker entries on disk.
[chromium-blink-merge.git] / sync / engine / get_commit_ids.cc
blob5f91915d5a0720e018ea333938e1329727c85cc4
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.
5 #include "sync/engine/get_commit_ids.h"
7 #include <set>
8 #include <vector>
10 #include "base/basictypes.h"
11 #include "sync/engine/syncer_util.h"
12 #include "sync/syncable/directory.h"
13 #include "sync/syncable/entry.h"
14 #include "sync/syncable/nigori_handler.h"
15 #include "sync/syncable/nigori_util.h"
16 #include "sync/syncable/syncable_base_transaction.h"
17 #include "sync/syncable/syncable_util.h"
18 #include "sync/util/cryptographer.h"
20 using std::set;
21 using std::vector;
23 namespace syncer {
25 namespace {
27 // Forward-declare some helper functions. This gives us more options for
28 // ordering the function defintions within this file.
30 // Filters |unsynced_handles| to remove all entries that do not belong to the
31 // specified |requested_types|, or are not eligible for a commit at this time.
32 void FilterUnreadyEntries(
33 syncable::BaseTransaction* trans,
34 ModelTypeSet requested_types,
35 ModelTypeSet encrypted_types,
36 bool passphrase_missing,
37 const syncable::Directory::Metahandles& unsynced_handles,
38 std::set<int64>* ready_unsynced_set);
40 // Given a set of commit metahandles that are ready for commit
41 // (|ready_unsynced_set|), sorts these into commit order and places up to
42 // |max_entries| of them in the output parameter |out|.
44 // See the header file for an explanation of commit ordering.
45 void OrderCommitIds(
46 syncable::BaseTransaction* trans,
47 size_t max_entries,
48 const std::set<int64>& ready_unsynced_set,
49 std::vector<int64>* out);
51 } // namespace
53 void GetCommitIdsForType(
54 syncable::BaseTransaction* trans,
55 ModelType type,
56 size_t max_entries,
57 syncable::Directory::Metahandles* out) {
58 syncable::Directory* dir = trans->directory();
60 // Gather the full set of unsynced items and store it in the session. They
61 // are not in the correct order for commit.
62 std::set<int64> ready_unsynced_set;
63 syncable::Directory::Metahandles all_unsynced_handles;
64 GetUnsyncedEntries(trans, &all_unsynced_handles);
66 ModelTypeSet encrypted_types;
67 bool passphrase_missing = false;
68 Cryptographer* cryptographer = dir->GetCryptographer(trans);
69 if (cryptographer) {
70 encrypted_types = dir->GetNigoriHandler()->GetEncryptedTypes(trans);
71 passphrase_missing = cryptographer->has_pending_keys();
74 // We filter out all unready entries from the set of unsynced handles. This
75 // new set of ready and unsynced items is then what we use to determine what
76 // is a candidate for commit. The caller is responsible for ensuring that no
77 // throttled types are included among the requested_types.
78 FilterUnreadyEntries(trans,
79 ModelTypeSet(type),
80 encrypted_types,
81 passphrase_missing,
82 all_unsynced_handles,
83 &ready_unsynced_set);
85 OrderCommitIds(trans, max_entries, ready_unsynced_set, out);
87 for (size_t i = 0; i < out->size(); i++) {
88 DVLOG(1) << "Debug commit batch result:" << (*out)[i];
92 namespace {
94 bool IsEntryInConflict(const syncable::Entry& entry) {
95 if (entry.GetIsUnsynced() &&
96 entry.GetServerVersion() > 0 &&
97 (entry.GetServerVersion() > entry.GetBaseVersion())) {
98 // The local and server versions don't match. The item must be in
99 // conflict, so there's no point in attempting to commit.
100 DCHECK(entry.GetIsUnappliedUpdate());
101 DVLOG(1) << "Excluding entry from commit due to version mismatch "
102 << entry;
103 return true;
105 return false;
108 // Return true if this entry has any attachments that haven't yet been uploaded
109 // to the server.
110 bool HasAttachmentNotOnServer(const syncable::Entry& entry) {
111 // TODO(maniscalco): Add test case (bug 356266).
112 const sync_pb::AttachmentMetadata& metadata = entry.GetAttachmentMetadata();
113 for (int i = 0; i < metadata.record_size(); ++i) {
114 if (!metadata.record(i).is_on_server()) {
115 return true;
118 return false;
121 // An entry is not considered ready for commit if any are true:
122 // 1. It's in conflict.
123 // 2. 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 // 3. It's type is currently throttled.
127 // 4. It's a delete but has not been committed.
128 bool IsEntryReadyForCommit(ModelTypeSet requested_types,
129 ModelTypeSet encrypted_types,
130 bool passphrase_missing,
131 const syncable::Entry& entry) {
132 DCHECK(entry.GetIsUnsynced());
133 if (IsEntryInConflict(entry))
134 return false;
136 const ModelType type = entry.GetModelType();
137 // We special case the nigori node because even though it is considered an
138 // "encrypted type", not all nigori node changes require valid encryption
139 // (ex: sync_tabs).
140 if ((type != NIGORI) && encrypted_types.Has(type) &&
141 (passphrase_missing ||
142 syncable::EntryNeedsEncryption(encrypted_types, entry))) {
143 // This entry requires encryption but is not properly encrypted (possibly
144 // due to the cryptographer not being initialized or the user hasn't
145 // provided the most recent passphrase).
146 DVLOG(1) << "Excluding entry from commit due to lack of encryption "
147 << entry;
148 return false;
151 // Ignore it if it's not in our set of requested types.
152 if (!requested_types.Has(type))
153 return false;
155 if (entry.GetIsDel() && !entry.GetId().ServerKnows()) {
156 // New clients (following the resolution of crbug.com/125381) should not
157 // create such items. Old clients may have left some in the database
158 // (crbug.com/132905), but we should now be cleaning them on startup.
159 NOTREACHED() << "Found deleted and unsynced local item: " << entry;
160 return false;
163 // Extra validity checks.
164 syncable::Id id = entry.GetId();
165 if (id == entry.GetParentId()) {
166 CHECK(id.IsRoot()) << "Non-root item is self parenting." << entry;
167 // If the root becomes unsynced it can cause us problems.
168 NOTREACHED() << "Root item became unsynced " << entry;
169 return false;
172 if (entry.IsRoot()) {
173 NOTREACHED() << "Permanent item became unsynced " << entry;
174 return false;
177 if (HasAttachmentNotOnServer(entry)) {
178 // This entry is not ready to be sent to the server because it has one or
179 // more attachments that have not yet been uploaded to the server. The idea
180 // here is avoid propagating an entry with dangling attachment references.
181 return false;
184 DVLOG(2) << "Entry is ready for commit: " << entry;
185 return true;
188 // Filters |unsynced_handles| to remove all entries that do not belong to the
189 // specified |requested_types|, or are not eligible for a commit at this time.
190 void FilterUnreadyEntries(
191 syncable::BaseTransaction* trans,
192 ModelTypeSet requested_types,
193 ModelTypeSet encrypted_types,
194 bool passphrase_missing,
195 const syncable::Directory::Metahandles& unsynced_handles,
196 std::set<int64>* ready_unsynced_set) {
197 for (syncable::Directory::Metahandles::const_iterator iter =
198 unsynced_handles.begin(); iter != unsynced_handles.end(); ++iter) {
199 syncable::Entry entry(trans, syncable::GET_BY_HANDLE, *iter);
200 // TODO(maniscalco): While we check if entry is ready to be committed, we
201 // also need to check that all of its ancestors (parents, transitive) are
202 // ready to be committed. Once attachments can prevent an entry from being
203 // committable, this method must ensure all ancestors are ready for commit
204 // (bug 356273).
205 if (IsEntryReadyForCommit(requested_types,
206 encrypted_types,
207 passphrase_missing,
208 entry)) {
209 ready_unsynced_set->insert(*iter);
214 // This class helps to implement OrderCommitIds(). Its members track the
215 // progress of a traversal while its methods extend it. It can return early if
216 // the traversal reaches the desired size before the full traversal is complete.
217 class Traversal {
218 public:
219 Traversal(
220 syncable::BaseTransaction* trans,
221 int64 max_entries,
222 syncable::Directory::Metahandles* out);
223 ~Traversal();
225 // First step of traversal building. Adds non-deleted items in order.
226 void AddCreatesAndMoves(const std::set<int64>& ready_unsynced_set);
228 // Second step of traverals building. Appends deleted items.
229 void AddDeletes(const std::set<int64>& ready_unsynced_set);
231 private:
232 // The following functions do not modify the traversal directly. They return
233 // their results in the |result| vector instead.
234 bool AddUncommittedParentsAndTheirPredecessors(
235 const std::set<int64>& ready_unsynced_set,
236 const syncable::Entry& item,
237 syncable::Directory::Metahandles* result) const;
239 void TryAddItem(const std::set<int64>& ready_unsynced_set,
240 const syncable::Entry& item,
241 syncable::Directory::Metahandles* result) const;
243 void AddItemThenPredecessors(
244 const std::set<int64>& ready_unsynced_set,
245 const syncable::Entry& item,
246 syncable::Directory::Metahandles* result) const;
248 void AddPredecessorsThenItem(
249 const std::set<int64>& ready_unsynced_set,
250 const syncable::Entry& item,
251 syncable::Directory::Metahandles* result) const;
253 // Returns true if we've collected enough items.
254 bool IsFull() const;
256 // Returns true if the specified handle is already in the traversal.
257 bool HaveItem(int64 handle) const;
259 // Adds the specified handles to the traversal.
260 void AppendManyToTraversal(const syncable::Directory::Metahandles& handles);
262 // Adds the specifed handle to the traversal.
263 void AppendToTraversal(int64 handle);
265 syncable::Directory::Metahandles* out_;
266 std::set<int64> added_handles_;
267 const size_t max_entries_;
268 syncable::BaseTransaction* trans_;
270 DISALLOW_COPY_AND_ASSIGN(Traversal);
273 Traversal::Traversal(
274 syncable::BaseTransaction* trans,
275 int64 max_entries,
276 syncable::Directory::Metahandles* out)
277 : out_(out),
278 max_entries_(max_entries),
279 trans_(trans) { }
281 Traversal::~Traversal() {}
283 bool Traversal::AddUncommittedParentsAndTheirPredecessors(
284 const std::set<int64>& ready_unsynced_set,
285 const syncable::Entry& item,
286 syncable::Directory::Metahandles* result) const {
287 syncable::Directory::Metahandles dependencies;
288 syncable::Id parent_id = item.GetParentId();
290 // Climb the tree adding entries leaf -> root.
291 while (!parent_id.ServerKnows()) {
292 syncable::Entry parent(trans_, syncable::GET_BY_ID, parent_id);
293 CHECK(parent.good()) << "Bad user-only parent in item path.";
294 int64 handle = parent.GetMetahandle();
295 if (HaveItem(handle)) {
296 // We've already added this parent (and therefore all of its parents).
297 // We can return early.
298 break;
300 if (IsEntryInConflict(parent)) {
301 // We ignore all entries that are children of a conflicing item. Return
302 // false immediately to forget the traversal we've built up so far.
303 DVLOG(1) << "Parent was in conflict, omitting " << item;
304 return false;
306 AddItemThenPredecessors(ready_unsynced_set,
307 parent,
308 &dependencies);
309 parent_id = parent.GetParentId();
312 // Reverse what we added to get the correct order.
313 result->insert(result->end(), dependencies.rbegin(), dependencies.rend());
314 return true;
317 // Adds the given item to the list if it is unsynced and ready for commit.
318 void Traversal::TryAddItem(const std::set<int64>& ready_unsynced_set,
319 const syncable::Entry& item,
320 syncable::Directory::Metahandles* result) const {
321 DCHECK(item.GetIsUnsynced());
322 int64 item_handle = item.GetMetahandle();
323 if (ready_unsynced_set.count(item_handle) != 0) {
324 result->push_back(item_handle);
328 // Adds the given item, and all its unsynced predecessors. The traversal will
329 // be cut short if any item along the traversal is not IS_UNSYNCED, or if we
330 // detect that this area of the tree has already been traversed. Items that are
331 // not 'ready' for commit (see IsEntryReadyForCommit()) will not be added to the
332 // list, though they will not stop the traversal.
333 void Traversal::AddItemThenPredecessors(
334 const std::set<int64>& ready_unsynced_set,
335 const syncable::Entry& item,
336 syncable::Directory::Metahandles* result) const {
337 int64 item_handle = item.GetMetahandle();
338 if (HaveItem(item_handle)) {
339 // We've already added this item to the commit set, and so must have
340 // already added the predecessors as well.
341 return;
343 TryAddItem(ready_unsynced_set, item, result);
344 if (item.GetIsDel())
345 return; // Deleted items have no predecessors.
347 syncable::Id prev_id = item.GetPredecessorId();
348 while (!prev_id.IsRoot()) {
349 syncable::Entry prev(trans_, syncable::GET_BY_ID, prev_id);
350 CHECK(prev.good()) << "Bad id when walking predecessors.";
351 if (!prev.GetIsUnsynced()) {
352 // We're interested in "runs" of unsynced items. This item breaks
353 // the streak, so we stop traversing.
354 return;
356 int64 handle = prev.GetMetahandle();
357 if (HaveItem(handle)) {
358 // We've already added this item to the commit set, and so must have
359 // already added the predecessors as well.
360 return;
362 TryAddItem(ready_unsynced_set, prev, result);
363 prev_id = prev.GetPredecessorId();
367 // Same as AddItemThenPredecessor, but the traversal order will be reversed.
368 void Traversal::AddPredecessorsThenItem(
369 const std::set<int64>& ready_unsynced_set,
370 const syncable::Entry& item,
371 syncable::Directory::Metahandles* result) const {
372 syncable::Directory::Metahandles dependencies;
373 AddItemThenPredecessors(ready_unsynced_set, item, &dependencies);
375 // Reverse what we added to get the correct order.
376 result->insert(result->end(), dependencies.rbegin(), dependencies.rend());
379 bool Traversal::IsFull() const {
380 return out_->size() >= max_entries_;
383 bool Traversal::HaveItem(int64 handle) const {
384 return added_handles_.find(handle) != added_handles_.end();
387 void Traversal::AppendManyToTraversal(
388 const syncable::Directory::Metahandles& handles) {
389 out_->insert(out_->end(), handles.begin(), handles.end());
390 added_handles_.insert(handles.begin(), handles.end());
393 void Traversal::AppendToTraversal(int64 metahandle) {
394 out_->push_back(metahandle);
395 added_handles_.insert(metahandle);
398 void Traversal::AddCreatesAndMoves(
399 const std::set<int64>& ready_unsynced_set) {
400 // Add moves and creates, and prepend their uncommitted parents.
401 for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin();
402 !IsFull() && iter != ready_unsynced_set.end(); ++iter) {
403 int64 metahandle = *iter;
404 if (HaveItem(metahandle))
405 continue;
407 syncable::Entry entry(trans_,
408 syncable::GET_BY_HANDLE,
409 metahandle);
410 if (!entry.GetIsDel()) {
411 // We only commit an item + its dependencies if it and all its
412 // dependencies are not in conflict.
413 syncable::Directory::Metahandles item_dependencies;
414 if (AddUncommittedParentsAndTheirPredecessors(
415 ready_unsynced_set,
416 entry,
417 &item_dependencies)) {
418 AddPredecessorsThenItem(ready_unsynced_set,
419 entry,
420 &item_dependencies);
421 AppendManyToTraversal(item_dependencies);
426 // It's possible that we overcommitted while trying to expand dependent
427 // items. If so, truncate the set down to the allowed size.
428 if (out_->size() > max_entries_)
429 out_->resize(max_entries_);
432 void Traversal::AddDeletes(
433 const std::set<int64>& ready_unsynced_set) {
434 set<syncable::Id> legal_delete_parents;
436 for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin();
437 !IsFull() && iter != ready_unsynced_set.end(); ++iter) {
438 int64 metahandle = *iter;
439 if (HaveItem(metahandle))
440 continue;
442 syncable::Entry entry(trans_, syncable::GET_BY_HANDLE,
443 metahandle);
445 if (entry.GetIsDel()) {
446 syncable::Entry parent(trans_, syncable::GET_BY_ID,
447 entry.GetParentId());
448 // If the parent is deleted and unsynced, then any children of that
449 // parent don't need to be added to the delete queue.
451 // Note: the parent could be synced if there was an update deleting a
452 // folder when we had a deleted all items in it.
453 // We may get more updates, or we may want to delete the entry.
454 if (parent.good() && parent.GetIsDel() && parent.GetIsUnsynced()) {
455 // However, if an entry is moved, these rules can apply differently.
457 // If the entry was moved, then the destination parent was deleted,
458 // then we'll miss it in the roll up. We have to add it in manually.
459 // TODO(chron): Unit test for move / delete cases:
460 // Case 1: Locally moved, then parent deleted
461 // Case 2: Server moved, then locally issue recursive delete.
462 if (entry.GetId().ServerKnows() &&
463 entry.GetParentId() != entry.GetServerParentId()) {
464 DVLOG(1) << "Inserting moved and deleted entry, will be missed by "
465 << "delete roll." << entry.GetId();
467 AppendToTraversal(metahandle);
470 // Skip this entry since it's a child of a parent that will be
471 // deleted. The server will unroll the delete and delete the
472 // child as well.
473 continue;
476 legal_delete_parents.insert(entry.GetParentId());
480 // We could store all the potential entries with a particular parent during
481 // the above scan, but instead we rescan here. This is less efficient, but
482 // we're dropping memory alloc/dealloc in favor of linear scans of recently
483 // examined entries.
485 // Scan through the UnsyncedMetaHandles again. If we have a deleted
486 // entry, then check if the parent is in legal_delete_parents.
488 // Parent being in legal_delete_parents means for the child:
489 // a recursive delete is not currently happening (no recent deletes in same
490 // folder)
491 // parent did expect at least one old deleted child
492 // parent was not deleted
493 for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin();
494 !IsFull() && iter != ready_unsynced_set.end(); ++iter) {
495 int64 metahandle = *iter;
496 if (HaveItem(metahandle))
497 continue;
498 syncable::Entry entry(trans_, syncable::GET_BY_HANDLE, metahandle);
499 if (entry.GetIsDel()) {
500 syncable::Id parent_id = entry.GetParentId();
501 if (legal_delete_parents.count(parent_id)) {
502 AppendToTraversal(metahandle);
508 void OrderCommitIds(
509 syncable::BaseTransaction* trans,
510 size_t max_entries,
511 const std::set<int64>& ready_unsynced_set,
512 syncable::Directory::Metahandles* out) {
513 // Commits follow these rules:
514 // 1. Moves or creates are preceded by needed folder creates, from
515 // root to leaf. For folders whose contents are ordered, moves
516 // and creates appear in order.
517 // 2. Moves/Creates before deletes.
518 // 3. Deletes, collapsed.
519 // We commit deleted moves under deleted items as moves when collapsing
520 // delete trees.
522 Traversal traversal(trans, max_entries, out);
524 // Add moves and creates, and prepend their uncommitted parents.
525 traversal.AddCreatesAndMoves(ready_unsynced_set);
527 // Add all deletes.
528 traversal.AddDeletes(ready_unsynced_set);
531 } // namespace
533 } // namespace syncer