imip-agent

Annotated imiptools/handlers/common.py

1122:2003934ef901
2016-04-19 Paul Boddie Support replies from attendees that refer to specific recurrences before the organiser does so, thus allowing attendees to selectively accept and decline recurrences. Allowed the test handler to refer to recurrences that have not been explicitly separated from their parent objects. Added a docstring for the Object initialiser as a reminder of how to use it. freebusy-collections
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@1062 6
Copyright (C) 2014, 2015, 2016 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@1062 25
from imiptools.period import FreeBusyPeriod, 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@737 128
        organiser = get_uri(self.obj.get_value("ORGANIZER"))
paul@737 129
        attendees = uri_dict(self.obj.get_value_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@1122 146
    def ensure_occurrence(self):
paul@1122 147
paul@1122 148
        """
paul@1122 149
        Ensure that the object originating from an attendee corresponds to an
paul@1122 150
        existing occurrence of an event, creating or reviving a specific
paul@1122 151
        recurrence if necessary.
paul@1122 152
paul@1122 153
        Return whether a valid occurrence was found.
paul@1122 154
        """
paul@1122 155
paul@1122 156
        # Obtain any stored object.
paul@1122 157
paul@1122 158
        obj = self.get_stored_object_version()
paul@1122 159
paul@1122 160
        # Handle any newly-defined occurrence.
paul@1122 161
paul@1122 162
        if not obj:
paul@1122 163
paul@1122 164
            # Check for a valid occurrence.
paul@1122 165
paul@1122 166
            if not self.is_recurrence():
paul@1122 167
                return False
paul@1122 168
paul@1122 169
            # Set the complete event if not an additional occurrence. For any newly-
paul@1122 170
            # indicated occurrence, use the received event details.
paul@1122 171
paul@1122 172
            self.store.remove_cancellation(self.user, self.uid, self.recurrenceid)
paul@1122 173
            self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node())
paul@1122 174
paul@1122 175
        return True
paul@1122 176
paul@108 177
# vim: tabstop=4 expandtab shiftwidth=4