1.1 --- a/imiptools/content.py Sun Mar 01 00:24:11 2015 +0100
1.2 +++ b/imiptools/content.py Sun Mar 01 22:00:09 2015 +0100
1.3 @@ -26,11 +26,10 @@
1.4 from imiptools.data import Object, parse_object, \
1.5 get_address, get_uri, get_value, get_window_end, \
1.6 is_new_object, uri_dict, uri_item
1.7 -from imiptools.dates import format_datetime, to_timezone
1.8 +from imiptools.dates import format_datetime, get_default_timezone, to_timezone
1.9 from imiptools.period import can_schedule, insert_period, remove_period, \
1.10 - remove_from_freebusy, \
1.11 - remove_from_freebusy_for_other, \
1.12 - update_freebusy, update_freebusy_for_other
1.13 + remove_affected_period, update_freebusy
1.14 +from imiptools.profile import Preferences
1.15 from socket import gethostname
1.16 import imip_store
1.17
1.18 @@ -165,43 +164,65 @@
1.19 def get_outgoing_methods(self):
1.20 return self.outgoing_methods
1.21
1.22 - # Access to calendar structures and other data.
1.23 + # Convenience methods for modifying free/busy collections.
1.24 +
1.25 + def remove_from_freebusy(self, freebusy):
1.26
1.27 - def remove_from_freebusy(self, freebusy, attendee):
1.28 - remove_from_freebusy(freebusy, attendee, self.uid, self.recurrenceid, self.store)
1.29 + "Remove this event from the given 'freebusy' collection."
1.30 +
1.31 + remove_period(freebusy, self.uid, self.recurrenceid)
1.32 +
1.33 + def _update_freebusy(self, freebusy, periods, recurrenceid):
1.34
1.35 - def remove_from_freebusy_for_other(self, freebusy, user, other):
1.36 - remove_from_freebusy_for_other(freebusy, user, other, self.uid, self.recurrenceid, self.store)
1.37 + """
1.38 + Update the 'freebusy' collection with the given 'periods', indicating an
1.39 + explicit 'recurrenceid' to affect either a recurrence or the parent
1.40 + event.
1.41 + """
1.42 +
1.43 + update_freebusy(freebusy, periods, self.obj.get_value("TRANSP"),
1.44 + self.uid, recurrenceid)
1.45
1.46 - def _update_freebusy(self, freebusy, attendee, periods, recurrenceid):
1.47 - update_freebusy(freebusy, attendee, periods, self.obj.get_value("TRANSP"),
1.48 - self.uid, recurrenceid, self.store)
1.49 + def update_freebusy(self, freebusy, periods):
1.50
1.51 - def update_freebusy(self, freebusy, attendee, periods):
1.52 - self._update_freebusy(freebusy, attendee, periods, self.recurrenceid)
1.53 + """
1.54 + Update the 'freebusy' collection for this event with the given
1.55 + 'periods'.
1.56 + """
1.57 +
1.58 + self._update_freebusy(freebusy, periods, self.recurrenceid)
1.59 +
1.60 + # Convenience methods for updating stored free/busy information.
1.61
1.62 def update_freebusy_from_participant(self, user, participant_item):
1.63
1.64 """
1.65 For the given 'user', record the free/busy information for the
1.66 - 'participant_item' (a value plus attributes), using the 'tzid' to define
1.67 - period information.
1.68 + 'participant_item' (a value plus attributes).
1.69 """
1.70
1.71 participant, participant_attr = participant_item
1.72
1.73 - if participant != user:
1.74 - freebusy = self.store.get_freebusy_for_other(user, participant)
1.75 + if participant == user:
1.76 + return
1.77
1.78 - window_end = get_window_end(tzid=None)
1.79 + freebusy = self.store.get_freebusy_for_other(user, participant)
1.80 + tzid = self.get_tzid(user)
1.81 + window_end = get_window_end(tzid)
1.82
1.83 - if participant_attr.get("PARTSTAT") != "DECLINED":
1.84 - update_freebusy_for_other(freebusy, user, participant,
1.85 - self.obj.get_periods_for_freebusy(tzid=None, end=window_end),
1.86 - self.obj.get_value("TRANSP"),
1.87 - self.uid, self.recurrenceid, self.store)
1.88 - else:
1.89 - self.remove_from_freebusy_for_other(freebusy, user, participant)
1.90 + if participant_attr.get("PARTSTAT") != "DECLINED":
1.91 + self.update_freebusy(freebusy,
1.92 + self.obj.get_periods_for_freebusy(tzid, window_end)
1.93 + )
1.94 + else:
1.95 + self.remove_from_freebusy(freebusy)
1.96 +
1.97 + # Remove any specific recurrence from parent free/busy details.
1.98 +
1.99 + if self.recurrenceid:
1.100 + remove_affected_period(freebusy, self.uid, self.recurrenceid)
1.101 +
1.102 + self.store.set_freebusy_for_other(user, freebusy, participant)
1.103
1.104 def update_freebusy_from_organiser(self, attendee, organiser_item):
1.105
1.106 @@ -219,6 +240,8 @@
1.107 for attendee_item in attendees.items():
1.108 self.update_freebusy_from_participant(organiser, attendee_item)
1.109
1.110 + # Logic, filtering and access to calendar structures and other data.
1.111 +
1.112 def can_schedule(self, freebusy, periods):
1.113 return can_schedule(freebusy, periods, self.uid, self.recurrenceid)
1.114
1.115 @@ -360,7 +383,7 @@
1.116 given 'user'.
1.117 """
1.118
1.119 - return self._get_object(user, self.uid, None)
1.120 + return self.recurrenceid and self._get_object(user, self.uid, None) or None
1.121
1.122 def have_new_object(self, attendee, obj=None):
1.123
1.124 @@ -461,6 +484,60 @@
1.125 sequence = self.obj.get_value("SEQUENCE") or "0"
1.126 self.obj["SEQUENCE"] = [(str(int(sequence) + (increment and 1 or 0)), {})]
1.127
1.128 + def detach_recurrence(self, identity):
1.129 +
1.130 + "Detach the current object from its parent if it is a recurrence."
1.131 +
1.132 + # Where a recurring object is updated by a specific occurrence, the
1.133 + # details of the recurring "parent" must be changed.
1.134 +
1.135 + obj = self.get_parent_object(identity)
1.136 + if not obj:
1.137 + return
1.138 +
1.139 + # The original recurrence is obtained, although the recurrence
1.140 + # identifier could be converted back to a UTC datetime and used
1.141 + # instead.
1.142 +
1.143 + recurrence = self.obj.get_datetime("RECURRENCE-ID")
1.144 + if not obj.has_recurrence(self.get_tzid(identity), recurrence):
1.145 + return
1.146 +
1.147 + # To detach the occurrence, the exceptions to the defined recurrence are
1.148 + # modified.
1.149 +
1.150 + item = obj.get_item("EXDATE")
1.151 + if item:
1.152 + exdates, exdate_attr = item
1.153 + if not isinstance(exdates, list):
1.154 + exdates = [exdates]
1.155 + else:
1.156 + exdates, exdate_attr = [], {}
1.157 +
1.158 + # Convert the occurrence to the same time regime as the other
1.159 + # exceptions.
1.160 +
1.161 + exdate_tzid = exdate_attr.get("TZID")
1.162 + exdate = recurrence
1.163 + if exdate_tzid:
1.164 + exdate = to_timezone(exdate, exdate_tzid)
1.165 + else:
1.166 + exdate = to_timezone(exdate, "UTC")
1.167 +
1.168 + # Update the exceptions and store the modified parent event.
1.169 +
1.170 + exdates.append(format_datetime(exdate))
1.171 + obj["EXDATE"] = [(exdates, exdate_attr)]
1.172 +
1.173 + self.store.set_event(identity, self.uid, None, obj.to_node())
1.174 +
1.175 + def get_tzid(self, identity):
1.176 +
1.177 + "Return the time regime applicable for the given 'identity'."
1.178 +
1.179 + preferences = Preferences(identity)
1.180 + return preferences.get("TZID") or get_default_timezone()
1.181 +
1.182 # Handler registry.
1.183
1.184 methods = {