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