imip-agent

imiptools/handlers/common.py

1122:2003934ef901
2016-04-19 Paul Boddie Support replies from attendees that refer to specific recurrences before the organiser does so, thus allowing attendees to selectively accept and decline recurrences. Allowed the test handler to refer to recurrences that have not been explicitly separated from their parent objects. Added a docstring for the Object initialiser as a reminder of how to use it. freebusy-collections
     1 #!/usr/bin/env python     2      3 """     4 Common handler functionality for different entities.     5      6 Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk>     7      8 This program is free software; you can redistribute it and/or modify it under     9 the terms of the GNU General Public License as published by the Free Software    10 Foundation; either version 3 of the License, or (at your option) any later    11 version.    12     13 This program is distributed in the hope that it will be useful, but WITHOUT    14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    15 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    16 details.    17     18 You should have received a copy of the GNU General Public License along with    19 this program.  If not, see <http://www.gnu.org/licenses/>.    20 """    21     22 from imiptools.data import get_address, get_uri, make_freebusy, to_part, \    23                            uri_dict    24 from imiptools.dates import format_datetime    25 from imiptools.period import FreeBusyPeriod, Period    26     27 class CommonFreebusy:    28     29     "Common free/busy mix-in."    30     31     def _record_freebusy(self, from_organiser=True):    32     33         """    34         Record free/busy information for a message originating from an organiser    35         if 'from_organiser' is set to a true value.    36         """    37     38         if from_organiser:    39             organiser_item = self.require_organiser(from_organiser)    40             if not organiser_item:    41                 return    42     43             senders = [organiser_item]    44         else:    45             oa = self.require_organiser_and_attendees(from_organiser)    46             if not oa:    47                 return    48     49             organiser_item, attendees = oa    50             senders = attendees.items()    51     52             if not senders:    53                 return    54     55         freebusy = [FreeBusyPeriod(p.get_start_point(), p.get_end_point()) for p in self.obj.get_period_values("FREEBUSY")]    56         dtstart = self.obj.get_datetime("DTSTART")    57         dtend = self.obj.get_datetime("DTEND")    58         period = Period(dtstart, dtend, self.get_tzid())    59     60         for sender, sender_attr in senders:    61             stored_freebusy = self.store.get_freebusy_for_other_for_update(self.user, sender)    62             stored_freebusy.replace_overlapping(period, freebusy)    63             self.store.set_freebusy_for_other(self.user, stored_freebusy, sender)    64     65     def request(self):    66     67         """    68         Respond to a request by preparing a reply containing free/busy    69         information for each indicated attendee.    70         """    71     72         oa = self.require_organiser_and_attendees()    73         if not oa:    74             return    75     76         (organiser, organiser_attr), attendees = oa    77     78         # Get the details for each attendee.    79     80         responses = []    81         rwrite = responses.append    82     83         # For replies, the organiser and attendee are preserved.    84     85         for attendee, attendee_attr in attendees.items():    86             freebusy = self.store.get_freebusy(attendee)    87     88             # Indicate the actual sender of the reply.    89     90             self.update_sender(attendee_attr)    91     92             dtstart = self.obj.get_datetime("DTSTART")    93             dtend = self.obj.get_datetime("DTEND")    94             period = dtstart and dtend and Period(dtstart, dtend, self.get_tzid()) or None    95     96             rwrite(make_freebusy(freebusy, self.uid, organiser, organiser_attr, attendee, attendee_attr, period))    97     98         # Return the reply.    99    100         self.add_result("REPLY", [get_address(organiser)], to_part("REPLY", responses))   101    102 class CommonEvent:   103    104     "Common outgoing message handling functionality mix-in."   105    106     def is_usable(self, method=None):   107    108         "Return whether the current object is usable with the given 'method'."   109    110         return self.obj and (   111             method in ("CANCEL", "REFRESH") or   112             self.obj.get_datetime("DTSTART") and   113                 (self.obj.get_datetime("DTEND") or self.obj.get_duration("DURATION")))   114    115     def will_refresh(self):   116    117         """   118         Indicate whether a REFRESH message should be used to respond to an ADD   119         message.   120         """   121    122         return not self.get_stored_object_version() or self.get_add_method_response() == "refresh"   123    124     def make_refresh(self):   125    126         "Make a REFRESH message."   127    128         organiser = get_uri(self.obj.get_value("ORGANIZER"))   129         attendees = uri_dict(self.obj.get_value_map("ATTENDEE"))   130    131         # Indicate the actual sender of the message.   132    133         attendee_attr = attendees[self.user]   134         self.update_sender(attendee_attr)   135    136         # Make a new object with a minimal property selection.   137    138         obj = self.obj.copy()   139         obj.preserve(("ORGANIZER", "DTSTAMP", "UID", "RECURRENCE-ID"))   140         obj["ATTENDEE"] = [(self.user, attendee_attr)]   141    142         # Send a REFRESH message in response.   143    144         self.add_result("REFRESH", [get_address(organiser)], obj.to_part("REFRESH"))   145    146     def ensure_occurrence(self):   147    148         """   149         Ensure that the object originating from an attendee corresponds to an   150         existing occurrence of an event, creating or reviving a specific   151         recurrence if necessary.   152    153         Return whether a valid occurrence was found.   154         """   155    156         # Obtain any stored object.   157    158         obj = self.get_stored_object_version()   159    160         # Handle any newly-defined occurrence.   161    162         if not obj:   163    164             # Check for a valid occurrence.   165    166             if not self.is_recurrence():   167                 return False   168    169             # Set the complete event if not an additional occurrence. For any newly-   170             # indicated occurrence, use the received event details.   171    172             self.store.remove_cancellation(self.user, self.uid, self.recurrenceid)   173             self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node())   174    175         return True   176    177 # vim: tabstop=4 expandtab shiftwidth=4