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