imip-agent

Annotated imipweb/handler.py

523:b9c05d30449f
2015-05-15 Paul Boddie Support the cancellation of previously unseparated recurrences.
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@473 22
from imiptools.client import Client
paul@473 23
from imiptools.data import get_address, get_uri, 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@501 44
    def send_message(self, method, sender, from_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@501 49
        'from_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@501 62
        if from_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@512 104
    def process_received_request(self):
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
paul@444 111
        # Reply only on behalf of this user.
paul@444 112
paul@444 113
        for attendee, attendee_attr in uri_items(self.obj.get_items("ATTENDEE")):
paul@444 114
paul@444 115
            if attendee == self.user:
paul@444 116
                if attendee_attr.has_key("RSVP"):
paul@444 117
                    del attendee_attr["RSVP"]
paul@444 118
                if self.messenger and self.messenger.sender != get_address(attendee):
paul@444 119
                    attendee_attr["SENT-BY"] = get_uri(self.messenger.sender)
paul@444 120
                self.obj["ATTENDEE"] = [(attendee, attendee_attr)]
paul@444 121
paul@444 122
                self.update_dtstamp()
paul@512 123
                self.set_sequence(False)
paul@444 124
paul@501 125
                self.send_message("REPLY", get_address(attendee), from_organiser=False)
paul@444 126
paul@444 127
                return True
paul@444 128
paul@444 129
        return False
paul@444 130
paul@512 131
    def process_created_request(self, method, to_cancel=None):
paul@444 132
paul@444 133
        """
paul@473 134
        Process the current request, sending a created request of the given
paul@473 135
        'method' to attendees. Return whether any action was taken.
paul@444 136
paul@473 137
        If 'to_cancel' is specified, a list of participants to be sent cancel
paul@473 138
        messages is provided.
paul@444 139
        """
paul@444 140
paul@444 141
        organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))
paul@444 142
paul@444 143
        if self.messenger and self.messenger.sender != get_address(organiser):
paul@444 144
            organiser_attr["SENT-BY"] = get_uri(self.messenger.sender)
paul@444 145
paul@444 146
        self.update_dtstamp()
paul@512 147
        self.set_sequence(True)
paul@444 148
paul@501 149
        self.send_message(method, get_address(organiser), from_organiser=True)
paul@444 150
paul@444 151
        # When cancelling, replace the attendees with those for whom the event
paul@444 152
        # is now cancelled.
paul@444 153
paul@444 154
        if to_cancel:
paul@444 155
            remaining = self.obj["ATTENDEE"]
paul@444 156
            self.obj["ATTENDEE"] = to_cancel
paul@501 157
            self.send_message("CANCEL", get_address(organiser), from_organiser=True)
paul@444 158
paul@444 159
            # Just in case more work is done with this event, the attendees are
paul@444 160
            # now restored.
paul@444 161
paul@444 162
            self.obj["ATTENDEE"] = remaining
paul@444 163
paul@444 164
        return True
paul@444 165
paul@444 166
# vim: tabstop=4 expandtab shiftwidth=4