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, from_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 'from_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 from_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): 105 106 """ 107 Process the current request for the given 'user'. Return whether any 108 action was taken. 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(False) 124 125 self.send_message("REPLY", get_address(attendee), from_organiser=False) 126 127 return True 128 129 return False 130 131 def process_created_request(self, method, to_cancel=None): 132 133 """ 134 Process the current request, sending a created request of the given 135 'method' to attendees. Return whether any action was taken. 136 137 If 'to_cancel' is specified, a list of participants to be sent cancel 138 messages is provided. 139 """ 140 141 organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER")) 142 143 if self.messenger and self.messenger.sender != get_address(organiser): 144 organiser_attr["SENT-BY"] = get_uri(self.messenger.sender) 145 146 self.update_dtstamp() 147 self.set_sequence(True) 148 149 self.send_message(method, get_address(organiser), from_organiser=True) 150 151 # When cancelling, replace the attendees with those for whom the event 152 # is now cancelled. 153 154 if to_cancel: 155 remaining = self.obj["ATTENDEE"] 156 self.obj["ATTENDEE"] = to_cancel 157 self.send_message("CANCEL", get_address(organiser), from_organiser=True) 158 159 # Just in case more work is done with this event, the attendees are 160 # now restored. 161 162 self.obj["ATTENDEE"] = remaining 163 164 return True 165 166 # vim: tabstop=4 expandtab shiftwidth=4