1.1 --- a/imip_manager.py Sun Mar 01 00:24:11 2015 +0100
1.2 +++ b/imip_manager.py Sun Mar 01 22:00:09 2015 +0100
1.3 @@ -42,8 +42,7 @@
1.4 from imiptools.period import add_day_start_points, add_empty_days, add_slots, \
1.5 convert_periods, get_freebusy_details, \
1.6 get_scale, have_conflict, get_slots, get_spans, \
1.7 - partition_by_day, remove_from_freebusy, update_freebusy, \
1.8 - _update_freebusy
1.9 + partition_by_day, remove_period, update_freebusy
1.10 from imiptools.profile import Preferences
1.11 import imip_store
1.12 import markup
1.13 @@ -137,7 +136,7 @@
1.14 def get_window_end(self):
1.15 return get_window_end(self.get_tzid(), self.get_window_size())
1.16
1.17 -class ManagerHandler(Handler, Common):
1.18 +class ManagerHandler(Common, Handler):
1.19
1.20 """
1.21 A content handler for use by the manager, as opposed to operating within the
1.22 @@ -193,7 +192,7 @@
1.23 # newer details (since the outgoing handler updates this user's
1.24 # free/busy details).
1.25
1.26 - _update_freebusy(freebusy,
1.27 + update_freebusy(freebusy,
1.28 self.obj.get_periods_for_freebusy(self.get_tzid(), self.get_window_end()),
1.29 self.obj.get_value("TRANSP") or "OPAQUE",
1.30 self.uid, self.recurrenceid)
1.31 @@ -392,13 +391,15 @@
1.32
1.33 def update_freebusy(self, uid, recurrenceid, obj):
1.34 freebusy = self.store.get_freebusy(self.user)
1.35 - update_freebusy(freebusy, self.user,
1.36 + update_freebusy(freebusy,
1.37 obj.get_periods_for_freebusy(self.get_tzid(), self.get_window_end()),
1.38 - obj.get_value("TRANSP"), uid, recurrenceid, self.store)
1.39 + obj.get_value("TRANSP"), uid, recurrenceid)
1.40 + self.store.set_freebusy(self.user, freebusy)
1.41
1.42 def remove_from_freebusy(self, uid, recurrenceid=None):
1.43 freebusy = self.store.get_freebusy(self.user)
1.44 - remove_from_freebusy(freebusy, self.user, uid, recurrenceid, self.store)
1.45 + remove_period(freebusy, uid, recurrenceid)
1.46 + self.store.set_freebusy(self.user, freebusy)
1.47
1.48 # Presentation methods.
1.49
1.50 @@ -532,7 +533,7 @@
1.51
1.52 node = ("VEVENT", {}, record)
1.53
1.54 - self.store.set_event(self.user, this_uid, node=node)
1.55 + self.store.set_event(self.user, this_uid, None, node=node)
1.56 self.store.queue_request(self.user, this_uid)
1.57
1.58 # Redirect to the object (or the first of the objects), where instead of
1.59 @@ -647,7 +648,7 @@
1.60 # Save single user events.
1.61
1.62 elif save:
1.63 - self.store.set_event(self.user, uid, node=obj.to_node())
1.64 + self.store.set_event(self.user, uid, None, node=obj.to_node())
1.65 self.update_freebusy(uid, None, obj=obj)
1.66 self.remove_request(uid)
1.67
2.1 --- a/imiptools/content.py Sun Mar 01 00:24:11 2015 +0100
2.2 +++ b/imiptools/content.py Sun Mar 01 22:00:09 2015 +0100
2.3 @@ -26,11 +26,10 @@
2.4 from imiptools.data import Object, parse_object, \
2.5 get_address, get_uri, get_value, get_window_end, \
2.6 is_new_object, uri_dict, uri_item
2.7 -from imiptools.dates import format_datetime, to_timezone
2.8 +from imiptools.dates import format_datetime, get_default_timezone, to_timezone
2.9 from imiptools.period import can_schedule, insert_period, remove_period, \
2.10 - remove_from_freebusy, \
2.11 - remove_from_freebusy_for_other, \
2.12 - update_freebusy, update_freebusy_for_other
2.13 + remove_affected_period, update_freebusy
2.14 +from imiptools.profile import Preferences
2.15 from socket import gethostname
2.16 import imip_store
2.17
2.18 @@ -165,43 +164,65 @@
2.19 def get_outgoing_methods(self):
2.20 return self.outgoing_methods
2.21
2.22 - # Access to calendar structures and other data.
2.23 + # Convenience methods for modifying free/busy collections.
2.24 +
2.25 + def remove_from_freebusy(self, freebusy):
2.26
2.27 - def remove_from_freebusy(self, freebusy, attendee):
2.28 - remove_from_freebusy(freebusy, attendee, self.uid, self.recurrenceid, self.store)
2.29 + "Remove this event from the given 'freebusy' collection."
2.30 +
2.31 + remove_period(freebusy, self.uid, self.recurrenceid)
2.32 +
2.33 + def _update_freebusy(self, freebusy, periods, recurrenceid):
2.34
2.35 - def remove_from_freebusy_for_other(self, freebusy, user, other):
2.36 - remove_from_freebusy_for_other(freebusy, user, other, self.uid, self.recurrenceid, self.store)
2.37 + """
2.38 + Update the 'freebusy' collection with the given 'periods', indicating an
2.39 + explicit 'recurrenceid' to affect either a recurrence or the parent
2.40 + event.
2.41 + """
2.42 +
2.43 + update_freebusy(freebusy, periods, self.obj.get_value("TRANSP"),
2.44 + self.uid, recurrenceid)
2.45
2.46 - def _update_freebusy(self, freebusy, attendee, periods, recurrenceid):
2.47 - update_freebusy(freebusy, attendee, periods, self.obj.get_value("TRANSP"),
2.48 - self.uid, recurrenceid, self.store)
2.49 + def update_freebusy(self, freebusy, periods):
2.50
2.51 - def update_freebusy(self, freebusy, attendee, periods):
2.52 - self._update_freebusy(freebusy, attendee, periods, self.recurrenceid)
2.53 + """
2.54 + Update the 'freebusy' collection for this event with the given
2.55 + 'periods'.
2.56 + """
2.57 +
2.58 + self._update_freebusy(freebusy, periods, self.recurrenceid)
2.59 +
2.60 + # Convenience methods for updating stored free/busy information.
2.61
2.62 def update_freebusy_from_participant(self, user, participant_item):
2.63
2.64 """
2.65 For the given 'user', record the free/busy information for the
2.66 - 'participant_item' (a value plus attributes), using the 'tzid' to define
2.67 - period information.
2.68 + 'participant_item' (a value plus attributes).
2.69 """
2.70
2.71 participant, participant_attr = participant_item
2.72
2.73 - if participant != user:
2.74 - freebusy = self.store.get_freebusy_for_other(user, participant)
2.75 + if participant == user:
2.76 + return
2.77
2.78 - window_end = get_window_end(tzid=None)
2.79 + freebusy = self.store.get_freebusy_for_other(user, participant)
2.80 + tzid = self.get_tzid(user)
2.81 + window_end = get_window_end(tzid)
2.82
2.83 - if participant_attr.get("PARTSTAT") != "DECLINED":
2.84 - update_freebusy_for_other(freebusy, user, participant,
2.85 - self.obj.get_periods_for_freebusy(tzid=None, end=window_end),
2.86 - self.obj.get_value("TRANSP"),
2.87 - self.uid, self.recurrenceid, self.store)
2.88 - else:
2.89 - self.remove_from_freebusy_for_other(freebusy, user, participant)
2.90 + if participant_attr.get("PARTSTAT") != "DECLINED":
2.91 + self.update_freebusy(freebusy,
2.92 + self.obj.get_periods_for_freebusy(tzid, window_end)
2.93 + )
2.94 + else:
2.95 + self.remove_from_freebusy(freebusy)
2.96 +
2.97 + # Remove any specific recurrence from parent free/busy details.
2.98 +
2.99 + if self.recurrenceid:
2.100 + remove_affected_period(freebusy, self.uid, self.recurrenceid)
2.101 +
2.102 + self.store.set_freebusy_for_other(user, freebusy, participant)
2.103
2.104 def update_freebusy_from_organiser(self, attendee, organiser_item):
2.105
2.106 @@ -219,6 +240,8 @@
2.107 for attendee_item in attendees.items():
2.108 self.update_freebusy_from_participant(organiser, attendee_item)
2.109
2.110 + # Logic, filtering and access to calendar structures and other data.
2.111 +
2.112 def can_schedule(self, freebusy, periods):
2.113 return can_schedule(freebusy, periods, self.uid, self.recurrenceid)
2.114
2.115 @@ -360,7 +383,7 @@
2.116 given 'user'.
2.117 """
2.118
2.119 - return self._get_object(user, self.uid, None)
2.120 + return self.recurrenceid and self._get_object(user, self.uid, None) or None
2.121
2.122 def have_new_object(self, attendee, obj=None):
2.123
2.124 @@ -461,6 +484,60 @@
2.125 sequence = self.obj.get_value("SEQUENCE") or "0"
2.126 self.obj["SEQUENCE"] = [(str(int(sequence) + (increment and 1 or 0)), {})]
2.127
2.128 + def detach_recurrence(self, identity):
2.129 +
2.130 + "Detach the current object from its parent if it is a recurrence."
2.131 +
2.132 + # Where a recurring object is updated by a specific occurrence, the
2.133 + # details of the recurring "parent" must be changed.
2.134 +
2.135 + obj = self.get_parent_object(identity)
2.136 + if not obj:
2.137 + return
2.138 +
2.139 + # The original recurrence is obtained, although the recurrence
2.140 + # identifier could be converted back to a UTC datetime and used
2.141 + # instead.
2.142 +
2.143 + recurrence = self.obj.get_datetime("RECURRENCE-ID")
2.144 + if not obj.has_recurrence(self.get_tzid(identity), recurrence):
2.145 + return
2.146 +
2.147 + # To detach the occurrence, the exceptions to the defined recurrence are
2.148 + # modified.
2.149 +
2.150 + item = obj.get_item("EXDATE")
2.151 + if item:
2.152 + exdates, exdate_attr = item
2.153 + if not isinstance(exdates, list):
2.154 + exdates = [exdates]
2.155 + else:
2.156 + exdates, exdate_attr = [], {}
2.157 +
2.158 + # Convert the occurrence to the same time regime as the other
2.159 + # exceptions.
2.160 +
2.161 + exdate_tzid = exdate_attr.get("TZID")
2.162 + exdate = recurrence
2.163 + if exdate_tzid:
2.164 + exdate = to_timezone(exdate, exdate_tzid)
2.165 + else:
2.166 + exdate = to_timezone(exdate, "UTC")
2.167 +
2.168 + # Update the exceptions and store the modified parent event.
2.169 +
2.170 + exdates.append(format_datetime(exdate))
2.171 + obj["EXDATE"] = [(exdates, exdate_attr)]
2.172 +
2.173 + self.store.set_event(identity, self.uid, None, obj.to_node())
2.174 +
2.175 + def get_tzid(self, identity):
2.176 +
2.177 + "Return the time regime applicable for the given 'identity'."
2.178 +
2.179 + preferences = Preferences(identity)
2.180 + return preferences.get("TZID") or get_default_timezone()
2.181 +
2.182 # Handler registry.
2.183
2.184 methods = {
3.1 --- a/imiptools/handlers/person.py Sun Mar 01 00:24:11 2015 +0100
3.2 +++ b/imiptools/handlers/person.py Sun Mar 01 22:00:09 2015 +0100
3.3 @@ -51,8 +51,11 @@
3.4
3.5 # Set the complete event if not an additional occurrence.
3.6
3.7 - event = self.obj.to_node()
3.8 - self.store.set_event(attendee, self.uid, self.recurrenceid, event)
3.9 + self.store.set_event(attendee, self.uid, self.recurrenceid, self.obj.to_node())
3.10 +
3.11 + # Detach any recurrence from its parent.
3.12 +
3.13 + self.detach_recurrence(attendee)
3.14
3.15 # Queue any request.
3.16
3.17 @@ -65,7 +68,9 @@
3.18 # information, so this is done here.
3.19
3.20 freebusy = self.store.get_freebusy(attendee)
3.21 - self.remove_from_freebusy(freebusy, attendee)
3.22 + self.remove_from_freebusy(freebusy)
3.23 +
3.24 + self.store.set_freebusy(attendee, freebusy)
3.25
3.26 if self.publisher:
3.27 self.publisher.set_freebusy(attendee, freebusy)
4.1 --- a/imiptools/handlers/person_outgoing.py Sun Mar 01 00:24:11 2015 +0100
4.2 +++ b/imiptools/handlers/person_outgoing.py Sun Mar 01 22:00:09 2015 +0100
4.3 @@ -22,8 +22,6 @@
4.4
4.5 from imiptools.content import Handler
4.6 from imiptools.data import get_window_end, uri_dict, uri_item, uri_values
4.7 -from imiptools.dates import format_datetime, get_default_timezone, to_timezone
4.8 -from imiptools.profile import Preferences
4.9
4.10 class PersonHandler(Handler):
4.11
4.12 @@ -59,10 +57,9 @@
4.13
4.14 if from_organiser:
4.15
4.16 - # Set the complete event if not an additional occurrence.
4.17 + # Set the complete event or an additional occurrence.
4.18
4.19 - event = self.obj.to_node()
4.20 - self.store.set_event(identity, self.uid, self.recurrenceid, event)
4.21 + self.store.set_event(identity, self.uid, self.recurrenceid, self.obj.to_node())
4.22
4.23 else:
4.24 organiser_item, attendees = self.require_organiser_and_attendees(from_organiser)
4.25 @@ -72,43 +69,9 @@
4.26
4.27 self.store.dequeue_request(identity, self.uid, self.recurrenceid)
4.28
4.29 - # Interpretation of periods can depend on the time zone.
4.30 -
4.31 - preferences = Preferences(identity)
4.32 - tzid = preferences.get("TZID") or get_default_timezone()
4.33 -
4.34 - # Where a recurring object is updated by a specific occurrence, the
4.35 - # details of the recurring "parent" must be changed.
4.36 -
4.37 - if self.recurrenceid:
4.38 - obj = self.get_parent_object(identity)
4.39 - recurrence = self.obj.get_datetime("RECURRENCE-ID")
4.40 + # Detach any recurrence from its parent.
4.41
4.42 - if obj.has_recurrence(tzid, recurrence):
4.43 - item = obj.get_item("EXDATE")
4.44 - if item:
4.45 - exdates, exdate_attr = item
4.46 - if not isinstance(exdates, list):
4.47 - exdates = [exdates]
4.48 - else:
4.49 - exdates, exdate_attr = [], {}
4.50 -
4.51 - # Convert the occurrence to the same time regime as the other
4.52 - # exceptions.
4.53 -
4.54 - exdate_tzid = exdate_attr.get("TZID")
4.55 - exdate = recurrence
4.56 - if exdate_tzid:
4.57 - exdate = to_timezone(exdate, exdate_tzid)
4.58 - else:
4.59 - exdate = to_timezone(exdate, "UTC")
4.60 -
4.61 - # Update the exceptions and store the modified parent event.
4.62 -
4.63 - exdates.append(format_datetime(exdate))
4.64 - obj["EXDATE"] = [(exdates, exdate_attr)]
4.65 -
4.66 - self.store.set_event(identity, self.uid, None, obj.to_node())
4.67 + self.detach_recurrence(identity)
4.68
4.69 # Update free/busy information.
4.70
4.71 @@ -116,6 +79,10 @@
4.72
4.73 freebusy = self.store.get_freebusy(identity)
4.74
4.75 + # Interpretation of periods can depend on the time zone.
4.76 +
4.77 + tzid = self.get_tzid(identity)
4.78 +
4.79 # Use the stored event in case the reply is incomplete, as is seen
4.80 # when Claws sends a REPLY for an object originally employing
4.81 # recurrence information. Additionally, parent object details will
4.82 @@ -129,17 +96,18 @@
4.83 periods = obj.get_periods_for_freebusy(tzid, get_window_end(tzid))
4.84
4.85 if attr.get("PARTSTAT") != "DECLINED":
4.86 - self.update_freebusy(freebusy, identity, periods)
4.87 + self.update_freebusy(freebusy, periods)
4.88 else:
4.89 - self.remove_from_freebusy(freebusy, identity)
4.90 + self.remove_from_freebusy(freebusy)
4.91
4.92 # For any parent object, refresh the updated periods.
4.93
4.94 - if self.recurrenceid:
4.95 - obj = self.get_parent_object(identity)
4.96 + obj = self.get_parent_object(identity)
4.97 + if obj:
4.98 periods = obj.get_periods_for_freebusy(tzid, get_window_end(tzid))
4.99 + self._update_freebusy(freebusy, periods, None)
4.100
4.101 - self._update_freebusy(freebusy, identity, periods, None)
4.102 + self.store.set_freebusy(identity, freebusy)
4.103
4.104 if self.publisher:
4.105 self.publisher.set_freebusy(identity, freebusy)
4.106 @@ -184,8 +152,7 @@
4.107
4.108 # Set the complete event if not an additional occurrence.
4.109
4.110 - event = obj.to_node()
4.111 - self.store.set_event(identity, self.uid, self.recurrenceid, event)
4.112 + self.store.set_event(identity, self.uid, self.recurrenceid, obj.to_node())
4.113
4.114 # Remove any associated request.
4.115
4.116 @@ -195,7 +162,9 @@
4.117
4.118 if update_freebusy:
4.119 freebusy = self.store.get_freebusy(identity)
4.120 - self.remove_from_freebusy(freebusy, identity)
4.121 + self.remove_from_freebusy(freebusy)
4.122 +
4.123 + self.store.set_freebusy(identity, freebusy)
4.124
4.125 if self.publisher:
4.126 self.publisher.set_freebusy(identity, freebusy)
5.1 --- a/imiptools/handlers/resource.py Sun Mar 01 00:24:11 2015 +0100
5.2 +++ b/imiptools/handlers/resource.py Sun Mar 01 22:00:09 2015 +0100
5.3 @@ -20,10 +20,9 @@
5.4 """
5.5
5.6 from imiptools.content import Handler
5.7 -from imiptools.data import get_address, get_uri, to_part
5.8 +from imiptools.data import get_address, get_uri, get_window_end, to_part
5.9 from imiptools.dates import get_default_timezone
5.10 from imiptools.handlers.common import CommonFreebusy
5.11 -from imiptools.profile import Preferences
5.12
5.13 class ResourceHandler(Handler):
5.14
5.15 @@ -60,13 +59,12 @@
5.16
5.17 # Interpretation of periods can depend on the time zone.
5.18
5.19 - preferences = Preferences(attendee)
5.20 - tzid = preferences.get("TZID") or get_default_timezone()
5.21 + tzid = self.get_tzid(attendee)
5.22
5.23 # If newer than any old version, discard old details from the
5.24 # free/busy record and check for suitability.
5.25
5.26 - periods = self.obj.get_periods_for_freebusy(tzid)
5.27 + periods = self.obj.get_periods_for_freebusy(tzid, get_window_end(tzid))
5.28 freebusy = self.store.get_freebusy(attendee)
5.29 scheduled = self.can_schedule(freebusy, periods)
5.30
5.31 @@ -84,17 +82,30 @@
5.32
5.33 self.update_dtstamp()
5.34
5.35 - # Set the complete event if not an additional occurrence.
5.36 + # Set the complete event or an additional occurrence.
5.37
5.38 event = self.obj.to_node()
5.39 self.store.set_event(attendee, self.uid, self.recurrenceid, event)
5.40
5.41 + # Detach any recurrence from its parent.
5.42 +
5.43 + self.detach_recurrence(attendee)
5.44 +
5.45 # Only update free/busy details if the event is scheduled.
5.46
5.47 if scheduled:
5.48 - self.update_freebusy(freebusy, attendee, periods)
5.49 + self.update_freebusy(freebusy, periods)
5.50 else:
5.51 - self.remove_from_freebusy(freebusy, attendee)
5.52 + self.remove_from_freebusy(freebusy)
5.53 +
5.54 + # For any parent object, refresh the updated periods.
5.55 +
5.56 + obj = self.get_parent_object(attendee)
5.57 + if obj:
5.58 + periods = obj.get_periods_for_freebusy(tzid, get_window_end(tzid))
5.59 + self._update_freebusy(freebusy, periods, None)
5.60 +
5.61 + self.store.set_freebusy(attendee, freebusy)
5.62
5.63 if self.publisher:
5.64 self.publisher.set_freebusy(attendee, freebusy)
5.65 @@ -106,7 +117,9 @@
5.66 self.store.cancel_event(attendee, self.uid, self.recurrenceid)
5.67
5.68 freebusy = self.store.get_freebusy(attendee)
5.69 - self.remove_from_freebusy(freebusy, attendee)
5.70 + self.remove_from_freebusy(freebusy)
5.71 +
5.72 + self.store.set_freebusy(attendee, freebusy)
5.73
5.74 if self.publisher:
5.75 self.publisher.set_freebusy(attendee, freebusy)
6.1 --- a/imiptools/period.py Sun Mar 01 00:24:11 2015 +0100
6.2 +++ b/imiptools/period.py Sun Mar 01 22:00:09 2015 +0100
6.3 @@ -73,6 +73,13 @@
6.4 else:
6.5 i += 1
6.6
6.7 +def remove_affected_period(freebusy, uid, recurrenceid):
6.8 + found = bisect_left(freebusy, (recurrenceid,))
6.9 + if found < len(freebusy):
6.10 + start, end, _uid, transp, _recurrenceid = freebusy[found][:5]
6.11 + if start == recurrenceid and recurrenceid != _recurrenceid and uid == _uid:
6.12 + del freebusy[found]
6.13 +
6.14 def get_overlapping(freebusy, period):
6.15
6.16 """
6.17 @@ -366,27 +373,7 @@
6.18
6.19 return start, end, uid, recurrenceid, key
6.20
6.21 -def remove_from_freebusy(freebusy, attendee, uid, recurrenceid, store):
6.22 -
6.23 - """
6.24 - For the given 'attendee', remove periods from 'freebusy' that are associated
6.25 - with 'uid' and 'recurrenceid' in the 'store'.
6.26 - """
6.27 -
6.28 - remove_period(freebusy, uid, recurrenceid)
6.29 - store.set_freebusy(attendee, freebusy)
6.30 -
6.31 -def remove_from_freebusy_for_other(freebusy, user, other, uid, recurrenceid, store):
6.32 -
6.33 - """
6.34 - For the given 'user', remove for the 'other' party periods from 'freebusy'
6.35 - that are associated with 'uid' and 'recurrenceid' in the 'store'.
6.36 - """
6.37 -
6.38 - remove_period(freebusy, uid, recurrenceid)
6.39 - store.set_freebusy_for_other(user, freebusy, other)
6.40 -
6.41 -def _update_freebusy(freebusy, periods, transp, uid, recurrenceid):
6.42 +def update_freebusy(freebusy, periods, transp, uid, recurrenceid):
6.43
6.44 """
6.45 Update the free/busy details with the given 'periods', 'transp' setting and
6.46 @@ -398,24 +385,4 @@
6.47 for start, end in periods:
6.48 insert_period(freebusy, (start, end, uid, transp, recurrenceid))
6.49
6.50 -def update_freebusy(freebusy, attendee, periods, transp, uid, recurrenceid, store):
6.51 -
6.52 - """
6.53 - For the given 'attendee', update the free/busy details with the given
6.54 - 'periods', 'transp' setting and 'uid' plus 'recurrenceid' in the 'store'.
6.55 - """
6.56 -
6.57 - _update_freebusy(freebusy, periods, transp, uid, recurrenceid)
6.58 - store.set_freebusy(attendee, freebusy)
6.59 -
6.60 -def update_freebusy_for_other(freebusy, user, other, periods, transp, uid, recurrenceid, store):
6.61 -
6.62 - """
6.63 - For the given 'user', update the free/busy details of 'other' with the given
6.64 - 'periods', 'transp' setting and 'uid' plus 'recurrenceid' in the 'store'.
6.65 - """
6.66 -
6.67 - _update_freebusy(freebusy, periods, transp, uid, recurrenceid)
6.68 - store.set_freebusy_for_other(user, freebusy, other)
6.69 -
6.70 # vim: tabstop=4 expandtab shiftwidth=4