imip-agent

imiptools/handlers/person_outgoing.py

886:8a3994e54ea4
2015-10-20 Paul Boddie Permit the selection of a same-day ending while still allowing time adjustments.
     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         self.store.dequeue_request(self.user, self.uid, self.recurrenceid)   132         self.store.remove_counters(self.user, self.uid, self.recurrenceid)   133    134         # Update free/busy information.   135    136         if not counter:   137             self.update_event_in_freebusy(from_organiser)   138    139         # For countered proposals, record the offer in the resource's   140         # free/busy collection.   141    142         else:   143             self.update_event_in_freebusy_offers()   144    145         return True   146    147     def _remove(self):   148    149         """   150         Remove details from the current object given a message originating   151         from an organiser if 'from_organiser' is set to a true value.   152         """   153    154         if not Client.is_participating(self):   155             return False   156    157         # Check for event using UID.   158    159         if not self.have_new_object():   160             return False   161    162         # Obtain any stored object, using parent object details if a newly-   163         # indicated occurrence is referenced.   164    165         obj = self.get_stored_object_version()   166         old = not obj and self.get_parent_object() or obj   167    168         if not old:   169             return False   170    171         # Only cancel the event completely if all attendees are given.   172    173         attendees = uri_dict(old.get_value_map("ATTENDEE"))   174         all_attendees = set(attendees.keys())   175         given_attendees = set(uri_values(self.obj.get_values("ATTENDEE")))   176         cancel_entire_event = not all_attendees.difference(given_attendees)   177    178         # Update the recipient's record of the organiser's schedule.   179    180         self.remove_freebusy_from_organiser(self.obj.get_value("ORGANIZER"))   181    182         # Otherwise, remove the given attendees and update the event.   183    184         if not cancel_entire_event and obj:   185             for attendee in given_attendees:   186                 if attendees.has_key(attendee):   187                     del attendees[attendee]   188             obj["ATTENDEE"] = attendees.items()   189    190         # Update the stored object with sequence information.   191    192         if obj:   193             obj["SEQUENCE"] = self.obj.get_items("SEQUENCE") or []   194             obj["DTSTAMP"] = self.obj.get_items("DTSTAMP") or []   195    196         # Update free/busy information.   197    198         if cancel_entire_event or self.user in given_attendees:   199             self.remove_event_from_freebusy()   200    201         # Set the complete event if not an additional occurrence. For any newly-   202         # indicated occurrence, use the received event details.   203    204         self.store.set_event(self.user, self.uid, self.recurrenceid, (obj or self.obj).to_node())   205    206         # Perform any cancellation after recording the latest state of the   207         # event.   208    209         if cancel_entire_event:   210             self.store.cancel_event(self.user, self.uid, self.recurrenceid)   211    212         # Remove any associated request.   213    214         self.store.dequeue_request(self.user, self.uid, self.recurrenceid)   215         self.store.remove_counters(self.user, self.uid, self.recurrenceid)   216    217         return True   218    219     def _declinecounter(self):   220    221         "Remove any counter-proposals for the given event."   222    223         if not Client.is_participating(self):   224             return False   225    226         # Check for event using UID.   227    228         if not self.have_new_object():   229             return False   230    231         self.remove_counters(uri_values(self.obj.get_values("ATTENDEE")))   232    233 class Event(PersonHandler):   234    235     "An event handler."   236    237     def add(self):   238    239         "Record the addition of a recurrence to an event."   240    241         self._add()   242    243     def cancel(self):   244    245         "Remove an event or a recurrence."   246    247         self._remove()   248    249     def counter(self):   250    251         "Record an offer made by a counter-proposal."   252    253         self._record(False, True)   254    255     def declinecounter(self):   256    257         "Expire any offer made by a counter-proposal."   258    259         self._declinecounter()   260    261     def publish(self):   262    263         "Published events are recorded."   264    265         self._record(True)   266    267     def refresh(self):   268    269         "Requests to refresh events do not provide event information."   270    271         pass   272    273     def reply(self):   274    275         "Replies to requests are inspected for attendee information."   276    277         self._record(False)   278    279     def request(self):   280    281         "Record events sent for potential scheduling."   282    283         self._record(True)   284    285 # Handler registry.   286    287 handlers = [   288     ("VEVENT",      Event),   289     ]   290    291 # vim: tabstop=4 expandtab shiftwidth=4