# HG changeset patch # User Paul Boddie # Date 1442269689 -7200 # Node ID 67a3c848daf711e15c90493b7a666b07821b83e6 # Parent bbb51f5c48517076ccd9516b99584c6a05908dc1 Integrated counter-proposals into the request queue mechanism. diff -r bbb51f5c4851 -r 67a3c848daf7 imip_store.py --- a/imip_store.py Mon Sep 14 19:58:51 2015 +0200 +++ b/imip_store.py Tue Sep 15 00:28:09 2015 +0200 @@ -19,6 +19,7 @@ this program. If not, see . """ +from bisect import bisect_left from datetime import datetime from imiptools.config import STORE_DIR, PUBLISH_DIR from imiptools.data import make_calendar, parse_object, to_stream @@ -107,13 +108,19 @@ f = codecs.open(filename, "wb", encoding="utf-8") try: for item in items: - if empty_defaults: - item = self._set_defaults(list(item), empty_defaults) - f.write("\t".join(item) + "\n") + self._set_table_item(f, item, empty_defaults) finally: f.close() fix_permissions(filename) + def _set_table_item(self, f, item, empty_defaults=None): + + "Set in table 'f' the given 'item', using any 'empty_defaults'." + + if empty_defaults: + item = self._set_defaults(list(item), empty_defaults) + f.write("\t".join(item) + "\n") + def _set_table_atomic(self, user, filename, items, empty_defaults=None): """ @@ -623,7 +630,7 @@ return self.set_freebusy(user, freebusy, "freebusy-offers") - # Object status details access. + # Requests and counter-proposals. def _get_requests(self, user, queue): @@ -633,7 +640,7 @@ if not filename or not exists(filename): return None - return self._get_table_atomic(user, filename, [(1, None)]) + return self._get_table_atomic(user, filename, [(1, None), (2, None)]) def get_requests(self, user): @@ -652,18 +659,7 @@ if not filename: return False - self.acquire_lock(user) - try: - f = open(filename, "w") - try: - for request in requests: - print >>f, "\t".join([value or "" for value in request]) - finally: - f.close() - fix_permissions(filename) - finally: - self.release_lock(user) - + self._set_table_atomic(user, filename, requests, [(1, ""), (2, "")]) return True def set_requests(self, user, requests): @@ -672,11 +668,10 @@ return self._set_requests(user, requests, "requests") - def _set_request(self, user, uid, recurrenceid, queue): + def _set_request(self, user, request, queue): """ - For the given 'user', set the queued 'uid' and 'recurrenceid' in the - given 'queue'. + For the given 'user', set the given 'request' in the given 'queue'. """ filename = self.get_object_in_store(user, queue) @@ -685,9 +680,9 @@ self.acquire_lock(user) try: - f = open(filename, "a") + f = codecs.open(filename, "ab", encoding="utf-8") try: - print >>f, "\t".join([uid, recurrenceid or ""]) + self._set_table_item(f, request, [(1, ""), (2, "")]) finally: f.close() fix_permissions(filename) @@ -696,44 +691,92 @@ return True - def set_request(self, user, uid, recurrenceid=None): - - "For the given 'user', set the queued 'uid' and 'recurrenceid'." + def set_request(self, user, uid, recurrenceid=None, type=None): - return self._set_request(user, uid, recurrenceid, "requests") + """ + For the given 'user', set the queued 'uid' and 'recurrenceid', + indicating a request, along with any given 'type'. + """ - def queue_request(self, user, uid, recurrenceid=None): + return self._set_request(user, (uid, recurrenceid, type), "requests") + + def queue_request(self, user, uid, recurrenceid=None, type=None): """ Queue a request for 'user' having the given 'uid'. If the optional - 'recurrenceid' is specified, the request refers to a specific instance - or occurrence of an event. + 'recurrenceid' is specified, the entry refers to a specific instance + or occurrence of an event. The 'type' parameter can be used to indicate + a specific type of request. + """ + + requests = self.get_requests(user) or [] + + if not self.have_request(requests, uid, recurrenceid): + return self.set_request(user, uid, recurrenceid, type) + + return False + + def dequeue_request(self, user, uid, recurrenceid=None, type=None): + + """ + Dequeue all requests for 'user' having the given 'uid'. If the optional + 'recurrenceid' is specified, all requests for that specific instance or + occurrence of an event are dequeued. """ requests = self.get_requests(user) or [] - if (uid, recurrenceid) not in requests: - return self.set_request(user, uid, recurrenceid) + if not self.have_request(requests, uid, recurrenceid): + return False + + i = bisect_left(requests, (uid, recurrenceid)) + while i < len(requests) and requests[i][:2] == (uid, recurrenceid): + + # Remove associated objects. + + type = requests[i][2] + if type == "COUNTER": + self.remove_counter(user, uid, recurrenceid) - return False + # Remove the request. + + del requests[i] + i += 1 - def dequeue_request(self, user, uid, recurrenceid=None): + self.set_requests(user, requests) + return True + + def have_request(self, requests, uid, recurrenceid=None): + i = bisect_left(requests, (uid, recurrenceid)) + return i < len(requests) and requests[i][:2] == (uid, recurrenceid) + + def set_counter(self, user, node, uid, recurrenceid=None): """ - Dequeue a request for 'user' having the given 'uid'. If the optional - 'recurrenceid' is specified, the request refers to a specific instance - or occurrence of an event. + For the given 'user', store the given 'node' for the given 'uid' and + 'recurrenceid' as a counter-proposal. """ - requests = self.get_requests(user) or [] + filename = self.get_event_filename(user, uid, recurrenceid, "counters") + if not filename: + return False + + return self._set_object(user, filename, node) + + def remove_counter(self, user, uid, recurrenceid=None): - try: - requests.remove((uid, recurrenceid)) - self.set_requests(user, requests) - except ValueError: + """ + For the given 'user', remove any counter-proposal associated with the + given 'uid' and 'recurrenceid'. + """ + + filename = self.get_event_filename(user, uid, recurrenceid, "counters") + if not filename: return False - else: - return True + + return self._remove_object(filename) + + # Event cancellation. def cancel_event(self, user, uid, recurrenceid=None): diff -r bbb51f5c4851 -r 67a3c848daf7 imiptools/handlers/person.py --- a/imiptools/handlers/person.py Mon Sep 14 19:58:51 2015 +0200 +++ b/imiptools/handlers/person.py Tue Sep 15 00:28:09 2015 +0200 @@ -97,6 +97,11 @@ if self.merge_attendance(attendees): self.update_freebusy_from_attendees(attendees) + # Queue any counter-proposal for perusal. + + self.store.set_counter(self.user, self.obj.to_node(), self.uid, self.recurrenceid) + self.store.queue_request(self.user, self.uid, self.recurrenceid, "COUNTER") + return True def _record(self, from_organiser=True, queue=False, cancel=False): diff -r bbb51f5c4851 -r 67a3c848daf7 tests/test_resource_invitation_constraints.sh --- a/tests/test_resource_invitation_constraints.sh Mon Sep 14 19:58:51 2015 +0200 +++ b/tests/test_resource_invitation_constraints.sh Tue Sep 15 00:28:09 2015 +0200 @@ -17,6 +17,7 @@ FBOFFERFILE="$STORE/$USER/freebusy-offers" FBSENDERFILE="$STORE/$SENDER/freebusy" FBSENDEROTHERFILE="$STORE/$SENDER/freebusy-other/$USER" +FBSENDERREQUESTS="$STORE/$SENDER/requests" FBRIVALSENDERFILE="$STORE/$RIVALSENDER/freebusy" TAB=`printf '\t'` @@ -85,6 +86,15 @@ || echo "Failed" grep -q 'ATTENDEE.*;PARTSTAT=DECLINED' "$STORE/$SENDER/objects/event13@example.com" \ +&& grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T161000' "$STORE/$SENDER/objects/event13@example.com" \ +&& echo "Success" \ +|| echo "Failed" + + grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T161500' "$STORE/$SENDER/counters/objects/event13@example.com" \ +&& echo "Success" \ +|| echo "Failed" + + grep -q 'event13@example.com' "$FBSENDERREQUESTS" \ && echo "Success" \ || echo "Failed" @@ -147,6 +157,14 @@ && echo "Success" \ || echo "Failed" + [ ! -e "$STORE/$SENDER/counters/objects/event13@example.com" ] \ +&& echo "Success" \ +|| echo "Failed" + + ! grep -q 'event13@example.com' "$FBSENDERREQUESTS" \ +&& echo "Success" \ +|| echo "Failed" + # Present the request to the resource. "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-good.txt" 2>> $ERROR \ @@ -182,6 +200,14 @@ && echo "Success" \ || echo "Failed" + [ ! -e "$STORE/$SENDER/counters/objects/event13@example.com" ] \ +&& echo "Success" \ +|| echo "Failed" + + ! grep -q 'event13@example.com' "$FBSENDERREQUESTS" \ +&& echo "Success" \ +|| echo "Failed" + # Check the free/busy state of the resource again. "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/fb-request-sauna-all.txt" 2>> $ERROR \ diff -r bbb51f5c4851 -r 67a3c848daf7 tests/test_resource_invitation_constraints_alternative.sh --- a/tests/test_resource_invitation_constraints_alternative.sh Mon Sep 14 19:58:51 2015 +0200 +++ b/tests/test_resource_invitation_constraints_alternative.sh Tue Sep 15 00:28:09 2015 +0200 @@ -17,6 +17,7 @@ FBOFFERFILE="$STORE/$USER/freebusy-offers" FBSENDERFILE="$STORE/$SENDER/freebusy" FBSENDEROTHERFILE="$STORE/$SENDER/freebusy-other/$USER" +FBSENDERREQUESTS="$STORE/$SENDER/requests" FBRIVALSENDERFILE="$STORE/$RIVALSENDER/freebusy" TAB=`printf '\t'` @@ -87,6 +88,15 @@ || echo "Failed" grep -q 'ATTENDEE.*;PARTSTAT=DECLINED' "$STORE/$SENDER/objects/event13@example.com" \ +&& grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T161000' "$STORE/$SENDER/objects/event13@example.com" \ +&& echo "Success" \ +|| echo "Failed" + + grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T161500' "$STORE/$SENDER/counters/objects/event13@example.com" \ +&& echo "Success" \ +|| echo "Failed" + + grep -q 'event13@example.com' "$FBSENDERREQUESTS" \ && echo "Success" \ || echo "Failed"