# HG changeset patch # User Paul Boddie # Date 1457288363 -3600 # Node ID 9cb80c0c685cb8e642d4cf5852e68985a27f52a3 # Parent c1a7b48a82ff4c77437c100efa6a0d67b58aed88 Introduced mutation constraints and "for update" methods when handling free/busy data, in order to potentially support storage mechanisms where the stored data is manipulated live. diff -r c1a7b48a82ff -r 9cb80c0c685c imiptools/client.py --- a/imiptools/client.py Sun Mar 06 00:46:26 2016 +0100 +++ b/imiptools/client.py Sun Mar 06 19:19:23 2016 +0100 @@ -1134,7 +1134,7 @@ self.acquire_lock() try: - freebusy = self.store.get_freebusy_for_other(self.user, user) + freebusy = self.store.get_freebusy_for_other_for_update(self.user, user) fn(freebusy, user, for_organiser, True) # Tidy up any obsolete recurrences. @@ -1189,7 +1189,7 @@ organiser of an event if 'for_organiser' is set to a true value. """ - freebusy = self.store.get_freebusy(self.user) + freebusy = self.store.get_freebusy_for_update(self.user) # Obtain the attendance attributes for this user, if available. @@ -1216,7 +1216,7 @@ "Remove free/busy information when handling an object." - freebusy = self.store.get_freebusy(self.user) + freebusy = self.store.get_freebusy_for_update(self.user) self.remove_from_freebusy(freebusy) self.remove_freebusy_for_recurrences(freebusy) @@ -1235,7 +1235,7 @@ "Update free/busy offers when handling an object." - freebusy = self.store.get_freebusy_offers(self.user) + freebusy = self.store.get_freebusy_offers_for_update(self.user) # Obtain the attendance attributes for this user, if available. @@ -1253,7 +1253,7 @@ "Remove free/busy offers when handling an object." - freebusy = self.store.get_freebusy_offers(self.user) + freebusy = self.store.get_freebusy_offers_for_update(self.user) self.remove_from_freebusy(freebusy) self.remove_freebusy_for_recurrences(freebusy) diff -r c1a7b48a82ff -r 9cb80c0c685c imiptools/handlers/common.py --- a/imiptools/handlers/common.py Sun Mar 06 00:46:26 2016 +0100 +++ b/imiptools/handlers/common.py Sun Mar 06 19:19:23 2016 +0100 @@ -58,7 +58,7 @@ period = Period(dtstart, dtend, self.get_tzid()) for sender, sender_attr in senders: - stored_freebusy = self.store.get_freebusy_for_other(self.user, sender) + stored_freebusy = self.store.get_freebusy_for_other_for_update(self.user, sender) stored_freebusy.replace_overlapping(period, freebusy) self.store.set_freebusy_for_other(self.user, stored_freebusy, sender) diff -r c1a7b48a82ff -r 9cb80c0c685c imiptools/handlers/scheduling/freebusy.py --- a/imiptools/handlers/scheduling/freebusy.py Sun Mar 06 00:46:26 2016 +0100 +++ b/imiptools/handlers/scheduling/freebusy.py Sun Mar 06 19:19:23 2016 +0100 @@ -103,17 +103,23 @@ # There should already be free/busy information for the user. user_freebusy = handler.get_store().get_freebusy(handler.user) - busy = user_freebusy + + # Maintain a separate copy of the data. + + busy = user_freebusy.copy() # Subtract any periods from this event from the free/busy collections. - event_periods = handler.remove_from_freebusy(user_freebusy) + event_periods = handler.remove_from_freebusy(busy) # Find busy periods for the other attendees. for attendee in uri_values(handler.obj.get_values("ATTENDEE")): if attendee != handler.user: - freebusy = handler.get_store().get_freebusy_for_other(handler.user, attendee) + + # Get a copy of the attendee's free/busy data. + + freebusy = handler.get_store().get_freebusy_for_other(handler.user, attendee).copy() if freebusy: freebusy.remove_periods(event_periods) busy += freebusy diff -r c1a7b48a82ff -r 9cb80c0c685c imiptools/handlers/scheduling/quota.py --- a/imiptools/handlers/scheduling/quota.py Sun Mar 06 00:46:26 2016 +0100 +++ b/imiptools/handlers/scheduling/quota.py Sun Mar 06 19:19:23 2016 +0100 @@ -82,7 +82,7 @@ # Update the journal entries. journal = handler.get_journal() - entries = journal.get_entries(quota, group) + entries = journal.get_entries_for_update(quota, group) handler.update_freebusy(entries, group, False) journal.set_entries(quota, group, entries) @@ -105,7 +105,7 @@ # Update the journal entries. journal = handler.get_journal() - entries = journal.get_entries(quota, group) + entries = journal.get_entries_for_update(quota, group) handler.remove_from_freebusy(entries) journal.set_entries(quota, group, entries) @@ -209,7 +209,7 @@ quota, organiser = _get_quota_and_identity(handler, args) journal = handler.get_journal() - freebusy = journal.get_freebusy(quota, organiser) + freebusy = journal.get_freebusy_for_update(quota, organiser) handler.update_freebusy(freebusy, organiser, True) journal.set_freebusy(quota, organiser, freebusy) @@ -223,7 +223,7 @@ quota, organiser = _get_quota_and_identity(handler, args) journal = handler.get_journal() - freebusy = journal.get_freebusy(quota, organiser) + freebusy = journal.get_freebusy_for_update(quota, organiser) handler.remove_from_freebusy(freebusy) journal.set_freebusy(quota, organiser, freebusy) diff -r c1a7b48a82ff -r 9cb80c0c685c imiptools/period.py --- a/imiptools/period.py Sun Mar 06 00:46:26 2016 +0100 +++ b/imiptools/period.py Sun Mar 06 19:19:23 2016 +0100 @@ -458,6 +458,19 @@ "Common operations on free/busy period collections." + def __init__(self, mutable=True): + self.mutable = mutable + + def _check_mutable(self): + if not self.mutable: + raise TypeError, "Cannot mutate this collection." + + def copy(self): + + "Make an independent mutable copy of the collection." + + return FreeBusyCollection(list(self), True) + # List emulation methods. def __iadd__(self, other): @@ -524,6 +537,8 @@ using the given 'replacements'. """ + self._check_mutable() + self.remove_overlapping(period) for replacement in replacements: self.insert_period(replacement) @@ -599,6 +614,8 @@ free/busy offer. """ + self._check_mutable() + self.remove_event_periods(uid, recurrenceid) for p in periods: @@ -608,13 +625,14 @@ "An abstraction for a collection of free/busy periods." - def __init__(self, periods=None): + def __init__(self, periods=None, mutable=True): """ Initialise the collection with the given list of 'periods', or start an empty collection if no list is given. """ + FreeBusyCollectionBase.__init__(self, mutable) self.periods = periods or [] # List emulation methods. @@ -637,6 +655,8 @@ "Insert the given 'period' into the collection." + self._check_mutable() + i = bisect_left(self.periods, period) if i == len(self.periods): self.periods.append(period) @@ -647,6 +667,8 @@ "Remove the given 'periods' from the collection." + self._check_mutable() + for period in periods: i = bisect_left(self.periods, period) if i < len(self.periods) and self.periods[i] == period: @@ -662,6 +684,8 @@ Return the removed periods. """ + self._check_mutable() + removed = [] i = 0 while i < len(self.periods): @@ -686,6 +710,8 @@ Return the removed periods. """ + self._check_mutable() + removed = [] i = 0 while i < len(self.periods): @@ -713,6 +739,8 @@ Return any removed period in a list. """ + self._check_mutable() + removed = [] search = Period(start, start) @@ -787,6 +815,8 @@ "Remove all periods overlapping with 'period' from the collection." + self._check_mutable() + overlapping = self.get_overlapping(period) if overlapping: @@ -800,12 +830,13 @@ system. """ - def __init__(self, cursor, table_name): + def __init__(self, cursor, table_name, mutable=True): """ Initialise the collection with the given 'cursor' and 'table_name'. """ + FreeBusyCollectionBase.__init__(self, mutable) self.cursor = cursor self.table_name = table_name @@ -857,6 +888,8 @@ "Insert the given 'period' into the collection." + self._check_mutable() + values = period.as_tuple(strings_only=True) query = "insert into %(table)s values (%(columns)s)" % { "table" : self.table_name, @@ -868,6 +901,8 @@ "Remove the given 'periods' from the collection." + self._check_mutable() + for period in periods: values = period.as_tuple(strings_only=True) query = """\ @@ -886,6 +921,8 @@ Return the removed periods. """ + self._check_mutable() + if recurrenceid: condition = "where uid = ? and recurrenceid = ?" values = (uid, recurrenceid) @@ -920,6 +957,8 @@ Return the removed periods. """ + self._check_mutable() + if recurrenceids is None: condition = "where uid = ? and recurrenceid is not null" values = (uid,) @@ -954,6 +993,8 @@ Return any removed period in a list. """ + self._check_mutable() + condition = "where uid = ? and start = ? and recurrenceid is null" values = (uid, start) @@ -1024,6 +1065,8 @@ "Remove all periods overlapping with 'period' from the collection." + self._check_mutable() + condition = "where start < ? and end > ?" values = (format_datetime(period.get_end_point()), format_datetime(period.get_start_point())) diff -r c1a7b48a82ff -r 9cb80c0c685c imiptools/stores/__init__.py --- a/imiptools/stores/__init__.py Sun Mar 06 00:46:26 2016 +0100 +++ b/imiptools/stores/__init__.py Sun Mar 06 19:19:23 2016 +0100 @@ -248,6 +248,18 @@ pass + def get_freebusy_for_update(self, user, name=None): + + "Get free/busy details for the given 'user'." + + pass + + def get_freebusy_for_other_for_update(self, user, other): + + "For the given 'user', get free/busy details for the 'other' user." + + pass + def set_freebusy(self, user, freebusy, name=None): "For the given 'user', set 'freebusy' details." @@ -268,6 +280,12 @@ pass + def get_freebusy_offers_for_update(self, user): + + "Get free/busy offers for the given 'user'." + + pass + def set_freebusy_offers(self, user, freebusy): "For the given 'user', set 'freebusy' offers." @@ -500,6 +518,12 @@ pass + def get_freebusy_for_update(self, quota, user): + + "Get free/busy details for the given 'quota' and 'user'." + + pass + def set_freebusy(self, quota, user, freebusy): "For the given 'quota' and 'user', set 'freebusy' details." @@ -517,6 +541,15 @@ pass + def get_entries_for_update(self, quota, group): + + """ + Return a list of journal entries for the given 'quota' for the indicated + 'group'. + """ + + pass + def set_entries(self, quota, group, entries): """ diff -r c1a7b48a82ff -r 9cb80c0c685c imiptools/stores/file.py --- a/imiptools/stores/file.py Sun Mar 06 00:46:26 2016 +0100 +++ b/imiptools/stores/file.py Sun Mar 06 19:19:23 2016 +0100 @@ -525,7 +525,7 @@ # Free/busy period access. - def get_freebusy(self, user, name=None): + def get_freebusy(self, user, name=None, mutable=False): "Get free/busy details for the given 'user'." @@ -537,9 +537,15 @@ periods = map(lambda t: FreeBusyPeriod(*t), self._get_table_atomic(user, filename)) - return FreeBusyCollection(periods) + return FreeBusyCollection(periods, mutable) + + def get_freebusy_for_update(self, user, name=None): - def get_freebusy_for_other(self, user, other): + "Get free/busy details for the given 'user'." + + return self.get_freebusy(user, name, True) + + def get_freebusy_for_other(self, user, other, mutable=False): "For the given 'user', get free/busy details for the 'other' user." @@ -551,7 +557,13 @@ periods = map(lambda t: FreeBusyPeriod(*t), self._get_table_atomic(user, filename)) - return FreeBusyCollection(periods) + return FreeBusyCollection(periods, mutable) + + def get_freebusy_for_other_for_update(self, user, other): + + "For the given 'user', get free/busy details for the 'other' user." + + return self.get_freebusy_for_other(user, other, True) def set_freebusy(self, user, freebusy, name=None): @@ -579,7 +591,7 @@ # Tentative free/busy periods related to countering. - def get_freebusy_offers(self, user): + def get_freebusy_offers(self, user, mutable=False): "Get free/busy offers for the given 'user'." @@ -603,7 +615,13 @@ finally: self.release_lock(user) - return FreeBusyCollection(offers) + return FreeBusyCollection(offers, mutable) + + def get_freebusy_offers_for_update(self, user): + + "Get free/busy offers for the given 'user'." + + return self.get_freebusy_offers(user, True) def set_freebusy_offers(self, user, freebusy): @@ -893,7 +911,7 @@ # Free/busy period access for users within quota groups. - def get_freebusy(self, quota, user): + def get_freebusy(self, quota, user, mutable=False): "Get free/busy details for the given 'quota' and 'user'." @@ -905,7 +923,13 @@ periods = map(lambda t: FreeBusyPeriod(*t), self._get_table_atomic(quota, filename)) - return FreeBusyCollection(periods) + return FreeBusyCollection(periods, mutable) + + def get_freebusy_for_update(self, quota, user): + + "Get free/busy details for the given 'quota' and 'user'." + + return self.get_freebusy(quota, user, True) def set_freebusy(self, quota, user, freebusy): @@ -921,7 +945,7 @@ # Journal entry methods. - def get_entries(self, quota, group): + def get_entries(self, quota, group, mutable=False): """ Return a list of journal entries for the given 'quota' for the indicated @@ -936,7 +960,16 @@ periods = map(lambda t: FreeBusyPeriod(*t), self._get_table_atomic(quota, filename)) - return FreeBusyCollection(periods) + return FreeBusyCollection(periods, mutable) + + def get_entries_for_update(self, quota, group): + + """ + Return a list of journal entries for the given 'quota' for the indicated + 'group'. + """ + + return self.get_entries(quota, group, True) def set_entries(self, quota, group, entries):