imip-agent

imiptools/handlers/person.py

738:ea254765154b
2015-09-13 Paul Boddie Removed redundant attendee tests performed by the require_attendees method.
     1 #!/usr/bin/env python     2      3 """     4 Handlers for a person for whom scheduling is performed.     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, to_part, uri_dict, uri_item    23 from imiptools.handlers import Handler    24 from imiptools.handlers.common import CommonFreebusy, CommonEvent    25 from imiptools.period import FreeBusyPeriod, Period, replace_overlapping    26     27 class PersonHandler(CommonEvent, Handler):    28     29     "Event handling mechanisms specific to people."    30     31     def _add(self, queue=True):    32     33         """    34         Add an event occurrence for the current object or produce a response    35         that requests the event details to be sent again.    36         """    37     38         # Obtain valid organiser and attendee details.    39     40         oa = self.require_organiser_and_attendees()    41         if not oa:    42             return False    43     44         (organiser, organiser_attr), attendees = oa    45     46         # Request details where configured, doing so for unknown objects anyway.    47     48         if self.will_refresh():    49             self.make_refresh()    50     51         # Record the event as a recurrence of the parent object.    52     53         self.update_recurrenceid()    54     55         # Update the recipient's record of the organiser's schedule.    56     57         self.update_freebusy_from_organiser(organiser)    58     59         # Stop if requesting the full event.    60     61         if self.will_refresh():    62             return    63     64         # Set the additional occurrence.    65     66         self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node())    67     68         # Queue any request, if appropriate.    69     70         if queue:    71             self.store.queue_request(self.user, self.uid, self.recurrenceid)    72     73         return True    74     75     def _record(self, from_organiser=True, queue=False, cancel=False):    76     77         """    78         Record details from the current object given a message originating    79         from an organiser if 'from_organiser' is set to a true value, queuing a    80         request if 'queue' is set to a true value, or cancelling an event if    81         'cancel' is set to a true value.    82         """    83     84         # Obtain valid organiser and attendee details.    85     86         oa = self.require_organiser_and_attendees(from_organiser)    87         if not oa:    88             return False    89     90         (organiser, organiser_attr), attendees = oa    91     92         # Handle notifications and invitations.    93     94         if from_organiser:    95     96             # Process for the current user, an attendee.    97     98             if not self.have_new_object():    99                 return False   100    101             # Remove additional recurrences if handling a complete event.   102    103             if not self.recurrenceid:   104                 self.store.remove_recurrences(self.user, self.uid)   105    106             # Queue any request, if appropriate.   107    108             if queue:   109                 self.store.queue_request(self.user, self.uid, self.recurrenceid)   110    111             # Cancel complete events or particular occurrences in recurring   112             # events.   113    114             if cancel:   115                 self.store.cancel_event(self.user, self.uid, self.recurrenceid)   116                 self.store.dequeue_request(self.user, self.uid, self.recurrenceid)   117    118                 # Remove any associated request.   119    120                 self.store.dequeue_request(self.user, self.uid, self.recurrenceid)   121    122                 # No return message will occur to update the free/busy   123                 # information, so this is done here using outgoing message   124                 # functionality.   125    126                 self.remove_event_from_freebusy()   127    128                 # Update the recipient's record of the organiser's schedule.   129    130                 self.remove_freebusy_from_organiser(organiser)   131    132             else:   133                 self.update_freebusy_from_organiser(organiser)   134    135             # Set the complete event or an additional occurrence.   136    137             self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node())   138    139         # As organiser, update attendance from valid attendees.   140    141         else:   142             if self.merge_attendance(attendees):   143                 self.update_freebusy_from_attendees(attendees)   144    145         return True   146    147     def _refresh(self):   148    149         """   150         Respond to a refresh message by providing complete event details to   151         attendees.   152         """   153    154         # Obtain valid organiser and attendee details.   155    156         oa = self.require_organiser_and_attendees(False)   157         if not oa:   158             return False   159    160         (organiser, organiser_attr), attendees = oa   161    162         # Filter by stored attendees.   163    164         obj = self.get_stored_object_version()   165         stored_attendees = set(obj.get_values("ATTENDEE"))   166         attendees = stored_attendees.intersection(attendees)   167    168         if not attendees:   169             return False   170    171         # Assume that the outcome will be a request. It would not seem   172         # completely bizarre to produce a publishing message instead if a   173         # refresh message was unprovoked.   174    175         method = "REQUEST"   176    177         for attendee in attendees:   178             responses = []   179    180             # Get the parent event, add SENT-BY details to the organiser.   181    182             obj = self.get_stored_object_version()   183    184             if self.is_participating(attendee, obj=obj):   185                 organiser, organiser_attr = uri_item(obj.get_item("ORGANIZER"))   186                 self.update_sender(organiser_attr)   187                 responses.append(obj.to_node())   188    189             # Get recurrences.   190    191             if not self.recurrenceid:   192                 for recurrenceid in self.store.get_active_recurrences(self.user, self.uid):   193    194                     # Get the recurrence, add SENT-BY details to the organiser.   195    196                     obj = self.get_stored_object(self.uid, recurrenceid)   197    198                     if self.is_participating(attendee, obj=obj):   199                         organiser, organiser_attr = uri_item(obj.get_item("ORGANIZER"))   200                         self.update_sender(organiser_attr)   201                         responses.append(obj.to_node())   202    203             self.add_result(method, [get_address(attendee)], to_part(method, responses))   204    205         return True   206    207 class Event(PersonHandler):   208    209     "An event handler."   210    211     def add(self):   212    213         "Queue a suggested additional recurrence for any active event."   214    215         if self.allow_add() and self._add(queue=True):   216             return self.wrap("An addition to an event has been received.")   217    218     def cancel(self):   219    220         "Queue a cancellation of any active event."   221    222         if self._record(from_organiser=True, queue=False, cancel=True):   223             return self.wrap("An event cancellation has been received.", link=False)   224    225     def counter(self):   226    227         # NOTE: Queue a suggested modification to any active event.   228    229         return self.wrap("A counter proposal to an event invitation has been received.", link=False)   230    231     def declinecounter(self):   232    233         # NOTE: Queue a rejected modification to any active event.   234    235         return self.wrap("Your counter proposal to an event invitation has been declined.", link=False)   236    237     def publish(self):   238    239         "Register details of any relevant event."   240    241         if self._record(from_organiser=True, queue=False):   242             return self.wrap("Details of an event have been received.")   243    244     def refresh(self):   245    246         "Requests to refresh events are handled either here or by the client."   247    248         if self.is_refreshing():   249             return self._refresh()   250         else:   251             return self.wrap("A request for updated event details has been received.")   252    253     def reply(self):   254    255         "Record replies and notify the recipient."   256    257         if self._record(from_organiser=False, queue=False):   258             return self.wrap("A reply to an event invitation has been received.")   259    260     def request(self):   261    262         "Hold requests and notify the recipient."   263    264         if self._record(from_organiser=True, queue=True):   265             return self.wrap("An event invitation has been received.")   266    267 class PersonFreebusy(CommonFreebusy, Handler):   268    269     "Free/busy handling mechanisms specific to people."   270    271     def _record_freebusy(self, from_organiser=True):   272    273         """   274         Record free/busy information for a message originating from an organiser   275         if 'from_organiser' is set to a true value.   276         """   277    278         if from_organiser:   279             organiser_item = self.require_organiser(from_organiser)   280             if not organiser_item:   281                 return   282    283             senders = [organiser_item]   284         else:   285             oa = self.require_organiser_and_attendees(from_organiser)   286             if not oa:   287                 return   288    289             organiser_item, attendees = oa   290             senders = attendees.items()   291    292             if not senders:   293                 return   294    295         freebusy = [FreeBusyPeriod(p.get_start_point(), p.get_end_point()) for p in self.obj.get_period_values("FREEBUSY")]   296         dtstart = self.obj.get_datetime("DTSTART")   297         dtend = self.obj.get_datetime("DTEND")   298         period = Period(dtstart, dtend, self.get_tzid())   299    300         for sender, sender_attr in senders:   301             stored_freebusy = self.store.get_freebusy_for_other(self.user, sender)   302             replace_overlapping(stored_freebusy, period, freebusy)   303             self.store.set_freebusy_for_other(self.user, stored_freebusy, sender)   304    305 class Freebusy(PersonFreebusy):   306    307     "A free/busy handler."   308    309     def publish(self):   310    311         "Register free/busy information."   312    313         self._record_freebusy(from_organiser=True)   314    315         # Produce a message if configured to do so.   316    317         if self.is_notifying():   318             return self.wrap("A free/busy update has been received.", link=False)   319    320     def reply(self):   321    322         "Record replies and notify the recipient."   323    324         self._record_freebusy(from_organiser=False)   325    326         # Produce a message if configured to do so.   327    328         if self.is_notifying():   329             return self.wrap("A reply to a free/busy request has been received.", link=False)   330    331     def request(self):   332    333         """   334         Respond to a request by preparing a reply containing free/busy   335         information for the recipient.   336         """   337    338         # Produce a reply if configured to do so.   339    340         if self.is_sharing():   341             return CommonFreebusy.request(self)   342    343 # Handler registry.   344    345 handlers = [   346     ("VFREEBUSY",   Freebusy),   347     ("VEVENT",      Event),   348     ]   349    350 # vim: tabstop=4 expandtab shiftwidth=4