OLD | NEW |
---|---|
1 #!/usr/bin/python2.4 | 1 #!/usr/bin/python2.4 |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 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 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
93 datatypes: a list of the datatypes (python enum) needing migration. | 93 datatypes: a list of the datatypes (python enum) needing migration. |
94 """ | 94 """ |
95 | 95 |
96 def __init__(self, datatypes): | 96 def __init__(self, datatypes): |
97 self.datatypes = datatypes | 97 self.datatypes = datatypes |
98 | 98 |
99 | 99 |
100 class StoreBirthdayError(Error): | 100 class StoreBirthdayError(Error): |
101 """The client sent a birthday that doesn't correspond to this server.""" | 101 """The client sent a birthday that doesn't correspond to this server.""" |
102 | 102 |
103 class TransientError(Error): | |
104 """The client would be sent a transient error.""" | |
105 | |
103 | 106 |
104 def GetEntryType(entry): | 107 def GetEntryType(entry): |
105 """Extract the sync type from a SyncEntry. | 108 """Extract the sync type from a SyncEntry. |
106 | 109 |
107 Args: | 110 Args: |
108 entry: A SyncEntity protobuf object whose type to determine. | 111 entry: A SyncEntity protobuf object whose type to determine. |
109 Returns: | 112 Returns: |
110 An enum value from ALL_TYPES if the entry's type can be determined, or None | 113 An enum value from ALL_TYPES if the entry's type can be determined, or None |
111 if the type cannot be determined. | 114 if the type cannot be determined. |
112 Raises: | 115 Raises: |
(...skipping 440 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
553 | 556 |
554 Args: | 557 Args: |
555 requested_types: A list of sync data types from ALL_TYPES. | 558 requested_types: A list of sync data types from ALL_TYPES. |
556 Permanent items of only these types will be created. | 559 Permanent items of only these types will be created. |
557 """ | 560 """ |
558 for spec in self._PERMANENT_ITEM_SPECS: | 561 for spec in self._PERMANENT_ITEM_SPECS: |
559 if spec.sync_type in requested_types: | 562 if spec.sync_type in requested_types: |
560 self._CreatePermanentItem(spec) | 563 self._CreatePermanentItem(spec) |
561 | 564 |
562 def ResetStoreBirthday(self): | 565 def ResetStoreBirthday(self): |
566 <<<<<<< HEAD | |
563 """Resets the store birthday to a random value.""" | 567 """Resets the store birthday to a random value.""" |
568 ======= | |
569 """Resets the store birthday to a random value. | |
570 """ | |
571 >>>>>>> fix. | |
Raghu Simha
2011/08/05 18:04:52
Merge.
lipalani1
2011/08/05 21:33:57
Done.
| |
564 # TODO(nick): uuid.uuid1() is better, but python 2.5 only. | 572 # TODO(nick): uuid.uuid1() is better, but python 2.5 only. |
565 self.store_birthday = '%0.30f' % random.random() | 573 self.store_birthday = '%0.30f' % random.random() |
566 | 574 |
567 def StoreBirthday(self): | 575 def StoreBirthday(self): |
576 <<<<<<< HEAD | |
568 """Gets the store birthday.""" | 577 """Gets the store birthday.""" |
578 ======= | |
579 """Gets the store birthday. | |
580 """ | |
581 >>>>>>> fix. | |
Raghu Simha
2011/08/05 18:04:52
Merge.
lipalani1
2011/08/05 21:33:57
Done.
| |
569 return self.store_birthday | 582 return self.store_birthday |
570 | 583 |
571 def GetChanges(self, sieve): | 584 def GetChanges(self, sieve): |
572 """Get entries which have changed, oldest first. | 585 """Get entries which have changed, oldest first. |
573 | 586 |
574 The returned entries are limited to being _BATCH_SIZE many. The entries | 587 The returned entries are limited to being _BATCH_SIZE many. The entries |
575 are returned in strict version order. | 588 are returned in strict version order. |
576 | 589 |
577 Args: | 590 Args: |
578 sieve: An update sieve to use to filter out updates the client | 591 sieve: An update sieve to use to filter out updates the client |
(...skipping 291 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
870 | 883 |
871 def __init__(self): | 884 def __init__(self): |
872 # The implementation supports exactly one account; its state is here. | 885 # The implementation supports exactly one account; its state is here. |
873 self.account = SyncDataModel() | 886 self.account = SyncDataModel() |
874 self.account_lock = threading.Lock() | 887 self.account_lock = threading.Lock() |
875 # Clients that have talked to us: a map from the full client ID | 888 # Clients that have talked to us: a map from the full client ID |
876 # to its nickname. | 889 # to its nickname. |
877 self.clients = {} | 890 self.clients = {} |
878 self.client_name_generator = ('+' * times + chr(c) | 891 self.client_name_generator = ('+' * times + chr(c) |
879 for times in xrange(0, sys.maxint) for c in xrange(ord('A'), ord('Z'))) | 892 for times in xrange(0, sys.maxint) for c in xrange(ord('A'), ord('Z'))) |
893 self.transient_error = False | |
880 | 894 |
881 def GetShortClientName(self, query): | 895 def GetShortClientName(self, query): |
882 parsed = cgi.parse_qs(query[query.find('?')+1:]) | 896 parsed = cgi.parse_qs(query[query.find('?')+1:]) |
883 client_id = parsed.get('client_id') | 897 client_id = parsed.get('client_id') |
884 if not client_id: | 898 if not client_id: |
885 return '?' | 899 return '?' |
886 client_id = client_id[0] | 900 client_id = client_id[0] |
887 if client_id not in self.clients: | 901 if client_id not in self.clients: |
888 self.clients[client_id] = self.client_name_generator.next() | 902 self.clients[client_id] = self.client_name_generator.next() |
889 return self.clients[client_id] | 903 return self.clients[client_id] |
890 | 904 |
891 def CheckStoreBirthday(self, request): | 905 def CheckStoreBirthday(self, request): |
892 """Raises StoreBirthdayError if the request's birthday is a mismatch.""" | 906 """Raises StoreBirthdayError if the request's birthday is a mismatch.""" |
893 if not request.HasField('store_birthday'): | 907 if not request.HasField('store_birthday'): |
894 return | 908 return |
895 if self.account.StoreBirthday() != request.store_birthday: | 909 if self.account.StoreBirthday() != request.store_birthday: |
896 raise StoreBirthdayError | 910 raise StoreBirthdayError |
897 | 911 |
912 def CheckTransientError(self): | |
913 """Raises Transiet error if |transient_error| variable is set.""" | |
Raghu Simha
2011/08/05 18:04:52
s/Transiet/Transient/
lipalani1
2011/08/05 21:33:57
Done.
| |
914 if self.transient_error == True: | |
915 raise TransientError | |
916 | |
898 def HandleMigrate(self, path): | 917 def HandleMigrate(self, path): |
899 query = urlparse.urlparse(path)[4] | 918 query = urlparse.urlparse(path)[4] |
900 code = 200 | 919 code = 200 |
901 self.account_lock.acquire() | 920 self.account_lock.acquire() |
902 try: | 921 try: |
903 datatypes = [DataTypeStringToSyncTypeLoose(x) | 922 datatypes = [DataTypeStringToSyncTypeLoose(x) |
904 for x in urlparse.parse_qs(query).get('type',[])] | 923 for x in urlparse.parse_qs(query).get('type',[])] |
905 if datatypes: | 924 if datatypes: |
906 self.account.TriggerMigration(datatypes) | 925 self.account.TriggerMigration(datatypes) |
907 response = 'Migrated datatypes %s' % ( | 926 response = 'Migrated datatypes %s' % ( |
908 ' and '.join(SyncTypeToString(x).upper() for x in datatypes)) | 927 ' and '.join(SyncTypeToString(x).upper() for x in datatypes)) |
909 else: | 928 else: |
910 response = 'Please specify one or more <i>type=name</i> parameters' | 929 response = 'Please specify one or more <i>type=name</i> parameters' |
911 code = 400 | 930 code = 400 |
912 except DataTypeIdNotRecognized, error: | 931 except DataTypeIdNotRecognized, error: |
913 response = 'Could not interpret datatype name' | 932 response = 'Could not interpret datatype name' |
914 code = 400 | 933 code = 400 |
915 finally: | 934 finally: |
916 self.account_lock.release() | 935 self.account_lock.release() |
917 return (code, '<html><title>Migration: %d</title><H1>%d %s</H1></html>' % | 936 return (code, '<html><title>Migration: %d</title><H1>%d %s</H1></html>' % |
918 (code, code, response)) | 937 (code, code, response)) |
919 | 938 |
920 def HandleCreateBirthdayError(self): | 939 def HandleCreateBirthdayError(self): |
921 self.account.ResetStoreBirthday() | 940 self.account.ResetStoreBirthday() |
922 return ( | 941 return ( |
923 200, | 942 200, |
924 '<html><title>Birthday error</title><H1>Birthday error</H1></html>') | 943 '<html><title>Birthday error</title><H1>Birthday error</H1></html>') |
925 | 944 |
945 <<<<<<< HEAD | |
946 ======= | |
947 def HandleSetTransientError(self): | |
948 self.transient_error = True | |
949 return ( | |
950 200, | |
951 '<html><title>Transient error</title><H1>Transient error</H1></html>') | |
952 | |
953 >>>>>>> fix. | |
Raghu Simha
2011/08/05 18:04:52
Merge.
lipalani1
2011/08/05 21:33:57
Done.
| |
926 def HandleCommand(self, query, raw_request): | 954 def HandleCommand(self, query, raw_request): |
927 """Decode and handle a sync command from a raw input of bytes. | 955 """Decode and handle a sync command from a raw input of bytes. |
928 | 956 |
929 This is the main entry point for this class. It is safe to call this | 957 This is the main entry point for this class. It is safe to call this |
930 method from multiple threads. | 958 method from multiple threads. |
931 | 959 |
932 Args: | 960 Args: |
933 raw_request: An iterable byte sequence to be interpreted as a sync | 961 raw_request: An iterable byte sequence to be interpreted as a sync |
934 protocol command. | 962 protocol command. |
935 Returns: | 963 Returns: |
936 A tuple (response_code, raw_response); the first value is an HTTP | 964 A tuple (response_code, raw_response); the first value is an HTTP |
937 result code, while the second value is a string of bytes which is the | 965 result code, while the second value is a string of bytes which is the |
938 serialized reply to the command. | 966 serialized reply to the command. |
939 """ | 967 """ |
940 self.account_lock.acquire() | 968 self.account_lock.acquire() |
941 def print_context(direction): | 969 def print_context(direction): |
942 print '[Client %s %s %s.py]' % (self.GetShortClientName(query), direction, | 970 print '[Client %s %s %s.py]' % (self.GetShortClientName(query), direction, |
943 __name__), | 971 __name__), |
944 | 972 |
945 try: | 973 try: |
946 request = sync_pb2.ClientToServerMessage() | 974 request = sync_pb2.ClientToServerMessage() |
947 request.MergeFromString(raw_request) | 975 request.MergeFromString(raw_request) |
948 contents = request.message_contents | 976 contents = request.message_contents |
949 | 977 |
950 response = sync_pb2.ClientToServerResponse() | 978 response = sync_pb2.ClientToServerResponse() |
951 response.error_code = sync_pb2.ClientToServerResponse.SUCCESS | 979 response.error_code = sync_pb2.ClientToServerResponse.SUCCESS |
952 self.CheckStoreBirthday(request) | 980 self.CheckStoreBirthday(request) |
953 response.store_birthday = self.account.store_birthday | 981 response.store_birthday = self.account.store_birthday |
982 self.CheckTransientError(); | |
954 | 983 |
955 print_context('->') | 984 print_context('->') |
956 | 985 |
957 if contents == sync_pb2.ClientToServerMessage.AUTHENTICATE: | 986 if contents == sync_pb2.ClientToServerMessage.AUTHENTICATE: |
958 print 'Authenticate' | 987 print 'Authenticate' |
959 # We accept any authentication token, and support only one account. | 988 # We accept any authentication token, and support only one account. |
960 # TODO(nick): Mock out the GAIA authentication as well; hook up here. | 989 # TODO(nick): Mock out the GAIA authentication as well; hook up here. |
961 response.authenticate.user.email = 'syncjuser@chromium' | 990 response.authenticate.user.email = 'syncjuser@chromium' |
962 response.authenticate.user.display_name = 'Sync J User' | 991 response.authenticate.user.display_name = 'Sync J User' |
963 elif contents == sync_pb2.ClientToServerMessage.COMMIT: | 992 elif contents == sync_pb2.ClientToServerMessage.COMMIT: |
(...skipping 17 matching lines...) Expand all Loading... | |
981 response.migrated_data_type_id[:] = [ | 1010 response.migrated_data_type_id[:] = [ |
982 SyncTypeToProtocolDataTypeId(x) for x in error.datatypes] | 1011 SyncTypeToProtocolDataTypeId(x) for x in error.datatypes] |
983 return (200, response.SerializeToString()) | 1012 return (200, response.SerializeToString()) |
984 except StoreBirthdayError, error: | 1013 except StoreBirthdayError, error: |
985 print_context('<-') | 1014 print_context('<-') |
986 print 'NOT_MY_BIRTHDAY' | 1015 print 'NOT_MY_BIRTHDAY' |
987 response = sync_pb2.ClientToServerResponse() | 1016 response = sync_pb2.ClientToServerResponse() |
988 response.store_birthday = self.account.store_birthday | 1017 response.store_birthday = self.account.store_birthday |
989 response.error_code = sync_pb2.ClientToServerResponse.NOT_MY_BIRTHDAY | 1018 response.error_code = sync_pb2.ClientToServerResponse.NOT_MY_BIRTHDAY |
990 return (200, response.SerializeToString()) | 1019 return (200, response.SerializeToString()) |
1020 except TransientError as error: | |
1021 print_context('<-') | |
1022 print 'TRANSIENT_ERROR' | |
1023 response.store_birthday = self.account.store_birthday | |
1024 response.error_code = sync_pb2.ClientToServerResponse.TRANSIENT_ERROR | |
1025 return (200, response.SerializeToString()) | |
991 finally: | 1026 finally: |
992 self.account_lock.release() | 1027 self.account_lock.release() |
993 | 1028 |
994 def HandleCommit(self, commit_message, commit_response): | 1029 def HandleCommit(self, commit_message, commit_response): |
995 """Respond to a Commit request by updating the user's account state. | 1030 """Respond to a Commit request by updating the user's account state. |
996 | 1031 |
997 Commit attempts stop after the first error, returning a CONFLICT result | 1032 Commit attempts stop after the first error, returning a CONFLICT result |
998 for any unattempted entries. | 1033 for any unattempted entries. |
999 | 1034 |
1000 Args: | 1035 Args: |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1055 | 1090 |
1056 update_sieve.CheckMigrationState() | 1091 update_sieve.CheckMigrationState() |
1057 | 1092 |
1058 new_timestamp, entries, remaining = self.account.GetChanges(update_sieve) | 1093 new_timestamp, entries, remaining = self.account.GetChanges(update_sieve) |
1059 | 1094 |
1060 update_response.changes_remaining = remaining | 1095 update_response.changes_remaining = remaining |
1061 for entry in entries: | 1096 for entry in entries: |
1062 reply = update_response.entries.add() | 1097 reply = update_response.entries.add() |
1063 reply.CopyFrom(entry) | 1098 reply.CopyFrom(entry) |
1064 update_sieve.SaveProgress(new_timestamp, update_response) | 1099 update_sieve.SaveProgress(new_timestamp, update_response) |
OLD | NEW |