imip-agent

Annotated imiptools/handlers/person_outgoing.py

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