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, from_organiser=True): 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(from_organiser) 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 # Otherwise, remove the given attendees and update the event. 150 151 if not cancel_entire_event and obj: 152 for attendee in given_attendees: 153 if attendees.has_key(attendee): 154 del attendees[attendee] 155 obj["ATTENDEE"] = attendees.items() 156 157 # Update the stored object with sequence information. 158 159 if obj: 160 obj["SEQUENCE"] = self.obj.get_items("SEQUENCE") or [] 161 obj["DTSTAMP"] = self.obj.get_items("DTSTAMP") or [] 162 163 # Update free/busy information. 164 165 if cancel_entire_event or self.user in given_attendees: 166 self.remove_event_from_freebusy() 167 168 # Set the complete event if not an additional occurrence. For any newly- 169 # indicated occurrence, use the received event details. 170 171 self.store.set_event(self.user, self.uid, self.recurrenceid, (obj or self.obj).to_node()) 172 173 # Perform any cancellation after recording the latest state of the 174 # event. 175 176 if cancel_entire_event: 177 self.store.cancel_event(self.user, self.uid, self.recurrenceid) 178 179 # Remove any associated request. 180 181 self.store.dequeue_request(self.user, self.uid, self.recurrenceid) 182 183 return True 184 185 class Event(PersonHandler): 186 187 "An event handler." 188 189 def add(self): 190 191 "Record the addition of a recurrence to an event." 192 193 self._add() 194 195 def cancel(self): 196 197 "Remove an event or a recurrence." 198 199 self._remove(True) 200 201 def counter(self): 202 203 "Counter-proposals are tentative and do not change events." 204 205 pass 206 207 def declinecounter(self): 208 209 "Declined counter-proposals are advisory and do not change events." 210 211 pass 212 213 def publish(self): 214 215 "Published events are recorded." 216 217 self._record(True) 218 219 def refresh(self): 220 221 "Requests to refresh events do not provide event information." 222 223 pass 224 225 def reply(self): 226 227 "Replies to requests are inspected for attendee information." 228 229 self._record(False) 230 231 def request(self): 232 233 "Record events sent for potential scheduling." 234 235 self._record(True) 236 237 # Handler registry. 238 239 handlers = [ 240 ("VEVENT", Event), 241 ] 242 243 # vim: tabstop=4 expandtab shiftwidth=4