[CalendarServer-changes] [8642] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Thu Feb 9 13:34:19 PST 2012
Revision: 8642
http://trac.macosforge.org/projects/calendarserver/changeset/8642
Author: cdaboo at apple.com
Date: 2012-02-09 13:34:19 -0800 (Thu, 09 Feb 2012)
Log Message:
-----------
Add new auto-schedule-modes to allow more control over how auto-scheduling occurs by default or a per-calendar user basis.
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/tools/principals.py
CalendarServer/trunk/calendarserver/tools/test/test_principals.py
CalendarServer/trunk/calendarserver/webadmin/resource.py
CalendarServer/trunk/calendarserver/webadmin/template.html
CalendarServer/trunk/calendarserver/webadmin/test/test_resource.py
CalendarServer/trunk/conf/auth/augments-test.xml
CalendarServer/trunk/conf/auth/augments.dtd
CalendarServer/trunk/conf/caldavd-test.plist
CalendarServer/trunk/doc/calendarserver_manage_principals.8
CalendarServer/trunk/twistedcaldav/customxml.py
CalendarServer/trunk/twistedcaldav/directory/augment.py
CalendarServer/trunk/twistedcaldav/directory/directory.py
CalendarServer/trunk/twistedcaldav/directory/principal.py
CalendarServer/trunk/twistedcaldav/directory/test/augments-test-default.xml
CalendarServer/trunk/twistedcaldav/directory/test/augments-test.xml
CalendarServer/trunk/twistedcaldav/directory/test/test_augment.py
CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
CalendarServer/trunk/twistedcaldav/directory/xmlaugmentsparser.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/scheduling/itip.py
CalendarServer/trunk/twistedcaldav/scheduling/processing.py
CalendarServer/trunk/twistedcaldav/stdconfig.py
Modified: CalendarServer/trunk/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/principals.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/calendarserver/tools/principals.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,7 +1,7 @@
#!/usr/bin/env python
##
-# Copyright (c) 2006-2011 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2012 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@
from twistedcaldav.directory.directory import UnknownRecordTypeError, DirectoryError
from calendarserver.tools.util import loadConfig, getDirectory, setupMemcached, booleanArgument, checkDirectory
+from twistedcaldav.directory.augment import allowedAutoScheduleModes
__all__ = [
"principalForPrincipalID", "proxySubprincipal", "addProxy", "removeProxy",
@@ -83,6 +84,8 @@
print " --remove-proxy=principal: remove a proxy"
print " --set-auto-schedule={true|false}: set auto-accept state"
print " --get-auto-schedule: read auto-schedule state"
+ print " --set-auto-schedule-mode={default|none|accept-always|decline-always|accept-if-free|decline-if-busy|automatic}: set auto-schedule mode"
+ print " --get-auto-schedule-mode: read auto-schedule mode"
print " --add {locations|resources} 'full name' [record name] [GUID]: add a principal"
print " --remove: remove a principal"
@@ -111,6 +114,8 @@
"remove-proxy=",
"set-auto-schedule=",
"get-auto-schedule",
+ "set-auto-schedule-mode=",
+ "get-auto-schedule-mode",
"verbose",
],
)
@@ -203,6 +208,19 @@
elif opt in ("", "--get-auto-schedule"):
principalActions.append((action_getAutoSchedule,))
+ elif opt in ("", "--set-auto-schedule-mode"):
+ try:
+ if arg not in allowedAutoScheduleModes:
+ raise ValueError("Unknown auto-schedule mode: %s" % (arg,))
+ autoScheduleMode = arg
+ except ValueError, e:
+ abort(e)
+
+ principalActions.append((action_setAutoScheduleMode, autoScheduleMode))
+
+ elif opt in ("", "--get-auto-schedule-mode"):
+ principalActions.append((action_getAutoScheduleMode,))
+
else:
raise NotImplementedError(opt)
@@ -677,7 +695,7 @@
if principal.record.recordType == "groups":
print "Enabling auto-schedule for %s is not allowed." % (principal,)
- elif principal.record.recordType == "users" and not config.Scheduling.Options.AllowUserAutoAccept:
+ elif principal.record.recordType == "users" and not config.Scheduling.Options.AutoSchedule.AllowUsers:
print "Enabling auto-schedule for %s is not allowed." % (principal,)
else:
@@ -697,12 +715,44 @@
def action_getAutoSchedule(principal):
autoSchedule = principal.getAutoSchedule()
- print "Autoschedule for %s is %s" % (
+ print "Auto-schedule for %s is %s" % (
prettyPrincipal(principal),
{ True: "true", False: "false" }[autoSchedule],
)
+ at inlineCallbacks
+def action_setAutoScheduleMode(principal, autoScheduleMode):
+ if principal.record.recordType == "groups":
+ print "Setting auto-schedule mode for %s is not allowed." % (principal,)
+ elif principal.record.recordType == "users" and not config.Scheduling.Options.AutoSchedule.AllowUsers:
+ print "Setting auto-schedule mode for %s is not allowed." % (principal,)
+
+ else:
+ print "Setting auto-schedule mode to %s for %s" % (
+ autoScheduleMode,
+ prettyPrincipal(principal),
+ )
+
+ (yield updateRecord(False, config.directory,
+ principal.record.recordType,
+ guid=principal.record.guid,
+ shortNames=principal.record.shortNames,
+ fullName=principal.record.fullName,
+ autoScheduleMode=autoScheduleMode,
+ **principal.record.extras
+ ))
+
+def action_getAutoScheduleMode(principal):
+ autoScheduleMode = principal.getAutoScheduleMode()
+ if not autoScheduleMode:
+ autoScheduleMode = "automatic"
+ print "Auto-schedule mode for %s is %s" % (
+ prettyPrincipal(principal),
+ autoScheduleMode,
+ )
+
+
def abort(msg, status=1):
sys.stdout.write("%s\n" % (msg,))
try:
@@ -796,6 +846,12 @@
else:
autoSchedule = recordType in ("locations", "resources")
+ if kwargs.has_key("autoScheduleMode"):
+ autoScheduleMode = kwargs["autoScheduleMode"]
+ del kwargs["autoScheduleMode"]
+ else:
+ autoScheduleMode = None
+
for key, value in kwargs.items():
if isinstance(value, unicode):
kwargs[key] = value.encode("utf-8")
@@ -819,6 +875,7 @@
augmentService = directory.serviceForRecordType(recordType).augmentService
augmentRecord = (yield augmentService.getAugmentRecord(kwargs['guid'], recordType))
augmentRecord.autoSchedule = autoSchedule
+ augmentRecord.autoScheduleMode = autoScheduleMode
(yield augmentService.addAugmentRecords([augmentRecord]))
try:
directory.updateRecord(recordType, **kwargs)
Modified: CalendarServer/trunk/calendarserver/tools/test/test_principals.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_principals.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/calendarserver/tools/test/test_principals.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2012 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -25,7 +25,8 @@
from twistedcaldav.directory.directory import DirectoryError
from twistedcaldav.directory import calendaruserproxy
-from twistedcaldav.test.util import TestCase, CapturingProcessProtocol
+from twistedcaldav.test.util import TestCase, CapturingProcessProtocol,\
+ ErrorOutput
from calendarserver.tap.util import directoryFromConfig
from calendarserver.tools.principals import parseCreationArgs, matchStrings, updateRecord, principalForPrincipalID, getProxies, setProxies
@@ -130,8 +131,12 @@
results = yield self.runCommand("--get-auto-schedule",
"resources:newresource")
- self.assertTrue(results.startswith('Autoschedule for "New Resource" (resources:newresource) is true'))
+ self.assertTrue(results.startswith('Auto-schedule for "New Resource" (resources:newresource) is true'))
+ results = yield self.runCommand("--get-auto-schedule-mode",
+ "resources:newresource")
+ self.assertTrue(results.startswith('Auto-schedule mode for "New Resource" (resources:newresource) is default'))
+
results = yield self.runCommand("--list-principals=resources")
self.assertTrue("newresource" in results)
@@ -220,7 +225,7 @@
def test_autoSchedule(self):
results = yield self.runCommand("--get-auto-schedule",
"locations:location01")
- self.assertTrue(results.startswith('Autoschedule for "Room 01" (locations:location01) is false'))
+ self.assertTrue(results.startswith('Auto-schedule for "Room 01" (locations:location01) is false'))
results = yield self.runCommand("--set-auto-schedule=true",
"locations:location01")
@@ -228,7 +233,7 @@
results = yield self.runCommand("--get-auto-schedule",
"locations:location01")
- self.assertTrue(results.startswith('Autoschedule for "Room 01" (locations:location01) is true'))
+ self.assertTrue(results.startswith('Auto-schedule for "Room 01" (locations:location01) is true'))
results = yield self.runCommand("--set-auto-schedule=true",
"users:user01")
@@ -236,6 +241,33 @@
@inlineCallbacks
+ def test_autoScheduleMode(self):
+ results = yield self.runCommand("--get-auto-schedule-mode",
+ "locations:location01")
+ self.assertTrue(results.startswith('Auto-schedule mode for "Room 01" (locations:location01) is default'))
+
+ results = yield self.runCommand("--set-auto-schedule-mode=accept-if-free",
+ "locations:location01")
+ self.assertTrue(results.startswith('Setting auto-schedule mode to accept-if-free for "Room 01" (locations:location01)'))
+
+ results = yield self.runCommand("--get-auto-schedule-mode",
+ "locations:location01")
+ self.assertTrue(results.startswith('Auto-schedule mode for "Room 01" (locations:location01) is accept-if-free'))
+
+ results = yield self.runCommand("--set-auto-schedule-mode=decline-if-busy",
+ "users:user01")
+ self.assertTrue(results.startswith('Setting auto-schedule mode for (users)user01 is not allowed.'))
+
+ try:
+ results = yield self.runCommand("--set-auto-schedule-mode=bogus",
+ "users:user01")
+ except ErrorOutput:
+ pass
+ else:
+ self.fail("Expected command failure")
+
+
+ @inlineCallbacks
def test_updateRecord(self):
directory = directoryFromConfig(config)
guid = "EEE28807-A8C5-46C8-A558-A08281C558A7"
Modified: CalendarServer/trunk/calendarserver/webadmin/resource.py
===================================================================
--- CalendarServer/trunk/calendarserver/webadmin/resource.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/calendarserver/webadmin/resource.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,6 +1,6 @@
# -*- test-case-name: calendarserver.webadmin.test.test_resource -*-
##
-# Copyright (c) 2009-2011 Apple Inc. All rights reserved.
+# Copyright (c) 2009-2012 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -267,7 +267,7 @@
if (self.principalResource.record.recordType != "users" and
self.principalResource.record.recordType != "groups" or
self.principalResource.record.recordType == "users" and
- config.Scheduling.Options.AllowUserAutoAccept):
+ config.Scheduling.Options.AutoSchedule.AllowUsers):
return tag
return ""
@@ -294,6 +294,83 @@
return tag
+ @renderer
+ def autoScheduleModeDefault(self, request, tag):
+ """
+ Renderer which sets the 'selected' attribute on its tag based on the resource
+ auto-schedule-mode.
+ """
+ if self.principalResource.getAutoScheduleMode() == "default":
+ tag(selected='selected')
+ return tag
+
+
+ @renderer
+ def autoScheduleModeNone(self, request, tag):
+ """
+ Renderer which sets the 'selected' attribute on its tag based on the resource
+ auto-schedule-mode.
+ """
+ if self.principalResource.getAutoScheduleMode() == "none":
+ tag(selected='selected')
+ return tag
+
+
+ @renderer
+ def autoScheduleModeAcceptAlways(self, request, tag):
+ """
+ Renderer which sets the 'selected' attribute on its tag based on the resource
+ auto-schedule-mode.
+ """
+ if self.principalResource.getAutoScheduleMode() == "accept-always":
+ tag(selected='selected')
+ return tag
+
+
+ @renderer
+ def autoScheduleModeDeclineAlways(self, request, tag):
+ """
+ Renderer which sets the 'selected' attribute on its tag based on the resource
+ auto-schedule-mode.
+ """
+ if self.principalResource.getAutoScheduleMode() == "decline-always":
+ tag(selected='selected')
+ return tag
+
+
+ @renderer
+ def autoScheduleModeAcceptIfFree(self, request, tag):
+ """
+ Renderer which sets the 'selected' attribute on its tag based on the resource
+ auto-schedule-mode.
+ """
+ if self.principalResource.getAutoScheduleMode() == "accept-if-free":
+ tag(selected='selected')
+ return tag
+
+
+ @renderer
+ def autoScheduleModeDeclineIfBusy(self, request, tag):
+ """
+ Renderer which sets the 'selected' attribute on its tag based on the resource
+ auto-schedule-mode.
+ """
+ if self.principalResource.getAutoScheduleMode() == "decline-if-busy":
+ tag(selected='selected')
+ return tag
+
+
+ @renderer
+ def autoScheduleModeAutomatic(self, request, tag):
+ """
+ Renderer which sets the 'selected' attribute on its tag based on the resource
+ auto-schedule-mode.
+ """
+ if self.principalResource.getAutoScheduleMode() == "automatic" or not self.principalResource.getAutoScheduleMode():
+ tag(selected='selected')
+ return tag
+
+
_matrix = None
@inlineCallbacks
@@ -547,6 +624,7 @@
return matches
autoSchedule = queryValue("autoSchedule")
+ autoScheduleMode = queryValue("autoScheduleMode")
makeReadProxies = queryValues("mkReadProxy|")
makeWriteProxies = queryValues("mkWriteProxy|")
removeProxies = queryValues("rmProxy|")
@@ -557,8 +635,9 @@
if ( principal.record.recordType != "users" and
principal.record.recordType != "groups" or
principal.record.recordType == "users" and
- config.Scheduling.Options.AllowUserAutoAccept):
+ config.Scheduling.Options.AutoSchedule.AllowUsers):
(yield principal.setAutoSchedule(autoSchedule == "true"))
+ (yield principal.setAutoScheduleMode(autoScheduleMode))
# Update the proxies if specified.
for proxyId in removeProxies:
Modified: CalendarServer/trunk/calendarserver/webadmin/template.html
===================================================================
--- CalendarServer/trunk/calendarserver/webadmin/template.html 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/calendarserver/webadmin/template.html 2012-02-09 21:34:19 UTC (rev 8642)
@@ -128,6 +128,18 @@
<option t:render="isAutoSchedule" value="true">Yes</option>
<option t:render="isntAutoSchedule" value="false">No</option>
</select>
+ <br/>
+ Auto-Schedule Mode
+ <select id="sel_autoScheduleMode" name="autoScheduleMode">
+ <option t:render="autoScheduleModeDefault" value="default">Default</option>
+ <option t:render="autoScheduleModeNone" value="none">None</option>
+ <option t:render="autoScheduleModeAcceptAlways" value="accept-always">Accept Always</option>
+ <option t:render="autoScheduleModeDeclineAlways" value="decline-always">Decline Always</option>
+ <option t:render="autoScheduleModeAcceptIfFree" value="accept-if-free">Accept If Free</option>
+ <option t:render="autoScheduleModeDeclineIfBusy" value="decline-if-busy">Decline If Busy</option>
+ <option t:render="autoScheduleModeAutomatic" value="automatic">Automatic (Accept and Decline)</option>
+ </select>
+ <br/>
<input type="submit" value="Change" />
</div>
</form>
Modified: CalendarServer/trunk/calendarserver/webadmin/test/test_resource.py
===================================================================
--- CalendarServer/trunk/calendarserver/webadmin/test/test_resource.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/calendarserver/webadmin/test/test_resource.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2011 Apple Inc. All rights reserved.
+# Copyright (c) 2011-2012 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -335,6 +335,38 @@
@inlineCallbacks
+ def test_autoScheduleModeMenu(self):
+ """
+ When rendering a resource, an "Auto-Schedule Mode" menu with various options
+ should be displayed.
+ """
+
+ modes = ("default", "none", "accept-always", "decline-always", "accept-if-free", "decline-if-busy", "automatic",)
+
+ for ctr, expectValue in enumerate(modes):
+
+ self.resource.getResourceById = partial(FakePrincipalResource, self,
+ recordType='resources',
+ autosched=True,
+ autoschedmode=expectValue)
+ document = yield self.renderPage(dict(resourceId=["qux"]))
+ autoScheduleModeMenu = document.getElementById("sel_autoScheduleMode")
+ self.assertEquals(autoScheduleModeMenu.getAttribute("name"),
+ "autoScheduleMode")
+
+ popup = getElementsByTagName(autoScheduleModeMenu, 'option')
+
+ # Sanity checks to make sure we got the right items
+ for i, mode in enumerate(modes):
+ self.assertEquals(popup[i].getAttribute("value"), mode)
+
+ popupValues = [popup[i] for i in range(len(modes))]
+ for i in range(len(modes)):
+ self.assertEquals(popupValues[i].hasAttribute("selected"), ctr == i)
+ self.assertEquals(popupValues[ctr].getAttribute("selected"), "selected")
+
+
+ @inlineCallbacks
def test_proxiesListing(self):
"""
Resource principals will have their proxies listed in a table.
@@ -403,11 +435,13 @@
class FakePrincipalResource(object):
- def __init__(self, test, req=None, resid='no-id-given', autosched=True,
+ def __init__(self, test, req=None, resid='no-id-given',
+ autosched=True, autoschedmode=None,
recordType="users", extraProperties=(), hasProxies=True):
self.test = test
self.resid = resid
self.autosched = autosched
+ self.autoschedmode = autoschedmode
self.recordType = recordType
self.extraProperties = extraProperties
self.hasProxies = hasProxies
@@ -469,3 +503,7 @@
return self.autosched
+ def getAutoScheduleMode(self):
+ return self.autoschedmode
+
+
Modified: CalendarServer/trunk/conf/auth/augments-test.xml
===================================================================
--- CalendarServer/trunk/conf/auth/augments-test.xml 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/conf/auth/augments-test.xml 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+Copyright (c) 2009-2012 Apple Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -32,13 +32,61 @@
<enable-addressbook>true</enable-addressbook>
<auto-schedule>true</auto-schedule>
</record>
- <record repeat="10">
+ <record repeat="4">
<uid>resource%02d</uid>
<enable>true</enable>
<enable-calendar>true</enable-calendar>
<enable-addressbook>true</enable-addressbook>
<auto-schedule>true</auto-schedule>
</record>
+ <record>
+ <uid>resource05</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>none</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>resource06</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>accept-always</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>resource07</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>decline-always</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>resource08</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>accept-if-free</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>resource09</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>decline-if-busy</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>resource10</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>automatic</auto-schedule-mode>
+ </record>
<record repeat="10">
<uid>group%02d</uid>
<enable>true</enable>
Modified: CalendarServer/trunk/conf/auth/augments.dtd
===================================================================
--- CalendarServer/trunk/conf/auth/augments.dtd 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/conf/auth/augments.dtd 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,5 +1,5 @@
<!--
-Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+Copyright (c) 2009-2012 Apple Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
<!ELEMENT augments (record*) >
- <!ELEMENT record (uid, enable, (server-id, partition-id?)?, enable-calendar?, enable-addressbook?, auto-schedule?)>
+ <!ELEMENT record (uid, enable, (server-id, partition-id?)?, enable-calendar?, enable-addressbook?, auto-schedule?, auto-schedule-mode?)>
<!ATTLIST record repeat CDATA "1">
<!ELEMENT uid (#PCDATA)>
@@ -26,4 +26,5 @@
<!ELEMENT enable-calendar (#PCDATA)>
<!ELEMENT enable-addressbook (#PCDATA)>
<!ELEMENT auto-schedule (#PCDATA)>
+ <!ELEMENT auto-schedule-mode (#PCDATA)>
>
Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/conf/caldavd-test.plist 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Copyright (c) 2006-2011 Apple Inc. All rights reserved.
+ Copyright (c) 2006-2012 Apple Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -815,8 +815,24 @@
<false/>
<key>AttendeeRefreshBatch</key>
<integer>0</integer>
+
+ <key>AutoSchedule</key>
+ <dict>
+ <key>Enabled</key>
+ <true/>
+ <key>Always</key>
+ <false/>
+ <!-- Default mode for auto-schedule processing, one of:
+ "none" - no auto-scheduling
+ "accept-always" - always accept, ignore busy time
+ "decline-always" - always decline, ignore free time
+ "accept-if-free" - accept if free, do nothing if busy
+ "decline-if-busy" - decline if busy, do nothing if free
+ "automatic" - accept if free, decline if busy -->
+ <key>DefaultMode</key>
+ <string>automatic</string>
+ </dict>
</dict>
-
</dict>
Modified: CalendarServer/trunk/doc/calendarserver_manage_principals.8
===================================================================
--- CalendarServer/trunk/doc/calendarserver_manage_principals.8 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/doc/calendarserver_manage_principals.8 2012-02-09 21:34:19 UTC (rev 8642)
@@ -36,6 +36,8 @@
.Op Fl -remove-proxy Ar principal
.Op Fl -set-auto-schedule Ar true|false
.Op Fl -get-auto-schedule
+.Op Fl -set-auto-schedule-mode Ar none|accept-always|decline-always|accept-if-free|decline-if-busy|automatic
+.Op Fl -get-auto-schedule-mode
.Op Fl -add Ar locations|resources full-name [record-name] [GUID]
.Op Fl -remove
.Ar principal
Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/twistedcaldav/customxml.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2011 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2012 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -674,6 +674,13 @@
namespace = calendarserver_namespace
name = "auto-schedule"
+class AutoScheduleMode (davxml.WebDAVTextElement):
+ """
+ The principal's auto-schedule mode
+ """
+ namespace = calendarserver_namespace
+ name = "auto-schedule-mode"
+
##
# Sharing
##
Modified: CalendarServer/trunk/twistedcaldav/directory/augment.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/augment.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/twistedcaldav/directory/augment.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2009-2011 Apple Inc. All rights reserved.
+# Copyright (c) 2009-2012 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -35,6 +35,16 @@
log = Logger()
+allowedAutoScheduleModes = frozenset((
+ "default",
+ "none",
+ "accept-always",
+ "decline-always",
+ "accept-if-free",
+ "decline-if-busy",
+ "automatic",
+))
+
class AugmentRecord(object):
"""
Augmented directory record information
@@ -48,6 +58,7 @@
partitionID="",
enabledForCalendaring=False,
autoSchedule=False,
+ autoScheduleMode="default",
enabledForAddressBooks=False,
enabledForLogin=True,
):
@@ -59,6 +70,7 @@
self.enabledForAddressBooks = enabledForAddressBooks
self.enabledForLogin = enabledForLogin
self.autoSchedule = autoSchedule
+ self.autoScheduleMode = autoScheduleMode if autoScheduleMode in allowedAutoScheduleModes else "default"
self.clonedFromDefault = False
recordTypesMap = {
@@ -417,6 +429,8 @@
addSubElement(recordNode, xmlaugmentsparser.ELEMENT_ENABLEADDRESSBOOK, "true" if record.enabledForAddressBooks else "false")
addSubElement(recordNode, xmlaugmentsparser.ELEMENT_ENABLELOGIN, "true" if record.enabledForLogin else "false")
addSubElement(recordNode, xmlaugmentsparser.ELEMENT_AUTOSCHEDULE, "true" if record.autoSchedule else "false")
+ if record.autoScheduleMode:
+ addSubElement(recordNode, xmlaugmentsparser.ELEMENT_AUTOSCHEDULE_MODE, record.autoScheduleMode)
def refresh(self):
"""
@@ -496,7 +510,7 @@
DBAPI based augment database implementation.
"""
- schema_version = "1"
+ schema_version = "2"
schema_type = "AugmentDB"
def __init__(self, dbID, dbapiName, dbapiArgs, **kwargs):
@@ -528,11 +542,11 @@
"""
# Query for the record information
- results = (yield self.query("select UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, LOGINENABLED from AUGMENTS where UID = :1", (uid,)))
+ results = (yield self.query("select UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, AUTOSCHEDULEMODE, LOGINENABLED from AUGMENTS where UID = :1", (uid,)))
if not results:
returnValue(None)
else:
- uid, enabled, serverid, partitionid, enabledForCalendaring, enabledForAddressBooks, autoSchedule, enabledForLogin = results[0]
+ uid, enabled, serverid, partitionid, enabledForCalendaring, enabledForAddressBooks, autoSchedule, autoScheduleMode, enabledForLogin = results[0]
record = AugmentRecord(
uid = uid,
@@ -543,6 +557,7 @@
enabledForAddressBooks = enabledForAddressBooks == "T",
enabledForLogin = enabledForLogin == "T",
autoSchedule = autoSchedule == "T",
+ autoScheduleMode = autoScheduleMode,
)
returnValue(record)
@@ -597,14 +612,15 @@
yield self._create_table(
"AUGMENTS",
(
- ("UID", "text unique"),
- ("ENABLED", "text(1)"),
- ("SERVERID", "text"),
- ("PARTITIONID", "text"),
- ("CALENDARING", "text(1)"),
- ("ADDRESSBOOKS", "text(1)"),
- ("AUTOSCHEDULE", "text(1)"),
- ("LOGINENABLED", "text(1)"),
+ ("UID", "text unique"),
+ ("ENABLED", "text(1)"),
+ ("SERVERID", "text"),
+ ("PARTITIONID", "text"),
+ ("CALENDARING", "text(1)"),
+ ("ADDRESSBOOKS", "text(1)"),
+ ("AUTOSCHEDULE", "text(1)"),
+ ("AUTOSCHEDULEMODE", "text"),
+ ("LOGINENABLED", "text(1)"),
),
ifnotexists=True,
)
@@ -627,8 +643,8 @@
def _addRecord(self, record):
yield self.execute(
"""insert or replace into AUGMENTS
- (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, LOGINENABLED)
- values (:1, :2, :3, :4, :5, :6, :7, :8)""",
+ (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, AUTOSCHEDULEMODE, LOGINENABLED)
+ values (:1, :2, :3, :4, :5, :6, :7, :8, :9)""",
(
record.uid,
"T" if record.enabled else "F",
@@ -637,6 +653,7 @@
"T" if record.enabledForCalendaring else "F",
"T" if record.enabledForAddressBooks else "F",
"T" if record.autoSchedule else "F",
+ record.autoScheduleMode if record.autoScheduleMode else "",
"T" if record.enabledForLogin else "F",
)
)
@@ -658,8 +675,8 @@
def _addRecord(self, record):
yield self.execute(
"""insert into AUGMENTS
- (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, LOGINENABLED)
- values (:1, :2, :3, :4, :5, :6, :7, :8)""",
+ (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, AUTOSCHEDULEMODE, LOGINENABLED)
+ values (:1, :2, :3, :4, :5, :6, :7, :8, :9)""",
(
record.uid,
"T" if record.enabled else "F",
@@ -668,6 +685,7 @@
"T" if record.enabledForCalendaring else "F",
"T" if record.enabledForAddressBooks else "F",
"T" if record.autoSchedule else "F",
+ record.autoScheduleMode if record.autoScheduleMode else "",
"T" if record.enabledForLogin else "F",
)
)
@@ -676,8 +694,8 @@
def _modifyRecord(self, record):
yield self.execute(
"""update AUGMENTS set
- (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, LOGINENABLED) =
- (:1, :2, :3, :4, :5, :6, :7 :8) where UID = :9""",
+ (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, AUTOSCHEDULEMODE, LOGINENABLED) =
+ (:1, :2, :3, :4, :5, :6, :7, :8, :9) where UID = :10""",
(
record.uid,
"T" if record.enabled else "F",
@@ -686,6 +704,7 @@
"T" if record.enabledForCalendaring else "F",
"T" if record.enabledForAddressBooks else "F",
"T" if record.autoSchedule else "F",
+ record.autoScheduleMode if record.autoScheduleMode else "",
"T" if record.enabledForLogin else "F",
record.uid,
)
Modified: CalendarServer/trunk/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/directory.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/twistedcaldav/directory/directory.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,6 +1,6 @@
# -*- test-case-name: twistedcaldav.directory.test -*-
##
-# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2012 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -59,7 +59,6 @@
from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
from twisted.application import service
from twisted.plugin import IPlugin
-from zope.interface import implements
from xml.parsers.expat import ExpatError
from plistlib import readPlistFromString
@@ -928,7 +927,9 @@
self, service, recordType, guid=None,
shortNames=(), authIDs=set(), fullName=None,
firstName=None, lastName=None, emailAddresses=set(),
- calendarUserAddresses=set(), autoSchedule=False, enabledForCalendaring=None,
+ calendarUserAddresses=set(),
+ autoSchedule=False, autoScheduleMode=None,
+ enabledForCalendaring=None,
enabledForAddressBooks=None,
uid=None,
enabledForLogin=True,
@@ -962,6 +963,7 @@
self.emailAddresses = emailAddresses
self.enabledForCalendaring = enabledForCalendaring
self.autoSchedule = autoSchedule
+ self.autoScheduleMode = autoScheduleMode
self.enabledForAddressBooks = enabledForAddressBooks
self.enabledForLogin = enabledForLogin
self.extProxies = extProxies
@@ -1017,6 +1019,7 @@
self.enabledForCalendaring = augment.enabledForCalendaring
self.enabledForAddressBooks = augment.enabledForAddressBooks
self.autoSchedule = augment.autoSchedule
+ self.autoScheduleMode = augment.autoScheduleMode
self.enabledForLogin = augment.enabledForLogin
if (self.enabledForCalendaring or self.enabledForAddressBooks) and self.recordType == self.service.recordType_groups:
Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -52,23 +52,21 @@
except ImportError:
NegotiateCredentials = None
from twisted.python.modules import getModule
+
+from twistedcaldav import caldavxml, customxml
+from twistedcaldav.cache import DisabledCacheNotifier, PropfindCacheMixin
from twistedcaldav.config import config
-from twistedcaldav.cache import DisabledCacheNotifier, PropfindCacheMixin
-
-from twistedcaldav.extensions import DirectoryElement
-
+from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.directory.augment import allowedAutoScheduleModes
from twistedcaldav.directory.common import uidsResourceName
from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.directory.idirectory import IDirectoryService
+from twistedcaldav.directory.wiki import getWikiACL
+from twistedcaldav.extensions import DirectoryElement
from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVPrincipalResource,\
DAVResourceWithChildrenMixin
-from twistedcaldav.resource import (
- CalendarPrincipalCollectionResource, CalendarPrincipalResource
-)
-from twistedcaldav.directory.idirectory import IDirectoryService
-from twistedcaldav import caldavxml, customxml
-from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.resource import CalendarPrincipalCollectionResource, CalendarPrincipalResource
from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
-from twistedcaldav.directory.wiki import getWikiACL
thisModule = getModule(__name__)
log = Logger()
@@ -964,6 +962,27 @@
def getAutoSchedule(self):
return self.record.autoSchedule
+ def canAutoSchedule(self):
+ """
+ Determine the auto-schedule state based on record state, type and config settings.
+ """
+
+ if config.Scheduling.Options.AutoSchedule.Enabled:
+ if config.Scheduling.Options.AutoSchedule.Always or self.getAutoSchedule():
+ if self.getCUType() != "INDIVIDUAL" or config.Scheduling.Options.AutoSchedule.AllowUsers:
+ return True
+ return False
+
+ @inlineCallbacks
+ def setAutoScheduleMode(self, autoScheduleMode):
+ self.record.autoScheduleMode = autoScheduleMode if autoScheduleMode in allowedAutoScheduleModes else "default"
+ augmentRecord = (yield self.record.service.augmentService.getAugmentRecord(self.record.guid, self.record.recordType))
+ augmentRecord.autoScheduleMode = autoScheduleMode
+ (yield self.record.service.augmentService.addAugmentRecords([augmentRecord]))
+
+ def getAutoScheduleMode(self):
+ return self.record.autoScheduleMode
+
def getCUType(self):
return self.record.getCUType()
Modified: CalendarServer/trunk/twistedcaldav/directory/test/augments-test-default.xml
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/augments-test-default.xml 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/twistedcaldav/directory/test/augments-test-default.xml 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+Copyright (c) 2009-2012 Apple Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -77,6 +77,7 @@
<enable-calendar>true</enable-calendar>
<enable-addressbook>true</enable-addressbook>
<auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>accept-always</auto-schedule-mode>
</record>
<record>
<uid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</uid>
@@ -117,6 +118,24 @@
<auto-schedule>true</auto-schedule>
</record>
<record>
+ <uid>C5BAADEE-6B35-4FD5-A98A-5DF6BBAAC47A</uid>
+ <enable>true</enable>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode/>
+ </record>
+ <record>
+ <uid>8AB34DF9-0297-4BA3-AADB-DB557DDD21E7</uid>
+ <enable>true</enable>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>accept-always</auto-schedule-mode>
+ </record>
+ <record>
<uid>FC674703-8008-4A77-B80E-0DB55A9CE620</uid>
<enable-login>false</enable-login>
</record>
Modified: CalendarServer/trunk/twistedcaldav/directory/test/augments-test.xml
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/augments-test.xml 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/twistedcaldav/directory/test/augments-test.xml 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+Copyright (c) 2009-2012 Apple Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -59,6 +59,24 @@
<auto-schedule>true</auto-schedule>
</record>
<record>
+ <uid>C5BAADEE-6B35-4FD5-A98A-5DF6BBAAC47A</uid>
+ <enable>true</enable>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode/>
+ </record>
+ <record>
+ <uid>8AB34DF9-0297-4BA3-AADB-DB557DDD21E7</uid>
+ <enable>true</enable>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>accept-always</auto-schedule-mode>
+ </record>
+ <record>
<uid>FC674703-8008-4A77-B80E-0DB55A9CE620</uid>
<enable-login>false</enable-login>
</record>
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_augment.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_augment.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_augment.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2009-2012 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -28,44 +28,46 @@
xmlFileDefault = os.path.join(os.path.dirname(__file__), "augments-test-default.xml")
testRecords = (
- {"uid":"D11F03A0-97EA-48AF-9A6C-FAC7F3975766", "enabled":True, "partitionID":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
- {"uid":"6423F94A-6B76-4A3A-815B-D52CFD77935D", "enabled":True, "partitionID":"", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":False},
- {"uid":"5A985493-EE2C-4665-94CF-4DFEA3A89500", "enabled":False, "partitionID":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
- {"uid":"8B4288F6-CC82-491D-8EF9-642EF4F3E7D0", "enabled":True, "partitionID":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
- {"uid":"5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1", "enabled":True, "partitionID":"00001", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
- {"uid":"543D28BA-F74F-4D5F-9243-B3E3A61171E5", "enabled":True, "partitionID":"00002", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
- {"uid":"6A73326A-F781-47E7-A9F8-AF47364D4152", "enabled":True, "partitionID":"00002", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":True},
+ {"uid":"D11F03A0-97EA-48AF-9A6C-FAC7F3975766", "enabled":True, "partitionID":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False, "autoScheduleMode":"default"},
+ {"uid":"6423F94A-6B76-4A3A-815B-D52CFD77935D", "enabled":True, "partitionID":"", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":False, "autoScheduleMode":"default"},
+ {"uid":"5A985493-EE2C-4665-94CF-4DFEA3A89500", "enabled":False, "partitionID":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False, "autoScheduleMode":"default"},
+ {"uid":"8B4288F6-CC82-491D-8EF9-642EF4F3E7D0", "enabled":True, "partitionID":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False, "autoScheduleMode":"default"},
+ {"uid":"5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1", "enabled":True, "partitionID":"00001", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False, "autoScheduleMode":"default"},
+ {"uid":"543D28BA-F74F-4D5F-9243-B3E3A61171E5", "enabled":True, "partitionID":"00002", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False, "autoScheduleMode":"default"},
+ {"uid":"6A73326A-F781-47E7-A9F8-AF47364D4152", "enabled":True, "partitionID":"00002", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":True, "autoScheduleMode":"default"},
+ {"uid":"C5BAADEE-6B35-4FD5-A98A-5DF6BBAAC47A", "enabled":True, "partitionID":"", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":True, "autoScheduleMode":"default"},
+ {"uid":"8AB34DF9-0297-4BA3-AADB-DB557DDD21E7", "enabled":True, "partitionID":"", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":True, "autoScheduleMode":"accept-always"},
{"uid":"FC674703-8008-4A77-B80E-0DB55A9CE620", "enabledForLogin":False,}, # Explicitly false
{"uid":"B473DC32-1B0D-45EE-9BAC-DA878AE9CE74", "enabledForLogin":True,}, # Explicitly True
{"uid":"9F2B176D-B3F5-483A-AA63-0A1FC6E6D54B", "enabledForLogin":True,}, # Default is True
)
testRecordWildcardDefault = (
- {"uid":"A4318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True, "partitionID":"00001", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":False},
- {"uid":"AA5F935F-3358-4510-A649-B391D63279F2", "enabled":True, "partitionID":"00001", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
- {"uid":"ABF1A83B-1A29-4E04-BDC3-A6A66ECF27CA", "enabled":False, "partitionID":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
- {"uid":"BC22A734-5E41-4FB7-B5C1-51DC0656DC2F", "enabled":True, "partitionID":"00002", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":False},
- {"uid":"C6DEEBB1-E14A-47F2-98BA-7E3BB4353E3A", "enabled":True, "partitionID":"00003", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":True },
- {"uid":"AA859321-2C72-4974-ADCF-0CBA0C76F95D", "enabled":True, "partitionID":"00001", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
- {"uid":"AB7C488B-9ED2-4265-881C-7E2E38A63584", "enabled":False, "partitionID":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
- {"uid":"BB0C0DA1-0545-45F6-8D08-917C554D93A4", "enabled":True, "partitionID":"00002", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":False},
- {"uid":"CCD30AD3-582F-4682-8B65-2EDE92C5656E", "enabled":True, "partitionID":"00003", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":True },
+ {"uid":"A4318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True, "partitionID":"00001", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":False, "autoScheduleMode":"default"},
+ {"uid":"AA5F935F-3358-4510-A649-B391D63279F2", "enabled":True, "partitionID":"00001", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False, "autoScheduleMode":"default"},
+ {"uid":"ABF1A83B-1A29-4E04-BDC3-A6A66ECF27CA", "enabled":False, "partitionID":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False, "autoScheduleMode":"default"},
+ {"uid":"BC22A734-5E41-4FB7-B5C1-51DC0656DC2F", "enabled":True, "partitionID":"00002", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":False, "autoScheduleMode":"default"},
+ {"uid":"C6DEEBB1-E14A-47F2-98BA-7E3BB4353E3A", "enabled":True, "partitionID":"00003", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":True, "autoScheduleMode":"accept-always"},
+ {"uid":"AA859321-2C72-4974-ADCF-0CBA0C76F95D", "enabled":True, "partitionID":"00001", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False, "autoScheduleMode":"default"},
+ {"uid":"AB7C488B-9ED2-4265-881C-7E2E38A63584", "enabled":False, "partitionID":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False, "autoScheduleMode":"default"},
+ {"uid":"BB0C0DA1-0545-45F6-8D08-917C554D93A4", "enabled":True, "partitionID":"00002", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":False, "autoScheduleMode":"default"},
+ {"uid":"CCD30AD3-582F-4682-8B65-2EDE92C5656E", "enabled":True, "partitionID":"00003", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":True, "autoScheduleMode":"accept-always"},
)
testRecordTypeDefault = (
- ("locations", {"uid":"A4318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True, "partitionID":"00004", "enabledForCalendaring":True, "enabledForAddressBooks":False, "autoSchedule":True}),
- ("locations", {"uid":"AA5F935F-3358-4510-A649-B391D63279F2", "enabled":True, "partitionID":"00005", "enabledForCalendaring":True, "enabledForAddressBooks":False, "autoSchedule":True}),
- ("resources", {"uid":"A5318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True, "partitionID":"00006", "enabledForCalendaring":True, "enabledForAddressBooks":False, "autoSchedule":True}),
- ("resources", {"uid":"AA6F935F-3358-4510-A649-B391D63279F2", "enabled":True, "partitionID":"00007", "enabledForCalendaring":True, "enabledForAddressBooks":False, "autoSchedule":True}),
+ ("locations", {"uid":"A4318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True, "partitionID":"00004", "enabledForCalendaring":True, "enabledForAddressBooks":False, "autoSchedule":True, "autoScheduleMode":"default"}),
+ ("locations", {"uid":"AA5F935F-3358-4510-A649-B391D63279F2", "enabled":True, "partitionID":"00005", "enabledForCalendaring":True, "enabledForAddressBooks":False, "autoSchedule":True, "autoScheduleMode":"default"}),
+ ("resources", {"uid":"A5318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True, "partitionID":"00006", "enabledForCalendaring":True, "enabledForAddressBooks":False, "autoSchedule":True, "autoScheduleMode":"default"}),
+ ("resources", {"uid":"AA6F935F-3358-4510-A649-B391D63279F2", "enabled":True, "partitionID":"00007", "enabledForCalendaring":True, "enabledForAddressBooks":False, "autoSchedule":True, "autoScheduleMode":"default"}),
)
testAddRecords = (
- {"uid":"D11F03A0-97EA-48AF-9A6C-FAC7F3975767", "enabled":True, "partitionID":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
+ {"uid":"D11F03A0-97EA-48AF-9A6C-FAC7F3975767", "enabled":True, "partitionID":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False, "autoScheduleMode":"default"},
)
testModifyRecords = (
- {"uid":"D11F03A0-97EA-48AF-9A6C-FAC7F3975767", "enabled":True, "partitionID":"", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":False},
+ {"uid":"D11F03A0-97EA-48AF-9A6C-FAC7F3975767", "enabled":True, "partitionID":"", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":False, "autoScheduleMode":"default"},
)
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2011 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2012 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -556,6 +556,40 @@
self.failIf(inboxURL)
self.failIf(outboxURL)
+ def test_canAutoSchedule(self):
+ """
+ DirectoryPrincipalResource.canAutoSchedule()
+ """
+
+ # Set all resources and locations to auto-schedule, plus one user
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ if record.enabledForCalendaring:
+ if recordType in ("locations", "resources") or record.uid == "cdaboo":
+ recordResource.record.autoSchedule = True
+
+ # Default state - resources and locations, enabled, others not
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ if record.enabledForCalendaring:
+ if recordType in ("locations", "resources"):
+ self.assertTrue(recordResource.canAutoSchedule())
+ else:
+ self.assertFalse(recordResource.canAutoSchedule())
+
+ # Set config to allow users
+ self.patch(config.Scheduling.Options.AutoSchedule, "AllowUsers", True)
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ if record.enabledForCalendaring:
+ if recordType in ("locations", "resources") or record.uid == "cdaboo":
+ self.assertTrue(recordResource.canAutoSchedule())
+ else:
+ self.assertFalse(recordResource.canAutoSchedule())
+
+ # Set config to disallow all
+ self.patch(config.Scheduling.Options.AutoSchedule, "Enabled", False)
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ if record.enabledForCalendaring:
+ self.assertFalse(recordResource.canAutoSchedule())
+
@inlineCallbacks
def test_defaultAccessControlList_principals(self):
"""
Modified: CalendarServer/trunk/twistedcaldav/directory/xmlaugmentsparser.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/xmlaugmentsparser.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/twistedcaldav/directory/xmlaugmentsparser.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2009-2011 Apple Inc. All rights reserved.
+# Copyright (c) 2009-2012 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -42,6 +42,7 @@
ELEMENT_ENABLEADDRESSBOOK = "enable-addressbook"
ELEMENT_ENABLELOGIN = "enable-login"
ELEMENT_AUTOSCHEDULE = "auto-schedule"
+ELEMENT_AUTOSCHEDULE_MODE = "auto-schedule-mode"
ATTRIBUTE_REPEAT = "repeat"
@@ -58,6 +59,7 @@
ELEMENT_ENABLEADDRESSBOOK: "enabledForAddressBooks",
ELEMENT_ENABLELOGIN: "enabledForLogin",
ELEMENT_AUTOSCHEDULE: "autoSchedule",
+ ELEMENT_AUTOSCHEDULE_MODE: "autoScheduleMode",
}
class XMLAugmentsParser(object):
@@ -100,6 +102,7 @@
ELEMENT_SERVERID,
ELEMENT_PARTITIONID,
ELEMENT_HOSTEDAT,
+ ELEMENT_AUTOSCHEDULE_MODE,
):
fields[node.tag] = node.text if node.text else ""
elif node.tag in (
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,6 +1,6 @@
# -*- test-case-name: twistedcaldav.test.test_resource,twistedcaldav.test.test_wrapping -*-
##
-# Copyright (c) 2005-2011 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2012 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -1872,6 +1872,7 @@
(calendarserver_namespace, "calendar-proxy-read-for" ),
(calendarserver_namespace, "calendar-proxy-write-for" ),
(calendarserver_namespace, "auto-schedule" ),
+ (calendarserver_namespace, "auto-schedule-mode" ),
)
if self.addressBooksEnabled():
@@ -1967,6 +1968,10 @@
autoSchedule = self.getAutoSchedule()
returnValue(customxml.AutoSchedule("true" if autoSchedule else "false"))
+ elif name == "auto-schedule-mode" and self.calendarsEnabled():
+ autoScheduleMode = self.getAutoScheduleMode()
+ returnValue(customxml.AutoScheduleMode(autoScheduleMode if autoScheduleMode else "default"))
+
elif namespace == carddav_namespace and self.addressBooksEnabled():
if name == "addressbook-home-set":
returnValue(carddavxml.AddressBookHomeSet(
Modified: CalendarServer/trunk/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/itip.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/twistedcaldav/scheduling/itip.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2011 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2012 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -50,7 +50,7 @@
class iTipProcessing(object):
@staticmethod
- def processNewRequest(itip_message, recipient=None, autoprocessing=False):
+ def processNewRequest(itip_message, recipient=None):
"""
Process a METHOD=REQUEST for a brand new calendar object.
@@ -65,14 +65,14 @@
method = calendar.getProperty("METHOD")
if method:
calendar.removeProperty(method)
-
- if recipient and not autoprocessing:
- iTipProcessing.fixForiCal3(calendar.subcomponents(), recipient, config.Scheduling.CalDAV.OldDraftCompatibility)
+ if recipient:
+ iTipProcessing.addTranspForNeedsAction(calendar.subcomponents(), recipient)
+
return calendar
@staticmethod
- def processRequest(itip_message, calendar, recipient, autoprocessing=False):
+ def processRequest(itip_message, calendar, recipient):
"""
Process a METHOD=REQUEST. We need to merge per-attendee properties such as TRANPS, COMPLETED etc
with the data coming from the organizer.
@@ -117,7 +117,7 @@
if itip_message.masterComponent() is not None:
# Get a new calendar object first
- new_calendar = iTipProcessing.processNewRequest(itip_message, recipient, autoprocessing)
+ new_calendar = iTipProcessing.processNewRequest(itip_message, recipient)
# Copy over master alarms, comments
master_component = new_calendar.masterComponent()
@@ -167,8 +167,8 @@
component = component.duplicate()
iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, component, remove_matched=True)
calendar.addComponent(component)
- if recipient and not autoprocessing:
- iTipProcessing.fixForiCal3((component,), recipient, config.Scheduling.CalDAV.OldDraftCompatibility)
+ if recipient:
+ iTipProcessing.addTranspForNeedsAction((component,), recipient)
# Write back the modified object
return calendar, rids
@@ -507,21 +507,15 @@
organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
@staticmethod
- def fixForiCal3(components, recipient, compatibilityMode):
+ def addTranspForNeedsAction(components, recipient):
# For each component where the ATTENDEE property of the recipient has PARTSTAT
- # NEEDS-ACTION we need to add X-APPLE-NEEDS-REPLY:TRUE
- # We also add TRANSP:TRANSPARENT for VEVENTs
+ # NEEDS-ACTION we add TRANSP:TRANSPARENT for VEVENTs
for component in components:
- if component.name() == "VTIMEZONE":
+ if component.name() != "VEVENT":
continue
attendee = component.getAttendeeProperty((recipient,))
- if attendee:
- partstat = attendee.parameterValue("PARTSTAT", "NEEDS-ACTION")
- if partstat == "NEEDS-ACTION":
- if compatibilityMode:
- component.addProperty(Property("X-APPLE-NEEDS-REPLY", "TRUE"))
- if component.name() == "VEVENT":
- component.replaceProperty(Property("TRANSP", "TRANSPARENT"))
+ if attendee and attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "NEEDS-ACTION":
+ component.replaceProperty(Property("TRANSP", "TRANSPARENT"))
@staticmethod
def sequenceComparison(itip, calendar):
Modified: CalendarServer/trunk/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/processing.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/twistedcaldav/scheduling/processing.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -454,18 +454,22 @@
raise ImplicitProcessorException(iTIPRequestStatus.NO_USER_SUPPORT)
log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - new processed" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
- autoprocessed = self.recipient.principal.getAutoSchedule()
- store_inbox = not autoprocessed or self.recipient.principal.getCUType() == "INDIVIDUAL"
- new_calendar = iTipProcessing.processNewRequest(self.message, self.recipient.cuaddr, autoprocessing=autoprocessed)
+ new_calendar = iTipProcessing.processNewRequest(self.message, self.recipient.cuaddr)
name = md5(str(new_calendar) + str(time.time()) + default.url()).hexdigest() + ".ics"
# Handle auto-reply behavior
- if autoprocessed:
- send_reply, partstat = (yield self.checkAttendeeAutoReply(new_calendar))
+ if self.recipient.principal.canAutoSchedule():
+ send_reply, store_inbox, partstat = (yield self.checkAttendeeAutoReply(new_calendar, self.recipient.principal.getAutoScheduleMode()))
+
+ # Only store inbox item when reply is not sent or always for users
+ store_inbox = store_inbox or self.recipient.principal.getCUType() == "INDIVIDUAL"
+ else:
+ send_reply = False
+ store_inbox = True
new_resource = (yield self.writeCalendarResource(default.url(), default, name, new_calendar))
- if autoprocessed and send_reply:
+ if send_reply:
# Track outstanding auto-reply processing
if not hasattr(self.request, "auto_reply_processing_count"):
self.request.auto_reply_processing_count = 1
@@ -480,23 +484,27 @@
customxml.Create(),
),
)
- result = (True, autoprocessed, store_inbox, changes,)
+ result = (True, send_reply, store_inbox, changes,)
else:
# Processing update to existing event
- autoprocessed = self.recipient.principal.getAutoSchedule()
- store_inbox = not autoprocessed or self.recipient.principal.getCUType() == "INDIVIDUAL"
- new_calendar, rids = iTipProcessing.processRequest(self.message, self.recipient_calendar, self.recipient.cuaddr, autoprocessing=autoprocessed)
+ new_calendar, rids = iTipProcessing.processRequest(self.message, self.recipient_calendar, self.recipient.cuaddr)
if new_calendar:
# Handle auto-reply behavior
- if autoprocessed:
- send_reply, partstat = (yield self.checkAttendeeAutoReply(new_calendar))
+ if self.recipient.principal.canAutoSchedule():
+ send_reply, store_inbox, partstat = (yield self.checkAttendeeAutoReply(new_calendar, self.recipient.principal.getAutoScheduleMode()))
+
+ # Only store inbox item when reply is not sent or always for users
+ store_inbox = store_inbox or self.recipient.principal.getCUType() == "INDIVIDUAL"
+ else:
+ send_reply = False
+ store_inbox = True
# Update the attendee's copy of the event
log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - updating event" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
new_resource = (yield self.writeCalendarResource(self.recipient_calendar_collection_uri, self.recipient_calendar_collection, self.recipient_calendar_name, new_calendar))
- if autoprocessed and send_reply:
+ if send_reply:
# Track outstanding auto-reply processing
if not hasattr(self.request, "auto_reply_processing_count"):
self.request.auto_reply_processing_count = 1
@@ -530,7 +538,7 @@
if hasattr(self.request, "doing_attendee_refresh"):
store_inbox = False
- result = (True, autoprocessed, store_inbox, changes,)
+ result = (True, send_reply, store_inbox, changes,)
else:
# Request needs to be ignored
@@ -654,7 +662,7 @@
self.request.auto_reply_processing_count -= 1
@inlineCallbacks
- def checkAttendeeAutoReply(self, calendar):
+ def checkAttendeeAutoReply(self, calendar, automode):
"""
Check whether a reply to the given iTIP message is needed. We will not process a reply
A reply will either be positive (accepted invitation) or negative (denied invitation).
@@ -664,94 +672,120 @@
BTW The incoming iTIP message may contain multiple components so we need to iterate over all those.
At the moment we will treat a failure on one instance as a DECLINE of the entire set.
- @return: C{bool} indicating whether changes were made.
+ @param calendar: the iTIP message to process
+ @type calendar: L{Component}
+ @param automode: the auto-schedule mode for the recipient
+ @type automode: C{str}
+
+ @return: C{tuple} of C{bool}, C{bool}, C{str} indicating whether changes were made, whether the inbox item
+ should be added, and the new PARTSTAT.
"""
- log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - checking for auto-reply" % (self.recipient.cuaddr, self.uid))
+ # First ignore the none mode
+ if automode == "none":
+ returnValue((False, True, "",))
+ elif not automode or automode == "default":
+ automode = config.Scheduling.Options.AutoSchedule.DefaultMode
- # First expand current one to get instances (only go 1 year into the future)
- default_future_expansion_duration = PyCalendarDuration(days=356*1)
- expand_max = PyCalendarDateTime.getToday() + default_future_expansion_duration
- instances = calendar.expandTimeRanges(expand_max, ignoreInvalidInstances=True)
- instance_states = dict([(instance, True) for instance in instances.instances.itervalues()])
+ log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - checking for auto-reply with mode: %s" % (self.recipient.cuaddr, self.uid, automode,))
+
+ # The accept-always and decline-always modes do not need any freebusy checks
+ if automode in ("accept-always", "decline-always",):
+ all_accepted = automode == "accept-always"
+ all_declined = automode == "decline-always"
- # Extract UID from primary component as we want to ignore this one if we match it
- # in any calendars.
- comp = calendar.mainComponent(allow_multiple=True)
- uid = comp.propertyValue("UID")
+ # Other modes need freebusy check
+ else:
+ # First expand current one to get instances (only go 1 year into the future)
+ default_future_expansion_duration = PyCalendarDuration(days=356*1)
+ expand_max = PyCalendarDateTime.getToday() + default_future_expansion_duration
+ instances = calendar.expandTimeRanges(expand_max, ignoreInvalidInstances=True)
+ instance_states = dict([(instance, True) for instance in instances.instances.itervalues()])
+
+ # Extract UID from primary component as we want to ignore this one if we match it
+ # in any calendars.
+ comp = calendar.mainComponent(allow_multiple=True)
+ uid = comp.propertyValue("UID")
+
+ # Now compare each instance time-range with the index and see if there is an overlap
+ calendars = (yield self._getCalendarsToMatch())
+
+ for calURL in calendars:
+ testcal = (yield self.request.locateResource(calURL))
- # Now compare each instance time-range with the index and see if there is an overlap
- calendars = (yield self._getCalendarsToMatch())
+ # Get the timezone property from the collection, and store in the query filter
+ # for use during the query itself.
+ has_prop = (yield testcal.hasProperty((caldav_namespace, "calendar-timezone"), self.request))
+ if has_prop:
+ tz = (yield testcal.readProperty((caldav_namespace, "calendar-timezone"), self.request))
+ tzinfo = tz.calendar().gettimezone()
+ else:
+ tzinfo = PyCalendarTimezone(utc=True)
- for calURL in calendars:
- testcal = (yield self.request.locateResource(calURL))
-
- # Get the timezone property from the collection, and store in the query filter
- # for use during the query itself.
- has_prop = (yield testcal.hasProperty((caldav_namespace, "calendar-timezone"), self.request))
- if has_prop:
- tz = (yield testcal.readProperty((caldav_namespace, "calendar-timezone"), self.request))
- tzinfo = tz.calendar().gettimezone()
- else:
- tzinfo = PyCalendarTimezone(utc=True)
-
- # Now do search for overlapping time-range
- for instance in instances.instances.itervalues():
- if instance_states[instance]:
- try:
- # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
- fbinfo = ([], [], [])
-
- def makeTimedUTC(dt):
- dt = dt.duplicate()
- if dt.isDateOnly():
- dt.setDateOnly(False)
- dt.setHHMMSS(0, 0, 0)
- if dt.floating():
- dt.setTimezone(tzinfo)
- dt.adjustToUTC()
- return dt
-
- tr = caldavxml.TimeRange(
- start=str(makeTimedUTC(instance.start)),
- end=str(makeTimedUTC(instance.end)),
- )
-
- yield report_common.generateFreeBusyInfo(self.request, testcal, fbinfo, tr, 0, uid, servertoserver=True)
-
- # If any fbinfo entries exist we have an overlap
- if len(fbinfo[0]) or len(fbinfo[1]) or len(fbinfo[2]):
+ # Now do search for overlapping time-range
+ for instance in instances.instances.itervalues():
+ if instance_states[instance]:
+ try:
+ # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
+ fbinfo = ([], [], [])
+
+ def makeTimedUTC(dt):
+ dt = dt.duplicate()
+ if dt.isDateOnly():
+ dt.setDateOnly(False)
+ dt.setHHMMSS(0, 0, 0)
+ if dt.floating():
+ dt.setTimezone(tzinfo)
+ dt.adjustToUTC()
+ return dt
+
+ tr = caldavxml.TimeRange(
+ start=str(makeTimedUTC(instance.start)),
+ end=str(makeTimedUTC(instance.end)),
+ )
+
+ yield report_common.generateFreeBusyInfo(self.request, testcal, fbinfo, tr, 0, uid, servertoserver=True)
+
+ # If any fbinfo entries exist we have an overlap
+ if len(fbinfo[0]) or len(fbinfo[1]) or len(fbinfo[2]):
+ instance_states[instance] = False
+ except NumberOfMatchesWithinLimits:
instance_states[instance] = False
- except NumberOfMatchesWithinLimits:
- instance_states[instance] = False
- log.info("Exceeded number of matches whilst trying to find free-time.")
+ log.info("Exceeded number of matches whilst trying to find free-time.")
+
+ # If everything is declined we can exit now
+ if not any(instance_states.itervalues()):
+ break
- # If everything is declined we can exit now
- if not any(instance_states.itervalues()):
- break
-
- # TODO: here we should do per-instance ACCEPT/DECLINE behavior
- # For now we will assume overall ACCEPT/DECLINE
+ # TODO: here we should do per-instance ACCEPT/DECLINE behavior
+ # For now we will assume overall ACCEPT/DECLINE
+
+ # Collect all the accepted and declined states
+ all_accepted = all(instance_states.itervalues())
+ all_declined = not any(instance_states.itervalues())
- # Collect all the accepted and declined states
- all_accepted = all(instance_states.itervalues())
- all_declined = not any(instance_states.itervalues())
-
# Do the simple case of all accepted or decline separately
cuas = self.recipient.principal.calendarUserAddresses()
if all_accepted or all_declined:
# Extract the ATTENDEE property matching current recipient from the calendar data
attendeeProps = calendar.getAttendeeProperties(cuas)
if not attendeeProps:
- returnValue((False, "",))
+ returnValue((False, True, "",))
- if all_accepted:
- partstat = "ACCEPTED"
+ if automode == "accept-always":
+ freePartstat = busyPartstat = "ACCEPTED"
+ elif automode == "decline-always":
+ freePartstat = busyPartstat = "DECLINED"
else:
- partstat = "DECLINED"
- calendar.replacePropertyInAllComponents(Property("TRANSP", "OPAQUE" if all_accepted else "TRANSPARENT"))
+ freePartstat = "ACCEPTED" if automode in ("accept-if-free", "automatic",) else "NEEDS-ACTION"
+ busyPartstat = "DECLINED" if automode in ("decline-if-busy", "automatic",) else "NEEDS-ACTION"
+ freeStateOpaque = freePartstat == "ACCEPTED"
+
+ partstat = freePartstat if all_accepted else busyPartstat
+ calendar.replacePropertyInAllComponents(Property("TRANSP", "OPAQUE" if all_accepted and freeStateOpaque else "TRANSPARENT"))
made_changes = self.changeAttendeePartstat(attendeeProps, partstat)
+ store_inbox = partstat == "NEEDS-ACTION"
else:
# Hard case: some accepted some declined
@@ -760,24 +794,34 @@
# any other declines.
made_changes = False
+ store_inbox = False
partstat = "MIXED RESPONSE"
+
+ freePartstat = "ACCEPTED" if automode in ("accept-if-free", "automatic",) else "NEEDS-ACTION"
+ busyPartstat = "DECLINED" if automode in ("decline-if-busy", "automatic",) else "NEEDS-ACTION"
+ freeStateOpaque = freePartstat == "ACCEPTED"
- # Default state is whichever of ACCEPTED or DECLINED has most instances
- defaultStateAccepted = len(filter(lambda x:x, instance_states.values())) >= len(instance_states.keys()) / 2
+ # Default state is whichever of free or busy has most instances
+ defaultStateFree = len(filter(lambda x:x, instance_states.values())) >= len(instance_states.keys()) / 2
# See if there is a master component first
+ hadMasterRsvp = False
master = calendar.masterComponent()
if master:
attendee = master.getAttendeeProperty(cuas)
if attendee:
- made_changes |= self.changeAttendeePartstat(attendee, "ACCEPTED" if defaultStateAccepted else "DECLINED")
- master.replaceProperty(Property("TRANSP", "OPAQUE" if defaultStateAccepted else "TRANSPARENT"))
+ hadMasterRsvp = attendee.parameterValue("RSVP", "FALSE") == "TRUE"
+ new_partstat = freePartstat if defaultStateFree else busyPartstat
+ if new_partstat == "NEEDS-ACTION":
+ store_inbox = True
+ made_changes |= self.changeAttendeePartstat(attendee, new_partstat)
+ master.replaceProperty(Property("TRANSP", "OPAQUE" if defaultStateFree and freeStateOpaque else "TRANSPARENT"))
# Look at expanded instances and change partstat accordingly
- for instance, accepted in sorted(instance_states.iteritems(), key=lambda x: x[0].rid):
+ for instance, free in sorted(instance_states.iteritems(), key=lambda x: x[0].rid):
overridden = calendar.overriddenComponent(instance.rid)
- if not overridden and accepted == defaultStateAccepted:
+ if not overridden and free == defaultStateFree:
# Nothing to do as state matches the master
continue
@@ -785,16 +829,23 @@
# Change ATTENDEE property to match new state
attendee = overridden.getAttendeeProperty(cuas)
if attendee:
- made_changes |= self.changeAttendeePartstat(attendee, "ACCEPTED" if accepted else "DECLINED")
- overridden.replaceProperty(Property("TRANSP", "OPAQUE" if accepted else "TRANSPARENT"))
+ new_partstat = freePartstat if free else busyPartstat
+ if new_partstat == "NEEDS-ACTION":
+ store_inbox = True
+ made_changes |= self.changeAttendeePartstat(attendee, new_partstat)
+ overridden.replaceProperty(Property("TRANSP", "OPAQUE" if free and freeStateOpaque else "TRANSPARENT"))
else:
- # Derive a new overridden component and change partstat
+ # Derive a new overridden component and change partstat. We also need to make sure we restore any RSVP
+ # value that may have been overwritten by any change to the master itself.
derived = calendar.deriveInstance(instance.rid)
if derived:
attendee = derived.getAttendeeProperty(cuas)
if attendee:
- self.changeAttendeePartstat(attendee, "ACCEPTED" if accepted else "DECLINED")
- derived.replaceProperty(Property("TRANSP", "OPAQUE" if accepted else "TRANSPARENT"))
+ new_partstat = freePartstat if free else busyPartstat
+ if new_partstat == "NEEDS-ACTION":
+ store_inbox = True
+ self.changeAttendeePartstat(attendee, new_partstat, hadMasterRsvp)
+ derived.replaceProperty(Property("TRANSP", "OPAQUE" if free and freeStateOpaque else "TRANSPARENT"))
calendar.addComponent(derived)
made_changes = True
@@ -802,7 +853,7 @@
if made_changes:
calendar.setParameterToValueForPropertyWithValue("SCHEDULE-STATUS", iTIPRequestStatus.MESSAGE_DELIVERED_CODE, "ORGANIZER", None)
- returnValue((made_changes, partstat,))
+ returnValue((made_changes, store_inbox, partstat,))
def _getCalendarsToMatch(self):
# Determine the set of calendar URIs for a principal need to be searched.
@@ -868,7 +919,7 @@
yield delchild.storeRemove(self.request, False, childURL)
- def changeAttendeePartstat(self, attendees, partstat):
+ def changeAttendeePartstat(self, attendees, partstat, hadRSVP=False):
"""
Change the PARTSTAT on any ATTENDEE properties passed in.
@@ -876,6 +927,8 @@
@type attendees: L{Property}, C{list} or C{tuple}
@param partstat: new PARTSTAT to set
@type partstat: C{str}
+ @param hadRSVP: indicates whether RSVP should be added when changing to NEEDS-ACTION
+ @type hadRSVP: C{bool}
@return: C{True} if any change was made, C{False} otherwise
"""
@@ -889,10 +942,13 @@
attendee.setParameter("PARTSTAT", partstat)
madeChanges = True
- # Always remove RSVP - this is only an attendee change so madeChanges
- # does not need to be changed
+ # Always remove RSVP when a state other than NEEDS-ACTION is set - this
+ # is only an attendee change so madeChanges does not need to be changed
try:
- attendee.removeParameter("RSVP")
+ if attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") != "NEEDS-ACTION":
+ attendee.removeParameter("RSVP")
+ elif hadRSVP:
+ attendee.setParameter("RSVP", "TRUE")
except KeyError:
pass
Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py 2012-02-09 21:23:20 UTC (rev 8641)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2012-02-09 21:34:19 UTC (rev 8642)
@@ -633,13 +633,25 @@
"AllowGroupAsOrganizer" : False, # Allow groups to be Organizers
"AllowLocationAsOrganizer" : False, # Allow locations to be Organizers
"AllowResourceAsOrganizer" : False, # Allow resources to be Organizers
- "AllowUserAutoAccept" : False, # Allow auto-accept for users
"LimitFreeBusyAttendees" : 30, # Maximum number of attendees to request freebusy for
"AttendeeRefreshBatch" : 5, # Number of attendees to do batched refreshes: 0 - no batching
"AttendeeRefreshBatchDelaySeconds" : 5, # Time after an iTIP REPLY for first batched attendee refresh
"AttendeeRefreshBatchIntervalSeconds" : 5, # Time between attendee batch refreshes
"UIDLockTimeoutSeconds" : 60, # Time for implicit UID lock timeout
- "UIDLockExpirySeconds" : 300, # Expiration time for UID lock
+ "UIDLockExpirySeconds" : 300, # Expiration time for UID lock,
+
+ "AutoSchedule" : {
+ "Enabled" : True, # Auto-scheduling will never occur if set to False
+ "Always" : False, # Override augments setting and always auto-schedule
+ "AllowUsers" : False, # Allow auto-schedule for users
+ "DefaultMode" : "automatic", # Default mode for auto-schedule processing, one of:
+ # "none" - no auto-scheduling
+ # "accept-always" - always accept, ignore busy time
+ # "decline-always" - always decline, ignore free time
+ # "accept-if-free" - accept if free, do nothing if busy
+ # "decline-if-busy" - decline if busy, do nothing if free
+ # "automatic" - accept if free, decline if busy
+ }
}
},
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120209/228a23eb/attachment-0001.html>
More information about the calendarserver-changes
mailing list