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