gremmie@1: """
gremmie@1: This file contains the calendar class wich abstracts the Google gdata API for working with
gremmie@1: Google Calendars.
bgneal@458: 
gremmie@1: """
gremmie@1: import datetime
gremmie@1: import pytz
gremmie@1: 
gremmie@1: from django.utils.tzinfo import FixedOffset
bgneal@458: from gdata.calendar.client import CalendarClient
bgneal@458: from gdata.calendar.data import (CalendarEventEntry, CalendarEventFeed,
bgneal@458:         CalendarWhere, When, EventWho)
bgneal@458: import atom.data
gremmie@1: 
gremmie@1: from gcalendar.models import Event
gremmie@1: 
gremmie@1: 
gremmie@1: class CalendarError(Exception):
bgneal@68:     def __init__(self, msg):
bgneal@68:         self.msg = msg
gremmie@1: 
gremmie@1:     def __str__(self):
bgneal@68:         return repr(self.msg)
gremmie@1: 
gremmie@1: 
gremmie@1: class Calendar(object):
gremmie@1:     DATE_FMT = '%Y-%m-%d'
gremmie@1:     DATE_TIME_FMT = DATE_FMT + 'T%H:%M:%S'
gremmie@1:     DATE_TIME_TZ_FMT = DATE_TIME_FMT + '.000Z'
gremmie@1: 
bgneal@458:     def __init__(self, source=None, calendar_id='default', access_token=None):
bgneal@458:         self.client = CalendarClient(source=source, auth_token=access_token)
bgneal@458: 
bgneal@458:         self.insert_feed = ('https://www.google.com/calendar/feeds/'
bgneal@458:             '%s/private/full' % calendar_id)
gremmie@1:         self.batch_feed = '%s/batch' % self.insert_feed
gremmie@1: 
gremmie@1:     def sync_events(self, qs):
gremmie@1:         request_feed = CalendarEventFeed()
gremmie@1:         for model in qs:
gremmie@1:             if model.status == Event.NEW_APRV:
gremmie@1:                 event = CalendarEventEntry()
gremmie@1:                 request_feed.AddInsert(entry=self._populate_event(model, event))
gremmie@1:             elif model.status == Event.EDIT_APRV:
gremmie@1:                 event = self._retrieve_event(model)
gremmie@1:                 request_feed.AddUpdate(entry=self._populate_event(model, event))
gremmie@1:             elif model.status == Event.DEL_APRV:
gremmie@1:                 event = self._retrieve_event(model)
gremmie@1:                 request_feed.AddDelete(entry=event)
gremmie@1:             else:
gremmie@1:                 assert False, 'unexpected status in sync_events'
gremmie@1: 
bgneal@68:         try:
bgneal@68:             response_feed = self.client.ExecuteBatch(request_feed, self.batch_feed)
bgneal@68:         except Exception, e:
bgneal@68:             raise CalendarError('ExecuteBatch exception: %s' % e)
bgneal@68: 
gremmie@1:         err_msgs = []
gremmie@1:         for entry in response_feed.entry:
gremmie@1:             i = int(entry.batch_id.text)
gremmie@1:             code = int(entry.batch_status.code)
gremmie@1: 
gremmie@1:             error = False
bgneal@458:             if qs[i].status == Event.NEW_APRV:
bgneal@458:                 if code == 201:
bgneal@458:                     qs[i].status = Event.ON_CAL
bgneal@458:                     qs[i].google_id = entry.GetEditLink().href
bgneal@228:                     qs[i].google_url = entry.GetHtmlLink().href
bgneal@458:                     qs[i].save()
bgneal@458:                     qs[i].notify_on_calendar()
bgneal@458:                 else:
bgneal@458:                     error = True
bgneal@458: 
bgneal@458:             elif qs[i].status == Event.EDIT_APRV:
bgneal@458:                 if code == 200:
gremmie@1:                     qs[i].status = Event.ON_CAL
gremmie@1:                     qs[i].save()
gremmie@1:                 else:
gremmie@1:                     error = True
bgneal@458: 
gremmie@1:             elif qs[i].status == Event.DEL_APRV:
gremmie@1:                 if code == 200:
gremmie@1:                     qs[i].delete()
gremmie@1:                 else:
gremmie@1:                     error = True
gremmie@1: 
gremmie@1:             if error:
bgneal@124:                 err_msgs.append('%s - (%d) %s' % (
bgneal@124:                     qs[i].what, code, entry.batch_status.reason))
gremmie@1: 
gremmie@1:         if len(err_msgs) > 0:
bgneal@68:             raise CalendarError(', '.join(err_msgs))
gremmie@1: 
gremmie@1:     def _retrieve_event(self, model):
gremmie@1:         try:
bgneal@458:             event = self.client.GetEventEntry(model.google_id)
bgneal@68:         except Exception, e:
bgneal@68:             raise CalendarError('Could not retrieve event from Google: %s, %s' \
bgneal@68:                     % (model.what, e))
gremmie@1:         return event
gremmie@1: 
gremmie@1:     def _populate_event(self, model, event):
gremmie@1:         """Populates a gdata event from an Event model object."""
bgneal@458:         event.title = atom.data.Title(text=model.what)
bgneal@458:         event.content = atom.data.Content(text=model.html)
bgneal@458:         event.where = [CalendarWhere(value=model.where)]
bgneal@458:         event.who = [EventWho(email=model.user.email)]
gremmie@1: 
gremmie@1:         if model.all_day:
gremmie@1:             start_time = self._make_time(model.start_date)
gremmie@1:             if model.start_date == model.end_date:
gremmie@1:                 end_time = None
gremmie@1:             else:
gremmie@1:                 end_time = self._make_time(model.end_date)
gremmie@1:         else:
gremmie@1:             start_time = self._make_time(model.start_date, model.start_time, model.time_zone)
gremmie@1:             end_time = self._make_time(model.end_date, model.end_time, model.time_zone)
gremmie@1: 
bgneal@458:         event.when = [When(start=start_time, end=end_time)]
gremmie@1:         return event
bgneal@458: 
gremmie@1:     def _make_time(self, date, time=None, tz_name=None):
gremmie@1:         """
gremmie@1:         Returns the gdata formatted date/time string given a date, optional time,
gremmie@1:         and optional time zone name (e.g. 'US/Pacific'). If the time zone name is None,
gremmie@1:         no time zone info will be added to the string.
gremmie@1:         """
gremmie@1: 
gremmie@1:         if time is not None:
gremmie@1:             d = datetime.datetime.combine(date, time)
gremmie@1:         else:
gremmie@1:             d = datetime.datetime(date.year, date.month, date.day)
gremmie@1: 
gremmie@1:         if time is None:
gremmie@1:             s = d.strftime(self.DATE_FMT)
gremmie@1:         elif tz_name is None:
gremmie@1:             s = d.strftime(self.DATE_TIME_FMT)
gremmie@1:         else:
gremmie@1:             try:
gremmie@1:                 tz = pytz.timezone(tz_name)
gremmie@1:             except pytz.UnknownTimeZoneError:
bgneal@68:                 raise CalendarError('Invalid time zone: %s' (tz_name,))
gremmie@1:             local = tz.localize(d)
gremmie@1:             zulu = local.astimezone(FixedOffset(0))
gremmie@1:             s = zulu.strftime(self.DATE_TIME_TZ_FMT)
gremmie@1: 
gremmie@1:         return s
gremmie@1: