xref: /aosp_15_r20/development/samples/SampleSyncAdapter/samplesyncadapter_server/web_services.py (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker#!/usr/bin/python2.5
2*90c8c64dSAndroid Build Coastguard Worker
3*90c8c64dSAndroid Build Coastguard Worker# Copyright (C) 2010 The Android Open Source Project
4*90c8c64dSAndroid Build Coastguard Worker#
5*90c8c64dSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); you may not
6*90c8c64dSAndroid Build Coastguard Worker# use this file except in compliance with the License. You may obtain a copy of
7*90c8c64dSAndroid Build Coastguard Worker# the License at
8*90c8c64dSAndroid Build Coastguard Worker#
9*90c8c64dSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0
10*90c8c64dSAndroid Build Coastguard Worker#
11*90c8c64dSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*90c8c64dSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13*90c8c64dSAndroid Build Coastguard Worker# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14*90c8c64dSAndroid Build Coastguard Worker# License for the specific language governing permissions and limitations under
15*90c8c64dSAndroid Build Coastguard Worker# the License.
16*90c8c64dSAndroid Build Coastguard Worker
17*90c8c64dSAndroid Build Coastguard Worker"""
18*90c8c64dSAndroid Build Coastguard WorkerHandlers for Sample SyncAdapter services.
19*90c8c64dSAndroid Build Coastguard Worker
20*90c8c64dSAndroid Build Coastguard WorkerContains several RequestHandler subclasses used to handle post operations.
21*90c8c64dSAndroid Build Coastguard WorkerThis script is designed to be run directly as a WSGI application.
22*90c8c64dSAndroid Build Coastguard Worker
23*90c8c64dSAndroid Build Coastguard Worker"""
24*90c8c64dSAndroid Build Coastguard Worker
25*90c8c64dSAndroid Build Coastguard Workerimport cgi
26*90c8c64dSAndroid Build Coastguard Workerimport logging
27*90c8c64dSAndroid Build Coastguard Workerimport time as _time
28*90c8c64dSAndroid Build Coastguard Workerfrom datetime import datetime
29*90c8c64dSAndroid Build Coastguard Workerfrom django.utils import simplejson
30*90c8c64dSAndroid Build Coastguard Workerfrom google.appengine.api import users
31*90c8c64dSAndroid Build Coastguard Workerfrom google.appengine.ext import db
32*90c8c64dSAndroid Build Coastguard Workerfrom google.appengine.ext import webapp
33*90c8c64dSAndroid Build Coastguard Workerfrom model import datastore
34*90c8c64dSAndroid Build Coastguard Workerimport wsgiref.handlers
35*90c8c64dSAndroid Build Coastguard Worker
36*90c8c64dSAndroid Build Coastguard Worker
37*90c8c64dSAndroid Build Coastguard Workerclass BaseWebServiceHandler(webapp.RequestHandler):
38*90c8c64dSAndroid Build Coastguard Worker    """
39*90c8c64dSAndroid Build Coastguard Worker    Base class for our web services. We put some common helper
40*90c8c64dSAndroid Build Coastguard Worker    functions here.
41*90c8c64dSAndroid Build Coastguard Worker    """
42*90c8c64dSAndroid Build Coastguard Worker
43*90c8c64dSAndroid Build Coastguard Worker    """
44*90c8c64dSAndroid Build Coastguard Worker    Since we're only simulating a single user account, declare our
45*90c8c64dSAndroid Build Coastguard Worker    hard-coded credentials here, so that they're easy to see/find.
46*90c8c64dSAndroid Build Coastguard Worker    We actually accept any and all usernames that start with this
47*90c8c64dSAndroid Build Coastguard Worker    hard-coded values. So if ACCT_USER_NAME is 'user', then we'll
48*90c8c64dSAndroid Build Coastguard Worker    accept 'user', 'user75', 'userbuddy', etc, all as legal account
49*90c8c64dSAndroid Build Coastguard Worker    usernames.
50*90c8c64dSAndroid Build Coastguard Worker    """
51*90c8c64dSAndroid Build Coastguard Worker    ACCT_USER_NAME  = 'user'
52*90c8c64dSAndroid Build Coastguard Worker    ACCT_PASSWORD   = 'test'
53*90c8c64dSAndroid Build Coastguard Worker    ACCT_AUTH_TOKEN = 'xyzzy'
54*90c8c64dSAndroid Build Coastguard Worker
55*90c8c64dSAndroid Build Coastguard Worker    DATE_TIME_FORMAT = '%Y/%m/%d %H:%M'
56*90c8c64dSAndroid Build Coastguard Worker
57*90c8c64dSAndroid Build Coastguard Worker    """
58*90c8c64dSAndroid Build Coastguard Worker    Process a request to authenticate a client.  We assume that the username
59*90c8c64dSAndroid Build Coastguard Worker    and password will be included in the request. If successful, we'll return
60*90c8c64dSAndroid Build Coastguard Worker    an authtoken as the only content.  If auth fails, we'll send an "invalid
61*90c8c64dSAndroid Build Coastguard Worker    credentials" error.
62*90c8c64dSAndroid Build Coastguard Worker    We return a boolean indicating whether we were successful (true) or not (false).
63*90c8c64dSAndroid Build Coastguard Worker    In the event that this call fails, we will setup the response, so callers just
64*90c8c64dSAndroid Build Coastguard Worker    need to RETURN in the error case.
65*90c8c64dSAndroid Build Coastguard Worker    """
66*90c8c64dSAndroid Build Coastguard Worker    def authenticate(self):
67*90c8c64dSAndroid Build Coastguard Worker        self.username = self.request.get('username')
68*90c8c64dSAndroid Build Coastguard Worker        self.password = self.request.get('password')
69*90c8c64dSAndroid Build Coastguard Worker
70*90c8c64dSAndroid Build Coastguard Worker        logging.info('Authenticatng username: ' + self.username)
71*90c8c64dSAndroid Build Coastguard Worker
72*90c8c64dSAndroid Build Coastguard Worker        if ((self.username != None) and
73*90c8c64dSAndroid Build Coastguard Worker                (self.username.startswith(BaseWebServiceHandler.ACCT_USER_NAME)) and
74*90c8c64dSAndroid Build Coastguard Worker                (self.password == BaseWebServiceHandler.ACCT_PASSWORD)):
75*90c8c64dSAndroid Build Coastguard Worker            # Authentication was successful - return our hard-coded
76*90c8c64dSAndroid Build Coastguard Worker            # auth-token as the only response.
77*90c8c64dSAndroid Build Coastguard Worker            self.response.set_status(200, 'OK')
78*90c8c64dSAndroid Build Coastguard Worker            self.response.out.write(BaseWebServiceHandler.ACCT_AUTH_TOKEN)
79*90c8c64dSAndroid Build Coastguard Worker            return True
80*90c8c64dSAndroid Build Coastguard Worker        else:
81*90c8c64dSAndroid Build Coastguard Worker            # Authentication failed. Return the standard HTTP auth failure
82*90c8c64dSAndroid Build Coastguard Worker            # response to let the client know.
83*90c8c64dSAndroid Build Coastguard Worker            self.response.set_status(401, 'Invalid Credentials')
84*90c8c64dSAndroid Build Coastguard Worker            return False
85*90c8c64dSAndroid Build Coastguard Worker
86*90c8c64dSAndroid Build Coastguard Worker    """
87*90c8c64dSAndroid Build Coastguard Worker    Validate the credentials of the client for a web service request.
88*90c8c64dSAndroid Build Coastguard Worker    The request should include username/password parameters that correspond
89*90c8c64dSAndroid Build Coastguard Worker    to our hard-coded single account values.
90*90c8c64dSAndroid Build Coastguard Worker    We return a boolean indicating whether we were successful (true) or not (false).
91*90c8c64dSAndroid Build Coastguard Worker    In the event that this call fails, we will setup the response, so callers just
92*90c8c64dSAndroid Build Coastguard Worker    need to RETURN in the error case.
93*90c8c64dSAndroid Build Coastguard Worker    """
94*90c8c64dSAndroid Build Coastguard Worker    def validate(self):
95*90c8c64dSAndroid Build Coastguard Worker        self.username = self.request.get('username')
96*90c8c64dSAndroid Build Coastguard Worker        self.authtoken = self.request.get('authtoken')
97*90c8c64dSAndroid Build Coastguard Worker
98*90c8c64dSAndroid Build Coastguard Worker        logging.info('Validating username: ' + self.username)
99*90c8c64dSAndroid Build Coastguard Worker
100*90c8c64dSAndroid Build Coastguard Worker        if ((self.username != None) and
101*90c8c64dSAndroid Build Coastguard Worker                (self.username.startswith(BaseWebServiceHandler.ACCT_USER_NAME)) and
102*90c8c64dSAndroid Build Coastguard Worker                (self.authtoken == BaseWebServiceHandler.ACCT_AUTH_TOKEN)):
103*90c8c64dSAndroid Build Coastguard Worker            return True
104*90c8c64dSAndroid Build Coastguard Worker        else:
105*90c8c64dSAndroid Build Coastguard Worker            self.response.set_status(401, 'Invalid Credentials')
106*90c8c64dSAndroid Build Coastguard Worker            return False
107*90c8c64dSAndroid Build Coastguard Worker
108*90c8c64dSAndroid Build Coastguard Worker
109*90c8c64dSAndroid Build Coastguard Workerclass Authenticate(BaseWebServiceHandler):
110*90c8c64dSAndroid Build Coastguard Worker    """
111*90c8c64dSAndroid Build Coastguard Worker    Handles requests for login and authentication.
112*90c8c64dSAndroid Build Coastguard Worker
113*90c8c64dSAndroid Build Coastguard Worker    UpdateHandler only accepts post events. It expects each
114*90c8c64dSAndroid Build Coastguard Worker    request to include username and password fields. It returns authtoken
115*90c8c64dSAndroid Build Coastguard Worker    after successful authentication and "invalid credentials" error otherwise.
116*90c8c64dSAndroid Build Coastguard Worker    """
117*90c8c64dSAndroid Build Coastguard Worker
118*90c8c64dSAndroid Build Coastguard Worker    def post(self):
119*90c8c64dSAndroid Build Coastguard Worker        self.authenticate()
120*90c8c64dSAndroid Build Coastguard Worker
121*90c8c64dSAndroid Build Coastguard Worker    def get(self):
122*90c8c64dSAndroid Build Coastguard Worker        """Used for debugging in a browser..."""
123*90c8c64dSAndroid Build Coastguard Worker        self.post()
124*90c8c64dSAndroid Build Coastguard Worker
125*90c8c64dSAndroid Build Coastguard Worker
126*90c8c64dSAndroid Build Coastguard Workerclass SyncContacts(BaseWebServiceHandler):
127*90c8c64dSAndroid Build Coastguard Worker    """Handles requests for fetching user's contacts.
128*90c8c64dSAndroid Build Coastguard Worker
129*90c8c64dSAndroid Build Coastguard Worker    UpdateHandler only accepts post events. It expects each
130*90c8c64dSAndroid Build Coastguard Worker    request to include username and authtoken. If the authtoken is valid
131*90c8c64dSAndroid Build Coastguard Worker    it returns user's contact info in JSON format.
132*90c8c64dSAndroid Build Coastguard Worker    """
133*90c8c64dSAndroid Build Coastguard Worker
134*90c8c64dSAndroid Build Coastguard Worker    def get(self):
135*90c8c64dSAndroid Build Coastguard Worker        """Used for debugging in a browser..."""
136*90c8c64dSAndroid Build Coastguard Worker        self.post()
137*90c8c64dSAndroid Build Coastguard Worker
138*90c8c64dSAndroid Build Coastguard Worker    def post(self):
139*90c8c64dSAndroid Build Coastguard Worker        logging.info('*** Starting contact sync ***')
140*90c8c64dSAndroid Build Coastguard Worker        if (not self.validate()):
141*90c8c64dSAndroid Build Coastguard Worker            return
142*90c8c64dSAndroid Build Coastguard Worker
143*90c8c64dSAndroid Build Coastguard Worker        updated_contacts = []
144*90c8c64dSAndroid Build Coastguard Worker
145*90c8c64dSAndroid Build Coastguard Worker        # Process any client-side changes sent up in the request.
146*90c8c64dSAndroid Build Coastguard Worker        # Any new contacts that were added are included in the
147*90c8c64dSAndroid Build Coastguard Worker        # updated_contacts list, so that we return them to the
148*90c8c64dSAndroid Build Coastguard Worker        # client. That way, the client can see the serverId of
149*90c8c64dSAndroid Build Coastguard Worker        # the newly added contact.
150*90c8c64dSAndroid Build Coastguard Worker        client_buffer = self.request.get('contacts')
151*90c8c64dSAndroid Build Coastguard Worker        if ((client_buffer != None) and (client_buffer != '')):
152*90c8c64dSAndroid Build Coastguard Worker            self.process_client_changes(client_buffer, updated_contacts)
153*90c8c64dSAndroid Build Coastguard Worker
154*90c8c64dSAndroid Build Coastguard Worker        # Add any contacts that have been updated on the server-side
155*90c8c64dSAndroid Build Coastguard Worker        # since the last sync by this client.
156*90c8c64dSAndroid Build Coastguard Worker        client_state = self.request.get('syncstate')
157*90c8c64dSAndroid Build Coastguard Worker        self.get_updated_contacts(client_state, updated_contacts)
158*90c8c64dSAndroid Build Coastguard Worker
159*90c8c64dSAndroid Build Coastguard Worker        logging.info('Returning ' + str(len(updated_contacts)) + ' contact records')
160*90c8c64dSAndroid Build Coastguard Worker
161*90c8c64dSAndroid Build Coastguard Worker        # Return the list of updated contacts to the client
162*90c8c64dSAndroid Build Coastguard Worker        self.response.set_status(200)
163*90c8c64dSAndroid Build Coastguard Worker        self.response.out.write(toJSON(updated_contacts))
164*90c8c64dSAndroid Build Coastguard Worker
165*90c8c64dSAndroid Build Coastguard Worker    def get_updated_contacts(self, client_state, updated_contacts):
166*90c8c64dSAndroid Build Coastguard Worker        logging.info('* Processing server changes')
167*90c8c64dSAndroid Build Coastguard Worker        timestamp = None
168*90c8c64dSAndroid Build Coastguard Worker
169*90c8c64dSAndroid Build Coastguard Worker        base_url = self.request.host_url
170*90c8c64dSAndroid Build Coastguard Worker
171*90c8c64dSAndroid Build Coastguard Worker        # The client sends the last high-water-mark that they successfully
172*90c8c64dSAndroid Build Coastguard Worker        # sync'd to in the syncstate parameter.  It's opaque to them, but
173*90c8c64dSAndroid Build Coastguard Worker        # its actually a seconds-in-unix-epoch timestamp that we use
174*90c8c64dSAndroid Build Coastguard Worker        # as a baseline.
175*90c8c64dSAndroid Build Coastguard Worker        if client_state:
176*90c8c64dSAndroid Build Coastguard Worker            logging.info('Client sync state: ' + client_state)
177*90c8c64dSAndroid Build Coastguard Worker            timestamp = datetime.utcfromtimestamp(float(client_state))
178*90c8c64dSAndroid Build Coastguard Worker
179*90c8c64dSAndroid Build Coastguard Worker        # Keep track of the update/delete counts, so we can log it
180*90c8c64dSAndroid Build Coastguard Worker        # below.  Makes debugging easier...
181*90c8c64dSAndroid Build Coastguard Worker        update_count = 0
182*90c8c64dSAndroid Build Coastguard Worker        delete_count = 0
183*90c8c64dSAndroid Build Coastguard Worker
184*90c8c64dSAndroid Build Coastguard Worker        contacts = datastore.Contact.all()
185*90c8c64dSAndroid Build Coastguard Worker        if contacts:
186*90c8c64dSAndroid Build Coastguard Worker            # Find the high-water mark for the most recently updated friend.
187*90c8c64dSAndroid Build Coastguard Worker            # We'll return this as the syncstate (x) value for all the friends
188*90c8c64dSAndroid Build Coastguard Worker            # we return from this function.
189*90c8c64dSAndroid Build Coastguard Worker            high_water_date = datetime.min
190*90c8c64dSAndroid Build Coastguard Worker            for contact in contacts:
191*90c8c64dSAndroid Build Coastguard Worker                if (contact.updated > high_water_date):
192*90c8c64dSAndroid Build Coastguard Worker                    high_water_date = contact.updated
193*90c8c64dSAndroid Build Coastguard Worker            high_water_mark = str(long(_time.mktime(high_water_date.utctimetuple())) + 1)
194*90c8c64dSAndroid Build Coastguard Worker            logging.info('New sync state: ' + high_water_mark)
195*90c8c64dSAndroid Build Coastguard Worker
196*90c8c64dSAndroid Build Coastguard Worker            # Now build the updated_contacts containing all the friends that have been
197*90c8c64dSAndroid Build Coastguard Worker            # changed since the last sync
198*90c8c64dSAndroid Build Coastguard Worker            for contact in contacts:
199*90c8c64dSAndroid Build Coastguard Worker                # If our list of contacts we're returning already contains this
200*90c8c64dSAndroid Build Coastguard Worker                # contact (for example, it's a contact just uploaded from the client)
201*90c8c64dSAndroid Build Coastguard Worker                # then don't bother processing it any further...
202*90c8c64dSAndroid Build Coastguard Worker                if (self.list_contains_contact(updated_contacts, contact)):
203*90c8c64dSAndroid Build Coastguard Worker                    continue
204*90c8c64dSAndroid Build Coastguard Worker
205*90c8c64dSAndroid Build Coastguard Worker                handle = contact.handle
206*90c8c64dSAndroid Build Coastguard Worker
207*90c8c64dSAndroid Build Coastguard Worker                if timestamp is None or contact.updated > timestamp:
208*90c8c64dSAndroid Build Coastguard Worker                    if contact.deleted == True:
209*90c8c64dSAndroid Build Coastguard Worker                        delete_count = delete_count + 1
210*90c8c64dSAndroid Build Coastguard Worker                        DeletedContactData(updated_contacts, handle, high_water_mark)
211*90c8c64dSAndroid Build Coastguard Worker                    else:
212*90c8c64dSAndroid Build Coastguard Worker                        update_count = update_count + 1
213*90c8c64dSAndroid Build Coastguard Worker                        UpdatedContactData(updated_contacts, handle, None, base_url, high_water_mark)
214*90c8c64dSAndroid Build Coastguard Worker
215*90c8c64dSAndroid Build Coastguard Worker        logging.info('Server-side updates: ' + str(update_count))
216*90c8c64dSAndroid Build Coastguard Worker        logging.info('Server-side deletes: ' + str(delete_count))
217*90c8c64dSAndroid Build Coastguard Worker
218*90c8c64dSAndroid Build Coastguard Worker    def process_client_changes(self, contacts_buffer, updated_contacts):
219*90c8c64dSAndroid Build Coastguard Worker        logging.info('* Processing client changes: ' + self.username)
220*90c8c64dSAndroid Build Coastguard Worker
221*90c8c64dSAndroid Build Coastguard Worker        base_url = self.request.host_url
222*90c8c64dSAndroid Build Coastguard Worker
223*90c8c64dSAndroid Build Coastguard Worker        # Build an array of generic objects containing contact data,
224*90c8c64dSAndroid Build Coastguard Worker        # using the Django built-in JSON parser
225*90c8c64dSAndroid Build Coastguard Worker        logging.info('Uploaded contacts buffer: ' + contacts_buffer)
226*90c8c64dSAndroid Build Coastguard Worker        json_list = simplejson.loads(contacts_buffer)
227*90c8c64dSAndroid Build Coastguard Worker        logging.info('Client-side updates: ' + str(len(json_list)))
228*90c8c64dSAndroid Build Coastguard Worker
229*90c8c64dSAndroid Build Coastguard Worker        # Keep track of the number of new contacts the client sent to us,
230*90c8c64dSAndroid Build Coastguard Worker        # so that we can log it below.
231*90c8c64dSAndroid Build Coastguard Worker        new_contact_count = 0
232*90c8c64dSAndroid Build Coastguard Worker
233*90c8c64dSAndroid Build Coastguard Worker        for jcontact in json_list:
234*90c8c64dSAndroid Build Coastguard Worker            new_contact = False
235*90c8c64dSAndroid Build Coastguard Worker            id = self.safe_attr(jcontact, 'i')
236*90c8c64dSAndroid Build Coastguard Worker            if (id != None):
237*90c8c64dSAndroid Build Coastguard Worker                logging.info('Updating contact: ' + str(id))
238*90c8c64dSAndroid Build Coastguard Worker                contact = datastore.Contact.get(db.Key.from_path('Contact', id))
239*90c8c64dSAndroid Build Coastguard Worker            else:
240*90c8c64dSAndroid Build Coastguard Worker                logging.info('Creating new contact record')
241*90c8c64dSAndroid Build Coastguard Worker                new_contact = True
242*90c8c64dSAndroid Build Coastguard Worker                contact = datastore.Contact(handle='temp')
243*90c8c64dSAndroid Build Coastguard Worker
244*90c8c64dSAndroid Build Coastguard Worker            # If the 'change' for this contact is that they were deleted
245*90c8c64dSAndroid Build Coastguard Worker            # on the client-side, all we want to do is set the deleted
246*90c8c64dSAndroid Build Coastguard Worker            # flag here, and we're done.
247*90c8c64dSAndroid Build Coastguard Worker            if (self.safe_attr(jcontact, 'd') == True):
248*90c8c64dSAndroid Build Coastguard Worker                contact.deleted = True
249*90c8c64dSAndroid Build Coastguard Worker                contact.put()
250*90c8c64dSAndroid Build Coastguard Worker                logging.info('Deleted contact: ' + contact.handle)
251*90c8c64dSAndroid Build Coastguard Worker                continue
252*90c8c64dSAndroid Build Coastguard Worker
253*90c8c64dSAndroid Build Coastguard Worker            contact.firstname = self.safe_attr(jcontact, 'f')
254*90c8c64dSAndroid Build Coastguard Worker            contact.lastname = self.safe_attr(jcontact, 'l')
255*90c8c64dSAndroid Build Coastguard Worker            contact.phone_home = self.safe_attr(jcontact, 'h')
256*90c8c64dSAndroid Build Coastguard Worker            contact.phone_office = self.safe_attr(jcontact, 'o')
257*90c8c64dSAndroid Build Coastguard Worker            contact.phone_mobile = self.safe_attr(jcontact, 'm')
258*90c8c64dSAndroid Build Coastguard Worker            contact.email = self.safe_attr(jcontact, 'e')
259*90c8c64dSAndroid Build Coastguard Worker            contact.deleted = (self.safe_attr(jcontact, 'd') == 'true')
260*90c8c64dSAndroid Build Coastguard Worker            if (new_contact):
261*90c8c64dSAndroid Build Coastguard Worker                # New record - add them to db...
262*90c8c64dSAndroid Build Coastguard Worker                new_contact_count = new_contact_count + 1
263*90c8c64dSAndroid Build Coastguard Worker                contact.handle = contact.firstname + '_' + contact.lastname
264*90c8c64dSAndroid Build Coastguard Worker                logging.info('Created new contact handle: ' + contact.handle)
265*90c8c64dSAndroid Build Coastguard Worker            contact.put()
266*90c8c64dSAndroid Build Coastguard Worker            logging.info('Saved contact: ' + contact.handle)
267*90c8c64dSAndroid Build Coastguard Worker
268*90c8c64dSAndroid Build Coastguard Worker            # We don't save off the client_id value (thus we add it after
269*90c8c64dSAndroid Build Coastguard Worker            # the "put"), but we want it to be in the JSON object we
270*90c8c64dSAndroid Build Coastguard Worker            # serialize out, so that the client can match this contact
271*90c8c64dSAndroid Build Coastguard Worker            # up with the client version.
272*90c8c64dSAndroid Build Coastguard Worker            client_id = self.safe_attr(jcontact, 'c')
273*90c8c64dSAndroid Build Coastguard Worker
274*90c8c64dSAndroid Build Coastguard Worker            # Create a high-water-mark for sync-state from the 'updated' time
275*90c8c64dSAndroid Build Coastguard Worker            # for this contact, so we return the correct value to the client.
276*90c8c64dSAndroid Build Coastguard Worker            high_water = str(long(_time.mktime(contact.updated.utctimetuple())) + 1)
277*90c8c64dSAndroid Build Coastguard Worker
278*90c8c64dSAndroid Build Coastguard Worker            # Add new contacts to our updated_contacts, so that we return them
279*90c8c64dSAndroid Build Coastguard Worker            # to the client (so the client gets the serverId for the
280*90c8c64dSAndroid Build Coastguard Worker            # added contact)
281*90c8c64dSAndroid Build Coastguard Worker            if (new_contact):
282*90c8c64dSAndroid Build Coastguard Worker                UpdatedContactData(updated_contacts, contact.handle, client_id, base_url,
283*90c8c64dSAndroid Build Coastguard Worker                        high_water)
284*90c8c64dSAndroid Build Coastguard Worker
285*90c8c64dSAndroid Build Coastguard Worker        logging.info('Client-side adds: ' + str(new_contact_count))
286*90c8c64dSAndroid Build Coastguard Worker
287*90c8c64dSAndroid Build Coastguard Worker    def list_contains_contact(self, contact_list, contact):
288*90c8c64dSAndroid Build Coastguard Worker        if (contact is None):
289*90c8c64dSAndroid Build Coastguard Worker            return False
290*90c8c64dSAndroid Build Coastguard Worker        contact_id = str(contact.key().id())
291*90c8c64dSAndroid Build Coastguard Worker        for next in contact_list:
292*90c8c64dSAndroid Build Coastguard Worker            if ((next != None) and (next['i'] == contact_id)):
293*90c8c64dSAndroid Build Coastguard Worker                return True
294*90c8c64dSAndroid Build Coastguard Worker        return False
295*90c8c64dSAndroid Build Coastguard Worker
296*90c8c64dSAndroid Build Coastguard Worker    def safe_attr(self, obj, attr_name):
297*90c8c64dSAndroid Build Coastguard Worker        if attr_name in obj:
298*90c8c64dSAndroid Build Coastguard Worker            return obj[attr_name]
299*90c8c64dSAndroid Build Coastguard Worker        return None
300*90c8c64dSAndroid Build Coastguard Worker
301*90c8c64dSAndroid Build Coastguard Workerclass ResetDatabase(BaseWebServiceHandler):
302*90c8c64dSAndroid Build Coastguard Worker    """
303*90c8c64dSAndroid Build Coastguard Worker    Handles cron request to reset the contact database.
304*90c8c64dSAndroid Build Coastguard Worker
305*90c8c64dSAndroid Build Coastguard Worker    We have a weekly cron task that resets the database back to a
306*90c8c64dSAndroid Build Coastguard Worker    few contacts, so that it doesn't grow to an absurd size.
307*90c8c64dSAndroid Build Coastguard Worker    """
308*90c8c64dSAndroid Build Coastguard Worker
309*90c8c64dSAndroid Build Coastguard Worker    def get(self):
310*90c8c64dSAndroid Build Coastguard Worker        # Delete all the existing contacts from the database
311*90c8c64dSAndroid Build Coastguard Worker        contacts = datastore.Contact.all()
312*90c8c64dSAndroid Build Coastguard Worker        for contact in contacts:
313*90c8c64dSAndroid Build Coastguard Worker            contact.delete()
314*90c8c64dSAndroid Build Coastguard Worker
315*90c8c64dSAndroid Build Coastguard Worker        # Now create three sample contacts
316*90c8c64dSAndroid Build Coastguard Worker        contact1 = datastore.Contact(handle = 'juliet',
317*90c8c64dSAndroid Build Coastguard Worker                firstname = 'Juliet',
318*90c8c64dSAndroid Build Coastguard Worker                lastname = 'Capulet',
319*90c8c64dSAndroid Build Coastguard Worker                phone_mobile = '(650) 555-1000',
320*90c8c64dSAndroid Build Coastguard Worker                phone_home = '(650) 555-1001',
321*90c8c64dSAndroid Build Coastguard Worker                status = 'Wherefore art thou Romeo?')
322*90c8c64dSAndroid Build Coastguard Worker        contact1.put()
323*90c8c64dSAndroid Build Coastguard Worker
324*90c8c64dSAndroid Build Coastguard Worker        contact2 = datastore.Contact(handle = 'romeo',
325*90c8c64dSAndroid Build Coastguard Worker                firstname = 'Romeo',
326*90c8c64dSAndroid Build Coastguard Worker                lastname = 'Montague',
327*90c8c64dSAndroid Build Coastguard Worker                phone_mobile = '(650) 555-2000',
328*90c8c64dSAndroid Build Coastguard Worker                phone_home = '(650) 555-2001',
329*90c8c64dSAndroid Build Coastguard Worker                status = 'I dream\'d a dream to-night')
330*90c8c64dSAndroid Build Coastguard Worker        contact2.put()
331*90c8c64dSAndroid Build Coastguard Worker
332*90c8c64dSAndroid Build Coastguard Worker        contact3 = datastore.Contact(handle = 'tybalt',
333*90c8c64dSAndroid Build Coastguard Worker                firstname = 'Tybalt',
334*90c8c64dSAndroid Build Coastguard Worker                lastname = 'Capulet',
335*90c8c64dSAndroid Build Coastguard Worker                phone_mobile = '(650) 555-3000',
336*90c8c64dSAndroid Build Coastguard Worker                phone_home = '(650) 555-3001',
337*90c8c64dSAndroid Build Coastguard Worker                status = 'Have at thee, coward')
338*90c8c64dSAndroid Build Coastguard Worker        contact3.put()
339*90c8c64dSAndroid Build Coastguard Worker
340*90c8c64dSAndroid Build Coastguard Worker
341*90c8c64dSAndroid Build Coastguard Worker
342*90c8c64dSAndroid Build Coastguard Worker
343*90c8c64dSAndroid Build Coastguard Workerdef toJSON(object):
344*90c8c64dSAndroid Build Coastguard Worker    """Dumps the data represented by the object to JSON for wire transfer."""
345*90c8c64dSAndroid Build Coastguard Worker    return simplejson.dumps(object)
346*90c8c64dSAndroid Build Coastguard Worker
347*90c8c64dSAndroid Build Coastguard Workerclass UpdatedContactData(object):
348*90c8c64dSAndroid Build Coastguard Worker    """Holds data for user's contacts.
349*90c8c64dSAndroid Build Coastguard Worker
350*90c8c64dSAndroid Build Coastguard Worker    This class knows how to serialize itself to JSON.
351*90c8c64dSAndroid Build Coastguard Worker    """
352*90c8c64dSAndroid Build Coastguard Worker    __FIELD_MAP = {
353*90c8c64dSAndroid Build Coastguard Worker        'handle': 'u',
354*90c8c64dSAndroid Build Coastguard Worker        'firstname': 'f',
355*90c8c64dSAndroid Build Coastguard Worker        'lastname': 'l',
356*90c8c64dSAndroid Build Coastguard Worker        'status': 's',
357*90c8c64dSAndroid Build Coastguard Worker        'phone_home': 'h',
358*90c8c64dSAndroid Build Coastguard Worker        'phone_office': 'o',
359*90c8c64dSAndroid Build Coastguard Worker        'phone_mobile': 'm',
360*90c8c64dSAndroid Build Coastguard Worker        'email': 'e',
361*90c8c64dSAndroid Build Coastguard Worker        'client_id': 'c'
362*90c8c64dSAndroid Build Coastguard Worker    }
363*90c8c64dSAndroid Build Coastguard Worker
364*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, contact_list, username, client_id, host_url, high_water_mark):
365*90c8c64dSAndroid Build Coastguard Worker        obj = datastore.Contact.get_contact_info(username)
366*90c8c64dSAndroid Build Coastguard Worker        contact = {}
367*90c8c64dSAndroid Build Coastguard Worker        for obj_name, json_name in self.__FIELD_MAP.items():
368*90c8c64dSAndroid Build Coastguard Worker            if hasattr(obj, obj_name):
369*90c8c64dSAndroid Build Coastguard Worker              v = getattr(obj, obj_name)
370*90c8c64dSAndroid Build Coastguard Worker              if (v != None):
371*90c8c64dSAndroid Build Coastguard Worker                  contact[json_name] = str(v)
372*90c8c64dSAndroid Build Coastguard Worker              else:
373*90c8c64dSAndroid Build Coastguard Worker                  contact[json_name] = None
374*90c8c64dSAndroid Build Coastguard Worker        contact['i'] = str(obj.key().id())
375*90c8c64dSAndroid Build Coastguard Worker        contact['a'] = host_url + "/avatar?id=" + str(obj.key().id())
376*90c8c64dSAndroid Build Coastguard Worker        contact['x'] = high_water_mark
377*90c8c64dSAndroid Build Coastguard Worker        if (client_id != None):
378*90c8c64dSAndroid Build Coastguard Worker            contact['c'] = str(client_id)
379*90c8c64dSAndroid Build Coastguard Worker        contact_list.append(contact)
380*90c8c64dSAndroid Build Coastguard Worker
381*90c8c64dSAndroid Build Coastguard Workerclass DeletedContactData(object):
382*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, contact_list, username, high_water_mark):
383*90c8c64dSAndroid Build Coastguard Worker        obj = datastore.Contact.get_contact_info(username)
384*90c8c64dSAndroid Build Coastguard Worker        contact = {}
385*90c8c64dSAndroid Build Coastguard Worker        contact['d'] = 'true'
386*90c8c64dSAndroid Build Coastguard Worker        contact['i'] = str(obj.key().id())
387*90c8c64dSAndroid Build Coastguard Worker        contact['x'] = high_water_mark
388*90c8c64dSAndroid Build Coastguard Worker        contact_list.append(contact)
389*90c8c64dSAndroid Build Coastguard Worker
390*90c8c64dSAndroid Build Coastguard Workerdef main():
391*90c8c64dSAndroid Build Coastguard Worker    application = webapp.WSGIApplication(
392*90c8c64dSAndroid Build Coastguard Worker            [('/auth', Authenticate),
393*90c8c64dSAndroid Build Coastguard Worker             ('/sync', SyncContacts),
394*90c8c64dSAndroid Build Coastguard Worker             ('/reset_database', ResetDatabase),
395*90c8c64dSAndroid Build Coastguard Worker            ],
396*90c8c64dSAndroid Build Coastguard Worker            debug=True)
397*90c8c64dSAndroid Build Coastguard Worker    wsgiref.handlers.CGIHandler().run(application)
398*90c8c64dSAndroid Build Coastguard Worker
399*90c8c64dSAndroid Build Coastguard Workerif __name__ == "__main__":
400*90c8c64dSAndroid Build Coastguard Worker    main()
401