1.1 --- a/imiptools/client.py Sun Mar 06 00:46:26 2016 +0100
1.2 +++ b/imiptools/client.py Sun Mar 06 19:19:23 2016 +0100
1.3 @@ -1134,7 +1134,7 @@
1.4
1.5 self.acquire_lock()
1.6 try:
1.7 - freebusy = self.store.get_freebusy_for_other(self.user, user)
1.8 + freebusy = self.store.get_freebusy_for_other_for_update(self.user, user)
1.9 fn(freebusy, user, for_organiser, True)
1.10
1.11 # Tidy up any obsolete recurrences.
1.12 @@ -1189,7 +1189,7 @@
1.13 organiser of an event if 'for_organiser' is set to a true value.
1.14 """
1.15
1.16 - freebusy = self.store.get_freebusy(self.user)
1.17 + freebusy = self.store.get_freebusy_for_update(self.user)
1.18
1.19 # Obtain the attendance attributes for this user, if available.
1.20
1.21 @@ -1216,7 +1216,7 @@
1.22
1.23 "Remove free/busy information when handling an object."
1.24
1.25 - freebusy = self.store.get_freebusy(self.user)
1.26 + freebusy = self.store.get_freebusy_for_update(self.user)
1.27
1.28 self.remove_from_freebusy(freebusy)
1.29 self.remove_freebusy_for_recurrences(freebusy)
1.30 @@ -1235,7 +1235,7 @@
1.31
1.32 "Update free/busy offers when handling an object."
1.33
1.34 - freebusy = self.store.get_freebusy_offers(self.user)
1.35 + freebusy = self.store.get_freebusy_offers_for_update(self.user)
1.36
1.37 # Obtain the attendance attributes for this user, if available.
1.38
1.39 @@ -1253,7 +1253,7 @@
1.40
1.41 "Remove free/busy offers when handling an object."
1.42
1.43 - freebusy = self.store.get_freebusy_offers(self.user)
1.44 + freebusy = self.store.get_freebusy_offers_for_update(self.user)
1.45
1.46 self.remove_from_freebusy(freebusy)
1.47 self.remove_freebusy_for_recurrences(freebusy)
2.1 --- a/imiptools/handlers/common.py Sun Mar 06 00:46:26 2016 +0100
2.2 +++ b/imiptools/handlers/common.py Sun Mar 06 19:19:23 2016 +0100
2.3 @@ -58,7 +58,7 @@
2.4 period = Period(dtstart, dtend, self.get_tzid())
2.5
2.6 for sender, sender_attr in senders:
2.7 - stored_freebusy = self.store.get_freebusy_for_other(self.user, sender)
2.8 + stored_freebusy = self.store.get_freebusy_for_other_for_update(self.user, sender)
2.9 stored_freebusy.replace_overlapping(period, freebusy)
2.10 self.store.set_freebusy_for_other(self.user, stored_freebusy, sender)
2.11
3.1 --- a/imiptools/handlers/scheduling/freebusy.py Sun Mar 06 00:46:26 2016 +0100
3.2 +++ b/imiptools/handlers/scheduling/freebusy.py Sun Mar 06 19:19:23 2016 +0100
3.3 @@ -103,17 +103,23 @@
3.4 # There should already be free/busy information for the user.
3.5
3.6 user_freebusy = handler.get_store().get_freebusy(handler.user)
3.7 - busy = user_freebusy
3.8 +
3.9 + # Maintain a separate copy of the data.
3.10 +
3.11 + busy = user_freebusy.copy()
3.12
3.13 # Subtract any periods from this event from the free/busy collections.
3.14
3.15 - event_periods = handler.remove_from_freebusy(user_freebusy)
3.16 + event_periods = handler.remove_from_freebusy(busy)
3.17
3.18 # Find busy periods for the other attendees.
3.19
3.20 for attendee in uri_values(handler.obj.get_values("ATTENDEE")):
3.21 if attendee != handler.user:
3.22 - freebusy = handler.get_store().get_freebusy_for_other(handler.user, attendee)
3.23 +
3.24 + # Get a copy of the attendee's free/busy data.
3.25 +
3.26 + freebusy = handler.get_store().get_freebusy_for_other(handler.user, attendee).copy()
3.27 if freebusy:
3.28 freebusy.remove_periods(event_periods)
3.29 busy += freebusy
4.1 --- a/imiptools/handlers/scheduling/quota.py Sun Mar 06 00:46:26 2016 +0100
4.2 +++ b/imiptools/handlers/scheduling/quota.py Sun Mar 06 19:19:23 2016 +0100
4.3 @@ -82,7 +82,7 @@
4.4 # Update the journal entries.
4.5
4.6 journal = handler.get_journal()
4.7 - entries = journal.get_entries(quota, group)
4.8 + entries = journal.get_entries_for_update(quota, group)
4.9 handler.update_freebusy(entries, group, False)
4.10 journal.set_entries(quota, group, entries)
4.11
4.12 @@ -105,7 +105,7 @@
4.13 # Update the journal entries.
4.14
4.15 journal = handler.get_journal()
4.16 - entries = journal.get_entries(quota, group)
4.17 + entries = journal.get_entries_for_update(quota, group)
4.18 handler.remove_from_freebusy(entries)
4.19 journal.set_entries(quota, group, entries)
4.20
4.21 @@ -209,7 +209,7 @@
4.22 quota, organiser = _get_quota_and_identity(handler, args)
4.23
4.24 journal = handler.get_journal()
4.25 - freebusy = journal.get_freebusy(quota, organiser)
4.26 + freebusy = journal.get_freebusy_for_update(quota, organiser)
4.27 handler.update_freebusy(freebusy, organiser, True)
4.28 journal.set_freebusy(quota, organiser, freebusy)
4.29
4.30 @@ -223,7 +223,7 @@
4.31 quota, organiser = _get_quota_and_identity(handler, args)
4.32
4.33 journal = handler.get_journal()
4.34 - freebusy = journal.get_freebusy(quota, organiser)
4.35 + freebusy = journal.get_freebusy_for_update(quota, organiser)
4.36 handler.remove_from_freebusy(freebusy)
4.37 journal.set_freebusy(quota, organiser, freebusy)
4.38
5.1 --- a/imiptools/period.py Sun Mar 06 00:46:26 2016 +0100
5.2 +++ b/imiptools/period.py Sun Mar 06 19:19:23 2016 +0100
5.3 @@ -458,6 +458,19 @@
5.4
5.5 "Common operations on free/busy period collections."
5.6
5.7 + def __init__(self, mutable=True):
5.8 + self.mutable = mutable
5.9 +
5.10 + def _check_mutable(self):
5.11 + if not self.mutable:
5.12 + raise TypeError, "Cannot mutate this collection."
5.13 +
5.14 + def copy(self):
5.15 +
5.16 + "Make an independent mutable copy of the collection."
5.17 +
5.18 + return FreeBusyCollection(list(self), True)
5.19 +
5.20 # List emulation methods.
5.21
5.22 def __iadd__(self, other):
5.23 @@ -524,6 +537,8 @@
5.24 using the given 'replacements'.
5.25 """
5.26
5.27 + self._check_mutable()
5.28 +
5.29 self.remove_overlapping(period)
5.30 for replacement in replacements:
5.31 self.insert_period(replacement)
5.32 @@ -599,6 +614,8 @@
5.33 free/busy offer.
5.34 """
5.35
5.36 + self._check_mutable()
5.37 +
5.38 self.remove_event_periods(uid, recurrenceid)
5.39
5.40 for p in periods:
5.41 @@ -608,13 +625,14 @@
5.42
5.43 "An abstraction for a collection of free/busy periods."
5.44
5.45 - def __init__(self, periods=None):
5.46 + def __init__(self, periods=None, mutable=True):
5.47
5.48 """
5.49 Initialise the collection with the given list of 'periods', or start an
5.50 empty collection if no list is given.
5.51 """
5.52
5.53 + FreeBusyCollectionBase.__init__(self, mutable)
5.54 self.periods = periods or []
5.55
5.56 # List emulation methods.
5.57 @@ -637,6 +655,8 @@
5.58
5.59 "Insert the given 'period' into the collection."
5.60
5.61 + self._check_mutable()
5.62 +
5.63 i = bisect_left(self.periods, period)
5.64 if i == len(self.periods):
5.65 self.periods.append(period)
5.66 @@ -647,6 +667,8 @@
5.67
5.68 "Remove the given 'periods' from the collection."
5.69
5.70 + self._check_mutable()
5.71 +
5.72 for period in periods:
5.73 i = bisect_left(self.periods, period)
5.74 if i < len(self.periods) and self.periods[i] == period:
5.75 @@ -662,6 +684,8 @@
5.76 Return the removed periods.
5.77 """
5.78
5.79 + self._check_mutable()
5.80 +
5.81 removed = []
5.82 i = 0
5.83 while i < len(self.periods):
5.84 @@ -686,6 +710,8 @@
5.85 Return the removed periods.
5.86 """
5.87
5.88 + self._check_mutable()
5.89 +
5.90 removed = []
5.91 i = 0
5.92 while i < len(self.periods):
5.93 @@ -713,6 +739,8 @@
5.94 Return any removed period in a list.
5.95 """
5.96
5.97 + self._check_mutable()
5.98 +
5.99 removed = []
5.100
5.101 search = Period(start, start)
5.102 @@ -787,6 +815,8 @@
5.103
5.104 "Remove all periods overlapping with 'period' from the collection."
5.105
5.106 + self._check_mutable()
5.107 +
5.108 overlapping = self.get_overlapping(period)
5.109
5.110 if overlapping:
5.111 @@ -800,12 +830,13 @@
5.112 system.
5.113 """
5.114
5.115 - def __init__(self, cursor, table_name):
5.116 + def __init__(self, cursor, table_name, mutable=True):
5.117
5.118 """
5.119 Initialise the collection with the given 'cursor' and 'table_name'.
5.120 """
5.121
5.122 + FreeBusyCollectionBase.__init__(self, mutable)
5.123 self.cursor = cursor
5.124 self.table_name = table_name
5.125
5.126 @@ -857,6 +888,8 @@
5.127
5.128 "Insert the given 'period' into the collection."
5.129
5.130 + self._check_mutable()
5.131 +
5.132 values = period.as_tuple(strings_only=True)
5.133 query = "insert into %(table)s values (%(columns)s)" % {
5.134 "table" : self.table_name,
5.135 @@ -868,6 +901,8 @@
5.136
5.137 "Remove the given 'periods' from the collection."
5.138
5.139 + self._check_mutable()
5.140 +
5.141 for period in periods:
5.142 values = period.as_tuple(strings_only=True)
5.143 query = """\
5.144 @@ -886,6 +921,8 @@
5.145 Return the removed periods.
5.146 """
5.147
5.148 + self._check_mutable()
5.149 +
5.150 if recurrenceid:
5.151 condition = "where uid = ? and recurrenceid = ?"
5.152 values = (uid, recurrenceid)
5.153 @@ -920,6 +957,8 @@
5.154 Return the removed periods.
5.155 """
5.156
5.157 + self._check_mutable()
5.158 +
5.159 if recurrenceids is None:
5.160 condition = "where uid = ? and recurrenceid is not null"
5.161 values = (uid,)
5.162 @@ -954,6 +993,8 @@
5.163 Return any removed period in a list.
5.164 """
5.165
5.166 + self._check_mutable()
5.167 +
5.168 condition = "where uid = ? and start = ? and recurrenceid is null"
5.169 values = (uid, start)
5.170
5.171 @@ -1024,6 +1065,8 @@
5.172
5.173 "Remove all periods overlapping with 'period' from the collection."
5.174
5.175 + self._check_mutable()
5.176 +
5.177 condition = "where start < ? and end > ?"
5.178 values = (format_datetime(period.get_end_point()), format_datetime(period.get_start_point()))
5.179
6.1 --- a/imiptools/stores/__init__.py Sun Mar 06 00:46:26 2016 +0100
6.2 +++ b/imiptools/stores/__init__.py Sun Mar 06 19:19:23 2016 +0100
6.3 @@ -248,6 +248,18 @@
6.4
6.5 pass
6.6
6.7 + def get_freebusy_for_update(self, user, name=None):
6.8 +
6.9 + "Get free/busy details for the given 'user'."
6.10 +
6.11 + pass
6.12 +
6.13 + def get_freebusy_for_other_for_update(self, user, other):
6.14 +
6.15 + "For the given 'user', get free/busy details for the 'other' user."
6.16 +
6.17 + pass
6.18 +
6.19 def set_freebusy(self, user, freebusy, name=None):
6.20
6.21 "For the given 'user', set 'freebusy' details."
6.22 @@ -268,6 +280,12 @@
6.23
6.24 pass
6.25
6.26 + def get_freebusy_offers_for_update(self, user):
6.27 +
6.28 + "Get free/busy offers for the given 'user'."
6.29 +
6.30 + pass
6.31 +
6.32 def set_freebusy_offers(self, user, freebusy):
6.33
6.34 "For the given 'user', set 'freebusy' offers."
6.35 @@ -500,6 +518,12 @@
6.36
6.37 pass
6.38
6.39 + def get_freebusy_for_update(self, quota, user):
6.40 +
6.41 + "Get free/busy details for the given 'quota' and 'user'."
6.42 +
6.43 + pass
6.44 +
6.45 def set_freebusy(self, quota, user, freebusy):
6.46
6.47 "For the given 'quota' and 'user', set 'freebusy' details."
6.48 @@ -517,6 +541,15 @@
6.49
6.50 pass
6.51
6.52 + def get_entries_for_update(self, quota, group):
6.53 +
6.54 + """
6.55 + Return a list of journal entries for the given 'quota' for the indicated
6.56 + 'group'.
6.57 + """
6.58 +
6.59 + pass
6.60 +
6.61 def set_entries(self, quota, group, entries):
6.62
6.63 """
7.1 --- a/imiptools/stores/file.py Sun Mar 06 00:46:26 2016 +0100
7.2 +++ b/imiptools/stores/file.py Sun Mar 06 19:19:23 2016 +0100
7.3 @@ -525,7 +525,7 @@
7.4
7.5 # Free/busy period access.
7.6
7.7 - def get_freebusy(self, user, name=None):
7.8 + def get_freebusy(self, user, name=None, mutable=False):
7.9
7.10 "Get free/busy details for the given 'user'."
7.11
7.12 @@ -537,9 +537,15 @@
7.13 periods = map(lambda t: FreeBusyPeriod(*t),
7.14 self._get_table_atomic(user, filename))
7.15
7.16 - return FreeBusyCollection(periods)
7.17 + return FreeBusyCollection(periods, mutable)
7.18 +
7.19 + def get_freebusy_for_update(self, user, name=None):
7.20
7.21 - def get_freebusy_for_other(self, user, other):
7.22 + "Get free/busy details for the given 'user'."
7.23 +
7.24 + return self.get_freebusy(user, name, True)
7.25 +
7.26 + def get_freebusy_for_other(self, user, other, mutable=False):
7.27
7.28 "For the given 'user', get free/busy details for the 'other' user."
7.29
7.30 @@ -551,7 +557,13 @@
7.31 periods = map(lambda t: FreeBusyPeriod(*t),
7.32 self._get_table_atomic(user, filename))
7.33
7.34 - return FreeBusyCollection(periods)
7.35 + return FreeBusyCollection(periods, mutable)
7.36 +
7.37 + def get_freebusy_for_other_for_update(self, user, other):
7.38 +
7.39 + "For the given 'user', get free/busy details for the 'other' user."
7.40 +
7.41 + return self.get_freebusy_for_other(user, other, True)
7.42
7.43 def set_freebusy(self, user, freebusy, name=None):
7.44
7.45 @@ -579,7 +591,7 @@
7.46
7.47 # Tentative free/busy periods related to countering.
7.48
7.49 - def get_freebusy_offers(self, user):
7.50 + def get_freebusy_offers(self, user, mutable=False):
7.51
7.52 "Get free/busy offers for the given 'user'."
7.53
7.54 @@ -603,7 +615,13 @@
7.55 finally:
7.56 self.release_lock(user)
7.57
7.58 - return FreeBusyCollection(offers)
7.59 + return FreeBusyCollection(offers, mutable)
7.60 +
7.61 + def get_freebusy_offers_for_update(self, user):
7.62 +
7.63 + "Get free/busy offers for the given 'user'."
7.64 +
7.65 + return self.get_freebusy_offers(user, True)
7.66
7.67 def set_freebusy_offers(self, user, freebusy):
7.68
7.69 @@ -893,7 +911,7 @@
7.70
7.71 # Free/busy period access for users within quota groups.
7.72
7.73 - def get_freebusy(self, quota, user):
7.74 + def get_freebusy(self, quota, user, mutable=False):
7.75
7.76 "Get free/busy details for the given 'quota' and 'user'."
7.77
7.78 @@ -905,7 +923,13 @@
7.79 periods = map(lambda t: FreeBusyPeriod(*t),
7.80 self._get_table_atomic(quota, filename))
7.81
7.82 - return FreeBusyCollection(periods)
7.83 + return FreeBusyCollection(periods, mutable)
7.84 +
7.85 + def get_freebusy_for_update(self, quota, user):
7.86 +
7.87 + "Get free/busy details for the given 'quota' and 'user'."
7.88 +
7.89 + return self.get_freebusy(quota, user, True)
7.90
7.91 def set_freebusy(self, quota, user, freebusy):
7.92
7.93 @@ -921,7 +945,7 @@
7.94
7.95 # Journal entry methods.
7.96
7.97 - def get_entries(self, quota, group):
7.98 + def get_entries(self, quota, group, mutable=False):
7.99
7.100 """
7.101 Return a list of journal entries for the given 'quota' for the indicated
7.102 @@ -936,7 +960,16 @@
7.103 periods = map(lambda t: FreeBusyPeriod(*t),
7.104 self._get_table_atomic(quota, filename))
7.105
7.106 - return FreeBusyCollection(periods)
7.107 + return FreeBusyCollection(periods, mutable)
7.108 +
7.109 + def get_entries_for_update(self, quota, group):
7.110 +
7.111 + """
7.112 + Return a list of journal entries for the given 'quota' for the indicated
7.113 + 'group'.
7.114 + """
7.115 +
7.116 + return self.get_entries(quota, group, True)
7.117
7.118 def set_entries(self, quota, group, entries):
7.119