1.1 --- a/imiptools/client.py Fri Oct 23 00:08:38 2015 +0200
1.2 +++ b/imiptools/client.py Fri Oct 23 00:26:46 2015 +0200
1.3 @@ -564,6 +564,176 @@
1.4 self.update_sender(attendee_attr)
1.5 return attendee_attr
1.6
1.7 + # Communication methods.
1.8 +
1.9 + def send_message(self, parts, sender, obj, from_organiser, bcc_sender):
1.10 +
1.11 + """
1.12 + Send the given 'parts' to the appropriate recipients, also sending a
1.13 + copy to the 'sender'. The 'obj' together with the 'from_organiser' value
1.14 + (which indicates whether the organiser is sending this message) are used
1.15 + to determine the recipients of the message.
1.16 + """
1.17 +
1.18 + # As organiser, send an invitation to attendees, excluding oneself if
1.19 + # also attending. The updated event will be saved by the outgoing
1.20 + # handler.
1.21 +
1.22 + organiser = get_uri(obj.get_value("ORGANIZER"))
1.23 + attendees = uri_values(obj.get_values("ATTENDEE"))
1.24 +
1.25 + if from_organiser:
1.26 + recipients = [get_address(attendee) for attendee in attendees if attendee != self.user]
1.27 + else:
1.28 + recipients = [get_address(organiser)]
1.29 +
1.30 + # Since the outgoing handler updates this user's free/busy details,
1.31 + # the stored details will probably not have the updated details at
1.32 + # this point, so we update our copy for serialisation as the bundled
1.33 + # free/busy object.
1.34 +
1.35 + freebusy = self.store.get_freebusy(self.user)
1.36 + self.update_freebusy(freebusy, self.user, from_organiser)
1.37 +
1.38 + # Bundle free/busy information if appropriate.
1.39 +
1.40 + part = self.get_freebusy_part(freebusy)
1.41 + if part:
1.42 + parts.append(part)
1.43 +
1.44 + if recipients or bcc_sender:
1.45 + self._send_message(sender, recipients, parts, bcc_sender)
1.46 +
1.47 + def _send_message(self, sender, recipients, parts, bcc_sender):
1.48 +
1.49 + """
1.50 + Send a message, explicitly specifying the 'sender' as an outgoing BCC
1.51 + recipient since the generic calendar user will be the actual sender.
1.52 + """
1.53 +
1.54 + if not bcc_sender:
1.55 + message = self.messenger.make_outgoing_message(parts, recipients)
1.56 + self.messenger.sendmail(recipients, message.as_string())
1.57 + else:
1.58 + message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender)
1.59 + self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender)
1.60 +
1.61 + def send_message_to_self(self, parts):
1.62 +
1.63 + "Send a message composed of the given 'parts' to the given user."
1.64 +
1.65 + sender = get_address(self.user)
1.66 + message = self.messenger.make_outgoing_message(parts, [sender])
1.67 + self.messenger.sendmail([sender], message.as_string())
1.68 +
1.69 + # Action methods.
1.70 +
1.71 + def process_declined_counter(self, attendee):
1.72 +
1.73 + "Process a declined counter-proposal."
1.74 +
1.75 + # Obtain the counter-proposal for the attendee.
1.76 +
1.77 + obj = self.get_stored_object(self.uid, self.recurrenceid, "counters", attendee)
1.78 + if not obj:
1.79 + return False
1.80 +
1.81 + method = "DECLINECOUNTER"
1.82 + self.update_senders(obj=obj)
1.83 + obj.update_dtstamp()
1.84 + obj.update_sequence(False)
1.85 + self._send_message(get_address(self.user), [get_address(attendee)], [obj.to_part(method)], True)
1.86 + return True
1.87 +
1.88 + def process_received_request(self, changed=False):
1.89 +
1.90 + """
1.91 + Process the current request for the current user. Return whether any
1.92 + action was taken. If 'changed' is set to a true value, or if 'attendees'
1.93 + is specified and differs from the stored attendees, a counter-proposal
1.94 + will be sent instead of a reply.
1.95 + """
1.96 +
1.97 + # Reply only on behalf of this user.
1.98 +
1.99 + attendee_attr = self.update_participation()
1.100 +
1.101 + if not attendee_attr:
1.102 + return False
1.103 +
1.104 + if not changed:
1.105 + self.obj["ATTENDEE"] = [(self.user, attendee_attr)]
1.106 + else:
1.107 + self.update_senders()
1.108 +
1.109 + self.update_dtstamp()
1.110 + self.update_sequence(False)
1.111 + self.send_message([self.obj.to_part(changed and "COUNTER" or "REPLY")], get_address(self.user), self.obj, False, True)
1.112 + return True
1.113 +
1.114 + def process_created_request(self, method, to_cancel=None, to_unschedule=None):
1.115 +
1.116 + """
1.117 + Process the current request, sending a created request of the given
1.118 + 'method' to attendees. Return whether any action was taken.
1.119 +
1.120 + If 'to_cancel' is specified, a list of participants to be sent cancel
1.121 + messages is provided.
1.122 +
1.123 + If 'to_unschedule' is specified, a list of periods to be unscheduled is
1.124 + provided.
1.125 + """
1.126 +
1.127 + # Here, the organiser should be the current user.
1.128 +
1.129 + organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))
1.130 +
1.131 + self.update_sender(organiser_attr)
1.132 + self.update_senders()
1.133 + self.update_dtstamp()
1.134 + self.update_sequence(True)
1.135 +
1.136 + if method == "REQUEST":
1.137 + methods, parts = self.get_message_parts(self.obj, "REQUEST")
1.138 +
1.139 + # Add message parts with cancelled occurrence information.
1.140 +
1.141 + unscheduled_parts = self.get_unscheduled_parts(to_unschedule)
1.142 +
1.143 + # Send the updated event, along with a cancellation for each of the
1.144 + # unscheduled occurrences.
1.145 +
1.146 + self.send_message(parts + unscheduled_parts, get_address(organiser), self.obj, True, False)
1.147 +
1.148 + # Since the organiser can update the SEQUENCE but this can leave any
1.149 + # mail/calendar client lagging, issue a PUBLISH message to the
1.150 + # user's address.
1.151 +
1.152 + methods, parts = self.get_message_parts(self.obj, "PUBLISH")
1.153 + self.send_message_to_self(parts + unscheduled_parts)
1.154 +
1.155 + # When cancelling, replace the attendees with those for whom the event
1.156 + # is now cancelled.
1.157 +
1.158 + if method == "CANCEL" or to_cancel:
1.159 + if to_cancel:
1.160 + obj = self.obj.copy()
1.161 + obj["ATTENDEE"] = to_cancel
1.162 + else:
1.163 + obj = self.obj
1.164 +
1.165 + # Send a cancellation to all uninvited attendees.
1.166 +
1.167 + parts = [obj.to_part("CANCEL")]
1.168 + self.send_message(parts, get_address(organiser), obj, True, False)
1.169 +
1.170 + # Issue a CANCEL message to the user's address.
1.171 +
1.172 + if method == "CANCEL":
1.173 + self.send_message_to_self(parts)
1.174 +
1.175 + return True
1.176 +
1.177 # Object-related tests.
1.178
1.179 def is_recognised_organiser(self, organiser):