import getpass
import os
import pickle
+import collections
import logging
logger = logging.getLogger('gooswapper')
logger.setLevel(logging.INFO)
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"
exchangelib.CalendarItem.register('gcal_link',ex_gcal_link)
-#see docs for exchangelib.UID for why this is needed
-class GlobalObjectId(exchangelib.ExtendedProperty):
- distinguished_property_set_id = 'Meeting'
- property_id = 3
- property_type = 'Binary'
-
-exchangelib.CalendarItem.register('global_object_id', GlobalObjectId)
-
-def get_ex_event_by_uid(calendar,uid):
- return calendar.get(global_object_id=GlobalObjectId(exchangelib.UID(uid)))
+def get_ex_event_by_itemid(calendar,itemid):
+ return calendar.get(item_id=itemid)
def get_ex_event_by_id_and_changekey(acct,itemid,changekey):
l=list(acct.fetch([(itemid,changekey)]))
if password is None:
password = getpass.getpass(prompt="Password for user %s: " % username)
return exchangelib.ServiceAccount(username,password)
-# return exchangelib.Credentials(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={}
- itemids={}
- for event in calendar.all().only('uid','changekey','item_id','gcal_link'):
- if event.gcal_link is not None:
-# event.delete()
- continue
- if event.uid in ans:
- logger.warning("Event uid %s was duplicated!" % event.uid)
- ans[event.uid] = event.changekey
- itemids[event.uid] = event.item_id
+ for event in calendar.all().only('changekey','item_id','gcal_link'):
+ if event.item_id in ans:
+ logger.warning("Event item_id %s was duplicated!" % event.item_id)
+ ans[event.item_id] = CachedExEvent(event.changekey,event.gcal_link)
logger.info("%d events found" % len(ans))
- return (ans,itemids)
+ return ans
def ex_event_changes(old,new):
olds = set(old.keys())
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
-#XXX doesn't work - cf https://github.com/ecederstrand/exchangelib/issues/492
-def add_ex_to_gcal_needs_ev_id(ex_cal,events):
- for ev_id in events:
- print(ev_id)
- event = get_ex_event_by_uid(ex_cal,ev_id)
- event.gcal_link = "Testing"
- event.save()
+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,
- itemids,added,
+ added,
gcal_id="primary"):
for ev_id in added:
- event = get_ex_event_by_id_and_changekey(ex_acct,
- itemids[ev_id],events[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()
+ event = get_ex_event_by_itemid(ex_acct.calendar,ev_id)
+ 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.uid] = 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)
except FileNotFoundError:
cache = None
- ex_account = ex_login("mv3@sanger.ac.uk")
- current,itemids = get_ex_events(ex_account.calendar)
+ ex_account = ex_login("mv3@sanger.ac.uk",".gooswapper_exch_conf.dat")
+ current = get_ex_events(ex_account.calendar)
gcal_account = gcal_login()
gcal_tz = get_gcal_timezone(gcal_account)
if cache is not None:
added,deleted,changed = ex_event_changes(cache,current)
- add_ex_to_gcal(ex_account,gcal_account,gcal_tz,current,
- itemids,added)
+ 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)