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 23 from imiptools.data import get_address, get_uri, 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(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) # this redefines the Handler initialisation 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, to_cancel=None): 135 136 """ 137 Process the current request, sending a created request of the given 138 'method' to attendees. Return whether any action was taken. 139 140 If 'update' is given, the sequence number will be incremented in order 141 to override any previous message. 142 143 If 'to_cancel' is specified, a list of participants to be sent cancel 144 messages 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 self.update_dtstamp() 153 self.set_sequence(update) 154 155 self.send_message(method, get_address(organiser), for_organiser=True) 156 157 # When cancelling, replace the attendees with those for whom the event 158 # is now cancelled. 159 160 if to_cancel: 161 remaining = self.obj["ATTENDEE"] 162 self.obj["ATTENDEE"] = to_cancel 163 self.send_message("CANCEL", get_address(organiser), for_organiser=True) 164 165 # Just in case more work is done with this event, the attendees are 166 # now restored. 167 168 self.obj["ATTENDEE"] = remaining 169 170 return True 171 172 # vim: tabstop=4 expandtab shiftwidth=4