chiark / gitweb /
Split out building gcal event structure (no functional change)
[gooswapper] / gooswapper.py
index f3b4bf1f35c7f0bb9d803373fb713f8f64e57ad6..2bee57d5781b7022a2b41a2ebdba901d13dc6db4 100644 (file)
@@ -4,6 +4,7 @@ import sys
 import getpass
 import os
 import pickle
+import collections
 import logging
 logger = logging.getLogger('gooswapper')
 logger.setLevel(logging.INFO)
@@ -44,6 +45,9 @@ cachepath=".gooswapcache"
 
 exchange_credential = None
 
+CachedExEvent=collections.namedtuple('CachedExEvent',
+                                     ['changekey','gcal_link'])
+
 class ex_gcal_link(exchangelib.ExtendedProperty):
     distinguished_property_set_id = 'PublicStrings'
     property_name = "google calendar event id"
@@ -63,23 +67,45 @@ def get_ex_cred(username="SANGER\mv3",password=None):
         password = getpass.getpass(prompt="Password for user %s: " % username)
     return exchangelib.ServiceAccount(username,password)
 
-def ex_login(emailaddr,autodiscover=True):
+def ex_login(emailaddr,ad_cache_path=None):
     global exchange_credential
+    autodiscover = True
     if exchange_credential is None:
         exchange_credential = get_ex_cred()
-    return exchangelib.Account(emailaddr,
-                               credentials = exchange_credential,
-                               autodiscover = autodiscover)
+    if ad_cache_path is not None:
+        try:
+            with open(ad_cache_path,"rb") as f:
+                url,auth_type = pickle.load(f)
+                autodiscover = False
+        except FileNotFoundError:
+            pass
+
+    if autodiscover:
+        ex_ac = exchangelib.Account(emailaddr,
+                                    credentials = exchange_credential,
+                                    autodiscover = autodiscover)
+        if ad_cache_path is not None:
+            cache=(ex_ac.protocol.service_endpoint,
+                   ex_ac.protocol.auth_type)
+            with open(ad_cache_path,"wb") as f:
+                pickle.dump(cache,f)
+    else:
+        ex_conf = exchangelib.Configuration(service_endpoint=url,
+                                            credentials=exchange_credential,
+                                            auth_type=auth_type)
+        ex_ac = exchangelib.Account(emailaddr,
+                                    config=ex_conf,
+                                    autodiscover=False,
+                                    access_type=exchangelib.DELEGATE)
+
+    return ex_ac
 
 def get_ex_events(calendar):
     ans={}
     for event in calendar.all().only('changekey','item_id','gcal_link'):
-        if event.gcal_link is not None:
-#            event.delete()
-            continue
         if event.item_id in ans:
             logger.warning("Event item_id %s was duplicated!" % event.item_id)
-        ans[event.item_id] = event.changekey
+        ans[event.item_id] = CachedExEvent(event.changekey,event.gcal_link)
     logger.info("%d events found" % len(ans))
     return ans
 
@@ -91,36 +117,54 @@ def ex_event_changes(old,new):
     changed = []
     #intersection - i.e. common to both sets
     for event in olds & news:
-        if old[event] != new[event]:
+        if old[event].changekey != new[event].changekey:
             changed.append(event)
     logger.info("%d events updated, %d added, %d deleted" % (len(changed),
                                                               len(added),
                                                               len(deleted)))
     return added, deleted, changed
 
+def build_gcal_event_from_ex(event):
+    gevent={}
+    gevent["summary"]=event.subject
+    if event.is_all_day:
+        gevent["end"]={"date": str(event.end.astimezone(gcal_tz).date())}
+        gevent["start"]={"date": str(event.start.astimezone(gcal_tz).date())}
+    else:
+        gevent["end"]={"dateTime": event.end.isoformat(),
+                       "timeZone": event.end.tzname()}
+        gevent["start"]={"dateTime": event.start.isoformat(),
+                         "timeZone": event.start.tzname()}
+    if event.text_body.strip() != '':
+        gevent["description"] = event.text_body
+    if event.location is not None:
+        gevent["location"] = event.location
+    gevent["extendedProperties"]={"shared": {"ex_id": event.item_id}}
+    return gevent
+
 def add_ex_to_gcal(ex_acct,
                    gcal_acct,gcal_tz,events,
                    added,
                    gcal_id="primary"):
     for ev_id in added:
         event = get_ex_event_by_itemid(ex_acct.calendar,ev_id)
-        if event.is_all_day:
-            gevent={}
-            gevent["summary"]=event.subject
-            gevent["end"]={"date": str(event.end.astimezone(gcal_tz).date())}
-            gevent["start"]={"date": str(event.start.astimezone(gcal_tz).date())}
-            if event.text_body.strip() != '':
-                gevent["description"] = event.text_body
-            if event.location is not None:
-                gevent["location"] = event.location
-            gevent["extended_properties"]={"shared": {"ex_id": event.item_id}}
-            gevent=gcal_acct.events().insert(calendarId=gcal_id, body=gevent).execute()
+        if not event.is_recurring:
+            gevent = build_gcal_event_from_ex(event)
+            gevent = gcal_acct.events().insert(calendarId=gcal_id,
+                                               body=gevent).execute()
             event.gcal_link = gevent.get("id")
             event.save()
-            events[event.item_id] = event.changekey
+            events[event.item_id] = events[event.item_id]._replace(changekey=event.changekey,gcal_link=event.gcal_link)
         else:
-            logger.warning("only all-day events supported")
-            
+            logger.warning("recurring events not yet supported")
+
+def del_ex_to_gcal(ex_acct, gcal_acct, events, deleted, gcal_id="primary"):
+    for ev_id in deleted:
+        if events[ev_id].gcal_link is not None:
+            gcal_acct.events().delete(calendarId=gcal_id,
+                                      eventId=events[ev_id].gcal_link,
+                                      sendUpdates="none").execute()
+    
 def get_gcal_cred():
     #each such file can only store a single credential
     storage = oauth2client.file.Storage(gcal_authpath)
@@ -152,7 +196,7 @@ def main():
     except FileNotFoundError:
         cache = None
 
-    ex_account = ex_login("mv3@sanger.ac.uk")
+    ex_account = ex_login("mv3@sanger.ac.uk",".gooswapper_exch_conf.dat")
     current = get_ex_events(ex_account.calendar)
 
     gcal_account = gcal_login()
@@ -161,6 +205,9 @@ def main():
     if cache is not None:
         added,deleted,changed = ex_event_changes(cache,current)
         add_ex_to_gcal(ex_account,gcal_account,gcal_tz,current,added)
+        #delete op needs the "cache" set, as that has the link ids in
+        #for events that are now deleted
+        del_ex_to_gcal(ex_account,gcal_account,cache,deleted)
         
     with open(cachepath,"wb") as f:
         pickle.dump(current,f)