imip-agent

Annotated imiptools/handlers/resource.py

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