# HG changeset patch # User Paul Boddie # Date 1445552806 -7200 # Node ID 6e55d2ac7b401927243552fa48d92dd0b07cbb9a # Parent 08480f6319b3705f0a01021a91fb3293b8dab86c Moved communications methods into a common client abstraction. diff -r 08480f6319b3 -r 6e55d2ac7b40 imiptools/client.py --- a/imiptools/client.py Fri Oct 23 00:08:38 2015 +0200 +++ b/imiptools/client.py Fri Oct 23 00:26:46 2015 +0200 @@ -564,6 +564,176 @@ self.update_sender(attendee_attr) return attendee_attr + # Communication methods. + + def send_message(self, parts, sender, obj, from_organiser, bcc_sender): + + """ + Send the given 'parts' to the appropriate recipients, also sending a + copy to the 'sender'. The 'obj' together with the 'from_organiser' value + (which indicates whether the organiser is sending this message) are used + to determine the recipients of the message. + """ + + # As organiser, send an invitation to attendees, excluding oneself if + # also attending. The updated event will be saved by the outgoing + # handler. + + organiser = get_uri(obj.get_value("ORGANIZER")) + attendees = uri_values(obj.get_values("ATTENDEE")) + + if from_organiser: + recipients = [get_address(attendee) for attendee in attendees if attendee != self.user] + else: + recipients = [get_address(organiser)] + + # Since the outgoing handler updates this user's free/busy details, + # the stored details will probably not have the updated details at + # this point, so we update our copy for serialisation as the bundled + # free/busy object. + + freebusy = self.store.get_freebusy(self.user) + self.update_freebusy(freebusy, self.user, from_organiser) + + # Bundle free/busy information if appropriate. + + part = self.get_freebusy_part(freebusy) + if part: + parts.append(part) + + if recipients or bcc_sender: + self._send_message(sender, recipients, parts, bcc_sender) + + def _send_message(self, sender, recipients, parts, bcc_sender): + + """ + Send a message, explicitly specifying the 'sender' as an outgoing BCC + recipient since the generic calendar user will be the actual sender. + """ + + if not bcc_sender: + message = self.messenger.make_outgoing_message(parts, recipients) + self.messenger.sendmail(recipients, message.as_string()) + else: + message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender) + self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender) + + def send_message_to_self(self, parts): + + "Send a message composed of the given 'parts' to the given user." + + sender = get_address(self.user) + message = self.messenger.make_outgoing_message(parts, [sender]) + self.messenger.sendmail([sender], message.as_string()) + + # Action methods. + + def process_declined_counter(self, attendee): + + "Process a declined counter-proposal." + + # Obtain the counter-proposal for the attendee. + + obj = self.get_stored_object(self.uid, self.recurrenceid, "counters", attendee) + if not obj: + return False + + method = "DECLINECOUNTER" + self.update_senders(obj=obj) + obj.update_dtstamp() + obj.update_sequence(False) + self._send_message(get_address(self.user), [get_address(attendee)], [obj.to_part(method)], True) + return True + + def process_received_request(self, changed=False): + + """ + Process the current request for the current user. Return whether any + action was taken. If 'changed' is set to a true value, or if 'attendees' + is specified and differs from the stored attendees, a counter-proposal + will be sent instead of a reply. + """ + + # Reply only on behalf of this user. + + attendee_attr = self.update_participation() + + if not attendee_attr: + return False + + if not changed: + self.obj["ATTENDEE"] = [(self.user, attendee_attr)] + else: + self.update_senders() + + self.update_dtstamp() + self.update_sequence(False) + self.send_message([self.obj.to_part(changed and "COUNTER" or "REPLY")], get_address(self.user), self.obj, False, True) + return True + + def process_created_request(self, method, to_cancel=None, to_unschedule=None): + + """ + Process the current request, sending a created request of the given + 'method' to attendees. Return whether any action was taken. + + If 'to_cancel' is specified, a list of participants to be sent cancel + messages is provided. + + If 'to_unschedule' is specified, a list of periods to be unscheduled is + provided. + """ + + # Here, the organiser should be the current user. + + organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER")) + + self.update_sender(organiser_attr) + self.update_senders() + self.update_dtstamp() + self.update_sequence(True) + + if method == "REQUEST": + methods, parts = self.get_message_parts(self.obj, "REQUEST") + + # Add message parts with cancelled occurrence information. + + unscheduled_parts = self.get_unscheduled_parts(to_unschedule) + + # Send the updated event, along with a cancellation for each of the + # unscheduled occurrences. + + self.send_message(parts + unscheduled_parts, get_address(organiser), self.obj, True, False) + + # Since the organiser can update the SEQUENCE but this can leave any + # mail/calendar client lagging, issue a PUBLISH message to the + # user's address. + + methods, parts = self.get_message_parts(self.obj, "PUBLISH") + self.send_message_to_self(parts + unscheduled_parts) + + # When cancelling, replace the attendees with those for whom the event + # is now cancelled. + + if method == "CANCEL" or to_cancel: + if to_cancel: + obj = self.obj.copy() + obj["ATTENDEE"] = to_cancel + else: + obj = self.obj + + # Send a cancellation to all uninvited attendees. + + parts = [obj.to_part("CANCEL")] + self.send_message(parts, get_address(organiser), obj, True, False) + + # Issue a CANCEL message to the user's address. + + if method == "CANCEL": + self.send_message_to_self(parts) + + return True + # Object-related tests. def is_recognised_organiser(self, organiser): diff -r 08480f6319b3 -r 6e55d2ac7b40 imipweb/resource.py --- a/imipweb/resource.py Fri Oct 23 00:08:38 2015 +0200 +++ b/imipweb/resource.py Fri Oct 23 00:26:46 2015 +0200 @@ -21,8 +21,8 @@ from datetime import datetime, timedelta from imiptools.client import Client, ClientForObject -from imiptools.data import get_address, get_uri, uri_item, uri_values -from imiptools.dates import format_datetime, get_recurrence_start_point, to_date +from imiptools.data import get_uri +from imiptools.dates import format_datetime, to_date from imiptools.period import remove_period, remove_affected_period from imipweb.data import event_period_from_period, form_period_from_period, \ FormDate, PeriodError @@ -221,176 +221,6 @@ user = self.env.get_user() ClientForObject.__init__(self, None, user and get_uri(user) or None, messenger) - # Communication methods. - - def send_message(self, parts, sender, obj, from_organiser, bcc_sender): - - """ - Send the given 'parts' to the appropriate recipients, also sending a - copy to the 'sender'. The 'obj' together with the 'from_organiser' value - (which indicates whether the organiser is sending this message) are used - to determine the recipients of the message. - """ - - # As organiser, send an invitation to attendees, excluding oneself if - # also attending. The updated event will be saved by the outgoing - # handler. - - organiser = get_uri(obj.get_value("ORGANIZER")) - attendees = uri_values(obj.get_values("ATTENDEE")) - - if from_organiser: - recipients = [get_address(attendee) for attendee in attendees if attendee != self.user] - else: - recipients = [get_address(organiser)] - - # Since the outgoing handler updates this user's free/busy details, - # the stored details will probably not have the updated details at - # this point, so we update our copy for serialisation as the bundled - # free/busy object. - - freebusy = self.store.get_freebusy(self.user) - self.update_freebusy(freebusy, self.user, from_organiser) - - # Bundle free/busy information if appropriate. - - part = self.get_freebusy_part(freebusy) - if part: - parts.append(part) - - if recipients or bcc_sender: - self._send_message(sender, recipients, parts, bcc_sender) - - def _send_message(self, sender, recipients, parts, bcc_sender): - - """ - Send a message, explicitly specifying the 'sender' as an outgoing BCC - recipient since the generic calendar user will be the actual sender. - """ - - if not bcc_sender: - message = self.messenger.make_outgoing_message(parts, recipients) - self.messenger.sendmail(recipients, message.as_string()) - else: - message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender) - self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender) - - def send_message_to_self(self, parts): - - "Send a message composed of the given 'parts' to the given user." - - sender = get_address(self.user) - message = self.messenger.make_outgoing_message(parts, [sender]) - self.messenger.sendmail([sender], message.as_string()) - - # Action methods. - - def process_declined_counter(self, attendee): - - "Process a declined counter-proposal." - - # Obtain the counter-proposal for the attendee. - - obj = self.get_stored_object(self.uid, self.recurrenceid, "counters", attendee) - if not obj: - return False - - method = "DECLINECOUNTER" - self.update_senders(obj=obj) - obj.update_dtstamp() - obj.update_sequence(False) - self._send_message(get_address(self.user), [get_address(attendee)], [obj.to_part(method)], True) - return True - - def process_received_request(self, changed=False): - - """ - Process the current request for the current user. Return whether any - action was taken. If 'changed' is set to a true value, or if 'attendees' - is specified and differs from the stored attendees, a counter-proposal - will be sent instead of a reply. - """ - - # Reply only on behalf of this user. - - attendee_attr = self.update_participation() - - if not attendee_attr: - return False - - if not changed: - self.obj["ATTENDEE"] = [(self.user, attendee_attr)] - else: - self.update_senders() - - self.update_dtstamp() - self.update_sequence(False) - self.send_message([self.obj.to_part(changed and "COUNTER" or "REPLY")], get_address(self.user), self.obj, False, True) - return True - - def process_created_request(self, method, to_cancel=None, to_unschedule=None): - - """ - Process the current request, sending a created request of the given - 'method' to attendees. Return whether any action was taken. - - If 'to_cancel' is specified, a list of participants to be sent cancel - messages is provided. - - If 'to_unschedule' is specified, a list of periods to be unscheduled is - provided. - """ - - # Here, the organiser should be the current user. - - organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER")) - - self.update_sender(organiser_attr) - self.update_senders() - self.update_dtstamp() - self.update_sequence(True) - - if method == "REQUEST": - methods, parts = self.get_message_parts(self.obj, "REQUEST") - - # Add message parts with cancelled occurrence information. - - unscheduled_parts = self.get_unscheduled_parts(to_unschedule) - - # Send the updated event, along with a cancellation for each of the - # unscheduled occurrences. - - self.send_message(parts + unscheduled_parts, get_address(organiser), self.obj, True, False) - - # Since the organiser can update the SEQUENCE but this can leave any - # mail/calendar client lagging, issue a PUBLISH message to the - # user's address. - - methods, parts = self.get_message_parts(self.obj, "PUBLISH") - self.send_message_to_self(parts + unscheduled_parts) - - # When cancelling, replace the attendees with those for whom the event - # is now cancelled. - - if method == "CANCEL" or to_cancel: - if to_cancel: - obj = self.obj.copy() - obj["ATTENDEE"] = to_cancel - else: - obj = self.obj - - # Send a cancellation to all uninvited attendees. - - parts = [obj.to_part("CANCEL")] - self.send_message(parts, get_address(organiser), obj, True, False) - - # Issue a CANCEL message to the user's address. - - if method == "CANCEL": - self.send_message_to_self(parts) - - return True - class FormUtilities: "Utility methods resource mix-in."