imip-agent

Annotated imiptools/handlers/person_outgoing.py

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