diff options
author | Thomas Bruederli <bruederli@kolabsys.com> | 2014-07-07 02:09:42 (GMT) |
---|---|---|
committer | Thomas Bruederli <bruederli@kolabsys.com> | 2014-07-07 02:09:42 (GMT) |
commit | 223871e43e7ff6cd3c4dcd49e5c362a1fdf912df (patch) | |
tree | 4d1fe11cd8651bfaa14f3af81883315ab31180bd /pykolab/itip | |
parent | 4a76d06a534417920f76fae229c7130a12d2965f (diff) | |
download | pykolab-223871e43e7ff6cd3c4dcd49e5c362a1fdf912df.tar.gz |
Refactored some iTip functions into a dedicated module for shared use
Diffstat (limited to 'pykolab/itip')
-rw-r--r-- | pykolab/itip/__init__.py | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/pykolab/itip/__init__.py b/pykolab/itip/__init__.py new file mode 100644 index 0000000..04b2d55 --- /dev/null +++ b/pykolab/itip/__init__.py @@ -0,0 +1,225 @@ +import icalendar +import pykolab + +from pykolab.xml import to_dt +from pykolab.xml import event_from_ical +from pykolab.translate import _ + +log = pykolab.getLogger('pykolab.wallace') + + +def events_from_message(message, methods=None): + return objects_from_message(message, "VEVENT", methods) + +def todos_from_message(message, methods=None): + return objects_from_message(message, "VTODO", methods) + + +def objects_from_message(message, objname, methods=None): + """ + Obtain the iTip payload from email.message <message> + """ + # Placeholder for any itip_objects found in the message. + itip_objects = [] + seen_uids = [] + + # iTip methods we are actually interested in. Other methods will be ignored. + if methods is None: + methods = [ "REQUEST", "CANCEL" ] + + # Are all iTip messages multipart? No! RFC 6047, section 2.4 states "A + # MIME body part containing content information that conforms to this + # document MUST have (...)" but does not state whether an iTip message must + # therefore also be multipart. + + # Check each part + for part in message.walk(): + + # The iTip part MUST be Content-Type: text/calendar (RFC 6047, section 2.4) + # But in real word, other mime-types are used as well + if part.get_content_type() in [ "text/calendar", "text/x-vcalendar", "application/ics" ]: + if not str(part.get_param('method')).upper() in methods: + log.info(_("Method %r not really interesting for us.") % (part.get_param('method'))) + continue + + # Get the itip_payload + itip_payload = part.get_payload(decode=True) + + log.debug(_("Raw iTip payload: %s") % (itip_payload), level=9) + + # Python iCalendar prior to 3.0 uses "from_string". + if hasattr(icalendar.Calendar, 'from_ical'): + cal = icalendar.Calendar.from_ical(itip_payload) + elif hasattr(icalendar.Calendar, 'from_string'): + cal = icalendar.Calendar.from_string(itip_payload) + + # If we can't read it, we're out + else: + log.error(_("Could not read iTip from message.")) + return [] + + for c in cal.walk(): + if c.name == objname: + itip = {} + + if c['uid'] in seen_uids: + log.debug(_("Duplicate iTip object: %s") % (c['uid']), level=9) + continue + + # From the event, take the following properties: + # + # - method + # - uid + # - sequence + # - start + # - end (if any) + # - duration (if any) + # - organizer + # - attendees (if any) + # - resources (if any) + # + + itip['uid'] = str(c['uid']) + itip['method'] = str(cal['method']).upper() + itip['sequence'] = int(c['sequence']) if c.has_key('sequence') else 0 + + if c.has_key('dtstart'): + itip['start'] = c['dtstart'].dt + else: + log.error(_("iTip event without a start")) + continue + + if c.has_key('dtend'): + itip['end'] = c['dtend'].dt + + if c.has_key('duration'): + itip['duration'] = c['duration'].dt + itip['end'] = itip['start'] + c['duration'].dt + + itip['organizer'] = c['organizer'] + + itip['attendees'] = c['attendee'] + + if c.has_key('resources'): + itip['resources'] = c['resources'] + + itip['raw'] = itip_payload + + try: + # TODO: distinguish event and todo here + itip['xml'] = event_from_ical(c.to_ical()) + except Exception, e: + log.error("event_from_ical() exception: %r" % (e)) + continue + + itip_objects.append(itip) + + seen_uids.append(c['uid']) + + # end if c.name == "VEVENT" + + # end for c in cal.walk() + + # end if part.get_content_type() == "text/calendar" + + # end for part in message.walk() + + if not len(itip_objects) and not message.is_multipart(): + log.debug(_("Message is not an iTip message (non-multipart message)"), level=5) + + return itip_objects + + +def check_event_conflict(kolab_event, itip_event): + """ + Determine whether the given kolab event conflicts with the given itip event + """ + conflict = False + + # don't consider conflict with myself + if kolab_event.uid == itip_event['uid']: + return conflict + + _es = to_dt(kolab_event.get_start()) + _ee = to_dt(kolab_event.get_end()) + + # naive loops to check for collisions in (recurring) events + # TODO: compare recurrence rules directly (e.g. matching time slot or weekday or monthday) + while not conflict and _es is not None: + _is = to_dt(itip_event['start']) + _ie = to_dt(itip_event['end']) + + while not conflict and _is is not None: + # log.debug("* Comparing event dates at %s/%s with %s/%s" % (_es, _ee, _is, _ie), level=9) + conflict = check_date_conflict(_es, _ee, _is, _ie) + _is = to_dt(itip_event['xml'].get_next_occurence(_is)) if kolab_event.is_recurring() else None + _ie = to_dt(itip_event['xml'].get_occurence_end_date(_is)) + + _es = to_dt(kolab_event.get_next_occurence(_es)) if kolab_event.is_recurring() else None + _ee = to_dt(kolab_event.get_occurence_end_date(_es)) + + return conflict + + +def check_date_conflict(_es, _ee, _is, _ie): + """ + Check the given event start/end dates for conflicts + """ + conflict = False + + # TODO: add margin for all-day dates (+13h; -12h) + + if _es < _is: + if _es <= _ie: + if _ee <= _is: + conflict = False + else: + conflict = True + else: + conflict = True + elif _es == _is: + conflict = True + else: # _es > _is + if _es <= _ie: + conflict = True + else: + conflict = False + + return conflict + + +def send_reply(from_address, itip_events, response_text, subject=None): + """ + Send the given iCal events as a valid iTip REPLY to the organizer. + """ + + import smtplib + smtp = smtplib.SMTP("localhost", 10027) + + conf = pykolab.getConf() + + if conf.debuglevel > 8: + smtp.set_debuglevel(True) + + if isinstance(itip_events, dict): + itip_events = [ itip_events ] + + for itip_event in itip_events: + attendee = itip_event['xml'].get_attendee_by_email(from_address) + participant_status = itip_event['xml'].get_ical_attendee_participant_status(attendee) + + event_summary = itip_event['xml'].get_summary() + message_text = response_text % { 'summary':event_summary, 'status':_(participant_status), 'name':attendee.get_name() } + + if subject is not None: + subject = subject % { 'summary':event_summary, 'status':_(participant_status), 'name':attendee.get_name() } + + message = itip_event['xml'].to_message_itip(from_address, + method="REPLY", + participant_status=participant_status, + message_text=message_text, + subject=subject + ) + smtp.sendmail(message['From'], message['To'], message.as_string()) + + smtp.quit() |