imip-agent

Annotated imipweb/handler.py

468:801ac79598b1
2015-03-31 Paul Boddie Introduced Client as a base class of the handlers, so that self.user can be used to refer to the current user (typically the recipient) in the different handlers, and so that various convenience methods can be more widely used. Introduced testing for free/busy sharing before publishing details on the Web.
paul@444 1
#!/usr/bin/env python
paul@444 2
paul@444 3
"""
paul@444 4
Interaction with the mail system for the manager interface.
paul@444 5
paul@444 6
Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>
paul@444 7
paul@444 8
This program is free software; you can redistribute it and/or modify it under
paul@444 9
the terms of the GNU General Public License as published by the Free Software
paul@444 10
Foundation; either version 3 of the License, or (at your option) any later
paul@444 11
version.
paul@444 12
paul@444 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@444 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@444 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@444 16
details.
paul@444 17
paul@444 18
You should have received a copy of the GNU General Public License along with
paul@444 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@444 20
"""
paul@444 21
paul@444 22
from imiptools.client import Client, update_attendees
paul@444 23
from imiptools.data import get_address, get_uri, get_window_end, make_freebusy, \
paul@444 24
                           to_part, uri_item, uri_items, uri_values
paul@444 25
from imiptools.dates import get_timestamp
paul@444 26
from imiptools.handlers import Handler
paul@444 27
from imiptools.period import update_freebusy
paul@444 28
paul@468 29
class ManagerHandler(Handler):
paul@444 30
paul@444 31
    """
paul@444 32
    A content handler for use by the manager, as opposed to operating within the
paul@444 33
    mail processing pipeline.
paul@444 34
    """
paul@444 35
paul@444 36
    def __init__(self, obj, user, messenger):
paul@444 37
        Handler.__init__(self, messenger=messenger)
paul@468 38
        Client.__init__(self, user) # this redefines the Handler initialisation
paul@444 39
paul@444 40
        self.set_object(obj)
paul@444 41
paul@444 42
    # Communication methods.
paul@444 43
paul@444 44
    def send_message(self, method, sender, for_organiser):
paul@444 45
paul@444 46
        """
paul@444 47
        Create a full calendar object employing the given 'method', and send it
paul@444 48
        to the appropriate recipients, also sending a copy to the 'sender'. The
paul@444 49
        'for_organiser' value indicates whether the organiser is sending this
paul@444 50
        message.
paul@444 51
        """
paul@444 52
paul@444 53
        parts = [self.obj.to_part(method)]
paul@444 54
paul@444 55
        # As organiser, send an invitation to attendees, excluding oneself if
paul@444 56
        # also attending. The updated event will be saved by the outgoing
paul@444 57
        # handler.
paul@444 58
paul@444 59
        organiser = get_uri(self.obj.get_value("ORGANIZER"))
paul@444 60
        attendees = uri_values(self.obj.get_values("ATTENDEE"))
paul@444 61
paul@444 62
        if for_organiser:
paul@444 63
            recipients = [get_address(attendee) for attendee in attendees if attendee != self.user]
paul@444 64
        else:
paul@444 65
            recipients = [get_address(organiser)]
paul@444 66
paul@444 67
        # Bundle free/busy information if appropriate.
paul@444 68
paul@444 69
        if self.is_sharing() and self.is_bundling():
paul@444 70
paul@444 71
            # Invent a unique identifier.
paul@444 72
paul@444 73
            utcnow = get_timestamp()
paul@444 74
            uid = "imip-agent-%s-%s" % (utcnow, get_address(self.user))
paul@444 75
paul@444 76
            freebusy = self.store.get_freebusy(self.user)
paul@444 77
paul@444 78
            # Replace the non-updated free/busy details for this event with
paul@444 79
            # newer details (since the outgoing handler updates this user's
paul@444 80
            # free/busy details).
paul@444 81
paul@444 82
            update_freebusy(freebusy,
paul@444 83
                self.obj.get_periods_for_freebusy(self.get_tzid(), self.get_window_end()),
paul@444 84
                self.obj.get_value("TRANSP") or "OPAQUE",
paul@444 85
                self.uid, self.recurrenceid,
paul@444 86
                self.obj.get_value("SUMMARY"),
paul@444 87
                organiser)
paul@444 88
paul@444 89
            user_attr = self.messenger and self.messenger.sender != get_address(self.user) and \
paul@444 90
                {"SENT-BY" : get_uri(self.messenger.sender)} or {}
paul@444 91
paul@444 92
            parts.append(to_part("PUBLISH", [
paul@444 93
                make_freebusy(freebusy, uid, self.user, user_attr)
paul@444 94
                ]))
paul@444 95
paul@445 96
        # Explicitly specify the outgoing BCC recipient since we are sending as
paul@445 97
        # the generic calendar user.
paul@445 98
paul@444 99
        message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender)
paul@444 100
        self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender)
paul@444 101
paul@444 102
    # Action methods.
paul@444 103
paul@444 104
    def process_received_request(self, update=False):
paul@444 105
paul@444 106
        """
paul@444 107
        Process the current request for the given 'user'. Return whether any
paul@444 108
        action was taken.
paul@444 109
paul@444 110
        If 'update' is given, the sequence number will be incremented in order
paul@444 111
        to override any previous response.
paul@444 112
        """
paul@444 113
paul@444 114
        # Reply only on behalf of this user.
paul@444 115
paul@444 116
        for attendee, attendee_attr in uri_items(self.obj.get_items("ATTENDEE")):
paul@444 117
paul@444 118
            if attendee == self.user:
paul@444 119
                if attendee_attr.has_key("RSVP"):
paul@444 120
                    del attendee_attr["RSVP"]
paul@444 121
                if self.messenger and self.messenger.sender != get_address(attendee):
paul@444 122
                    attendee_attr["SENT-BY"] = get_uri(self.messenger.sender)
paul@444 123
                self.obj["ATTENDEE"] = [(attendee, attendee_attr)]
paul@444 124
paul@444 125
                self.update_dtstamp()
paul@444 126
                self.set_sequence(update)
paul@444 127
paul@444 128
                self.send_message("REPLY", get_address(attendee), for_organiser=False)
paul@444 129
paul@444 130
                return True
paul@444 131
paul@444 132
        return False
paul@444 133
paul@444 134
    def process_created_request(self, method, update=False, removed=None, added=None):
paul@444 135
paul@444 136
        """
paul@444 137
        Process the current request for the given 'user', sending a created
paul@444 138
        request of the given 'method' to attendees. Return whether any action
paul@444 139
        was taken.
paul@444 140
paul@444 141
        If 'update' is given, the sequence number will be incremented in order
paul@444 142
        to override any previous message.
paul@444 143
paul@444 144
        If 'removed' is specified, a list of participants to be removed is
paul@444 145
        provided.
paul@444 146
paul@444 147
        If 'added' is specified, a list of participants to be added is provided.
paul@444 148
        """
paul@444 149
paul@444 150
        organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))
paul@444 151
paul@444 152
        if self.messenger and self.messenger.sender != get_address(organiser):
paul@444 153
            organiser_attr["SENT-BY"] = get_uri(self.messenger.sender)
paul@444 154
paul@444 155
        # Update the attendees in the event.
paul@444 156
paul@444 157
        to_cancel = update_attendees(self.obj, added, removed)
paul@444 158
paul@444 159
        self.update_dtstamp()
paul@444 160
        self.set_sequence(update)
paul@444 161
paul@444 162
        self.send_message(method, get_address(organiser), for_organiser=True)
paul@444 163
paul@444 164
        # When cancelling, replace the attendees with those for whom the event
paul@444 165
        # is now cancelled.
paul@444 166
paul@444 167
        if to_cancel:
paul@444 168
            remaining = self.obj["ATTENDEE"]
paul@444 169
            self.obj["ATTENDEE"] = to_cancel
paul@444 170
            self.send_message("CANCEL", get_address(organiser), for_organiser=True)
paul@444 171
paul@444 172
            # Just in case more work is done with this event, the attendees are
paul@444 173
            # now restored.
paul@444 174
paul@444 175
            self.obj["ATTENDEE"] = remaining
paul@444 176
paul@444 177
        return True
paul@444 178
paul@444 179
# vim: tabstop=4 expandtab shiftwidth=4