imip-agent

imiptools/handlers/resource.py

523:b9c05d30449f
2015-05-15 Paul Boddie Support the cancellation of previously unseparated recurrences.
     1 #!/usr/bin/env python     2      3 """     4 Handlers for a resource.     5      6 Copyright (C) 2014, 2015 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, to_part    23 from imiptools.handlers import Handler    24 from imiptools.handlers.common import CommonFreebusy    25 from imiptools.period import remove_affected_period    26     27 class ResourceHandler(Handler):    28     29     "Handling mechanisms specific to resources."    30     31     def _record_and_respond(self, handle_for_attendee):    32     33         """    34         Record details from the incoming message, using the given    35         'handle_for_attendee' callable to process any valid message    36         appropriately.    37         """    38     39         oa = self.require_organiser_and_attendees()    40         if not oa:    41             return None    42     43         organiser_item, attendees = oa    44     45         # Process each attendee separately.    46     47         calendar = []    48     49         # Process for the current user, a resource as attendee.    50     51         if not self.have_new_object() or not self.is_attendee(self.user):    52             return None    53     54         # Collect response objects produced when handling the request.    55     56         response = handle_for_attendee()    57         if response:    58             calendar.append(response)    59     60         return calendar    61     62     def _schedule_for_attendee(self):    63     64         """    65         Schedule the current object for the current user.    66         """    67     68         # Interpretation of periods can depend on the time zone.    69     70         tzid = self.get_tzid()    71     72         # If newer than any old version, discard old details from the    73         # free/busy record and check for suitability.    74     75         periods = self.obj.get_periods_for_freebusy(tzid, self.get_window_end())    76         freebusy = self.store.get_freebusy(self.user)    77         scheduled = self.can_schedule(freebusy, periods)    78     79         attendee_attr = self.obj.get_value_map("ATTENDEE").get(self.user)    80     81         attendee_attr["PARTSTAT"] = scheduled and "ACCEPTED" or "DECLINED"    82         if attendee_attr.has_key("RSVP"):    83             del attendee_attr["RSVP"]    84         if self.messenger and self.messenger.sender != get_address(self.user):    85             attendee_attr["SENT-BY"] = get_uri(self.messenger.sender)    86     87         # Make a version of the request with just this attendee.    88     89         self.obj["ATTENDEE"] = [(self.user, attendee_attr)]    90     91         # Update the DTSTAMP.    92     93         self.update_dtstamp()    94     95         # Set the complete event or an additional occurrence.    96     97         event = self.obj.to_node()    98         self.store.set_event(self.user, self.uid, self.recurrenceid, event)    99    100         # Remove additional recurrences if handling a complete event.   101    102         if not self.recurrenceid:   103             self.store.remove_recurrences(self.user, self.uid)   104    105         # Only update free/busy details if the event is scheduled.   106    107         if scheduled:   108             self.update_freebusy(freebusy, periods)   109         else:   110             self.remove_from_freebusy(freebusy)   111    112         # Remove original recurrence details replaced by additional   113         # recurrences, as well as obsolete additional recurrences.   114    115         self.remove_freebusy_for_recurrences(freebusy, self.store.get_recurrences(self.user, self.uid))   116         self.store.set_freebusy(self.user, freebusy)   117    118         if self.publisher and self.is_sharing():   119             self.publisher.set_freebusy(self.user, freebusy)   120    121         return event   122    123     def _cancel_for_attendee(self):   124    125         """   126         Cancel for the current user their attendance of the event described by   127         the current object.   128         """   129    130         self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node())   131         self.store.cancel_event(self.user, self.uid, self.recurrenceid)   132    133         freebusy = self.store.get_freebusy(self.user)   134         self.remove_from_freebusy(freebusy)   135         self.store.set_freebusy(self.user, freebusy)   136    137         if self.publisher and self.is_sharing():   138             self.publisher.set_freebusy(self.user, freebusy)   139    140         return None   141    142 class Event(ResourceHandler):   143    144     "An event handler."   145    146     def add(self):   147         pass   148    149     def cancel(self):   150    151         "Cancel attendance for attendees."   152    153         self._record_and_respond(self._cancel_for_attendee)   154    155     def counter(self):   156    157         "Since this handler does not send requests, it will not handle replies."   158    159         pass   160    161     def declinecounter(self):   162    163         """   164         Since this handler does not send counter proposals, it will not handle   165         replies to such proposals.   166         """   167    168         pass   169    170     def publish(self):   171         pass   172    173     def refresh(self):   174         pass   175    176     def reply(self):   177    178         "Since this handler does not send requests, it will not handle replies."   179    180         pass   181    182     def request(self):   183    184         """   185         Respond to a request by preparing a reply containing accept/decline   186         information for the recipient.   187    188         No support for countering requests is implemented.   189         """   190    191         response = self._record_and_respond(self._schedule_for_attendee)   192         if response:   193             self.add_result("REPLY", map(get_address, self.obj.get_values("ORGANIZER")), to_part("REPLY", response))   194    195 class Freebusy(Handler, CommonFreebusy):   196    197     "A free/busy handler."   198    199     def publish(self):   200         pass   201    202     def reply(self):   203    204         "Since this handler does not send requests, it will not handle replies."   205    206         pass   207    208     # request provided by CommonFreeBusy.request   209    210 # Handler registry.   211    212 handlers = [   213     ("VFREEBUSY",   Freebusy),   214     ("VEVENT",      Event),   215     ]   216    217 # vim: tabstop=4 expandtab shiftwidth=4