Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / sync / engine / get_commit_ids.cc
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 <set>
8 #include <vector>
9
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"
19
20 using std::set;
21 using std::vector;
22
23 namespace syncer {
24
25 namespace {
26
27 // Forward-declare some helper functions.  This gives us more options for
28 // ordering the function defintions within this file.
29
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);
39
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|.
43 //
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);
50
51 }  // namespace
52
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();
59
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);
65
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();
72   };
73
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);
84
85   OrderCommitIds(trans, max_entries, ready_unsynced_set, out);
86
87   for (size_t i = 0; i < out->size(); i++) {
88     DVLOG(1) << "Debug commit batch result:" << (*out)[i];
89   }
90 }
91
92 namespace {
93
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;
104   }
105   return false;
106 }
107
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;
116     }
117   }
118   return false;
119 }
120
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;
135
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;
149   }
150
151   // Ignore it if it's not in our set of requested types.
152   if (!requested_types.Has(type))
153     return false;
154
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;
161   }
162
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;
170   }
171
172   if (entry.IsRoot()) {
173     NOTREACHED() << "Permanent item became unsynced " << entry;
174     return false;
175   }
176
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;
182   }
183
184   DVLOG(2) << "Entry is ready for commit: " << entry;
185   return true;
186 }
187
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);
210     }
211   }
212 }
213
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();
224
225   // First step of traversal building.  Adds non-deleted items in order.
226   void AddCreatesAndMoves(const std::set<int64>& ready_unsynced_set);
227
228   // Second step of traverals building.  Appends deleted items.
229   void AddDeletes(const std::set<int64>& ready_unsynced_set);
230
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;
238
239   void TryAddItem(const std::set<int64>& ready_unsynced_set,
240                   const syncable::Entry& item,
241                   syncable::Directory::Metahandles* result) const;
242
243   void AddItemThenPredecessors(
244       const std::set<int64>& ready_unsynced_set,
245       const syncable::Entry& item,
246       syncable::Directory::Metahandles* result) const;
247
248   void AddPredecessorsThenItem(
249       const std::set<int64>& ready_unsynced_set,
250       const syncable::Entry& item,
251       syncable::Directory::Metahandles* result) const;
252
253   // Returns true if we've collected enough items.
254   bool IsFull() const;
255
256   // Returns true if the specified handle is already in the traversal.
257   bool HaveItem(int64 handle) const;
258
259   // Adds the specified handles to the traversal.
260   void AppendManyToTraversal(const syncable::Directory::Metahandles& handles);
261
262   // Adds the specifed handle to the traversal.
263   void AppendToTraversal(int64 handle);
264
265   syncable::Directory::Metahandles* out_;
266   std::set<int64> added_handles_;
267   const size_t max_entries_;
268   syncable::BaseTransaction* trans_;
269
270   DISALLOW_COPY_AND_ASSIGN(Traversal);
271 };
272
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) { }
280
281 Traversal::~Traversal() {}
282
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();
289
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;
299     }
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;
305     }
306     AddItemThenPredecessors(ready_unsynced_set,
307                             parent,
308                             &dependencies);
309     parent_id = parent.GetParentId();
310   }
311
312   // Reverse what we added to get the correct order.
313   result->insert(result->end(), dependencies.rbegin(), dependencies.rend());
314   return true;
315 }
316
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);
325   }
326 }
327
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;
342   }
343   TryAddItem(ready_unsynced_set, item, result);
344   if (item.GetIsDel())
345     return;  // Deleted items have no predecessors.
346
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;
355     }
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;
361     }
362     TryAddItem(ready_unsynced_set, prev, result);
363     prev_id = prev.GetPredecessorId();
364   }
365 }
366
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);
374
375   // Reverse what we added to get the correct order.
376   result->insert(result->end(), dependencies.rbegin(), dependencies.rend());
377 }
378
379 bool Traversal::IsFull() const {
380   return out_->size() >= max_entries_;
381 }
382
383 bool Traversal::HaveItem(int64 handle) const {
384   return added_handles_.find(handle) != added_handles_.end();
385 }
386
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());
391 }
392
393 void Traversal::AppendToTraversal(int64 metahandle) {
394   out_->push_back(metahandle);
395   added_handles_.insert(metahandle);
396 }
397
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;
406
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);
422       }
423     }
424   }
425
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_);
430 }
431
432 void Traversal::AddDeletes(
433     const std::set<int64>& ready_unsynced_set) {
434   set<syncable::Id> legal_delete_parents;
435
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;
441
442     syncable::Entry entry(trans_, syncable::GET_BY_HANDLE,
443                           metahandle);
444
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.
450       //
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.
456         //
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();
466
467           AppendToTraversal(metahandle);
468         }
469
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;
474       }
475
476       legal_delete_parents.insert(entry.GetParentId());
477     }
478   }
479
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.
484   //
485   // Scan through the UnsyncedMetaHandles again. If we have a deleted
486   // entry, then check if the parent is in legal_delete_parents.
487   //
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);
503       }
504     }
505   }
506 }
507
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.
521
522   Traversal traversal(trans, max_entries, out);
523
524   // Add moves and creates, and prepend their uncommitted parents.
525   traversal.AddCreatesAndMoves(ready_unsynced_set);
526
527   // Add all deletes.
528   traversal.AddDeletes(ready_unsynced_set);
529 }
530
531 }  // namespace
532
533 }  // namespace syncer