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 attendee_map = uri_dict(self.get_value_map("ATTENDEE")) 62 63 for attendee, attendee_attr in attendees.items(): 64 65 # Update attendance in the loaded object. 66 67 attendee_map[attendee] = attendee_attr 68 69 # Set the new details and store the object. 70 71 obj["ATTENDEE"] = attendee_map.items() 72 73 self.store.set_event(organiser, self.uid, to_node( 74 {objtype : [(obj, {})]} 75 )) 76 77 return True 78 79 def _record_freebusy(self, from_organiser=True): 80 81 "Record free/busy information for the received information." 82 83 freebusy = [] 84 85 for value in self.get_values("FREEBUSY") or []: 86 if not isinstance(value, list): 87 value = [value] 88 for v in value: 89 try: 90 start, end = v.split("/", 1) 91 freebusy.append((start, end)) 92 except ValueError: 93 pass 94 95 for sender, sender_attr in uri_items(self.get_items(from_organiser and "ORGANIZER" or "ATTENDEE")): 96 for recipient in self.recipients: 97 self.store.set_freebusy_for_other(get_uri(recipient), freebusy, sender) 98 99 def reply(self): 100 101 "Wrap any valid message and pass it on to the recipient." 102 103 attendee = get_address(self.get_value("ATTENDEE")) 104 if attendee: 105 return "REPLY", MIMEText("A reply has been received from %s." % attendee) 106 107 class Event(PersonHandler): 108 109 "An event handler." 110 111 def add(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 "ADD", MIMEText("An addition to an event has been received.") 118 119 def cancel(self): 120 121 # NOTE: Queue a suggested modification to any active event. 122 123 # The message is now wrapped and passed on to the recipient. 124 125 return "CANCEL", MIMEText("A cancellation has been received.") 126 127 def counter(self): 128 129 # NOTE: Queue a suggested modification to any active event. 130 131 # The message is now wrapped and passed on to the recipient. 132 133 return "COUNTER", MIMEText("A counter proposal has been received.") 134 135 def declinecounter(self): 136 137 # NOTE: Queue a suggested modification to any active event. 138 139 # The message is now wrapped and passed on to the recipient. 140 141 return "DECLINECOUNTER", MIMEText("A declining counter proposal has been received.") 142 143 def publish(self): 144 145 # NOTE: Register details of any relevant event. 146 147 # The message is now wrapped and passed on to the recipient. 148 149 return "PUBLISH", MIMEText("Details of an event have been received.") 150 151 def refresh(self): 152 153 # NOTE: Update details of any active event. 154 155 # The message is now wrapped and passed on to the recipient. 156 157 return "REFRESH", MIMEText("An event update has been received.") 158 159 def reply(self): 160 161 "Record replies and notify the recipient." 162 163 self._record_and_deliver("VEVENT", from_organiser=False, queue=False) 164 return PersonHandler.reply(self) 165 166 def request(self): 167 168 "Hold requests and notify the recipient." 169 170 self._record_and_deliver("VEVENT", from_organiser=True, queue=True) 171 172 # The message is now wrapped and passed on to the recipient. 173 174 url = "%s/%s" % (get_manager_url().rstrip("/"), self.uid) 175 return "REQUEST", MIMEText("A request has been queued and can be viewed here: %s" % url) 176 177 class Freebusy(PersonHandler, CommonFreebusy): 178 179 "A free/busy handler." 180 181 def publish(self): 182 183 "Register free/busy information." 184 185 self._record_freebusy(from_organiser=True) 186 187 # The message is now wrapped and passed on to the recipient. 188 189 return "PUBLISH", MIMEText("Details of a contact's availability have been received.") 190 191 def reply(self): 192 193 "Record replies and notify the recipient." 194 195 self._record_freebusy(from_organiser=False) 196 return PersonHandler.reply(self) 197 198 def request(self): 199 200 """ 201 Respond to a request by preparing a reply containing free/busy 202 information for each indicated attendee. 203 """ 204 205 # NOTE: This should be subject to policy/preferences. 206 207 return CommonFreebusy.request(self) 208 209 class Journal(PersonHandler): 210 211 "A journal entry handler." 212 213 def add(self): 214 215 # NOTE: Queue a suggested modification to any active entry. 216 217 # The message is now wrapped and passed on to the recipient. 218 219 return "ADD", MIMEText("An addition to a journal entry has been received.") 220 221 def cancel(self): 222 223 # NOTE: Queue a suggested modification to any active entry. 224 225 # The message is now wrapped and passed on to the recipient. 226 227 return "CANCEL", MIMEText("A cancellation has been received.") 228 229 def publish(self): 230 231 # NOTE: Register details of any relevant entry. 232 233 # The message is now wrapped and passed on to the recipient. 234 235 return "PUBLISH", MIMEText("Details of a journal entry have been received.") 236 237 class Todo(PersonHandler): 238 239 "A to-do item handler." 240 241 def add(self): 242 243 # NOTE: Queue a suggested modification to any active item. 244 245 # The message is now wrapped and passed on to the recipient. 246 247 return "ADD", MIMEText("An addition to an item has been received.") 248 249 def cancel(self): 250 251 # NOTE: Queue a suggested modification to any active item. 252 253 # The message is now wrapped and passed on to the recipient. 254 255 return "CANCEL", MIMEText("A cancellation has been received.") 256 257 def counter(self): 258 259 # NOTE: Queue a suggested modification to any active item. 260 261 # The message is now wrapped and passed on to the recipient. 262 263 return "COUNTER", MIMEText("A counter proposal has been received.") 264 265 def declinecounter(self): 266 267 # NOTE: Queue a suggested modification to any active item. 268 269 # The message is now wrapped and passed on to the recipient. 270 271 return "DECLINECOUNTER", MIMEText("A declining counter proposal has been received.") 272 273 def publish(self): 274 275 # NOTE: Register details of any relevant item. 276 277 # The message is now wrapped and passed on to the recipient. 278 279 return "PUBLISH", MIMEText("Details of an item have been received.") 280 281 def refresh(self): 282 283 # NOTE: Update details of any active item. 284 285 # The message is now wrapped and passed on to the recipient. 286 287 return "REFRESH", MIMEText("An item update has been received.") 288 289 def reply(self): 290 291 "Record replies and notify the recipient." 292 293 self._record_and_deliver("VTODO", from_organiser=False, queue=False) 294 return PersonHandler.reply(self) 295 296 def request(self): 297 298 "Hold requests and notify the recipient." 299 300 self._record_and_deliver("VTODO", from_organiser=True, queue=True) 301 302 # The message is now wrapped and passed on to the recipient. 303 304 return "REQUEST", MIMEText("A request has been queued.") 305 306 # Handler registry. 307 308 handlers = [ 309 ("VFREEBUSY", Freebusy), 310 ("VEVENT", Event), 311 ("VTODO", Todo), 312 ("VJOURNAL", Journal), 313 ] 314 315 # vim: tabstop=4 expandtab shiftwidth=4