chiark / gitweb /
Remove old part-written broken function
[gooswapper] / gooswapper.py
1 #!/usr/bin/env python3
2
3 import sys
4 import getpass
5 import os
6 import pickle
7 import logging
8 logger = logging.getLogger('gooswapper')
9 logger.setLevel(logging.INFO)
10 consolelog = logging.StreamHandler()
11 consolelog.setLevel(logging.INFO)
12 logformatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
13 consolelog.setFormatter(logformatter)
14 logger.addHandler(consolelog)
15 #We can't use this, because that way all the libraries' logs spam us
16 #logging.basicConfig(level=logging.INFO)
17
18 #Exchange-related library
19 sys.path.append("/upstreams/exchangelib")
20 import exchangelib
21
22 #Google calendar-api libraries
23 import httplib2
24 import apiclient.discovery
25 import oauth2client
26 import oauth2client.file
27 import oauth2client.client
28
29 #Not sure what the distribution approach here is...
30 gcal_client_id = '805127902516-ptbbtgpq9o8pjr6r3k6hsm60j589o85u.apps.googleusercontent.com'
31 gcal_client_secret = '8hpdxV3MauorryTDoZ1YK8JO'
32
33 #scope URL for r/w calendar access
34 scope = 'https://www.googleapis.com/auth/calendar'
35 #flow object, for doing OAuth2.0 stuff
36 flow = oauth2client.client.OAuth2WebServerFlow(gcal_client_id,
37                                                gcal_client_secret,
38                                                scope)
39
40
41 gcal_authpath=".gooswap_gcal_creds.dat"
42
43 cachepath=".gooswapcache"
44
45 exchange_credential = None
46
47 class ex_gcal_link(exchangelib.ExtendedProperty):
48     distinguished_property_set_id = 'PublicStrings'
49     property_name = "google calendar event id"
50     property_type = 'String'
51
52 exchangelib.CalendarItem.register('gcal_link',ex_gcal_link)
53
54 def get_ex_event_by_itemid(calendar,itemid):
55     return calendar.get(item_id=itemid)
56
57 def get_ex_event_by_id_and_changekey(acct,itemid,changekey):
58     l=list(acct.fetch([(itemid,changekey)]))
59     return list(acct.fetch([(itemid,changekey)]))[0]
60
61 def get_ex_cred(username="SANGER\mv3",password=None):
62     if password is None:
63         password = getpass.getpass(prompt="Password for user %s: " % username)
64     return exchangelib.ServiceAccount(username,password)
65
66 def ex_login(emailaddr,autodiscover=True):
67     global exchange_credential
68     if exchange_credential is None:
69         exchange_credential = get_ex_cred()
70     return exchangelib.Account(emailaddr,
71                                credentials = exchange_credential,
72                                autodiscover = autodiscover)
73
74 def get_ex_events(calendar):
75     ans={}
76     for event in calendar.all().only('changekey','item_id','gcal_link'):
77         if event.gcal_link is not None:
78 #            event.delete()
79             continue
80         if event.item_id in ans:
81             logger.warning("Event item_id %s was duplicated!" % event.item_id)
82         ans[event.item_id] = event.changekey
83     logger.info("%d events found" % len(ans))
84     return ans
85
86 def ex_event_changes(old,new):
87     olds = set(old.keys())
88     news = set(new.keys())
89     added = list(news-olds)
90     deleted = list(olds-news)
91     changed = []
92     #intersection - i.e. common to both sets
93     for event in olds & news:
94         if old[event] != new[event]:
95             changed.append(event)
96     logger.info("%d events updated, %d added, %d deleted" % (len(changed),
97                                                               len(added),
98                                                               len(deleted)))
99     return added, deleted, changed
100
101 def add_ex_to_gcal(ex_acct,
102                    gcal_acct,gcal_tz,events,
103                    added,
104                    gcal_id="primary"):
105     for ev_id in added:
106         event = get_ex_event_by_itemid(ex_acct.calendar,ev_id)
107         if event.is_all_day:
108             gevent={}
109             gevent["summary"]=event.subject
110             gevent["end"]={"date": str(event.end.astimezone(gcal_tz).date())}
111             gevent["start"]={"date": str(event.start.astimezone(gcal_tz).date())}
112             if event.text_body.strip() != '':
113                 gevent["description"] = event.text_body
114             if event.location is not None:
115                 gevent["location"] = event.location
116             gevent["extended_properties"]={"shared": {"ex_id": event.item_id}}
117             gevent=gcal_acct.events().insert(calendarId=gcal_id, body=gevent).execute()
118             event.gcal_link = gevent.get("id")
119             event.save()
120             events[event.item_id] = event.changekey
121         else:
122             logger.warning("only all-day events supported")
123             
124 def get_gcal_cred():
125     #each such file can only store a single credential
126     storage = oauth2client.file.Storage(gcal_authpath)
127     gcal_credential = storage.get()
128     #if no credential found, or they're invalid (e.g. expired),
129     #then get a new one; pass --noauth_local_webserver on the command line
130     #if you don't want it to spawn a browser
131     if gcal_credential is None or gcal_credential.invalid:
132         gcal_credential = oauth2client.tools.run_flow(flow,
133                                                       storage,
134                                                       oauth2client.tools.argparser.parse_args())
135     return gcal_credential
136
137 def gcal_login():
138     gcal_credential = get_gcal_cred()
139     # Object to handle http requests; could add proxy details
140     http = httplib2.Http()
141     http = gcal_credential.authorize(http)
142     return apiclient.discovery.build('calendar', 'v3', http=http)
143
144 def get_gcal_timezone(gcal_account,calendarid="primary"):
145     gcal = gcal_account.calendars().get(calendarId=calendarid).execute()
146     return exchangelib.EWSTimeZone.timezone(gcal['timeZone'])
147
148 def main():
149     try:
150         with open(cachepath,"rb") as f:
151             cache = pickle.load(f)
152     except FileNotFoundError:
153         cache = None
154
155     ex_account = ex_login("mv3@sanger.ac.uk")
156     current = get_ex_events(ex_account.calendar)
157
158     gcal_account = gcal_login()
159     gcal_tz = get_gcal_timezone(gcal_account)
160     
161     if cache is not None:
162         added,deleted,changed = ex_event_changes(cache,current)
163         add_ex_to_gcal(ex_account,gcal_account,gcal_tz,current,added)
164         
165     with open(cachepath,"wb") as f:
166         pickle.dump(current,f)
167
168 if __name__ == "__main__":
169     main()
170
171