Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(180)

Side by Side Diff: chrome/browser/sync/engine/syncer_util.cc

Issue 194065: Initial commit of sync engine code to browser/sync.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Fixes to gtest include path, reverted syncapi. Created 11 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2006-2008 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/syncer_util.h"
6
7 #include <set>
8 #include <string>
9 #include <vector>
10
11 #include "chrome/browser/sync/engine/conflict_resolver.h"
12 #include "chrome/browser/sync/engine/syncer_proto_util.h"
13 #include "chrome/browser/sync/engine/syncer_session.h"
14 #include "chrome/browser/sync/engine/syncer_types.h"
15 #include "chrome/browser/sync/engine/syncproto.h"
16 #include "chrome/browser/sync/syncable/directory_manager.h"
17 #include "chrome/browser/sync/syncable/syncable.h"
18 #include "chrome/browser/sync/syncable/syncable_changes_version.h"
19 #include "chrome/browser/sync/util/character_set_converters.h"
20 #include "chrome/browser/sync/util/path_helpers.h"
21 #include "chrome/browser/sync/util/sync_types.h"
22
23 using syncable::BASE_VERSION;
24 using syncable::BOOKMARK_FAVICON;
25 using syncable::BOOKMARK_URL;
26 using syncable::Blob;
27 using syncable::CHANGES_VERSION;
28 using syncable::CREATE;
29 using syncable::CREATE_NEW_UPDATE_ITEM;
30 using syncable::CTIME;
31 using syncable::ComparePathNames;
32 using syncable::Directory;
33 using syncable::Entry;
34 using syncable::ExtendedAttributeKey;
35 using syncable::GET_BY_HANDLE;
36 using syncable::GET_BY_ID;
37 using syncable::GET_BY_PARENTID_AND_DBNAME;
38 using syncable::ID;
39 using syncable::IS_BOOKMARK_OBJECT;
40 using syncable::IS_DEL;
41 using syncable::IS_DIR;
42 using syncable::IS_UNAPPLIED_UPDATE;
43 using syncable::IS_UNSYNCED;
44 using syncable::Id;
45 using syncable::META_HANDLE;
46 using syncable::MTIME;
47 using syncable::MutableEntry;
48 using syncable::MutableExtendedAttribute;
49 using syncable::NEXT_ID;
50 using syncable::Name;
51 using syncable::PARENT_ID;
52 using syncable::PREV_ID;
53 using syncable::ReadTransaction;
54 using syncable::SERVER_BOOKMARK_FAVICON;
55 using syncable::SERVER_BOOKMARK_URL;
56 using syncable::SERVER_CTIME;
57 using syncable::SERVER_IS_BOOKMARK_OBJECT;
58 using syncable::SERVER_IS_DEL;
59 using syncable::SERVER_IS_DIR;
60 using syncable::SERVER_MTIME;
61 using syncable::SERVER_NAME;
62 using syncable::SERVER_PARENT_ID;
63 using syncable::SERVER_POSITION_IN_PARENT;
64 using syncable::SERVER_VERSION;
65 using syncable::SINGLETON_TAG;
66 using syncable::SYNCER;
67 using syncable::SyncName;
68 using syncable::UNSANITIZED_NAME;
69 using syncable::WriteTransaction;
70
71 namespace browser_sync {
72
73 using std::string;
74 using std::vector;
75
76 // TODO(ncarter): Remove unique-in-parent title support and name conflicts.
77 // static
78 syncable::Id SyncerUtil::GetNameConflictingItemId(
79 syncable::BaseTransaction* trans,
80 const syncable::Id& parent_id,
81 const PathString& server_name) {
82
83 Entry same_path(trans, GET_BY_PARENTID_AND_DBNAME, parent_id, server_name);
84 if (same_path.good() && !same_path.GetName().HasBeenSanitized())
85 return same_path.Get(ID);
86 Name doctored_name(server_name);
87 doctored_name.db_value().MakeOSLegal();
88 if (!doctored_name.HasBeenSanitized())
89 return syncable::kNullId;
90 Directory::ChildHandles children;
91 trans->directory()->GetChildHandles(trans, parent_id, &children);
92 Directory::ChildHandles::iterator i = children.begin();
93 while (i != children.end()) {
94 Entry child_entry(trans, GET_BY_HANDLE, *i++);
95 CHECK(child_entry.good());
96 if (0 == ComparePathNames(child_entry.Get(UNSANITIZED_NAME), server_name))
97 return child_entry.Get(ID);
98 }
99 return syncable::kNullId;
100 }
101
102 // Returns the number of unsynced entries.
103 // static
104 int SyncerUtil::GetUnsyncedEntries(syncable::BaseTransaction* trans,
105 vector<int64> *handles) {
106 trans->directory()->GetUnsyncedMetaHandles(trans, handles);
107 LOG_IF(INFO, handles->size() > 0)
108 << "Have " << handles->size() << " unsynced items.";
109 return handles->size();
110 }
111
112 // static
113 void SyncerUtil::ChangeEntryIDAndUpdateChildren(
114 syncable::WriteTransaction* trans,
115 syncable::MutableEntry* entry,
116 const syncable::Id& new_id,
117 syncable::Directory::ChildHandles* children) {
118 syncable::Id old_id = entry->Get(ID);
119 if (!entry->Put(ID, new_id)) {
120 Entry old_entry(trans, GET_BY_ID, new_id);
121 CHECK(old_entry.good());
122 LOG(FATAL) << "Attempt to change ID to " << new_id
123 << " conflicts with existing entry.\n\n"
124 << *entry << "\n\n" << old_entry;
125 }
126 if (entry->Get(IS_DIR)) {
127 // Get all child entries of the old id
128 trans->directory()->GetChildHandles(trans, old_id, children);
129 Directory::ChildHandles::iterator i = children->begin();
130 while (i != children->end()) {
131 MutableEntry child_entry(trans, GET_BY_HANDLE, *i++);
132 CHECK(child_entry.good());
133 CHECK(child_entry.Put(PARENT_ID, new_id));
134 }
135 }
136 // Update Id references on the previous and next nodes in the sibling
137 // order. Do this by reinserting into the linked list; the first
138 // step in PutPredecessor is to Unlink from the existing order, which
139 // will overwrite the stale Id value from the adjacent nodes.
140 if (entry->Get(PREV_ID) == entry->Get(NEXT_ID) &&
141 entry->Get(PREV_ID) == old_id) {
142 // We just need a shallow update to |entry|'s fields since it is already
143 // self looped.
144 entry->Put(NEXT_ID, new_id);
145 entry->Put(PREV_ID, new_id);
146 } else {
147 entry->PutPredecessor(entry->Get(PREV_ID));
148 }
149 }
150
151 // static
152 void SyncerUtil::ChangeEntryIDAndUpdateChildren(
153 syncable::WriteTransaction* trans,
154 syncable::MutableEntry* entry,
155 const syncable::Id& new_id) {
156 syncable::Directory::ChildHandles children;
157 ChangeEntryIDAndUpdateChildren(trans, entry, new_id, &children);
158 }
159
160 // static
161 void SyncerUtil::AttemptReuniteLostCommitResponses(
162 syncable::WriteTransaction* trans,
163 const SyncEntity& server_entry,
164 const string& client_id) {
165 // If a commit succeeds, but the response does not come back fast enough
166 // then the syncer might assume that it was never committed.
167 // The server will track the client that sent up the original commit and
168 // return this in a get updates response. When this matches a local
169 // uncommitted item, we must mutate our local item and version to pick up
170 // the committed version of the same item whose commit response was lost.
171 // There is however still a race condition if the server has not
172 // completed the commit by the time the syncer tries to get updates
173 // again. To mitigate this, we need to have the server time out in
174 // a reasonable span, our commit batches have to be small enough
175 // to process within our HTTP response "assumed alive" time.
176
177 // We need to check if we have a that didn't get its server
178 // id updated correctly. The server sends down a client ID
179 // and a local (negative) id. If we have a entry by that
180 // description, we should update the ID and version to the
181 // server side ones to avoid multiple commits to the same name.
182 if (server_entry.has_originator_cache_guid() &&
183 server_entry.originator_cache_guid() == client_id) {
184 syncable::Id server_id = syncable::Id::CreateFromClientString(
185 server_entry.originator_client_item_id());
186 CHECK(!server_id.ServerKnows());
187 syncable::MutableEntry local_entry(trans, GET_BY_ID, server_id);
188
189 // If it exists, then our local client lost a commit response.
190 if (local_entry.good() && !local_entry.Get(IS_DEL)) {
191 int64 old_version = local_entry.Get(BASE_VERSION);
192 int64 new_version = server_entry.version();
193 CHECK(old_version <= 0);
194 CHECK(new_version > 0);
195 // Otherwise setting the base version could cause a consistency failure.
196 // An entry should never be version 0 and SYNCED.
197 CHECK(local_entry.Get(IS_UNSYNCED));
198
199 // just a quick sanity check
200 CHECK(!local_entry.Get(ID).ServerKnows());
201
202 LOG(INFO) << "Reuniting lost commit response IDs" <<
203 " server id: " << server_entry.id() << " local id: " <<
204 local_entry.Get(ID) << " new version: " << new_version;
205
206 local_entry.Put(BASE_VERSION, new_version);
207
208 ChangeEntryIDAndUpdateChildren(trans, &local_entry, server_entry.id());
209
210 // We need to continue normal processing on this update after we
211 // reunited its ID.
212 }
213 // !local_entry.Good() means we don't have a left behind entry for this
214 // ID. We successfully committed before. In the future we should get rid
215 // of this system and just have client side generated IDs as a whole.
216 }
217 }
218
219 // static
220 UpdateAttemptResponse SyncerUtil::AttemptToUpdateEntry(
221 syncable::WriteTransaction* const trans,
222 syncable::MutableEntry* const entry,
223 SyncerSession* const session) {
224
225 syncable::Id conflicting_id;
226 UpdateAttemptResponse result =
227 AttemptToUpdateEntryWithoutMerge(trans, entry, session,
228 &conflicting_id);
229 if (result != NAME_CONFLICT) {
230 return result;
231 }
232 syncable::MutableEntry same_path(trans, syncable::GET_BY_ID, conflicting_id);
233 CHECK(same_path.good());
234
235 ConflictResolver* resolver = session->resolver();
236
237 if (resolver &&
238 resolver->AttemptItemMerge(trans, &same_path, entry)) {
239 return SUCCESS;
240 }
241 LOG(INFO) << "Not updating item, path collision. Update:\n" << *entry
242 << "\nSame Path:\n" << same_path;
243 return CONFLICT;
244 }
245
246 // static
247 UpdateAttemptResponse SyncerUtil::AttemptToUpdateEntryWithoutMerge(
248 syncable::WriteTransaction* const trans,
249 syncable::MutableEntry* const entry,
250 SyncerSession* const session, syncable::Id* const conflicting_id) {
251
252 CHECK(entry->good());
253 if (!entry->Get(IS_UNAPPLIED_UPDATE))
254 return SUCCESS; // No work to do
255 syncable::Id id = entry->Get(ID);
256
257 if (entry->Get(IS_UNSYNCED)) {
258 LOG(INFO) << "Skipping update, returning conflict for: " << id
259 << " ; it's unsynced.";
260 return CONFLICT;
261 }
262 if (!entry->Get(SERVER_IS_DEL)) {
263 syncable::Id new_parent = entry->Get(SERVER_PARENT_ID);
264 Entry parent(trans, GET_BY_ID, new_parent);
265 // A note on non-directory parents:
266 // We catch most unfixable tree invariant errors at update receipt time,
267 // however we deal with this case here because we may receive the child
268 // first then the illegal parent. Instead of dealing with it twice in
269 // different ways we deal with it once here to reduce the amount of code and
270 // potential errors.
271 if (!parent.good() || parent.Get(IS_DEL) || !parent.Get(IS_DIR)) {
272 return CONFLICT;
273 }
274 if (entry->Get(PARENT_ID) != new_parent) {
275 if (!entry->Get(IS_DEL) && !IsLegalNewParent(trans, id, new_parent)) {
276 LOG(INFO) << "Not updating item " << id << ", illegal new parent "
277 "(would cause loop).";
278 return CONFLICT;
279 }
280 }
281 PathString server_name = entry->Get(SERVER_NAME);
282 syncable::Id conflict_id =
283 SyncerUtil::GetNameConflictingItemId(trans,
284 entry->Get(SERVER_PARENT_ID),
285 server_name);
286 if (conflict_id != syncable::kNullId && conflict_id != id) {
287 if (conflicting_id)
288 *conflicting_id = conflict_id;
289 return NAME_CONFLICT;
290 }
291 } else if (entry->Get(IS_DIR)) {
292 Directory::ChildHandles handles;
293 trans->directory()->GetChildHandles(trans, id, &handles);
294 if (!handles.empty()) {
295 // If we have still-existing children, then we need to deal with
296 // them before we can process this change.
297 LOG(INFO) << "Not deleting directory; it's not empty " << *entry;
298 return CONFLICT;
299 }
300 }
301
302 int64 old_version = entry->Get(BASE_VERSION);
303 SyncerUtil::UpdateLocalDataFromServerData(trans, entry);
304
305 return SUCCESS;
306 }
307
308 // Pass in name and checksum because of UTF8 conversion.
309 // static
310 void SyncerUtil::UpdateServerFieldsFromUpdate(
311 MutableEntry* local_entry,
312 const SyncEntity& server_entry,
313 const SyncName& name) {
314 if (server_entry.deleted()) {
315 // The server returns very lightweight replies for deletions, so
316 // we don't clobber a bunch of fields on delete.
317 local_entry->Put(SERVER_IS_DEL, true);
318 local_entry->Put(SERVER_VERSION,
319 std::max(local_entry->Get(SERVER_VERSION),
320 local_entry->Get(BASE_VERSION)) + 1L);
321 local_entry->Put(IS_UNAPPLIED_UPDATE, true);
322 return;
323 }
324
325 CHECK(local_entry->Get(ID) == server_entry.id())
326 << "ID Changing not supported here";
327 local_entry->Put(SERVER_PARENT_ID, server_entry.parent_id());
328 local_entry->PutServerName(name);
329 local_entry->Put(SERVER_VERSION, server_entry.version());
330 local_entry->Put(SERVER_CTIME,
331 ServerTimeToClientTime(server_entry.ctime()));
332 local_entry->Put(SERVER_MTIME,
333 ServerTimeToClientTime(server_entry.mtime()));
334 local_entry->Put(SERVER_IS_BOOKMARK_OBJECT, server_entry.has_bookmarkdata());
335 local_entry->Put(SERVER_IS_DIR, server_entry.IsFolder());
336 if (server_entry.has_singleton_tag()) {
337 PathString tag;
338 AppendUTF8ToPathString(server_entry.singleton_tag(), &tag);
339 local_entry->Put(SINGLETON_TAG, tag);
340 }
341 if (server_entry.has_bookmarkdata() && !server_entry.deleted()) {
342 const SyncEntity::BookmarkData& bookmark = server_entry.bookmarkdata();
343 if (bookmark.has_bookmark_url()) {
344 PathString url;
345 AppendUTF8ToPathString(bookmark.bookmark_url(), &url);
346 local_entry->Put(SERVER_BOOKMARK_URL, url);
347 }
348 if (bookmark.has_bookmark_favicon()) {
349 Blob favicon_blob;
350 SyncerProtoUtil::CopyProtoBytesIntoBlob(bookmark.bookmark_favicon(),
351 &favicon_blob);
352 local_entry->Put(SERVER_BOOKMARK_FAVICON, favicon_blob);
353 }
354 }
355 if (server_entry.has_position_in_parent()) {
356 local_entry->Put(SERVER_POSITION_IN_PARENT,
357 server_entry.position_in_parent());
358 }
359
360 local_entry->Put(SERVER_IS_DEL, server_entry.deleted());
361 // We only mark the entry as unapplied if its version is greater than
362 // the local data. If we're processing the update that corresponds to one of
363 // our commit we don't apply it as time differences may occur.
364 if (server_entry.version() > local_entry->Get(BASE_VERSION)) {
365 local_entry->Put(IS_UNAPPLIED_UPDATE, true);
366 }
367 ApplyExtendedAttributes(local_entry, server_entry);
368 }
369
370 // static
371 void SyncerUtil::ApplyExtendedAttributes(
372 syncable::MutableEntry* local_entry,
373 const SyncEntity& server_entry) {
374 local_entry->DeleteAllExtendedAttributes(local_entry->trans());
375 if (server_entry.has_extended_attributes()) {
376 const sync_pb::ExtendedAttributes & extended_attributes =
377 server_entry.extended_attributes();
378 for (int i = 0; i < extended_attributes.extendedattribute_size(); i++) {
379 PathString pathstring_key;
380 AppendUTF8ToPathString(
381 extended_attributes.extendedattribute(i).key(), &pathstring_key);
382 ExtendedAttributeKey key(local_entry->Get(META_HANDLE), pathstring_key);
383 MutableExtendedAttribute local_attribute(local_entry->trans(),
384 CREATE, key);
385 SyncerProtoUtil::CopyProtoBytesIntoBlob(
386 extended_attributes.extendedattribute(i).value(),
387 local_attribute.mutable_value());
388 }
389 }
390 }
391
392 // Creates a new Entry iff no Entry exists with the given id.
393 // static
394 void SyncerUtil::CreateNewEntry(syncable::WriteTransaction *trans,
395 const syncable::Id& id) {
396 syncable::MutableEntry entry(trans, syncable::GET_BY_ID, id);
397 if (!entry.good()) {
398 syncable::MutableEntry new_entry(trans, syncable::CREATE_NEW_UPDATE_ITEM,
399 id);
400 }
401 }
402
403 // static
404 bool SyncerUtil::ServerAndLocalOrdersMatch(syncable::Entry* entry) {
405 // Find the closest up-to-date local sibling by walking the linked list.
406 syncable::Id local_up_to_date_predecessor = entry->Get(PREV_ID);
407 while (!local_up_to_date_predecessor.IsRoot()) {
408 Entry local_prev(entry->trans(), GET_BY_ID, local_up_to_date_predecessor);
409 if (!local_prev.good() || local_prev.Get(IS_DEL))
410 return false;
411 if (!local_prev.Get(IS_UNAPPLIED_UPDATE) && !local_prev.Get(IS_UNSYNCED))
412 break;
413 local_up_to_date_predecessor = local_prev.Get(PREV_ID);
414 }
415 // Now find the closest up-to-date sibling in the server order.
416
417 syncable::Id server_up_to_date_predecessor =
418 ComputePrevIdFromServerPosition(entry->trans(), entry,
419 entry->Get(SERVER_PARENT_ID));
420 return server_up_to_date_predecessor == local_up_to_date_predecessor;
421 }
422
423 // static
424 bool SyncerUtil::ServerAndLocalEntriesMatch(syncable::Entry* entry) {
425 if (!ClientAndServerTimeMatch(
426 entry->Get(CTIME), ClientTimeToServerTime(entry->Get(SERVER_CTIME)))) {
427 LOG(WARNING) << "Client and server time mismatch";
428 return false;
429 }
430 if (entry->Get(IS_DEL) && entry->Get(SERVER_IS_DEL))
431 return true;
432 // Name should exactly match here.
433 if (!entry->SyncNameMatchesServerName()) {
434 LOG(WARNING) << "Unsanitized name mismatch";
435 return false;
436 }
437
438 if (entry->Get(PARENT_ID) != entry->Get(SERVER_PARENT_ID) ||
439 entry->Get(IS_DIR) != entry->Get(SERVER_IS_DIR) ||
440 entry->Get(IS_DEL) != entry->Get(SERVER_IS_DEL)) {
441 LOG(WARNING) << "Metabit mismatch";
442 return false;
443 }
444
445 if (!ServerAndLocalOrdersMatch(entry)) {
446 LOG(WARNING) << "Server/local ordering mismatch";
447 return false;
448 }
449
450 if (entry->Get(IS_BOOKMARK_OBJECT)) {
451 if (!entry->Get(IS_DIR)) {
452 if (entry->Get(BOOKMARK_URL) != entry->Get(SERVER_BOOKMARK_URL)) {
453 LOG(WARNING) << "Bookmark URL mismatch";
454 return false;
455 }
456 }
457 }
458 if (entry->Get(IS_DIR))
459 return true;
460 // For historical reasons, a folder's MTIME changes when its contents change.
461 // TODO(ncarter): Remove the special casing of MTIME.
462 bool time_match = ClientAndServerTimeMatch(entry->Get(MTIME),
463 ClientTimeToServerTime(entry->Get(SERVER_MTIME)));
464 if (!time_match) {
465 LOG(WARNING) << "Time mismatch";
466 }
467 return time_match;
468 }
469
470 // static
471 void SyncerUtil::SplitServerInformationIntoNewEntry(
472 syncable::WriteTransaction* trans,
473 syncable::MutableEntry* entry) {
474 syncable::Id id = entry->Get(ID);
475 ChangeEntryIDAndUpdateChildren(trans, entry, trans->directory()->NextId());
476 entry->Put(BASE_VERSION, 0);
477
478 MutableEntry new_entry(trans, CREATE_NEW_UPDATE_ITEM, id);
479 CopyServerFields(entry, &new_entry);
480 ClearServerData(entry);
481
482 LOG(INFO) << "Splitting server information, local entry: " << *entry <<
483 " server entry: " << new_entry;
484 }
485
486 // This function is called on an entry when we can update the user-facing data
487 // from the server data.
488 // static
489 void SyncerUtil::UpdateLocalDataFromServerData(
490 syncable::WriteTransaction* trans,
491 syncable::MutableEntry* entry) {
492 CHECK(!entry->Get(IS_UNSYNCED));
493 CHECK(entry->Get(IS_UNAPPLIED_UPDATE));
494 LOG(INFO) << "Updating entry : " << *entry;
495 entry->Put(IS_BOOKMARK_OBJECT, entry->Get(SERVER_IS_BOOKMARK_OBJECT));
496 // This strange dance around the IS_DEL flag
497 // avoids problems when setting the name.
498 if (entry->Get(SERVER_IS_DEL)) {
499 entry->Put(IS_DEL, true);
500 } else {
501 Name name = Name::FromSyncName(entry->GetServerName());
502 name.db_value().MakeOSLegal();
503 bool was_doctored_before_made_noncolliding = name.HasBeenSanitized();
504 name.db_value().MakeNoncollidingForEntry(trans,
505 entry->Get(SERVER_PARENT_ID),
506 entry);
507 bool was_doctored = name.HasBeenSanitized();
508 if (was_doctored) {
509 // If we're changing the name of entry, either its name
510 // should be illegal, or some other entry should have an unsanitized
511 // name. There's should be a CHECK in every code path.
512 Entry blocking_entry(trans, GET_BY_PARENTID_AND_DBNAME,
513 entry->Get(SERVER_PARENT_ID),
514 name.value());
515 if (blocking_entry.good())
516 CHECK(blocking_entry.GetName().HasBeenSanitized());
517 else
518 CHECK(was_doctored_before_made_noncolliding);
519 }
520 CHECK(entry->PutParentIdAndName(entry->Get(SERVER_PARENT_ID), name))
521 << "Name Clash in UpdateLocalDataFromServerData: "
522 << *entry;
523 CHECK(entry->Put(IS_DEL, false));
524 Id new_predecessor = ComputePrevIdFromServerPosition(trans, entry,
525 entry->Get(SERVER_PARENT_ID));
526 CHECK(entry->PutPredecessor(new_predecessor))
527 << " Illegal predecessor after converting from server position.";
528 }
529
530 entry->Put(CTIME, entry->Get(SERVER_CTIME));
531 entry->Put(MTIME, entry->Get(SERVER_MTIME));
532 entry->Put(BASE_VERSION, entry->Get(SERVER_VERSION));
533 entry->Put(IS_DIR, entry->Get(SERVER_IS_DIR));
534 entry->Put(IS_DEL, entry->Get(SERVER_IS_DEL));
535 entry->Put(BOOKMARK_URL, entry->Get(SERVER_BOOKMARK_URL));
536 entry->Put(BOOKMARK_FAVICON, entry->Get(SERVER_BOOKMARK_FAVICON));
537 entry->Put(IS_UNAPPLIED_UPDATE, false);
538 }
539
540 // static
541 VerifyCommitResult SyncerUtil::ValidateCommitEntry(
542 syncable::MutableEntry* entry) {
543 syncable::Id id = entry->Get(ID);
544 if (id == entry->Get(PARENT_ID)) {
545 CHECK(id.IsRoot()) << "Non-root item is self parenting." << *entry;
546 // If the root becomes unsynced it can cause us problems.
547 LOG(ERROR) << "Root item became unsynced " << *entry;
548 return VERIFY_UNSYNCABLE;
549 }
550 if (entry->IsRoot()) {
551 LOG(ERROR) << "Permanent item became unsynced " << *entry;
552 return VERIFY_UNSYNCABLE;
553 }
554 if (entry->Get(IS_DEL) && !entry->Get(ID).ServerKnows()) {
555 // drop deleted uncommitted entries.
556 return VERIFY_UNSYNCABLE;
557 }
558 return VERIFY_OK;
559 }
560
561 // static
562 bool SyncerUtil::AddItemThenPredecessors(
563 syncable::BaseTransaction* trans,
564 syncable::Entry* item,
565 syncable::IndexedBitField inclusion_filter,
566 syncable::MetahandleSet* inserted_items,
567 vector<syncable::Id>* commit_ids) {
568
569 if (!inserted_items->insert(item->Get(META_HANDLE)).second)
570 return false;
571 commit_ids->push_back(item->Get(ID));
572 if (item->Get(IS_DEL))
573 return true; // Deleted items have no predecessors.
574
575 Id prev_id = item->Get(PREV_ID);
576 while (!prev_id.IsRoot()) {
577 Entry prev(trans, GET_BY_ID, prev_id);
578 CHECK(prev.good()) << "Bad id when walking predecessors.";
579 if (!prev.Get(inclusion_filter))
580 break;
581 if (!inserted_items->insert(prev.Get(META_HANDLE)).second)
582 break;
583 commit_ids->push_back(prev_id);
584 prev_id = prev.Get(PREV_ID);
585 }
586 return true;
587 }
588
589 // static
590 void SyncerUtil::AddPredecessorsThenItem(
591 syncable::BaseTransaction* trans,
592 syncable::Entry* item,
593 syncable::IndexedBitField inclusion_filter,
594 syncable::MetahandleSet* inserted_items,
595 vector<syncable::Id>* commit_ids) {
596
597 vector<syncable::Id>::size_type initial_size = commit_ids->size();
598 if (!AddItemThenPredecessors(trans, item, inclusion_filter, inserted_items,
599 commit_ids))
600 return;
601 // Reverse what we added to get the correct order.
602 std::reverse(commit_ids->begin() + initial_size, commit_ids->end());
603 }
604
605 // TODO(ncarter): This is redundant to some code in GetCommitIdsCommand. Unify
606 // them.
607 // static
608 void SyncerUtil::AddUncommittedParentsAndTheirPredecessors(
609 syncable::BaseTransaction* trans,
610 syncable::MetahandleSet* inserted_items,
611 vector<syncable::Id>* commit_ids,
612 syncable::Id parent_id) {
613 vector<syncable::Id>::size_type intial_commit_ids_size = commit_ids->size();
614 // Climb the tree adding entries leaf -> root.
615 while (!parent_id.ServerKnows()) {
616 Entry parent(trans, GET_BY_ID, parent_id);
617 CHECK(parent.good()) << "Bad user-only parent in item path.";
618 if (!AddItemThenPredecessors(trans, &parent, IS_UNSYNCED, inserted_items,
619 commit_ids))
620 break; // Parent was already present in |inserted_items|.
621 parent_id = parent.Get(PARENT_ID);
622 }
623 // Reverse what we added to get the correct order.
624 std::reverse(commit_ids->begin() + intial_commit_ids_size, commit_ids->end());
625 }
626
627 // static
628 void SyncerUtil::MarkDeletedChildrenSynced(
629 const syncable::ScopedDirLookup &dir,
630 std::set<syncable::Id>* deleted_folders) {
631 // There's two options here.
632 // 1. Scan deleted unsynced entries looking up their pre-delete tree for any
633 // of the deleted folders.
634 // 2. Take each folder and do a tree walk of all entries underneath it.
635 // #2 has a lower big O cost, but writing code to limit the time spent inside
636 // the transaction during each step is simpler with 1. Changing this decision
637 // may be sensible if this code shows up in profiling.
638 if (deleted_folders->empty())
639 return;
640 Directory::UnsyncedMetaHandles handles;
641 {
642 ReadTransaction trans(dir, __FILE__, __LINE__);
643 dir->GetUnsyncedMetaHandles(&trans, &handles);
644 }
645 if (handles.empty())
646 return;
647 Directory::UnsyncedMetaHandles::iterator it;
648 for (it = handles.begin() ; it != handles.end() ; ++it) {
649 // Single transaction / entry we deal with.
650 WriteTransaction trans(dir, SYNCER, __FILE__, __LINE__);
651 MutableEntry entry(&trans, GET_BY_HANDLE, *it);
652 if (!entry.Get(IS_UNSYNCED) || !entry.Get(IS_DEL))
653 continue;
654 syncable::Id id = entry.Get(PARENT_ID);
655 while (id != trans.root_id()) {
656 if (deleted_folders->find(id) != deleted_folders->end()) {
657 // We've synced the deletion of this deleted entries parent
658 entry.Put(IS_UNSYNCED, false);
659 break;
660 }
661 Entry parent(&trans, GET_BY_ID, id);
662 if (!parent.good() || !parent.Get(IS_DEL))
663 break;
664 id = parent.Get(PARENT_ID);
665 }
666 }
667 }
668
669 // static
670 VerifyResult SyncerUtil::VerifyNewEntry(
671 const SyncEntity& entry,
672 syncable::MutableEntry* same_id,
673 const bool deleted) {
674 if (same_id->good()) {
675 // Not a new entry.
676 return VERIFY_UNDECIDED;
677 }
678 if (deleted) {
679 // Deletion of an item we've never seen can be ignored.
680 return VERIFY_SKIP;
681 }
682
683 return VERIFY_SUCCESS;
684 }
685
686 // Assumes we have an existing entry; check here for updates that break
687 // consistency rules.
688 // static
689 VerifyResult SyncerUtil::VerifyUpdateConsistency(
690 syncable::WriteTransaction* trans,
691 const SyncEntity& entry,
692 syncable::MutableEntry* same_id,
693 const bool deleted,
694 const bool is_directory,
695 const bool has_bookmark_data) {
696
697 CHECK(same_id->good());
698
699 // If the entry is a delete, we don't really need to worry at this stage.
700 if (deleted)
701 return VERIFY_SUCCESS;
702
703 if (same_id->Get(SERVER_VERSION) > 0) {
704 // Then we've had an update for this entry before.
705 if (is_directory != same_id->Get(SERVER_IS_DIR) ||
706 has_bookmark_data != same_id->Get(SERVER_IS_BOOKMARK_OBJECT)) {
707 if (same_id->Get(IS_DEL)) { // if we've deleted the item, we don't care.
708 return VERIFY_SKIP;
709 } else {
710 LOG(ERROR) << "Server update doesn't agree with previous updates. ";
711 LOG(ERROR) << " Entry: " << *same_id;
712 LOG(ERROR) << " Update: " << SyncEntityDebugString(entry);
713 return VERIFY_FAIL;
714 }
715 }
716
717 if (!deleted &&
718 (same_id->Get(SERVER_IS_DEL) ||
719 (!same_id->Get(IS_UNSYNCED) && same_id->Get(IS_DEL) &&
720 same_id->Get(BASE_VERSION) > 0))) {
721 // An undelete. The latter case in the above condition is for
722 // when the server does not give us an update following the
723 // commit of a delete, before undeleting. Undeletion is possible
724 // in the server's storage backend, so it's possible on the client,
725 // though not expected to be something that is commonly possible.
726 VerifyResult result =
727 SyncerUtil::VerifyUndelete(trans, entry, same_id);
728 if (VERIFY_UNDECIDED != result)
729 return result;
730 }
731 }
732 if (same_id->Get(BASE_VERSION) > 0) {
733 // We've committed this entry in the past.
734 if (is_directory != same_id->Get(IS_DIR) ||
735 has_bookmark_data != same_id->Get(IS_BOOKMARK_OBJECT)) {
736 LOG(ERROR) << "Server update doesn't agree with committed item. ";
737 LOG(ERROR) << " Entry: " << *same_id;
738 LOG(ERROR) << " Update: " << SyncEntityDebugString(entry);
739 return VERIFY_FAIL;
740 }
741 if (same_id->Get(BASE_VERSION) == entry.version() &&
742 !same_id->Get(IS_UNSYNCED) &&
743 !SyncerProtoUtil::Compare(*same_id, entry)) {
744 // TODO(sync): This constraint needs to be relaxed. For now it's OK to
745 // fail the verification and deal with it when we ApplyUpdates.
746 LOG(ERROR) << "Server update doesn't match local data with same "
747 "version. A bug should be filed. Entry: " << *same_id <<
748 "Update: " << SyncEntityDebugString(entry);
749 return VERIFY_FAIL;
750 }
751 if (same_id->Get(SERVER_VERSION) > entry.version()) {
752 LOG(WARNING) << "We've already seen a more recent update from the server";
753 LOG(WARNING) << " Entry: " << *same_id;
754 LOG(WARNING) << " Update: " << SyncEntityDebugString(entry);
755 return VERIFY_SKIP;
756 }
757 }
758 return VERIFY_SUCCESS;
759 }
760
761 // Assumes we have an existing entry; verify an update that seems to be
762 // expressing an 'undelete'
763 // static
764 VerifyResult SyncerUtil::VerifyUndelete(syncable::WriteTransaction* trans,
765 const SyncEntity& entry,
766 syncable::MutableEntry* same_id) {
767 CHECK(same_id->good());
768 LOG(INFO) << "Server update is attempting undelete. " << *same_id
769 << "Update:" << SyncEntityDebugString(entry);
770 // Move the old one aside and start over. It's too tricky to
771 // get the old one back into a state that would pass
772 // CheckTreeInvariants().
773 if (same_id->Get(IS_DEL)) {
774 same_id->Put(ID, trans->directory()->NextId());
775 same_id->Put(BASE_VERSION, CHANGES_VERSION);
776 same_id->Put(SERVER_VERSION, 0);
777 return VERIFY_SUCCESS;
778 }
779 if (entry.version() < same_id->Get(SERVER_VERSION)) {
780 LOG(WARNING) << "Update older than current server version for" <<
781 *same_id << "Update:" << SyncEntityDebugString(entry);
782 return VERIFY_SUCCESS; // Expected in new sync protocol.
783 }
784 return VERIFY_UNDECIDED;
785 }
786
787 // static
788 syncable::Id SyncerUtil::ComputePrevIdFromServerPosition(
789 syncable::BaseTransaction* trans,
790 syncable::Entry* update_item,
791 const syncable::Id& parent_id) {
792 const int64 position_in_parent = update_item->Get(SERVER_POSITION_IN_PARENT);
793
794 // TODO(ncarter): This computation is linear in the number of children, but
795 // we could make it logarithmic if we kept an index on server position.
796 syncable::Id closest_sibling;
797 syncable::Id next_id = trans->directory()->GetFirstChildId(trans, parent_id);
798 while (!next_id.IsRoot()) {
799 syncable::Entry candidate(trans, GET_BY_ID, next_id);
800 if (!candidate.good()) {
801 LOG(WARNING) << "Should not happen";
802 return closest_sibling;
803 }
804 next_id = candidate.Get(NEXT_ID);
805
806 // Defensively prevent self-comparison.
807 if (candidate.Get(META_HANDLE) == update_item->Get(META_HANDLE)) {
808 continue;
809 }
810
811 // Ignore unapplied updates -- they might not even be server-siblings.
812 if (candidate.Get(IS_UNAPPLIED_UPDATE)) {
813 continue;
814 }
815
816 // Unsynced items don't have a valid server position.
817 if (!candidate.Get(IS_UNSYNCED)) {
818 // If |candidate| is after |update_entry| according to the server
819 // ordering, then we're done. ID is the tiebreaker.
820 if ((candidate.Get(SERVER_POSITION_IN_PARENT) > position_in_parent) ||
821 (candidate.Get(SERVER_POSITION_IN_PARENT) == position_in_parent) &&
822 (candidate.Get(ID) > update_item->Get(ID))) {
823 return closest_sibling;
824 }
825 }
826
827 // We can't trust the SERVER_ fields of unsynced items, but they are
828 // potentially legitimate local predecessors. In the case where
829 // |update_item| and an unsynced item wind up in the same insertion
830 // position, we need to choose how to order them. The following check puts
831 // the unapplied update first; removing it would put the unsynced item(s)
832 // first.
833 if (candidate.Get(IS_UNSYNCED)) {
834 continue;
835 }
836
837 // |update_entry| is considered to be somewhere after |candidate|, so
838 // store it as the upper bound.
839 closest_sibling = candidate.Get(ID);
840 }
841
842 return closest_sibling;
843 }
844
845 } // namespace browser_sync
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698