# HG changeset patch # User Paul Boddie # Date 1414424251 -3600 # Node ID c280c6d58da28471a524a1bff24c15f17347d317 # Parent 77f2ad0d6f88c56f024270c7a5c8d2b6a2fbd8ec Made it possible for the manager to reply to received requests, currently only supporting acceptance. diff -r 77f2ad0d6f88 -r c280c6d58da2 imip_manager.py --- a/imip_manager.py Sun Oct 26 23:39:11 2014 +0100 +++ b/imip_manager.py Mon Oct 27 16:37:31 2014 +0100 @@ -4,10 +4,14 @@ sys.path.append("/var/lib/imip-agent") -from imiptools.content import format_datetime, get_datetime, get_item, \ - get_utc_datetime, get_values, parse_object, \ - to_timezone +from imiptools import make_message, sendmail +from imiptools.content import format_datetime, get_address, get_datetime, \ + get_item, get_items, get_periods, get_uri, \ + get_utc_datetime, get_value, get_values, \ + parse_object, to_part, to_timezone, \ + update_freebusy from imiptools.period import have_conflict +from vCalendar import to_node import markup import imip_store @@ -65,14 +69,20 @@ "A simple manager application." def __init__(self): - self.store = imip_store.FileStore() self.env = CGIEnvironment() user = self.env.get_user() - self.user = user and "mailto:%s" % user or None + self.user = user and get_uri(user) or None self.out = self.env.get_output() self.page = markup.page() self.encoding = "utf-8" + self.store = imip_store.FileStore() + + try: + self.publisher = imip_store.FilePublisher() + except OSError: + self.publisher = None + def new_page(self, title): self.page.init(title=title, charset=self.encoding) @@ -103,7 +113,7 @@ for request in requests: self.page.li() - self.page.a(request, href="%s/%s" % (self.env.get_url(), request)) + self.page.a(request, href="%s/%s" % (self.env.get_url().rstrip("/"), request)) self.page.li.close() self.page.ul.close() @@ -128,8 +138,44 @@ args = self.env.get_args() show_form = False + freebusy = self.store.get_freebusy(self.user) + + # When accepting, do so only on behalf of this user. + if args.has_key("accept"): - pass + organisers = map(get_address, get_values(request, "ORGANIZER")) + + for attendee, attendee_attr in get_items(request, "ATTENDEE"): + if attendee == self.user: + attendee_attr["PARTSTAT"] = "ACCEPTED" + request["ATTENDEE"] = [(attendee, attendee_attr)] + + # Create a full calendar object and send it. + # NOTE: Should support more than just events. + # NOTE: Should parameterise the subject and body text. + + event = to_node({"VEVENT" : [(request, {})]}) + part = to_part("REPLY", [event]) + message = make_message([part], organisers, get_address(attendee), "Response to request", "Response to a calendar request") + sendmail(get_address(attendee), organisers, message.as_string()) + + # Remove the request from the list. + + requests = self.store.get_requests(self.user) + if uid in requests: + requests.remove(uid) + self.store.set_requests(self.user, requests) + + # Update the free/busy information. + + periods = get_periods(request) + update_freebusy(freebusy, attendee, periods, get_value(request, "TRANSP"), uid, self.store) + + if self.publisher: + self.publisher.set_freebusy(attendee, freebusy) + + break + elif args.has_key("decline"): pass elif args.has_key("ignore"): @@ -156,7 +202,6 @@ # Indicate whether there are conflicting events. - freebusy = self.store.get_freebusy(self.user) if freebusy: # Obtain any time zone details from the suggested event. @@ -166,10 +211,11 @@ # Show any conflicts. - for start, end, uid in have_conflict(freebusy, [(dtstart, dtend)], True): - start = format_datetime(to_timezone(get_datetime(start), tzid)) - end = format_datetime(to_timezone(get_datetime(end), tzid)) - self.page.p("Event conflicts with another from %s to %s." % (start, end)) + for start, end, found_uid in have_conflict(freebusy, [(dtstart, dtend)], True): + if uid != found_uid: + start = format_datetime(to_timezone(get_datetime(start), tzid)) + end = format_datetime(to_timezone(get_datetime(end), tzid)) + self.page.p("Event conflicts with another from %s to %s." % (start, end)) # Show a form if no action has just been taken. diff -r 77f2ad0d6f88 -r c280c6d58da2 imiptools/__init__.py --- a/imiptools/__init__.py Sun Oct 26 23:39:11 2014 +0100 +++ b/imiptools/__init__.py Mon Oct 27 16:37:31 2014 +0100 @@ -37,6 +37,19 @@ smtp.sendmail(sender, recipients, data) smtp.quit() +def make_message(parts, recipients, sender, subject, body_text): + message = MIMEMultipart("mixed", _subparts=parts) + message.preamble = body_text + payload = message.get_payload() + payload.insert(0, MIMEText(body_text)) + + message["From"] = sender + for recipient in recipients: + message["To"] = recipient + message["Subject"] = subject + + return message + # Processing of incoming messages. def get_all_values(msg, key): @@ -123,17 +136,7 @@ "Make a message from the given 'parts' for the given 'recipients'." - message = MIMEMultipart("mixed", _subparts=parts) - message.preamble = self.body_text - payload = message.get_payload() - payload.insert(0, MIMEText(self.body_text)) - - message["From"] = self.sender - for recipient in recipients: - message["To"] = recipient - message["Subject"] = self.subject - - return message + return make_message(parts, recipients, self.sender, self.subject, self.body_text) def wrap_message(self, msg, parts): diff -r 77f2ad0d6f88 -r c280c6d58da2 imiptools/content.py --- a/imiptools/content.py Sun Oct 26 23:39:11 2014 +0100 +++ b/imiptools/content.py Mon Oct 27 16:37:31 2014 +0100 @@ -192,34 +192,34 @@ return periods -def update_freebusy(attendee, periods, transp, uid, store): +def update_freebusy(freebusy, attendee, periods, transp, uid, store): """ For the given 'attendee', update the free/busy details with the given - 'periods', 'transp' setting and 'uid' in the 'store'. Where no conflict - occurs, return the updated free/busy details; otherwise return None. + 'periods', 'transp' setting and 'uid' in the 'store'. """ - conflict = False - freebusy = store.get_freebusy(attendee) or [] + remove_period(freebusy, uid) + + for start, end in periods: + insert_period(freebusy, (start, end, uid)) - if freebusy: - remove_period(freebusy, uid) - conflict = have_conflict(freebusy, periods) + if transp in (None, "OPAQUE"): + store.set_freebusy(attendee, freebusy) - # If the event can be scheduled, it is registered in the free/busy list. +def can_schedule(freebusy, periods, uid): - if not conflict: - - for start, end in periods: - insert_period(freebusy, (start, end, uid)) + """ + Return whether the 'freebusy' list can accommodate the given 'periods' + employing the specified 'uid'. + """ - if transp in (None, "OPAQUE"): - store.set_freebusy(attendee, freebusy) + for conflict in have_conflict(freebusy, periods, True): + start, end, found_uid = conflict + if found_uid != uid: + return False - return freebusy - - return None + return True # Handler mechanism objects. @@ -360,8 +360,11 @@ def get_periods(self): return get_periods(self.details) - def update_freebusy(self, attendee, periods): - return update_freebusy(attendee, periods, self.get_value("TRANSP"), self.uid, self.store) + def update_freebusy(self, freebusy, attendee, periods): + return update_freebusy(freebusy, attendee, periods, self.get_value("TRANSP"), self.uid, self.store) + + def can_schedule(self, freebusy, periods): + return can_schedule(freebusy, periods, self.uid) def filter_by_recipients(self, values): return self.recipients.intersection(map(get_address, values)) diff -r 77f2ad0d6f88 -r c280c6d58da2 imiptools/handlers/resource.py --- a/imiptools/handlers/resource.py Sun Oct 26 23:39:11 2014 +0100 +++ b/imiptools/handlers/resource.py Mon Oct 27 16:37:31 2014 +0100 @@ -74,21 +74,20 @@ # free/busy record and check for suitability. periods = self.get_periods() - freebusy = self.update_freebusy(attendee, periods) - scheduled = freebusy is not None + freebusy = self.store.get_freebusy(attendee) or [] + scheduled = self.can_schedule(freebusy, periods) attendee_attr["PARTSTAT"] = scheduled and "ACCEPTED" or "DECLINED" - self.details["ATTENDEE"] = [(attendee, attendee_attr)] - calendar.append(to_node( - {"VEVENT" : [(self.details, {})]} - )) + event = to_node({"VEVENT" : [(self.details, {})]}) + calendar.append(event) + self.store.set_event(attendee, self.uid, event) + + # Only update free/busy details if the event is scheduled. if scheduled: - self.store.set_event(attendee, self.uid, to_node( - {"VEVENT" : [(self.details, {})]} - )) + self.update_freebusy(freebusy, attendee, periods) if self.publisher: self.publisher.set_freebusy(attendee, freebusy)