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 self.store.dequeue_request(self.user, self.uid, self.recurrenceid) 183 184 # Remove any associated request. 185 186 self.store.dequeue_request(self.user, self.uid, self.recurrenceid) 187 188 return True 189 190 class Event(PersonHandler): 191 192 "An event handler." 193 194 def add(self): 195 196 "Record the addition of a recurrence to an event." 197 198 self._add() 199 200 def cancel(self): 201 202 "Remove an event or a recurrence." 203 204 self._remove() 205 206 def counter(self): 207 208 "Counter-proposals are tentative and do not change events." 209 210 pass 211 212 def declinecounter(self): 213 214 "Declined counter-proposals are advisory and do not change events." 215 216 pass 217 218 def publish(self): 219 220 "Published events are recorded." 221 222 self._record(True) 223 224 def refresh(self): 225 226 "Requests to refresh events do not provide event information." 227 228 pass 229 230 def reply(self): 231 232 "Replies to requests are inspected for attendee information." 233 234 self._record(False) 235 236 def request(self): 237 238 "Record events sent for potential scheduling." 239 240 self._record(True) 241 242 # Handler registry. 243 244 handlers = [ 245 ("VEVENT", Event), 246 ] 247 248 # vim: tabstop=4 expandtab shiftwidth=4