imip-agent

Annotated imiptools/handlers/person.py

421:f658ca7505b2
2015-03-22 Paul Boddie Moved methods around.
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@271 22
from imiptools.data import get_uri
paul@327 23
from imiptools.dates import format_datetime
paul@418 24
from imiptools.handlers import Handler
paul@216 25
from imiptools.handlers.common import CommonFreebusy
paul@327 26
from imiptools.period import replace_overlapping
paul@179 27
from imiptools.profile import Preferences
paul@55 28
paul@67 29
class PersonHandler(Handler):
paul@55 30
paul@67 31
    "Handling mechanisms specific to people."
paul@55 32
paul@271 33
    def _record(self, from_organiser=True, queue=False, cancel=False):
paul@55 34
paul@420 35
        """
paul@420 36
        Record details from the current object given a message originating
paul@420 37
        from an organiser if 'from_organiser' is set to a true value, queuing a
paul@420 38
        request if 'queue' is set to a true value, or cancelling an event if
paul@420 39
        'cancel' is set to a true value.
paul@420 40
        """
paul@420 41
paul@420 42
        # Obtain valid organiser and attendee details.
paul@420 43
paul@100 44
        oa = self.require_organiser_and_attendees(from_organiser)
paul@55 45
        if not oa:
paul@61 46
            return False
paul@55 47
paul@94 48
        (organiser, organiser_attr), attendees = organiser_item, attendees = oa
paul@94 49
paul@110 50
        # Handle notifications and invitations.
paul@55 51
paul@100 52
        if from_organiser:
paul@110 53
paul@110 54
            # Process each attendee separately.
paul@110 55
paul@100 56
            for attendee, attendee_attr in attendees.items():
paul@100 57
paul@213 58
                if not self.have_new_object(attendee):
paul@100 59
                    continue
paul@100 60
paul@381 61
                # Set the complete event or an additional occurrence.
paul@334 62
paul@361 63
                self.store.set_event(attendee, self.uid, self.recurrenceid, self.obj.to_node())
paul@361 64
paul@381 65
                # Remove additional recurrences if handling a complete event.
paul@381 66
paul@381 67
                if not self.recurrenceid:
paul@381 68
                    self.store.remove_recurrences(attendee, self.uid)
paul@381 69
paul@334 70
                # Queue any request.
paul@55 71
paul@100 72
                if queue:
paul@343 73
                    self.store.queue_request(attendee, self.uid, self.recurrenceid)
paul@142 74
                elif cancel:
paul@343 75
                    self.store.cancel_event(attendee, self.uid, self.recurrenceid)
paul@100 76
paul@261 77
                    # No return message will occur to update the free/busy
paul@261 78
                    # information, so this is done here.
paul@261 79
paul@261 80
                    freebusy = self.store.get_freebusy(attendee)
paul@361 81
                    self.remove_from_freebusy(freebusy)
paul@361 82
paul@361 83
                    self.store.set_freebusy(attendee, freebusy)
paul@261 84
paul@261 85
                    if self.publisher:
paul@261 86
                        self.publisher.set_freebusy(attendee, freebusy)
paul@261 87
paul@268 88
                self.update_freebusy_from_organiser(attendee, organiser_item)
paul@268 89
paul@420 90
        # As organiser, update attendance from valid attendees.
paul@55 91
paul@100 92
        else:
paul@268 93
            if self.merge_attendance(attendees, organiser):
paul@268 94
                self.update_freebusy_from_attendees(organiser, attendees)
paul@61 95
paul@61 96
        return True
paul@61 97
paul@110 98
    def _record_freebusy(self, from_organiser=True):
paul@110 99
paul@420 100
        """
paul@420 101
        Record free/busy information for a message originating from an organiser
paul@420 102
        if 'from_organiser' is set to a true value.
paul@420 103
        """
paul@110 104
paul@292 105
        if from_organiser:
paul@292 106
            organiser_item = self.require_organiser(from_organiser)
paul@292 107
            if not organiser_item:
paul@292 108
                return
paul@271 109
paul@292 110
            senders = [organiser_item]
paul@292 111
        else:
paul@292 112
            oa = self.require_organiser_and_attendees(from_organiser)
paul@292 113
            if not oa:
paul@292 114
                return
paul@218 115
paul@292 116
            organiser_item, attendees = oa
paul@292 117
            senders = attendees.items()
paul@292 118
paul@292 119
            if not senders:
paul@292 120
                return
paul@218 121
paul@110 122
        freebusy = []
paul@111 123
paul@213 124
        for value in self.obj.get_values("FREEBUSY") or []:
paul@110 125
            if not isinstance(value, list):
paul@110 126
                value = [value]
paul@110 127
            for v in value:
paul@110 128
                try:
paul@110 129
                    start, end = v.split("/", 1)
paul@110 130
                    freebusy.append((start, end))
paul@110 131
                except ValueError:
paul@110 132
                    pass
paul@110 133
paul@327 134
        dtstart = format_datetime(self.obj.get_utc_datetime("DTSTART"))
paul@327 135
        dtend = format_datetime(self.obj.get_utc_datetime("DTEND"))
paul@327 136
        user = get_uri(self.recipient)
paul@327 137
paul@271 138
        for sender, sender_attr in senders:
paul@335 139
            stored_freebusy = self.store.get_freebusy_for_other(user, sender)
paul@327 140
            replace_overlapping(stored_freebusy, (dtstart, dtend), freebusy)
paul@339 141
            self.store.set_freebusy_for_other(user, stored_freebusy, sender)
paul@110 142
paul@216 143
class Event(PersonHandler):
paul@67 144
paul@67 145
    "An event handler."
paul@67 146
paul@63 147
    def add(self):
paul@63 148
paul@63 149
        # NOTE: Queue a suggested modification to any active event.
paul@63 150
paul@182 151
        return self.wrap("An addition to an event has been received.", link=False)
paul@63 152
paul@63 153
    def cancel(self):
paul@63 154
paul@142 155
        "Queue a cancellation of any active event."
paul@63 156
paul@271 157
        self._record(from_organiser=True, queue=False, cancel=True)
paul@182 158
        return self.wrap("A cancellation has been received.", link=False)
paul@63 159
paul@63 160
    def counter(self):
paul@63 161
paul@63 162
        # NOTE: Queue a suggested modification to any active event.
paul@63 163
paul@182 164
        return self.wrap("A counter proposal has been received.", link=False)
paul@63 165
paul@63 166
    def declinecounter(self):
paul@63 167
paul@63 168
        # NOTE: Queue a suggested modification to any active event.
paul@63 169
paul@182 170
        return self.wrap("A declining counter proposal has been received.", link=False)
paul@63 171
paul@63 172
    def publish(self):
paul@63 173
paul@139 174
        "Register details of any relevant event."
paul@63 175
paul@271 176
        self._record(from_organiser=True, queue=False)
paul@182 177
        return self.wrap("Details of an event have been received.")
paul@63 178
paul@63 179
    def refresh(self):
paul@63 180
paul@353 181
        "Generate details of any active event."
paul@63 182
paul@353 183
        # NOTE: Return event details if configured to do so.
paul@353 184
paul@353 185
        return self.wrap("A request for updated event details has been received.")
paul@63 186
paul@61 187
    def reply(self):
paul@61 188
paul@61 189
        "Record replies and notify the recipient."
paul@61 190
paul@271 191
        self._record(from_organiser=False, queue=False)
paul@182 192
        return self.wrap("A reply has been received.")
paul@61 193
paul@61 194
    def request(self):
paul@61 195
paul@61 196
        "Hold requests and notify the recipient."
paul@61 197
paul@271 198
        self._record(from_organiser=True, queue=True)
paul@216 199
        return self.wrap("A request has been received.")
paul@60 200
paul@108 201
class Freebusy(PersonHandler, CommonFreebusy):
paul@55 202
paul@55 203
    "A free/busy handler."
paul@55 204
paul@55 205
    def publish(self):
paul@63 206
paul@110 207
        "Register free/busy information."
paul@110 208
paul@180 209
        self._record_freebusy(from_organiser=True)
paul@180 210
paul@180 211
        # Produce a message if configured to do so.
paul@63 212
paul@180 213
        preferences = Preferences(get_uri(self.recipient))
paul@180 214
        if preferences.get("freebusy_messages") == "notify":
paul@182 215
            return self.wrap("A free/busy update has been received.", link=False)
paul@55 216
paul@55 217
    def reply(self):
paul@55 218
paul@63 219
        "Record replies and notify the recipient."
paul@63 220
paul@180 221
        self._record_freebusy(from_organiser=False)
paul@180 222
paul@180 223
        # Produce a message if configured to do so.
paul@139 224
paul@180 225
        preferences = Preferences(get_uri(self.recipient))
paul@180 226
        if preferences.get("freebusy_messages") == "notify":
paul@182 227
            return self.wrap("A reply to a free/busy request has been received.", link=False)
paul@55 228
paul@55 229
    def request(self):
paul@55 230
paul@55 231
        """
paul@55 232
        Respond to a request by preparing a reply containing free/busy
paul@55 233
        information for each indicated attendee.
paul@55 234
        """
paul@55 235
paul@180 236
        # Produce a reply if configured to do so.
paul@55 237
paul@180 238
        preferences = Preferences(get_uri(self.recipient))
paul@180 239
        if preferences.get("freebusy_sharing") == "share":
paul@180 240
            return CommonFreebusy.request(self)
paul@55 241
paul@55 242
# Handler registry.
paul@55 243
paul@55 244
handlers = [
paul@55 245
    ("VFREEBUSY",   Freebusy),
paul@55 246
    ("VEVENT",      Event),
paul@55 247
    ]
paul@55 248
paul@55 249
# vim: tabstop=4 expandtab shiftwidth=4