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