Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """An implementation of the server side of the Chromium sync protocol. | 5 """An implementation of the server side of the Chromium sync protocol. |
| 6 | 6 |
| 7 The details of the protocol are described mostly by comments in the protocol | 7 The details of the protocol are described mostly by comments in the protocol |
| 8 buffer definition at chrome/browser/sync/protocol/sync.proto. | 8 buffer definition at chrome/browser/sync/protocol/sync.proto. |
| 9 """ | 9 """ |
|
ncarter (slow)
2012/02/28 18:45:09
Did you run chromiumsync_test.py? It has pretty go
tim (not reviewing)
2012/03/01 22:07:02
Done.
| |
| 10 | 10 |
| 11 import cgi | 11 import cgi |
| 12 import copy | 12 import copy |
| 13 import operator | 13 import operator |
| 14 import pickle | 14 import pickle |
| 15 import random | 15 import random |
| 16 import sys | 16 import sys |
| 17 import threading | 17 import threading |
| 18 import time | 18 import time |
| 19 import urlparse | 19 import urlparse |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 30 import preference_specifics_pb2 | 30 import preference_specifics_pb2 |
| 31 import search_engine_specifics_pb2 | 31 import search_engine_specifics_pb2 |
| 32 import session_specifics_pb2 | 32 import session_specifics_pb2 |
| 33 import sync_pb2 | 33 import sync_pb2 |
| 34 import sync_enums_pb2 | 34 import sync_enums_pb2 |
| 35 import theme_specifics_pb2 | 35 import theme_specifics_pb2 |
| 36 import typed_url_specifics_pb2 | 36 import typed_url_specifics_pb2 |
| 37 | 37 |
| 38 # An enumeration of the various kinds of data that can be synced. | 38 # An enumeration of the various kinds of data that can be synced. |
| 39 # Over the wire, this enumeration is not used: a sync object's type is | 39 # Over the wire, this enumeration is not used: a sync object's type is |
| 40 # inferred by which EntitySpecifics extension it has. But in the context | 40 # inferred by which EntitySpecifics field it has. But in the context |
| 41 # of a program, it is useful to have an enumeration. | 41 # of a program, it is useful to have an enumeration. |
| 42 ALL_TYPES = ( | 42 ALL_TYPES = ( |
| 43 TOP_LEVEL, # The type of the 'Google Chrome' folder. | 43 TOP_LEVEL, # The type of the 'Google Chrome' folder. |
| 44 APPS, | 44 APPS, |
| 45 APP_NOTIFICATION, | 45 APP_NOTIFICATION, |
| 46 APP_SETTINGS, | 46 APP_SETTINGS, |
| 47 AUTOFILL, | 47 AUTOFILL, |
| 48 AUTOFILL_PROFILE, | 48 AUTOFILL_PROFILE, |
| 49 BOOKMARK, | 49 BOOKMARK, |
| 50 EXTENSIONS, | 50 EXTENSIONS, |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 61 # to the client. This would be specified by the url that triggers the error. | 61 # to the client. This would be specified by the url that triggers the error. |
| 62 # Note: This enum should be kept in the same order as the enum in sync_test.h. | 62 # Note: This enum should be kept in the same order as the enum in sync_test.h. |
| 63 SYNC_ERROR_FREQUENCY = ( | 63 SYNC_ERROR_FREQUENCY = ( |
| 64 ERROR_FREQUENCY_NONE, | 64 ERROR_FREQUENCY_NONE, |
| 65 ERROR_FREQUENCY_ALWAYS, | 65 ERROR_FREQUENCY_ALWAYS, |
| 66 ERROR_FREQUENCY_TWO_THIRDS) = range(3) | 66 ERROR_FREQUENCY_TWO_THIRDS) = range(3) |
| 67 | 67 |
| 68 # Well-known server tag of the top level 'Google Chrome' folder. | 68 # Well-known server tag of the top level 'Google Chrome' folder. |
| 69 TOP_LEVEL_FOLDER_TAG = 'google_chrome' | 69 TOP_LEVEL_FOLDER_TAG = 'google_chrome' |
| 70 | 70 |
| 71 # Given a sync type from ALL_TYPES, find the extension token corresponding | 71 # Given a sync type from ALL_TYPES, find the FieldDescriptor corresponding |
| 72 # to that datatype. Note that TOP_LEVEL has no such token. | 72 # to that datatype. Note that TOP_LEVEL has no such token. |
| 73 SYNC_TYPE_TO_EXTENSION = { | 73 SYNC_TYPE_FIELDS = sync_pb2.EntitySpecifics.DESCRIPTOR.fields_by_name |
| 74 APP_NOTIFICATION: app_notification_specifics_pb2.app_notification, | 74 SYNC_TYPE_TO_DESCRIPTOR = { |
| 75 APP_SETTINGS: app_setting_specifics_pb2.app_setting, | 75 APP_NOTIFICATION: SYNC_TYPE_FIELDS['app_notification'], |
| 76 APPS: app_specifics_pb2.app, | 76 APP_SETTINGS: SYNC_TYPE_FIELDS['app_setting'], |
| 77 AUTOFILL: autofill_specifics_pb2.autofill, | 77 APPS: SYNC_TYPE_FIELDS['app'], |
| 78 AUTOFILL_PROFILE: autofill_specifics_pb2.autofill_profile, | 78 AUTOFILL: SYNC_TYPE_FIELDS['autofill'], |
| 79 BOOKMARK: bookmark_specifics_pb2.bookmark, | 79 AUTOFILL_PROFILE: SYNC_TYPE_FIELDS['autofill_profile'], |
| 80 EXTENSION_SETTINGS: extension_setting_specifics_pb2.extension_setting, | 80 BOOKMARK: SYNC_TYPE_FIELDS['bookmark'], |
| 81 EXTENSIONS: extension_specifics_pb2.extension, | 81 EXTENSION_SETTINGS: SYNC_TYPE_FIELDS['extension_setting'], |
| 82 NIGORI: nigori_specifics_pb2.nigori, | 82 EXTENSIONS: SYNC_TYPE_FIELDS['extension'], |
| 83 PASSWORD: password_specifics_pb2.password, | 83 NIGORI: SYNC_TYPE_FIELDS['nigori'], |
| 84 PREFERENCE: preference_specifics_pb2.preference, | 84 PASSWORD: SYNC_TYPE_FIELDS['password'], |
| 85 SEARCH_ENGINE: search_engine_specifics_pb2.search_engine, | 85 PREFERENCE: SYNC_TYPE_FIELDS['preference'], |
| 86 SESSION: session_specifics_pb2.session, | 86 SEARCH_ENGINE: SYNC_TYPE_FIELDS['search_engine'], |
| 87 THEME: theme_specifics_pb2.theme, | 87 SESSION: SYNC_TYPE_FIELDS['session'], |
| 88 TYPED_URL: typed_url_specifics_pb2.typed_url, | 88 THEME: SYNC_TYPE_FIELDS['theme'], |
| 89 TYPED_URL: SYNC_TYPE_FIELDS['typed_url'], | |
| 89 } | 90 } |
| 90 | 91 |
| 91 # The parent ID used to indicate a top-level node. | 92 # The parent ID used to indicate a top-level node. |
| 92 ROOT_ID = '0' | 93 ROOT_ID = '0' |
| 93 | 94 |
| 94 # Unix time epoch in struct_time format. The tuple corresponds to UTC Wednesday | 95 # Unix time epoch in struct_time format. The tuple corresponds to UTC Wednesday |
| 95 # Jan 1 1970, 00:00:00, non-dst. | 96 # Jan 1 1970, 00:00:00, non-dst. |
| 96 UNIX_TIME_EPOCH = (1970, 1, 1, 0, 0, 0, 3, 1, 0) | 97 UNIX_TIME_EPOCH = (1970, 1, 1, 0, 0, 0, 3, 1, 0) |
| 97 | 98 |
| 98 class Error(Exception): | 99 class Error(Exception): |
| 99 """Error class for this module.""" | 100 """Error class for this module.""" |
| 100 | 101 |
| 101 | 102 |
| 102 class ProtobufExtensionNotUnique(Error): | 103 class ProtobufDataTypeFieldNotUnique(Error): |
| 103 """An entry should not have more than one protobuf extension present.""" | 104 """An entry should not have more than one data type present.""" |
| 104 | 105 |
| 105 | 106 |
| 106 class DataTypeIdNotRecognized(Error): | 107 class DataTypeIdNotRecognized(Error): |
| 107 """The requested data type is not recognized.""" | 108 """The requested data type is not recognized.""" |
| 108 | 109 |
| 109 | 110 |
| 110 class MigrationDoneError(Error): | 111 class MigrationDoneError(Error): |
| 111 """A server-side migration occurred; clients must re-sync some datatypes. | 112 """A server-side migration occurred; clients must re-sync some datatypes. |
| 112 | 113 |
| 113 Attributes: | 114 Attributes: |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 136 | 137 |
| 137 def GetEntryType(entry): | 138 def GetEntryType(entry): |
| 138 """Extract the sync type from a SyncEntry. | 139 """Extract the sync type from a SyncEntry. |
| 139 | 140 |
| 140 Args: | 141 Args: |
| 141 entry: A SyncEntity protobuf object whose type to determine. | 142 entry: A SyncEntity protobuf object whose type to determine. |
| 142 Returns: | 143 Returns: |
| 143 An enum value from ALL_TYPES if the entry's type can be determined, or None | 144 An enum value from ALL_TYPES if the entry's type can be determined, or None |
| 144 if the type cannot be determined. | 145 if the type cannot be determined. |
| 145 Raises: | 146 Raises: |
| 146 ProtobufExtensionNotUnique: More than one type was indicated by the entry. | 147 ProtobufDataTypeFieldNotUnique: More than one type was indicated by |
| 148 the entry. | |
| 147 """ | 149 """ |
| 148 if entry.server_defined_unique_tag == TOP_LEVEL_FOLDER_TAG: | 150 if entry.server_defined_unique_tag == TOP_LEVEL_FOLDER_TAG: |
| 149 return TOP_LEVEL | 151 return TOP_LEVEL |
| 150 entry_types = GetEntryTypesFromSpecifics(entry.specifics) | 152 entry_types = GetEntryTypesFromSpecifics(entry.specifics) |
| 151 if not entry_types: | 153 if not entry_types: |
| 152 return None | 154 return None |
| 153 | 155 |
| 154 # If there is more than one, either there's a bug, or else the caller | 156 # If there is more than one, either there's a bug, or else the caller |
| 155 # should use GetEntryTypes. | 157 # should use GetEntryTypes. |
| 156 if len(entry_types) > 1: | 158 if len(entry_types) > 1: |
| 157 raise ProtobufExtensionNotUnique | 159 raise ProtobufDataTypeFieldNotUnique |
| 158 return entry_types[0] | 160 return entry_types[0] |
| 159 | 161 |
| 160 | 162 |
| 161 def GetEntryTypesFromSpecifics(specifics): | 163 def GetEntryTypesFromSpecifics(specifics): |
| 162 """Determine the sync types indicated by an EntitySpecifics's extension(s). | 164 """Determine the sync types indicated by an EntitySpecifics's field(s). |
| 163 | 165 |
| 164 If the specifics have more than one recognized extension (as commonly | 166 If the specifics have more than one recognized data type field (as commonly |
| 165 happens with the requested_types field of GetUpdatesMessage), all types | 167 happens with the requested_types field of GetUpdatesMessage), all types |
| 166 will be returned. Callers must handle the possibility of the returned | 168 will be returned. Callers must handle the possibility of the returned |
| 167 value having more than one item. | 169 value having more than one item. |
| 168 | 170 |
| 169 Args: | 171 Args: |
| 170 specifics: A EntitySpecifics protobuf message whose extensions to | 172 specifics: A EntitySpecifics protobuf message whose extensions to |
| 171 enumerate. | 173 enumerate. |
| 172 Returns: | 174 Returns: |
| 173 A list of the sync types (values from ALL_TYPES) associated with each | 175 A list of the sync types (values from ALL_TYPES) associated with each |
| 174 recognized extension of the specifics message. | 176 recognized extension of the specifics message. |
| 175 """ | 177 """ |
| 176 return [data_type for data_type, extension | 178 return [data_type for data_type, field_descriptor |
| 177 in SYNC_TYPE_TO_EXTENSION.iteritems() | 179 in SYNC_TYPE_TO_DESCRIPTOR.iteritems() |
| 178 if specifics.HasExtension(extension)] | 180 if specifics.HasField(field_descriptor.name)] |
| 179 | 181 |
| 180 | 182 |
| 181 def SyncTypeToProtocolDataTypeId(data_type): | 183 def SyncTypeToProtocolDataTypeId(data_type): |
| 182 """Convert from a sync type (python enum) to the protocol's data type id.""" | 184 """Convert from a sync type (python enum) to the protocol's data type id.""" |
| 183 return SYNC_TYPE_TO_EXTENSION[data_type].number | 185 return SYNC_TYPE_TO_DESCRIPTOR[data_type].number |
| 184 | 186 |
| 185 | 187 |
| 186 def ProtocolDataTypeIdToSyncType(protocol_data_type_id): | 188 def ProtocolDataTypeIdToSyncType(protocol_data_type_id): |
| 187 """Convert from the protocol's data type id to a sync type (python enum).""" | 189 """Convert from the protocol's data type id to a sync type (python enum).""" |
| 188 for data_type, protocol_extension in SYNC_TYPE_TO_EXTENSION.iteritems(): | 190 for data_type, field_descriptor in SYNC_TYPE_TO_DESCRIPTOR.iteritems(): |
| 189 if protocol_extension.number == protocol_data_type_id: | 191 if field_descriptor.number == protocol_data_type_id: |
| 190 return data_type | 192 return data_type |
| 191 raise DataTypeIdNotRecognized | 193 raise DataTypeIdNotRecognized |
| 192 | 194 |
| 193 | 195 |
| 194 def DataTypeStringToSyncTypeLoose(data_type_string): | 196 def DataTypeStringToSyncTypeLoose(data_type_string): |
| 195 """Converts a human-readable string to a sync type (python enum). | 197 """Converts a human-readable string to a sync type (python enum). |
| 196 | 198 |
| 197 Capitalization and pluralization don't matter; this function is appropriate | 199 Capitalization and pluralization don't matter; this function is appropriate |
| 198 for values that might have been typed by a human being; e.g., command-line | 200 for values that might have been typed by a human being; e.g., command-line |
| 199 flags or query parameters. | 201 flags or query parameters. |
| 200 """ | 202 """ |
| 201 if data_type_string.isdigit(): | 203 if data_type_string.isdigit(): |
| 202 return ProtocolDataTypeIdToSyncType(int(data_type_string)) | 204 return ProtocolDataTypeIdToSyncType(int(data_type_string)) |
| 203 name = data_type_string.lower().rstrip('s') | 205 name = data_type_string.lower().rstrip('s') |
| 204 for data_type, protocol_extension in SYNC_TYPE_TO_EXTENSION.iteritems(): | 206 for data_type, field_descriptor in SYNC_TYPE_TO_DESCRIPTOR.iteritems(): |
| 205 if protocol_extension.name.lower().rstrip('s') == name: | 207 if field_descriptor.name.lower().rstrip('s') == name: |
| 206 return data_type | 208 return data_type |
| 207 raise DataTypeIdNotRecognized | 209 raise DataTypeIdNotRecognized |
| 208 | 210 |
| 209 | 211 |
| 210 def SyncTypeToString(data_type): | 212 def SyncTypeToString(data_type): |
| 211 """Formats a sync type enum (from ALL_TYPES) to a human-readable string.""" | 213 """Formats a sync type enum (from ALL_TYPES) to a human-readable string.""" |
| 212 return SYNC_TYPE_TO_EXTENSION[data_type].name | 214 return SYNC_TYPE_TO_DESCRIPTOR[data_type].name |
| 213 | 215 |
| 214 | 216 |
| 215 def CallerInfoToString(caller_info_source): | 217 def CallerInfoToString(caller_info_source): |
| 216 """Formats a GetUpdatesSource enum value to a readable string.""" | 218 """Formats a GetUpdatesSource enum value to a readable string.""" |
| 217 return sync_pb2.GetUpdatesCallerInfo.DESCRIPTOR.enum_types_by_name[ | 219 return sync_pb2.GetUpdatesCallerInfo.DESCRIPTOR.enum_types_by_name[ |
| 218 'GetUpdatesSource'].values_by_number[caller_info_source].name | 220 'GetUpdatesSource'].values_by_number[caller_info_source].name |
| 219 | 221 |
| 220 | 222 |
| 221 def ShortDatatypeListSummary(data_types): | 223 def ShortDatatypeListSummary(data_types): |
| 222 """Formats compactly a list of sync types (python enums) for human eyes. | 224 """Formats compactly a list of sync types (python enums) for human eyes. |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 234 simple_text = '+'.join(sorted([SyncTypeToString(x) for x in included])) | 236 simple_text = '+'.join(sorted([SyncTypeToString(x) for x in included])) |
| 235 all_but_text = 'all except %s' % ( | 237 all_but_text = 'all except %s' % ( |
| 236 '+'.join(sorted([SyncTypeToString(x) for x in excluded]))) | 238 '+'.join(sorted([SyncTypeToString(x) for x in excluded]))) |
| 237 if len(included) < len(excluded) or len(simple_text) <= len(all_but_text): | 239 if len(included) < len(excluded) or len(simple_text) <= len(all_but_text): |
| 238 return simple_text | 240 return simple_text |
| 239 else: | 241 else: |
| 240 return all_but_text | 242 return all_but_text |
| 241 | 243 |
| 242 | 244 |
| 243 def GetDefaultEntitySpecifics(data_type): | 245 def GetDefaultEntitySpecifics(data_type): |
| 244 """Get an EntitySpecifics having a sync type's default extension value.""" | 246 """Get an EntitySpecifics having a sync type's default field value.""" |
| 245 specifics = sync_pb2.EntitySpecifics() | 247 specifics = sync_pb2.EntitySpecifics() |
| 246 if data_type in SYNC_TYPE_TO_EXTENSION: | 248 if data_type in SYNC_TYPE_TO_DESCRIPTOR: |
| 247 extension_handle = SYNC_TYPE_TO_EXTENSION[data_type] | 249 descriptor_handle = SYNC_TYPE_TO_DESCRIPTOR[data_type] |
|
ncarter (slow)
2012/02/28 18:45:09
Drop the "_handle." Just "descriptor." It's cleane
tim (not reviewing)
2012/03/01 22:07:02
Done.
| |
| 248 specifics.Extensions[extension_handle].SetInParent() | 250 getattr(specifics, descriptor_handle.name).SetInParent() |
| 249 return specifics | 251 return specifics |
| 250 | 252 |
| 251 | 253 |
| 252 class PermanentItem(object): | 254 class PermanentItem(object): |
| 253 """A specification of one server-created permanent item. | 255 """A specification of one server-created permanent item. |
| 254 | 256 |
| 255 Attributes: | 257 Attributes: |
| 256 tag: A known-to-the-client value that uniquely identifies a server-created | 258 tag: A known-to-the-client value that uniquely identifies a server-created |
| 257 permanent item. | 259 permanent item. |
| 258 name: The human-readable display name for this item. | 260 name: The human-readable display name for this item. |
| (...skipping 650 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 909 | 911 |
| 910 def TriggerSyncTabs(self): | 912 def TriggerSyncTabs(self): |
| 911 """Set the 'sync_tabs' field to this account's nigori node. | 913 """Set the 'sync_tabs' field to this account's nigori node. |
| 912 | 914 |
| 913 If the field is not currently set, will write a new nigori node entry | 915 If the field is not currently set, will write a new nigori node entry |
| 914 with the field set. Else does nothing. | 916 with the field set. Else does nothing. |
| 915 """ | 917 """ |
| 916 | 918 |
| 917 nigori_tag = "google_chrome_nigori" | 919 nigori_tag = "google_chrome_nigori" |
| 918 nigori_original = self._entries.get(self._ServerTagToId(nigori_tag)) | 920 nigori_original = self._entries.get(self._ServerTagToId(nigori_tag)) |
| 919 if (nigori_original.specifics.Extensions[nigori_specifics_pb2.nigori]. | 921 if (nigori_original.specifics.nigori.sync_tabs): |
| 920 sync_tabs): | |
| 921 return | 922 return |
| 922 nigori_new = copy.deepcopy(nigori_original) | 923 nigori_new = copy.deepcopy(nigori_original) |
| 923 nigori_new.specifics.Extensions[nigori_specifics_pb2.nigori].sync_tabs = ( | 924 nigori_new.specifics.nigori.sync_tabs = True |
| 924 True) | |
| 925 self._SaveEntry(nigori_new) | 925 self._SaveEntry(nigori_new) |
| 926 | 926 |
| 927 def SetInducedError(self, error, error_frequency, | 927 def SetInducedError(self, error, error_frequency, |
| 928 sync_count_before_errors): | 928 sync_count_before_errors): |
| 929 self.induced_error = error | 929 self.induced_error = error |
| 930 self.induced_error_frequency = error_frequency | 930 self.induced_error_frequency = error_frequency |
| 931 self.sync_count_before_errors = sync_count_before_errors | 931 self.sync_count_before_errors = sync_count_before_errors |
| 932 | 932 |
| 933 def GetInducedError(self): | 933 def GetInducedError(self): |
| 934 return self.induced_error | 934 return self.induced_error |
| (...skipping 290 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1225 | 1225 |
| 1226 update_sieve.CheckMigrationState() | 1226 update_sieve.CheckMigrationState() |
| 1227 | 1227 |
| 1228 new_timestamp, entries, remaining = self.account.GetChanges(update_sieve) | 1228 new_timestamp, entries, remaining = self.account.GetChanges(update_sieve) |
| 1229 | 1229 |
| 1230 update_response.changes_remaining = remaining | 1230 update_response.changes_remaining = remaining |
| 1231 for entry in entries: | 1231 for entry in entries: |
| 1232 reply = update_response.entries.add() | 1232 reply = update_response.entries.add() |
| 1233 reply.CopyFrom(entry) | 1233 reply.CopyFrom(entry) |
| 1234 update_sieve.SaveProgress(new_timestamp, update_response) | 1234 update_sieve.SaveProgress(new_timestamp, update_response) |
| OLD | NEW |