|  | // Copyright 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "sync/internal_api/change_reorder_buffer.h" | 
|  |  | 
|  | #include <limits> | 
|  | #include <queue> | 
|  | #include <set> | 
|  | #include <utility>  // for pair<> | 
|  |  | 
|  | #include "sync/internal_api/public/base_node.h" | 
|  | #include "sync/internal_api/public/base_transaction.h" | 
|  | #include "sync/syncable/entry.h" | 
|  | #include "sync/syncable/syncable_base_transaction.h" | 
|  |  | 
|  | using std::numeric_limits; | 
|  | using std::pair; | 
|  | using std::queue; | 
|  | using std::set; | 
|  |  | 
|  | namespace syncer { | 
|  |  | 
|  | // Traversal provides a way to collect a set of nodes from the syncable | 
|  | // directory structure and then traverse them, along with any intermediate | 
|  | // nodes, in a top-down fashion, starting from a single common ancestor.  A | 
|  | // Traversal starts out empty and is grown by means of the ExpandToInclude | 
|  | // method.  Once constructed, the top(), begin_children(), and end_children() | 
|  | // methods can be used to explore the nodes in root-to-leaf order. | 
|  | class ChangeReorderBuffer::Traversal { | 
|  | public: | 
|  | typedef pair<int64, int64> ParentChildLink; | 
|  | typedef set<ParentChildLink> LinkSet; | 
|  |  | 
|  | Traversal() : top_(kInvalidId) { } | 
|  |  | 
|  | // Expand the traversal so that it includes the node indicated by | 
|  | // |child_handle|. | 
|  | void ExpandToInclude(syncable::BaseTransaction* trans, | 
|  | int64 child_handle) { | 
|  | // If |top_| is invalid, this is the first insertion -- easy. | 
|  | if (top_ == kInvalidId) { | 
|  | top_ = child_handle; | 
|  | return; | 
|  | } | 
|  |  | 
|  | int64 node_to_include = child_handle; | 
|  | while (node_to_include != kInvalidId && node_to_include != top_) { | 
|  | int64 node_parent = 0; | 
|  |  | 
|  | syncable::Entry node(trans, syncable::GET_BY_HANDLE, node_to_include); | 
|  | CHECK(node.good()); | 
|  | if (node.GetId().IsRoot()) { | 
|  | // If we've hit the root, and the root isn't already in the tree | 
|  | // (it would have to be |top_| if it were), start a new expansion | 
|  | // upwards from |top_| to unite the original traversal with the | 
|  | // path we just added that goes from |child_handle| to the root. | 
|  | node_to_include = top_; | 
|  | top_ = node.GetMetahandle(); | 
|  | } else { | 
|  | // Otherwise, get the parent ID so that we can add a ParentChildLink. | 
|  |  | 
|  | // Treat nodes with unset parent ID as if they were linked to the root. | 
|  | // That is a valid way to traverse the tree because all hierarchical | 
|  | // datatypes must have a valid parent ID and the ones with unset parent | 
|  | // ID have flat hierarchy where the order doesn't matter. | 
|  | const syncable::Id& parent_id = !node.GetParentId().IsNull() | 
|  | ? node.GetParentId() | 
|  | : syncable::Id::GetRoot(); | 
|  | syncable::Entry parent(trans, syncable::GET_BY_ID, parent_id); | 
|  | CHECK(parent.good()); | 
|  | node_parent = parent.GetMetahandle(); | 
|  |  | 
|  | ParentChildLink link(node_parent, node_to_include); | 
|  |  | 
|  | // If the link exists in the LinkSet |links_|, we don't need to search | 
|  | // any higher; we are done. | 
|  | if (links_.find(link) != links_.end()) | 
|  | return; | 
|  |  | 
|  | // Otherwise, extend |links_|, and repeat on the parent. | 
|  | links_.insert(link); | 
|  | node_to_include = node_parent; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Return the top node of the traversal.  Use this as a starting point | 
|  | // for walking the tree. | 
|  | int64 top() const { return top_; } | 
|  |  | 
|  | // Return an iterator corresponding to the first child (in the traversal) | 
|  | // of the node specified by |parent_id|.  Iterate this return value until | 
|  | // it is equal to the value returned by end_children(parent_id).  The | 
|  | // enumeration thus provided is unordered. | 
|  | LinkSet::const_iterator begin_children(int64 parent_id) const { | 
|  | return links_.upper_bound( | 
|  | ParentChildLink(parent_id, numeric_limits<int64>::min())); | 
|  | } | 
|  |  | 
|  | // Return an iterator corresponding to the last child in the traversal | 
|  | // of the node specified by |parent_id|. | 
|  | LinkSet::const_iterator end_children(int64 parent_id) const { | 
|  | return begin_children(parent_id + 1); | 
|  | } | 
|  |  | 
|  | private: | 
|  | // The topmost point in the directory hierarchy that is in the traversal, | 
|  | // and thus the first node to be traversed.  If the traversal is empty, | 
|  | // this is kInvalidId.  If the traversal contains exactly one member, |top_| | 
|  | // will be the solitary member, and |links_| will be empty. | 
|  | int64 top_; | 
|  | // A set of single-level links that compose the traversal below |top_|.  The | 
|  | // (parent, child) ordering of values enables efficient lookup of children | 
|  | // given the parent handle, which is used for top-down traversal.  |links_| | 
|  | // is expected to be connected -- every node that appears as a parent in a | 
|  | // link must either appear as a child of another link, or else be the | 
|  | // topmost node, |top_|. | 
|  | LinkSet links_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(Traversal); | 
|  | }; | 
|  |  | 
|  | ChangeReorderBuffer::ChangeReorderBuffer() { | 
|  | } | 
|  |  | 
|  | ChangeReorderBuffer::~ChangeReorderBuffer() { | 
|  | } | 
|  |  | 
|  | void ChangeReorderBuffer::PushAddedItem(int64 id) { | 
|  | operations_[id] = ChangeRecord::ACTION_ADD; | 
|  | } | 
|  |  | 
|  | void ChangeReorderBuffer::PushDeletedItem(int64 id) { | 
|  | operations_[id] = ChangeRecord::ACTION_DELETE; | 
|  | } | 
|  |  | 
|  | void ChangeReorderBuffer::PushUpdatedItem(int64 id) { | 
|  | operations_[id] = ChangeRecord::ACTION_UPDATE; | 
|  | } | 
|  |  | 
|  | void ChangeReorderBuffer::SetExtraDataForId( | 
|  | int64 id, | 
|  | ExtraPasswordChangeRecordData* extra) { | 
|  | extra_data_[id] = make_linked_ptr<ExtraPasswordChangeRecordData>(extra); | 
|  | } | 
|  |  | 
|  | void ChangeReorderBuffer::SetSpecificsForId( | 
|  | int64 id, | 
|  | const sync_pb::EntitySpecifics& specifics) { | 
|  | specifics_[id] = specifics; | 
|  | } | 
|  |  | 
|  | void ChangeReorderBuffer::Clear() { | 
|  | operations_.clear(); | 
|  | } | 
|  |  | 
|  | bool ChangeReorderBuffer::IsEmpty() const { | 
|  | return operations_.empty(); | 
|  | } | 
|  |  | 
|  | bool ChangeReorderBuffer::GetAllChangesInTreeOrder( | 
|  | const BaseTransaction* sync_trans, | 
|  | ImmutableChangeRecordList* changes) { | 
|  | syncable::BaseTransaction* trans = sync_trans->GetWrappedTrans(); | 
|  |  | 
|  | // Step 1: Iterate through the operations, doing three things: | 
|  | // (a) Push deleted items straight into the |changelist|. | 
|  | // (b) Construct a traversal spanning all non-deleted items. | 
|  | // (c) Construct a set of all parent nodes of any position changes. | 
|  | Traversal traversal; | 
|  |  | 
|  | ChangeRecordList changelist; | 
|  |  | 
|  | OperationMap::const_iterator i; | 
|  | for (i = operations_.begin(); i != operations_.end(); ++i) { | 
|  | if (i->second == ChangeRecord::ACTION_DELETE) { | 
|  | ChangeRecord record; | 
|  | record.id = i->first; | 
|  | record.action = i->second; | 
|  | if (specifics_.find(record.id) != specifics_.end()) | 
|  | record.specifics = specifics_[record.id]; | 
|  | if (extra_data_.find(record.id) != extra_data_.end()) | 
|  | record.extra = extra_data_[record.id]; | 
|  | changelist.push_back(record); | 
|  | } else { | 
|  | traversal.ExpandToInclude(trans, i->first); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Step 2: Breadth-first expansion of the traversal. | 
|  | queue<int64> to_visit; | 
|  | to_visit.push(traversal.top()); | 
|  | while (!to_visit.empty()) { | 
|  | int64 next = to_visit.front(); | 
|  | to_visit.pop(); | 
|  |  | 
|  | // If the node has an associated action, output a change record. | 
|  | i = operations_.find(next); | 
|  | if (i != operations_.end()) { | 
|  | ChangeRecord record; | 
|  | record.id = next; | 
|  | record.action = i->second; | 
|  | if (specifics_.find(record.id) != specifics_.end()) | 
|  | record.specifics = specifics_[record.id]; | 
|  | if (extra_data_.find(record.id) != extra_data_.end()) | 
|  | record.extra = extra_data_[record.id]; | 
|  | changelist.push_back(record); | 
|  | } | 
|  |  | 
|  | // Now add the children of |next| to |to_visit|. | 
|  | Traversal::LinkSet::const_iterator j = traversal.begin_children(next); | 
|  | Traversal::LinkSet::const_iterator end = traversal.end_children(next); | 
|  | for (; j != end; ++j) { | 
|  | CHECK(j->first == next); | 
|  | to_visit.push(j->second); | 
|  | } | 
|  | } | 
|  |  | 
|  | *changes = ImmutableChangeRecordList(&changelist); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | }  // namespace syncer |