imip-agent

Annotated imiptools/handlers/resource.py

421:f658ca7505b2
2015-03-22 Paul Boddie Moved methods around.
paul@48 1
#!/usr/bin/env python
paul@48 2
paul@48 3
"""
paul@48 4
Handlers for a resource.
paul@146 5
paul@146 6
Copyright (C) 2014, 2015 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@48 20
"""
paul@48 21
paul@361 22
from imiptools.data import get_address, get_uri, get_window_end, to_part
paul@291 23
from imiptools.dates import get_default_timezone
paul@418 24
from imiptools.handlers import Handler
paul@108 25
from imiptools.handlers.common import CommonFreebusy
paul@380 26
from imiptools.period import remove_affected_period
paul@48 27
paul@131 28
class ResourceHandler(Handler):
paul@131 29
paul@131 30
    "Handling mechanisms specific to resources."
paul@131 31
paul@131 32
    def _record_and_respond(self, handle_for_attendee):
paul@131 33
paul@420 34
        """
paul@420 35
        Record details from the incoming message, using the given
paul@420 36
        'handle_for_attendee' callable to process any valid message
paul@420 37
        appropriately.
paul@420 38
        """
paul@420 39
paul@131 40
        oa = self.require_organiser_and_attendees()
paul@131 41
        if not oa:
paul@131 42
            return None
paul@131 43
paul@131 44
        organiser_item, attendees = oa
paul@131 45
paul@131 46
        # Process each attendee separately.
paul@131 47
paul@131 48
        calendar = []
paul@131 49
paul@131 50
        for attendee, attendee_attr in attendees.items():
paul@131 51
paul@131 52
            # Check for event using UID.
paul@131 53
paul@213 54
            if not self.have_new_object(attendee):
paul@131 55
                continue
paul@131 56
paul@131 57
            # Collect response objects produced when handling the request.
paul@131 58
paul@131 59
            response = handle_for_attendee(attendee, attendee_attr)
paul@131 60
            if response:
paul@131 61
                calendar.append(response)
paul@131 62
paul@131 63
        return calendar
paul@131 64
paul@131 65
    def _schedule_for_attendee(self, attendee, attendee_attr):
paul@131 66
paul@420 67
        """
paul@420 68
        Schedule for the given 'attendee' and accompanying 'attendee_attr' the
paul@420 69
        current object.
paul@420 70
        """
paul@420 71
paul@291 72
        # Interpretation of periods can depend on the time zone.
paul@291 73
paul@361 74
        tzid = self.get_tzid(attendee)
paul@291 75
paul@131 76
        # If newer than any old version, discard old details from the
paul@131 77
        # free/busy record and check for suitability.
paul@131 78
paul@361 79
        periods = self.obj.get_periods_for_freebusy(tzid, get_window_end(tzid))
paul@173 80
        freebusy = self.store.get_freebusy(attendee)
paul@131 81
        scheduled = self.can_schedule(freebusy, periods)
paul@131 82
paul@131 83
        attendee_attr["PARTSTAT"] = scheduled and "ACCEPTED" or "DECLINED"
paul@267 84
        if attendee_attr.has_key("RSVP"):
paul@267 85
            del attendee_attr["RSVP"]
paul@131 86
        if self.messenger and self.messenger.sender != get_address(attendee):
paul@131 87
            attendee_attr["SENT-BY"] = get_uri(self.messenger.sender)
paul@131 88
paul@131 89
        # Make a version of the request with just this attendee.
paul@131 90
paul@213 91
        self.obj["ATTENDEE"] = [(attendee, attendee_attr)]
paul@131 92
paul@158 93
        # Update the DTSTAMP.
paul@158 94
paul@158 95
        self.update_dtstamp()
paul@158 96
paul@361 97
        # Set the complete event or an additional occurrence.
paul@334 98
paul@213 99
        event = self.obj.to_node()
paul@343 100
        self.store.set_event(attendee, self.uid, self.recurrenceid, event)
paul@131 101
paul@381 102
        # Remove additional recurrences if handling a complete event.
paul@381 103
paul@381 104
        if not self.recurrenceid:
paul@381 105
            self.store.remove_recurrences(attendee, self.uid)
paul@381 106
paul@131 107
        # Only update free/busy details if the event is scheduled.
paul@131 108
paul@131 109
        if scheduled:
paul@361 110
            self.update_freebusy(freebusy, periods)
paul@131 111
        else:
paul@361 112
            self.remove_from_freebusy(freebusy)
paul@361 113
paul@381 114
        # Remove either original recurrence or additional recurrence
paul@381 115
        # details depending on whether an additional recurrence or a
paul@381 116
        # complete event are being handled, respectively.
paul@361 117
paul@381 118
        self.remove_freebusy_for_recurrences(freebusy)
paul@361 119
        self.store.set_freebusy(attendee, freebusy)
paul@131 120
paul@131 121
        if self.publisher:
paul@131 122
            self.publisher.set_freebusy(attendee, freebusy)
paul@131 123
paul@131 124
        return event
paul@131 125
paul@131 126
    def _cancel_for_attendee(self, attendee, attendee_attr):
paul@131 127
paul@420 128
        """
paul@420 129
        Cancel for the given 'attendee' and accompanying 'attendee_attr' their
paul@420 130
        attendance of the event described by the current object.
paul@420 131
        """
paul@420 132
paul@343 133
        self.store.cancel_event(attendee, self.uid, self.recurrenceid)
paul@264 134
paul@173 135
        freebusy = self.store.get_freebusy(attendee)
paul@361 136
        self.remove_from_freebusy(freebusy)
paul@361 137
paul@361 138
        self.store.set_freebusy(attendee, freebusy)
paul@131 139
paul@131 140
        if self.publisher:
paul@131 141
            self.publisher.set_freebusy(attendee, freebusy)
paul@131 142
paul@131 143
        return None
paul@131 144
paul@131 145
class Event(ResourceHandler):
paul@48 146
paul@48 147
    "An event handler."
paul@48 148
paul@48 149
    def add(self):
paul@48 150
        pass
paul@48 151
paul@48 152
    def cancel(self):
paul@131 153
paul@131 154
        "Cancel attendance for attendees."
paul@131 155
paul@131 156
        self._record_and_respond(self._cancel_for_attendee)
paul@48 157
paul@48 158
    def counter(self):
paul@48 159
paul@48 160
        "Since this handler does not send requests, it will not handle replies."
paul@48 161
paul@48 162
        pass
paul@48 163
paul@48 164
    def declinecounter(self):
paul@48 165
paul@48 166
        """
paul@48 167
        Since this handler does not send counter proposals, it will not handle
paul@48 168
        replies to such proposals.
paul@48 169
        """
paul@48 170
paul@48 171
        pass
paul@48 172
paul@48 173
    def publish(self):
paul@48 174
        pass
paul@48 175
paul@48 176
    def refresh(self):
paul@48 177
        pass
paul@48 178
paul@48 179
    def reply(self):
paul@48 180
paul@48 181
        "Since this handler does not send requests, it will not handle replies."
paul@48 182
paul@48 183
        pass
paul@48 184
paul@48 185
    def request(self):
paul@48 186
paul@48 187
        """
paul@48 188
        Respond to a request by preparing a reply containing accept/decline
paul@48 189
        information for each indicated attendee.
paul@48 190
paul@48 191
        No support for countering requests is implemented.
paul@48 192
        """
paul@48 193
paul@131 194
        response = self._record_and_respond(self._schedule_for_attendee)
paul@131 195
        if response:
paul@228 196
            self.add_result("REPLY", map(get_address, self.obj.get_values("ORGANIZER")), to_part("REPLY", response))
paul@48 197
paul@251 198
class Freebusy(Handler, CommonFreebusy):
paul@48 199
paul@48 200
    "A free/busy handler."
paul@48 201
paul@48 202
    def publish(self):
paul@48 203
        pass
paul@48 204
paul@48 205
    def reply(self):
paul@48 206
paul@48 207
        "Since this handler does not send requests, it will not handle replies."
paul@48 208
paul@48 209
        pass
paul@48 210
paul@108 211
    # request provided by CommonFreeBusy.request
paul@48 212
paul@48 213
# Handler registry.
paul@48 214
paul@48 215
handlers = [
paul@48 216
    ("VFREEBUSY",   Freebusy),
paul@48 217
    ("VEVENT",      Event),
paul@48 218
    ]
paul@48 219
paul@48 220
# vim: tabstop=4 expandtab shiftwidth=4