| OLD | NEW |
| 1 #!/usr/bin/python2.4 | 1 #!/usr/bin/python2.4 |
| 2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """An implementation of the server side of the Chromium sync protocol. | 6 """An implementation of the server side of the Chromium sync protocol. |
| 7 | 7 |
| 8 The details of the protocol are described mostly by comments in the protocol | 8 The details of the protocol are described mostly by comments in the protocol |
| 9 buffer definition at chrome/browser/sync/protocol/sync.proto. | 9 buffer definition at chrome/browser/sync/protocol/sync.proto. |
| 10 """ | 10 """ |
| (...skipping 491 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 502 # TODO(nick): Implement cycle detection and resolution. | 502 # TODO(nick): Implement cycle detection and resolution. |
| 503 if not self._CheckParentIdForCommit(entry): | 503 if not self._CheckParentIdForCommit(entry): |
| 504 return None | 504 return None |
| 505 | 505 |
| 506 # At this point, the commit is definitely going to happen. | 506 # At this point, the commit is definitely going to happen. |
| 507 | 507 |
| 508 # Deletion works by storing a limited record for an entry, called a | 508 # Deletion works by storing a limited record for an entry, called a |
| 509 # tombstone. A sync server must track deleted IDs forever, since it does | 509 # tombstone. A sync server must track deleted IDs forever, since it does |
| 510 # not keep track of client knowledge (there's no deletion ACK event). | 510 # not keep track of client knowledge (there's no deletion ACK event). |
| 511 if entry.deleted: | 511 if entry.deleted: |
| 512 # Only the ID, version and deletion state are preserved on a tombstone. | 512 def MakeTombstone(id_string): |
| 513 # TODO(nick): Does the production server not preserve the type? Not | 513 """Make a tombstone entry that will replace the entry being deleted. |
| 514 # doing so means that tombstones cannot be filtered based on | 514 |
| 515 # requested_types at GetUpdates time. | 515 Args: |
| 516 tombstone = sync_pb2.SyncEntity() | 516 id_string: Index of the SyncEntity to be deleted. |
| 517 tombstone.id_string = entry.id_string | 517 Returns: |
| 518 tombstone.deleted = True | 518 A new SyncEntity reflecting the fact that the entry is deleted. |
| 519 tombstone.name = '' | 519 """ |
| 520 entry = tombstone | 520 # Only the ID, version and deletion state are preserved on a tombstone. |
| 521 # TODO(nick): Does the production server not preserve the type? Not |
| 522 # doing so means that tombstones cannot be filtered based on |
| 523 # requested_types at GetUpdates time. |
| 524 tombstone = sync_pb2.SyncEntity() |
| 525 tombstone.id_string = id_string |
| 526 tombstone.deleted = True |
| 527 tombstone.name = '' |
| 528 return tombstone |
| 529 |
| 530 def IsChild(child_id): |
| 531 """Check if a SyncEntity is a child of entry, or any of its children. |
| 532 |
| 533 Args: |
| 534 child_id: Index of the SyncEntity that is a possible child of entry. |
| 535 Returns: |
| 536 True if it is a child; false otherwise. |
| 537 """ |
| 538 if child_id not in self._entries: |
| 539 return False |
| 540 if self._entries[child_id].parent_id_string == entry.id_string: |
| 541 return True |
| 542 return IsChild(self._entries[child_id].parent_id_string) |
| 543 |
| 544 # Identify any children entry might have. |
| 545 child_ids = [] |
| 546 for possible_child in self._entries.itervalues(): |
| 547 if IsChild(possible_child.id_string): |
| 548 child_ids.append(possible_child.id_string) |
| 549 |
| 550 # Mark all children that were identified as deleted. |
| 551 for child_id in child_ids: |
| 552 self._SaveEntry(MakeTombstone(child_id)) |
| 553 |
| 554 # Delete entry itself. |
| 555 entry = MakeTombstone(entry.id_string) |
| 521 else: | 556 else: |
| 522 # Comments in sync.proto detail how the representation of positional | 557 # Comments in sync.proto detail how the representation of positional |
| 523 # ordering works: the 'insert_after_item_id' field specifies a | 558 # ordering works: the 'insert_after_item_id' field specifies a |
| 524 # predecessor during Commit operations, but the 'position_in_parent' | 559 # predecessor during Commit operations, but the 'position_in_parent' |
| 525 # field provides an absolute ordering in GetUpdates contexts. Here | 560 # field provides an absolute ordering in GetUpdates contexts. Here |
| 526 # we convert from the former to the latter. Specifically, we'll | 561 # we convert from the former to the latter. Specifically, we'll |
| 527 # generate a numeric position placing the item just after the object | 562 # generate a numeric position placing the item just after the object |
| 528 # identified by 'insert_after_item_id', and then clear the | 563 # identified by 'insert_after_item_id', and then clear the |
| 529 # 'insert_after_item_id' field so that it's not sent back to the client | 564 # 'insert_after_item_id' field so that it's not sent back to the client |
| 530 # during later GetUpdates requests. | 565 # during later GetUpdates requests. |
| 531 if entry.HasField('insert_after_item_id'): | 566 if entry.HasField('insert_after_item_id'): |
| 532 self._WritePosition(entry, entry.parent_id_string, | 567 self._WritePosition(entry, entry.parent_id_string, |
| 533 entry.insert_after_item_id) | 568 entry.insert_after_item_id) |
| 534 else: | 569 else: |
| 535 self._WritePosition(entry, entry.parent_id_string) | 570 self._WritePosition(entry, entry.parent_id_string) |
| 536 | 571 |
| 537 # Preserve the originator info, which the client is not required to send | 572 # Preserve the originator info, which the client is not required to send |
| 538 # when updating. | 573 # when updating. |
| 539 base_entry = self._entries.get(entry.id_string) | 574 base_entry = self._entries.get(entry.id_string) |
| 540 if base_entry and not entry.HasField("originator_cache_guid"): | 575 if base_entry and not entry.HasField("originator_cache_guid"): |
| 541 entry.originator_cache_guid = base_entry.originator_cache_guid | 576 entry.originator_cache_guid = base_entry.originator_cache_guid |
| 542 entry.originator_client_item_id = base_entry.originator_client_item_id | 577 entry.originator_client_item_id = base_entry.originator_client_item_id |
| 543 | 578 |
| 544 # Commit the change. This also updates the version number. | 579 # Commit the change. This also updates the version number. |
| 545 self._SaveEntry(entry) | 580 self._SaveEntry(entry) |
| 546 # TODO(nick): Handle recursive deletion. | |
| 547 return entry | 581 return entry |
| 548 | 582 |
| 549 class TestServer(object): | 583 class TestServer(object): |
| 550 """An object to handle requests for one (and only one) Chrome Sync account. | 584 """An object to handle requests for one (and only one) Chrome Sync account. |
| 551 | 585 |
| 552 TestServer consumes the sync command messages that are the outermost | 586 TestServer consumes the sync command messages that are the outermost |
| 553 layers of the protocol, performs the corresponding actions on its | 587 layers of the protocol, performs the corresponding actions on its |
| 554 SyncDataModel, and constructs an appropropriate response message. | 588 SyncDataModel, and constructs an appropropriate response message. |
| 555 """ | 589 """ |
| 556 | 590 |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 658 new_timestamp, entries = self.account.GetChangesFromTimestamp( | 692 new_timestamp, entries = self.account.GetChangesFromTimestamp( |
| 659 requested_types, update_request.from_timestamp) | 693 requested_types, update_request.from_timestamp) |
| 660 | 694 |
| 661 # If the client is up to date, we are careful not to set the | 695 # If the client is up to date, we are careful not to set the |
| 662 # new_timestamp field. | 696 # new_timestamp field. |
| 663 if new_timestamp != update_request.from_timestamp: | 697 if new_timestamp != update_request.from_timestamp: |
| 664 update_response.new_timestamp = new_timestamp | 698 update_response.new_timestamp = new_timestamp |
| 665 for e in entries: | 699 for e in entries: |
| 666 reply = update_response.entries.add() | 700 reply = update_response.entries.add() |
| 667 reply.CopyFrom(e) | 701 reply.CopyFrom(e) |
| OLD | NEW |