imip-agent

Annotated imiptools/handlers/common.py

1363:96314da27b34
2017-10-24 Paul Boddie Updated materialise method usage.
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@1336 22
from imiptools.data import get_address, make_freebusy, to_part
paul@327 23
from imiptools.dates import format_datetime
paul@1230 24
from imiptools.freebusy import FreeBusyPeriod
paul@1230 25
from imiptools.period import Period
paul@108 26
paul@222 27
class CommonFreebusy:
paul@108 28
paul@222 29
    "Common free/busy mix-in."
paul@181 30
paul@943 31
    def _record_freebusy(self, from_organiser=True):
paul@943 32
paul@943 33
        """
paul@943 34
        Record free/busy information for a message originating from an organiser
paul@943 35
        if 'from_organiser' is set to a true value.
paul@943 36
        """
paul@943 37
paul@943 38
        if from_organiser:
paul@943 39
            organiser_item = self.require_organiser(from_organiser)
paul@943 40
            if not organiser_item:
paul@943 41
                return
paul@943 42
paul@943 43
            senders = [organiser_item]
paul@943 44
        else:
paul@943 45
            oa = self.require_organiser_and_attendees(from_organiser)
paul@943 46
            if not oa:
paul@943 47
                return
paul@943 48
paul@943 49
            organiser_item, attendees = oa
paul@943 50
            senders = attendees.items()
paul@943 51
paul@943 52
            if not senders:
paul@943 53
                return
paul@943 54
paul@943 55
        freebusy = [FreeBusyPeriod(p.get_start_point(), p.get_end_point()) for p in self.obj.get_period_values("FREEBUSY")]
paul@943 56
        dtstart = self.obj.get_datetime("DTSTART")
paul@943 57
        dtend = self.obj.get_datetime("DTEND")
paul@943 58
        period = Period(dtstart, dtend, self.get_tzid())
paul@943 59
paul@943 60
        for sender, sender_attr in senders:
paul@1071 61
            stored_freebusy = self.store.get_freebusy_for_other_for_update(self.user, sender)
paul@1062 62
            stored_freebusy.replace_overlapping(period, freebusy)
paul@943 63
            self.store.set_freebusy_for_other(self.user, stored_freebusy, sender)
paul@943 64
paul@222 65
    def request(self):
paul@181 66
paul@181 67
        """
paul@222 68
        Respond to a request by preparing a reply containing free/busy
paul@222 69
        information for each indicated attendee.
paul@181 70
        """
paul@181 71
paul@108 72
        oa = self.require_organiser_and_attendees()
paul@108 73
        if not oa:
paul@228 74
            return
paul@108 75
paul@181 76
        (organiser, organiser_attr), attendees = oa
paul@108 77
paul@181 78
        # Get the details for each attendee.
paul@108 79
paul@222 80
        responses = []
paul@222 81
        rwrite = responses.append
paul@222 82
paul@222 83
        # For replies, the organiser and attendee are preserved.
paul@108 84
paul@181 85
        for attendee, attendee_attr in attendees.items():
paul@222 86
            freebusy = self.store.get_freebusy(attendee)
paul@292 87
paul@292 88
            # Indicate the actual sender of the reply.
paul@292 89
paul@830 90
            self.update_sender(attendee_attr)
paul@292 91
paul@562 92
            dtstart = self.obj.get_datetime("DTSTART")
paul@562 93
            dtend = self.obj.get_datetime("DTEND")
paul@620 94
            period = dtstart and dtend and Period(dtstart, dtend, self.get_tzid()) or None
paul@327 95
paul@562 96
            rwrite(make_freebusy(freebusy, self.uid, organiser, organiser_attr, attendee, attendee_attr, period))
paul@183 97
paul@183 98
        # Return the reply.
paul@183 99
paul@228 100
        self.add_result("REPLY", [get_address(organiser)], to_part("REPLY", responses))
paul@183 101
paul@683 102
class CommonEvent:
paul@580 103
paul@606 104
    "Common outgoing message handling functionality mix-in."
paul@580 105
paul@727 106
    def is_usable(self, method=None):
paul@727 107
paul@727 108
        "Return whether the current object is usable with the given 'method'."
paul@720 109
paul@727 110
        return self.obj and (
paul@727 111
            method in ("CANCEL", "REFRESH") or
paul@727 112
            self.obj.get_datetime("DTSTART") and
paul@727 113
                (self.obj.get_datetime("DTEND") or self.obj.get_duration("DURATION")))
paul@720 114
paul@737 115
    def will_refresh(self):
paul@737 116
paul@737 117
        """
paul@737 118
        Indicate whether a REFRESH message should be used to respond to an ADD
paul@737 119
        message.
paul@737 120
        """
paul@737 121
paul@737 122
        return not self.get_stored_object_version() or self.get_add_method_response() == "refresh"
paul@737 123
paul@737 124
    def make_refresh(self):
paul@737 125
paul@737 126
        "Make a REFRESH message."
paul@737 127
paul@1336 128
        organiser = self.obj.get_uri("ORGANIZER")
paul@1336 129
        attendees = self.obj.get_uri_map("ATTENDEE")
paul@737 130
paul@830 131
        # Indicate the actual sender of the message.
paul@737 132
paul@737 133
        attendee_attr = attendees[self.user]
paul@737 134
        self.update_sender(attendee_attr)
paul@737 135
paul@737 136
        # Make a new object with a minimal property selection.
paul@737 137
paul@737 138
        obj = self.obj.copy()
paul@737 139
        obj.preserve(("ORGANIZER", "DTSTAMP", "UID", "RECURRENCE-ID"))
paul@737 140
        obj["ATTENDEE"] = [(self.user, attendee_attr)]
paul@737 141
paul@737 142
        # Send a REFRESH message in response.
paul@737 143
paul@737 144
        self.add_result("REFRESH", [get_address(organiser)], obj.to_part("REFRESH"))
paul@737 145
paul@1125 146
    def is_newly_separated_occurrence(self):
paul@1123 147
paul@1125 148
        "Return whether the current object is a newly-separated occurrence."
paul@1123 149
paul@1123 150
        # Obtain any stored object.
paul@1123 151
paul@1123 152
        obj = self.get_stored_object_version()
paul@1123 153
paul@1125 154
        # Handle any newly-separated, valid occurrence.
paul@1125 155
paul@1125 156
        return not obj and self.is_recurrence()
paul@1125 157
paul@1125 158
    def make_separate_occurrence(self, for_organiser=False):
paul@1123 159
paul@1125 160
        """
paul@1125 161
        Set the current object as a separate occurrence and redefine free/busy
paul@1125 162
        records in terms of this new occurrence for other participants.
paul@1125 163
        """
paul@1123 164
paul@1125 165
        parent = self.get_parent_object()
paul@1125 166
        if not parent:
paul@1125 167
            return False
paul@1125 168
paul@1125 169
        # Transfer attendance information from the parent.
paul@1123 170
paul@1336 171
        parent_attendees = parent.get_uri_map("ATTENDEE")
paul@1336 172
        attendee_map = self.obj.get_uri_map("ATTENDEE")
paul@1125 173
paul@1125 174
        for attendee, attendee_attr in parent_attendees.items():
paul@1125 175
            if not attendee_map.has_key(attendee):
paul@1125 176
                attendee_map[attendee] = attendee_attr
paul@1123 177
paul@1125 178
        self.obj["ATTENDEE"] = attendee_map.items()
paul@1125 179
        self.obj.remove_all(["RDATE", "RRULE"])
paul@1125 180
paul@1125 181
        # Create or revive the occurrence.
paul@1123 182
paul@1125 183
        self.store.remove_cancellation(self.user, self.uid, self.recurrenceid)
paul@1125 184
        self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node())
paul@1125 185
paul@1125 186
        # Update free/busy details for the current object for all attendees.
paul@1125 187
paul@1125 188
        self.update_freebusy_from_attendees(attendee_map.keys())
paul@1123 189
paul@1123 190
        return True
paul@1123 191
paul@108 192
# vim: tabstop=4 expandtab shiftwidth=4