<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -16,11 +16,12 @@
 __author__ = 'brad@danga.com (Brad Fitzpatrick)'
 
 # Core Python
+import cgi
+import logging
 import pprint
+import random
 import re
-import urllib # Used to unescape URL parameters.
-import logging
-import cgi
+import urllib
 
 # Core/AppEngine stuff
 import wsgiref.handlers
@@ -78,6 +79,34 @@ def FindEntryToMergeInto(contact, feed):
 
   return None
 
+
+class Updater(object):
+  &quot;&quot;&quot;Queues up updates and flushes them to gdata batch as needed.&quot;&quot;&quot;
+
+  def __init__(self, client=None):
+    self.client = client
+    self.batch_feed = gdata.contacts.ContactsFeed()
+
+  def AddInsert(self, entry):
+    self.batch_feed.AddInsert(entry)
+    self.FlushIfNeeded()
+
+  def AddUpdate(self, entry):
+    self.batch_feed.AddUpdate(entry)
+    self.FlushIfNeeded()
+
+  def FlushIfNeeded(self):
+    if len(self.batch_feed.entry) &gt;= 50:   # could be 100 max
+      self.Flush()
+
+  def Flush(self):
+    if not len(self.batch_feed.entry):
+      return
+    self.client.ExecuteBatch(self.batch_feed,
+                             gdata.contacts.service.DEFAULT_BATCH_URL)
+    self.batch_feed = gdata.contacts.ContactsFeed()
+
+
 class AddressBooker(webapp.RequestHandler):
 
   def get(self):
@@ -99,184 +128,6 @@ class AddressBooker(webapp.RequestHandler):
           users.create_login_url('http://%s/merge/' % settings.HOST_NAME)))
     self.response.out.write('&lt;/div&gt;')
 
-    # Initialize a client to talk to Google Data API services.
-    client = gdata.service.GDataService()
-    gdata.alt.appengine.run_on_appengine(client)
-
-    # And the subclass of the Service for the Contacts API:
-    contacts_client = contactsservice.ContactsService()
-    gdata.alt.appengine.run_on_appengine(contacts_client)
-
-    session_token = None
-    # Find the AuthSub token and upgrade it to a session token.
-    auth_token = gdata.auth.extract_auth_sub_token_from_url(self.request.uri)
-    if auth_token:
-      # Upgrade the single-use AuthSub token to a multi-use session token.
-      session_token = client.upgrade_to_session_token(auth_token)
-    if session_token and users.get_current_user():
-      # If there is a current user, store the token in the datastore and
-      # associate it with the current user. Since we told the client to
-      # run_on_appengine, the add_token call will automatically store the
-      # session token if there is a current_user.
-      client.token_store.add_token(session_token)
-    elif session_token:
-      # Since there is no current user, we will put the session token
-      # in a property of the client. We will not store the token in the
-      # datastore, since we wouldn't know which user it belongs to.
-      # Since a new client object is created with each get call, we don't
-      # need to worry about the anonymous token being used by other users.
-      client.current_token = session_token
-
-    # Get the URL for the desired feed and get the display option.
-    feed_url = self.request.get('feed_url')
-    erase_tokens = self.request.get('erase_tokens')
-    if erase_tokens:
-      self.EraseStoredTokens()
-    show_xml = self.request.get('xml')
-
-    if show_xml:
-      checked_string = 'checked'
-    else:
-      checked_string = ''
-      
-    self.response.out.write(&quot;&quot;&quot;&lt;div id=&quot;wrap&quot;&gt;&lt;div id=&quot;header&quot;&gt;
-          &lt;h1&gt;AddressBooker&lt;/h1&gt;
-          &lt;form action=&quot;/&quot; method=&quot;get&quot;&gt;
-          &lt;label id=&quot;feed_url_label&quot; for=&quot;feed_url&quot;&gt;Target URL:&lt;/label&gt;
-          &lt;input type=&quot;text&quot; size=&quot;60&quot; name=&quot;feed_url&quot; id=&quot;feed_url&quot; 
-              value=&quot;%s&quot;&gt;&lt;/input&gt;
-          &lt;input type=&quot;submit&quot; value=&quot;Fetch Atom&quot;&gt;&lt;/input&gt;
-          &lt;label for=&quot;xml&quot;&gt;Show XML:&lt;/label&gt;
-          &lt;input type=&quot;checkbox&quot; id=&quot;xml&quot; name=&quot;xml&quot; value=&quot;true&quot; %s&gt;&lt;/input&gt;
-        &lt;/form&gt;&lt;/div&gt;&quot;&quot;&quot; % ((feed_url or ''), checked_string))
-
-    self.response.out.write('&lt;div id=&quot;main&quot;&gt;')
-    if not feed_url:
-      self.ShowInstructions()
-    else:
-      self.FetchFeed(client, feed_url, show_xml)
-    self.response.out.write('&lt;/div&gt;')
-
-    if users.get_current_user():
-      self.response.out.write(&quot;&quot;&quot;&lt;div id=&quot;sidebar&quot;&gt;&lt;div id=&quot;scopes&quot;&gt;
-          &lt;h4&gt;Request a token for some common scopes&lt;/h4&gt;&lt;ul&gt;
-          &lt;li&gt;&lt;a href=&quot;%s&quot;&gt;Blogger&lt;/a&gt;&lt;/li&gt;
-          &lt;li&gt;&lt;a href=&quot;%s&quot;&gt;Calendar&lt;/a&gt;&lt;/li&gt;
-          &lt;li&gt;&lt;a href=&quot;%s&quot;&gt;Google Documents&lt;/a&gt;&lt;/li&gt;
-          &lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tokens&quot;&gt;&quot;&quot;&quot; % (
-              self.GenerateScopeRequestLink(client, 
-                  'http://www.blogger.com/feeds/'),
-              self.GenerateScopeRequestLink(client, 
-                  'http://www.google.com/calendar/feeds'),
-              self.GenerateScopeRequestLink(client, 
-                  'http://docs.google.com/feeds/')))
-
-      self.DisplayAuthorizedUrls()
-      self.response.out.write('&lt;/div&gt;')
-    self.response.out.write('&lt;/div&gt;&lt;/div&gt;&lt;/body&gt;&lt;/html&gt;')
-    
-  def GenerateScopeRequestLink(self, client, scope):
-    return client.GenerateAuthSubURL('http://%s/' % (
-            settings.HOST_NAME,),
-        scope, secure=False, session=True)
-
-  def ShowInstructions(self):
-    self.response.out.write(
-      &quot;&quot;&quot;&lt;p&gt;This sample application illustrates the
-        use of &lt;a 
-        href=&quot;http://code.google.com/apis/accounts/docs/AuthForWebApps.html&quot;&gt;
-        AuthSub authentication&lt;/a&gt; to access 
-        &lt;a href=&quot;http://code.google.com/apis/gdata/&quot;&gt;Google Data feeds&lt;/a&gt;.&lt;/p&gt;
-      &quot;&quot;&quot;)
-
-
-  def GenerateFeedRequestLink(self, feed_url):
-    return atom.url.Url('http', settings.HOST_NAME, path='/', 
-        params={'feed_url':feed_url}).to_string()
-
-  def FetchFeed(self, client, feed_url, show_xml=False):
-    # Attempt to fetch the feed.
-    try:
-      if show_xml:
-        response = client.Get(feed_url, converter=str)
-        response = response.decode('UTF-8')
-        self.response.out.write(cgi.escape(response))
-      else:
-        response = client.Get(feed_url)
-        if isinstance(response, atom.Feed):
-          self.RenderFeed(response)
-        elif isinstance(response, atom.Entry):
-          self.RenderEntry(response)
-        else:
-          self.response.out.write(cgi.escape(response.read()))
-    except gdata.service.RequestError, request_error:
-      # If fetching fails, then tell the user that they need to login to
-      # authorize this app by logging in at the following URL.
-      if request_error[0]['status'] == 401:
-        # Get the URL of the current page so that our AuthSub request will
-        # send the user back to here.
-        next = self.request.uri
-        auth_sub_url = client.GenerateAuthSubURL(next, feed_url,
-            secure=False, session=True)
-        self.response.out.write('&lt;a href=&quot;%s&quot;&gt;' % (auth_sub_url))
-        self.response.out.write(
-            'Click here to authorize this application to view the feed&lt;/a&gt;')
-      else:
-        self.response.out.write(
-            'Something else went wrong, here is the error object: %s ' % (
-                str(request_error[0])))
-
-  def RenderFeed(self, feed):
-    self.response.out.write('&lt;h2&gt;Feed Title: %s&lt;/h2&gt;' % (
-        feed.title.text.decode('UTF-8')))
-    for link in feed.link:
-      self.RenderLink(link)
-    for entry in feed.entry:
-      self.RenderEntry(entry)
-
-  def RenderEntry(self, entry):
-    if entry.title and entry.title.text:
-      self.response.out.write('&lt;h3&gt;Entry Title: %s&lt;/h3&gt;' % (
-        entry.title.text.decode('UTF-8')))
-    else:
-      self.response.out.write(&quot;&lt;h3&gt;(title-less entry)&lt;/h3&gt;&quot;);
-      
-    if entry.content and entry.content.text:
-      self.response.out.write('&lt;p&gt;Content: %s&lt;/p&gt;' % (
-          entry.content.text.decode('UTF-8')))
-    elif entry.summary and entry.summary.text:
-      self.response.out.write('&lt;p&gt;Summary: %s&lt;/p&gt;' % (
-          entry.summary.text.decode('UTF-8')))
-      
-    for link in entry.link:
-      self.RenderLink(link)
-
-  def RenderLink(self, link):
-    if link.rel == 'alternate' and link.type == 'text/html':
-      self.response.out.write(
-          'Link: &lt;a href=&quot;%s&quot;&gt;alternate HTML&lt;/a&gt;&lt;br/&gt;' % link.href)
-    elif link.type == 'application/atom+xml':
-      self.response.out.write(
-          'Link: &lt;a href=&quot;/?feed_url=%s&quot;&gt;Fetch %s link (%s)&lt;/a&gt;&lt;br/&gt;' % (
-              urllib.quote_plus(link.href), link.rel, link.type))
-    else:
-      self.response.out.write(
-          'Link: &lt;a href=&quot;%s&quot;&gt;%s link (%s)&lt;/a&gt;&lt;br/&gt;' % (link.href, link.rel,
-              link.type))
-    
-  def DisplayAuthorizedUrls(self):
-    self.response.out.write('&lt;h4&gt;Stored Authorization Tokens&lt;/h4&gt;&lt;ul&gt;')
-    tokens = gdata.alt.appengine.load_auth_tokens()
-    for token_scope in tokens:
-      self.response.out.write('&lt;li&gt;&lt;a href=&quot;/?feed_url=%s&quot;&gt;%s*&lt;/a&gt;&lt;/li&gt;' % (
-          urllib.quote_plus(str(token_scope)), str(token_scope)))
-    self.response.out.write(
-        '&lt;/ul&gt;To erase your stored tokens, &lt;a href=&quot;%s&quot;&gt;click here&lt;/a&gt;' % (
-            atom.url.Url('http', settings.HOST_NAME, path='/', 
-                params={'erase_tokens':'true'}).to_string()))
-
-  def EraseStoredTokens(self):
-    gdata.alt.appengine.save_auth_tokens({})
 
   def post(self):
     handle = self.request.get('handle')
@@ -403,6 +254,20 @@ class MergeGoogle(webapp.RequestHandler):
     full_feed_url = contacts_url + &quot;?max-results=99999&quot;
     feed = client.Get(full_feed_url, converter=gdata.contacts.ContactsFeedFromString)
 
+    updater = Updater(client=client);
+
+    if True:
+      new_entry = gdata.contacts.ContactEntry()
+      new_entry.title = atom.Title(text=&quot;TEST ENTRY &quot; + str(random.randint(1, 100)))
+      new_entry.email.append(gdata.contacts.Email(
+          rel='http://schemas.google.com/g/2005#work', 
+          address='TESTTEST@gmail.com'))
+      new_entry.phone_number.append(gdata.contacts.PhoneNumber(
+          rel='http://schemas.google.com/g/2005#mobile', text='(206)555-1212'))
+      new_entry.content = atom.Content(text='Test Notes')
+      updater.AddInsert(new_entry)
+      updater.Flush()
+
     for contact in contacts:
       out(&quot;&lt;br clear='both'&gt;&lt;h2&gt;%s&lt;/h2&gt;&quot; % contact[&quot;name&quot;])
       out(&quot;&lt;img src='%s' style='float:left' /&gt;&quot; % contact[&quot;img&quot;])</diff>
      <filename>addressbooker.py</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>f2b7da9b6ecfc4a93a2e12c6c76ec0ed78bf55ab</id>
    </parent>
  </parents>
  <author>
    <name>Brad Fitzpatrick</name>
    <email>brad@danga.com</email>
  </author>
  <url>http://github.com/bradfitz/addressbooker/commit/f3be64f3077645d142d6578ae2370cff49b1930d</url>
  <id>f3be64f3077645d142d6578ae2370cff49b1930d</id>
  <committed-date>2008-11-29T15:40:12-08:00</committed-date>
  <authored-date>2008-11-29T15:40:12-08:00</authored-date>
  <message>remove unused crap, and add Updater class.</message>
  <tree>8ec4ca6237ad66685ab4cd488585bf0d74185dbb</tree>
  <committer>
    <name>Brad Fitzpatrick</name>
    <email>brad@danga.com</email>
  </committer>
</commit>
