imip-agent

imiptools/handlers/person_outgoing.py

1065:2175787d1896
2016-03-04 Paul Boddie Merged changes from the default branch. freebusy-collections
     1 #!/usr/bin/env python     2      3 """     4 Handlers for a person for whom scheduling is performed, inspecting outgoing     5 messages to obtain scheduling done externally.     6      7 Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>     8      9 This program is free software; you can redistribute it and/or modify it under    10 the terms of the GNU General Public License as published by the Free Software    11 Foundation; either version 3 of the License, or (at your option) any later    12 version.    13     14 This program is distributed in the hope that it will be useful, but WITHOUT    15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    16 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    17 details.    18     19 You should have received a copy of the GNU General Public License along with    20 this program.  If not, see <http://www.gnu.org/licenses/>.    21 """    22     23 from imiptools.client import Client    24 from imiptools.data import get_uri, uri_dict, uri_values    25 from imiptools.handlers import Handler    26 from imiptools.handlers.common import CommonEvent    27     28 class PersonHandler(CommonEvent, Handler):    29     30     "Handling mechanisms specific to people."    31     32     def set_identity(self, method):    33     34         """    35         Set the current user for the current object in the context of the given    36         'method'. It is usually set when initialising the handler, using the    37         recipient details, but outgoing messages do not reference the recipient    38         in this way.    39         """    40     41         if self.obj and not self.user:    42             from_organiser = method in self.organiser_methods    43             if from_organiser:    44                 self.user = get_uri(self.obj.get_value("ORGANIZER"))    45     46             # Since there may be many attendees in an attendee-provided outgoing    47             # message, because counter-proposals can have more than one    48             # attendee, the attendee originating from the calendar system is    49             # chosen.    50     51             else:    52                 self.user = self.get_sending_attendee()    53     54     def _add(self):    55     56         "Add a recurrence for the current object."    57     58         if not Client.is_participating(self):    59             return False    60     61         # Check for event using UID.    62     63         if not self.have_new_object():    64             return False    65     66         # Ignore unknown objects.    67     68         if not self.get_stored_object_version():    69             return    70     71         # Record the event as a recurrence of the parent object.    72     73         self.update_recurrenceid()    74     75         # Set the additional occurrence.    76     77         self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node())    78     79         # Remove any previous cancellations involving this event.    80     81         self.store.remove_cancellation(self.user, self.uid, self.recurrenceid)    82     83         # Update free/busy information.    84     85         self.update_event_in_freebusy()    86     87         return True    88     89     def _record(self, from_organiser=True, counter=False):    90     91         """    92         Record details from the current object given a message originating    93         from an organiser if 'from_organiser' is set to a true value.    94         """    95     96         if not Client.is_participating(self):    97             return False    98     99         # Check for a new event, tolerating not-strictly-new events if the   100         # attendee is responding.   101    102         if not self.have_new_object(strict=from_organiser):   103             return False   104    105         # Update the object.   106    107         if from_organiser:   108    109             # Set the complete event or an additional occurrence.   110    111             self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node())   112    113             # Remove additional recurrences if handling a complete event.   114             # Also remove any previous cancellations involving this event.   115    116             if not self.recurrenceid:   117                 self.store.remove_recurrences(self.user, self.uid)   118                 self.store.remove_cancellations(self.user, self.uid)   119             else:   120                 self.store.remove_cancellation(self.user, self.uid, self.recurrenceid)   121    122         else:   123             # Obtain valid attendees, merging their attendance with the stored   124             # object.   125    126             attendees = self.require_attendees(from_organiser)   127             self.merge_attendance(attendees)   128    129         # Remove any associated request.   130    131         if from_organiser or self.has_indicated_attendance():   132             self.store.dequeue_request(self.user, self.uid, self.recurrenceid)   133         self.store.remove_counters(self.user, self.uid, self.recurrenceid)   134    135         # Update free/busy information.   136    137         if not counter:   138             self.update_event_in_freebusy(from_organiser)   139    140         # For countered proposals, record the offer in the resource's   141         # free/busy collection.   142    143         else:   144             self.update_event_in_freebusy_offers()   145    146         return True   147    148     def _remove(self):   149    150         """   151         Remove details from the current object given a message originating   152         from an organiser if 'from_organiser' is set to a true value.   153         """   154    155         if not Client.is_participating(self):   156             return False   157    158         # Check for event using UID.   159    160         if not self.have_new_object():   161             return False   162    163         # Obtain any stored object, using parent object details if a newly-   164         # indicated occurrence is referenced.   165    166         obj = self.get_stored_object_version()   167         old = not obj and self.get_parent_object() or obj   168    169         if not old:   170             return False   171    172         # Only cancel the event completely if all attendees are given.   173    174         attendees = uri_dict(old.get_value_map("ATTENDEE"))   175         all_attendees = set(attendees.keys())   176         given_attendees = set(uri_values(self.obj.get_values("ATTENDEE")))   177         cancel_entire_event = not all_attendees.difference(given_attendees)   178    179         # Update the recipient's record of the organiser's schedule.   180    181         self.remove_freebusy_from_organiser(self.obj.get_value("ORGANIZER"))   182    183         # Otherwise, remove the given attendees and update the event.   184    185         if not cancel_entire_event and obj:   186             for attendee in given_attendees:   187                 if attendees.has_key(attendee):   188                     del attendees[attendee]   189             obj["ATTENDEE"] = attendees.items()   190    191         # Update the stored object with sequence information.   192    193         if obj:   194             obj["SEQUENCE"] = self.obj.get_items("SEQUENCE") or []   195             obj["DTSTAMP"] = self.obj.get_items("DTSTAMP") or []   196    197         # Update free/busy information.   198    199         if cancel_entire_event or self.user in given_attendees:   200             self.remove_event_from_freebusy()   201             self.remove_freebusy_from_attendees(attendees)   202    203         # Set the complete event if not an additional occurrence. For any newly-   204         # indicated occurrence, use the received event details.   205    206         self.store.set_event(self.user, self.uid, self.recurrenceid, (obj or self.obj).to_node())   207    208         # Perform any cancellation after recording the latest state of the   209         # event.   210    211         if cancel_entire_event:   212             self.store.cancel_event(self.user, self.uid, self.recurrenceid)   213    214         # Remove any associated request.   215    216         self.store.dequeue_request(self.user, self.uid, self.recurrenceid)   217         self.store.remove_counters(self.user, self.uid, self.recurrenceid)   218    219         return True   220    221     def _declinecounter(self):   222    223         "Remove any counter-proposals for the given event."   224    225         if not Client.is_participating(self):   226             return False   227    228         # Check for event using UID.   229    230         if not self.have_new_object():   231             return False   232    233         self.remove_counters(uri_values(self.obj.get_values("ATTENDEE")))   234    235 class Event(PersonHandler):   236    237     "An event handler."   238    239     def add(self):   240    241         "Record the addition of a recurrence to an event."   242    243         self._add()   244    245     def cancel(self):   246    247         "Remove an event or a recurrence."   248    249         self._remove()   250    251     def counter(self):   252    253         "Record an offer made by a counter-proposal."   254    255         self._record(False, True)   256    257     def declinecounter(self):   258    259         "Expire any offer made by a counter-proposal."   260    261         self._declinecounter()   262    263     def publish(self):   264    265         "Published events are recorded."   266    267         self._record(True)   268    269     def refresh(self):   270    271         "Requests to refresh events do not provide event information."   272    273         pass   274    275     def reply(self):   276    277         "Replies to requests are inspected for attendee information."   278    279         self._record(False)   280    281     def request(self):   282    283         "Record events sent for potential scheduling."   284    285         self._record(True)   286    287 # Handler registry.   288    289 handlers = [   290     ("VEVENT",      Event),   291     ]   292    293 # vim: tabstop=4 expandtab shiftwidth=4