public
Description: Address Book tool
Homepage: http://addressbooker.appspot.com/
Clone URL: git://github.com/bradfitz/addressbooker.git
addressbooker / addressbooker.py
0ed12f30 » Brad Fitzpatrick 2008-11-28 forked feedfetcher into add... 1 # Copyright (C) 2008 Brad Fitzpatrick
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15
16 __author__ = 'brad@danga.com (Brad Fitzpatrick)'
17
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 18 # Core Python
f3be64f3 » Brad Fitzpatrick 2008-11-29 remove unused crap, and add... 19 import cgi
20 import logging
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 21 import pprint
f3be64f3 » Brad Fitzpatrick 2008-11-29 remove unused crap, and add... 22 import random
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 23 import re
f3be64f3 » Brad Fitzpatrick 2008-11-29 remove unused crap, and add... 24 import urllib
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 25
26 # Core/AppEngine stuff
0ed12f30 » Brad Fitzpatrick 2008-11-28 forked feedfetcher into add... 27 import wsgiref.handlers
28 from google.appengine.api import users
29 from google.appengine.ext import webapp
30 from google.appengine.ext import db
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 31 from google.appengine.ext.webapp import template
0ed12f30 » Brad Fitzpatrick 2008-11-28 forked feedfetcher into add... 32 from google.appengine.api import urlfetch
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 33
34 # Libraries included w/ app
0ed12f30 » Brad Fitzpatrick 2008-11-28 forked feedfetcher into add... 35 import atom
36 import atom.http_interface
37 import atom.token_store
38 import atom.url
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 39 import gdata.alt.appengine
40 import gdata.auth
41 import gdata.contacts.service as contactsservice
42 import gdata.service
0ed12f30 » Brad Fitzpatrick 2008-11-28 forked feedfetcher into add... 43 import simplejson
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 44
45 # App stuff
46 import settings
47 import models
48
49 VALID_HANDLE = re.compile(r"^\w+$")
50
0ed12f30 » Brad Fitzpatrick 2008-11-28 forked feedfetcher into add... 51
f2b7da9b » Brad Fitzpatrick 2008-11-29 find candidates for merging. 52 def NumberSuffixesMatch(num1, num2):
53 """Given two phone numbers, return bool if they match.
54
55 Numbers are strings. A match is 7 matching final
56 numbers, ignoring punctuations and space and stuff.
57 """
58 num1 = re.sub(r"[^\d]", "", num1)
59 num2 = re.sub(r"[^\d]", "", num2)
60 if len(num1) < 6 or len(num2) < 6:
61 return False
62 return num1[-7:] == num2[-7:]
63
64
68e2b7b8 » Brad Fitzpatrick 2008-11-30 proper updating 65 def PhoneNumberListContainsNumber(number_list, number):
66 """Searches number_list for number. Returns True if found.
67
68 Args:
69 number_list: list of gdata PhoneNumber
70 number: string phone number.
71
72 Returns: bool.
73 """
74 for phone_number in number_list:
75 google_number = phone_number.text
76 if NumberSuffixesMatch(google_number, number):
77 return True
78 return False
79
80
81 def GroupListContainsGroup(group_list, group):
82 """Returns true if group found in group_list.
83
84 Args:
85 group_list: array of GroupMembershipInfo
86 group: a GroupMembershipInfo
87
88 Returns: bool.
89 """
90 assert group
91
92 sought_href = group.href
93 for potential_match in group_list:
94 if potential_match.href == sought_href:
95 return True
96 return False
97
98
f2b7da9b » Brad Fitzpatrick 2008-11-29 find candidates for merging. 99 def FindEntryToMergeInto(contact, feed):
100 """Finds Entry (or None) in feed to merge contact into."""
101 contact_name = contact["name"]
102 for entry in feed.entry:
103 if entry.title and entry.title.text and \
104 entry.title.text == contact_name:
105 return entry
106
68e2b7b8 » Brad Fitzpatrick 2008-11-30 proper updating 107 for number_rec in contact["numbers"]:
108 contact_number = number_rec["number"]
109 if PhoneNumberListContainsNumber(entry.phone_number,
110 contact_number):
111 return entry
f2b7da9b » Brad Fitzpatrick 2008-11-29 find candidates for merging. 112
113 return None
114
f3be64f3 » Brad Fitzpatrick 2008-11-29 remove unused crap, and add... 115
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 116 def PhoneRelType(text):
117 """Given some free-formish text, map that to the GData phone rel."""
118 if re.match(r"^\s*(mobile|cell)", text, re.I):
119 return "http://schemas.google.com/g/2005#mobile"
120 if re.match(r"^\s*(work|office)", text, re.I):
121 return "http://schemas.google.com/g/2005#work"
122 if re.match(r"^\s*(house|home)", text, re.I):
123 return "http://schemas.google.com/g/2005#home"
124 return "http://schemas.google.com/g/2005#other"
125
126
71122ab1 » Brad Fitzpatrick 2008-11-30 vcard support from david re... 127 def VcardPhoneType(phone_rel):
128 """Given a phone rel type from PhoneRelType, returns the vcard type."""
129 vcard_map = {
130 "http://schemas.google.com/g/2005#mobile": "CELL",
131 "http://schemas.google.com/g/2005#home": "HOME",
132 "http://schemas.google.com/g/2005#work": "WORK",
133 }
134 if phone_rel in vcard_map:
135 return vcard_map[phone_rel]
136 return "OTHER"
137
138
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 139 def NewContactEntry(contact, group=None):
68e2b7b8 » Brad Fitzpatrick 2008-11-30 proper updating 140 """Make a new GData Contact Entry from a submitted contact dict.
141
142 Returns: The new, populated cgdata.contacts.ContactEntry object.
143 """
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 144 new_entry = gdata.contacts.ContactEntry()
68e2b7b8 » Brad Fitzpatrick 2008-11-30 proper updating 145 UpdateContactEntry(new_entry, contact, group)
146 return new_entry
147
148
149 def UpdateContactEntry(merge_entry, contact, group=None):
150 """Merge user-submitted contact data into GData entry.
151
152 Args:
153 merge_entry: The gdata.contacts.ContactEntry() object to
154 merge into:
155 contact: Input contact dictionary from user w/ fields
156 group: optional GroupMembershipInfo to put user in
157
158 Returns: List of changes (terse English each). If no changes,
159 returns the empty list, which is false in boolean context.
160 """
161 changes = []
162
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 163 if contact["name"]:
68e2b7b8 » Brad Fitzpatrick 2008-11-30 proper updating 164 # TODO: promote short names (e.g. "Brad" or "Brad F.") to
165 # full names ("Brad Fitzpatrick"). For now, never modify
166 # the name.
167 if not merge_entry.title or not merge_entry.title.text:
168 changes.append("set name: %s" % contact["name"])
169 merge_entry.title = atom.Title(text=contact["name"])
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 170
171 for number_rec in contact["numbers"]:
68e2b7b8 » Brad Fitzpatrick 2008-11-30 proper updating 172 if not PhoneNumberListContainsNumber(merge_entry.phone_number,
173 number_rec["number"]):
174 changes.append("adding number: %s" % number_rec["number"])
175 merge_entry.phone_number.append(gdata.contacts.PhoneNumber(
176 rel=PhoneRelType(number_rec["type"]),
177 text=number_rec["number"]))
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 178
68e2b7b8 » Brad Fitzpatrick 2008-11-30 proper updating 179 if group and not GroupListContainsGroup(merge_entry.group_membership_info,
180 group):
181 changes.append("adding to group.")
182 merge_entry.group_membership_info.append(group)
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 183
68e2b7b8 » Brad Fitzpatrick 2008-11-30 proper updating 184 return changes
185
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 186
f3be64f3 » Brad Fitzpatrick 2008-11-29 remove unused crap, and add... 187 class Updater(object):
188 """Queues up updates and flushes them to gdata batch as needed."""
189
9dbc186f » Brad Fitzpatrick 2008-11-30 prettier 190 def __init__(self, client=None, noop_mode=False):
f3be64f3 » Brad Fitzpatrick 2008-11-29 remove unused crap, and add... 191 self.client = client
192 self.batch_feed = gdata.contacts.ContactsFeed()
9dbc186f » Brad Fitzpatrick 2008-11-30 prettier 193 self.noop_mode = noop_mode
f3be64f3 » Brad Fitzpatrick 2008-11-29 remove unused crap, and add... 194
195 def AddInsert(self, entry):
9dbc186f » Brad Fitzpatrick 2008-11-30 prettier 196 if self.noop_mode:
197 return
f3be64f3 » Brad Fitzpatrick 2008-11-29 remove unused crap, and add... 198 self.batch_feed.AddInsert(entry)
199 self.FlushIfNeeded()
200
201 def AddUpdate(self, entry):
9dbc186f » Brad Fitzpatrick 2008-11-30 prettier 202 if self.noop_mode:
203 return
f3be64f3 » Brad Fitzpatrick 2008-11-29 remove unused crap, and add... 204 self.batch_feed.AddUpdate(entry)
205 self.FlushIfNeeded()
206
207 def FlushIfNeeded(self):
58351427 » Brad Fitzpatrick 2008-12-01 reduce limit further 208 if len(self.batch_feed.entry) >= 15: # 100 is max but too slow for App Engine
f3be64f3 » Brad Fitzpatrick 2008-11-29 remove unused crap, and add... 209 self.Flush()
210
211 def Flush(self):
212 if not len(self.batch_feed.entry):
213 return
214 self.client.ExecuteBatch(self.batch_feed,
215 gdata.contacts.service.DEFAULT_BATCH_URL)
216 self.batch_feed = gdata.contacts.ContactsFeed()
217
bfbfcd50 » Brad Fitzpatrick 2008-12-01 new HTTP requests per batch... 218 def FlushBufferEmpty(self):
219 return len(self.batch_feed.entry) == 0
220
f3be64f3 » Brad Fitzpatrick 2008-11-29 remove unused crap, and add... 221
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 222 class AddressBookerBaseHandler(webapp.RequestHandler):
223 def WritePage(self, title, template_file, dict=None):
224 sign_inout = ""
225 user = users.get_current_user()
226 if user:
227 sign_inout = 'Hello, %s! <a href="%s">Sign Out</a>' % (
228 cgi.escape(user.nickname()),
229 users.create_logout_url('http://%s/' % settings.HOST_NAME))
230 else:
231 sign_inout = '<a href="%s">Sign In</a>' % (
232 users.create_login_url('http://%s/' % settings.HOST_NAME))
233
234 self.response.out.write(template.render('page.html', {
235 'title': title or "AddressBooker",
236 'sign_inout': sign_inout,
237 'body': template.render(template_file, dict or {}),
238 }))
239
240 class AddressBooker(AddressBookerBaseHandler):
0ed12f30 » Brad Fitzpatrick 2008-11-28 forked feedfetcher into add... 241
242 def get(self):
243 self.response.headers['Content-Type'] = 'text/html'
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 244 self.WritePage("AddressBooker", "index.html")
245
0ed12f30 » Brad Fitzpatrick 2008-11-28 forked feedfetcher into add... 246
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 247 class Submit(webapp.RequestHandler):
248
249 def get(self):
250 self.redirect("/")
0ed12f30 » Brad Fitzpatrick 2008-11-28 forked feedfetcher into add... 251
252 def post(self):
4ee0964e » Brad Fitzpatrick 2008-11-28 start of simple model to ho... 253 handle = self.request.get('handle')
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 254 if not handle:
255 raise "Missing argument 'handle'"
256 if not VALID_HANDLE.match(handle):
257 raise "Bogus handle."
258
4ee0964e » Brad Fitzpatrick 2008-11-28 start of simple model to ho... 259 json = self.request.get('json')
260 group = self.request.get('group')
261
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 262 post_dump = models.PostDump(key_name="handle:" + handle,
263 json=json,
264 group=group,
265 handle=handle)
266 post_dump.put()
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 267
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 268 user = users.get_current_user()
269 target_url = "http://%s/menu?key=%s" % (
270 settings.HOST_NAME, str(post_dump.key()))
271 if user:
272 self.redirect(target_url)
273 else:
274 self.redirect(users.create_login_url(target_url))
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 275
276
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 277 class Menu(AddressBookerBaseHandler):
278 def get(self):
279 key = self.request.get('key')
280 if not key:
281 raise "Missing argument 'key'"
282 post_dump = models.PostDump.get(db.Key(key))
283 if not post_dump:
284 raise "State lost? Um, do it again."
285
286 contacts = simplejson.loads(post_dump.json)
287
288 self.WritePage("AddressBooker Menu", "menu.html", {
289 'n_contacts': len(contacts),
290 'key': str(post_dump.key()),
291 })
292
293
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 294 class MergeView(webapp.RequestHandler):
295 """View the contacts for a given handle."""
296
297 def get(self):
298 key = self.request.get('key')
299 if not key:
300 raise "Missing argument 'key'"
301 post_dump = models.PostDump.get(db.Key(key))
302 if not post_dump:
303 raise "State lost? Um, do it again."
304
305 contacts = simplejson.loads(post_dump.json)
0ed12f30 » Brad Fitzpatrick 2008-11-28 forked feedfetcher into add... 306 for contact in contacts:
307 self.response.out.write("<br clear='both'><h2>%s</h2>" % contact["name"])
308 self.response.out.write("<img src='%s' style='float:left' />" % contact["img"])
309 for number in contact["numbers"]:
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 310 # obf_number = re.sub(r"\d{3}$", "<i>xxx</i>", number["number"])
0ed12f30 » Brad Fitzpatrick 2008-11-28 forked feedfetcher into add... 311 self.response.out.write("<p><b>%s</b> %s</p>" % (
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 312 cgi.escape(number["type"]), cgi.escape(number["number"])))
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 313
314
71122ab1 » Brad Fitzpatrick 2008-11-30 vcard support from david re... 315 class VCard(webapp.RequestHandler):
316 """Get a vcard."""
317
318 def get(self):
319 key = self.request.get('key')
320 if not key:
321 raise "Missing argument 'key'"
322 post_dump = models.PostDump.get(db.Key(key))
323 if not post_dump:
324 raise "State lost? Um, do it again."
325
326 contacts = simplejson.loads(post_dump.json)
327 self.response.headers['Content-Type'] = "text/x-vcard; charset=UTF-8"
328 self.response.headers['Content-Disposition'] = "attachment; filename=\"addressbooker.vcf\""
329
330 for contact in contacts:
331 self.response.out.write("BEGIN:VCARD\n")
332 self.response.out.write("VERSION:3.0\n")
333 self.response.out.write("FN:%s\n" % (contact["name"] or ""))
334 for number in contact["numbers"]:
335 self.response.out.write("TEL;type=%s:%s\n" % (
336 VcardPhoneType(PhoneRelType(number["type"])),
337 number["number"]))
338 self.response.out.write("END:VCARD\n")
339
340
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 341 class MergeGoogle(AddressBookerBaseHandler):
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 342 """Merge contacts into Google Contacts w/ Google Contacts API."""
343
344 def get(self):
9dbc186f » Brad Fitzpatrick 2008-11-30 prettier 345 self.ProcessMerge(method='GET')
346
347 def post(self):
348 self.ProcessMerge(method='POST')
349
350 def ProcessMerge(self, method):
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 351 key = self.request.get('key')
352 if not key:
353 raise "Missing argument 'key'"
354 post_dump = models.PostDump.get(db.Key(key))
355 if not post_dump:
356 raise "State lost? Um, do it again."
357
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 358 body = [] # of str
f2b7da9b » Brad Fitzpatrick 2008-11-29 find candidates for merging. 359 def out(str):
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 360 body.append(str)
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 361
362 # We need a logged-in user for the GData.client.token_store to work.
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 363 user = users.get_current_user()
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 364 if not user:
365 logging.info("Redirecting to sign-in.");
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 366 sign_in_url = users.create_login_url('http://%s/gcontacts?key=%s' %
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 367 (settings.HOST_NAME, key))
368 self.redirect(sign_in_url)
369 return
370
371 # And the subclass of the Service for the Contacts API:
372 client = contactsservice.ContactsService()
373 gdata.alt.appengine.run_on_appengine(client)
374
375 contacts = simplejson.loads(post_dump.json)
376
377 contacts_url = "http://www.google.com/m8/feeds/contacts/default/full"
378 auth_base_url = "http://www.google.com/m8/feeds/"
379
380 session_token = client.token_store.find_token(auth_base_url)
381 if type(session_token) == atom.http_interface.GenericToken:
382 session_token = None
383
384 if not session_token:
385 # Find the AuthSub token and upgrade it to a session token.
386 auth_token = gdata.auth.extract_auth_sub_token_from_url(self.request.uri)
387 if auth_token:
388 session_token = client.upgrade_to_session_token(auth_token)
389 client.token_store.add_token(session_token)
390 # just to sanitize our URL:
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 391 self.redirect('http://%s/gcontacts?key=%s' %
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 392 (settings.HOST_NAME, key))
393 else:
394 next = self.request.uri
395 auth_sub_url = client.GenerateAuthSubURL(next, auth_base_url,
396 secure=False, session=True)
397 self.redirect(str(auth_sub_url))
398 return
bfbfcd50 » Brad Fitzpatrick 2008-12-01 new HTTP requests per batch... 399
400 # Are we a GET request in auto-submit mode?
401 working = (method == "GET" and self.request.get("continue"))
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 402
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 403 # Process Groups
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 404 groups_feed = client.Get("http://www.google.com/m8/feeds/groups/default/full")
9c78ed16 » Brad Fitzpatrick 2008-11-30 fix utf-8 bug in group names 405 groups_feed = gdata.contacts.GroupsFeedFromString(groups_feed.ToString().decode("utf-8"))
f2b7da9b » Brad Fitzpatrick 2008-11-29 find candidates for merging. 406 group_name = {} # id -> name
407 group_id = {} # name -> id
408 for group in groups_feed.entry:
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 409 group_name[group.id.text] = group.content.text
410 group_id[group.content.text] = group.id.text
9c78ed16 » Brad Fitzpatrick 2008-11-30 fix utf-8 bug in group names 411 if False:
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 412 out("<h3>Group</h3><ul>")
413 out("<li>id: %s</li>" % cgi.escape(group.id.text))
9c78ed16 » Brad Fitzpatrick 2008-11-30 fix utf-8 bug in group names 414 out("<li>content: %s</li>" % cgi.escape(group.content.text.decode("utf-8")))
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 415 out("</ul>")
416
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 417 # Initialize 'group' (or keep it None, if not using groups), creating the
418 # group if necessary.
419 group = None
420 dest_group_name = post_dump.group
421 if dest_group_name and dest_group_name not in group_id:
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 422 new_group = gdata.contacts.GroupEntry(title=atom.Title(
423 text=dest_group_name))
424 group = client.CreateGroup(new_group)
425 group_name[group.id] = dest_group_name
426 group_id[dest_group_name] = group.id
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 427 if dest_group_name:
428 group = gdata.contacts.GroupMembershipInfo(href=unicode(group_id[dest_group_name]))
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 429
430 full_feed_url = contacts_url + "?max-results=99999"
431 feed = client.Get(full_feed_url, converter=gdata.contacts.ContactsFeedFromString)
f2b7da9b » Brad Fitzpatrick 2008-11-29 find candidates for merging. 432
9dbc186f » Brad Fitzpatrick 2008-11-30 prettier 433 preview_mode = True
434 title = "Preview Proposed GContacts Changes"
435 if method == "POST":
436 preview_mode = False
437 title = "GContacts Updates Complete"
438
439 contact_changes = []
440
441 updater = Updater(client=client, noop_mode=preview_mode);
f3be64f3 » Brad Fitzpatrick 2008-11-29 remove unused crap, and add... 442
4c47dff7 » Brad Fitzpatrick 2008-11-30 prettier 443 no_change_contacts = []
f2b7da9b » Brad Fitzpatrick 2008-11-29 find candidates for merging. 444 for contact in contacts:
9dbc186f » Brad Fitzpatrick 2008-11-30 prettier 445 contact_change = {
446 "contact": contact,
447 }
448
f2b7da9b » Brad Fitzpatrick 2008-11-29 find candidates for merging. 449 merge_entry = FindEntryToMergeInto(contact, feed)
450 if merge_entry:
9dbc186f » Brad Fitzpatrick 2008-11-30 prettier 451 entry_changes = UpdateContactEntry(merge_entry, contact, group=group)
452 if entry_changes:
453 contact_change["action"] = "merge"
454 contact_change["merge_target"] = merge_entry.title.text.decode("utf-8")
455 contact_change["changes"] = entry_changes
68e2b7b8 » Brad Fitzpatrick 2008-11-30 proper updating 456 updater.AddUpdate(merge_entry)
457 else:
9dbc186f » Brad Fitzpatrick 2008-11-30 prettier 458 contact_change["action"] = "none"
f2b7da9b » Brad Fitzpatrick 2008-11-29 find candidates for merging. 459 else:
9dbc186f » Brad Fitzpatrick 2008-11-30 prettier 460 contact_change["action"] = "new"
461 contact_change["changes"] = ["Create new contact."]
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 462 updater.AddInsert(NewContactEntry(contact, group=group))
463
4c47dff7 » Brad Fitzpatrick 2008-11-30 prettier 464 if contact_change["action"] == "none":
465 no_change_contacts.append(contact_change)
466 else:
467 contact_changes.append(contact_change)
bfbfcd50 » Brad Fitzpatrick 2008-12-01 new HTTP requests per batch... 468 if not preview_mode and updater.FlushBufferEmpty():
469 # redirect for the next batch; new HTTP request
470 # to get around App Engine long request deadlines.
471 self.redirect('http://%s/gcontacts?key=%s&continue=1' %
472 (settings.HOST_NAME, key))
58351427 » Brad Fitzpatrick 2008-12-01 reduce limit further 473 return
bfbfcd50 » Brad Fitzpatrick 2008-12-01 new HTTP requests per batch... 474
4c47dff7 » Brad Fitzpatrick 2008-11-30 prettier 475
476 # Put the boring ones at bottom.
bfbfcd50 » Brad Fitzpatrick 2008-12-01 new HTTP requests per batch... 477 n_changes = len(contact_changes)
4c47dff7 » Brad Fitzpatrick 2008-11-30 prettier 478 contact_changes.extend(no_change_contacts)
479
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 480 render_google_list = False
481 if render_google_list:
482 out("<hr />")
483 for entry in feed.entry:
484 if entry.title and entry.title.text:
485 out('<h3>Entry Title: %s</h3>' % (
486 entry.title.text.decode('UTF-8')))
487 else:
488 out("<h3>(title-less entry)</h3>");
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 489
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 490 for phone_number in entry.phone_number:
491 out("<p><b>Phone: (%s)</b> %s</p>" %
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 492 (phone_number.rel, phone_number.text))
493
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 494 for email in entry.email:
495 out("<p><b>Email: (%s)</b> %s</p>" %
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 496 (email.rel, email.address))
497
15176620 » Brad Fitzpatrick 2008-11-29 Inserting new entries works... 498 for group in entry.group_membership_info:
499 out("<p><b>Group: (%s)</b> %s</p>" %
500 (group.href, cgi.escape(str(group))))
501
502 updater.Flush()
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 503
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 504 self.WritePage(title, "google-merge.html", {
9dbc186f » Brad Fitzpatrick 2008-11-30 prettier 505 "preview_mode": preview_mode,
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 506 "body": "".join(body),
9dbc186f » Brad Fitzpatrick 2008-11-30 prettier 507 "session_token": str(session_token),
4c47dff7 » Brad Fitzpatrick 2008-11-30 prettier 508 "key": key,
bfbfcd50 » Brad Fitzpatrick 2008-12-01 new HTTP requests per batch... 509 "changes": contact_changes,
510 "n_changes": n_changes,
511 "working": working,
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 512 })
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 513
0ed12f30 » Brad Fitzpatrick 2008-11-28 forked feedfetcher into add... 514
515 class Acker(webapp.RequestHandler):
516 """Simulates an HTML page to prove ownership of this domain for AuthSub
517 registration."""
518
519 def get(self):
520 self.response.headers['Content-Type'] = 'text/plain'
521 self.response.out.write('This file present for AuthSub registration.')
522
523
524 def main():
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 525 application = webapp.WSGIApplication([
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 526 ('/', AddressBooker),
527 ('/submit', Submit),
528 ('/menu', Menu),
71122ab1 » Brad Fitzpatrick 2008-11-30 vcard support from david re... 529 ('/vcard', VCard),
6b65d890 » Brad Fitzpatrick 2008-11-30 Pretty(ier) HTML and URLs. 530 ('/gcontacts', MergeGoogle),
531 ('/view', MergeView),
04acea54 » Brad Fitzpatrick 2008-11-29 gdata contacts stuff workin... 532 ('/google72db3d6838b4c438.html', Acker),
533 ], debug=True)
0ed12f30 » Brad Fitzpatrick 2008-11-28 forked feedfetcher into add... 534 wsgiref.handlers.CGIHandler().run(application)
535
536
537 if __name__ == '__main__':
538 main()