1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/imipweb/client.py Sat Jul 25 23:18:58 2015 +0200
1.3 @@ -0,0 +1,164 @@
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 ClientForObject
1.26 +from imiptools.data import get_address, get_uri, make_freebusy, \
1.27 + to_part, uri_item, uri_values
1.28 +from imiptools.dates import format_datetime, get_timestamp
1.29 +
1.30 +class ManagerClient(ClientForObject):
1.31 +
1.32 + """
1.33 + A content client for use by the manager, as opposed to operating within the
1.34 + mail processing pipeline.
1.35 + """
1.36 +
1.37 + # Communication methods.
1.38 +
1.39 + def send_message(self, method, sender, from_organiser, parts=None):
1.40 +
1.41 + """
1.42 + Create a full calendar object employing the given 'method', and send it
1.43 + to the appropriate recipients, also sending a copy to the 'sender'. The
1.44 + 'from_organiser' value indicates whether the organiser is sending this
1.45 + message.
1.46 + """
1.47 +
1.48 + parts = parts or [self.obj.to_part(method)]
1.49 +
1.50 + # As organiser, send an invitation to attendees, excluding oneself if
1.51 + # also attending. The updated event will be saved by the outgoing
1.52 + # handler.
1.53 +
1.54 + organiser = get_uri(self.obj.get_value("ORGANIZER"))
1.55 + attendees = uri_values(self.obj.get_values("ATTENDEE"))
1.56 +
1.57 + if from_organiser:
1.58 + recipients = [get_address(attendee) for attendee in attendees if attendee != self.user]
1.59 + else:
1.60 + recipients = [get_address(organiser)]
1.61 +
1.62 + # Bundle free/busy information if appropriate.
1.63 +
1.64 + if self.is_sharing() and self.is_bundling():
1.65 +
1.66 + # Invent a unique identifier.
1.67 +
1.68 + utcnow = get_timestamp()
1.69 + uid = "imip-agent-%s-%s" % (utcnow, get_address(self.user))
1.70 +
1.71 + freebusy = self.store.get_freebusy(self.user)
1.72 +
1.73 + # Since the outgoing handler updates this user's free/busy details,
1.74 + # the stored details will probably not have the updated details at
1.75 + # this point, so we update our copy for serialisation as the bundled
1.76 + # free/busy object.
1.77 +
1.78 + self.update_freebusy(freebusy,
1.79 + self.obj.get_periods(self.get_tzid(), self.get_window_end()))
1.80 +
1.81 + user_attr = {}
1.82 + self.update_sender(user_attr)
1.83 +
1.84 + parts.append(to_part("PUBLISH", [
1.85 + make_freebusy(freebusy, uid, self.user, user_attr)
1.86 + ]))
1.87 +
1.88 + # Explicitly specify the outgoing BCC recipient since we are sending as
1.89 + # the generic calendar user.
1.90 +
1.91 + message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender)
1.92 + self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender)
1.93 +
1.94 + # Action methods.
1.95 +
1.96 + def process_received_request(self):
1.97 +
1.98 + """
1.99 + Process the current request for the current user. Return whether any
1.100 + action was taken.
1.101 + """
1.102 +
1.103 + # Reply only on behalf of this user.
1.104 +
1.105 + attendee_attr = self.update_participation(self.obj)
1.106 +
1.107 + if not attendee_attr:
1.108 + return False
1.109 +
1.110 + self.obj["ATTENDEE"] = [(self.user, attendee_attr)]
1.111 + self.update_dtstamp()
1.112 + self.set_sequence(False)
1.113 + self.send_message("REPLY", get_address(self.user), from_organiser=False)
1.114 + return True
1.115 +
1.116 + def process_created_request(self, method, to_cancel=None, to_unschedule=None):
1.117 +
1.118 + """
1.119 + Process the current request, sending a created request of the given
1.120 + 'method' to attendees. Return whether any action was taken.
1.121 +
1.122 + If 'to_cancel' is specified, a list of participants to be sent cancel
1.123 + messages is provided.
1.124 + """
1.125 +
1.126 + # Here, the organiser should be the current user.
1.127 +
1.128 + organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))
1.129 +
1.130 + self.update_sender(organiser_attr)
1.131 + self.update_dtstamp()
1.132 + self.set_sequence(True)
1.133 +
1.134 + parts = [self.obj.to_part(method)]
1.135 +
1.136 + # Add message parts with cancelled occurrence information.
1.137 + # NOTE: This could probably be merged with the updated event message.
1.138 +
1.139 + if to_unschedule:
1.140 + obj = self.obj.copy()
1.141 + obj.remove_all(["RRULE", "RDATE", "DTSTART", "DTEND", "DURATION"])
1.142 +
1.143 + for p in to_unschedule:
1.144 + if not p.origin:
1.145 + continue
1.146 + obj["RECURRENCE-ID"] = [(format_datetime(p.get_start()), {})]
1.147 + parts.append(obj.to_part("CANCEL"))
1.148 +
1.149 + # Send the updated event, along with a cancellation for each of the
1.150 + # unscheduled occurrences.
1.151 +
1.152 + self.send_message("CANCEL", get_address(organiser), from_organiser=True, parts=parts)
1.153 +
1.154 + # When cancelling, replace the attendees with those for whom the event
1.155 + # is now cancelled.
1.156 +
1.157 + if to_cancel:
1.158 + obj = self.obj.copy()
1.159 + obj["ATTENDEE"] = to_cancel
1.160 +
1.161 + # Send a cancellation to all uninvited attendees.
1.162 +
1.163 + self.send_message("CANCEL", get_address(organiser), from_organiser=True)
1.164 +
1.165 + return True
1.166 +
1.167 +# vim: tabstop=4 expandtab shiftwidth=4