1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/imipweb/handler.py Wed Mar 25 18:23:37 2015 +0100
1.3 @@ -0,0 +1,176 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +Interaction with the mail system for the manager interface.
1.8 +
1.9 +Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>
1.10 +
1.11 +This program is free software; you can redistribute it and/or modify it under
1.12 +the terms of the GNU General Public License as published by the Free Software
1.13 +Foundation; either version 3 of the License, or (at your option) any later
1.14 +version.
1.15 +
1.16 +This program is distributed in the hope that it will be useful, but WITHOUT
1.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.19 +details.
1.20 +
1.21 +You should have received a copy of the GNU General Public License along with
1.22 +this program. If not, see <http://www.gnu.org/licenses/>.
1.23 +"""
1.24 +
1.25 +from imiptools.client import Client, update_attendees
1.26 +from imiptools.data import get_address, get_uri, get_window_end, make_freebusy, \
1.27 + to_part, uri_item, uri_items, uri_values
1.28 +from imiptools.dates import get_timestamp
1.29 +from imiptools.handlers import Handler
1.30 +from imiptools.period import update_freebusy
1.31 +
1.32 +class ManagerHandler(Client, Handler):
1.33 +
1.34 + """
1.35 + A content handler for use by the manager, as opposed to operating within the
1.36 + mail processing pipeline.
1.37 + """
1.38 +
1.39 + def __init__(self, obj, user, messenger):
1.40 + Handler.__init__(self, messenger=messenger)
1.41 + Client.__init__(self, user)
1.42 +
1.43 + self.set_object(obj)
1.44 +
1.45 + # Communication methods.
1.46 +
1.47 + def send_message(self, method, sender, for_organiser):
1.48 +
1.49 + """
1.50 + Create a full calendar object employing the given 'method', and send it
1.51 + to the appropriate recipients, also sending a copy to the 'sender'. The
1.52 + 'for_organiser' value indicates whether the organiser is sending this
1.53 + message.
1.54 + """
1.55 +
1.56 + parts = [self.obj.to_part(method)]
1.57 +
1.58 + # As organiser, send an invitation to attendees, excluding oneself if
1.59 + # also attending. The updated event will be saved by the outgoing
1.60 + # handler.
1.61 +
1.62 + organiser = get_uri(self.obj.get_value("ORGANIZER"))
1.63 + attendees = uri_values(self.obj.get_values("ATTENDEE"))
1.64 +
1.65 + if for_organiser:
1.66 + recipients = [get_address(attendee) for attendee in attendees if attendee != self.user]
1.67 + else:
1.68 + recipients = [get_address(organiser)]
1.69 +
1.70 + # Bundle free/busy information if appropriate.
1.71 +
1.72 + if self.is_sharing() and self.is_bundling():
1.73 +
1.74 + # Invent a unique identifier.
1.75 +
1.76 + utcnow = get_timestamp()
1.77 + uid = "imip-agent-%s-%s" % (utcnow, get_address(self.user))
1.78 +
1.79 + freebusy = self.store.get_freebusy(self.user)
1.80 +
1.81 + # Replace the non-updated free/busy details for this event with
1.82 + # newer details (since the outgoing handler updates this user's
1.83 + # free/busy details).
1.84 +
1.85 + update_freebusy(freebusy,
1.86 + self.obj.get_periods_for_freebusy(self.get_tzid(), self.get_window_end()),
1.87 + self.obj.get_value("TRANSP") or "OPAQUE",
1.88 + self.uid, self.recurrenceid,
1.89 + self.obj.get_value("SUMMARY"),
1.90 + organiser)
1.91 +
1.92 + user_attr = self.messenger and self.messenger.sender != get_address(self.user) and \
1.93 + {"SENT-BY" : get_uri(self.messenger.sender)} or {}
1.94 +
1.95 + parts.append(to_part("PUBLISH", [
1.96 + make_freebusy(freebusy, uid, self.user, user_attr)
1.97 + ]))
1.98 +
1.99 + message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender)
1.100 + self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender)
1.101 +
1.102 + # Action methods.
1.103 +
1.104 + def process_received_request(self, update=False):
1.105 +
1.106 + """
1.107 + Process the current request for the given 'user'. Return whether any
1.108 + action was taken.
1.109 +
1.110 + If 'update' is given, the sequence number will be incremented in order
1.111 + to override any previous response.
1.112 + """
1.113 +
1.114 + # Reply only on behalf of this user.
1.115 +
1.116 + for attendee, attendee_attr in uri_items(self.obj.get_items("ATTENDEE")):
1.117 +
1.118 + if attendee == self.user:
1.119 + if attendee_attr.has_key("RSVP"):
1.120 + del attendee_attr["RSVP"]
1.121 + if self.messenger and self.messenger.sender != get_address(attendee):
1.122 + attendee_attr["SENT-BY"] = get_uri(self.messenger.sender)
1.123 + self.obj["ATTENDEE"] = [(attendee, attendee_attr)]
1.124 +
1.125 + self.update_dtstamp()
1.126 + self.set_sequence(update)
1.127 +
1.128 + self.send_message("REPLY", get_address(attendee), for_organiser=False)
1.129 +
1.130 + return True
1.131 +
1.132 + return False
1.133 +
1.134 + def process_created_request(self, method, update=False, removed=None, added=None):
1.135 +
1.136 + """
1.137 + Process the current request for the given 'user', sending a created
1.138 + request of the given 'method' to attendees. Return whether any action
1.139 + was taken.
1.140 +
1.141 + If 'update' is given, the sequence number will be incremented in order
1.142 + to override any previous message.
1.143 +
1.144 + If 'removed' is specified, a list of participants to be removed is
1.145 + provided.
1.146 +
1.147 + If 'added' is specified, a list of participants to be added is provided.
1.148 + """
1.149 +
1.150 + organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))
1.151 +
1.152 + if self.messenger and self.messenger.sender != get_address(organiser):
1.153 + organiser_attr["SENT-BY"] = get_uri(self.messenger.sender)
1.154 +
1.155 + # Update the attendees in the event.
1.156 +
1.157 + to_cancel = update_attendees(self.obj, added, removed)
1.158 +
1.159 + self.update_dtstamp()
1.160 + self.set_sequence(update)
1.161 +
1.162 + self.send_message(method, get_address(organiser), for_organiser=True)
1.163 +
1.164 + # When cancelling, replace the attendees with those for whom the event
1.165 + # is now cancelled.
1.166 +
1.167 + if to_cancel:
1.168 + remaining = self.obj["ATTENDEE"]
1.169 + self.obj["ATTENDEE"] = to_cancel
1.170 + self.send_message("CANCEL", get_address(organiser), for_organiser=True)
1.171 +
1.172 + # Just in case more work is done with this event, the attendees are
1.173 + # now restored.
1.174 +
1.175 + self.obj["ATTENDEE"] = remaining
1.176 +
1.177 + return True
1.178 +
1.179 +# vim: tabstop=4 expandtab shiftwidth=4