# HG changeset patch # User Paul Boddie # Date 1463172525 -7200 # Node ID 5618f1121c4a2a6eed4b9608983f5f79b494e5d8 # Parent 9870d5d67a50bf5d41ce409898c3ede8bbac3172 Made use of the get_scheduling_conflicts common scheduling function to support delegation, introducing a "most-available" policy and tentatively supporting multiple policies. Tidied up the quota module code, making handling of endlessly-recurring events identical between the addition and removal functions modifying journal entries. diff -r 9870d5d67a50 -r 5618f1121c4a imiptools/handlers/scheduling/quota.py --- a/imiptools/handlers/scheduling/quota.py Fri May 13 22:41:17 2016 +0200 +++ b/imiptools/handlers/scheduling/quota.py Fri May 13 22:48:45 2016 +0200 @@ -21,6 +21,8 @@ from imiptools.dates import get_duration, to_utc_datetime from imiptools.data import get_uri, uri_dict +from imiptools.handlers.scheduling.common import get_scheduling_conflicts, \ + standard_responses from imiptools.period import Endless from datetime import timedelta @@ -50,6 +52,15 @@ if not limit: return "DECLINED", _("You have no quota allocation for the recipient.") + # Decline endless events even for unlimited quotas. + # NOTE: Such events could be supported in a similar way to those supported + # NOTE: for each user. + + total = _get_duration(handler) + + if total == Endless(): + return "DECLINED", _("The event period exceeds your quota allocation for the recipient.") + # Where the quota is unlimited, accept the invitation. if limit == "*": @@ -57,11 +68,6 @@ # Decline events whose durations exceed the balance. - total = _get_duration(handler) - - if total == Endless(): - return "DECLINED", _("The event period exceeds your quota allocation for the recipient.") - balance = get_duration(limit) - _get_usage(entries) if total > balance: @@ -81,7 +87,7 @@ total = _get_duration(handler) expiry = _get_expiry_time(handler) - # Reject indefinitely recurring events. + # Ignore indefinitely recurring events. if total == Endless() or not expiry: return @@ -103,11 +109,12 @@ quota, group = _get_quota_and_group(handler, args) total = _get_duration(handler) - - # Allow indefinitely recurring events. + expiry = _get_expiry_time(handler) - if total == Endless(): - total = None + # Ignore indefinitely recurring events. + + if total == Endless() or not expiry: + return # Update the journal entries. @@ -196,8 +203,6 @@ nor are the quotas themselves. """ - _ = handler.get_translator() - quota, organiser = _get_quota_and_identity(handler, args) # Check the event periods against the quota's consolidated record of the @@ -207,10 +212,7 @@ freebusy = handler.get_journal().get_freebusy(quota, organiser) scheduled = handler.can_schedule(freebusy, periods) - if scheduled: - return "ACCEPTED", _("The recipient has scheduled the requested period.") - else: - return "DECLINED", _("The requested period cannot be scheduled.") + return standard_responses(handler, scheduled and "ACCEPTED" or "DECLINED") def add_to_quota_freebusy(handler, args): @@ -263,11 +265,9 @@ """ Check the current object of the given 'handler' against the schedules managed by the quota, delegating to a specific recipient according to the - given policy. + given policies. """ - _ = handler.get_translator() - # First check the quota and decline any request that would exceed the quota. scheduled = check_quota(handler, args) @@ -279,7 +279,7 @@ # Obtain the quota and organiser group details to evaluate delegation. quota, group = _get_quota_and_group(handler, args) - policy = args and (args[1:] or ["arbitrary"])[0] + policies = args and args[1:] or ["available"] # Determine the status of the recipient. @@ -305,26 +305,29 @@ # unavailable delegates. entries = handler.get_journal().get_entries(quota, group) - unavailable = set() + conflicts = get_scheduling_conflicts(handler, entries, delegates, attendee=True) - for period in handler.get_periods(handler.obj): - overlapping = entries.get_overlapping(period) + # Get the delegates in order of increasing unavailability (or decreasing + # availability). - # Where scheduling cannot occur, find the busy potential delegates. + unavailability = conflicts.items() + + # Apply the policies to choose a suitable delegate. - if overlapping: - for p in overlapping: - unavailable.add(p.attendee) + if "most-available" in policies: + unavailability.sort(key=lambda t: t[1]) + available = [delegate for (delegate, commitments) in unavailability] + delegate = available and available[0] - # Get the remaining, available delegates. + # The default is to select completely available delegates. - available = delegates.difference(unavailable) + else: + available = [delegate for (delegate, commitments) in unavailability if not commitments] + delegate = available and (handler.user in available and handler.user or available[0]) - # Apply the policy to choose an available delegate. - # NOTE: Currently an arbitrary delegate is chosen if not the recipient. + # Only accept or delegate if a suitably available delegate is found. - if available: - delegate = handler.user in available and handler.user or list(available)[0] + if delegate: # Add attendee for delegate, obtaining the original attendee dictionary. # Modify this user's status to refer to the delegate. @@ -335,11 +338,13 @@ attendee_attr["DELEGATED-TO"] = [delegate] handler.obj["ATTENDEE"] = attendee_map.items() - return "DELEGATED", _("The recipient has delegated the requested period.") + response = "DELEGATED" else: - return "ACCEPTED", _("The recipient has scheduled the requested period.") + response = "ACCEPTED" else: - return "DECLINED", _("The requested period cannot be scheduled.") + response = "DECLINED" + + return standard_responses(handler, response) # Locking and unlocking. diff -r 9870d5d67a50 -r 5618f1121c4a tests/test_resource_invitation_constraints_quota_delegation.sh --- a/tests/test_resource_invitation_constraints_quota_delegation.sh Fri May 13 22:41:17 2016 +0200 +++ b/tests/test_resource_invitation_constraints_quota_delegation.sh Fri May 13 22:48:45 2016 +0200 @@ -226,16 +226,16 @@ # The delegate should now have a changed schedule. "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ -> out4f1.tmp +> out4f0.tmp - ! grep -q "^20141126T153000Z${TAB}20141126T163000Z" "out4f1.tmp" \ + ! grep -q "^20141126T153000Z${TAB}20141126T163000Z" "out4f0.tmp" \ && echo "Success" \ || echo "Failed" "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ -> out4f2.tmp +> out4f1.tmp - grep -q "^20141126T153000Z${TAB}20141126T163000Z" "out4f2.tmp" \ + grep -q "^20141126T153000Z${TAB}20141126T163000Z" "out4f1.tmp" \ && echo "Success" \ || echo "Failed" diff -r 9870d5d67a50 -r 5618f1121c4a tests/test_resource_invitation_constraints_quota_delegation_policy.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_resource_invitation_constraints_quota_delegation_policy.sh Fri May 13 22:48:45 2016 +0200 @@ -0,0 +1,332 @@ +#!/bin/sh + +. "`dirname \"$0\"`/common.sh" + +USER1="mailto:resource-car-porsche911@example.com" +USER2="mailto:resource-car-fiat500@example.com" +SENDER1="mailto:paul.boddie@example.com" +SENDER2="mailto:vincent.vole@example.com" +USER1ADDRESS="resource-car-porsche911@example.com" +USER2ADDRESS="resource-car-fiat500@example.com" +SENDER1ADDRESS="paul.boddie@example.com" +SENDER2ADDRESS="vincent.vole@example.com" +QUOTA=cars +OTHER_QUOTA=rooms + +mkdir -p "$PREFS/$USER1" +echo 'Europe/Oslo' > "$PREFS/$USER1/TZID" +echo 'share' > "$PREFS/$USER1/freebusy_sharing" +cat > "$PREFS/$USER1/scheduling_function" < "$PREFS/$USER2/TZID" +echo 'share' > "$PREFS/$USER2/freebusy_sharing" +cat > "$PREFS/$USER2/scheduling_function" <> $ERROR \ +| "$SHOWMAIL" \ +> out0.tmp + + grep -q 'METHOD:REPLY' out0.tmp \ +&& ! grep -q '^FREEBUSY' out0.tmp \ +&& echo "Success" \ +|| echo "Failed" + +# Attempt to schedule an event. + +"$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-car.txt" 2>> $ERROR + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER1" "freebusy" \ +| tee out0s.tmp \ +| grep -q "^20141126T150000Z${TAB}20141126T160000Z" \ +&& echo "Success" \ +|| echo "Failed" + +# Present the request to the resource. + + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-request-car.txt" 2>> $ERROR \ +| tee out1r.tmp \ +| "$SHOWMAIL" \ +> out1.tmp + + grep -q 'METHOD:REPLY' out1.tmp \ +&& grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED' out1.tmp \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +| tee out1f.tmp \ +| grep -q "^20141126T150000Z${TAB}20141126T160000Z" \ +&& echo "Success" \ +|| echo "Failed" + +# Check the quota (event is confirmed). + + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "all" \ +| tee out1e.tmp \ +| grep -q "event21@example.com" \ +&& echo "Success" \ +|| echo "Failed" + +# Attempt to schedule another event. + + sed 's/FREQ=DAILY/FREQ=DAILY;COUNT=5/;' "$TEMPLATES/event-request-cars-recurring.txt" \ +| "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER1" "freebusy" \ +> out2s.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2s.tmp" \ +&& grep -q "event21@example.com" "out2s.tmp" \ +&& grep -q "event25@example.com" "out2s.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Present the request to the recipients. + + sed 's/FREQ=DAILY/FREQ=DAILY;COUNT=5/;' "$TEMPLATES/event-request-cars-recurring.txt" \ +| "$RESOURCE_SCRIPT" $ARGS 2>> $ERROR \ +> out2r.tmp + + "$SHOWMAIL" < "out2r.tmp" \ +> out2p0.tmp + + "$SHOWMAIL" 1 < "out2r.tmp" \ +> out2p1.tmp + + grep -q 'METHOD:REPLY' out2p0.tmp \ +&& grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED' out2p0.tmp \ +&& echo "Success" \ +|| echo "Failed" + + grep -q 'METHOD:REPLY' out2p1.tmp \ +&& grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED' out2p1.tmp \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out2f0.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2f0.tmp" \ +&& grep -q "event21@example.com" "out2f0.tmp" \ +&& grep -q "event25@example.com" "out2f0.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +> out2f1.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2f1.tmp" \ +&& grep -q "event25@example.com" "out2f1.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Check the quota (event is confirmed). + + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "all" \ +| tee out2e.tmp \ +| grep -q "event25@example.com" \ +&& echo "Success" \ +|| echo "Failed" + +# Attempt to schedule another event. + +"$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-car-delegating.txt" 2>> $ERROR + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER2" "freebusy" \ +| tee out3s.tmp \ +| grep -q "^20141126T153000Z${TAB}20141126T163000Z" \ +&& echo "Success" \ +|| echo "Failed" + +# Present the request to the resource. + + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-request-car-delegating.txt" 2>> $ERROR \ +> out3r.tmp + + "$SHOWMAIL" < out3r.tmp \ +> out3p0.tmp + + "$SHOWMAIL" 1 < out3r.tmp \ +> out3p1.tmp + +if grep -q "To: $SENDER2ADDRESS" out3p0.tmp ; then + ORGFN=out3p0.tmp ; DELFN=out3p1.tmp +else + ORGFN=out3p1.tmp ; DELFN=out3p0.tmp +fi + +# One of the responses will be a request sent to the delegate. + + grep -q "To: $USER2ADDRESS" "$DELFN" \ +&& grep -q 'METHOD:REQUEST' "$DELFN" \ +&& grep -q 'ATTENDEE.*;PARTSTAT=DELEGATED.*:'"$USER1" "$DELFN" \ +&& grep -q 'ATTENDEE.*:'"$USER2" "$DELFN" \ +&& echo "Success" \ +|| echo "Failed" + +# The other will be a reply to the organiser. + + grep -q "To: $SENDER2ADDRESS" "$ORGFN" \ +&& grep -q 'METHOD:REPLY' "$ORGFN" \ +&& grep -q 'ATTENDEE.*;PARTSTAT=DELEGATED.*:'"$USER1" "$ORGFN" \ +&& grep -q 'ATTENDEE.*:'"$USER2" "$ORGFN" \ +&& echo "Success" \ +|| echo "Failed" + +# Neither the delegator or the delegate will have changed their schedules. + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out4f1.tmp + + ! grep -q "^20141126T153000Z${TAB}20141126T163000Z" "out4f1.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +> out4f2.tmp + + ! grep -q "^20141126T153000Z${TAB}20141126T163000Z" "out4f2.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Check the quota (event is not confirmed). + + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "all" \ +> out4e.tmp + + grep -q "event21@example.com" "out4e.tmp" \ +&& grep -q "event25@example.com" "out4e.tmp" \ +&& ! grep -q "event27@example.com" "out4e.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Present the reply to the organiser. + + "$PERSON_SCRIPT" $ARGS < "$ORGFN" 2>> "$ERROR" \ +| tee out5r.tmp \ +| "$SHOWMAIL" \ +> out5.tmp + +# Check the free/busy status of the attendees at the organiser. +# Currently, neither are attending. + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER2" "freebusy_other" "$USER1" \ +> out5s0.tmp \ + + ! grep -q "^20141126T153000Z${TAB}20141126T163000Z" out5s0.tmp \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER2" "freebusy_other" "$USER2" \ +> out5s1.tmp \ + + ! grep -q "^20141126T153000Z${TAB}20141126T163000Z" out5s1.tmp \ +&& echo "Success" \ +|| echo "Failed" + +# Present the request to the delegate. + + "$RESOURCE_SCRIPT" $ARGS < "$DELFN" 2>> "$ERROR" \ +> out6r.tmp + + "$SHOWMAIL" < out6r.tmp \ +> out6p0.tmp + + "$SHOWMAIL" 1 < out6r.tmp \ +> out6p1.tmp + +if grep -q "To: $SENDER2ADDRESS" out6p0.tmp ; then + ORGFN=out6p0.tmp ; DELFN=out6p1.tmp +else + ORGFN=out6p1.tmp ; DELFN=out6p0.tmp +fi + +# One of the responses will be a reply sent to the organiser. + + grep -q "To: $SENDER2ADDRESS" "$ORGFN" \ +&& grep -q 'METHOD:REPLY' "$ORGFN" \ +&& grep -q 'ATTENDEE.*;PARTSTAT=DELEGATED.*:'"$USER1" "$ORGFN" \ +&& grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED.*:'"$USER2" "$ORGFN" \ +&& echo "Success" \ +|| echo "Failed" + +# The other will be a reply to the delegator. + + grep -q "To: $USER1ADDRESS" "$DELFN" \ +&& grep -q 'METHOD:REPLY' "$DELFN" \ +&& grep -q 'ATTENDEE.*;PARTSTAT=DELEGATED.*:'"$USER1" "$DELFN" \ +&& grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED.*:'"$USER2" "$DELFN" \ +&& echo "Success" \ +|| echo "Failed" + +# The delegate should now have a changed schedule. + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out7f0.tmp + + ! grep -q "^20141126T153000Z${TAB}20141126T163000Z" "out7f0.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +> out7f1.tmp + + grep -q "^20141126T153000Z${TAB}20141126T163000Z" "out7f1.tmp" \ +&& grep -q "event27@example.com" "out7f1.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Present the reply to the organiser. + + "$PERSON_SCRIPT" $ARGS < "$ORGFN" 2>> "$ERROR" \ +| tee out8r.tmp \ +| "$SHOWMAIL" \ +> out8.tmp + +# Check the free/busy status of the attendees at the organiser. +# Now, the delegate is attending. + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER2" "freebusy_other" "$USER1" \ +> out8s0.tmp \ + + ! grep -q "^20141126T153000Z${TAB}20141126T163000Z" out8s0.tmp \ +&& ! grep -q "event27@example.com" out8s0.tmp \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER2" "freebusy_other" "$USER2" \ +> out8s1.tmp \ + + grep -q "^20141126T153000Z${TAB}20141126T163000Z" out8s1.tmp \ +&& grep -q "event27@example.com" out8s1.tmp \ +&& echo "Success" \ +|| echo "Failed" + +# Present the reply to the delegator. + + "$RESOURCE_SCRIPT" $ARGS < "$DELFN" 2>> "$ERROR" \ +> out9r.tmp