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 message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender) 97 self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender) 98 99 # Action methods. 100 101 def process_received_request(self, update=False): 102 103 """ 104 Process the current request for the given 'user'. Return whether any 105 action was taken. 106 107 If 'update' is given, the sequence number will be incremented in order 108 to override any previous response. 109 """ 110 111 # Reply only on behalf of this user. 112 113 for attendee, attendee_attr in uri_items(self.obj.get_items("ATTENDEE")): 114 115 if attendee == self.user: 116 if attendee_attr.has_key("RSVP"): 117 del attendee_attr["RSVP"] 118 if self.messenger and self.messenger.sender != get_address(attendee): 119 attendee_attr["SENT-BY"] = get_uri(self.messenger.sender) 120 self.obj["ATTENDEE"] = [(attendee, attendee_attr)] 121 122 self.update_dtstamp() 123 self.set_sequence(update) 124 125 self.send_message("REPLY", get_address(attendee), for_organiser=False) 126 127 return True 128 129 return False 130 131 def process_created_request(self, method, update=False, removed=None, added=None): 132 133 """ 134 Process the current request for the given 'user', sending a created 135 request of the given 'method' to attendees. Return whether any action 136 was taken. 137 138 If 'update' is given, the sequence number will be incremented in order 139 to override any previous message. 140 141 If 'removed' is specified, a list of participants to be removed is 142 provided. 143 144 If 'added' is specified, a list of participants to be added is provided. 145 """ 146 147 organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER")) 148 149 if self.messenger and self.messenger.sender != get_address(organiser): 150 organiser_attr["SENT-BY"] = get_uri(self.messenger.sender) 151 152 # Update the attendees in the event. 153 154 to_cancel = update_attendees(self.obj, added, removed) 155 156 self.update_dtstamp() 157 self.set_sequence(update) 158 159 self.send_message(method, get_address(organiser), for_organiser=True) 160 161 # When cancelling, replace the attendees with those for whom the event 162 # is now cancelled. 163 164 if to_cancel: 165 remaining = self.obj["ATTENDEE"] 166 self.obj["ATTENDEE"] = to_cancel 167 self.send_message("CANCEL", get_address(organiser), for_organiser=True) 168 169 # Just in case more work is done with this event, the attendees are 170 # now restored. 171 172 self.obj["ATTENDEE"] = remaining 173 174 return True 175 176 # vim: tabstop=4 expandtab shiftwidth=4