1 #!/usr/bin/env python 2 3 """ 4 Handlers for a person for whom scheduling is performed. 5 """ 6 7 from email.mime.text import MIMEText 8 from imiptools.config import MANAGER_PATH, MANAGER_URL 9 from imiptools.content import Handler, to_part 10 from socket import gethostname 11 from vCalendar import to_node 12 13 def get_manager_url(): 14 url_base = MANAGER_URL or "http://%s/" % gethostname() 15 return "%s/%s" % (url_base.rstrip("/"), MANAGER_PATH.lstrip("/")) 16 17 class PersonHandler(Handler): 18 19 "Handling mechanisms specific to people." 20 21 def _record_and_deliver(self, objtype, from_organiser=True, queue=False): 22 23 oa = self.require_organiser_and_attendees(from_organiser) 24 if not oa: 25 return False 26 27 (organiser, organiser_attr), attendees = organiser_item, attendees = oa 28 29 # Validate the organiser or attendee, ignoring spoofed requests. 30 31 if not self.validate_identities(from_organiser and [organiser_item] or attendees.items()): 32 return False 33 34 # Process each attendee separately. 35 36 if from_organiser: 37 for attendee, attendee_attr in attendees.items(): 38 39 if not self.have_new_object(attendee, objtype): 40 continue 41 42 # Store the object and queue any request. 43 44 self.store.set_event(attendee, self.uid, to_node( 45 {objtype : [(self.details, {})]} 46 )) 47 48 if queue: 49 self.store.queue_request(attendee, self.uid) 50 51 # As organiser, update attendance. 52 53 else: 54 obj = self.get_object(organiser, objtype) 55 56 if obj and self.have_new_object(organiser, objtype, obj): 57 attendee_map = self.get_value_map("ATTENDEE") 58 59 for attendee, attendee_attr in attendees.items(): 60 61 # Update attendance in the loaded object. 62 63 attendee_map[attendee] = attendee_attr 64 65 # Set the new details and store the object. 66 67 obj["ATTENDEE"] = attendee_map.items() 68 69 self.store.set_event(organiser, self.uid, to_node( 70 {objtype : [(obj, {})]} 71 )) 72 73 return True 74 75 def reply(self): 76 77 "Wrap any valid message and pass it on to the recipient." 78 79 attendee = self.get_value("ATTENDEE") 80 if attendee: 81 return "REPLY", MIMEText("A reply has been received from %s." % attendee) 82 83 class Event(PersonHandler): 84 85 "An event handler." 86 87 def add(self): 88 89 # NOTE: Queue a suggested modification to any active event. 90 91 # The message is now wrapped and passed on to the recipient. 92 93 return "ADD", MIMEText("An addition to an event has been received.") 94 95 def cancel(self): 96 97 # NOTE: Queue a suggested modification to any active event. 98 99 # The message is now wrapped and passed on to the recipient. 100 101 return "CANCEL", MIMEText("A cancellation has been received.") 102 103 def counter(self): 104 105 # NOTE: Queue a suggested modification to any active event. 106 107 # The message is now wrapped and passed on to the recipient. 108 109 return "COUNTER", MIMEText("A counter proposal has been received.") 110 111 def declinecounter(self): 112 113 # NOTE: Queue a suggested modification to any active event. 114 115 # The message is now wrapped and passed on to the recipient. 116 117 return "DECLINECOUNTER", MIMEText("A declining counter proposal has been received.") 118 119 def publish(self): 120 121 # NOTE: Register details of any relevant event. 122 123 # The message is now wrapped and passed on to the recipient. 124 125 return "PUBLISH", MIMEText("Details of an event have been received.") 126 127 def refresh(self): 128 129 # NOTE: Update details of any active event. 130 131 # The message is now wrapped and passed on to the recipient. 132 133 return "REFRESH", MIMEText("An event update has been received.") 134 135 def reply(self): 136 137 "Record replies and notify the recipient." 138 139 self._record_and_deliver("VEVENT", from_organiser=False, queue=False) 140 return PersonHandler.reply(self) 141 142 def request(self): 143 144 "Hold requests and notify the recipient." 145 146 self._record_and_deliver("VEVENT", from_organiser=True, queue=True) 147 148 # The message is now wrapped and passed on to the recipient. 149 150 url = "%s/%s" % (get_manager_url().rstrip("/"), self.uid) 151 return "REQUEST", MIMEText("A request has been queued and can be viewed here: %s" % url) 152 153 class Freebusy(PersonHandler): 154 155 "A free/busy handler." 156 157 def publish(self): 158 159 # NOTE: Register free/busy information. 160 161 # The message is now wrapped and passed on to the recipient. 162 163 return "PUBLISH", MIMEText("Details of a contact's availability have been received.") 164 165 def reply(self): 166 167 "Record replies and notify the recipient." 168 169 self._record_and_deliver("VFREEBUSY", from_organiser=False, queue=False) 170 return PersonHandler.reply(self) 171 172 def request(self): 173 174 """ 175 Respond to a request by preparing a reply containing free/busy 176 information for each indicated attendee. 177 """ 178 179 # NOTE: This is currently the same as the resource handler but should be 180 # NOTE: subject to policy/preferences. 181 182 oa = self.require_organiser_and_attendees() 183 if not oa: 184 return None 185 186 (organiser, organiser_attr), attendees = organiser_item, attendees = oa 187 188 # Validate the organiser, ignoring spoofed requests. 189 190 if not self.validate_identities([organiser_item]): 191 return None 192 193 # Construct an appropriate fragment. 194 195 calendar = [] 196 cwrite = calendar.append 197 198 # Get the details for each attendee. 199 200 for attendee, attendee_attr in attendees.items(): 201 freebusy = self.store.get_freebusy(attendee) 202 203 record = [] 204 rwrite = record.append 205 206 rwrite(("ORGANIZER", organiser_attr, organiser)) 207 rwrite(("ATTENDEE", attendee_attr, attendee)) 208 rwrite(("UID", {}, self.uid)) 209 210 if freebusy: 211 for start, end, uid in freebusy: 212 rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, [start, end])) 213 214 cwrite(("VFREEBUSY", {}, record)) 215 216 # Return the reply. 217 218 return "REPLY", to_part("REPLY", calendar) 219 220 class Journal(PersonHandler): 221 222 "A journal entry handler." 223 224 def add(self): 225 226 # NOTE: Queue a suggested modification to any active entry. 227 228 # The message is now wrapped and passed on to the recipient. 229 230 return "ADD", MIMEText("An addition to a journal entry has been received.") 231 232 def cancel(self): 233 234 # NOTE: Queue a suggested modification to any active entry. 235 236 # The message is now wrapped and passed on to the recipient. 237 238 return "CANCEL", MIMEText("A cancellation has been received.") 239 240 def publish(self): 241 242 # NOTE: Register details of any relevant entry. 243 244 # The message is now wrapped and passed on to the recipient. 245 246 return "PUBLISH", MIMEText("Details of a journal entry have been received.") 247 248 class Todo(PersonHandler): 249 250 "A to-do item handler." 251 252 def add(self): 253 254 # NOTE: Queue a suggested modification to any active item. 255 256 # The message is now wrapped and passed on to the recipient. 257 258 return "ADD", MIMEText("An addition to an item has been received.") 259 260 def cancel(self): 261 262 # NOTE: Queue a suggested modification to any active item. 263 264 # The message is now wrapped and passed on to the recipient. 265 266 return "CANCEL", MIMEText("A cancellation has been received.") 267 268 def counter(self): 269 270 # NOTE: Queue a suggested modification to any active item. 271 272 # The message is now wrapped and passed on to the recipient. 273 274 return "COUNTER", MIMEText("A counter proposal has been received.") 275 276 def declinecounter(self): 277 278 # NOTE: Queue a suggested modification to any active item. 279 280 # The message is now wrapped and passed on to the recipient. 281 282 return "DECLINECOUNTER", MIMEText("A declining counter proposal has been received.") 283 284 def publish(self): 285 286 # NOTE: Register details of any relevant item. 287 288 # The message is now wrapped and passed on to the recipient. 289 290 return "PUBLISH", MIMEText("Details of an item have been received.") 291 292 def refresh(self): 293 294 # NOTE: Update details of any active item. 295 296 # The message is now wrapped and passed on to the recipient. 297 298 return "REFRESH", MIMEText("An item update has been received.") 299 300 def reply(self): 301 302 "Record replies and notify the recipient." 303 304 self._record_and_deliver("VTODO", from_organiser=False, queue=False) 305 return PersonHandler.reply(self) 306 307 def request(self): 308 309 "Hold requests and notify the recipient." 310 311 self._record_and_deliver("VTODO", from_organiser=True, queue=True) 312 313 # The message is now wrapped and passed on to the recipient. 314 315 return "REQUEST", MIMEText("A request has been queued.") 316 317 # Handler registry. 318 319 handlers = [ 320 ("VFREEBUSY", Freebusy), 321 ("VEVENT", Event), 322 ("VTODO", Todo), 323 ("VJOURNAL", Journal), 324 ] 325 326 # vim: tabstop=4 expandtab shiftwidth=4