imip-agent

Annotated imiptools/handlers/person_outgoing.py

1045:5a9722130d25
2016-02-08 Paul Boddie Used event-related attributes stored in the handler directly.
paul@96 1
#!/usr/bin/env python
paul@96 2
paul@96 3
"""
paul@96 4
Handlers for a person for whom scheduling is performed, inspecting outgoing
paul@96 5
messages to obtain scheduling done externally.
paul@146 6
paul@146 7
Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>
paul@146 8
paul@146 9
This program is free software; you can redistribute it and/or modify it under
paul@146 10
the terms of the GNU General Public License as published by the Free Software
paul@146 11
Foundation; either version 3 of the License, or (at your option) any later
paul@146 12
version.
paul@146 13
paul@146 14
This program is distributed in the hope that it will be useful, but WITHOUT
paul@146 15
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@146 16
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@146 17
details.
paul@146 18
paul@146 19
You should have received a copy of the GNU General Public License along with
paul@146 20
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@96 21
"""
paul@96 22
paul@705 23
from imiptools.client import Client
paul@847 24
from imiptools.data import get_uri, uri_dict, uri_values
paul@418 25
from imiptools.handlers import Handler
paul@683 26
from imiptools.handlers.common import CommonEvent
paul@96 27
paul@725 28
class PersonHandler(CommonEvent, Handler):
paul@96 29
paul@96 30
    "Handling mechanisms specific to people."
paul@96 31
paul@729 32
    def set_identity(self, method):
paul@130 33
paul@140 34
        """
paul@729 35
        Set the current user for the current object in the context of the given
paul@729 36
        'method'. It is usually set when initialising the handler, using the
paul@729 37
        recipient details, but outgoing messages do not reference the recipient
paul@729 38
        in this way.
paul@140 39
        """
paul@130 40
paul@832 41
        if self.obj and not self.user:
paul@729 42
            from_organiser = method in self.organiser_methods
paul@832 43
            if from_organiser:
paul@832 44
                self.user = get_uri(self.obj.get_value("ORGANIZER"))
paul@832 45
paul@832 46
            # Since there may be many attendees in an attendee-provided outgoing
paul@832 47
            # message, because counter-proposals can have more than one
paul@832 48
            # attendee, the attendee originating from the calendar system is
paul@832 49
            # chosen.
paul@832 50
paul@832 51
            else:
paul@847 52
                self.user = self.get_sending_attendee()
paul@140 53
paul@684 54
    def _add(self):
paul@684 55
paul@684 56
        "Add a recurrence for the current object."
paul@684 57
paul@705 58
        if not Client.is_participating(self):
paul@705 59
            return False
paul@684 60
paul@832 61
        # Check for event using UID.
paul@684 62
paul@832 63
        if not self.have_new_object():
paul@684 64
            return False
paul@684 65
paul@684 66
        # Ignore unknown objects.
paul@684 67
paul@684 68
        if not self.get_stored_object_version():
paul@684 69
            return
paul@684 70
paul@684 71
        # Record the event as a recurrence of the parent object.
paul@684 72
paul@684 73
        self.update_recurrenceid()
paul@684 74
paul@733 75
        # Set the additional occurrence.
paul@733 76
paul@733 77
        self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node())
paul@733 78
paul@859 79
        # Remove any previous cancellations involving this event.
paul@859 80
paul@859 81
        self.store.remove_cancellation(self.user, self.uid, self.recurrenceid)
paul@859 82
paul@684 83
        # Update free/busy information.
paul@684 84
paul@684 85
        self.update_event_in_freebusy()
paul@684 86
paul@684 87
        return True
paul@684 88
paul@799 89
    def _record(self, from_organiser=True, counter=False):
paul@140 90
paul@420 91
        """
paul@420 92
        Record details from the current object given a message originating
paul@580 93
        from an organiser if 'from_organiser' is set to a true value.
paul@420 94
        """
paul@140 95
paul@705 96
        if not Client.is_participating(self):
paul@705 97
            return False
paul@468 98
paul@682 99
        # Check for a new event, tolerating not-strictly-new events if the
paul@682 100
        # attendee is responding.
paul@468 101
paul@682 102
        if not self.have_new_object(strict=from_organiser):
paul@99 103
            return False
paul@97 104
paul@268 105
        # Update the object.
paul@96 106
paul@268 107
        if from_organiser:
paul@334 108
paul@361 109
            # Set the complete event or an additional occurrence.
paul@334 110
paul@468 111
            self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node())
paul@334 112
paul@381 113
            # Remove additional recurrences if handling a complete event.
paul@796 114
            # Also remove any previous cancellations involving this event.
paul@381 115
paul@381 116
            if not self.recurrenceid:
paul@468 117
                self.store.remove_recurrences(self.user, self.uid)
paul@796 118
                self.store.remove_cancellations(self.user, self.uid)
paul@796 119
            else:
paul@796 120
                self.store.remove_cancellation(self.user, self.uid, self.recurrenceid)
paul@381 121
paul@268 122
        else:
paul@420 123
            # Obtain valid attendees, merging their attendance with the stored
paul@420 124
            # object.
paul@420 125
paul@420 126
            attendees = self.require_attendees(from_organiser)
paul@468 127
            self.merge_attendance(attendees)
paul@96 128
paul@160 129
        # Remove any associated request.
paul@160 130
paul@917 131
        if from_organiser or self.has_indicated_attendance():
paul@917 132
            self.store.dequeue_request(self.user, self.uid, self.recurrenceid)
paul@809 133
        self.store.remove_counters(self.user, self.uid, self.recurrenceid)
paul@160 134
paul@96 135
        # Update free/busy information.
paul@96 136
paul@799 137
        if not counter:
paul@799 138
            self.update_event_in_freebusy(from_organiser)
paul@799 139
paul@799 140
        # For countered proposals, record the offer in the resource's
paul@799 141
        # free/busy collection.
paul@799 142
paul@799 143
        else:
paul@799 144
            self.update_event_in_freebusy_offers()
paul@96 145
paul@96 146
        return True
paul@96 147
paul@697 148
    def _remove(self):
paul@140 149
paul@580 150
        """
paul@580 151
        Remove details from the current object given a message originating
paul@580 152
        from an organiser if 'from_organiser' is set to a true value.
paul@580 153
        """
paul@140 154
paul@705 155
        if not Client.is_participating(self):
paul@705 156
            return False
paul@468 157
paul@468 158
        # Check for event using UID.
paul@468 159
paul@468 160
        if not self.have_new_object():
paul@140 161
            return False
paul@140 162
paul@527 163
        # Obtain any stored object, using parent object details if a newly-
paul@527 164
        # indicated occurrence is referenced.
paul@312 165
paul@606 166
        obj = self.get_stored_object_version()
paul@527 167
        old = not obj and self.get_parent_object() or obj
paul@527 168
paul@527 169
        if not old:
paul@420 170
            return False
paul@261 171
paul@527 172
        # Only cancel the event completely if all attendees are given.
paul@527 173
paul@527 174
        attendees = uri_dict(old.get_value_map("ATTENDEE"))
paul@312 175
        all_attendees = set(attendees.keys())
paul@312 176
        given_attendees = set(uri_values(self.obj.get_values("ATTENDEE")))
paul@690 177
        cancel_entire_event = not all_attendees.difference(given_attendees)
paul@312 178
paul@697 179
        # Update the recipient's record of the organiser's schedule.
paul@697 180
paul@697 181
        self.remove_freebusy_from_organiser(self.obj.get_value("ORGANIZER"))
paul@697 182
paul@312 183
        # Otherwise, remove the given attendees and update the event.
paul@312 184
paul@694 185
        if not cancel_entire_event and obj:
paul@312 186
            for attendee in given_attendees:
paul@314 187
                if attendees.has_key(attendee):
paul@314 188
                    del attendees[attendee]
paul@312 189
            obj["ATTENDEE"] = attendees.items()
paul@264 190
paul@312 191
        # Update the stored object with sequence information.
paul@312 192
paul@527 193
        if obj:
paul@577 194
            obj["SEQUENCE"] = self.obj.get_items("SEQUENCE") or []
paul@577 195
            obj["DTSTAMP"] = self.obj.get_items("DTSTAMP") or []
paul@312 196
paul@672 197
        # Update free/busy information.
paul@672 198
paul@672 199
        if cancel_entire_event or self.user in given_attendees:
paul@672 200
            self.remove_event_from_freebusy()
paul@911 201
            self.remove_freebusy_from_attendees(attendees)
paul@672 202
paul@527 203
        # Set the complete event if not an additional occurrence. For any newly-
paul@527 204
        # indicated occurrence, use the received event details.
paul@334 205
paul@527 206
        self.store.set_event(self.user, self.uid, self.recurrenceid, (obj or self.obj).to_node())
paul@264 207
paul@694 208
        # Perform any cancellation after recording the latest state of the
paul@694 209
        # event.
paul@694 210
paul@694 211
        if cancel_entire_event:
paul@694 212
            self.store.cancel_event(self.user, self.uid, self.recurrenceid)
paul@694 213
paul@261 214
        # Remove any associated request.
paul@261 215
paul@468 216
        self.store.dequeue_request(self.user, self.uid, self.recurrenceid)
paul@809 217
        self.store.remove_counters(self.user, self.uid, self.recurrenceid)
paul@261 218
paul@140 219
        return True
paul@140 220
paul@804 221
    def _declinecounter(self):
paul@804 222
paul@804 223
        "Remove any counter-proposals for the given event."
paul@804 224
paul@804 225
        if not Client.is_participating(self):
paul@804 226
            return False
paul@804 227
paul@804 228
        # Check for event using UID.
paul@804 229
paul@804 230
        if not self.have_new_object():
paul@804 231
            return False
paul@804 232
paul@809 233
        self.remove_counters(uri_values(self.obj.get_values("ATTENDEE")))
paul@804 234
paul@96 235
class Event(PersonHandler):
paul@96 236
paul@96 237
    "An event handler."
paul@96 238
paul@96 239
    def add(self):
paul@686 240
paul@686 241
        "Record the addition of a recurrence to an event."
paul@686 242
paul@684 243
        self._add()
paul@96 244
paul@96 245
    def cancel(self):
paul@686 246
paul@686 247
        "Remove an event or a recurrence."
paul@686 248
paul@697 249
        self._remove()
paul@96 250
paul@96 251
    def counter(self):
paul@686 252
paul@799 253
        "Record an offer made by a counter-proposal."
paul@686 254
paul@799 255
        self._record(False, True)
paul@96 256
paul@96 257
    def declinecounter(self):
paul@686 258
paul@804 259
        "Expire any offer made by a counter-proposal."
paul@686 260
paul@804 261
        self._declinecounter()
paul@96 262
paul@96 263
    def publish(self):
paul@686 264
paul@686 265
        "Published events are recorded."
paul@686 266
paul@580 267
        self._record(True)
paul@96 268
paul@96 269
    def refresh(self):
paul@686 270
paul@686 271
        "Requests to refresh events do not provide event information."
paul@686 272
paul@353 273
        pass
paul@96 274
paul@96 275
    def reply(self):
paul@686 276
paul@686 277
        "Replies to requests are inspected for attendee information."
paul@686 278
paul@580 279
        self._record(False)
paul@96 280
paul@96 281
    def request(self):
paul@686 282
paul@686 283
        "Record events sent for potential scheduling."
paul@686 284
paul@580 285
        self._record(True)
paul@96 286
paul@96 287
# Handler registry.
paul@96 288
paul@96 289
handlers = [
paul@96 290
    ("VEVENT",      Event),
paul@96 291
    ]
paul@96 292
paul@96 293
# vim: tabstop=4 expandtab shiftwidth=4