# HG changeset patch # User Paul Boddie # Date 1413985161 -7200 # Node ID 93a7beba1d8a0dab8497978d117fa4f8a980f599 # Parent d574e0854a240b4e9806b48cb90ddf745bc42fb4 Moved the resource handlers to a subpackage. diff -r d574e0854a24 -r 93a7beba1d8a imip_resource.py --- a/imip_resource.py Wed Oct 22 14:20:59 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,272 +0,0 @@ -#!/usr/bin/env python - -""" -Handlers for a resource. -""" - -from datetime import date, datetime, timedelta -from imiptools.content import Handler, format_datetime -from imiptools.period import insert_period, period_overlaps, remove_period -from vCalendar import to_node -from vRecurrence import get_parameters, get_rule - -class Event(Handler): - - "An event handler." - - def add(self): - pass - - def cancel(self): - pass - - def counter(self): - - "Since this handler does not send requests, it will not handle replies." - - pass - - def declinecounter(self): - - """ - Since this handler does not send counter proposals, it will not handle - replies to such proposals. - """ - - pass - - def publish(self): - pass - - def refresh(self): - pass - - def reply(self): - - "Since this handler does not send requests, it will not handle replies." - - pass - - def request(self): - - """ - Respond to a request by preparing a reply containing accept/decline - information for each indicated attendee. - - No support for countering requests is implemented. - """ - - oa = self.require_organiser_and_attendees() - if not oa: - return None - - (organiser, organiser_attr), attendees = oa - - # Process each attendee separately. - - calendar = [] - - for attendee, attendee_attr in attendees.items(): - - # Check for event using UID. - - if not self.have_new_object(attendee, "VEVENT"): - continue - - # If newer than any old version, discard old details from the - # free/busy record and check for suitability. - - dtstart = self.get_utc_datetime("DTSTART") - dtend = self.get_utc_datetime("DTEND") - - # NOTE: Need also DURATION support. - - duration = dtend - dtstart - - # Recurrence rules create multiple instances to be checked. - # Conflicts may only be assessed within a period defined by policy - # for the agent, with instances outside that period being considered - # unchecked. - - # NOTE: Need to expose the 100 day window in the configuration. - - window_end = datetime.now() + timedelta(100) - - # NOTE: Need also RDATE and EXDATE support. - - rrule = self.get_value("RRULE") - - if rrule: - selector = get_rule(dtstart, rrule) - parameters = get_parameters(rrule) - periods = [] - for start in selector.materialise(dtstart, window_end, parameters.get("COUNT"), parameters.get("BYSETPOS")): - start = datetime(*start, tzinfo=timezone("UTC")) - end = start + duration - periods.append((format_datetime(start), format_datetime(end))) - else: - periods = [(format_datetime(dtstart), format_datetime(dtend))] - - conflict = False - freebusy = self.store.get_freebusy(attendee) - - if freebusy: - remove_period(freebusy, self.uid) - conflict = True - for start, end in periods: - if period_overlaps(freebusy, (start, end)): - break - else: - conflict = False - else: - freebusy = [] - - # If the event can be scheduled, it is registered and a reply sent - # accepting the event. (The attendee has PARTSTAT=ACCEPTED as an - # attribute.) - - if not conflict: - for start, end in periods: - insert_period(freebusy, (start, end, self.uid)) - - if self.get_value("TRANSP") in (None, "OPAQUE"): - self.store.set_freebusy(attendee, freebusy) - - if self.publisher: - self.publisher.set_freebusy(attendee, freebusy) - - self.store.set_event(attendee, self.uid, to_node( - {"VEVENT" : [(self.details, {})]} - )) - attendee_attr["PARTSTAT"] = "ACCEPTED" - - # If the event cannot be scheduled, it is not registered and a reply - # sent declining the event. (The attendee has PARTSTAT=DECLINED as an - # attribute.) - - else: - attendee_attr["PARTSTAT"] = "DECLINED" - - self.details["ATTENDEE"] = [(attendee, attendee_attr)] - calendar.append(to_node( - {"VEVENT" : [(self.details, {})]} - )) - - return calendar - -class Freebusy(Handler): - - "A free/busy handler." - - def publish(self): - pass - - def reply(self): - - "Since this handler does not send requests, it will not handle replies." - - pass - - def request(self): - - """ - Respond to a request by preparing a reply containing free/busy - information for each indicated attendee. - """ - - oa = self.require_organiser_and_attendees() - if not oa: - return None - - (organiser, organiser_attr), attendees = oa - - # Construct an appropriate fragment. - - calendar = [] - cwrite = calendar.append - - # Get the details for each attendee. - - for attendee, attendee_attr in attendees.items(): - freebusy = self.store.get_freebusy(attendee) - - if freebusy: - record = [] - rwrite = record.append - - rwrite(("ORGANIZER", organiser_attr, organiser)) - rwrite(("ATTENDEE", attendee_attr, attendee)) - rwrite(("UID", {}, self.uid)) - - for start, end, uid in freebusy: - rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, [start, end])) - - cwrite(("VFREEBUSY", {}, record)) - - # Return the reply. - - return calendar - -class Journal(Handler): - - "A journal entry handler." - - def add(self): - pass - - def cancel(self): - pass - - def publish(self): - pass - -class Todo(Handler): - - "A to-do item handler." - - def add(self): - pass - - def cancel(self): - pass - - def counter(self): - - "Since this handler does not send requests, it will not handle replies." - - pass - - def declinecounter(self): - - """ - Since this handler does not send counter proposals, it will not handle - replies to such proposals. - """ - - pass - - def publish(self): - pass - - def refresh(self): - pass - - def reply(self): - - "Since this handler does not send requests, it will not handle replies." - - pass - - def request(self): - pass - -# Handler registry. - -handlers = [ - ("VFREEBUSY", Freebusy), - ("VEVENT", Event), - ("VTODO", Todo), - ("VJOURNAL", Journal), - ] - -# vim: tabstop=4 expandtab shiftwidth=4 diff -r d574e0854a24 -r 93a7beba1d8a imiptools/handlers/resource.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/imiptools/handlers/resource.py Wed Oct 22 15:39:21 2014 +0200 @@ -0,0 +1,272 @@ +#!/usr/bin/env python + +""" +Handlers for a resource. +""" + +from datetime import date, datetime, timedelta +from imiptools.content import Handler, format_datetime +from imiptools.period import insert_period, period_overlaps, remove_period +from vCalendar import to_node +from vRecurrence import get_parameters, get_rule + +class Event(Handler): + + "An event handler." + + def add(self): + pass + + def cancel(self): + pass + + def counter(self): + + "Since this handler does not send requests, it will not handle replies." + + pass + + def declinecounter(self): + + """ + Since this handler does not send counter proposals, it will not handle + replies to such proposals. + """ + + pass + + def publish(self): + pass + + def refresh(self): + pass + + def reply(self): + + "Since this handler does not send requests, it will not handle replies." + + pass + + def request(self): + + """ + Respond to a request by preparing a reply containing accept/decline + information for each indicated attendee. + + No support for countering requests is implemented. + """ + + oa = self.require_organiser_and_attendees() + if not oa: + return None + + (organiser, organiser_attr), attendees = oa + + # Process each attendee separately. + + calendar = [] + + for attendee, attendee_attr in attendees.items(): + + # Check for event using UID. + + if not self.have_new_object(attendee, "VEVENT"): + continue + + # If newer than any old version, discard old details from the + # free/busy record and check for suitability. + + dtstart = self.get_utc_datetime("DTSTART") + dtend = self.get_utc_datetime("DTEND") + + # NOTE: Need also DURATION support. + + duration = dtend - dtstart + + # Recurrence rules create multiple instances to be checked. + # Conflicts may only be assessed within a period defined by policy + # for the agent, with instances outside that period being considered + # unchecked. + + # NOTE: Need to expose the 100 day window in the configuration. + + window_end = datetime.now() + timedelta(100) + + # NOTE: Need also RDATE and EXDATE support. + + rrule = self.get_value("RRULE") + + if rrule: + selector = get_rule(dtstart, rrule) + parameters = get_parameters(rrule) + periods = [] + for start in selector.materialise(dtstart, window_end, parameters.get("COUNT"), parameters.get("BYSETPOS")): + start = datetime(*start, tzinfo=timezone("UTC")) + end = start + duration + periods.append((format_datetime(start), format_datetime(end))) + else: + periods = [(format_datetime(dtstart), format_datetime(dtend))] + + conflict = False + freebusy = self.store.get_freebusy(attendee) + + if freebusy: + remove_period(freebusy, self.uid) + conflict = True + for start, end in periods: + if period_overlaps(freebusy, (start, end)): + break + else: + conflict = False + else: + freebusy = [] + + # If the event can be scheduled, it is registered and a reply sent + # accepting the event. (The attendee has PARTSTAT=ACCEPTED as an + # attribute.) + + if not conflict: + for start, end in periods: + insert_period(freebusy, (start, end, self.uid)) + + if self.get_value("TRANSP") in (None, "OPAQUE"): + self.store.set_freebusy(attendee, freebusy) + + if self.publisher: + self.publisher.set_freebusy(attendee, freebusy) + + self.store.set_event(attendee, self.uid, to_node( + {"VEVENT" : [(self.details, {})]} + )) + attendee_attr["PARTSTAT"] = "ACCEPTED" + + # If the event cannot be scheduled, it is not registered and a reply + # sent declining the event. (The attendee has PARTSTAT=DECLINED as an + # attribute.) + + else: + attendee_attr["PARTSTAT"] = "DECLINED" + + self.details["ATTENDEE"] = [(attendee, attendee_attr)] + calendar.append(to_node( + {"VEVENT" : [(self.details, {})]} + )) + + return calendar + +class Freebusy(Handler): + + "A free/busy handler." + + def publish(self): + pass + + def reply(self): + + "Since this handler does not send requests, it will not handle replies." + + pass + + def request(self): + + """ + Respond to a request by preparing a reply containing free/busy + information for each indicated attendee. + """ + + oa = self.require_organiser_and_attendees() + if not oa: + return None + + (organiser, organiser_attr), attendees = oa + + # Construct an appropriate fragment. + + calendar = [] + cwrite = calendar.append + + # Get the details for each attendee. + + for attendee, attendee_attr in attendees.items(): + freebusy = self.store.get_freebusy(attendee) + + if freebusy: + record = [] + rwrite = record.append + + rwrite(("ORGANIZER", organiser_attr, organiser)) + rwrite(("ATTENDEE", attendee_attr, attendee)) + rwrite(("UID", {}, self.uid)) + + for start, end, uid in freebusy: + rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, [start, end])) + + cwrite(("VFREEBUSY", {}, record)) + + # Return the reply. + + return calendar + +class Journal(Handler): + + "A journal entry handler." + + def add(self): + pass + + def cancel(self): + pass + + def publish(self): + pass + +class Todo(Handler): + + "A to-do item handler." + + def add(self): + pass + + def cancel(self): + pass + + def counter(self): + + "Since this handler does not send requests, it will not handle replies." + + pass + + def declinecounter(self): + + """ + Since this handler does not send counter proposals, it will not handle + replies to such proposals. + """ + + pass + + def publish(self): + pass + + def refresh(self): + pass + + def reply(self): + + "Since this handler does not send requests, it will not handle replies." + + pass + + def request(self): + pass + +# Handler registry. + +handlers = [ + ("VFREEBUSY", Freebusy), + ("VEVENT", Event), + ("VTODO", Todo), + ("VJOURNAL", Journal), + ] + +# vim: tabstop=4 expandtab shiftwidth=4