1 // Copyright (c) 2010 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/get_commit_ids_command.h"
6
7 #include <set>
8 #include <utility>
9 #include <vector>
10
11 #include "chrome/browser/sync/engine/syncer_util.h"
12 #include "chrome/browser/sync/syncable/syncable.h"
13
14 using std::set;
15 using std::vector;
16
17 namespace browser_sync {
18
19 using sessions::OrderedCommitSet;
20 using sessions::SyncSession;
21 using sessions::StatusController;
22
GetCommitIdsCommand(int commit_batch_size)23 GetCommitIdsCommand::GetCommitIdsCommand(int commit_batch_size)
24 : requested_commit_batch_size_(commit_batch_size) {}
25
~GetCommitIdsCommand()26 GetCommitIdsCommand::~GetCommitIdsCommand() {}
27
ExecuteImpl(SyncSession * session)28 void GetCommitIdsCommand::ExecuteImpl(SyncSession* session) {
29 // Gather the full set of unsynced items and store it in the session. They
30 // are not in the correct order for commit.
31 syncable::Directory::UnsyncedMetaHandles all_unsynced_handles;
32 SyncerUtil::GetUnsyncedEntries(session->write_transaction(),
33 &all_unsynced_handles);
34 StatusController* status = session->status_controller();
35 status->set_unsynced_handles(all_unsynced_handles);
36
37 BuildCommitIds(status->unsynced_handles(), session->write_transaction(),
38 session->routing_info());
39
40 const vector<syncable::Id>& verified_commit_ids =
41 ordered_commit_set_->GetAllCommitIds();
42
43 for (size_t i = 0; i < verified_commit_ids.size(); i++)
44 VLOG(1) << "Debug commit batch result:" << verified_commit_ids[i];
45
46 status->set_commit_set(*ordered_commit_set_.get());
47 }
48
AddUncommittedParentsAndTheirPredecessors(syncable::BaseTransaction * trans,syncable::Id parent_id,const ModelSafeRoutingInfo & routes)49 void GetCommitIdsCommand::AddUncommittedParentsAndTheirPredecessors(
50 syncable::BaseTransaction* trans,
51 syncable::Id parent_id,
52 const ModelSafeRoutingInfo& routes) {
53 using namespace syncable;
54 OrderedCommitSet item_dependencies(routes);
55
56 // Climb the tree adding entries leaf -> root.
57 while (!parent_id.ServerKnows()) {
58 Entry parent(trans, GET_BY_ID, parent_id);
59 CHECK(parent.good()) << "Bad user-only parent in item path.";
60 int64 handle = parent.Get(META_HANDLE);
61 if (ordered_commit_set_->HaveCommitItem(handle) ||
62 item_dependencies.HaveCommitItem(handle)) {
63 break;
64 }
65 if (!AddItemThenPredecessors(trans, &parent, IS_UNSYNCED,
66 &item_dependencies)) {
67 break; // Parent was already present in the set.
68 }
69 parent_id = parent.Get(PARENT_ID);
70 }
71
72 // Reverse what we added to get the correct order.
73 ordered_commit_set_->AppendReverse(item_dependencies);
74 }
75
AddItem(syncable::Entry * item,OrderedCommitSet * result)76 bool GetCommitIdsCommand::AddItem(syncable::Entry* item,
77 OrderedCommitSet* result) {
78 int64 item_handle = item->Get(syncable::META_HANDLE);
79 if (result->HaveCommitItem(item_handle) ||
80 ordered_commit_set_->HaveCommitItem(item_handle)) {
81 return false;
82 }
83 result->AddCommitItem(item_handle, item->Get(syncable::ID),
84 item->GetModelType());
85 return true;
86 }
87
AddItemThenPredecessors(syncable::BaseTransaction * trans,syncable::Entry * item,syncable::IndexedBitField inclusion_filter,OrderedCommitSet * result)88 bool GetCommitIdsCommand::AddItemThenPredecessors(
89 syncable::BaseTransaction* trans,
90 syncable::Entry* item,
91 syncable::IndexedBitField inclusion_filter,
92 OrderedCommitSet* result) {
93 if (!AddItem(item, result))
94 return false;
95 if (item->Get(syncable::IS_DEL))
96 return true; // Deleted items have no predecessors.
97
98 syncable::Id prev_id = item->Get(syncable::PREV_ID);
99 while (!prev_id.IsRoot()) {
100 syncable::Entry prev(trans, syncable::GET_BY_ID, prev_id);
101 CHECK(prev.good()) << "Bad id when walking predecessors.";
102 if (!prev.Get(inclusion_filter))
103 break;
104 if (!AddItem(&prev, result))
105 break;
106 prev_id = prev.Get(syncable::PREV_ID);
107 }
108 return true;
109 }
110
AddPredecessorsThenItem(syncable::BaseTransaction * trans,syncable::Entry * item,syncable::IndexedBitField inclusion_filter,const ModelSafeRoutingInfo & routes)111 void GetCommitIdsCommand::AddPredecessorsThenItem(
112 syncable::BaseTransaction* trans,
113 syncable::Entry* item,
114 syncable::IndexedBitField inclusion_filter,
115 const ModelSafeRoutingInfo& routes) {
116 OrderedCommitSet item_dependencies(routes);
117 AddItemThenPredecessors(trans, item, inclusion_filter, &item_dependencies);
118
119 // Reverse what we added to get the correct order.
120 ordered_commit_set_->AppendReverse(item_dependencies);
121 }
122
IsCommitBatchFull()123 bool GetCommitIdsCommand::IsCommitBatchFull() {
124 return ordered_commit_set_->Size() >= requested_commit_batch_size_;
125 }
126
AddCreatesAndMoves(const vector<int64> & unsynced_handles,syncable::WriteTransaction * write_transaction,const ModelSafeRoutingInfo & routes)127 void GetCommitIdsCommand::AddCreatesAndMoves(
128 const vector<int64>& unsynced_handles,
129 syncable::WriteTransaction* write_transaction,
130 const ModelSafeRoutingInfo& routes) {
131 // Add moves and creates, and prepend their uncommitted parents.
132 for (CommitMetahandleIterator iterator(unsynced_handles, write_transaction,
133 ordered_commit_set_.get());
134 !IsCommitBatchFull() && iterator.Valid();
135 iterator.Increment()) {
136 int64 metahandle = iterator.Current();
137
138 syncable::Entry entry(write_transaction,
139 syncable::GET_BY_HANDLE,
140 metahandle);
141 if (!entry.Get(syncable::IS_DEL)) {
142 AddUncommittedParentsAndTheirPredecessors(write_transaction,
143 entry.Get(syncable::PARENT_ID), routes);
144 AddPredecessorsThenItem(write_transaction, &entry,
145 syncable::IS_UNSYNCED, routes);
146 }
147 }
148
149 // It's possible that we overcommitted while trying to expand dependent
150 // items. If so, truncate the set down to the allowed size.
151 ordered_commit_set_->Truncate(requested_commit_batch_size_);
152 }
153
AddDeletes(const vector<int64> & unsynced_handles,syncable::WriteTransaction * write_transaction)154 void GetCommitIdsCommand::AddDeletes(const vector<int64>& unsynced_handles,
155 syncable::WriteTransaction* write_transaction) {
156 set<syncable::Id> legal_delete_parents;
157
158 for (CommitMetahandleIterator iterator(unsynced_handles, write_transaction,
159 ordered_commit_set_.get());
160 !IsCommitBatchFull() && iterator.Valid();
161 iterator.Increment()) {
162 int64 metahandle = iterator.Current();
163
164 syncable::Entry entry(write_transaction, syncable::GET_BY_HANDLE,
165 metahandle);
166
167 if (entry.Get(syncable::IS_DEL)) {
168 syncable::Entry parent(write_transaction, syncable::GET_BY_ID,
169 entry.Get(syncable::PARENT_ID));
170 // If the parent is deleted and unsynced, then any children of that
171 // parent don't need to be added to the delete queue.
172 //
173 // Note: the parent could be synced if there was an update deleting a
174 // folder when we had a deleted all items in it.
175 // We may get more updates, or we may want to delete the entry.
176 if (parent.good() &&
177 parent.Get(syncable::IS_DEL) &&
178 parent.Get(syncable::IS_UNSYNCED)) {
179 // However, if an entry is moved, these rules can apply differently.
180 //
181 // If the entry was moved, then the destination parent was deleted,
182 // then we'll miss it in the roll up. We have to add it in manually.
183 // TODO(chron): Unit test for move / delete cases:
184 // Case 1: Locally moved, then parent deleted
185 // Case 2: Server moved, then locally issue recursive delete.
186 if (entry.Get(syncable::ID).ServerKnows() &&
187 entry.Get(syncable::PARENT_ID) !=
188 entry.Get(syncable::SERVER_PARENT_ID)) {
189 VLOG(1) << "Inserting moved and deleted entry, will be missed by "
190 "delete roll." << entry.Get(syncable::ID);
191
192 ordered_commit_set_->AddCommitItem(metahandle,
193 entry.Get(syncable::ID),
194 entry.GetModelType());
195 }
196
197 // Skip this entry since it's a child of a parent that will be
198 // deleted. The server will unroll the delete and delete the
199 // child as well.
200 continue;
201 }
202
203 legal_delete_parents.insert(entry.Get(syncable::PARENT_ID));
204 }
205 }
206
207 // We could store all the potential entries with a particular parent during
208 // the above scan, but instead we rescan here. This is less efficient, but
209 // we're dropping memory alloc/dealloc in favor of linear scans of recently
210 // examined entries.
211 //
212 // Scan through the UnsyncedMetaHandles again. If we have a deleted
213 // entry, then check if the parent is in legal_delete_parents.
214 //
215 // Parent being in legal_delete_parents means for the child:
216 // a recursive delete is not currently happening (no recent deletes in same
217 // folder)
218 // parent did expect at least one old deleted child
219 // parent was not deleted
220
221 for (CommitMetahandleIterator iterator(unsynced_handles, write_transaction,
222 ordered_commit_set_.get());
223 !IsCommitBatchFull() && iterator.Valid();
224 iterator.Increment()) {
225 int64 metahandle = iterator.Current();
226 syncable::MutableEntry entry(write_transaction, syncable::GET_BY_HANDLE,
227 metahandle);
228 if (entry.Get(syncable::IS_DEL)) {
229 syncable::Id parent_id = entry.Get(syncable::PARENT_ID);
230 if (legal_delete_parents.count(parent_id)) {
231 ordered_commit_set_->AddCommitItem(metahandle, entry.Get(syncable::ID),
232 entry.GetModelType());
233 }
234 }
235 }
236 }
237
BuildCommitIds(const vector<int64> & unsynced_handles,syncable::WriteTransaction * write_transaction,const ModelSafeRoutingInfo & routes)238 void GetCommitIdsCommand::BuildCommitIds(const vector<int64>& unsynced_handles,
239 syncable::WriteTransaction* write_transaction,
240 const ModelSafeRoutingInfo& routes) {
241 ordered_commit_set_.reset(new OrderedCommitSet(routes));
242 // Commits follow these rules:
243 // 1. Moves or creates are preceded by needed folder creates, from
244 // root to leaf. For folders whose contents are ordered, moves
245 // and creates appear in order.
246 // 2. Moves/Creates before deletes.
247 // 3. Deletes, collapsed.
248 // We commit deleted moves under deleted items as moves when collapsing
249 // delete trees.
250
251 // Add moves and creates, and prepend their uncommitted parents.
252 AddCreatesAndMoves(unsynced_handles, write_transaction, routes);
253
254 // Add all deletes.
255 AddDeletes(unsynced_handles, write_transaction);
256 }
257
258 } // namespace browser_sync
259