| 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 |