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