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 ClientForObject 23 from imiptools.data import get_address, get_uri, make_freebusy, \ 24 to_part, uri_item, uri_values 25 from imiptools.dates import format_datetime, get_timestamp 26 27 class ManagerClient(ClientForObject): 28 29 """ 30 A content client for use by the manager, as opposed to operating within the 31 mail processing pipeline. 32 """ 33 34 # Communication methods. 35 36 def send_message(self, method, sender, from_organiser, parts=None): 37 38 """ 39 Create a full calendar object employing the given 'method', and send it 40 to the appropriate recipients, also sending a copy to the 'sender'. The 41 'from_organiser' value indicates whether the organiser is sending this 42 message. 43 """ 44 45 parts = parts or [self.obj.to_part(method)] 46 47 # As organiser, send an invitation to attendees, excluding oneself if 48 # also attending. The updated event will be saved by the outgoing 49 # handler. 50 51 organiser = get_uri(self.obj.get_value("ORGANIZER")) 52 attendees = uri_values(self.obj.get_values("ATTENDEE")) 53 54 if from_organiser: 55 recipients = [get_address(attendee) for attendee in attendees if attendee != self.user] 56 else: 57 recipients = [get_address(organiser)] 58 59 # Bundle free/busy information if appropriate. 60 61 part = self.get_freebusy_part() 62 if part: 63 parts.append(part) 64 65 # Since the outgoing handler updates this user's free/busy details, 66 # the stored details will probably not have the updated details at 67 # this point, so we update our copy for serialisation as the bundled 68 # free/busy object. 69 70 freebusy = self.store.get_freebusy(self.user) 71 72 self.update_freebusy(freebusy, 73 self.obj.get_periods(self.get_tzid(), self.get_window_end())) 74 75 # Explicitly specify the outgoing BCC recipient since we are sending as 76 # the generic calendar user. 77 78 message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender) 79 self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender) 80 81 # Action methods. 82 83 def process_received_request(self): 84 85 """ 86 Process the current request for the current user. Return whether any 87 action was taken. 88 """ 89 90 # Reply only on behalf of this user. 91 92 attendee_attr = self.update_participation(self.obj) 93 94 if not attendee_attr: 95 return False 96 97 self.obj["ATTENDEE"] = [(self.user, attendee_attr)] 98 self.update_dtstamp() 99 self.set_sequence(False) 100 self.send_message("REPLY", get_address(self.user), from_organiser=False) 101 return True 102 103 def process_created_request(self, method, to_cancel=None, to_unschedule=None): 104 105 """ 106 Process the current request, sending a created request of the given 107 'method' to attendees. Return whether any action was taken. 108 109 If 'to_cancel' is specified, a list of participants to be sent cancel 110 messages is provided. 111 """ 112 113 # Here, the organiser should be the current user. 114 115 organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER")) 116 117 self.update_sender(organiser_attr) 118 self.update_dtstamp() 119 self.set_sequence(True) 120 121 parts = [self.obj.to_part(method)] 122 123 # Add message parts with cancelled occurrence information. 124 # NOTE: This could probably be merged with the updated event message. 125 126 if to_unschedule: 127 obj = self.obj.copy() 128 obj.remove_all(["RRULE", "RDATE", "DTSTART", "DTEND", "DURATION"]) 129 130 for p in to_unschedule: 131 if not p.origin: 132 continue 133 obj["RECURRENCE-ID"] = [(format_datetime(p.get_start()), {})] 134 parts.append(obj.to_part("CANCEL")) 135 136 # Send the updated event, along with a cancellation for each of the 137 # unscheduled occurrences. 138 139 self.send_message("CANCEL", get_address(organiser), from_organiser=True, parts=parts) 140 141 # When cancelling, replace the attendees with those for whom the event 142 # is now cancelled. 143 144 if to_cancel: 145 obj = self.obj.copy() 146 obj["ATTENDEE"] = to_cancel 147 148 # Send a cancellation to all uninvited attendees. 149 150 self.send_message("CANCEL", get_address(organiser), from_organiser=True) 151 152 return True 153 154 # vim: tabstop=4 expandtab shiftwidth=4