# HG changeset patch # User Paul Boddie # Date 1427821941 -7200 # Node ID 801ac79598b190940872f94082b2b862ee482400 # Parent dd4e2cdb39b460bf0113c0990728864eb5242609 Introduced Client as a base class of the handlers, so that self.user can be used to refer to the current user (typically the recipient) in the different handlers, and so that various convenience methods can be more widely used. Introduced testing for free/busy sharing before publishing details on the Web. diff -r dd4e2cdb39b4 -r 801ac79598b1 imiptools/handlers/__init__.py --- a/imiptools/handlers/__init__.py Tue Mar 31 19:09:11 2015 +0200 +++ b/imiptools/handlers/__init__.py Tue Mar 31 19:12:21 2015 +0200 @@ -21,11 +21,12 @@ from datetime import datetime from email.mime.text import MIMEText +from imiptools.client import Client from imiptools.config import MANAGER_PATH, MANAGER_URL from imiptools.data import Object, \ - get_address, get_uri, get_value, get_window_end, \ + get_address, get_uri, get_value, \ is_new_object, uri_dict, uri_item, uri_values -from imiptools.dates import format_datetime, get_default_timezone, to_timezone +from imiptools.dates import format_datetime, to_timezone from imiptools.period import can_schedule, remove_period, \ remove_additional_periods, remove_affected_period, \ update_freebusy @@ -45,7 +46,7 @@ recurrenceid and "/%s" % recurrenceid or "" ) -class Handler: +class Handler(Client): "General handler support." @@ -56,6 +57,8 @@ 'recipient' of the object (if specifically indicated). """ + Client.__init__(self, recipient and get_uri(recipient)) + self.senders = senders and set(map(get_address, senders)) self.recipient = recipient and get_address(recipient) self.messenger = messenger @@ -186,65 +189,66 @@ # Convenience methods for updating stored free/busy information. - def update_freebusy_from_participant(self, user, participant_item, for_organiser): + def update_freebusy_from_participant(self, participant_item, for_organiser): """ - For the given 'user', record the free/busy information for the + For the calendar user, record the free/busy information for the 'participant_item' (a value plus attributes) representing a different identity, thus maintaining a separate record of their free/busy details. """ participant, participant_attr = participant_item - if participant == user: + if participant == self.user: return - freebusy = self.store.get_freebusy_for_other(user, participant) - tzid = self.get_tzid(user) - window_end = get_window_end(tzid) + freebusy = self.store.get_freebusy_for_other(self.user, participant) # Obtain the stored object if the current object is not issued by the # organiser. - obj = for_organiser and self.obj or self.get_object(user) + obj = for_organiser and self.obj or self.get_object() if not obj: return # Obtain the affected periods. - periods = obj.get_periods_for_freebusy(tzid, window_end) + periods = obj.get_periods_for_freebusy(self.get_tzid(), self.get_window_end()) # Record in the free/busy details unless a non-participating attendee. self.update_freebusy_for_participant(freebusy, periods, participant_attr, - for_organiser and self.is_not_attendee(participant, self.obj)) + for_organiser and not self.is_attendee(participant)) - self.remove_freebusy_for_recurrences(freebusy, self.store.get_recurrences(user, self.uid)) - self.store.set_freebusy_for_other(user, freebusy, participant) + self.remove_freebusy_for_recurrences(freebusy, self.store.get_recurrences(self.user, self.uid)) + self.store.set_freebusy_for_other(self.user, freebusy, participant) - def update_freebusy_from_organiser(self, attendee, organiser_item): + def update_freebusy_from_organiser(self, organiser_item): """ - For the 'attendee', record free/busy information from the + For the current user, record free/busy information from the 'organiser_item' (a value plus attributes). """ - self.update_freebusy_from_participant(attendee, organiser_item, True) + self.update_freebusy_from_participant(organiser_item, True) - def update_freebusy_from_attendees(self, organiser, attendees): + def update_freebusy_from_attendees(self, attendees): - "For the 'organiser', record free/busy information from 'attendees'." + "For the current user, record free/busy information from 'attendees'." for attendee_item in attendees.items(): - self.update_freebusy_from_participant(organiser, attendee_item, False) + self.update_freebusy_from_participant(attendee_item, False) # Logic, filtering and access to calendar structures and other data. - def is_not_attendee(self, identity, obj): + def is_attendee(self, identity, obj=None): - "Return whether 'identity' is not an attendee in 'obj'." + """ + Return whether 'identity' is an attendee in the current object, or in + 'obj' if specified. + """ - return identity not in uri_values(obj.get_values("ATTENDEE")) + return identity in uri_values((obj or self.obj).get_values("ATTENDEE")) def can_schedule(self, freebusy, periods): return can_schedule(freebusy, periods, self.uid, self.recurrenceid) @@ -362,41 +366,42 @@ return senders - def _get_object(self, user, uid, recurrenceid): + def _get_object(self, uid, recurrenceid): """ - Return the stored object for the given 'user', 'uid' and 'recurrenceid'. + Return the stored object for the current user, with the given 'uid' and + 'recurrenceid'. """ - fragment = self.store.get_event(user, uid, recurrenceid) + fragment = self.store.get_event(self.user, uid, recurrenceid) return fragment and Object(fragment) - def get_object(self, user): + def get_object(self): """ Return the stored object to which the current object refers for the - given 'user'. + current user. """ - return self._get_object(user, self.uid, self.recurrenceid) + return self._get_object(self.uid, self.recurrenceid) - def get_parent_object(self, user): + def get_parent_object(self): """ Return the parent object to which the current object refers for the - given 'user'. + current user. """ - return self.recurrenceid and self._get_object(user, self.uid, None) or None + return self.recurrenceid and self._get_object(self.uid, None) or None - def have_new_object(self, attendee, obj=None): + def have_new_object(self, obj=None): """ - Return whether the current object is new to the 'attendee' (or if the + Return whether the current object is new to the current user (or if the given 'obj' is new). """ - obj = obj or self.get_object(attendee) + obj = obj or self.get_object() # If found, compare SEQUENCE and potentially DTSTAMP. @@ -438,16 +443,16 @@ return False - def merge_attendance(self, attendees, identity): + def merge_attendance(self, attendees): """ Merge attendance from the current object's 'attendees' into the version - stored for the given 'identity'. + stored for the current user. """ - obj = self.get_object(identity) + obj = self.get_object() - if not obj or not self.have_new_object(identity, obj=obj): + if not obj or not self.have_new_object(obj): return False # Get attendee details in a usable form. @@ -469,7 +474,7 @@ event = obj.to_node() recurrenceid = format_datetime(obj.get_utc_datetime("RECURRENCE-ID")) - self.store.set_event(identity, self.uid, self.recurrenceid, event) + self.store.set_event(self.user, self.uid, self.recurrenceid, event) return True @@ -488,11 +493,4 @@ sequence = self.obj.get_value("SEQUENCE") or "0" self.obj["SEQUENCE"] = [(str(int(sequence) + (increment and 1 or 0)), {})] - def get_tzid(self, identity): - - "Return the time regime applicable for the given 'identity'." - - preferences = Preferences(identity) - return preferences.get("TZID") or get_default_timezone() - # vim: tabstop=4 expandtab shiftwidth=4 diff -r dd4e2cdb39b4 -r 801ac79598b1 imiptools/handlers/person.py --- a/imiptools/handlers/person.py Tue Mar 31 19:09:11 2015 +0200 +++ b/imiptools/handlers/person.py Tue Mar 31 19:12:21 2015 +0200 @@ -19,7 +19,7 @@ this program. If not, see . """ -from imiptools.data import get_uri +from imiptools.data import get_uri, uri_values, values_from_items from imiptools.dates import format_datetime from imiptools.handlers import Handler from imiptools.handlers.common import CommonFreebusy @@ -51,47 +51,45 @@ if from_organiser: - # Process each attendee separately. - - for attendee, attendee_attr in attendees.items(): + # Process for the current user, an attendee. - if not self.have_new_object(attendee): - continue + if not self.have_new_object() or not self.is_attendee(self.user): + return False - # Set the complete event or an additional occurrence. + # Set the complete event or an additional occurrence. - self.store.set_event(attendee, self.uid, self.recurrenceid, self.obj.to_node()) + self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node()) - # Remove additional recurrences if handling a complete event. + # Remove additional recurrences if handling a complete event. - if not self.recurrenceid: - self.store.remove_recurrences(attendee, self.uid) + if not self.recurrenceid: + self.store.remove_recurrences(self.user, self.uid) - # Queue any request. + # Queue any request. - if queue: - self.store.queue_request(attendee, self.uid, self.recurrenceid) - elif cancel: - self.store.cancel_event(attendee, self.uid, self.recurrenceid) + if queue: + self.store.queue_request(self.user, self.uid, self.recurrenceid) + elif cancel: + self.store.cancel_event(self.user, self.uid, self.recurrenceid) - # No return message will occur to update the free/busy - # information, so this is done here. + # No return message will occur to update the free/busy + # information, so this is done here. - freebusy = self.store.get_freebusy(attendee) - self.remove_from_freebusy(freebusy) + freebusy = self.store.get_freebusy(self.user) + self.remove_from_freebusy(freebusy) - self.store.set_freebusy(attendee, freebusy) + self.store.set_freebusy(self.user, freebusy) - if self.publisher: - self.publisher.set_freebusy(attendee, freebusy) + if self.publisher and self.is_sharing(): + self.publisher.set_freebusy(self.user, freebusy) - self.update_freebusy_from_organiser(attendee, organiser_item) + self.update_freebusy_from_organiser(organiser_item) # As organiser, update attendance from valid attendees. else: - if self.merge_attendance(attendees, organiser): - self.update_freebusy_from_attendees(organiser, attendees) + if self.merge_attendance(attendees): + self.update_freebusy_from_attendees(attendees) return True @@ -133,12 +131,11 @@ dtstart = format_datetime(self.obj.get_utc_datetime("DTSTART")) dtend = format_datetime(self.obj.get_utc_datetime("DTEND")) - user = get_uri(self.recipient) for sender, sender_attr in senders: - stored_freebusy = self.store.get_freebusy_for_other(user, sender) + stored_freebusy = self.store.get_freebusy_for_other(self.user, sender) replace_overlapping(stored_freebusy, Period(dtstart, dtend), freebusy) - self.store.set_freebusy_for_other(user, stored_freebusy, sender) + self.store.set_freebusy_for_other(self.user, stored_freebusy, sender) class Event(PersonHandler): @@ -210,8 +207,7 @@ # Produce a message if configured to do so. - preferences = Preferences(get_uri(self.recipient)) - if preferences.get("freebusy_messages") == "notify": + if self.is_notifying(): return self.wrap("A free/busy update has been received.", link=False) def reply(self): @@ -222,21 +218,19 @@ # Produce a message if configured to do so. - preferences = Preferences(get_uri(self.recipient)) - if preferences.get("freebusy_messages") == "notify": + if self.is_notifying(): return self.wrap("A reply to a free/busy request has been received.", link=False) def request(self): """ Respond to a request by preparing a reply containing free/busy - information for each indicated attendee. + information for the recipient. """ # Produce a reply if configured to do so. - preferences = Preferences(get_uri(self.recipient)) - if preferences.get("freebusy_sharing") == "share": + if self.is_sharing(): return CommonFreebusy.request(self) # Handler registry. diff -r dd4e2cdb39b4 -r 801ac79598b1 imiptools/handlers/person_outgoing.py --- a/imiptools/handlers/person_outgoing.py Tue Mar 31 19:09:11 2015 +0200 +++ b/imiptools/handlers/person_outgoing.py Tue Mar 31 19:12:21 2015 +0200 @@ -20,28 +20,22 @@ this program. If not, see . """ -from imiptools.data import get_window_end, uri_dict, uri_item, uri_values +from imiptools.data import uri_dict, uri_item, uri_values from imiptools.handlers import Handler class PersonHandler(Handler): "Handling mechanisms specific to people." - def _get_identity(self, from_organiser=True): + def set_identity(self, from_organiser=True): """ - Get the identity of interest in a usable form for any unprocessed - object. + Set the current user for the current object. Return attributes for the + user if the """ - identity, attr = item = uri_item(self.obj.get_item(from_organiser and "ORGANIZER" or "ATTENDEE")) - - # Check for event using UID. - - if not self.have_new_object(identity): - return None - - return item + self.user, attr = uri_item(self.obj.get_item(from_organiser and "ORGANIZER" or "ATTENDEE")) + return attr def _record(self, from_organiser=True, update_freebusy=False): @@ -51,71 +45,70 @@ free/busy information if 'update_freebusy' is set to a true value. """ - item = self._get_identity(from_organiser) - if not item: + attr = self.set_identity(from_organiser) + + # Check for event using UID. + + if not self.have_new_object(): return False - identity, attr = item - # Update the object. if from_organiser: # Set the complete event or an additional occurrence. - self.store.set_event(identity, self.uid, self.recurrenceid, self.obj.to_node()) + self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node()) # Remove additional recurrences if handling a complete event. if not self.recurrenceid: - self.store.remove_recurrences(identity, self.uid) + self.store.remove_recurrences(self.user, self.uid) else: # Obtain valid attendees, merging their attendance with the stored # object. attendees = self.require_attendees(from_organiser) - self.merge_attendance(attendees, identity) + self.merge_attendance(attendees) # Remove any associated request. - self.store.dequeue_request(identity, self.uid, self.recurrenceid) + self.store.dequeue_request(self.user, self.uid, self.recurrenceid) # Update free/busy information. if update_freebusy: - freebusy = self.store.get_freebusy(identity) - - # Interpretation of periods can depend on the time zone. - - tzid = self.get_tzid(identity) + freebusy = self.store.get_freebusy(self.user) # Use the stored event in case the reply is incomplete, as is seen # when Claws sends a REPLY for an object originally employing # recurrence information. - obj = self.get_object(identity) + obj = self.get_object() if not obj: return False # although this should not happen # If newer than any old version, discard old details from the # free/busy record and check for suitability. - periods = obj.get_periods_for_freebusy(tzid, get_window_end(tzid)) + # Interpretation of periods can depend on the time zone. + + periods = obj.get_periods_for_freebusy(self.get_tzid(), self.get_window_end()) self.update_freebusy_for_participant(freebusy, periods, attr, - from_organiser and self.is_not_attendee(identity, obj)) + from_organiser and not self.is_attendee(self.user, obj)) # Remove either original recurrence or additional recurrence # details depending on whether an additional recurrence or a # complete event are being handled, respectively. - self.remove_freebusy_for_recurrences(freebusy, self.store.get_recurrences(identity, self.uid)) - self.store.set_freebusy(identity, freebusy) + self.remove_freebusy_for_recurrences(freebusy, self.store.get_recurrences(self.user, self.uid)) + self.store.set_freebusy(self.user, freebusy) - if self.publisher: - self.publisher.set_freebusy(identity, freebusy) + if self.publisher and self.is_sharing(): + self.publisher.set_freebusy(self.user, freebusy) return True @@ -123,17 +116,18 @@ "Remove free/busy information for any unprocessed object." - item = self._get_identity(from_organiser) - if not item: + self.set_identity(from_organiser) + + # Check for event using UID. + + if not self.have_new_object(): return False - identity, attr = item - # Only cancel the event completely if all attendees are given. # NOTE: Need to also check for recurrence identifiers and selective # NOTE: cancellations. - obj = self.get_object(identity) + obj = self.get_object() if not obj: return False @@ -142,7 +136,7 @@ given_attendees = set(uri_values(self.obj.get_values("ATTENDEE"))) if given_attendees == all_attendees: - self.store.cancel_event(identity, self.uid, self.recurrenceid) + self.store.cancel_event(self.user, self.uid, self.recurrenceid) # Otherwise, remove the given attendees and update the event. @@ -159,21 +153,21 @@ # Set the complete event if not an additional occurrence. - self.store.set_event(identity, self.uid, self.recurrenceid, obj.to_node()) + self.store.set_event(self.user, self.uid, self.recurrenceid, obj.to_node()) # Remove any associated request. - self.store.dequeue_request(identity, self.uid, self.recurrenceid) + self.store.dequeue_request(self.user, self.uid, self.recurrenceid) # Update free/busy information. if update_freebusy: - freebusy = self.store.get_freebusy(identity) + freebusy = self.store.get_freebusy(self.user) self.remove_from_freebusy(freebusy) - self.store.set_freebusy(identity, freebusy) + self.store.set_freebusy(self.user, freebusy) - if self.publisher: - self.publisher.set_freebusy(identity, freebusy) + if self.publisher and self.is_sharing(): + self.publisher.set_freebusy(self.user, freebusy) return True diff -r dd4e2cdb39b4 -r 801ac79598b1 imiptools/handlers/resource.py --- a/imiptools/handlers/resource.py Tue Mar 31 19:09:11 2015 +0200 +++ b/imiptools/handlers/resource.py Tue Mar 31 19:12:21 2015 +0200 @@ -19,8 +19,7 @@ this program. If not, see . """ -from imiptools.data import get_address, get_uri, get_window_end, to_part -from imiptools.dates import get_default_timezone +from imiptools.data import get_address, get_uri, to_part from imiptools.handlers import Handler from imiptools.handlers.common import CommonFreebusy from imiptools.period import remove_affected_period @@ -47,48 +46,47 @@ calendar = [] - for attendee, attendee_attr in attendees.items(): + # Process for the current user, a resource as attendee. - # Check for event using UID. + if not self.have_new_object() or not self.is_attendee(self.user): + return None - if not self.have_new_object(attendee): - continue + # Collect response objects produced when handling the request. - # Collect response objects produced when handling the request. - - response = handle_for_attendee(attendee, attendee_attr) - if response: - calendar.append(response) + response = handle_for_attendee() + if response: + calendar.append(response) return calendar - def _schedule_for_attendee(self, attendee, attendee_attr): + def _schedule_for_attendee(self): """ - Schedule for the given 'attendee' and accompanying 'attendee_attr' the - current object. + Schedule the current object for the current user. """ # Interpretation of periods can depend on the time zone. - tzid = self.get_tzid(attendee) + tzid = self.get_tzid() # If newer than any old version, discard old details from the # free/busy record and check for suitability. - periods = self.obj.get_periods_for_freebusy(tzid, get_window_end(tzid)) - freebusy = self.store.get_freebusy(attendee) + periods = self.obj.get_periods_for_freebusy(tzid, self.get_window_end()) + freebusy = self.store.get_freebusy(self.user) scheduled = self.can_schedule(freebusy, periods) + attendee_attr = self.obj.get_value_map("ATTENDEE").get(self.user) + attendee_attr["PARTSTAT"] = scheduled and "ACCEPTED" or "DECLINED" if attendee_attr.has_key("RSVP"): del attendee_attr["RSVP"] - if self.messenger and self.messenger.sender != get_address(attendee): + if self.messenger and self.messenger.sender != get_address(self.user): attendee_attr["SENT-BY"] = get_uri(self.messenger.sender) # Make a version of the request with just this attendee. - self.obj["ATTENDEE"] = [(attendee, attendee_attr)] + self.obj["ATTENDEE"] = [(self.user, attendee_attr)] # Update the DTSTAMP. @@ -97,12 +95,12 @@ # Set the complete event or an additional occurrence. event = self.obj.to_node() - self.store.set_event(attendee, self.uid, self.recurrenceid, event) + self.store.set_event(self.user, self.uid, self.recurrenceid, event) # Remove additional recurrences if handling a complete event. if not self.recurrenceid: - self.store.remove_recurrences(attendee, self.uid) + self.store.remove_recurrences(self.user, self.uid) # Only update free/busy details if the event is scheduled. @@ -116,29 +114,28 @@ # complete event are being handled, respectively. self.remove_freebusy_for_recurrences(freebusy) - self.store.set_freebusy(attendee, freebusy) + self.store.set_freebusy(self.user, freebusy) - if self.publisher: - self.publisher.set_freebusy(attendee, freebusy) + if self.publisher and self.is_sharing(): + self.publisher.set_freebusy(self.user, freebusy) return event - def _cancel_for_attendee(self, attendee, attendee_attr): + def _cancel_for_attendee(self): """ - Cancel for the given 'attendee' and accompanying 'attendee_attr' their - attendance of the event described by the current object. + Cancel for the current user their attendance of the event described by + the current object. """ - self.store.cancel_event(attendee, self.uid, self.recurrenceid) - - freebusy = self.store.get_freebusy(attendee) - self.remove_from_freebusy(freebusy) + self.store.cancel_event(self.user, self.uid, self.recurrenceid) - self.store.set_freebusy(attendee, freebusy) + freebusy = self.store.get_freebusy(self.user) + self.remove_from_freebusy(freebusy) + self.store.set_freebusy(self.user, freebusy) - if self.publisher: - self.publisher.set_freebusy(attendee, freebusy) + if self.publisher and self.is_sharing(): + self.publisher.set_freebusy(self.user, freebusy) return None @@ -186,7 +183,7 @@ """ Respond to a request by preparing a reply containing accept/decline - information for each indicated attendee. + information for the recipient. No support for countering requests is implemented. """ diff -r dd4e2cdb39b4 -r 801ac79598b1 imipweb/handler.py --- a/imipweb/handler.py Tue Mar 31 19:09:11 2015 +0200 +++ b/imipweb/handler.py Tue Mar 31 19:12:21 2015 +0200 @@ -26,7 +26,7 @@ from imiptools.handlers import Handler from imiptools.period import update_freebusy -class ManagerHandler(Client, Handler): +class ManagerHandler(Handler): """ A content handler for use by the manager, as opposed to operating within the @@ -35,7 +35,7 @@ def __init__(self, obj, user, messenger): Handler.__init__(self, messenger=messenger) - Client.__init__(self, user) + Client.__init__(self, user) # this redefines the Handler initialisation self.set_object(obj) diff -r dd4e2cdb39b4 -r 801ac79598b1 imipweb/resource.py --- a/imipweb/resource.py Tue Mar 31 19:09:11 2015 +0200 +++ b/imipweb/resource.py Tue Mar 31 19:12:21 2015 +0200 @@ -205,13 +205,19 @@ remove_affected_period(freebusy, uid, recurrenceid) self.store.set_freebusy(self.user, freebusy) - - if self.publisher: - self.publisher.set_freebusy(self.user, freebusy) + self.publish_freebusy(freebusy) def remove_from_freebusy(self, uid, recurrenceid=None): freebusy = self.store.get_freebusy(self.user) remove_period(freebusy, uid, recurrenceid) self.store.set_freebusy(self.user, freebusy) + self.publish_freebusy(freebusy) + + def publish_freebusy(self, freebusy): + + "Publish the details if configured to share them." + + if self.publisher and self.is_sharing(): + self.publisher.set_freebusy(self.user, freebusy) # vim: tabstop=4 expandtab shiftwidth=4