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 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
92 Attributes: | 92 Attributes: |
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 |
ncarter (slow)
2011/07/29 18:52:17
The style guide requires 2 blank lines between top
lipalani1
2011/08/05 21:33:57
Done.
| |
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 795 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
908 | 911 |
909 def __init__(self): | 912 def __init__(self): |
910 # The implementation supports exactly one account; its state is here. | 913 # The implementation supports exactly one account; its state is here. |
911 self.account = SyncDataModel() | 914 self.account = SyncDataModel() |
912 self.account_lock = threading.Lock() | 915 self.account_lock = threading.Lock() |
913 # Clients that have talked to us: a map from the full client ID | 916 # Clients that have talked to us: a map from the full client ID |
914 # to its nickname. | 917 # to its nickname. |
915 self.clients = {} | 918 self.clients = {} |
916 self.client_name_generator = ('+' * times + chr(c) | 919 self.client_name_generator = ('+' * times + chr(c) |
917 for times in xrange(0, sys.maxint) for c in xrange(ord('A'), ord('Z'))) | 920 for times in xrange(0, sys.maxint) for c in xrange(ord('A'), ord('Z'))) |
921 self.transient_error = False | |
918 | 922 |
919 def GetShortClientName(self, query): | 923 def GetShortClientName(self, query): |
920 parsed = cgi.parse_qs(query[query.find('?')+1:]) | 924 parsed = cgi.parse_qs(query[query.find('?')+1:]) |
921 client_id = parsed.get('client_id') | 925 client_id = parsed.get('client_id') |
922 if not client_id: | 926 if not client_id: |
923 return '?' | 927 return '?' |
924 client_id = client_id[0] | 928 client_id = client_id[0] |
925 if client_id not in self.clients: | 929 if client_id not in self.clients: |
926 self.clients[client_id] = self.client_name_generator.next() | 930 self.clients[client_id] = self.client_name_generator.next() |
927 return self.clients[client_id] | 931 return self.clients[client_id] |
928 | 932 |
929 def CheckStoreBirthday(self, request): | 933 def CheckStoreBirthday(self, request): |
930 """Raises StoreBirthdayError if the request's birthday is a mismatch.""" | 934 """Raises StoreBirthdayError if the request's birthday is a mismatch.""" |
931 if not request.HasField('store_birthday'): | 935 if not request.HasField('store_birthday'): |
932 return | 936 return |
933 if self.account.store_birthday != request.store_birthday: | 937 if self.account.store_birthday != request.store_birthday: |
934 raise StoreBirthdayError | 938 raise StoreBirthdayError |
935 | 939 |
940 def CheckTransientError(self): | |
941 """Raises Transiet error if |transient_error| variable is set.""" | |
ncarter (slow)
2011/07/29 18:52:17
"Transiet" is a misspelling, you should probably s
lipalani1
2011/08/05 21:33:57
Done.
| |
942 if self.transient_error == True: | |
ncarter (slow)
2011/07/29 18:52:17
Just say "if self.transient_error:". While this i
lipalani1
2011/08/05 21:33:57
Done.
| |
943 raise TransientError | |
944 | |
936 def HandleMigrate(self, path): | 945 def HandleMigrate(self, path): |
937 query = urlparse.urlparse(path)[4] | 946 query = urlparse.urlparse(path)[4] |
938 code = 200 | 947 code = 200 |
939 self.account_lock.acquire() | 948 self.account_lock.acquire() |
940 try: | 949 try: |
941 datatypes = [DataTypeStringToSyncTypeLoose(x) | 950 datatypes = [DataTypeStringToSyncTypeLoose(x) |
942 for x in urlparse.parse_qs(query).get('type',[])] | 951 for x in urlparse.parse_qs(query).get('type',[])] |
943 if datatypes: | 952 if datatypes: |
944 self.account.TriggerMigration(datatypes) | 953 self.account.TriggerMigration(datatypes) |
945 response = 'Migrated datatypes %s' % ( | 954 response = 'Migrated datatypes %s' % ( |
946 ' and '.join(SyncTypeToString(x).upper() for x in datatypes)) | 955 ' and '.join(SyncTypeToString(x).upper() for x in datatypes)) |
947 else: | 956 else: |
948 response = 'Please specify one or more <i>type=name</i> parameters' | 957 response = 'Please specify one or more <i>type=name</i> parameters' |
949 code = 400 | 958 code = 400 |
950 except DataTypeIdNotRecognized, error: | 959 except DataTypeIdNotRecognized, error: |
951 response = 'Could not interpret datatype name' | 960 response = 'Could not interpret datatype name' |
952 code = 400 | 961 code = 400 |
953 finally: | 962 finally: |
954 self.account_lock.release() | 963 self.account_lock.release() |
955 return (code, '<html><title>Migration: %d</title><H1>%d %s</H1></html>' % | 964 return (code, '<html><title>Migration: %d</title><H1>%d %s</H1></html>' % |
956 (code, code, response)) | 965 (code, code, response)) |
957 | 966 |
958 def HandleCreateBirthdayError(self): | 967 def HandleCreateBirthdayError(self): |
959 self.account.store_birthday = '%0.30f' % random.random() | 968 self.account.store_birthday = '%0.30f' % random.random() |
960 return ( | 969 return ( |
961 200, | 970 200, |
962 '<html><title>Birthday error</title><H1>Birthday error</H1></html>') | 971 '<html><title>Birthday error</title><H1>Birthday error</H1></html>') |
963 | 972 |
973 def HandleSetTransientError(self): | |
974 self.transient_error = True | |
975 return ( | |
976 200, | |
977 '<html><title>Transient error</title><H1>Transient error</H1></html>') | |
978 | |
964 def HandleCommand(self, query, raw_request): | 979 def HandleCommand(self, query, raw_request): |
965 """Decode and handle a sync command from a raw input of bytes. | 980 """Decode and handle a sync command from a raw input of bytes. |
966 | 981 |
967 This is the main entry point for this class. It is safe to call this | 982 This is the main entry point for this class. It is safe to call this |
968 method from multiple threads. | 983 method from multiple threads. |
969 | 984 |
970 Args: | 985 Args: |
971 raw_request: An iterable byte sequence to be interpreted as a sync | 986 raw_request: An iterable byte sequence to be interpreted as a sync |
972 protocol command. | 987 protocol command. |
973 Returns: | 988 Returns: |
974 A tuple (response_code, raw_response); the first value is an HTTP | 989 A tuple (response_code, raw_response); the first value is an HTTP |
975 result code, while the second value is a string of bytes which is the | 990 result code, while the second value is a string of bytes which is the |
976 serialized reply to the command. | 991 serialized reply to the command. |
977 """ | 992 """ |
978 self.account_lock.acquire() | 993 self.account_lock.acquire() |
979 def print_context(direction): | 994 def print_context(direction): |
980 print '[Client %s %s %s.py]' % (self.GetShortClientName(query), direction, | 995 print '[Client %s %s %s.py]' % (self.GetShortClientName(query), direction, |
981 __name__), | 996 __name__), |
982 | 997 |
983 try: | 998 try: |
984 request = sync_pb2.ClientToServerMessage() | 999 request = sync_pb2.ClientToServerMessage() |
985 request.MergeFromString(raw_request) | 1000 request.MergeFromString(raw_request) |
986 contents = request.message_contents | 1001 contents = request.message_contents |
987 | 1002 |
988 response = sync_pb2.ClientToServerResponse() | 1003 response = sync_pb2.ClientToServerResponse() |
989 response.error_code = sync_pb2.ClientToServerResponse.SUCCESS | 1004 response.error_code = sync_pb2.ClientToServerResponse.SUCCESS |
990 self.CheckStoreBirthday(request) | 1005 self.CheckStoreBirthday(request) |
991 response.store_birthday = self.account.store_birthday | 1006 response.store_birthday = self.account.store_birthday |
1007 self.CheckTransientError(); | |
992 | 1008 |
993 print_context('->') | 1009 print_context('->') |
994 | 1010 |
995 if contents == sync_pb2.ClientToServerMessage.AUTHENTICATE: | 1011 if contents == sync_pb2.ClientToServerMessage.AUTHENTICATE: |
996 print 'Authenticate' | 1012 print 'Authenticate' |
997 # We accept any authentication token, and support only one account. | 1013 # We accept any authentication token, and support only one account. |
998 # TODO(nick): Mock out the GAIA authentication as well; hook up here. | 1014 # TODO(nick): Mock out the GAIA authentication as well; hook up here. |
999 response.authenticate.user.email = 'syncjuser@chromium' | 1015 response.authenticate.user.email = 'syncjuser@chromium' |
1000 response.authenticate.user.display_name = 'Sync J User' | 1016 response.authenticate.user.display_name = 'Sync J User' |
1001 elif contents == sync_pb2.ClientToServerMessage.COMMIT: | 1017 elif contents == sync_pb2.ClientToServerMessage.COMMIT: |
(...skipping 17 matching lines...) Expand all Loading... | |
1019 response.migrated_data_type_id[:] = [ | 1035 response.migrated_data_type_id[:] = [ |
1020 SyncTypeToProtocolDataTypeId(x) for x in error.datatypes] | 1036 SyncTypeToProtocolDataTypeId(x) for x in error.datatypes] |
1021 return (200, response.SerializeToString()) | 1037 return (200, response.SerializeToString()) |
1022 except StoreBirthdayError as error: | 1038 except StoreBirthdayError as error: |
1023 print_context('<-') | 1039 print_context('<-') |
1024 print 'NOT_MY_BIRTHDAY' | 1040 print 'NOT_MY_BIRTHDAY' |
1025 response = sync_pb2.ClientToServerResponse() | 1041 response = sync_pb2.ClientToServerResponse() |
1026 response.store_birthday = self.account.store_birthday | 1042 response.store_birthday = self.account.store_birthday |
1027 response.error_code = sync_pb2.ClientToServerResponse.NOT_MY_BIRTHDAY | 1043 response.error_code = sync_pb2.ClientToServerResponse.NOT_MY_BIRTHDAY |
1028 return (200, response.SerializeToString()) | 1044 return (200, response.SerializeToString()) |
1045 except TransientError as error: | |
1046 print_context('<-') | |
1047 print 'TRANSIENT_ERROR' | |
1048 response.store_birthday = self.account.store_birthday | |
1049 response.error_code = sync_pb2.ClientToServerResponse.TRANSIENT_ERROR | |
1050 return (200, response.SerializeToString()) | |
1029 finally: | 1051 finally: |
1030 self.account_lock.release() | 1052 self.account_lock.release() |
1031 | 1053 |
1032 def HandleCommit(self, commit_message, commit_response): | 1054 def HandleCommit(self, commit_message, commit_response): |
1033 """Respond to a Commit request by updating the user's account state. | 1055 """Respond to a Commit request by updating the user's account state. |
1034 | 1056 |
1035 Commit attempts stop after the first error, returning a CONFLICT result | 1057 Commit attempts stop after the first error, returning a CONFLICT result |
1036 for any unattempted entries. | 1058 for any unattempted entries. |
1037 | 1059 |
1038 Args: | 1060 Args: |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1093 | 1115 |
1094 update_sieve.CheckMigrationState() | 1116 update_sieve.CheckMigrationState() |
1095 | 1117 |
1096 new_timestamp, entries, remaining = self.account.GetChanges(update_sieve) | 1118 new_timestamp, entries, remaining = self.account.GetChanges(update_sieve) |
1097 | 1119 |
1098 update_response.changes_remaining = remaining | 1120 update_response.changes_remaining = remaining |
1099 for entry in entries: | 1121 for entry in entries: |
1100 reply = update_response.entries.add() | 1122 reply = update_response.entries.add() |
1101 reply.CopyFrom(entry) | 1123 reply.CopyFrom(entry) |
1102 update_sieve.SaveProgress(new_timestamp, update_response) | 1124 update_sieve.SaveProgress(new_timestamp, update_response) |
OLD | NEW |