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