Home | History | Annotate | Download | only in engine
      1 // Copyright (c) 2006-2009 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/change_reorder_buffer.h"
      6 
      7 #include <limits>
      8 #include <queue>
      9 #include <set>
     10 #include <utility>  // for pair<>
     11 #include <vector>
     12 
     13 #include "chrome/browser/sync/syncable/syncable.h"
     14 
     15 using std::numeric_limits;
     16 using std::pair;
     17 using std::queue;
     18 using std::set;
     19 using std::vector;
     20 
     21 namespace sync_api {
     22 
     23 // Traversal provides a way to collect a set of nodes from the syncable
     24 // directory structure and then traverse them, along with any intermediate
     25 // nodes, in a top-down fashion, starting from a single common ancestor.  A
     26 // Traversal starts out empty and is grown by means of the ExpandToInclude
     27 // method.  Once constructed, the top(), begin_children(), and end_children()
     28 // methods can be used to explore the nodes in root-to-leaf order.
     29 class ChangeReorderBuffer::Traversal {
     30  public:
     31   typedef pair<int64, int64> ParentChildLink;
     32   typedef set<ParentChildLink> LinkSet;
     33 
     34   Traversal() : top_(kInvalidId) { }
     35 
     36   // Expand the traversal so that it includes the node indicated by
     37   // |child_handle|.
     38   void ExpandToInclude(syncable::BaseTransaction* trans,
     39                        int64 child_handle) {
     40     // If |top_| is invalid, this is the first insertion -- easy.
     41     if (top_ == kInvalidId) {
     42       top_ = child_handle;
     43       return;
     44     }
     45 
     46     int64 node_to_include = child_handle;
     47     while (node_to_include != kInvalidId && node_to_include != top_) {
     48       int64 node_parent = 0;
     49 
     50       syncable::Entry node(trans, syncable::GET_BY_HANDLE, node_to_include);
     51       CHECK(node.good());
     52       if (node.Get(syncable::ID).IsRoot()) {
     53         // If we've hit the root, and the root isn't already in the tree
     54         // (it would have to be |top_| if it were), start a new expansion
     55         // upwards from |top_| to unite the original traversal with the
     56         // path we just added that goes from |child_handle| to the root.
     57         node_to_include = top_;
     58         top_ = node.Get(syncable::META_HANDLE);
     59       } else {
     60         // Otherwise, get the parent ID so that we can add a ParentChildLink.
     61         syncable::Entry parent(trans, syncable::GET_BY_ID,
     62                              node.Get(syncable::PARENT_ID));
     63         CHECK(parent.good());
     64         node_parent = parent.Get(syncable::META_HANDLE);
     65 
     66         ParentChildLink link(node_parent, node_to_include);
     67 
     68         // If the link exists in the LinkSet |links_|, we don't need to search
     69         // any higher; we are done.
     70         if (links_.find(link) != links_.end())
     71           return;
     72 
     73         // Otherwise, extend |links_|, and repeat on the parent.
     74         links_.insert(link);
     75         node_to_include = node_parent;
     76       }
     77     }
     78   }
     79 
     80   // Return the top node of the traversal.  Use this as a starting point
     81   // for walking the tree.
     82   int64 top() const { return top_; }
     83 
     84   // Return an iterator corresponding to the first child (in the traversal)
     85   // of the node specified by |parent_id|.  Iterate this return value until
     86   // it is equal to the value returned by end_children(parent_id).  The
     87   // enumeration thus provided is unordered.
     88   LinkSet::const_iterator begin_children(int64 parent_id) const {
     89     return links_.upper_bound(
     90         ParentChildLink(parent_id, numeric_limits<int64>::min()));
     91   }
     92 
     93   // Return an iterator corresponding to the last child in the traversal
     94   // of the node specified by |parent_id|.
     95   LinkSet::const_iterator end_children(int64 parent_id) const {
     96     return begin_children(parent_id + 1);
     97   }
     98 
     99  private:
    100   // The topmost point in the directory hierarchy that is in the traversal,
    101   // and thus the first node to be traversed.  If the traversal is empty,
    102   // this is kInvalidId.  If the traversal contains exactly one member, |top_|
    103   // will be the solitary member, and |links_| will be empty.
    104   int64 top_;
    105   // A set of single-level links that compose the traversal below |top_|.  The
    106   // (parent, child) ordering of values enables efficient lookup of children
    107   // given the parent handle, which is used for top-down traversal.  |links_|
    108   // is expected to be connected -- every node that appears as a parent in a
    109   // link must either appear as a child of another link, or else be the
    110   // topmost node, |top_|.
    111   LinkSet links_;
    112 
    113   DISALLOW_COPY_AND_ASSIGN(Traversal);
    114 };
    115 
    116 ChangeReorderBuffer::ChangeReorderBuffer() {
    117 }
    118 
    119 ChangeReorderBuffer::~ChangeReorderBuffer() {
    120 }
    121 
    122 void ChangeReorderBuffer::GetAllChangesInTreeOrder(
    123     const BaseTransaction* sync_trans,
    124     vector<ChangeRecord>* changelist) {
    125   syncable::BaseTransaction* trans = sync_trans->GetWrappedTrans();
    126 
    127   // Step 1: Iterate through the operations, doing three things:
    128   // (a) Push deleted items straight into the |changelist|.
    129   // (b) Construct a traversal spanning all non-deleted items.
    130   // (c) Construct a set of all parent nodes of any position changes.
    131   set<int64> parents_of_position_changes;
    132   Traversal traversal;
    133 
    134   OperationMap::const_iterator i;
    135   for (i = operations_.begin(); i != operations_.end(); ++i) {
    136     if (i->second == OP_DELETE) {
    137       ChangeRecord record;
    138       record.id = i->first;
    139       record.action = ChangeRecord::ACTION_DELETE;
    140       if (specifics_.find(record.id) != specifics_.end())
    141         record.specifics = specifics_[record.id];
    142       if (extra_data_.find(record.id) != extra_data_.end())
    143         record.extra = extra_data_[record.id];
    144       changelist->push_back(record);
    145     } else {
    146       traversal.ExpandToInclude(trans, i->first);
    147       if (i->second == OP_ADD ||
    148           i->second == OP_UPDATE_POSITION_AND_PROPERTIES) {
    149         ReadNode node(sync_trans);
    150         CHECK(node.InitByIdLookup(i->first));
    151 
    152         // We only care about parents of entry's with position-sensitive models.
    153         if (node.GetEntry()->ShouldMaintainPosition())
    154           parents_of_position_changes.insert(node.GetParentId());
    155       }
    156     }
    157   }
    158 
    159   // Step 2: Breadth-first expansion of the traversal, enumerating children in
    160   // the syncable sibling order if there were any position updates.
    161   queue<int64> to_visit;
    162   to_visit.push(traversal.top());
    163   while (!to_visit.empty()) {
    164     int64 next = to_visit.front();
    165     to_visit.pop();
    166 
    167     // If the node has an associated action, output a change record.
    168     i = operations_.find(next);
    169     if (i != operations_.end()) {
    170       ChangeRecord record;
    171       record.id = next;
    172       if (i->second == OP_ADD)
    173         record.action = ChangeRecord::ACTION_ADD;
    174       else
    175         record.action = ChangeRecord::ACTION_UPDATE;
    176       if (specifics_.find(record.id) != specifics_.end())
    177         record.specifics = specifics_[record.id];
    178       if (extra_data_.find(record.id) != extra_data_.end())
    179         record.extra = extra_data_[record.id];
    180       changelist->push_back(record);
    181     }
    182 
    183     // Now add the children of |next| to |to_visit|.
    184     if (parents_of_position_changes.find(next) ==
    185         parents_of_position_changes.end()) {
    186         // No order changes on this parent -- traverse only the nodes listed
    187         // in the traversal (and not in sibling order).
    188         Traversal::LinkSet::const_iterator j = traversal.begin_children(next);
    189         Traversal::LinkSet::const_iterator end = traversal.end_children(next);
    190         for (; j != end; ++j) {
    191           CHECK(j->first == next);
    192           to_visit.push(j->second);
    193         }
    194     } else {
    195       // There were ordering changes on the children of this parent, so
    196       // enumerate all the children in the sibling order.
    197       syncable::Entry parent(trans, syncable::GET_BY_HANDLE, next);
    198       syncable::Id id = trans->directory()->
    199           GetFirstChildId(trans, parent.Get(syncable::ID));
    200       while (!id.IsRoot()) {
    201         syncable::Entry child(trans, syncable::GET_BY_ID, id);
    202         CHECK(child.good());
    203         int64 handle = child.Get(syncable::META_HANDLE);
    204         to_visit.push(handle);
    205         // If there is no operation on this child node, record it as as an
    206         // update, so that the listener gets notified of all nodes in the new
    207         // ordering.
    208         if (operations_.find(handle) == operations_.end())
    209           operations_[handle] = OP_UPDATE_POSITION_AND_PROPERTIES;
    210         id = child.Get(syncable::NEXT_ID);
    211       }
    212     }
    213   }
    214 }
    215 
    216 }  // namespace sync_api
    217