imip-agent

Annotated imiptools/handlers/resource.py

427:d6466d09eb7e
2015-03-24 Paul Boddie Introduced some support for editing recurrence periods in events, employing common methods to handle datetime controls. Updated the stylesheet to use checkboxes instead of radio buttons to configure period end and time details, so that a collection of controls may be used for a collection of recurrence periods with the controls having the same field name.
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