12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""An implementation of the server side of the Chromium sync protocol.
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The details of the protocol are described mostly by comments in the protocol
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)buffer definition at chrome/browser/sync/protocol/sync.proto.
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import base64
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import cgi
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import copy
14a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)import google.protobuf.text_format
15f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import hashlib
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import operator
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import pickle
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import random
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import string
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import threading
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urlparse
24f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import uuid
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import app_list_specifics_pb2
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import app_notification_specifics_pb2
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import app_setting_specifics_pb2
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import app_specifics_pb2
304e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)import article_specifics_pb2
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import autofill_specifics_pb2
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import bookmark_specifics_pb2
33f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import client_commands_pb2
342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import dictionary_specifics_pb2
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import get_updates_caller_info_pb2
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import extension_setting_specifics_pb2
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import extension_specifics_pb2
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import favicon_image_specifics_pb2
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import favicon_tracking_specifics_pb2
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import history_delete_directive_specifics_pb2
41c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import managed_user_setting_specifics_pb2
4290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)import managed_user_specifics_pb2
435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import managed_user_shared_setting_specifics_pb2
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import nigori_specifics_pb2
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import password_specifics_pb2
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import preference_specifics_pb2
47c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import priority_preference_specifics_pb2
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import search_engine_specifics_pb2
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import session_specifics_pb2
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sync_pb2
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sync_enums_pb2
525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import synced_notification_app_info_specifics_pb2
53f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import synced_notification_data_pb2
54f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import synced_notification_render_pb2
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import synced_notification_specifics_pb2
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import theme_specifics_pb2
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import typed_url_specifics_pb2
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# An enumeration of the various kinds of data that can be synced.
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Over the wire, this enumeration is not used: a sync object's type is
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# inferred by which EntitySpecifics field it has.  But in the context
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# of a program, it is useful to have an enumeration.
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ALL_TYPES = (
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    TOP_LEVEL,  # The type of the 'Google Chrome' folder.
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    APPS,
665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    APP_LIST,
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    APP_NOTIFICATION,
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    APP_SETTINGS,
694e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    ARTICLE,
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    AUTOFILL,
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    AUTOFILL_PROFILE,
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    BOOKMARK,
732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    DEVICE_INFO,
742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    DICTIONARY,
752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    EXPERIMENTS,
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    EXTENSIONS,
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    HISTORY_DELETE_DIRECTIVE,
78c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    MANAGED_USER_SETTING,
795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    MANAGED_USER_SHARED_SETTING,
8090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    MANAGED_USER,
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    NIGORI,
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    PASSWORD,
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    PREFERENCE,
84c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    PRIORITY_PREFERENCE,
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SEARCH_ENGINE,
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SESSION,
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    SYNCED_NOTIFICATION,
885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    SYNCED_NOTIFICATION_APP_INFO,
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    THEME,
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    TYPED_URL,
912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    EXTENSION_SETTINGS,
922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    FAVICON_IMAGES,
935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    FAVICON_TRACKING) = range(30)
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# An enumeration on the frequency at which the server should send errors
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# to the client. This would be specified by the url that triggers the error.
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Note: This enum should be kept in the same order as the enum in sync_test.h.
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SYNC_ERROR_FREQUENCY = (
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ERROR_FREQUENCY_NONE,
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ERROR_FREQUENCY_ALWAYS,
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ERROR_FREQUENCY_TWO_THIRDS) = range(3)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Well-known server tag of the top level 'Google Chrome' folder.
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)TOP_LEVEL_FOLDER_TAG = 'google_chrome'
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Given a sync type from ALL_TYPES, find the FieldDescriptor corresponding
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# to that datatype.  Note that TOP_LEVEL has no such token.
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SYNC_TYPE_FIELDS = sync_pb2.EntitySpecifics.DESCRIPTOR.fields_by_name
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SYNC_TYPE_TO_DESCRIPTOR = {
1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    APP_LIST: SYNC_TYPE_FIELDS['app_list'],
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    APP_NOTIFICATION: SYNC_TYPE_FIELDS['app_notification'],
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    APP_SETTINGS: SYNC_TYPE_FIELDS['app_setting'],
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    APPS: SYNC_TYPE_FIELDS['app'],
1144e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    ARTICLE: SYNC_TYPE_FIELDS['article'],
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    AUTOFILL: SYNC_TYPE_FIELDS['autofill'],
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    AUTOFILL_PROFILE: SYNC_TYPE_FIELDS['autofill_profile'],
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    BOOKMARK: SYNC_TYPE_FIELDS['bookmark'],
1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    DEVICE_INFO: SYNC_TYPE_FIELDS['device_info'],
1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    DICTIONARY: SYNC_TYPE_FIELDS['dictionary'],
1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    EXPERIMENTS: SYNC_TYPE_FIELDS['experiments'],
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    EXTENSION_SETTINGS: SYNC_TYPE_FIELDS['extension_setting'],
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    EXTENSIONS: SYNC_TYPE_FIELDS['extension'],
1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    FAVICON_IMAGES: SYNC_TYPE_FIELDS['favicon_image'],
1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    FAVICON_TRACKING: SYNC_TYPE_FIELDS['favicon_tracking'],
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    HISTORY_DELETE_DIRECTIVE: SYNC_TYPE_FIELDS['history_delete_directive'],
1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    MANAGED_USER_SHARED_SETTING:
1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        SYNC_TYPE_FIELDS['managed_user_shared_setting'],
128c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    MANAGED_USER_SETTING: SYNC_TYPE_FIELDS['managed_user_setting'],
12990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    MANAGED_USER: SYNC_TYPE_FIELDS['managed_user'],
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    NIGORI: SYNC_TYPE_FIELDS['nigori'],
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    PASSWORD: SYNC_TYPE_FIELDS['password'],
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    PREFERENCE: SYNC_TYPE_FIELDS['preference'],
133c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    PRIORITY_PREFERENCE: SYNC_TYPE_FIELDS['priority_preference'],
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SEARCH_ENGINE: SYNC_TYPE_FIELDS['search_engine'],
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SESSION: SYNC_TYPE_FIELDS['session'],
1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    SYNCED_NOTIFICATION: SYNC_TYPE_FIELDS["synced_notification"],
1375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    SYNCED_NOTIFICATION_APP_INFO:
1385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        SYNC_TYPE_FIELDS["synced_notification_app_info"],
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    THEME: SYNC_TYPE_FIELDS['theme'],
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    TYPED_URL: SYNC_TYPE_FIELDS['typed_url'],
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The parent ID used to indicate a top-level node.
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ROOT_ID = '0'
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)# Unix time epoch +1 day in struct_time format. The tuple corresponds to
14758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)# UTC Thursday Jan 2 1970, 00:00:00, non-dst.
14858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)# We have to add one day after start of epoch, since in timezones with positive
14958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)# UTC offset time.mktime throws an OverflowError,
15058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)# rather then returning negative number.
15158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)FIRST_DAY_UNIX_TIME_EPOCH = (1970, 1, 2, 0, 0, 0, 4, 2, 0)
15258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)ONE_DAY_SECONDS = 60 * 60 * 24
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The number of characters in the server-generated encryption key.
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)KEYSTORE_KEY_LENGTH = 16
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
157ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch# The hashed client tags for some experiment nodes.
1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)KEYSTORE_ENCRYPTION_EXPERIMENT_TAG = "pis8ZRzh98/MKLtVEio2mr42LQA="
159ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben MurdochPRE_COMMIT_GU_AVOIDANCE_EXPERIMENT_TAG = "Z1xgeh3QUBa50vdEPd8C/4c7jfE="
1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Error(Exception):
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Error class for this module."""
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class ProtobufDataTypeFieldNotUnique(Error):
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """An entry should not have more than one data type present."""
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class DataTypeIdNotRecognized(Error):
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """The requested data type is not recognized."""
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class MigrationDoneError(Error):
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """A server-side migration occurred; clients must re-sync some datatypes.
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Attributes:
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    datatypes: a list of the datatypes (python enum) needing migration.
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, datatypes):
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.datatypes = datatypes
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class StoreBirthdayError(Error):
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """The client sent a birthday that doesn't correspond to this server."""
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class TransientError(Error):
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """The client would be sent a transient error."""
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class SyncInducedError(Error):
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """The client would be sent an error."""
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class InducedErrorFrequencyNotDefined(Error):
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """The error frequency defined is not handled."""
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
200a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)class ClientNotConnectedError(Error):
201a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  """The client is not connected to the server."""
202a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
203a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def GetEntryType(entry):
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Extract the sync type from a SyncEntry.
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry: A SyncEntity protobuf object whose type to determine.
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    An enum value from ALL_TYPES if the entry's type can be determined, or None
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if the type cannot be determined.
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Raises:
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ProtobufDataTypeFieldNotUnique: More than one type was indicated by
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    the entry.
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if entry.server_defined_unique_tag == TOP_LEVEL_FOLDER_TAG:
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return TOP_LEVEL
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  entry_types = GetEntryTypesFromSpecifics(entry.specifics)
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not entry_types:
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return None
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If there is more than one, either there's a bug, or else the caller
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # should use GetEntryTypes.
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if len(entry_types) > 1:
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    raise ProtobufDataTypeFieldNotUnique
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return entry_types[0]
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def GetEntryTypesFromSpecifics(specifics):
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Determine the sync types indicated by an EntitySpecifics's field(s).
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  If the specifics have more than one recognized data type field (as commonly
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  happens with the requested_types field of GetUpdatesMessage), all types
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  will be returned.  Callers must handle the possibility of the returned
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  value having more than one item.
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    specifics: A EntitySpecifics protobuf message whose extensions to
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      enumerate.
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    A list of the sync types (values from ALL_TYPES) associated with each
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    recognized extension of the specifics message.
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return [data_type for data_type, field_descriptor
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          in SYNC_TYPE_TO_DESCRIPTOR.iteritems()
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if specifics.HasField(field_descriptor.name)]
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def SyncTypeToProtocolDataTypeId(data_type):
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Convert from a sync type (python enum) to the protocol's data type id."""
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return SYNC_TYPE_TO_DESCRIPTOR[data_type].number
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def ProtocolDataTypeIdToSyncType(protocol_data_type_id):
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Convert from the protocol's data type id to a sync type (python enum)."""
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for data_type, field_descriptor in SYNC_TYPE_TO_DESCRIPTOR.iteritems():
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if field_descriptor.number == protocol_data_type_id:
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return data_type
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  raise DataTypeIdNotRecognized
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def DataTypeStringToSyncTypeLoose(data_type_string):
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Converts a human-readable string to a sync type (python enum).
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Capitalization and pluralization don't matter; this function is appropriate
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for values that might have been typed by a human being; e.g., command-line
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  flags or query parameters.
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if data_type_string.isdigit():
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return ProtocolDataTypeIdToSyncType(int(data_type_string))
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  name = data_type_string.lower().rstrip('s')
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for data_type, field_descriptor in SYNC_TYPE_TO_DESCRIPTOR.iteritems():
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if field_descriptor.name.lower().rstrip('s') == name:
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return data_type
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  raise DataTypeIdNotRecognized
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def MakeNewKeystoreKey():
2792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  """Returns a new random keystore key."""
2802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return ''.join(random.choice(string.ascii_uppercase + string.digits)
2812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        for x in xrange(KEYSTORE_KEY_LENGTH))
2822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def SyncTypeToString(data_type):
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Formats a sync type enum (from ALL_TYPES) to a human-readable string."""
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return SYNC_TYPE_TO_DESCRIPTOR[data_type].name
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def CallerInfoToString(caller_info_source):
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Formats a GetUpdatesSource enum value to a readable string."""
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return get_updates_caller_info_pb2.GetUpdatesCallerInfo \
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      .DESCRIPTOR.enum_types_by_name['GetUpdatesSource'] \
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      .values_by_number[caller_info_source].name
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def ShortDatatypeListSummary(data_types):
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Formats compactly a list of sync types (python enums) for human eyes.
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  This function is intended for use by logging.  If the list of datatypes
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  contains almost all of the values, the return value will be expressed
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  in terms of the datatypes that aren't set.
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  included = set(data_types) - set([TOP_LEVEL])
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not included:
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 'nothing'
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  excluded = set(ALL_TYPES) - included - set([TOP_LEVEL])
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not excluded:
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 'everything'
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  simple_text = '+'.join(sorted([SyncTypeToString(x) for x in included]))
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  all_but_text = 'all except %s' % (
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      '+'.join(sorted([SyncTypeToString(x) for x in excluded])))
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if len(included) < len(excluded) or len(simple_text) <= len(all_but_text):
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return simple_text
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else:
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return all_but_text
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def GetDefaultEntitySpecifics(data_type):
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Get an EntitySpecifics having a sync type's default field value."""
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  specifics = sync_pb2.EntitySpecifics()
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if data_type in SYNC_TYPE_TO_DESCRIPTOR:
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    descriptor = SYNC_TYPE_TO_DESCRIPTOR[data_type]
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    getattr(specifics, descriptor.name).SetInParent()
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return specifics
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class PermanentItem(object):
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """A specification of one server-created permanent item.
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Attributes:
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    tag: A known-to-the-client value that uniquely identifies a server-created
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      permanent item.
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    name: The human-readable display name for this item.
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    parent_tag: The tag of the permanent item's parent.  If ROOT_ID, indicates
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      a top-level item.  Otherwise, this must be the tag value of some other
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      server-created permanent item.
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sync_type: A value from ALL_TYPES, giving the datatype of this permanent
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      item.  This controls which types of client GetUpdates requests will
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cause the permanent item to be created and returned.
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    create_by_default: Whether the permanent item is created at startup or not.
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      This value is set to True in the default case. Non-default permanent items
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      are those that are created only when a client explicitly tells the server
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      to do so.
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, tag, name, parent_tag, sync_type, create_by_default=True):
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.tag = tag
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.name = name
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.parent_tag = parent_tag
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.sync_type = sync_type
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.create_by_default = create_by_default
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class MigrationHistory(object):
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """A record of the migration events associated with an account.
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Each migration event invalidates one or more datatypes on all clients
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  that had synced the datatype before the event.  Such clients will continue
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  to receive MigrationDone errors until they throw away their progress and
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  re-sync that datatype from the beginning.
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self):
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._migrations = {}
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for datatype in ALL_TYPES:
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._migrations[datatype] = [1]
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._next_migration_version = 2
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetLatestVersion(self, datatype):
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self._migrations[datatype][-1]
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def CheckAllCurrent(self, versions_map):
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Raises an error if any the provided versions are out of date.
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    This function intentionally returns migrations in the order that they were
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    triggered.  Doing it this way allows the client to queue up two migrations
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    in a row, so the second one is received while responding to the first.
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Arguments:
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      version_map: a map whose keys are datatypes and whose values are versions.
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Raises:
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      MigrationDoneError: if a mismatch is found.
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    problems = {}
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for datatype, client_migration in versions_map.iteritems():
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for server_migration in self._migrations[datatype]:
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if client_migration < server_migration:
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          problems.setdefault(server_migration, []).append(datatype)
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if problems:
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise MigrationDoneError(problems[min(problems.keys())])
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Bump(self, datatypes):
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Add a record of a migration, to cause errors on future requests."""
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for idx, datatype in enumerate(datatypes):
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._migrations[datatype].append(self._next_migration_version)
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._next_migration_version += 1
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class UpdateSieve(object):
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """A filter to remove items the client has already seen."""
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, request, migration_history=None):
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._original_request = request
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._state = {}
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._migration_history = migration_history or MigrationHistory()
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._migration_versions_to_check = {}
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if request.from_progress_marker:
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for marker in request.from_progress_marker:
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        data_type = ProtocolDataTypeIdToSyncType(marker.data_type_id)
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if marker.HasField('timestamp_token_for_migration'):
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          timestamp = marker.timestamp_token_for_migration
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if timestamp:
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self._migration_versions_to_check[data_type] = 1
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif marker.token:
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          (timestamp, version) = pickle.loads(marker.token)
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          self._migration_versions_to_check[data_type] = version
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif marker.HasField('token'):
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          timestamp = 0
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        else:
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          raise ValueError('No timestamp information in progress marker.')
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        data_type = ProtocolDataTypeIdToSyncType(marker.data_type_id)
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._state[data_type] = timestamp
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif request.HasField('from_timestamp'):
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for data_type in GetEntryTypesFromSpecifics(request.requested_types):
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._state[data_type] = request.from_timestamp
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._migration_versions_to_check[data_type] = 1
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self._state:
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._state[TOP_LEVEL] = min(self._state.itervalues())
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def SummarizeRequest(self):
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    timestamps = {}
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for data_type, timestamp in self._state.iteritems():
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if data_type == TOP_LEVEL:
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        continue
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      timestamps.setdefault(timestamp, []).append(data_type)
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return ', '.join('<%s>@%d' % (ShortDatatypeListSummary(types), stamp)
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                     for stamp, types in sorted(timestamps.iteritems()))
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def CheckMigrationState(self):
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._migration_history.CheckAllCurrent(self._migration_versions_to_check)
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ClientWantsItem(self, item):
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Return true if the client hasn't already seen an item."""
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self._state.get(GetEntryType(item), sys.maxint) < item.version
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def HasAnyTimestamp(self):
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Return true if at least one datatype was requested."""
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return bool(self._state)
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetMinTimestamp(self):
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Return true the smallest timestamp requested across all datatypes."""
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return min(self._state.itervalues())
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetFirstTimeTypes(self):
4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Return a list of datatypes requesting updates from timestamp zero."""
4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return [datatype for datatype, timestamp in self._state.iteritems()
4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if timestamp == 0]
4575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def GetCreateMobileBookmarks(self):
4592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Return true if the client has requested to create the 'Mobile Bookmarks'
4602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)       folder.
4612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """
4622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return (self._original_request.HasField('create_mobile_bookmarks_folder')
4632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            and self._original_request.create_mobile_bookmarks_folder)
4642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def SaveProgress(self, new_timestamp, get_updates_response):
4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Write the new_timestamp or new_progress_marker fields to a response."""
4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self._original_request.from_progress_marker:
4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for data_type, old_timestamp in self._state.iteritems():
4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if data_type == TOP_LEVEL:
4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          continue
4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        new_marker = sync_pb2.DataTypeProgressMarker()
4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        new_marker.data_type_id = SyncTypeToProtocolDataTypeId(data_type)
4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        final_stamp = max(old_timestamp, new_timestamp)
4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        final_migration = self._migration_history.GetLatestVersion(data_type)
4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        new_marker.token = pickle.dumps((final_stamp, final_migration))
4761e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        get_updates_response.new_progress_marker.add().MergeFrom(new_marker)
4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif self._original_request.HasField('from_timestamp'):
4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if self._original_request.from_timestamp < new_timestamp:
4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        get_updates_response.new_timestamp = new_timestamp
4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class SyncDataModel(object):
4835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Models the account state of one sync user."""
4845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  _BATCH_SIZE = 100
4855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Specify all the permanent items that a model might need.
4875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  _PERMANENT_ITEM_SPECS = [
4882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      PermanentItem('google_chrome_apps', name='Apps',
4892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=APPS),
4905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      PermanentItem('google_chrome_app_list', name='App List',
4915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=APP_LIST),
4922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      PermanentItem('google_chrome_app_notifications', name='App Notifications',
4932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=APP_NOTIFICATION),
4942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      PermanentItem('google_chrome_app_settings',
4952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    name='App Settings',
4962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=APP_SETTINGS),
4975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('google_chrome_bookmarks', name='Bookmarks',
4982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=BOOKMARK),
4995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('bookmark_bar', name='Bookmark Bar',
5005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    parent_tag='google_chrome_bookmarks', sync_type=BOOKMARK),
5015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('other_bookmarks', name='Other Bookmarks',
5025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    parent_tag='google_chrome_bookmarks', sync_type=BOOKMARK),
5035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('synced_bookmarks', name='Synced Bookmarks',
5045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    parent_tag='google_chrome_bookmarks', sync_type=BOOKMARK,
5055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    create_by_default=False),
5065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('google_chrome_autofill', name='Autofill',
5072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=AUTOFILL),
5085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('google_chrome_autofill_profiles', name='Autofill Profiles',
5092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=AUTOFILL_PROFILE),
5102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      PermanentItem('google_chrome_device_info', name='Device Info',
5112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=DEVICE_INFO),
5122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      PermanentItem('google_chrome_experiments', name='Experiments',
5132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=EXPERIMENTS),
5145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('google_chrome_extension_settings',
5155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    name='Extension Settings',
5162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=EXTENSION_SETTINGS),
5175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('google_chrome_extensions', name='Extensions',
5182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=EXTENSIONS),
5195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('google_chrome_history_delete_directives',
5205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    name='History Delete Directives',
5212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID,
5225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    sync_type=HISTORY_DELETE_DIRECTIVE),
5232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      PermanentItem('google_chrome_favicon_images',
5242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    name='Favicon Images',
5252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID,
5262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    sync_type=FAVICON_IMAGES),
5272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      PermanentItem('google_chrome_favicon_tracking',
5282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    name='Favicon Tracking',
5292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID,
5302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    sync_type=FAVICON_TRACKING),
531c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      PermanentItem('google_chrome_managed_user_settings',
532c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                    name='Managed User Settings',
533c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=MANAGED_USER_SETTING),
53490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      PermanentItem('google_chrome_managed_users',
53590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                    name='Managed Users',
53690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=MANAGED_USER),
5375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      PermanentItem('google_chrome_managed_user_shared_settings',
5385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    name='Managed User Shared Settings',
5395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=MANAGED_USER_SHARED_SETTING),
5402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      PermanentItem('google_chrome_nigori', name='Nigori',
5412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=NIGORI),
5425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('google_chrome_passwords', name='Passwords',
5432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=PASSWORD),
5442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      PermanentItem('google_chrome_preferences', name='Preferences',
5452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=PREFERENCE),
546c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      PermanentItem('google_chrome_priority_preferences',
547c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                    name='Priority Preferences',
548c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=PRIORITY_PREFERENCE),
5492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      PermanentItem('google_chrome_synced_notifications',
5502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    name='Synced Notifications',
5512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=SYNCED_NOTIFICATION),
5525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      PermanentItem('google_chrome_synced_notification_app_info',
5535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    name='Synced Notification App Info',
5545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=SYNCED_NOTIFICATION_APP_INFO),
5555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('google_chrome_search_engines', name='Search Engines',
5562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=SEARCH_ENGINE),
5575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('google_chrome_sessions', name='Sessions',
5582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=SESSION),
5595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('google_chrome_themes', name='Themes',
5602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=THEME),
5615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PermanentItem('google_chrome_typed_urls', name='Typed URLs',
5622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=TYPED_URL),
5632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      PermanentItem('google_chrome_dictionary', name='Dictionary',
5642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=DICTIONARY),
5654e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      PermanentItem('google_chrome_articles', name='Articles',
5664e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                    parent_tag=ROOT_ID, sync_type=ARTICLE),
5675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ]
5685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self):
5705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Monotonically increasing version number.  The next object change will
5715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # take on this value + 1.
5725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._version = 0
5735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # The definitive copy of this client's items: a map from ID string to a
5755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # SyncEntity protocol buffer.
5765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._entries = {}
5775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.ResetStoreBirthday()
5795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.migration_history = MigrationHistory()
5805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.induced_error = sync_pb2.ClientToServerResponse.Error()
5815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.induced_error_frequency = 0
5825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.sync_count_before_errors = 0
583868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    self.acknowledge_managed_users = False
5842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self._keys = [MakeNewKeystoreKey()]
5855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _SaveEntry(self, entry):
5875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Insert or update an entry in the change log, and give it a new version.
5885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    The ID fields of this entry are assumed to be valid server IDs.  This
5905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry will be updated with a new version number and sync_timestamp.
5915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
5935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry: The entry to be added or updated.
5945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
5955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._version += 1
5965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Maintain a global (rather than per-item) sequence number and use it
5975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # both as the per-entry version as well as the update-progress timestamp.
5985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # This simulates the behavior of the original server implementation.
5995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry.version = self._version
6005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry.sync_timestamp = self._version
6015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Preserve the originator info, which the client is not required to send
6035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # when updating.
6045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    base_entry = self._entries.get(entry.id_string)
6055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if base_entry:
6065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry.originator_cache_guid = base_entry.originator_cache_guid
6075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry.originator_client_item_id = base_entry.originator_client_item_id
6085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._entries[entry.id_string] = copy.deepcopy(entry)
6105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _ServerTagToId(self, tag):
6125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Determine the server ID from a server-unique tag.
6135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    The resulting value is guaranteed not to collide with the other ID
6155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    generation methods.
6165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
6185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      tag: The unique, known-to-the-client tag of a server-generated item.
6195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
6205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The string value of the computed server ID.
6215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
6225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not tag or tag == ROOT_ID:
6235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return tag
6245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    spec = [x for x in self._PERMANENT_ITEM_SPECS if x.tag == tag][0]
6255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self._MakeCurrentId(spec.sync_type, '<server tag>%s' % tag)
6265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
627116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  def _TypeToTypeRootId(self, model_type):
628116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    """Returns the server ID for the type root node of the given type."""
629116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    tag = [x.tag for x in self._PERMANENT_ITEM_SPECS
630116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch           if x.sync_type == model_type][0]
631116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    return self._ServerTagToId(tag)
632116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
6335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _ClientTagToId(self, datatype, tag):
6345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Determine the server ID from a client-unique tag.
6355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    The resulting value is guaranteed not to collide with the other ID
6375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    generation methods.
6385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
6405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      datatype: The sync type (python enum) of the identified object.
6415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      tag: The unique, opaque-to-the-server tag of a client-tagged item.
6425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
6435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The string value of the computed server ID.
6445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
6455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self._MakeCurrentId(datatype, '<client tag>%s' % tag)
6465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _ClientIdToId(self, datatype, client_guid, client_item_id):
6485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Compute a unique server ID from a client-local ID tag.
6495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    The resulting value is guaranteed not to collide with the other ID
6515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    generation methods.
6525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
6545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      datatype: The sync type (python enum) of the identified object.
6555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      client_guid: A globally unique ID that identifies the client which
6565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        created this item.
6575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      client_item_id: An ID that uniquely identifies this item on the client
6585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        which created it.
6595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
6605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The string value of the computed server ID.
6615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
6625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Using the client ID info is not required here (we could instead generate
6635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # a random ID), but it's useful for debugging.
6645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self._MakeCurrentId(datatype,
6655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        '<server ID originally>%s/%s' % (client_guid, client_item_id))
6665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _MakeCurrentId(self, datatype, inner_id):
6685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return '%d^%d^%s' % (datatype,
6695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                         self.migration_history.GetLatestVersion(datatype),
6705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                         inner_id)
6715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _ExtractIdInfo(self, id_string):
6735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not id_string or id_string == ROOT_ID:
6745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return None
6755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    datatype_string, separator, remainder = id_string.partition('^')
6765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    migration_version_string, separator, inner_id = remainder.partition('^')
6775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (int(datatype_string), int(migration_version_string), inner_id)
6785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _WritePosition(self, entry, parent_id):
6805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Ensure the entry has an absolute, numeric position and parent_id.
6815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Historically, clients would specify positions using the predecessor-based
6835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    references in the insert_after_item_id field; starting July 2011, this
6845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    was changed and Chrome now sends up the absolute position.  The server
6855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    must store a position_in_parent value and must not maintain
6865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    insert_after_item_id.
687c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    Starting in Jan 2013, the client will also send up a unique_position field
688c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    which should be saved and returned on subsequent GetUpdates.
6895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
6915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry: The entry for which to write a position.  Its ID field are
692c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        assumed to be server IDs.  This entry will have its parent_id_string,
693c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        position_in_parent and unique_position fields updated; its
694c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        insert_after_item_id field will be cleared.
6955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      parent_id: The ID of the entry intended as the new parent.
6965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
6975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry.parent_id_string = parent_id
6995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not entry.HasField('position_in_parent'):
7005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry.position_in_parent = 1337  # A debuggable, distinctive default.
7015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry.ClearField('insert_after_item_id')
7025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _ItemExists(self, id_string):
7045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Determine whether an item exists in the changelog."""
7055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return id_string in self._entries
7065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _CreatePermanentItem(self, spec):
7085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Create one permanent item from its spec, if it doesn't exist.
7095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    The resulting item is added to the changelog.
7115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
7135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      spec: A PermanentItem object holding the properties of the item to create.
7145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
7155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    id_string = self._ServerTagToId(spec.tag)
7165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self._ItemExists(id_string):
7175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
7185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print 'Creating permanent item: %s' % spec.name
7195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry = sync_pb2.SyncEntity()
7205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry.id_string = id_string
7215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry.non_unique_name = spec.name
7225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry.name = spec.name
7235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry.server_defined_unique_tag = spec.tag
7245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry.folder = True
7255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry.deleted = False
7265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry.specifics.CopyFrom(GetDefaultEntitySpecifics(spec.sync_type))
7275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._WritePosition(entry, self._ServerTagToId(spec.parent_tag))
7285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._SaveEntry(entry)
7295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _CreateDefaultPermanentItems(self, requested_types):
7315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Ensure creation of all default permanent items for a given set of types.
7325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
7345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      requested_types: A list of sync data types from ALL_TYPES.
7355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        All default permanent items of only these types will be created.
7365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
7375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for spec in self._PERMANENT_ITEM_SPECS:
7385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if spec.sync_type in requested_types and spec.create_by_default:
7395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._CreatePermanentItem(spec)
7405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ResetStoreBirthday(self):
7425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Resets the store birthday to a random value."""
7435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO(nick): uuid.uuid1() is better, but python 2.5 only.
7445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.store_birthday = '%0.30f' % random.random()
7455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def StoreBirthday(self):
7475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Gets the store birthday."""
7485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self.store_birthday
7495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetChanges(self, sieve):
7515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Get entries which have changed, oldest first.
7525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    The returned entries are limited to being _BATCH_SIZE many.  The entries
7545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    are returned in strict version order.
7555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
7575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      sieve: An update sieve to use to filter out updates the client
7585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        has already seen.
7595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
7605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      A tuple of (version, entries, changes_remaining).  Version is a new
7615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      timestamp value, which should be used as the starting point for the
7625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      next query.  Entries is the batch of entries meeting the current
7635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      timestamp query.  Changes_remaining indicates the number of changes
7645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      left on the server after this batch.
7655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
7665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not sieve.HasAnyTimestamp():
7675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (0, [], 0)
7685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    min_timestamp = sieve.GetMinTimestamp()
7692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    first_time_types = sieve.GetFirstTimeTypes()
7702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self._CreateDefaultPermanentItems(first_time_types)
7712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Mobile bookmark folder is not created by default, create it only when
7722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # client requested it.
7732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (sieve.GetCreateMobileBookmarks() and
7742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        first_time_types.count(BOOKMARK) > 0):
7752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.TriggerCreateSyncedBookmarks()
7762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
777868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    self.TriggerAcknowledgeManagedUsers()
778868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
7795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    change_log = sorted(self._entries.values(),
7805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        key=operator.attrgetter('version'))
7815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    new_changes = [x for x in change_log if x.version > min_timestamp]
7825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Pick batch_size new changes, and then filter them.  This matches
7835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # the RPC behavior of the production sync server.
7845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    batch = new_changes[:self._BATCH_SIZE]
7855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not batch:
7865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Client is up to date.
7875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (min_timestamp, [], 0)
7885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Restrict batch to requested types.  Tombstones are untyped
7905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # and will always get included.
7915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    filtered = [copy.deepcopy(item) for item in batch
7925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                if item.deleted or sieve.ClientWantsItem(item)]
7935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # The new client timestamp is the timestamp of the last item in the
7955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # batch, even if that item was filtered out.
7965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (batch[-1].version, filtered, len(new_changes) - len(batch))
7975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def GetKeystoreKeys(self):
7992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Returns the encryption keys for this account."""
8002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    print "Returning encryption keys: %s" % self._keys
8012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return self._keys
8025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _CopyOverImmutableFields(self, entry):
8045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Preserve immutable fields by copying pre-commit state.
8055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
8075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry: A sync entity from the client.
8085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
8095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if entry.id_string in self._entries:
8105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if self._entries[entry.id_string].HasField(
8115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          'server_defined_unique_tag'):
8125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        entry.server_defined_unique_tag = (
8135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self._entries[entry.id_string].server_defined_unique_tag)
8145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _CheckVersionForCommit(self, entry):
8165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Perform an optimistic concurrency check on the version number.
8175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Clients are only allowed to commit if they report having seen the most
8195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    recent version of an object.
8205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
8225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry: A sync entity from the client.  It is assumed that ID fields
8235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        have been converted to server IDs.
8245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
8255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      A boolean value indicating whether the client's version matches the
8265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      newest server version for the given entry.
8275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
8285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if entry.id_string in self._entries:
8295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Allow edits/deletes if the version matches, and any undeletion.
8305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (self._entries[entry.id_string].version == entry.version or
8315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              self._entries[entry.id_string].deleted)
8325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
8335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Allow unknown ID only if the client thinks it's new too.
8345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return entry.version == 0
8355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _CheckParentIdForCommit(self, entry):
8375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Check that the parent ID referenced in a SyncEntity actually exists.
8385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
8405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry: A sync entity from the client.  It is assumed that ID fields
8415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        have been converted to server IDs.
8425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
8435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      A boolean value indicating whether the entity's parent ID is an object
8445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      that actually exists (and is not deleted) in the current account state.
8455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
8465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if entry.parent_id_string == ROOT_ID:
8475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # This is generally allowed.
8485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return True
849116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    if (not entry.HasField('parent_id_string') and
850116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        entry.HasField('client_defined_unique_tag')):
851116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      return True  # Unique client tag items do not need to specify a parent.
8525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if entry.parent_id_string not in self._entries:
8535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print 'Warning: Client sent unknown ID.  Should never happen.'
8545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return False
8555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if entry.parent_id_string == entry.id_string:
8565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print 'Warning: Client sent circular reference.  Should never happen.'
8575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return False
8585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self._entries[entry.parent_id_string].deleted:
8595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # This can happen in a race condition between two clients.
8605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return False
8615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not self._entries[entry.parent_id_string].folder:
8625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print 'Warning: Client sent non-folder parent.  Should never happen.'
8635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return False
8645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return True
8655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _RewriteIdsAsServerIds(self, entry, cache_guid, commit_session):
8675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Convert ID fields in a client sync entry to server IDs.
8685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    A commit batch sent by a client may contain new items for which the
8705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    server has not generated IDs yet.  And within a commit batch, later
8715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    items are allowed to refer to earlier items.  This method will
8725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    generate server IDs for new items, as well as rewrite references
8735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    to items whose server IDs were generated earlier in the batch.
8745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
8765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry: The client sync entry to modify.
8775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cache_guid: The globally unique ID of the client that sent this
8785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        commit request.
8795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      commit_session: A dictionary mapping the original IDs to the new server
8805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        IDs, for any items committed earlier in the batch.
8815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
8825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if entry.version == 0:
8835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      data_type = GetEntryType(entry)
8845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if entry.HasField('client_defined_unique_tag'):
8855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # When present, this should determine the item's ID.
8865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        new_id = self._ClientTagToId(data_type, entry.client_defined_unique_tag)
8875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
8885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        new_id = self._ClientIdToId(data_type, cache_guid, entry.id_string)
8895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        entry.originator_cache_guid = cache_guid
8905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        entry.originator_client_item_id = entry.id_string
8915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      commit_session[entry.id_string] = new_id  # Remember the remapping.
8925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry.id_string = new_id
8935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if entry.parent_id_string in commit_session:
8945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry.parent_id_string = commit_session[entry.parent_id_string]
8955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if entry.insert_after_item_id in commit_session:
8965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry.insert_after_item_id = commit_session[entry.insert_after_item_id]
8975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ValidateCommitEntries(self, entries):
8995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Raise an exception if a commit batch contains any global errors.
9005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Arguments:
9025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entries: an iterable containing commit-form SyncEntity protocol buffers.
9035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Raises:
9055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      MigrationDoneError: if any of the entries reference a recently-migrated
9065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        datatype.
9075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
9085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    server_ids_in_commit = set()
9095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local_ids_in_commit = set()
9105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for entry in entries:
9115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if entry.version:
9125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        server_ids_in_commit.add(entry.id_string)
9135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
9145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        local_ids_in_commit.add(entry.id_string)
9155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if entry.HasField('parent_id_string'):
9165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if entry.parent_id_string not in local_ids_in_commit:
9175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          server_ids_in_commit.add(entry.parent_id_string)
9185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    versions_present = {}
9205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for server_id in server_ids_in_commit:
9215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      parsed = self._ExtractIdInfo(server_id)
9225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if parsed:
9235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        datatype, version, _ = parsed
9245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        versions_present.setdefault(datatype, []).append(version)
9255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.migration_history.CheckAllCurrent(
9275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         dict((k, min(v)) for k, v in versions_present.iteritems()))
9285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def CommitEntry(self, entry, cache_guid, commit_session):
9305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Attempt to commit one entry to the user's account.
9315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
9335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry: A SyncEntity protobuf representing desired object changes.
9345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cache_guid: A string value uniquely identifying the client; this
9355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        is used for ID generation and will determine the originator_cache_guid
9365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if the entry is new.
9375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      commit_session: A dictionary mapping client IDs to server IDs for any
9385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        objects committed earlier this session.  If the entry gets a new ID
9395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        during commit, the change will be recorded here.
9405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
9415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      A SyncEntity reflecting the post-commit value of the entry, or None
9425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if the entry was not committed due to an error.
9435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
9445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry = copy.deepcopy(entry)
9455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Generate server IDs for this entry, and write generated server IDs
9475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # from earlier entries into the message's fields, as appropriate.  The
9485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # ID generation state is stored in 'commit_session'.
9495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._RewriteIdsAsServerIds(entry, cache_guid, commit_session)
9505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
951116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    # Sets the parent ID field for a client-tagged item.  The client is allowed
952116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    # to not specify parents for these types of items.  The server can figure
953116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    # out on its own what the parent ID for this entry should be.
954116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    self._RewriteParentIdForUniqueClientEntry(entry)
955116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
9565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Perform the optimistic concurrency check on the entry's version number.
9575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Clients are not allowed to commit unless they indicate that they've seen
9585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # the most recent version of an object.
9595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not self._CheckVersionForCommit(entry):
9605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return None
9615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Check the validity of the parent ID; it must exist at this point.
9635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO(nick): Implement cycle detection and resolution.
9645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not self._CheckParentIdForCommit(entry):
9655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return None
9665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._CopyOverImmutableFields(entry);
9685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # At this point, the commit is definitely going to happen.
9705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Deletion works by storing a limited record for an entry, called a
9725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # tombstone.  A sync server must track deleted IDs forever, since it does
9735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # not keep track of client knowledge (there's no deletion ACK event).
9745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if entry.deleted:
9751e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)      def MakeTombstone(id_string, datatype):
9765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Make a tombstone entry that will replace the entry being deleted.
9775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        Args:
9795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          id_string: Index of the SyncEntity to be deleted.
9805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        Returns:
9815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          A new SyncEntity reflecting the fact that the entry is deleted.
9825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """
9835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Only the ID, version and deletion state are preserved on a tombstone.
9845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        tombstone = sync_pb2.SyncEntity()
9855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        tombstone.id_string = id_string
9865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        tombstone.deleted = True
9875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        tombstone.name = ''
9881e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        tombstone.specifics.CopyFrom(GetDefaultEntitySpecifics(datatype))
9895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return tombstone
9905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def IsChild(child_id):
9925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Check if a SyncEntity is a child of entry, or any of its children.
9935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        Args:
9955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          child_id: Index of the SyncEntity that is a possible child of entry.
9965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        Returns:
9975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          True if it is a child; false otherwise.
9985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """
9995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if child_id not in self._entries:
10005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          return False
10015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if self._entries[child_id].parent_id_string == entry.id_string:
10025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          return True
10035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return IsChild(self._entries[child_id].parent_id_string)
10045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Identify any children entry might have.
10065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      child_ids = [child.id_string for child in self._entries.itervalues()
10075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   if IsChild(child.id_string)]
10085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Mark all children that were identified as deleted.
10105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for child_id in child_ids:
10111e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        datatype = GetEntryType(self._entries[child_id])
10121e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)        self._SaveEntry(MakeTombstone(child_id, datatype))
10135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Delete entry itself.
10151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)      datatype = GetEntryType(self._entries[entry.id_string])
10161e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)      entry = MakeTombstone(entry.id_string, datatype)
10175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
10185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Comments in sync.proto detail how the representation of positional
1019c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      # ordering works.
1020c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      #
1021c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      # We've almost fully deprecated the 'insert_after_item_id' field.
1022c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      # The 'position_in_parent' field is also deprecated, but as of Jan 2013
1023c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      # is still in common use.  The 'unique_position' field is the latest
1024c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      # and greatest in positioning technology.
1025c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      #
1026c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      # This server supports 'position_in_parent' and 'unique_position'.
10275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._WritePosition(entry, entry.parent_id_string)
10285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Preserve the originator info, which the client is not required to send
10305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # when updating.
10315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    base_entry = self._entries.get(entry.id_string)
10325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if base_entry and not entry.HasField('originator_cache_guid'):
10335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry.originator_cache_guid = base_entry.originator_cache_guid
10345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry.originator_client_item_id = base_entry.originator_client_item_id
10355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Store the current time since the Unix epoch in milliseconds.
10375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entry.mtime = (int((time.mktime(time.gmtime()) -
103858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)        (time.mktime(FIRST_DAY_UNIX_TIME_EPOCH) - ONE_DAY_SECONDS))*1000))
10395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Commit the change.  This also updates the version number.
10415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._SaveEntry(entry)
10425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return entry
10435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _RewriteVersionInId(self, id_string):
10455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Rewrites an ID so that its migration version becomes current."""
10465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    parsed_id = self._ExtractIdInfo(id_string)
10475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not parsed_id:
10485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return id_string
10495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    datatype, old_migration_version, inner_id = parsed_id
10505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self._MakeCurrentId(datatype, inner_id)
10515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1052116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  def _RewriteParentIdForUniqueClientEntry(self, entry):
1053116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    """Sets the entry's parent ID field to the appropriate value.
1054116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
1055116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    The client must always set enough of the specifics of the entries it sends
1056116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    up such that the server can identify its type.  (See crbug.com/373859)
1057116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
1058116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    The client is under no obligation to set the parent ID field.  The server
1059116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    can always infer what the appropriate parent for this model type should be.
1060116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    Having the client not send the parent ID is a step towards the removal of
1061116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    type root nodes.  (See crbug.com/373869)
1062116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
1063116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    This server implements these features by "faking" the existing of a parent
1064116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    ID early on in the commit processing.
1065116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
1066116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    This function has no effect on non-client-tagged items.
1067116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    """
1068116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    if not entry.HasField('client_defined_unique_tag'):
1069116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      return  # Skip this processing for non-client-tagged types.
1070116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    data_type = GetEntryType(entry)
1071116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    entry.parent_id_string = self._TypeToTypeRootId(data_type)
1072116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
10735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def TriggerMigration(self, datatypes):
10745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Cause a migration to occur for a set of datatypes on this account.
10755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Clients will see the MIGRATION_DONE error for these datatypes until they
10775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    resync them.
10785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
10795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    versions_to_remap = self.migration_history.Bump(datatypes)
10805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    all_entries = self._entries.values()
10815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._entries.clear()
10825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for entry in all_entries:
10835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      new_id = self._RewriteVersionInId(entry.id_string)
10845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry.id_string = new_id
10855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if entry.HasField('parent_id_string'):
10865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        entry.parent_id_string = self._RewriteVersionInId(
10875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            entry.parent_id_string)
10885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._entries[entry.id_string] = entry
10895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def TriggerSyncTabFavicons(self):
10915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Set the 'sync_tab_favicons' field to this account's nigori node.
10925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    If the field is not currently set, will write a new nigori node entry
10945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    with the field set. Else does nothing.
10955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
10965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    nigori_tag = "google_chrome_nigori"
10985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    nigori_original = self._entries.get(self._ServerTagToId(nigori_tag))
10995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (nigori_original.specifics.nigori.sync_tab_favicons):
11005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
11015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    nigori_new = copy.deepcopy(nigori_original)
11025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    nigori_new.specifics.nigori.sync_tabs = True
11035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._SaveEntry(nigori_new)
11045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def TriggerCreateSyncedBookmarks(self):
11065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Create the Synced Bookmarks folder under the Bookmarks permanent item.
11075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Clients will then receive the Synced Bookmarks folder on future
11095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    GetUpdates, and new bookmarks can be added within the Synced Bookmarks
11105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    folder.
11115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
11125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    synced_bookmarks_spec, = [spec for spec in self._PERMANENT_ITEM_SPECS
11145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                              if spec.name == "Synced Bookmarks"]
11155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._CreatePermanentItem(synced_bookmarks_spec)
11165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def TriggerEnableKeystoreEncryption(self):
11182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Create the keystore_encryption experiment entity and enable it.
11192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
11202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    A new entity within the EXPERIMENTS datatype is created with the unique
11212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    client tag "keystore_encryption" if it doesn't already exist. The
11222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    keystore_encryption message is then filled with |enabled| set to true.
11232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """
11242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
11252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    experiment_id = self._ServerTagToId("google_chrome_experiments")
11262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    keystore_encryption_id = self._ClientTagToId(
11272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        EXPERIMENTS,
11282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        KEYSTORE_ENCRYPTION_EXPERIMENT_TAG)
11292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    keystore_entry = self._entries.get(keystore_encryption_id)
11302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if keystore_entry is None:
11312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      keystore_entry = sync_pb2.SyncEntity()
11322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      keystore_entry.id_string = keystore_encryption_id
11332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      keystore_entry.name = "Keystore Encryption"
11342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      keystore_entry.client_defined_unique_tag = (
11352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          KEYSTORE_ENCRYPTION_EXPERIMENT_TAG)
11362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      keystore_entry.folder = False
11372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      keystore_entry.deleted = False
11382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      keystore_entry.specifics.CopyFrom(GetDefaultEntitySpecifics(EXPERIMENTS))
11392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self._WritePosition(keystore_entry, experiment_id)
11402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
11412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    keystore_entry.specifics.experiments.keystore_encryption.enabled = True
11422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
11432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self._SaveEntry(keystore_entry)
11442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
11452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def TriggerRotateKeystoreKeys(self):
11462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Rotate the current set of keystore encryption keys.
11472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
11482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    |self._keys| will have a new random encryption key appended to it. We touch
11492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    the nigori node so that each client will receive the new encryption keys
11502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    only once.
11512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """
11522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
11532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Add a new encryption key.
11542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self._keys += [MakeNewKeystoreKey(), ]
11552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
11562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Increment the nigori node's timestamp, so clients will get the new keys
11572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # on their next GetUpdates (any time the nigori node is sent back, we also
11582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # send back the keystore keys).
11592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    nigori_tag = "google_chrome_nigori"
11602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self._SaveEntry(self._entries.get(self._ServerTagToId(nigori_tag)))
11612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1162868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  def TriggerAcknowledgeManagedUsers(self):
1163868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    """Set the "acknowledged" flag for any managed user entities that don't have
1164868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)       it set already.
1165868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    """
1166868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
1167868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    if not self.acknowledge_managed_users:
1168868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      return
1169868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
1170868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    managed_users = [copy.deepcopy(entry) for entry in self._entries.values()
1171868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                     if entry.specifics.HasField('managed_user')
1172868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                     and not entry.specifics.managed_user.acknowledged]
1173868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    for user in managed_users:
1174868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      user.specifics.managed_user.acknowledged = True
1175868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      self._SaveEntry(user)
1176868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
1177ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  def TriggerEnablePreCommitGetUpdateAvoidance(self):
1178ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    """Sets the experiment to enable pre-commit GetUpdate avoidance."""
1179ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    experiment_id = self._ServerTagToId("google_chrome_experiments")
1180ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    pre_commit_gu_avoidance_id = self._ClientTagToId(
1181ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        EXPERIMENTS,
1182ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        PRE_COMMIT_GU_AVOIDANCE_EXPERIMENT_TAG)
1183ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    entry = self._entries.get(pre_commit_gu_avoidance_id)
1184ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    if entry is None:
1185ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      entry = sync_pb2.SyncEntity()
1186ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      entry.id_string = pre_commit_gu_avoidance_id
1187ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      entry.name = "Pre-commit GU avoidance"
1188ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      entry.client_defined_unique_tag = PRE_COMMIT_GU_AVOIDANCE_EXPERIMENT_TAG
1189ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      entry.folder = False
1190ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      entry.deleted = False
1191ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      entry.specifics.CopyFrom(GetDefaultEntitySpecifics(EXPERIMENTS))
1192ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      self._WritePosition(entry, experiment_id)
1193ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    entry.specifics.experiments.pre_commit_update_avoidance.enabled = True
1194ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    self._SaveEntry(entry)
1195ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
11965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def SetInducedError(self, error, error_frequency,
11975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      sync_count_before_errors):
11985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.induced_error = error
11995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.induced_error_frequency = error_frequency
12005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.sync_count_before_errors = sync_count_before_errors
12015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetInducedError(self):
12035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self.induced_error
12045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1205a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  def AddSyncedNotification(self, serialized_notification):
1206f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    """Adds a synced notification to the server data.
1207f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1208f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    The notification will be delivered to the client on the next GetUpdates
1209f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    call.
1210f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1211f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    Args:
1212a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      serialized_notification: A serialized CoalescedSyncedNotification.
1213f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1214f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    Returns:
1215f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      The string representation of the added SyncEntity.
1216a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
1217a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    Raises:
1218a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      ClientNotConnectedError: if the client has not yet connected to this
1219a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      server
1220f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    """
1221f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    # A unique string used wherever a unique ID for this notification is
1222f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    # required.
1223f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    unique_notification_id = str(uuid.uuid4())
1224f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1225f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    specifics = self._CreateSyncedNotificationEntitySpecifics(
1226a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)        unique_notification_id, serialized_notification)
1227f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1228f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    # Create the root SyncEntity representing a single notification.
1229f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    entity = sync_pb2.SyncEntity()
1230f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    entity.specifics.CopyFrom(specifics)
1231f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    entity.parent_id_string = self._ServerTagToId(
1232f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        'google_chrome_synced_notifications')
1233f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    entity.name = 'Synced notification added for testing'
12340529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    entity.version = self._GetNextVersionNumber()
1235f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1236f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    entity.client_defined_unique_tag = self._CreateSyncedNotificationClientTag(
1237f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        specifics.synced_notification.coalesced_notification.key)
1238f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    entity.id_string = self._ClientTagToId(GetEntryType(entity),
12395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                           entity.client_defined_unique_tag)
1240f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1241f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self._entries[entity.id_string] = copy.deepcopy(entity)
1242f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1243a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    return google.protobuf.text_format.MessageToString(entity)
1244f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
12450529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  def _GetNextVersionNumber(self):
12460529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    """Set the version to one more than the greatest version number seen."""
12470529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    entries = sorted(self._entries.values(), key=operator.attrgetter('version'))
12480529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    if len(entries) < 1:
12490529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      raise ClientNotConnectedError
12500529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    return entries[-1].version + 1
12510529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
1252a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  def _CreateSyncedNotificationEntitySpecifics(self, unique_id,
1253a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                                               serialized_notification):
1254f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    """Create the EntitySpecifics proto for a synced notification."""
1255f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    coalesced = synced_notification_data_pb2.CoalescedSyncedNotification()
1256a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    google.protobuf.text_format.Merge(serialized_notification, coalesced)
1257f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1258a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    # Override the provided key so that we have a unique one.
1259a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    coalesced.key = unique_id
1260f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1261f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    specifics = sync_pb2.EntitySpecifics()
1262f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    notification_specifics = \
1263f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        synced_notification_specifics_pb2.SyncedNotificationSpecifics()
1264f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    notification_specifics.coalesced_notification.CopyFrom(coalesced)
1265f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    specifics.synced_notification.CopyFrom(notification_specifics)
1266f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1267f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return specifics
1268f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1269f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def _CreateSyncedNotificationClientTag(self, key):
1270f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    """Create the client_defined_unique_tag value for a SyncedNotification.
1271f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1272f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    Args:
1273f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      key: The entity used to create the client tag.
1274f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1275f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    Returns:
1276f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      The string value of the to be used as the client_defined_unique_tag.
1277f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    """
1278f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    serialized_type = sync_pb2.EntitySpecifics()
1279f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    specifics = synced_notification_specifics_pb2.SyncedNotificationSpecifics()
1280f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    serialized_type.synced_notification.CopyFrom(specifics)
1281f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    hash_input = serialized_type.SerializeToString() + key
1282f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return base64.b64encode(hashlib.sha1(hash_input).digest())
1283f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
12840529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  def AddSyncedNotificationAppInfo(self, app_info):
12850529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    """Adds an app info struct to the server data.
12860529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
12870529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    The notification will be delivered to the client on the next GetUpdates
12880529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    call.
12890529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
12900529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    Args:
12910529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      app_info: A serialized AppInfo.
12920529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
12930529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    Returns:
12940529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      The string representation of the added SyncEntity.
12950529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
12960529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    Raises:
12970529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      ClientNotConnectedError: if the client has not yet connected to this
12980529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      server
12990529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    """
13000529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    specifics = self._CreateSyncedNotificationAppInfoEntitySpecifics(app_info)
13010529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
13020529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    # Create the root SyncEntity representing a single app info protobuf.
13030529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    entity = sync_pb2.SyncEntity()
13040529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    entity.specifics.CopyFrom(specifics)
13050529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    entity.parent_id_string = self._ServerTagToId(
13060529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        'google_chrome_synced_notification_app_info')
13070529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    entity.name = 'App info added for testing'
13080529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    entity.version = self._GetNextVersionNumber()
13090529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
13100529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    # App Infos do not have a strong id, it only needs to be unique.
13110529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    entity.client_defined_unique_tag = "foo"
13120529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    entity.id_string = "foo"
13130529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
13140529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    self._entries[entity.id_string] = copy.deepcopy(entity)
13150529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
13160529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    print "entity before exit is ", entity
13170529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
13180529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    return google.protobuf.text_format.MessageToString(entity)
13190529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
13200529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  def _CreateSyncedNotificationAppInfoEntitySpecifics(
13210529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    self, synced_notification_app_info):
13220529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    """Create the EntitySpecifics proto for a synced notification app info."""
13230529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    # Create a single, empty app_info object
13240529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    app_info = \
13250529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      synced_notification_app_info_specifics_pb2.SyncedNotificationAppInfo()
13260529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    # Fill the app_info object from the text format protobuf.
13270529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    google.protobuf.text_format.Merge(synced_notification_app_info, app_info)
13280529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
13290529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    # Create a new specifics object with a contained app_info
13300529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    specifics = sync_pb2.EntitySpecifics()
13310529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    app_info_specifics = \
13320529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        synced_notification_app_info_specifics_pb2.\
13330529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        SyncedNotificationAppInfoSpecifics()
13340529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
13350529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    # Copy the app info from the text format protobuf
13360529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    contained_app_info = app_info_specifics.synced_notification_app_info.add()
13370529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    contained_app_info.CopyFrom(app_info)
13380529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
13390529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    # And put the new app_info_specifics into the specifics before returning.
13400529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    specifics.synced_notification_app_info.CopyFrom(app_info_specifics)
13410529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
13420529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    return specifics
13430529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
13445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class TestServer(object):
13455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """An object to handle requests for one (and only one) Chrome Sync account.
13465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TestServer consumes the sync command messages that are the outermost
13485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  layers of the protocol, performs the corresponding actions on its
13492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  SyncDataModel, and constructs an appropriate response message.
13505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
13515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self):
13535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # The implementation supports exactly one account; its state is here.
13545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.account = SyncDataModel()
13555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.account_lock = threading.Lock()
13565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Clients that have talked to us: a map from the full client ID
13575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # to its nickname.
13585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.clients = {}
13595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.client_name_generator = ('+' * times + chr(c)
13605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for times in xrange(0, sys.maxint) for c in xrange(ord('A'), ord('Z')))
13615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.transient_error = False
13625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.sync_count = 0
1363f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    # Gaia OAuth2 Token fields and their default values.
1364f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self.response_code = 200
1365f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self.request_token = 'rt1'
1366f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self.access_token = 'at1'
1367f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self.expires_in = 3600
1368f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self.token_type = 'Bearer'
1369f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    # The ClientCommand to send back on each ServerToClientResponse. If set to
1370f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    # None, no ClientCommand should be sent.
1371f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self._client_command = None
1372f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
13735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetShortClientName(self, query):
13755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    parsed = cgi.parse_qs(query[query.find('?')+1:])
13765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    client_id = parsed.get('client_id')
13775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not client_id:
13785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return '?'
13795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    client_id = client_id[0]
13805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if client_id not in self.clients:
13815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.clients[client_id] = self.client_name_generator.next()
13825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self.clients[client_id]
13835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def CheckStoreBirthday(self, request):
13855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Raises StoreBirthdayError if the request's birthday is a mismatch."""
13865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not request.HasField('store_birthday'):
13875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
13885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self.account.StoreBirthday() != request.store_birthday:
13895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise StoreBirthdayError
13905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def CheckTransientError(self):
13925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Raises TransientError if transient_error variable is set."""
13935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self.transient_error:
13945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise TransientError
13955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def CheckSendError(self):
13975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     """Raises SyncInducedError if needed."""
13985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     if (self.account.induced_error.error_type !=
13995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         sync_enums_pb2.SyncEnums.UNKNOWN):
14005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       # Always means return the given error for all requests.
14015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       if self.account.induced_error_frequency == ERROR_FREQUENCY_ALWAYS:
14025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         raise SyncInducedError
14035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       # This means the FIRST 2 requests of every 3 requests
14045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       # return an error. Don't switch the order of failures. There are
14055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       # test cases that rely on the first 2 being the failure rather than
14065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       # the last 2.
14075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       elif (self.account.induced_error_frequency ==
14085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)             ERROR_FREQUENCY_TWO_THIRDS):
14095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         if (((self.sync_count -
14105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               self.account.sync_count_before_errors) % 3) != 0):
14115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)           raise SyncInducedError
14125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       else:
14135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         raise InducedErrorFrequencyNotDefined
14145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def HandleMigrate(self, path):
14165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    query = urlparse.urlparse(path)[4]
14175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    code = 200
14185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.account_lock.acquire()
14195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
14205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      datatypes = [DataTypeStringToSyncTypeLoose(x)
14215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   for x in urlparse.parse_qs(query).get('type',[])]
14225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if datatypes:
14235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.account.TriggerMigration(datatypes)
14245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        response = 'Migrated datatypes %s' % (
14255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            ' and '.join(SyncTypeToString(x).upper() for x in datatypes))
14265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
14275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        response = 'Please specify one or more <i>type=name</i> parameters'
14285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        code = 400
14295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except DataTypeIdNotRecognized, error:
14305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response = 'Could not interpret datatype name'
14315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      code = 400
14325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    finally:
14335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.account_lock.release()
14345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (code, '<html><title>Migration: %d</title><H1>%d %s</H1></html>' %
14355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                (code, code, response))
14365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def HandleSetInducedError(self, path):
14385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     query = urlparse.urlparse(path)[4]
14395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     self.account_lock.acquire()
14405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     code = 200
14415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     response = 'Success'
14425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     error = sync_pb2.ClientToServerResponse.Error()
14435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     try:
14445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       error_type = urlparse.parse_qs(query)['error']
14455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       action = urlparse.parse_qs(query)['action']
14465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       error.error_type = int(error_type[0])
14475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       error.action = int(action[0])
14485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       try:
14495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         error.url = (urlparse.parse_qs(query)['url'])[0]
14505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       except KeyError:
14515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         error.url = ''
14525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       try:
14535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         error.error_description =(
14545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         (urlparse.parse_qs(query)['error_description'])[0])
14555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       except KeyError:
14565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         error.error_description = ''
14575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       try:
14585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         error_frequency = int((urlparse.parse_qs(query)['frequency'])[0])
14595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       except KeyError:
14605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         error_frequency = ERROR_FREQUENCY_ALWAYS
14615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       self.account.SetInducedError(error, error_frequency, self.sync_count)
14625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       response = ('Error = %d, action = %d, url = %s, description = %s' %
14635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   (error.error_type, error.action,
14645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    error.url,
14655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    error.error_description))
14665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     except error:
14675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       response = 'Could not parse url'
14685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       code = 400
14695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     finally:
14705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       self.account_lock.release()
14715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     return (code, '<html><title>SetError: %d</title><H1>%d %s</H1></html>' %
14725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                (code, code, response))
14735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def HandleCreateBirthdayError(self):
14755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.account.ResetStoreBirthday()
14765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (
14775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        200,
14785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        '<html><title>Birthday error</title><H1>Birthday error</H1></html>')
14795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def HandleSetTransientError(self):
14815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.transient_error = True
14825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (
14835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        200,
14845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        '<html><title>Transient error</title><H1>Transient error</H1></html>')
14855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def HandleSetSyncTabFavicons(self):
14875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Set 'sync_tab_favicons' field of the nigori node for this account."""
14885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.account.TriggerSyncTabFavicons()
14895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (
14905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        200,
14915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        '<html><title>Tab Favicons</title><H1>Tab Favicons</H1></html>')
14925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def HandleCreateSyncedBookmarks(self):
14945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Create the Synced Bookmarks folder under Bookmarks."""
14955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.account.TriggerCreateSyncedBookmarks()
14965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (
14975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        200,
14985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        '<html><title>Synced Bookmarks</title><H1>Synced Bookmarks</H1></html>')
14995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def HandleEnableKeystoreEncryption(self):
15012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Enables the keystore encryption experiment."""
15022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.account.TriggerEnableKeystoreEncryption()
15032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return (
15042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        200,
15052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        '<html><title>Enable Keystore Encryption</title>'
15062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            '<H1>Enable Keystore Encryption</H1></html>')
15072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
15082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def HandleRotateKeystoreKeys(self):
15092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Rotate the keystore encryption keys."""
15102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.account.TriggerRotateKeystoreKeys()
15112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return (
15122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        200,
15132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        '<html><title>Rotate Keystore Keys</title>'
15142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            '<H1>Rotate Keystore Keys</H1></html>')
15152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1516868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  def HandleEnableManagedUserAcknowledgement(self):
1517868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    """Enable acknowledging newly created managed users."""
1518868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    self.account.acknowledge_managed_users = True
1519868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return (
1520868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)        200,
1521868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)        '<html><title>Enable Managed User Acknowledgement</title>'
1522868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)            '<h1>Enable Managed User Acknowledgement</h1></html>')
1523868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
1524ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  def HandleEnablePreCommitGetUpdateAvoidance(self):
1525ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    """Enables the pre-commit GU avoidance experiment."""
1526ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    self.account.TriggerEnablePreCommitGetUpdateAvoidance()
1527ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    return (
1528ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        200,
1529ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        '<html><title>Enable pre-commit GU avoidance</title>'
1530ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch            '<H1>Enable pre-commit GU avoidance</H1></html>')
1531ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
15325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def HandleCommand(self, query, raw_request):
15335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Decode and handle a sync command from a raw input of bytes.
15345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    This is the main entry point for this class.  It is safe to call this
15365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    method from multiple threads.
15375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
15395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raw_request: An iterable byte sequence to be interpreted as a sync
15405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        protocol command.
15415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
15425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      A tuple (response_code, raw_response); the first value is an HTTP
15435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      result code, while the second value is a string of bytes which is the
15445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      serialized reply to the command.
15455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
15465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.account_lock.acquire()
15475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.sync_count += 1
15485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def print_context(direction):
15495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print '[Client %s %s %s.py]' % (self.GetShortClientName(query), direction,
15505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                      __name__),
15515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
15535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      request = sync_pb2.ClientToServerMessage()
15545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      request.MergeFromString(raw_request)
15555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      contents = request.message_contents
15565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response = sync_pb2.ClientToServerResponse()
15585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.error_code = sync_enums_pb2.SyncEnums.SUCCESS
1559f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1560f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if self._client_command:
1561f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        response.client_command.CopyFrom(self._client_command)
1562f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
15635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.CheckStoreBirthday(request)
15645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.store_birthday = self.account.store_birthday
15655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.CheckTransientError()
15665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.CheckSendError()
15675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print_context('->')
15695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if contents == sync_pb2.ClientToServerMessage.AUTHENTICATE:
15715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        print 'Authenticate'
15725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # We accept any authentication token, and support only one account.
15735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # TODO(nick): Mock out the GAIA authentication as well; hook up here.
15745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        response.authenticate.user.email = 'syncjuser@chromium'
15755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        response.authenticate.user.display_name = 'Sync J User'
15765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elif contents == sync_pb2.ClientToServerMessage.COMMIT:
15775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        print 'Commit %d item(s)' % len(request.commit.entries)
15785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.HandleCommit(request.commit, response.commit)
15795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elif contents == sync_pb2.ClientToServerMessage.GET_UPDATES:
15805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        print 'GetUpdates',
15815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.HandleGetUpdates(request.get_updates, response.get_updates)
15825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        print_context('<-')
15835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        print '%d update(s)' % len(response.get_updates.entries)
15845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
15855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        print 'Unrecognizable sync request!'
15865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return (400, None)  # Bad request.
15875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (200, response.SerializeToString())
15885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except MigrationDoneError, error:
15895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print_context('<-')
15905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print 'MIGRATION_DONE: <%s>' % (ShortDatatypeListSummary(error.datatypes))
15915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response = sync_pb2.ClientToServerResponse()
15925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.store_birthday = self.account.store_birthday
15935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.error_code = sync_enums_pb2.SyncEnums.MIGRATION_DONE
15945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.migrated_data_type_id[:] = [
15955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          SyncTypeToProtocolDataTypeId(x) for x in error.datatypes]
15965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (200, response.SerializeToString())
15975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except StoreBirthdayError, error:
15985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print_context('<-')
15995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print 'NOT_MY_BIRTHDAY'
16005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response = sync_pb2.ClientToServerResponse()
16015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.store_birthday = self.account.store_birthday
16025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.error_code = sync_enums_pb2.SyncEnums.NOT_MY_BIRTHDAY
16035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (200, response.SerializeToString())
16045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except TransientError, error:
16055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ### This is deprecated now. Would be removed once test cases are removed.
16065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print_context('<-')
16075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print 'TRANSIENT_ERROR'
16085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.store_birthday = self.account.store_birthday
16095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.error_code = sync_enums_pb2.SyncEnums.TRANSIENT_ERROR
16105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (200, response.SerializeToString())
16115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except SyncInducedError, error:
16125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print_context('<-')
16135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print 'INDUCED_ERROR'
16145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.store_birthday = self.account.store_birthday
16155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      error = self.account.GetInducedError()
16165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.error.error_type = error.error_type
16175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.error.url = error.url
16185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.error.error_description = error.error_description
16195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.error.action = error.action
16205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (200, response.SerializeToString())
16215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    finally:
16225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.account_lock.release()
16235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def HandleCommit(self, commit_message, commit_response):
16255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Respond to a Commit request by updating the user's account state.
16265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Commit attempts stop after the first error, returning a CONFLICT result
16285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for any unattempted entries.
16295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
16315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      commit_message: A sync_pb.CommitMessage protobuf holding the content
16325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        of the client's request.
16335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      commit_response: A sync_pb.CommitResponse protobuf into which a reply
16345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        to the client request will be written.
16355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
16365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    commit_response.SetInParent()
16375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    batch_failure = False
16385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    session = {}  # Tracks ID renaming during the commit operation.
16395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    guid = commit_message.cache_guid
16405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.account.ValidateCommitEntries(commit_message.entries)
16425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for entry in commit_message.entries:
16445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      server_entry = None
16455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not batch_failure:
16465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Try to commit the change to the account.
16475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        server_entry = self.account.CommitEntry(entry, guid, session)
16485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # An entryresponse is returned in both success and failure cases.
16505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      reply = commit_response.entryresponse.add()
16515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not server_entry:
16525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        reply.response_type = sync_pb2.CommitResponse.CONFLICT
16535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        reply.error_message = 'Conflict.'
16545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        batch_failure = True  # One failure halts the batch.
16555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
16565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        reply.response_type = sync_pb2.CommitResponse.SUCCESS
16575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # These are the properties that the server is allowed to override
16585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # during commit; the client wants to know their values at the end
16595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # of the operation.
16605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        reply.id_string = server_entry.id_string
16615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if not server_entry.deleted:
16625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # Note: the production server doesn't actually send the
16635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # parent_id_string on commit responses, so we don't either.
16645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          reply.position_in_parent = server_entry.position_in_parent
16655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          reply.version = server_entry.version
16665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          reply.name = server_entry.name
16675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          reply.non_unique_name = server_entry.non_unique_name
16685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        else:
16695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          reply.version = entry.version + 1
16705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def HandleGetUpdates(self, update_request, update_response):
16725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Respond to a GetUpdates request by querying the user's account.
16735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
16755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      update_request: A sync_pb.GetUpdatesMessage protobuf holding the content
16765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        of the client's request.
16775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      update_response: A sync_pb.GetUpdatesResponse protobuf into which a reply
16785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        to the client request will be written.
16795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
16805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    update_response.SetInParent()
16815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    update_sieve = UpdateSieve(update_request, self.account.migration_history)
16825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print CallerInfoToString(update_request.caller_info.source),
16845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print update_sieve.SummarizeRequest()
16855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    update_sieve.CheckMigrationState()
16875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    new_timestamp, entries, remaining = self.account.GetChanges(update_sieve)
16895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    update_response.changes_remaining = remaining
16912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    sending_nigori_node = False
16925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for entry in entries:
16932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if entry.name == 'Nigori':
16942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        sending_nigori_node = True
16955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      reply = update_response.entries.add()
16965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      reply.CopyFrom(entry)
16975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    update_sieve.SaveProgress(new_timestamp, update_response)
16985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if update_request.need_encryption_key or sending_nigori_node:
17002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      update_response.encryption_keys.extend(self.account.GetKeystoreKeys())
1701f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1702f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def HandleGetOauth2Token(self):
1703f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return (int(self.response_code),
1704f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '{\n'
1705f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '  \"refresh_token\": \"' + self.request_token + '\",\n'
1706f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '  \"access_token\": \"' + self.access_token + '\",\n'
1707f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '  \"expires_in\": ' + str(self.expires_in) + ',\n'
1708f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '  \"token_type\": \"' + self.token_type +'\"\n'
1709f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '}')
1710f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1711f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def HandleSetOauth2Token(self, response_code, request_token, access_token,
1712f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                           expires_in, token_type):
1713f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if response_code != 0:
1714f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      self.response_code = response_code
1715f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if request_token != '':
1716f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      self.request_token = request_token
1717f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if access_token != '':
1718f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      self.access_token = access_token
1719f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if expires_in != 0:
1720f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      self.expires_in = expires_in
1721f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if token_type != '':
1722f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      self.token_type = token_type
1723f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1724f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return (200,
1725f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '<html><title>Set OAuth2 Token</title>'
1726f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '<H1>This server will now return the OAuth2 Token:</H1>'
1727f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '<p>response_code: ' + str(self.response_code) + '</p>'
1728f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '<p>request_token: ' + self.request_token + '</p>'
1729f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '<p>access_token: ' + self.access_token + '</p>'
1730f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '<p>expires_in: ' + str(self.expires_in) + '</p>'
1731f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '<p>token_type: ' + self.token_type + '</p>'
1732f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            '</html>')
1733f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1734f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def CustomizeClientCommand(self, sessions_commit_delay_seconds):
1735f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    """Customizes the value of the ClientCommand of ServerToClientResponse.
1736f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1737f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    Currently, this only allows for changing the sessions_commit_delay_seconds
1738f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    field. This is useful for testing in conjunction with
1739f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    AddSyncedNotification so that synced notifications are seen immediately
1740f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    after triggering them with an HTTP call to the test server.
1741f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1742f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    Args:
1743f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      sessions_commit_delay_seconds: The desired sync delay time for sessions.
1744f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    """
1745f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if not self._client_command:
1746f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      self._client_command = client_commands_pb2.ClientCommand()
1747f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1748f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self._client_command.sessions_commit_delay_seconds = \
1749f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        sessions_commit_delay_seconds
1750f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return self._client_command
1751