imip-agent

Annotated imiptools/handlers/person.py

218:bb42d525c718
2015-02-01 Paul Boddie Handle free/busy messages without valid sender information.
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@213 22
from imiptools.content import Handler
paul@213 23
from imiptools.data import get_address, get_uri, uri_dict, uri_items
paul@216 24
from imiptools.handlers.common import CommonFreebusy
paul@179 25
from imiptools.profile import Preferences
paul@55 26
paul@67 27
class PersonHandler(Handler):
paul@55 28
paul@67 29
    "Handling mechanisms specific to people."
paul@55 30
paul@213 31
    def _record_and_deliver(self, from_organiser=True, queue=False, cancel=False):
paul@55 32
paul@100 33
        oa = self.require_organiser_and_attendees(from_organiser)
paul@55 34
        if not oa:
paul@61 35
            return False
paul@55 36
paul@94 37
        (organiser, organiser_attr), attendees = organiser_item, attendees = oa
paul@94 38
paul@110 39
        # Handle notifications and invitations.
paul@55 40
paul@100 41
        if from_organiser:
paul@110 42
paul@110 43
            # Process each attendee separately.
paul@110 44
paul@100 45
            for attendee, attendee_attr in attendees.items():
paul@100 46
paul@213 47
                if not self.have_new_object(attendee):
paul@100 48
                    continue
paul@100 49
paul@168 50
                # Record other party free/busy information.
paul@168 51
paul@168 52
                if organiser != attendee:
paul@168 53
                    freebusy = self.store.get_freebusy_for_other(attendee, organiser)
paul@168 54
paul@168 55
                    if organiser_attr.get("PARTSTAT") != "DECLINED":
paul@168 56
                        self.update_freebusy_for_other(freebusy, attendee, organiser, self.get_periods())
paul@168 57
                    else:
paul@168 58
                        self.remove_from_freebusy_for_other(freebusy, attendee, organiser)
paul@168 59
paul@100 60
                # Store the object and queue any request.
paul@55 61
paul@213 62
                self.store.set_event(attendee, self.uid, self.obj.to_node())
paul@55 63
paul@100 64
                if queue:
paul@100 65
                    self.store.queue_request(attendee, self.uid)
paul@142 66
                elif cancel:
paul@142 67
                    self.store.cancel_event(attendee, self.uid)
paul@100 68
paul@100 69
        # As organiser, update attendance.
paul@55 70
paul@100 71
        else:
paul@213 72
            obj = self.get_object(organiser)
paul@100 73
paul@213 74
            if obj and self.have_new_object(organiser, obj=obj):
paul@130 75
paul@130 76
                # Get attendee details in a usable form.
paul@130 77
paul@214 78
                attendee_map = uri_dict(obj.get_value_map("ATTENDEE"))
paul@100 79
paul@100 80
                for attendee, attendee_attr in attendees.items():
paul@100 81
paul@100 82
                    # Update attendance in the loaded object.
paul@55 83
paul@100 84
                    attendee_map[attendee] = attendee_attr
paul@100 85
paul@168 86
                    # Record other party free/busy information.
paul@168 87
paul@168 88
                    if organiser != attendee:
paul@168 89
                        freebusy = self.store.get_freebusy_for_other(organiser, attendee)
paul@168 90
paul@168 91
                        if attendee_attr.get("PARTSTAT") != "DECLINED":
paul@168 92
                            self.update_freebusy_for_other(freebusy, organiser, attendee, self.get_periods())
paul@168 93
                        else:
paul@168 94
                            self.remove_from_freebusy_for_other(freebusy, organiser, attendee)
paul@163 95
paul@100 96
                # Set the new details and store the object.
paul@100 97
paul@100 98
                obj["ATTENDEE"] = attendee_map.items()
paul@100 99
paul@213 100
                self.store.set_event(organiser, self.uid, obj.to_node())
paul@61 101
paul@61 102
        return True
paul@61 103
paul@110 104
    def _record_freebusy(self, from_organiser=True):
paul@110 105
paul@110 106
        "Record free/busy information for the received information."
paul@110 107
paul@218 108
        senders = self.obj.get_items(from_organiser and "ORGANIZER" or "ATTENDEE")
paul@218 109
paul@218 110
        if not senders:
paul@218 111
            return
paul@218 112
paul@110 113
        freebusy = []
paul@111 114
paul@213 115
        for value in self.obj.get_values("FREEBUSY") or []:
paul@110 116
            if not isinstance(value, list):
paul@110 117
                value = [value]
paul@110 118
            for v in value:
paul@110 119
                try:
paul@110 120
                    start, end = v.split("/", 1)
paul@110 121
                    freebusy.append((start, end))
paul@110 122
                except ValueError:
paul@110 123
                    pass
paul@110 124
paul@218 125
        for sender, sender_attr in uri_items(senders):
paul@179 126
            self.store.set_freebusy_for_other(get_uri(self.recipient), freebusy, sender)
paul@110 127
paul@216 128
class Event(PersonHandler):
paul@67 129
paul@67 130
    "An event handler."
paul@67 131
paul@63 132
    def add(self):
paul@63 133
paul@63 134
        # NOTE: Queue a suggested modification to any active event.
paul@63 135
paul@182 136
        return self.wrap("An addition to an event has been received.", link=False)
paul@63 137
paul@63 138
    def cancel(self):
paul@63 139
paul@142 140
        "Queue a cancellation of any active event."
paul@63 141
paul@213 142
        self._record_and_deliver(from_organiser=True, queue=False, cancel=True)
paul@182 143
        return self.wrap("A cancellation has been received.", link=False)
paul@63 144
paul@63 145
    def counter(self):
paul@63 146
paul@63 147
        # NOTE: Queue a suggested modification to any active event.
paul@63 148
paul@182 149
        return self.wrap("A counter proposal has been received.", link=False)
paul@63 150
paul@63 151
    def declinecounter(self):
paul@63 152
paul@63 153
        # NOTE: Queue a suggested modification to any active event.
paul@63 154
paul@182 155
        return self.wrap("A declining counter proposal has been received.", link=False)
paul@63 156
paul@63 157
    def publish(self):
paul@63 158
paul@139 159
        "Register details of any relevant event."
paul@63 160
paul@213 161
        self._record_and_deliver(from_organiser=True, queue=False)
paul@182 162
        return self.wrap("Details of an event have been received.")
paul@63 163
paul@63 164
    def refresh(self):
paul@63 165
paul@139 166
        "Update details of any active event."
paul@63 167
paul@213 168
        self._record_and_deliver(from_organiser=True, queue=False)
paul@182 169
        return self.wrap("An event update has been received.")
paul@63 170
paul@61 171
    def reply(self):
paul@61 172
paul@61 173
        "Record replies and notify the recipient."
paul@61 174
paul@213 175
        self._record_and_deliver(from_organiser=False, queue=False)
paul@182 176
        return self.wrap("A reply has been received.")
paul@61 177
paul@61 178
    def request(self):
paul@61 179
paul@61 180
        "Hold requests and notify the recipient."
paul@61 181
paul@213 182
        self._record_and_deliver(from_organiser=True, queue=True)
paul@216 183
        return self.wrap("A request has been received.")
paul@60 184
paul@108 185
class Freebusy(PersonHandler, CommonFreebusy):
paul@55 186
paul@55 187
    "A free/busy handler."
paul@55 188
paul@55 189
    def publish(self):
paul@63 190
paul@110 191
        "Register free/busy information."
paul@110 192
paul@180 193
        self._record_freebusy(from_organiser=True)
paul@180 194
paul@180 195
        # Produce a message if configured to do so.
paul@63 196
paul@180 197
        preferences = Preferences(get_uri(self.recipient))
paul@180 198
        if preferences.get("freebusy_messages") == "notify":
paul@182 199
            return self.wrap("A free/busy update has been received.", link=False)
paul@55 200
paul@55 201
    def reply(self):
paul@55 202
paul@63 203
        "Record replies and notify the recipient."
paul@63 204
paul@180 205
        self._record_freebusy(from_organiser=False)
paul@180 206
paul@180 207
        # Produce a message if configured to do so.
paul@139 208
paul@180 209
        preferences = Preferences(get_uri(self.recipient))
paul@180 210
        if preferences.get("freebusy_messages") == "notify":
paul@182 211
            return self.wrap("A reply to a free/busy request has been received.", link=False)
paul@55 212
paul@55 213
    def request(self):
paul@55 214
paul@55 215
        """
paul@55 216
        Respond to a request by preparing a reply containing free/busy
paul@55 217
        information for each indicated attendee.
paul@55 218
        """
paul@55 219
paul@180 220
        # Produce a reply if configured to do so.
paul@55 221
paul@180 222
        preferences = Preferences(get_uri(self.recipient))
paul@180 223
        if preferences.get("freebusy_sharing") == "share":
paul@180 224
            return CommonFreebusy.request(self)
paul@55 225
paul@67 226
class Journal(PersonHandler):
paul@55 227
paul@55 228
    "A journal entry handler."
paul@55 229
paul@55 230
    def add(self):
paul@63 231
paul@63 232
        # NOTE: Queue a suggested modification to any active entry.
paul@63 233
paul@182 234
        return self.wrap("An addition to a journal entry has been received.", link=False)
paul@55 235
paul@55 236
    def cancel(self):
paul@63 237
paul@63 238
        # NOTE: Queue a suggested modification to any active entry.
paul@63 239
paul@182 240
        return self.wrap("A cancellation has been received.", link=False)
paul@55 241
paul@55 242
    def publish(self):
paul@63 243
paul@63 244
        # NOTE: Register details of any relevant entry.
paul@63 245
paul@213 246
        self._record_and_deliver(from_organiser=True, queue=False)
paul@182 247
        return self.wrap("Details of a journal entry have been received.")
paul@55 248
paul@67 249
class Todo(PersonHandler):
paul@55 250
paul@55 251
    "A to-do item handler."
paul@55 252
paul@55 253
    def add(self):
paul@63 254
paul@63 255
        # NOTE: Queue a suggested modification to any active item.
paul@63 256
paul@182 257
        return self.wrap("An addition to an item has been received.", link=False)
paul@55 258
paul@55 259
    def cancel(self):
paul@63 260
paul@63 261
        # NOTE: Queue a suggested modification to any active item.
paul@63 262
paul@182 263
        return self.wrap("A cancellation has been received.", link=False)
paul@55 264
paul@55 265
    def counter(self):
paul@55 266
paul@63 267
        # NOTE: Queue a suggested modification to any active item.
paul@55 268
paul@182 269
        return self.wrap("A counter proposal has been received.", link=False)
paul@55 270
paul@55 271
    def declinecounter(self):
paul@55 272
paul@63 273
        # NOTE: Queue a suggested modification to any active item.
paul@55 274
paul@182 275
        return self.wrap("A declining counter proposal has been received.", link=False)
paul@55 276
paul@55 277
    def publish(self):
paul@63 278
paul@139 279
        "Register details of any relevant item."
paul@63 280
paul@213 281
        self._record_and_deliver(from_organiser=True, queue=False)
paul@182 282
        return self.wrap("Details of an item have been received.")
paul@55 283
paul@55 284
    def refresh(self):
paul@63 285
paul@139 286
        "Update details of any active item."
paul@63 287
paul@213 288
        self._record_and_deliver(from_organiser=True, queue=False)
paul@182 289
        return self.wrap("An item update has been received.")
paul@55 290
paul@55 291
    def reply(self):
paul@55 292
paul@63 293
        "Record replies and notify the recipient."
paul@63 294
paul@213 295
        self._record_and_deliver(from_organiser=False, queue=False)
paul@182 296
        return self.wrap("A reply has been received.")
paul@55 297
paul@55 298
    def request(self):
paul@63 299
paul@63 300
        "Hold requests and notify the recipient."
paul@63 301
paul@213 302
        self._record_and_deliver(from_organiser=True, queue=True)
paul@182 303
        return self.wrap("A request has been received.")
paul@55 304
paul@55 305
# Handler registry.
paul@55 306
paul@55 307
handlers = [
paul@55 308
    ("VFREEBUSY",   Freebusy),
paul@55 309
    ("VEVENT",      Event),
paul@55 310
    ("VTODO",       Todo),
paul@55 311
    ("VJOURNAL",    Journal),
paul@55 312
    ]
paul@55 313
paul@55 314
# vim: tabstop=4 expandtab shiftwidth=4