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 set_identity(self, method): 33 34 """ 35 Set the current user for the current object in the context of the given 36 'method'. It is usually set when initialising the handler, using the 37 recipient details, but outgoing messages do not reference the recipient 38 in this way. 39 """ 40 41 if self.obj: 42 from_organiser = method in self.organiser_methods 43 self.user = get_uri(self.obj.get_value(from_organiser and "ORGANIZER" or "ATTENDEE")) 44 45 def _add(self): 46 47 "Add a recurrence for the current object." 48 49 if not Client.is_participating(self): 50 return False 51 52 # Obtain valid organiser and attendee details. 53 54 oa = self.require_organiser_and_attendees() 55 if not oa: 56 return False 57 58 (organiser, organiser_attr), attendees = oa 59 60 # Ignore unknown objects. 61 62 if not self.get_stored_object_version(): 63 return 64 65 # Record the event as a recurrence of the parent object. 66 67 self.update_recurrenceid() 68 69 # Set the additional occurrence. 70 71 self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node()) 72 73 # Update free/busy information. 74 75 self.update_event_in_freebusy() 76 77 return True 78 79 def _record(self, from_organiser=True): 80 81 """ 82 Record details from the current object given a message originating 83 from an organiser if 'from_organiser' is set to a true value. 84 """ 85 86 if not Client.is_participating(self): 87 return False 88 89 # Check for a new event, tolerating not-strictly-new events if the 90 # attendee is responding. 91 92 if not self.have_new_object(strict=from_organiser): 93 return False 94 95 # Update the object. 96 97 if from_organiser: 98 99 # Set the complete event or an additional occurrence. 100 101 self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node()) 102 103 # Remove additional recurrences if handling a complete event. 104 # Also remove any previous cancellations involving this event. 105 106 if not self.recurrenceid: 107 self.store.remove_recurrences(self.user, self.uid) 108 self.store.remove_cancellations(self.user, self.uid) 109 else: 110 self.store.remove_cancellation(self.user, self.uid, self.recurrenceid) 111 112 else: 113 # Obtain valid attendees, merging their attendance with the stored 114 # object. 115 116 attendees = self.require_attendees(from_organiser) 117 self.merge_attendance(attendees) 118 119 # Remove any associated request. 120 121 self.store.dequeue_request(self.user, self.uid, self.recurrenceid) 122 123 # Update free/busy information. 124 125 self.update_event_in_freebusy(from_organiser) 126 127 return True 128 129 def _remove(self): 130 131 """ 132 Remove details from the current object given a message originating 133 from an organiser if 'from_organiser' is set to a true value. 134 """ 135 136 if not Client.is_participating(self): 137 return False 138 139 # Check for event using UID. 140 141 if not self.have_new_object(): 142 return False 143 144 # Obtain any stored object, using parent object details if a newly- 145 # indicated occurrence is referenced. 146 147 obj = self.get_stored_object_version() 148 old = not obj and self.get_parent_object() or obj 149 150 if not old: 151 return False 152 153 # Only cancel the event completely if all attendees are given. 154 155 attendees = uri_dict(old.get_value_map("ATTENDEE")) 156 all_attendees = set(attendees.keys()) 157 given_attendees = set(uri_values(self.obj.get_values("ATTENDEE"))) 158 cancel_entire_event = not all_attendees.difference(given_attendees) 159 160 # Update the recipient's record of the organiser's schedule. 161 162 self.remove_freebusy_from_organiser(self.obj.get_value("ORGANIZER")) 163 164 # Otherwise, remove the given attendees and update the event. 165 166 if not cancel_entire_event and obj: 167 for attendee in given_attendees: 168 if attendees.has_key(attendee): 169 del attendees[attendee] 170 obj["ATTENDEE"] = attendees.items() 171 172 # Update the stored object with sequence information. 173 174 if obj: 175 obj["SEQUENCE"] = self.obj.get_items("SEQUENCE") or [] 176 obj["DTSTAMP"] = self.obj.get_items("DTSTAMP") or [] 177 178 # Update free/busy information. 179 180 if cancel_entire_event or self.user in given_attendees: 181 self.remove_event_from_freebusy() 182 183 # Set the complete event if not an additional occurrence. For any newly- 184 # indicated occurrence, use the received event details. 185 186 self.store.set_event(self.user, self.uid, self.recurrenceid, (obj or self.obj).to_node()) 187 188 # Perform any cancellation after recording the latest state of the 189 # event. 190 191 if cancel_entire_event: 192 self.store.cancel_event(self.user, self.uid, self.recurrenceid) 193 self.store.dequeue_request(self.user, self.uid, self.recurrenceid) 194 195 # Remove any associated request. 196 197 self.store.dequeue_request(self.user, self.uid, self.recurrenceid) 198 199 return True 200 201 class Event(PersonHandler): 202 203 "An event handler." 204 205 def add(self): 206 207 "Record the addition of a recurrence to an event." 208 209 self._add() 210 211 def cancel(self): 212 213 "Remove an event or a recurrence." 214 215 self._remove() 216 217 def counter(self): 218 219 "Counter-proposals are tentative and do not change events." 220 221 pass 222 223 def declinecounter(self): 224 225 "Declined counter-proposals are advisory and do not change events." 226 227 pass 228 229 def publish(self): 230 231 "Published events are recorded." 232 233 self._record(True) 234 235 def refresh(self): 236 237 "Requests to refresh events do not provide event information." 238 239 pass 240 241 def reply(self): 242 243 "Replies to requests are inspected for attendee information." 244 245 self._record(False) 246 247 def request(self): 248 249 "Record events sent for potential scheduling." 250 251 self._record(True) 252 253 # Handler registry. 254 255 handlers = [ 256 ("VEVENT", Event), 257 ] 258 259 # vim: tabstop=4 expandtab shiftwidth=4