imip-agent

Annotated imiptools/handlers/person.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@55 1
#!/usr/bin/env python
paul@55 2
paul@55 3
"""
paul@55 4
Handlers for a person for whom scheduling is performed.
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@55 20
"""
paul@55 21
paul@271 22
from imiptools.data import get_uri
paul@327 23
from imiptools.dates import format_datetime
paul@418 24
from imiptools.handlers import Handler
paul@216 25
from imiptools.handlers.common import CommonFreebusy
paul@327 26
from imiptools.period import replace_overlapping
paul@179 27
from imiptools.profile import Preferences
paul@55 28
paul@67 29
class PersonHandler(Handler):
paul@55 30
paul@67 31
    "Handling mechanisms specific to people."
paul@55 32
paul@271 33
    def _record(self, from_organiser=True, queue=False, cancel=False):
paul@55 34
paul@420 35
        """
paul@420 36
        Record details from the current object given a message originating
paul@420 37
        from an organiser if 'from_organiser' is set to a true value, queuing a
paul@420 38
        request if 'queue' is set to a true value, or cancelling an event if
paul@420 39
        'cancel' is set to a true value.
paul@420 40
        """
paul@420 41
paul@420 42
        # Obtain valid organiser and attendee details.
paul@420 43
paul@100 44
        oa = self.require_organiser_and_attendees(from_organiser)
paul@55 45
        if not oa:
paul@61 46
            return False
paul@55 47
paul@94 48
        (organiser, organiser_attr), attendees = organiser_item, attendees = oa
paul@94 49
paul@110 50
        # Handle notifications and invitations.
paul@55 51
paul@100 52
        if from_organiser:
paul@110 53
paul@110 54
            # Process each attendee separately.
paul@110 55
paul@100 56
            for attendee, attendee_attr in attendees.items():
paul@100 57
paul@213 58
                if not self.have_new_object(attendee):
paul@100 59
                    continue
paul@100 60
paul@381 61
                # Set the complete event or an additional occurrence.
paul@334 62
paul@361 63
                self.store.set_event(attendee, self.uid, self.recurrenceid, self.obj.to_node())
paul@361 64
paul@381 65
                # Remove additional recurrences if handling a complete event.
paul@381 66
paul@381 67
                if not self.recurrenceid:
paul@381 68
                    self.store.remove_recurrences(attendee, self.uid)
paul@381 69
paul@334 70
                # Queue any request.
paul@55 71
paul@100 72
                if queue:
paul@343 73
                    self.store.queue_request(attendee, self.uid, self.recurrenceid)
paul@142 74
                elif cancel:
paul@343 75
                    self.store.cancel_event(attendee, self.uid, self.recurrenceid)
paul@100 76
paul@261 77
                    # No return message will occur to update the free/busy
paul@261 78
                    # information, so this is done here.
paul@261 79
paul@261 80
                    freebusy = self.store.get_freebusy(attendee)
paul@361 81
                    self.remove_from_freebusy(freebusy)
paul@361 82
paul@361 83
                    self.store.set_freebusy(attendee, freebusy)
paul@261 84
paul@261 85
                    if self.publisher:
paul@261 86
                        self.publisher.set_freebusy(attendee, freebusy)
paul@261 87
paul@268 88
                self.update_freebusy_from_organiser(attendee, organiser_item)
paul@268 89
paul@420 90
        # As organiser, update attendance from valid attendees.
paul@55 91
paul@100 92
        else:
paul@268 93
            if self.merge_attendance(attendees, organiser):
paul@268 94
                self.update_freebusy_from_attendees(organiser, attendees)
paul@61 95
paul@61 96
        return True
paul@61 97
paul@110 98
    def _record_freebusy(self, from_organiser=True):
paul@110 99
paul@420 100
        """
paul@420 101
        Record free/busy information for a message originating from an organiser
paul@420 102
        if 'from_organiser' is set to a true value.
paul@420 103
        """
paul@110 104
paul@292 105
        if from_organiser:
paul@292 106
            organiser_item = self.require_organiser(from_organiser)
paul@292 107
            if not organiser_item:
paul@292 108
                return
paul@271 109
paul@292 110
            senders = [organiser_item]
paul@292 111
        else:
paul@292 112
            oa = self.require_organiser_and_attendees(from_organiser)
paul@292 113
            if not oa:
paul@292 114
                return
paul@218 115
paul@292 116
            organiser_item, attendees = oa
paul@292 117
            senders = attendees.items()
paul@292 118
paul@292 119
            if not senders:
paul@292 120
                return
paul@218 121
paul@110 122
        freebusy = []
paul@111 123
paul@213 124
        for value in self.obj.get_values("FREEBUSY") or []:
paul@110 125
            if not isinstance(value, list):
paul@110 126
                value = [value]
paul@110 127
            for v in value:
paul@110 128
                try:
paul@110 129
                    start, end = v.split("/", 1)
paul@110 130
                    freebusy.append((start, end))
paul@110 131
                except ValueError:
paul@110 132
                    pass
paul@110 133
paul@327 134
        dtstart = format_datetime(self.obj.get_utc_datetime("DTSTART"))
paul@327 135
        dtend = format_datetime(self.obj.get_utc_datetime("DTEND"))
paul@327 136
        user = get_uri(self.recipient)
paul@327 137
paul@271 138
        for sender, sender_attr in senders:
paul@335 139
            stored_freebusy = self.store.get_freebusy_for_other(user, sender)
paul@327 140
            replace_overlapping(stored_freebusy, (dtstart, dtend), freebusy)
paul@339 141
            self.store.set_freebusy_for_other(user, stored_freebusy, sender)
paul@110 142
paul@216 143
class Event(PersonHandler):
paul@67 144
paul@67 145
    "An event handler."
paul@67 146
paul@63 147
    def add(self):
paul@63 148
paul@63 149
        # NOTE: Queue a suggested modification to any active event.
paul@63 150
paul@182 151
        return self.wrap("An addition to an event has been received.", link=False)
paul@63 152
paul@63 153
    def cancel(self):
paul@63 154
paul@142 155
        "Queue a cancellation of any active event."
paul@63 156
paul@271 157
        self._record(from_organiser=True, queue=False, cancel=True)
paul@182 158
        return self.wrap("A cancellation has been received.", link=False)
paul@63 159
paul@63 160
    def counter(self):
paul@63 161
paul@63 162
        # NOTE: Queue a suggested modification to any active event.
paul@63 163
paul@182 164
        return self.wrap("A counter proposal has been received.", link=False)
paul@63 165
paul@63 166
    def declinecounter(self):
paul@63 167
paul@63 168
        # NOTE: Queue a suggested modification to any active event.
paul@63 169
paul@182 170
        return self.wrap("A declining counter proposal has been received.", link=False)
paul@63 171
paul@63 172
    def publish(self):
paul@63 173
paul@139 174
        "Register details of any relevant event."
paul@63 175
paul@271 176
        self._record(from_organiser=True, queue=False)
paul@182 177
        return self.wrap("Details of an event have been received.")
paul@63 178
paul@63 179
    def refresh(self):
paul@63 180
paul@353 181
        "Generate details of any active event."
paul@63 182
paul@353 183
        # NOTE: Return event details if configured to do so.
paul@353 184
paul@353 185
        return self.wrap("A request for updated event details has been received.")
paul@63 186
paul@61 187
    def reply(self):
paul@61 188
paul@61 189
        "Record replies and notify the recipient."
paul@61 190
paul@271 191
        self._record(from_organiser=False, queue=False)
paul@182 192
        return self.wrap("A reply has been received.")
paul@61 193
paul@61 194
    def request(self):
paul@61 195
paul@61 196
        "Hold requests and notify the recipient."
paul@61 197
paul@271 198
        self._record(from_organiser=True, queue=True)
paul@216 199
        return self.wrap("A request has been received.")
paul@60 200
paul@108 201
class Freebusy(PersonHandler, CommonFreebusy):
paul@55 202
paul@55 203
    "A free/busy handler."
paul@55 204
paul@55 205
    def publish(self):
paul@63 206
paul@110 207
        "Register free/busy information."
paul@110 208
paul@180 209
        self._record_freebusy(from_organiser=True)
paul@180 210
paul@180 211
        # Produce a message if configured to do so.
paul@63 212
paul@180 213
        preferences = Preferences(get_uri(self.recipient))
paul@180 214
        if preferences.get("freebusy_messages") == "notify":
paul@182 215
            return self.wrap("A free/busy update has been received.", link=False)
paul@55 216
paul@55 217
    def reply(self):
paul@55 218
paul@63 219
        "Record replies and notify the recipient."
paul@63 220
paul@180 221
        self._record_freebusy(from_organiser=False)
paul@180 222
paul@180 223
        # Produce a message if configured to do so.
paul@139 224
paul@180 225
        preferences = Preferences(get_uri(self.recipient))
paul@180 226
        if preferences.get("freebusy_messages") == "notify":
paul@182 227
            return self.wrap("A reply to a free/busy request has been received.", link=False)
paul@55 228
paul@55 229
    def request(self):
paul@55 230
paul@55 231
        """
paul@55 232
        Respond to a request by preparing a reply containing free/busy
paul@55 233
        information for each indicated attendee.
paul@55 234
        """
paul@55 235
paul@180 236
        # Produce a reply if configured to do so.
paul@55 237
paul@180 238
        preferences = Preferences(get_uri(self.recipient))
paul@180 239
        if preferences.get("freebusy_sharing") == "share":
paul@180 240
            return CommonFreebusy.request(self)
paul@55 241
paul@55 242
# Handler registry.
paul@55 243
paul@55 244
handlers = [
paul@55 245
    ("VFREEBUSY",   Freebusy),
paul@55 246
    ("VEVENT",      Event),
paul@55 247
    ]
paul@55 248
paul@55 249
# vim: tabstop=4 expandtab shiftwidth=4