X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~matthewv/git?a=blobdiff_plain;f=gooswapper.py;h=41dd8075abcd20ba04c6dd44cb74564e1d963751;hb=64f11e92b966f6cb1d533bc30c3bd1bf401f93f9;hp=ffad0e0bbf82a697dcf573d0a0cb7ecaf0bd4c21;hpb=1b4f9bbafa324c8c0ce12df271bfa981d4411849;p=gooswapper diff --git a/gooswapper.py b/gooswapper.py index ffad0e0..41dd807 100644 --- a/gooswapper.py +++ b/gooswapper.py @@ -5,6 +5,7 @@ import getpass import os import pickle import collections +import argparse import logging logger = logging.getLogger('gooswapper') logger.setLevel(logging.INFO) @@ -78,6 +79,9 @@ def get_gcal_event_by_eventid(gcal_acct,eventId,gcal_id="primary"): return gcal_acct.events().get(calendarId=gcal_id,eventId=eventId).execute() def get_gcal_recur_instance(gcal_acct,gcal_master,start,gcal_id="primary"): + if gcal_master is None: + logger.warning("Cannot get recurrences from event with null gcal id") + return None ans = gcal_acct.events().instances(calendarId=gcal_id, eventId=gcal_master, originalStart=start.isoformat(), @@ -93,11 +97,11 @@ 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,ad_cache_path=None): +def ex_login(username,emailaddr,ad_cache_path=None): global exchange_credential autodiscover = True if exchange_credential is None: - exchange_credential = get_ex_cred() + exchange_credential = get_ex_cred(username) if ad_cache_path is not None: try: with open(ad_cache_path,"rb") as f: @@ -306,6 +310,9 @@ def update_ex_to_gcal(ex_acct, gcal_id="primary"): for ev_id in changed: event = get_ex_event_by_itemid(ex_acct.calendar,ev_id) + if event.gcal_link is None: + logger.warning("Cannot apply update where event has no gcal link") + continue gevent = build_gcal_event_from_ex(event,gcal_tz) if event.type=="RecurringMaster": rr = rrule_from_ex(event,gcal_tz) @@ -320,24 +327,26 @@ def update_ex_to_gcal(ex_acct, else: logger.warning("Unable to set recurrence for %s" % event.item_id) continue #don't make the gcal event - gevent = gcal_acct.events().update(calendarId=gcal_id, + try: #may fail if we don't own the event + gevent = gcal_acct.events().update(calendarId=gcal_id, eventId=event.gcal_link, body=gevent, sendUpdates="none").execute() + except googleapiclient.errors.HttpError as err: + if err.resp.status == 403: + pass def match_ex_to_gcal(ex_acct,gcal_acct,gcal_tz,events,gcal_id="primary",ignore_link=True): recur = 0 matched = 0 skipped = 0 + toadd = [] for ev_id in events: event = get_ex_event_by_itemid(ex_acct.calendar,ev_id) -# if event.is_recurring: -# recur += 1 -# continue -#next line needs to be elif if above uncommented if event.gcal_link is not None and ignore_link is False: skipped += 1 continue + missing=True matches = gcal_acct.events().list(calendarId=gcal_id, timeMin=event.start.isoformat(), timeMax=event.end.isoformat()).execute() @@ -364,10 +373,14 @@ def match_ex_to_gcal(ex_acct,gcal_acct,gcal_tz,events,gcal_id="primary",ignore_l if err.resp.status == 403: pass matched += 1 + missing = False break + if missing == True: + toadd.append(ev_id) logger.info("Matched %d events, skipped %d with existing link, and %d recurring ones" % (matched,skipped,recur)) + return toadd -def get_gcal_cred(): +def get_gcal_cred(args): #each such file can only store a single credential storage = oauth2client.file.Storage(gcal_authpath) gcal_credential = storage.get() @@ -377,11 +390,11 @@ def get_gcal_cred(): if gcal_credential is None or gcal_credential.invalid: gcal_credential = oauth2client.tools.run_flow(flow, storage, - oauth2client.tools.argparser.parse_args()) + args) return gcal_credential -def gcal_login(): - gcal_credential = get_gcal_cred() +def gcal_login(args): + gcal_credential = get_gcal_cred(args) # Object to handle http requests; could add proxy details http = httplib2.Http() http = gcal_credential.authorize(http) @@ -392,27 +405,44 @@ def get_gcal_timezone(gcal_account,calendarid="primary"): return exchangelib.EWSTimeZone.timezone(gcal['timeZone']) def main(): + ap=argparse.ArgumentParser(description="Gooswapper calendar sync", + parents=[oauth2client.tools.argparser]) + ap.add_argument("exchuser",help="Exchange user e.g. 'SANGER\mv3'") + ap.add_argument("exchemail", + help="Exchange calendar email e.g. ISGGroup@sanger.ac.uk") + ap.add_argument("-g","--gcalid",help="google Calendar ID") + ap.add_argument("-l","--loop",help="keep running indefinitely", + action="store_true") + args = ap.parse_args() + if args.gcalid is None: + gcal_id = "primary" + else: + gcal_id = args.gcalid try: with open(cachepath,"rb") as f: cache = pickle.load(f) except FileNotFoundError: cache = None - ex_account = ex_login("mv3@sanger.ac.uk",".gooswapper_exch_conf.dat") + ex_account = ex_login(args.exchuser,args.exchemail, + ".gooswapper_exch_conf.dat") current = get_ex_events(ex_account.calendar) - gcal_account = gcal_login() - gcal_tz = get_gcal_timezone(gcal_account) + gcal_account = gcal_login(args) + gcal_tz = get_gcal_timezone(gcal_account,gcal_id) 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) + add_ex_to_gcal(ex_account,gcal_account,gcal_tz,current,added,gcal_id) #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) - update_ex_to_gcal(ex_account,gcal_account,gcal_tz,current,changed) + del_ex_to_gcal(ex_account,gcal_account,cache,deleted,gcal_id) + update_ex_to_gcal(ex_account,gcal_account,gcal_tz,current, + changed,gcal_id) else: - match_ex_to_gcal(ex_account,gcal_account,gcal_tz,current) + toadd = match_ex_to_gcal(ex_account,gcal_account,gcal_tz,current, + gcal_id) + add_ex_to_gcal(ex_account,gcal_account,gcal_tz,current,toadd,gcal_id) with open(cachepath,"wb") as f: pickle.dump(current,f)