imip-agent

imipweb/client.py

601:f6cad1ec5673
2015-07-25 Paul Boddie Introduced a separate client-related abstraction involving a specific object for each instance, of which a handler is a specialisation, but at a level more appropriate for manager and test purposes than a handler (which is really intended to support object-specific handling methods as part of handling a message in transit).
     1 #!/usr/bin/env python     2      3 """     4 Interaction with the mail system for the manager interface.     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.client import ClientForObject    23 from imiptools.data import get_address, get_uri, make_freebusy, \    24                            to_part, uri_item, uri_values    25 from imiptools.dates import format_datetime, get_timestamp    26     27 class ManagerClient(ClientForObject):    28     29     """    30     A content client for use by the manager, as opposed to operating within the    31     mail processing pipeline.    32     """    33     34     # Communication methods.    35     36     def send_message(self, method, sender, from_organiser, parts=None):    37     38         """    39         Create a full calendar object employing the given 'method', and send it    40         to the appropriate recipients, also sending a copy to the 'sender'. The    41         'from_organiser' value indicates whether the organiser is sending this    42         message.    43         """    44     45         parts = parts or [self.obj.to_part(method)]    46     47         # As organiser, send an invitation to attendees, excluding oneself if    48         # also attending. The updated event will be saved by the outgoing    49         # handler.    50     51         organiser = get_uri(self.obj.get_value("ORGANIZER"))    52         attendees = uri_values(self.obj.get_values("ATTENDEE"))    53     54         if from_organiser:    55             recipients = [get_address(attendee) for attendee in attendees if attendee != self.user]    56         else:    57             recipients = [get_address(organiser)]    58     59         # Bundle free/busy information if appropriate.    60     61         if self.is_sharing() and self.is_bundling():    62     63             # Invent a unique identifier.    64     65             utcnow = get_timestamp()    66             uid = "imip-agent-%s-%s" % (utcnow, get_address(self.user))    67     68             freebusy = self.store.get_freebusy(self.user)    69     70             # Since the outgoing handler updates this user's free/busy details,    71             # the stored details will probably not have the updated details at    72             # this point, so we update our copy for serialisation as the bundled    73             # free/busy object.    74     75             self.update_freebusy(freebusy,    76                 self.obj.get_periods(self.get_tzid(), self.get_window_end()))    77     78             user_attr = {}    79             self.update_sender(user_attr)    80     81             parts.append(to_part("PUBLISH", [    82                 make_freebusy(freebusy, uid, self.user, user_attr)    83                 ]))    84     85         # Explicitly specify the outgoing BCC recipient since we are sending as    86         # the generic calendar user.    87     88         message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender)    89         self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender)    90     91     # Action methods.    92     93     def process_received_request(self):    94     95         """    96         Process the current request for the current user. Return whether any    97         action was taken.    98         """    99    100         # Reply only on behalf of this user.   101    102         attendee_attr = self.update_participation(self.obj)   103    104         if not attendee_attr:   105             return False   106    107         self.obj["ATTENDEE"] = [(self.user, attendee_attr)]   108         self.update_dtstamp()   109         self.set_sequence(False)   110         self.send_message("REPLY", get_address(self.user), from_organiser=False)   111         return True   112    113     def process_created_request(self, method, to_cancel=None, to_unschedule=None):   114    115         """   116         Process the current request, sending a created request of the given   117         'method' to attendees. Return whether any action was taken.   118    119         If 'to_cancel' is specified, a list of participants to be sent cancel   120         messages is provided.   121         """   122    123         # Here, the organiser should be the current user.   124    125         organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))   126    127         self.update_sender(organiser_attr)   128         self.update_dtstamp()   129         self.set_sequence(True)   130    131         parts = [self.obj.to_part(method)]   132    133         # Add message parts with cancelled occurrence information.   134         # NOTE: This could probably be merged with the updated event message.   135    136         if to_unschedule:   137             obj = self.obj.copy()   138             obj.remove_all(["RRULE", "RDATE", "DTSTART", "DTEND", "DURATION"])   139    140             for p in to_unschedule:   141                 if not p.origin:   142                     continue   143                 obj["RECURRENCE-ID"] = [(format_datetime(p.get_start()), {})]   144                 parts.append(obj.to_part("CANCEL"))   145    146         # Send the updated event, along with a cancellation for each of the   147         # unscheduled occurrences.   148    149         self.send_message("CANCEL", get_address(organiser), from_organiser=True, parts=parts)   150    151         # When cancelling, replace the attendees with those for whom the event   152         # is now cancelled.   153    154         if to_cancel:   155             obj = self.obj.copy()   156             obj["ATTENDEE"] = to_cancel   157    158             # Send a cancellation to all uninvited attendees.   159    160             self.send_message("CANCEL", get_address(organiser), from_organiser=True)   161    162         return True   163    164 # vim: tabstop=4 expandtab shiftwidth=4