imip-agent

Annotated imiptools/handlers/common.py

1309:644b7e259059
2017-10-14 Paul Boddie Support BCC sending suppression so that routines requesting it can still be used with senders that will not support it, usually because there are no outgoing routing destinations for those senders.
paul@108 1
#!/usr/bin/env python
paul@108 2
paul@108 3
"""
paul@108 4
Common handler functionality for different entities.
paul@146 5
paul@1230 6
Copyright (C) 2014, 2015, 2016, 2017 Paul Boddie <paul@boddie.org.uk>
paul@146 7
paul@146 8
This program is free software; you can redistribute it and/or modify it under
paul@146 9
the terms of the GNU General Public License as published by the Free Software
paul@146 10
Foundation; either version 3 of the License, or (at your option) any later
paul@146 11
version.
paul@146 12
paul@146 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@146 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@146 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@146 16
details.
paul@146 17
paul@146 18
You should have received a copy of the GNU General Public License along with
paul@146 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@108 20
"""
paul@108 21
paul@580 22
from imiptools.data import get_address, get_uri, make_freebusy, to_part, \
paul@580 23
                           uri_dict
paul@327 24
from imiptools.dates import format_datetime
paul@1230 25
from imiptools.freebusy import FreeBusyPeriod
paul@1230 26
from imiptools.period import Period
paul@108 27
paul@222 28
class CommonFreebusy:
paul@108 29
paul@222 30
    "Common free/busy mix-in."
paul@181 31
paul@943 32
    def _record_freebusy(self, from_organiser=True):
paul@943 33
paul@943 34
        """
paul@943 35
        Record free/busy information for a message originating from an organiser
paul@943 36
        if 'from_organiser' is set to a true value.
paul@943 37
        """
paul@943 38
paul@943 39
        if from_organiser:
paul@943 40
            organiser_item = self.require_organiser(from_organiser)
paul@943 41
            if not organiser_item:
paul@943 42
                return
paul@943 43
paul@943 44
            senders = [organiser_item]
paul@943 45
        else:
paul@943 46
            oa = self.require_organiser_and_attendees(from_organiser)
paul@943 47
            if not oa:
paul@943 48
                return
paul@943 49
paul@943 50
            organiser_item, attendees = oa
paul@943 51
            senders = attendees.items()
paul@943 52
paul@943 53
            if not senders:
paul@943 54
                return
paul@943 55
paul@943 56
        freebusy = [FreeBusyPeriod(p.get_start_point(), p.get_end_point()) for p in self.obj.get_period_values("FREEBUSY")]
paul@943 57
        dtstart = self.obj.get_datetime("DTSTART")
paul@943 58
        dtend = self.obj.get_datetime("DTEND")
paul@943 59
        period = Period(dtstart, dtend, self.get_tzid())
paul@943 60
paul@943 61
        for sender, sender_attr in senders:
paul@1071 62
            stored_freebusy = self.store.get_freebusy_for_other_for_update(self.user, sender)
paul@1062 63
            stored_freebusy.replace_overlapping(period, freebusy)
paul@943 64
            self.store.set_freebusy_for_other(self.user, stored_freebusy, sender)
paul@943 65
paul@222 66
    def request(self):
paul@181 67
paul@181 68
        """
paul@222 69
        Respond to a request by preparing a reply containing free/busy
paul@222 70
        information for each indicated attendee.
paul@181 71
        """
paul@181 72
paul@108 73
        oa = self.require_organiser_and_attendees()
paul@108 74
        if not oa:
paul@228 75
            return
paul@108 76
paul@181 77
        (organiser, organiser_attr), attendees = oa
paul@108 78
paul@181 79
        # Get the details for each attendee.
paul@108 80
paul@222 81
        responses = []
paul@222 82
        rwrite = responses.append
paul@222 83
paul@222 84
        # For replies, the organiser and attendee are preserved.
paul@108 85
paul@181 86
        for attendee, attendee_attr in attendees.items():
paul@222 87
            freebusy = self.store.get_freebusy(attendee)
paul@292 88
paul@292 89
            # Indicate the actual sender of the reply.
paul@292 90
paul@830 91
            self.update_sender(attendee_attr)
paul@292 92
paul@562 93
            dtstart = self.obj.get_datetime("DTSTART")
paul@562 94
            dtend = self.obj.get_datetime("DTEND")
paul@620 95
            period = dtstart and dtend and Period(dtstart, dtend, self.get_tzid()) or None
paul@327 96
paul@562 97
            rwrite(make_freebusy(freebusy, self.uid, organiser, organiser_attr, attendee, attendee_attr, period))
paul@183 98
paul@183 99
        # Return the reply.
paul@183 100
paul@228 101
        self.add_result("REPLY", [get_address(organiser)], to_part("REPLY", responses))
paul@183 102
paul@683 103
class CommonEvent:
paul@580 104
paul@606 105
    "Common outgoing message handling functionality mix-in."
paul@580 106
paul@727 107
    def is_usable(self, method=None):
paul@727 108
paul@727 109
        "Return whether the current object is usable with the given 'method'."
paul@720 110
paul@727 111
        return self.obj and (
paul@727 112
            method in ("CANCEL", "REFRESH") or
paul@727 113
            self.obj.get_datetime("DTSTART") and
paul@727 114
                (self.obj.get_datetime("DTEND") or self.obj.get_duration("DURATION")))
paul@720 115
paul@737 116
    def will_refresh(self):
paul@737 117
paul@737 118
        """
paul@737 119
        Indicate whether a REFRESH message should be used to respond to an ADD
paul@737 120
        message.
paul@737 121
        """
paul@737 122
paul@737 123
        return not self.get_stored_object_version() or self.get_add_method_response() == "refresh"
paul@737 124
paul@737 125
    def make_refresh(self):
paul@737 126
paul@737 127
        "Make a REFRESH message."
paul@737 128
paul@737 129
        organiser = get_uri(self.obj.get_value("ORGANIZER"))
paul@737 130
        attendees = uri_dict(self.obj.get_value_map("ATTENDEE"))
paul@737 131
paul@830 132
        # Indicate the actual sender of the message.
paul@737 133
paul@737 134
        attendee_attr = attendees[self.user]
paul@737 135
        self.update_sender(attendee_attr)
paul@737 136
paul@737 137
        # Make a new object with a minimal property selection.
paul@737 138
paul@737 139
        obj = self.obj.copy()
paul@737 140
        obj.preserve(("ORGANIZER", "DTSTAMP", "UID", "RECURRENCE-ID"))
paul@737 141
        obj["ATTENDEE"] = [(self.user, attendee_attr)]
paul@737 142
paul@737 143
        # Send a REFRESH message in response.
paul@737 144
paul@737 145
        self.add_result("REFRESH", [get_address(organiser)], obj.to_part("REFRESH"))
paul@737 146
paul@1125 147
    def is_newly_separated_occurrence(self):
paul@1123 148
paul@1125 149
        "Return whether the current object is a newly-separated occurrence."
paul@1123 150
paul@1123 151
        # Obtain any stored object.
paul@1123 152
paul@1123 153
        obj = self.get_stored_object_version()
paul@1123 154
paul@1125 155
        # Handle any newly-separated, valid occurrence.
paul@1125 156
paul@1125 157
        return not obj and self.is_recurrence()
paul@1125 158
paul@1125 159
    def make_separate_occurrence(self, for_organiser=False):
paul@1123 160
paul@1125 161
        """
paul@1125 162
        Set the current object as a separate occurrence and redefine free/busy
paul@1125 163
        records in terms of this new occurrence for other participants.
paul@1125 164
        """
paul@1123 165
paul@1125 166
        parent = self.get_parent_object()
paul@1125 167
        if not parent:
paul@1125 168
            return False
paul@1125 169
paul@1125 170
        # Transfer attendance information from the parent.
paul@1123 171
paul@1125 172
        parent_attendees = uri_dict(parent.get_value_map("ATTENDEE"))
paul@1125 173
        attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE"))
paul@1125 174
paul@1125 175
        for attendee, attendee_attr in parent_attendees.items():
paul@1125 176
            if not attendee_map.has_key(attendee):
paul@1125 177
                attendee_map[attendee] = attendee_attr
paul@1123 178
paul@1125 179
        self.obj["ATTENDEE"] = attendee_map.items()
paul@1125 180
        self.obj.remove_all(["RDATE", "RRULE"])
paul@1125 181
paul@1125 182
        # Create or revive the occurrence.
paul@1123 183
paul@1125 184
        self.store.remove_cancellation(self.user, self.uid, self.recurrenceid)
paul@1125 185
        self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node())
paul@1125 186
paul@1125 187
        # Update free/busy details for the current object for all attendees.
paul@1125 188
paul@1125 189
        self.update_freebusy_from_attendees(attendee_map.keys())
paul@1123 190
paul@1123 191
        return True
paul@1123 192
paul@108 193
# vim: tabstop=4 expandtab shiftwidth=4