imip-agent

Annotated imiptools/handlers/person_outgoing.py

900:3308aa0462ca
2015-10-22 Paul Boddie Tidied up the treatment of local SMTP and LMTP in the documentation. Changed the simple Postfix configuration to have local routing as an additional configuration file, rather than a replacement. Added calendar-to-root aliasing for the Exim simple configuration.
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@468 131
        self.store.dequeue_request(self.user, self.uid, self.recurrenceid)
paul@809 132
        self.store.remove_counters(self.user, self.uid, self.recurrenceid)
paul@160 133
paul@96 134
        # Update free/busy information.
paul@96 135
paul@799 136
        if not counter:
paul@799 137
            self.update_event_in_freebusy(from_organiser)
paul@799 138
paul@799 139
        # For countered proposals, record the offer in the resource's
paul@799 140
        # free/busy collection.
paul@799 141
paul@799 142
        else:
paul@799 143
            self.update_event_in_freebusy_offers()
paul@96 144
paul@96 145
        return True
paul@96 146
paul@697 147
    def _remove(self):
paul@140 148
paul@580 149
        """
paul@580 150
        Remove details from the current object given a message originating
paul@580 151
        from an organiser if 'from_organiser' is set to a true value.
paul@580 152
        """
paul@140 153
paul@705 154
        if not Client.is_participating(self):
paul@705 155
            return False
paul@468 156
paul@468 157
        # Check for event using UID.
paul@468 158
paul@468 159
        if not self.have_new_object():
paul@140 160
            return False
paul@140 161
paul@527 162
        # Obtain any stored object, using parent object details if a newly-
paul@527 163
        # indicated occurrence is referenced.
paul@312 164
paul@606 165
        obj = self.get_stored_object_version()
paul@527 166
        old = not obj and self.get_parent_object() or obj
paul@527 167
paul@527 168
        if not old:
paul@420 169
            return False
paul@261 170
paul@527 171
        # Only cancel the event completely if all attendees are given.
paul@527 172
paul@527 173
        attendees = uri_dict(old.get_value_map("ATTENDEE"))
paul@312 174
        all_attendees = set(attendees.keys())
paul@312 175
        given_attendees = set(uri_values(self.obj.get_values("ATTENDEE")))
paul@690 176
        cancel_entire_event = not all_attendees.difference(given_attendees)
paul@312 177
paul@697 178
        # Update the recipient's record of the organiser's schedule.
paul@697 179
paul@697 180
        self.remove_freebusy_from_organiser(self.obj.get_value("ORGANIZER"))
paul@697 181
paul@312 182
        # Otherwise, remove the given attendees and update the event.
paul@312 183
paul@694 184
        if not cancel_entire_event and obj:
paul@312 185
            for attendee in given_attendees:
paul@314 186
                if attendees.has_key(attendee):
paul@314 187
                    del attendees[attendee]
paul@312 188
            obj["ATTENDEE"] = attendees.items()
paul@264 189
paul@312 190
        # Update the stored object with sequence information.
paul@312 191
paul@527 192
        if obj:
paul@577 193
            obj["SEQUENCE"] = self.obj.get_items("SEQUENCE") or []
paul@577 194
            obj["DTSTAMP"] = self.obj.get_items("DTSTAMP") or []
paul@312 195
paul@672 196
        # Update free/busy information.
paul@672 197
paul@672 198
        if cancel_entire_event or self.user in given_attendees:
paul@672 199
            self.remove_event_from_freebusy()
paul@672 200
paul@527 201
        # Set the complete event if not an additional occurrence. For any newly-
paul@527 202
        # indicated occurrence, use the received event details.
paul@334 203
paul@527 204
        self.store.set_event(self.user, self.uid, self.recurrenceid, (obj or self.obj).to_node())
paul@264 205
paul@694 206
        # Perform any cancellation after recording the latest state of the
paul@694 207
        # event.
paul@694 208
paul@694 209
        if cancel_entire_event:
paul@694 210
            self.store.cancel_event(self.user, self.uid, self.recurrenceid)
paul@694 211
paul@261 212
        # Remove any associated request.
paul@261 213
paul@468 214
        self.store.dequeue_request(self.user, self.uid, self.recurrenceid)
paul@809 215
        self.store.remove_counters(self.user, self.uid, self.recurrenceid)
paul@261 216
paul@140 217
        return True
paul@140 218
paul@804 219
    def _declinecounter(self):
paul@804 220
paul@804 221
        "Remove any counter-proposals for the given event."
paul@804 222
paul@804 223
        if not Client.is_participating(self):
paul@804 224
            return False
paul@804 225
paul@804 226
        # Check for event using UID.
paul@804 227
paul@804 228
        if not self.have_new_object():
paul@804 229
            return False
paul@804 230
paul@809 231
        self.remove_counters(uri_values(self.obj.get_values("ATTENDEE")))
paul@804 232
paul@96 233
class Event(PersonHandler):
paul@96 234
paul@96 235
    "An event handler."
paul@96 236
paul@96 237
    def add(self):
paul@686 238
paul@686 239
        "Record the addition of a recurrence to an event."
paul@686 240
paul@684 241
        self._add()
paul@96 242
paul@96 243
    def cancel(self):
paul@686 244
paul@686 245
        "Remove an event or a recurrence."
paul@686 246
paul@697 247
        self._remove()
paul@96 248
paul@96 249
    def counter(self):
paul@686 250
paul@799 251
        "Record an offer made by a counter-proposal."
paul@686 252
paul@799 253
        self._record(False, True)
paul@96 254
paul@96 255
    def declinecounter(self):
paul@686 256
paul@804 257
        "Expire any offer made by a counter-proposal."
paul@686 258
paul@804 259
        self._declinecounter()
paul@96 260
paul@96 261
    def publish(self):
paul@686 262
paul@686 263
        "Published events are recorded."
paul@686 264
paul@580 265
        self._record(True)
paul@96 266
paul@96 267
    def refresh(self):
paul@686 268
paul@686 269
        "Requests to refresh events do not provide event information."
paul@686 270
paul@353 271
        pass
paul@96 272
paul@96 273
    def reply(self):
paul@686 274
paul@686 275
        "Replies to requests are inspected for attendee information."
paul@686 276
paul@580 277
        self._record(False)
paul@96 278
paul@96 279
    def request(self):
paul@686 280
paul@686 281
        "Record events sent for potential scheduling."
paul@686 282
paul@580 283
        self._record(True)
paul@96 284
paul@96 285
# Handler registry.
paul@96 286
paul@96 287
handlers = [
paul@96 288
    ("VEVENT",      Event),
paul@96 289
    ]
paul@96 290
paul@96 291
# vim: tabstop=4 expandtab shiftwidth=4