1.1 --- a/imip_manager.py Sun Oct 26 23:39:11 2014 +0100
1.2 +++ b/imip_manager.py Mon Oct 27 16:37:31 2014 +0100
1.3 @@ -4,10 +4,14 @@
1.4
1.5 sys.path.append("/var/lib/imip-agent")
1.6
1.7 -from imiptools.content import format_datetime, get_datetime, get_item, \
1.8 - get_utc_datetime, get_values, parse_object, \
1.9 - to_timezone
1.10 +from imiptools import make_message, sendmail
1.11 +from imiptools.content import format_datetime, get_address, get_datetime, \
1.12 + get_item, get_items, get_periods, get_uri, \
1.13 + get_utc_datetime, get_value, get_values, \
1.14 + parse_object, to_part, to_timezone, \
1.15 + update_freebusy
1.16 from imiptools.period import have_conflict
1.17 +from vCalendar import to_node
1.18 import markup
1.19 import imip_store
1.20
1.21 @@ -65,14 +69,20 @@
1.22 "A simple manager application."
1.23
1.24 def __init__(self):
1.25 - self.store = imip_store.FileStore()
1.26 self.env = CGIEnvironment()
1.27 user = self.env.get_user()
1.28 - self.user = user and "mailto:%s" % user or None
1.29 + self.user = user and get_uri(user) or None
1.30 self.out = self.env.get_output()
1.31 self.page = markup.page()
1.32 self.encoding = "utf-8"
1.33
1.34 + self.store = imip_store.FileStore()
1.35 +
1.36 + try:
1.37 + self.publisher = imip_store.FilePublisher()
1.38 + except OSError:
1.39 + self.publisher = None
1.40 +
1.41 def new_page(self, title):
1.42 self.page.init(title=title, charset=self.encoding)
1.43
1.44 @@ -103,7 +113,7 @@
1.45
1.46 for request in requests:
1.47 self.page.li()
1.48 - self.page.a(request, href="%s/%s" % (self.env.get_url(), request))
1.49 + self.page.a(request, href="%s/%s" % (self.env.get_url().rstrip("/"), request))
1.50 self.page.li.close()
1.51
1.52 self.page.ul.close()
1.53 @@ -128,8 +138,44 @@
1.54 args = self.env.get_args()
1.55 show_form = False
1.56
1.57 + freebusy = self.store.get_freebusy(self.user)
1.58 +
1.59 + # When accepting, do so only on behalf of this user.
1.60 +
1.61 if args.has_key("accept"):
1.62 - pass
1.63 + organisers = map(get_address, get_values(request, "ORGANIZER"))
1.64 +
1.65 + for attendee, attendee_attr in get_items(request, "ATTENDEE"):
1.66 + if attendee == self.user:
1.67 + attendee_attr["PARTSTAT"] = "ACCEPTED"
1.68 + request["ATTENDEE"] = [(attendee, attendee_attr)]
1.69 +
1.70 + # Create a full calendar object and send it.
1.71 + # NOTE: Should support more than just events.
1.72 + # NOTE: Should parameterise the subject and body text.
1.73 +
1.74 + event = to_node({"VEVENT" : [(request, {})]})
1.75 + part = to_part("REPLY", [event])
1.76 + message = make_message([part], organisers, get_address(attendee), "Response to request", "Response to a calendar request")
1.77 + sendmail(get_address(attendee), organisers, message.as_string())
1.78 +
1.79 + # Remove the request from the list.
1.80 +
1.81 + requests = self.store.get_requests(self.user)
1.82 + if uid in requests:
1.83 + requests.remove(uid)
1.84 + self.store.set_requests(self.user, requests)
1.85 +
1.86 + # Update the free/busy information.
1.87 +
1.88 + periods = get_periods(request)
1.89 + update_freebusy(freebusy, attendee, periods, get_value(request, "TRANSP"), uid, self.store)
1.90 +
1.91 + if self.publisher:
1.92 + self.publisher.set_freebusy(attendee, freebusy)
1.93 +
1.94 + break
1.95 +
1.96 elif args.has_key("decline"):
1.97 pass
1.98 elif args.has_key("ignore"):
1.99 @@ -156,7 +202,6 @@
1.100
1.101 # Indicate whether there are conflicting events.
1.102
1.103 - freebusy = self.store.get_freebusy(self.user)
1.104 if freebusy:
1.105
1.106 # Obtain any time zone details from the suggested event.
1.107 @@ -166,10 +211,11 @@
1.108
1.109 # Show any conflicts.
1.110
1.111 - for start, end, uid in have_conflict(freebusy, [(dtstart, dtend)], True):
1.112 - start = format_datetime(to_timezone(get_datetime(start), tzid))
1.113 - end = format_datetime(to_timezone(get_datetime(end), tzid))
1.114 - self.page.p("Event conflicts with another from %s to %s." % (start, end))
1.115 + for start, end, found_uid in have_conflict(freebusy, [(dtstart, dtend)], True):
1.116 + if uid != found_uid:
1.117 + start = format_datetime(to_timezone(get_datetime(start), tzid))
1.118 + end = format_datetime(to_timezone(get_datetime(end), tzid))
1.119 + self.page.p("Event conflicts with another from %s to %s." % (start, end))
1.120
1.121 # Show a form if no action has just been taken.
1.122
2.1 --- a/imiptools/__init__.py Sun Oct 26 23:39:11 2014 +0100
2.2 +++ b/imiptools/__init__.py Mon Oct 27 16:37:31 2014 +0100
2.3 @@ -37,6 +37,19 @@
2.4 smtp.sendmail(sender, recipients, data)
2.5 smtp.quit()
2.6
2.7 +def make_message(parts, recipients, sender, subject, body_text):
2.8 + message = MIMEMultipart("mixed", _subparts=parts)
2.9 + message.preamble = body_text
2.10 + payload = message.get_payload()
2.11 + payload.insert(0, MIMEText(body_text))
2.12 +
2.13 + message["From"] = sender
2.14 + for recipient in recipients:
2.15 + message["To"] = recipient
2.16 + message["Subject"] = subject
2.17 +
2.18 + return message
2.19 +
2.20 # Processing of incoming messages.
2.21
2.22 def get_all_values(msg, key):
2.23 @@ -123,17 +136,7 @@
2.24
2.25 "Make a message from the given 'parts' for the given 'recipients'."
2.26
2.27 - message = MIMEMultipart("mixed", _subparts=parts)
2.28 - message.preamble = self.body_text
2.29 - payload = message.get_payload()
2.30 - payload.insert(0, MIMEText(self.body_text))
2.31 -
2.32 - message["From"] = self.sender
2.33 - for recipient in recipients:
2.34 - message["To"] = recipient
2.35 - message["Subject"] = self.subject
2.36 -
2.37 - return message
2.38 + return make_message(parts, recipients, self.sender, self.subject, self.body_text)
2.39
2.40 def wrap_message(self, msg, parts):
2.41
3.1 --- a/imiptools/content.py Sun Oct 26 23:39:11 2014 +0100
3.2 +++ b/imiptools/content.py Mon Oct 27 16:37:31 2014 +0100
3.3 @@ -192,34 +192,34 @@
3.4
3.5 return periods
3.6
3.7 -def update_freebusy(attendee, periods, transp, uid, store):
3.8 +def update_freebusy(freebusy, attendee, periods, transp, uid, store):
3.9
3.10 """
3.11 For the given 'attendee', update the free/busy details with the given
3.12 - 'periods', 'transp' setting and 'uid' in the 'store'. Where no conflict
3.13 - occurs, return the updated free/busy details; otherwise return None.
3.14 + 'periods', 'transp' setting and 'uid' in the 'store'.
3.15 """
3.16
3.17 - conflict = False
3.18 - freebusy = store.get_freebusy(attendee) or []
3.19 + remove_period(freebusy, uid)
3.20 +
3.21 + for start, end in periods:
3.22 + insert_period(freebusy, (start, end, uid))
3.23
3.24 - if freebusy:
3.25 - remove_period(freebusy, uid)
3.26 - conflict = have_conflict(freebusy, periods)
3.27 + if transp in (None, "OPAQUE"):
3.28 + store.set_freebusy(attendee, freebusy)
3.29
3.30 - # If the event can be scheduled, it is registered in the free/busy list.
3.31 +def can_schedule(freebusy, periods, uid):
3.32
3.33 - if not conflict:
3.34 -
3.35 - for start, end in periods:
3.36 - insert_period(freebusy, (start, end, uid))
3.37 + """
3.38 + Return whether the 'freebusy' list can accommodate the given 'periods'
3.39 + employing the specified 'uid'.
3.40 + """
3.41
3.42 - if transp in (None, "OPAQUE"):
3.43 - store.set_freebusy(attendee, freebusy)
3.44 + for conflict in have_conflict(freebusy, periods, True):
3.45 + start, end, found_uid = conflict
3.46 + if found_uid != uid:
3.47 + return False
3.48
3.49 - return freebusy
3.50 -
3.51 - return None
3.52 + return True
3.53
3.54 # Handler mechanism objects.
3.55
3.56 @@ -360,8 +360,11 @@
3.57 def get_periods(self):
3.58 return get_periods(self.details)
3.59
3.60 - def update_freebusy(self, attendee, periods):
3.61 - return update_freebusy(attendee, periods, self.get_value("TRANSP"), self.uid, self.store)
3.62 + def update_freebusy(self, freebusy, attendee, periods):
3.63 + return update_freebusy(freebusy, attendee, periods, self.get_value("TRANSP"), self.uid, self.store)
3.64 +
3.65 + def can_schedule(self, freebusy, periods):
3.66 + return can_schedule(freebusy, periods, self.uid)
3.67
3.68 def filter_by_recipients(self, values):
3.69 return self.recipients.intersection(map(get_address, values))
4.1 --- a/imiptools/handlers/resource.py Sun Oct 26 23:39:11 2014 +0100
4.2 +++ b/imiptools/handlers/resource.py Mon Oct 27 16:37:31 2014 +0100
4.3 @@ -74,21 +74,20 @@
4.4 # free/busy record and check for suitability.
4.5
4.6 periods = self.get_periods()
4.7 - freebusy = self.update_freebusy(attendee, periods)
4.8 - scheduled = freebusy is not None
4.9 + freebusy = self.store.get_freebusy(attendee) or []
4.10 + scheduled = self.can_schedule(freebusy, periods)
4.11
4.12 attendee_attr["PARTSTAT"] = scheduled and "ACCEPTED" or "DECLINED"
4.13 -
4.14 self.details["ATTENDEE"] = [(attendee, attendee_attr)]
4.15
4.16 - calendar.append(to_node(
4.17 - {"VEVENT" : [(self.details, {})]}
4.18 - ))
4.19 + event = to_node({"VEVENT" : [(self.details, {})]})
4.20 + calendar.append(event)
4.21 + self.store.set_event(attendee, self.uid, event)
4.22 +
4.23 + # Only update free/busy details if the event is scheduled.
4.24
4.25 if scheduled:
4.26 - self.store.set_event(attendee, self.uid, to_node(
4.27 - {"VEVENT" : [(self.details, {})]}
4.28 - ))
4.29 + self.update_freebusy(freebusy, attendee, periods)
4.30 if self.publisher:
4.31 self.publisher.set_freebusy(attendee, freebusy)
4.32