1 #!/usr/bin/env python 2 3 """ 4 Interaction with the mail system for the manager interface. 5 6 Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from imiptools.client import Client, update_attendees 23 from imiptools.data import get_address, get_uri, get_window_end, make_freebusy, \ 24 to_part, uri_item, uri_items, uri_values 25 from imiptools.dates import get_timestamp 26 from imiptools.handlers import Handler 27 from imiptools.period import update_freebusy 28 29 class ManagerHandler(Client, Handler): 30 31 """ 32 A content handler for use by the manager, as opposed to operating within the 33 mail processing pipeline. 34 """ 35 36 def __init__(self, obj, user, messenger): 37 Handler.__init__(self, messenger=messenger) 38 Client.__init__(self, user) 39 40 self.set_object(obj) 41 42 # Communication methods. 43 44 def send_message(self, method, sender, for_organiser): 45 46 """ 47 Create a full calendar object employing the given 'method', and send it 48 to the appropriate recipients, also sending a copy to the 'sender'. The 49 'for_organiser' value indicates whether the organiser is sending this 50 message. 51 """ 52 53 parts = [self.obj.to_part(method)] 54 55 # As organiser, send an invitation to attendees, excluding oneself if 56 # also attending. The updated event will be saved by the outgoing 57 # handler. 58 59 organiser = get_uri(self.obj.get_value("ORGANIZER")) 60 attendees = uri_values(self.obj.get_values("ATTENDEE")) 61 62 if for_organiser: 63 recipients = [get_address(attendee) for attendee in attendees if attendee != self.user] 64 else: 65 recipients = [get_address(organiser)] 66 67 # Bundle free/busy information if appropriate. 68 69 if self.is_sharing() and self.is_bundling(): 70 71 # Invent a unique identifier. 72 73 utcnow = get_timestamp() 74 uid = "imip-agent-%s-%s" % (utcnow, get_address(self.user)) 75 76 freebusy = self.store.get_freebusy(self.user) 77 78 # Replace the non-updated free/busy details for this event with 79 # newer details (since the outgoing handler updates this user's 80 # free/busy details). 81 82 update_freebusy(freebusy, 83 self.obj.get_periods_for_freebusy(self.get_tzid(), self.get_window_end()), 84 self.obj.get_value("TRANSP") or "OPAQUE", 85 self.uid, self.recurrenceid, 86 self.obj.get_value("SUMMARY"), 87 organiser) 88 89 user_attr = self.messenger and self.messenger.sender != get_address(self.user) and \ 90 {"SENT-BY" : get_uri(self.messenger.sender)} or {} 91 92 parts.append(to_part("PUBLISH", [ 93 make_freebusy(freebusy, uid, self.user, user_attr) 94 ])) 95 96 # Explicitly specify the outgoing BCC recipient since we are sending as 97 # the generic calendar user. 98 99 message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender) 100 self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender) 101 102 # Action methods. 103 104 def process_received_request(self, update=False): 105 106 """ 107 Process the current request for the given 'user'. Return whether any 108 action was taken. 109 110 If 'update' is given, the sequence number will be incremented in order 111 to override any previous response. 112 """ 113 114 # Reply only on behalf of this user. 115 116 for attendee, attendee_attr in uri_items(self.obj.get_items("ATTENDEE")): 117 118 if attendee == self.user: 119 if attendee_attr.has_key("RSVP"): 120 del attendee_attr["RSVP"] 121 if self.messenger and self.messenger.sender != get_address(attendee): 122 attendee_attr["SENT-BY"] = get_uri(self.messenger.sender) 123 self.obj["ATTENDEE"] = [(attendee, attendee_attr)] 124 125 self.update_dtstamp() 126 self.set_sequence(update) 127 128 self.send_message("REPLY", get_address(attendee), for_organiser=False) 129 130 return True 131 132 return False 133 134 def process_created_request(self, method, update=False, removed=None, added=None): 135 136 """ 137 Process the current request for the given 'user', sending a created 138 request of the given 'method' to attendees. Return whether any action 139 was taken. 140 141 If 'update' is given, the sequence number will be incremented in order 142 to override any previous message. 143 144 If 'removed' is specified, a list of participants to be removed is 145 provided. 146 147 If 'added' is specified, a list of participants to be added is provided. 148 """ 149 150 organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER")) 151 152 if self.messenger and self.messenger.sender != get_address(organiser): 153 organiser_attr["SENT-BY"] = get_uri(self.messenger.sender) 154 155 # Update the attendees in the event. 156 157 to_cancel = update_attendees(self.obj, added, removed) 158 159 self.update_dtstamp() 160 self.set_sequence(update) 161 162 self.send_message(method, get_address(organiser), for_organiser=True) 163 164 # When cancelling, replace the attendees with those for whom the event 165 # is now cancelled. 166 167 if to_cancel: 168 remaining = self.obj["ATTENDEE"] 169 self.obj["ATTENDEE"] = to_cancel 170 self.send_message("CANCEL", get_address(organiser), for_organiser=True) 171 172 # Just in case more work is done with this event, the attendees are 173 # now restored. 174 175 self.obj["ATTENDEE"] = remaining 176 177 return True 178 179 # vim: tabstop=4 expandtab shiftwidth=4