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