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