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