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 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
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 | 103 |
| 104 class TransientError(Error): |
| 105 """The client would be sent a transient error.""" |
| 106 |
| 107 |
104 def GetEntryType(entry): | 108 def GetEntryType(entry): |
105 """Extract the sync type from a SyncEntry. | 109 """Extract the sync type from a SyncEntry. |
106 | 110 |
107 Args: | 111 Args: |
108 entry: A SyncEntity protobuf object whose type to determine. | 112 entry: A SyncEntity protobuf object whose type to determine. |
109 Returns: | 113 Returns: |
110 An enum value from ALL_TYPES if the entry's type can be determined, or None | 114 An enum value from ALL_TYPES if the entry's type can be determined, or None |
111 if the type cannot be determined. | 115 if the type cannot be determined. |
112 Raises: | 116 Raises: |
113 ProtobufExtensionNotUnique: More than one type was indicated by the entry. | 117 ProtobufExtensionNotUnique: More than one type was indicated by the entry. |
(...skipping 756 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
870 | 874 |
871 def __init__(self): | 875 def __init__(self): |
872 # The implementation supports exactly one account; its state is here. | 876 # The implementation supports exactly one account; its state is here. |
873 self.account = SyncDataModel() | 877 self.account = SyncDataModel() |
874 self.account_lock = threading.Lock() | 878 self.account_lock = threading.Lock() |
875 # Clients that have talked to us: a map from the full client ID | 879 # Clients that have talked to us: a map from the full client ID |
876 # to its nickname. | 880 # to its nickname. |
877 self.clients = {} | 881 self.clients = {} |
878 self.client_name_generator = ('+' * times + chr(c) | 882 self.client_name_generator = ('+' * times + chr(c) |
879 for times in xrange(0, sys.maxint) for c in xrange(ord('A'), ord('Z'))) | 883 for times in xrange(0, sys.maxint) for c in xrange(ord('A'), ord('Z'))) |
| 884 self.transient_error = False |
880 | 885 |
881 def GetShortClientName(self, query): | 886 def GetShortClientName(self, query): |
882 parsed = cgi.parse_qs(query[query.find('?')+1:]) | 887 parsed = cgi.parse_qs(query[query.find('?')+1:]) |
883 client_id = parsed.get('client_id') | 888 client_id = parsed.get('client_id') |
884 if not client_id: | 889 if not client_id: |
885 return '?' | 890 return '?' |
886 client_id = client_id[0] | 891 client_id = client_id[0] |
887 if client_id not in self.clients: | 892 if client_id not in self.clients: |
888 self.clients[client_id] = self.client_name_generator.next() | 893 self.clients[client_id] = self.client_name_generator.next() |
889 return self.clients[client_id] | 894 return self.clients[client_id] |
890 | 895 |
891 def CheckStoreBirthday(self, request): | 896 def CheckStoreBirthday(self, request): |
892 """Raises StoreBirthdayError if the request's birthday is a mismatch.""" | 897 """Raises StoreBirthdayError if the request's birthday is a mismatch.""" |
893 if not request.HasField('store_birthday'): | 898 if not request.HasField('store_birthday'): |
894 return | 899 return |
895 if self.account.StoreBirthday() != request.store_birthday: | 900 if self.account.StoreBirthday() != request.store_birthday: |
896 raise StoreBirthdayError | 901 raise StoreBirthdayError |
897 | 902 |
| 903 def CheckTransientError(self): |
| 904 """Raises TransientError if transient_error variable is set.""" |
| 905 if self.transient_error: |
| 906 raise TransientError |
| 907 |
898 def HandleMigrate(self, path): | 908 def HandleMigrate(self, path): |
899 query = urlparse.urlparse(path)[4] | 909 query = urlparse.urlparse(path)[4] |
900 code = 200 | 910 code = 200 |
901 self.account_lock.acquire() | 911 self.account_lock.acquire() |
902 try: | 912 try: |
903 datatypes = [DataTypeStringToSyncTypeLoose(x) | 913 datatypes = [DataTypeStringToSyncTypeLoose(x) |
904 for x in urlparse.parse_qs(query).get('type',[])] | 914 for x in urlparse.parse_qs(query).get('type',[])] |
905 if datatypes: | 915 if datatypes: |
906 self.account.TriggerMigration(datatypes) | 916 self.account.TriggerMigration(datatypes) |
907 response = 'Migrated datatypes %s' % ( | 917 response = 'Migrated datatypes %s' % ( |
908 ' and '.join(SyncTypeToString(x).upper() for x in datatypes)) | 918 ' and '.join(SyncTypeToString(x).upper() for x in datatypes)) |
909 else: | 919 else: |
910 response = 'Please specify one or more <i>type=name</i> parameters' | 920 response = 'Please specify one or more <i>type=name</i> parameters' |
911 code = 400 | 921 code = 400 |
912 except DataTypeIdNotRecognized, error: | 922 except DataTypeIdNotRecognized, error: |
913 response = 'Could not interpret datatype name' | 923 response = 'Could not interpret datatype name' |
914 code = 400 | 924 code = 400 |
915 finally: | 925 finally: |
916 self.account_lock.release() | 926 self.account_lock.release() |
917 return (code, '<html><title>Migration: %d</title><H1>%d %s</H1></html>' % | 927 return (code, '<html><title>Migration: %d</title><H1>%d %s</H1></html>' % |
918 (code, code, response)) | 928 (code, code, response)) |
919 | 929 |
920 def HandleCreateBirthdayError(self): | 930 def HandleCreateBirthdayError(self): |
921 self.account.ResetStoreBirthday() | 931 self.account.ResetStoreBirthday() |
922 return ( | 932 return ( |
923 200, | 933 200, |
924 '<html><title>Birthday error</title><H1>Birthday error</H1></html>') | 934 '<html><title>Birthday error</title><H1>Birthday error</H1></html>') |
925 | 935 |
| 936 def HandleSetTransientError(self): |
| 937 self.transient_error = True |
| 938 return ( |
| 939 200, |
| 940 '<html><title>Transient error</title><H1>Transient error</H1></html>') |
| 941 |
926 def HandleCommand(self, query, raw_request): | 942 def HandleCommand(self, query, raw_request): |
927 """Decode and handle a sync command from a raw input of bytes. | 943 """Decode and handle a sync command from a raw input of bytes. |
928 | 944 |
929 This is the main entry point for this class. It is safe to call this | 945 This is the main entry point for this class. It is safe to call this |
930 method from multiple threads. | 946 method from multiple threads. |
931 | 947 |
932 Args: | 948 Args: |
933 raw_request: An iterable byte sequence to be interpreted as a sync | 949 raw_request: An iterable byte sequence to be interpreted as a sync |
934 protocol command. | 950 protocol command. |
935 Returns: | 951 Returns: |
936 A tuple (response_code, raw_response); the first value is an HTTP | 952 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 | 953 result code, while the second value is a string of bytes which is the |
938 serialized reply to the command. | 954 serialized reply to the command. |
939 """ | 955 """ |
940 self.account_lock.acquire() | 956 self.account_lock.acquire() |
941 def print_context(direction): | 957 def print_context(direction): |
942 print '[Client %s %s %s.py]' % (self.GetShortClientName(query), direction, | 958 print '[Client %s %s %s.py]' % (self.GetShortClientName(query), direction, |
943 __name__), | 959 __name__), |
944 | 960 |
945 try: | 961 try: |
946 request = sync_pb2.ClientToServerMessage() | 962 request = sync_pb2.ClientToServerMessage() |
947 request.MergeFromString(raw_request) | 963 request.MergeFromString(raw_request) |
948 contents = request.message_contents | 964 contents = request.message_contents |
949 | 965 |
950 response = sync_pb2.ClientToServerResponse() | 966 response = sync_pb2.ClientToServerResponse() |
951 response.error_code = sync_pb2.ClientToServerResponse.SUCCESS | 967 response.error_code = sync_pb2.ClientToServerResponse.SUCCESS |
952 self.CheckStoreBirthday(request) | 968 self.CheckStoreBirthday(request) |
953 response.store_birthday = self.account.store_birthday | 969 response.store_birthday = self.account.store_birthday |
| 970 self.CheckTransientError(); |
954 | 971 |
955 print_context('->') | 972 print_context('->') |
956 | 973 |
957 if contents == sync_pb2.ClientToServerMessage.AUTHENTICATE: | 974 if contents == sync_pb2.ClientToServerMessage.AUTHENTICATE: |
958 print 'Authenticate' | 975 print 'Authenticate' |
959 # We accept any authentication token, and support only one account. | 976 # We accept any authentication token, and support only one account. |
960 # TODO(nick): Mock out the GAIA authentication as well; hook up here. | 977 # TODO(nick): Mock out the GAIA authentication as well; hook up here. |
961 response.authenticate.user.email = 'syncjuser@chromium' | 978 response.authenticate.user.email = 'syncjuser@chromium' |
962 response.authenticate.user.display_name = 'Sync J User' | 979 response.authenticate.user.display_name = 'Sync J User' |
963 elif contents == sync_pb2.ClientToServerMessage.COMMIT: | 980 elif contents == sync_pb2.ClientToServerMessage.COMMIT: |
(...skipping 17 matching lines...) Expand all Loading... |
981 response.migrated_data_type_id[:] = [ | 998 response.migrated_data_type_id[:] = [ |
982 SyncTypeToProtocolDataTypeId(x) for x in error.datatypes] | 999 SyncTypeToProtocolDataTypeId(x) for x in error.datatypes] |
983 return (200, response.SerializeToString()) | 1000 return (200, response.SerializeToString()) |
984 except StoreBirthdayError, error: | 1001 except StoreBirthdayError, error: |
985 print_context('<-') | 1002 print_context('<-') |
986 print 'NOT_MY_BIRTHDAY' | 1003 print 'NOT_MY_BIRTHDAY' |
987 response = sync_pb2.ClientToServerResponse() | 1004 response = sync_pb2.ClientToServerResponse() |
988 response.store_birthday = self.account.store_birthday | 1005 response.store_birthday = self.account.store_birthday |
989 response.error_code = sync_pb2.ClientToServerResponse.NOT_MY_BIRTHDAY | 1006 response.error_code = sync_pb2.ClientToServerResponse.NOT_MY_BIRTHDAY |
990 return (200, response.SerializeToString()) | 1007 return (200, response.SerializeToString()) |
| 1008 except TransientError as error: |
| 1009 print_context('<-') |
| 1010 print 'TRANSIENT_ERROR' |
| 1011 response.store_birthday = self.account.store_birthday |
| 1012 response.error_code = sync_pb2.ClientToServerResponse.TRANSIENT_ERROR |
| 1013 return (200, response.SerializeToString()) |
991 finally: | 1014 finally: |
992 self.account_lock.release() | 1015 self.account_lock.release() |
993 | 1016 |
994 def HandleCommit(self, commit_message, commit_response): | 1017 def HandleCommit(self, commit_message, commit_response): |
995 """Respond to a Commit request by updating the user's account state. | 1018 """Respond to a Commit request by updating the user's account state. |
996 | 1019 |
997 Commit attempts stop after the first error, returning a CONFLICT result | 1020 Commit attempts stop after the first error, returning a CONFLICT result |
998 for any unattempted entries. | 1021 for any unattempted entries. |
999 | 1022 |
1000 Args: | 1023 Args: |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1055 | 1078 |
1056 update_sieve.CheckMigrationState() | 1079 update_sieve.CheckMigrationState() |
1057 | 1080 |
1058 new_timestamp, entries, remaining = self.account.GetChanges(update_sieve) | 1081 new_timestamp, entries, remaining = self.account.GetChanges(update_sieve) |
1059 | 1082 |
1060 update_response.changes_remaining = remaining | 1083 update_response.changes_remaining = remaining |
1061 for entry in entries: | 1084 for entry in entries: |
1062 reply = update_response.entries.add() | 1085 reply = update_response.entries.add() |
1063 reply.CopyFrom(entry) | 1086 reply.CopyFrom(entry) |
1064 update_sieve.SaveProgress(new_timestamp, update_response) | 1087 update_sieve.SaveProgress(new_timestamp, update_response) |
OLD | NEW |