imip-agent

imiptools/handlers/person.py

142:caf2415fb036
2014-12-19 Paul Boddie Added support for queuing cancellation requests for approval (or review) in the manager.
     1 #!/usr/bin/env python     2      3 """     4 Handlers for a person for whom scheduling is performed.     5 """     6      7 from email.mime.text import MIMEText     8 from imiptools.config import MANAGER_PATH, MANAGER_URL     9 from imiptools.content import Handler, get_address, get_uri, to_part, uri_dict, uri_items    10 from imiptools.handlers.common import CommonFreebusy    11 from socket import gethostname    12 from vCalendar import to_node    13     14 def get_manager_url():    15     url_base = MANAGER_URL or "http://%s/" % gethostname()    16     return "%s/%s" % (url_base.rstrip("/"), MANAGER_PATH.lstrip("/"))    17     18 def get_object_url(uid):    19     return "%s/%s" % (get_manager_url().rstrip("/"), uid)    20     21 class PersonHandler(Handler):    22     23     "Handling mechanisms specific to people."    24     25     def _record_and_deliver(self, objtype, from_organiser=True, queue=False, cancel=False):    26     27         oa = self.require_organiser_and_attendees(from_organiser)    28         if not oa:    29             return False    30     31         (organiser, organiser_attr), attendees = organiser_item, attendees = oa    32     33         # Validate the organiser or attendee, ignoring spoofed requests.    34     35         if not self.validate_identities(from_organiser and [organiser_item] or attendees.items()):    36             return False    37     38         # Handle notifications and invitations.    39     40         if from_organiser:    41     42             # Process each attendee separately.    43     44             for attendee, attendee_attr in attendees.items():    45     46                 if not self.have_new_object(attendee, objtype):    47                     continue    48     49                 # Store the object and queue any request.    50     51                 self.store.set_event(attendee, self.uid, to_node(    52                     {objtype : [(self.details, {})]}    53                     ))    54     55                 if queue:    56                     self.store.queue_request(attendee, self.uid)    57                 elif cancel:    58                     self.store.cancel_event(attendee, self.uid)    59     60         # As organiser, update attendance.    61     62         else:    63             obj = self.get_object(organiser, objtype)    64     65             if obj and self.have_new_object(organiser, objtype, obj):    66     67                 # Get attendee details in a usable form.    68     69                 attendee_map = uri_dict(self.get_value_map("ATTENDEE"))    70     71                 for attendee, attendee_attr in attendees.items():    72     73                     # Update attendance in the loaded object.    74     75                     attendee_map[attendee] = attendee_attr    76     77                 # Set the new details and store the object.    78     79                 obj["ATTENDEE"] = attendee_map.items()    80     81                 self.store.set_event(organiser, self.uid, to_node(    82                     {objtype : [(obj, {})]}    83                     ))    84     85         return True    86     87     def _record_freebusy(self, from_organiser=True):    88     89         "Record free/busy information for the received information."    90     91         freebusy = []    92     93         for value in self.get_values("FREEBUSY") or []:    94             if not isinstance(value, list):    95                 value = [value]    96             for v in value:    97                 try:    98                     start, end = v.split("/", 1)    99                     freebusy.append((start, end))   100                 except ValueError:   101                     pass   102    103         for sender, sender_attr in uri_items(self.get_items(from_organiser and "ORGANIZER" or "ATTENDEE")):   104             for recipient in self.recipients:   105                 self.store.set_freebusy_for_other(get_uri(recipient), freebusy, sender)   106    107     def wrap(self, method, text, from_organiser=True, link=True):   108    109         "Wrap any valid message and pass it on to the recipient."   110    111         texts = []   112         texts.append(text)   113         if link:   114             texts.append("If your mail program cannot handle this "   115                          "message, you may view the details here:\n\n%s" %   116                          get_object_url(self.uid))   117    118         return method, MIMEText("\n".join(texts))   119    120 class Event(PersonHandler):   121    122     "An event handler."   123    124     def add(self):   125    126         # NOTE: Queue a suggested modification to any active event.   127    128         # The message is now wrapped and passed on to the recipient.   129    130         return "ADD", MIMEText("An addition to an event has been received.")   131    132     def cancel(self):   133    134         "Queue a cancellation of any active event."   135    136         self._record_and_deliver("VEVENT", from_organiser=True, queue=False, cancel=True)   137         return self.wrap("CANCEL", "A cancellation has been received.", from_organiser=True, link=True)   138    139     def counter(self):   140    141         # NOTE: Queue a suggested modification to any active event.   142    143         # The message is now wrapped and passed on to the recipient.   144    145         return "COUNTER", MIMEText("A counter proposal has been received.")   146    147     def declinecounter(self):   148    149         # NOTE: Queue a suggested modification to any active event.   150    151         # The message is now wrapped and passed on to the recipient.   152    153         return "DECLINECOUNTER", MIMEText("A declining counter proposal has been received.")   154    155     def publish(self):   156    157         "Register details of any relevant event."   158    159         self._record_and_deliver("VEVENT", from_organiser=True, queue=False)   160         return self.wrap("PUBLISH", "Details of an event have been received.", from_organiser=True, link=True)   161    162     def refresh(self):   163    164         "Update details of any active event."   165    166         self._record_and_deliver("VEVENT", from_organiser=True, queue=False)   167         return self.wrap("REFRESH", "An event update has been received.", from_organiser=True, link=True)   168    169     def reply(self):   170    171         "Record replies and notify the recipient."   172    173         self._record_and_deliver("VEVENT", from_organiser=False, queue=False)   174         return self.wrap("REPLY", "A reply has been received.", from_organiser=False, link=True)   175    176     def request(self):   177    178         "Hold requests and notify the recipient."   179    180         self._record_and_deliver("VEVENT", from_organiser=True, queue=True)   181    182         # The message is now wrapped and passed on to the recipient.   183    184         return "REQUEST", MIMEText("A request has been queued and can be viewed here: %s" % get_object_url(self.uid))   185    186 class Freebusy(PersonHandler, CommonFreebusy):   187    188     "A free/busy handler."   189    190     def publish(self):   191    192         "Register free/busy information."   193    194         # NOTE: This could be configured to not produce a message.   195    196         self._record_freebusy(from_organiser=True)   197         return self.wrap("PUBLISH", "A free/busy update has been received.", from_organiser=True, link=False)   198    199     def reply(self):   200    201         "Record replies and notify the recipient."   202    203         # NOTE: This could be configured to not produce a message.   204    205         self._record_freebusy(from_organiser=False)   206         return self.wrap("REPLY", "A reply has been received.", from_organiser=False, link=False)   207    208     def request(self):   209    210         """   211         Respond to a request by preparing a reply containing free/busy   212         information for each indicated attendee.   213         """   214    215         # NOTE: This should be subject to policy/preferences.   216    217         return CommonFreebusy.request(self)   218    219 class Journal(PersonHandler):   220    221     "A journal entry handler."   222    223     def add(self):   224    225         # NOTE: Queue a suggested modification to any active entry.   226    227         # The message is now wrapped and passed on to the recipient.   228    229         return "ADD", MIMEText("An addition to a journal entry has been received.")   230    231     def cancel(self):   232    233         # NOTE: Queue a suggested modification to any active entry.   234    235         # The message is now wrapped and passed on to the recipient.   236    237         return "CANCEL", MIMEText("A cancellation has been received.")   238    239     def publish(self):   240    241         # NOTE: Register details of any relevant entry.   242    243         # The message is now wrapped and passed on to the recipient.   244    245         self._record_and_deliver("VJOURNAL", from_organiser=True, queue=False)   246         return self.wrap("PUBLISH", "Details of a journal entry have been received.", from_organiser=True, link=False)   247    248 class Todo(PersonHandler):   249    250     "A to-do item handler."   251    252     def add(self):   253    254         # NOTE: Queue a suggested modification to any active item.   255    256         # The message is now wrapped and passed on to the recipient.   257    258         return "ADD", MIMEText("An addition to an item has been received.")   259    260     def cancel(self):   261    262         # NOTE: Queue a suggested modification to any active item.   263    264         # The message is now wrapped and passed on to the recipient.   265    266         return "CANCEL", MIMEText("A cancellation has been received.")   267    268     def counter(self):   269    270         # NOTE: Queue a suggested modification to any active item.   271    272         # The message is now wrapped and passed on to the recipient.   273    274         return "COUNTER", MIMEText("A counter proposal has been received.")   275    276     def declinecounter(self):   277    278         # NOTE: Queue a suggested modification to any active item.   279    280         # The message is now wrapped and passed on to the recipient.   281    282         return "DECLINECOUNTER", MIMEText("A declining counter proposal has been received.")   283    284     def publish(self):   285    286         "Register details of any relevant item."   287    288         self._record_and_deliver("VTODO", from_organiser=True, queue=False)   289         return self.wrap("PUBLISH", "Details of an item have been received.", from_organiser=True, link=True)   290    291     def refresh(self):   292    293         "Update details of any active item."   294    295         self._record_and_deliver("VTODO", from_organiser=True, queue=False)   296         return self.wrap("REFRESH", "An item update has been received.", from_organiser=True, link=True)   297    298     def reply(self):   299    300         "Record replies and notify the recipient."   301    302         self._record_and_deliver("VTODO", from_organiser=False, queue=False)   303         return self.wrap("REPLY", "A reply has been received.", from_organiser=False, link=True)   304    305     def request(self):   306    307         "Hold requests and notify the recipient."   308    309         self._record_and_deliver("VTODO", from_organiser=True, queue=True)   310    311         # The message is now wrapped and passed on to the recipient.   312    313         return "REQUEST", MIMEText("A request has been queued.")   314    315 # Handler registry.   316    317 handlers = [   318     ("VFREEBUSY",   Freebusy),   319     ("VEVENT",      Event),   320     ("VTODO",       Todo),   321     ("VJOURNAL",    Journal),   322     ]   323    324 # vim: tabstop=4 expandtab shiftwidth=4