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