[CalendarServer-changes] [5049] CalendarServer/branches/users/sagen/locations-resources

source_changes at macosforge.org source_changes at macosforge.org
Sun Feb 7 19:42:53 PST 2010


Revision: 5049
          http://trac.macosforge.org/projects/calendarserver/changeset/5049
Author:   sagen at apple.com
Date:     2010-02-07 19:42:50 -0800 (Sun, 07 Feb 2010)
Log Message:
-----------
Adds tests for servermgr command gateway and refactors tools/principals.py in order to share code

Modified Paths:
--------------
    CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/gateway.py
    CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/principals.py
    CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/util.py
    CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/aggregate.py
    CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/cachingdirectory.py
    CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/test/test_cachedirectory.py
    CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/test/test_modify.py
    CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/xmlfile.py
    CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/stdconfig.py

Added Paths:
-----------
    CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/
    CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/__init__.py
    CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/
    CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/augments.xml
    CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/caldavd.plist
    CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/resources-locations.xml
    CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/users-groups.xml
    CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/test_gateway.py

Modified: CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/gateway.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/gateway.py	2010-02-07 00:39:01 UTC (rev 5048)
+++ CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/gateway.py	2010-02-08 03:42:50 UTC (rev 5049)
@@ -16,22 +16,23 @@
 # limitations under the License.
 ##
 
-import sys
+from getopt import getopt, GetoptError
+from grp import getgrnam
+from pwd import getpwnam
 import os
 import plistlib
+import sys
 import xml
 
-import operator
-from getopt import getopt, GetoptError
-from pwd import getpwnam
-from grp import getgrnam
-
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks
 from twisted.python.util import switchUID
-
 from twistedcaldav.config import config, ConfigurationError
 from twistedcaldav.directory.directory import DirectoryError
+from twisted.web2.dav import davxml
 
-from calendarserver.tools.util import loadConfig, getDirectory, autoDisableMemcached
+from calendarserver.tools.util import loadConfig, getDirectory, setupMemcached, setupNotifications
+from calendarserver.tools.principals import principalForPrincipalID, proxySubprincipal, addProxy, removeProxy, ProxyError, ProxyWarning
 
 def usage(e=None):
 
@@ -93,7 +94,8 @@
             config.directory = getDirectory()
         except DirectoryError, e:
             abort(e)
-        autoDisableMemcached(config)
+        setupMemcached(config)
+        setupNotifications(config)
     except ConfigurationError, e:
         abort(e)
 
@@ -115,8 +117,12 @@
 
     runner = Runner(config.directory, commands)
     runner.validate()
-    runner.run()
 
+    #
+    # Start the reactor
+    #
+    reactor.callLater(0, runner.run)
+    reactor.run()
 
 
 class Runner(object):
@@ -135,21 +141,25 @@
             if not hasattr(self, methodName):
                 abort("Unknown command '%s'" % (commandName,))
 
+    @inlineCallbacks
     def run(self):
         for command in self.commands:
             commandName = command['command']
             methodName = "command_%s" % (commandName,)
             if hasattr(self, methodName):
-                getattr(self, methodName)(command)
+                (yield getattr(self, methodName)(command))
             else:
                 abort("Unknown command '%s'" % (commandName,))
 
+        reactor.stop()
+
     # Locations
 
     def command_getLocationList(self, command):
         respondWithRecordsOfType(self.dir, command, "locations")
 
     def command_createLocation(self, command):
+
         try:
             self.dir.createRecord("locations", guid=command['GeneratedUID'],
                 shortNames=command['RecordName'], fullName=command['RealName'])
@@ -184,7 +194,83 @@
             abort(str(e))
         respondWithRecordsOfType(self.dir, command, "resources")
 
+    # Proxies
 
+    @inlineCallbacks
+    def command_listWriteProxies(self, command):
+        principal = principalForPrincipalID(command['Principal'], directory=self.dir)
+        (yield respondWithProxies(self.dir, command, principal, "write"))
+
+    @inlineCallbacks
+    def command_addWriteProxy(self, command):
+        principal = principalForPrincipalID(command['Principal'], directory=self.dir)
+        proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
+        try:
+            (yield addProxy(principal, "write", proxy))
+        except ProxyError, e:
+            abort(str(e))
+        except ProxyWarning, e:
+            pass
+        (yield respondWithProxies(self.dir, command, principal, "write"))
+
+    @inlineCallbacks
+    def command_removeWriteProxy(self, command):
+        principal = principalForPrincipalID(command['Principal'], directory=self.dir)
+        proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
+        try:
+            (yield removeProxy(principal, proxy, proxyTypes=("write",)))
+        except ProxyError, e:
+            abort(str(e))
+        except ProxyWarning, e:
+            pass
+        (yield respondWithProxies(self.dir, command, principal, "write"))
+
+    @inlineCallbacks
+    def command_listReadProxies(self, command):
+        principal = principalForPrincipalID(command['Principal'], directory=self.dir)
+        (yield respondWithProxies(self.dir, command, principal, "read"))
+
+    @inlineCallbacks
+    def command_addReadProxy(self, command):
+        principal = principalForPrincipalID(command['Principal'], directory=self.dir)
+        proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
+        try:
+            (yield addProxy(principal, "read", proxy))
+        except ProxyError, e:
+            abort(str(e))
+        except ProxyWarning, e:
+            pass
+        (yield respondWithProxies(self.dir, command, principal, "read"))
+
+    @inlineCallbacks
+    def command_removeReadProxy(self, command):
+        principal = principalForPrincipalID(command['Principal'], directory=self.dir)
+        proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
+        try:
+            (yield removeProxy(principal, proxy, proxyTypes=("read",)))
+        except ProxyError, e:
+            abort(str(e))
+        except ProxyWarning, e:
+            pass
+        (yield respondWithProxies(self.dir, command, principal, "read"))
+
+
+ at inlineCallbacks
+def respondWithProxies(directory, command, principal, proxyType):
+    proxies = []
+    subPrincipal = proxySubprincipal(principal, proxyType)
+    if subPrincipal is not None:
+        membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
+        if membersProperty.children:
+            for member in membersProperty.children:
+                proxyPrincipal = principalForPrincipalID(str(member))
+                proxies.append(proxyPrincipal.record.guid)
+
+    respond(command, {
+        'Principal' : principal.record.guid, 'Proxies' : proxies
+    })
+
+
 def respondWithRecordsOfType(directory, command, recordType):
     result = []
     for record in directory.listRecords(recordType):
@@ -201,6 +287,10 @@
 
 def abort(msg, status=1):
     sys.stdout.write(plistlib.writePlistToString( { 'error' : msg, } ) )
+    try:
+        reactor.stop()
+    except RuntimeError:
+        pass
     sys.exit(status)
 
 if __name__ == "__main__":

Modified: CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/principals.py	2010-02-07 00:39:01 UTC (rev 5048)
+++ CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/principals.py	2010-02-08 03:42:50 UTC (rev 5049)
@@ -33,15 +33,10 @@
 from twext.python.log import StandardIOObserver
 from twext.web2.dav.davxml import sname2qname, qname2sname
 
-from twistedcaldav import memcachepool
 from twistedcaldav.config import config, ConfigurationError
-from twistedcaldav.notify import installNotificationClient
-from twistedcaldav.static import CalendarHomeProvisioningFile
 from twistedcaldav.directory.directory import UnknownRecordTypeError, DirectoryError
 
-from calendarserver.tools.util import booleanArgument, autoDisableMemcached
-from calendarserver.tools.util import loadConfig, getDirectory
-from calendarserver.provision.root import RootResource
+from calendarserver.tools.util import loadConfig, getDirectory, setupMemcached, setupNotifications, booleanArgument
 
 def usage(e=None):
     if e:
@@ -210,7 +205,8 @@
             config.directory = getDirectory()
         except DirectoryError, e:
             abort(e)
-        autoDisableMemcached(config)
+        setupMemcached(config)
+        setupNotifications(config)
     except ConfigurationError, e:
         abort(e)
 
@@ -261,34 +257,6 @@
 @inlineCallbacks
 def run(principalIDs, actions):
     try:
-        #
-        # Connect to memcached, notifications
-        #
-        memcachepool.installPools(
-            config.Memcached.Pools,
-            config.Memcached.MaxClients
-        )
-        if config.Notifications.Enabled:
-            installNotificationClient(
-                config.Notifications.InternalNotificationHost,
-                config.Notifications.InternalNotificationPort,
-            )
-
-        #
-        # Wire up the resource hierarchy
-        #
-        principalCollection = config.directory.getPrincipalCollection()
-        root = RootResource(
-            config.DocumentRoot,
-            principalCollections=(principalCollection,),
-        )
-        root.putChild("principals", principalCollection)
-        calendarCollection = CalendarHomeProvisioningFile(
-            os.path.join(config.DocumentRoot, "calendars"),
-            config.directory, "/calendars/",
-        )
-        root.putChild("calendars", calendarCollection)
-
         for principalID in principalIDs:
             # Resolve the given principal IDs to principals
             try:
@@ -311,6 +279,7 @@
         #
         reactor.stop()
 
+
 def principalForPrincipalID(principalID, checkOnly=False, directory=None):
     
     # Allow a directory parameter to be passed in, but default to config.directory
@@ -320,11 +289,19 @@
         directory = config.directory
 
     if principalID.startswith("/"):
-        raise ValueError("Can't resolve paths yet")
+        segments = principalID.strip("/").split("/")
+        if (len(segments) == 3 and
+            segments[0] == "principals" and segments[1] == "__uids__"):
+            uid = segments[2]
+        else:
+            raise ValueError("Can't resolve all paths yet")
 
         if checkOnly:
             return None
 
+        return directory.principalCollection.principalForUID(uid)
+
+
     if principalID.startswith("("):
         try:
             i = principalID.index(")")
@@ -401,31 +378,40 @@
 
 @inlineCallbacks
 def action_addProxyPrincipal(principal, proxyType, proxyPrincipal):
+    try:
+        (yield addProxy(principal, proxyType, proxyPrincipal))
+        print "Added %s as a %s proxy for %s" % (proxyPrincipal, proxyType, principal)
+    except ProxyError, e:
+        print "Error:", e
+    except ProxyWarning, e:
+        print e
+
+ at inlineCallbacks
+def addProxy(principal, proxyType, proxyPrincipal):
     proxyURL = proxyPrincipal.url()
 
     subPrincipal = proxySubprincipal(principal, proxyType)
     if subPrincipal is None:
-        sys.stderr.write("Unable to edit %s proxies for %s\n" % (proxyType, principal))
-        return
+        raise ProxyError("Unable to edit %s proxies for %s\n" % (proxyType, principal))
 
     membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
 
     for memberURL in membersProperty.children:
         if str(memberURL) == proxyURL:
-            print "%s is already a %s proxy for %s" % (proxyPrincipal, proxyType, principal)
-            break
+            raise ProxyWarning("%s is already a %s proxy for %s" % (proxyPrincipal, proxyType, principal))
+
     else:
         memberURLs = list(membersProperty.children)
         memberURLs.append(davxml.HRef(proxyURL))
         membersProperty = davxml.GroupMemberSet(*memberURLs)
         (yield subPrincipal.writeProperty(membersProperty, None))
-        print "Added %s as a %s proxy for %s" % (proxyPrincipal, proxyType, principal)
 
     proxyTypes = ["read", "write"]
     proxyTypes.remove(proxyType)
 
     (yield action_removeProxyPrincipal(principal, proxyPrincipal, proxyTypes=proxyTypes))
 
+
 @inlineCallbacks
 def action_removeProxy(principal, *proxyIDs, **kwargs):
     for proxyID in proxyIDs:
@@ -434,14 +420,23 @@
 
 @inlineCallbacks
 def action_removeProxyPrincipal(principal, proxyPrincipal, **kwargs):
+    try:
+        (yield removeProxy(principal, proxyPrincipal, **kwargs))
+    except ProxyError, e:
+        print "Error:", e
+    except ProxyWarning, e:
+        print e
+
+
+ at inlineCallbacks
+def removeProxy(principal, proxyPrincipal, **kwargs):
     proxyTypes = kwargs.get("proxyTypes", ("read", "write"))
     for proxyType in proxyTypes:
         proxyURL = proxyPrincipal.url()
 
         subPrincipal = proxySubprincipal(principal, proxyType)
         if subPrincipal is None:
-            sys.stderr.write("Unable to edit %s proxies for %s\n" % (proxyType, principal))
-            continue
+            raise ProxyError("Unable to edit %s proxies for %s\n" % (proxyType, principal))
 
         membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
 
@@ -456,8 +451,9 @@
 
         membersProperty = davxml.GroupMemberSet(*memberURLs)
         (yield subPrincipal.writeProperty(membersProperty, None))
-        print "Removed %s as a %s proxy for %s" % (proxyPrincipal, proxyType, principal)
 
+
+
 @inlineCallbacks
 def action_setAutoSchedule(principal, autoSchedule):
     if autoSchedule and principal.record.recordType in ("users", "groups"):
@@ -524,5 +520,16 @@
         pass
     sys.exit(status)
 
+class ProxyError(Exception):
+    """
+    Raised when proxy assignments cannot be performed
+    """
+
+class ProxyWarning(Exception):
+    """
+    Raised for harmless proxy assignment failures such as trying to add a
+    duplicate or remove a non-existent assignment.
+    """
+
 if __name__ == "__main__":
     main()

Added: CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/__init__.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/__init__.py	                        (rev 0)
+++ CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/__init__.py	2010-02-08 03:42:50 UTC (rev 5049)
@@ -0,0 +1,19 @@
+##
+# Copyright (c) 2005-2010 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Tests for the calendarserver.tools module.
+"""

Added: CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/augments.xml
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/augments.xml	                        (rev 0)
+++ CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/augments.xml	2010-02-08 03:42:50 UTC (rev 5049)
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2009-2010 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.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE accounts SYSTEM "../../../conf/auth/augments.dtd">
+
+<augments>
+  <record>
+    <guid>user01</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <guid>user02</guid>
+    <enable>false</enable>
+    <enable-calendar>false</enable-calendar>
+  </record>
+  <record>
+    <guid>location01</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+</augments>

Added: CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/caldavd.plist
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/caldavd.plist	                        (rev 0)
+++ CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/caldavd.plist	2010-02-08 03:42:50 UTC (rev 5049)
@@ -0,0 +1,750 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    Copyright (c) 2006-2010 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.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+  <dict>
+
+    <!--
+        Public network address information
+
+        This is the server's public network address, which is provided to
+        clients in URLs and the like.  It may or may not be the network
+        address that the server is listening to directly, though it is by
+        default.  For example, it may be the address of a load balancer or
+        proxy which forwards connections to the server.
+      -->
+
+    <!-- Network host name [empty = system host name] -->
+    <key>ServerHostName</key>
+    <string></string> <!-- The hostname clients use when connecting -->
+
+    <!-- HTTP port [0 = disable HTTP] -->
+    <key>HTTPPort</key>
+    <integer>8008</integer>
+
+    <!-- SSL port [0 = disable HTTPS] -->
+    <!-- (Must also configure SSLCertificate and SSLPrivateKey below) -->
+    <key>SSLPort</key>
+    <integer>8443</integer>
+
+    <!-- Redirect non-SSL ports to an SSL port -->
+    <key>RedirectHTTPToHTTPS</key>
+    <false/>
+
+    <!--
+        Network address configuration information
+
+        This configures the actual network address that the server binds to.
+      -->
+
+    <!-- List of IP addresses to bind to [empty = all] -->
+    <key>BindAddresses</key>
+    <array>
+    </array>
+
+    <!-- List of port numbers to bind to for HTTP [empty = same as "Port"] -->
+    <key>BindHTTPPorts</key>
+    <array>
+    </array>
+
+    <!-- List of port numbers to bind to for SSL [empty = same as "SSLPort"] -->
+    <key>BindSSLPorts</key>
+    <array>
+    </array>
+
+
+    <!--
+        Data Store
+      -->
+
+    <!-- Data root -->
+    <key>DataRoot</key>
+    <string>%(DataRoot)s</string>
+
+    <!-- Document root -->
+    <key>DocumentRoot</key>
+    <string>%(DocumentRoot)s</string>
+
+    <!-- Child aliases -->
+    <key>Aliases</key>
+    <dict>
+      <!--
+      <key>foo</key>
+      <dict>
+        <key>path</key>
+        <string>/path/to/foo</string>
+      </dict>
+       -->
+    </dict>
+
+    <!-- User quota (in bytes) -->
+    <key>UserQuota</key>
+    <integer>104857600</integer><!-- 100Mb -->
+
+    <!-- Attachment size limit (in bytes) -->
+    <key>MaximumAttachmentSize</key>
+    <integer>1048576</integer><!-- 1Mb -->
+
+    <!-- Maximum number of unique attendees per entire event -->
+    <!-- 0 for no limit -->
+    <key>MaxAttendeesPerInstance</key>
+    <integer>100</integer>
+
+    <!-- Maximum number of instances allowed for a single RRULE -->
+    <!-- 0 for no limit -->
+    <key>MaxInstancesForRRULE</key>
+    <integer>400</integer>
+
+
+    <!--
+        Directory service
+
+        A directory service provides information about principals (eg.
+        users, groups, locations and resources) to the server.
+
+        A variety of directory services are available for use.
+      -->
+
+    <!-- XML File Directory Service -->
+    <key>DirectoryService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
+      
+      <key>params</key>
+      <dict>
+        <key>xmlFile</key>
+        <string>%(DirectoryXMLFile)s</string>
+        <key>recordTypes</key>
+        <array>
+            <string>users</string>
+            <string>groups</string>
+        </array>
+      </dict>
+    </dict>
+
+    <!-- XML File Resource Service -->
+    <key>ResourceService</key>
+    <dict>
+      <key>Enabled</key>
+      <true/>
+      <key>type</key>
+      <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
+      
+      <key>params</key>
+      <dict>
+        <key>xmlFile</key>
+        <string>%(ResourceXMLFile)s</string>
+        <key>recordTypes</key>
+        <array>
+            <string>resources</string>
+            <string>locations</string>
+        </array>
+        <key>cacheTimeout</key>
+        <integer>30</integer>
+      </dict>
+    </dict>
+    
+    <!-- Open Directory Service (Mac OS X) -->
+    <!--
+    <key>DirectoryService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</string>
+      
+      <key>params</key>
+      <dict>
+        <key>node</key>
+        <string>/Search</string>
+        <key>cacheTimeout</key>
+        <integer>30</integer>
+      </dict>
+    </dict>
+    -->
+
+    <!--
+        Augment service
+
+        Augments for the directory service records to add calendar specific attributes.
+
+        A variety of augment services are available for use.
+        When using a partitioned server, a service that can be accessed from each host will be needed.
+      -->
+
+    <!-- XML File Augment Service -->
+    <key>AugmentService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.augment.AugmentXMLDB</string>
+      
+      <key>params</key>
+      <dict>
+        <key>xmlFiles</key>
+        <array>
+	      <string>%(AugmentXMLFile)s</string>
+        </array>
+      </dict>
+    </dict>
+    
+    <!-- Sqlite Augment Service -->
+    <!--
+    <key>AugmentService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.augment.AugmentSqliteDB</string>
+      
+      <key>params</key>
+      <dict>
+        <key>dbpath</key>
+        <string>/etc/caldavd/augments.sqlite</string>
+      </dict>
+    </dict>
+     -->
+
+    <!-- PostgreSQL Augment Service -->
+    <!--
+    <key>AugmentService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.augment.AugmentPostgreSQLDB</string>
+      
+      <key>params</key>
+      <dict>
+        <key>host</key>
+        <string>localhost</string>
+        <key>database</key>
+        <string>augments</string>
+      </dict>
+    </dict>
+     -->
+
+    <!-- Sqlite ProxyDB Service -->
+    <key>ProxyDBService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.calendaruserproxy.ProxySqliteDB</string>
+      
+      <key>params</key>
+      <dict>
+        <key>dbpath</key>
+        <string>data/proxies.sqlite</string>
+      </dict>
+    </dict>
+
+    <!-- PostgreSQL ProxyDB Service -->
+    <!--
+    <key>ProxyDBService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB</string>
+      
+      <key>params</key>
+      <dict>
+        <key>host</key>
+        <string>localhost</string>
+        <key>database</key>
+        <string>proxies</string>
+      </dict>
+    </dict>
+     -->
+
+	<key>ProxyLoadFromFile</key>
+    <string>conf/auth/proxies-test.xml</string>
+
+    <!--
+        Special principals
+
+        These principals are granted special access and/or perform
+        special roles on the server.
+      -->
+
+    <!-- Principals with "DAV:all" access (relative URLs) -->
+    <key>AdminPrincipals</key>
+    <array>
+      <string>/principals/__uids__/admin/</string>
+    </array>
+
+    <!-- Principals with "DAV:read" access (relative URLs) -->
+    <key>ReadPrincipals</key>
+    <array>
+      <!-- <string>/principals/__uids__/983C8238-FB6B-4D92-9242-89C0A39E5F81/</string> -->
+    </array>
+
+    <!-- Principals that can pose as other principals -->
+    <key>SudoersFile</key>
+    <string>conf/sudoers.plist</string>
+
+    <!-- Create "proxy access" principals -->
+    <key>EnableProxyPrincipals</key>
+    <true/>
+
+
+    <!--
+        Permissions
+      -->
+
+    <!-- Anonymous read access for root resource -->
+    <key>EnableAnonymousReadRoot</key>
+    <true/>
+
+    <!-- Anonymous read access for resource hierarchy -->
+    <key>EnableAnonymousReadNav</key>
+    <false/>
+
+    <!-- Enables directory listings for principals -->
+    <key>EnablePrincipalListings</key>
+    <true/>
+
+    <!-- Render calendar collections as a monolithic iCalendar object -->
+    <key>EnableMonolithicCalendars</key>
+    <true/>
+
+
+    <!--
+        Authentication
+      -->
+
+    <key>Authentication</key>
+    <dict>
+
+      <!-- Clear text; best avoided -->
+      <key>Basic</key>
+      <dict>
+        <key>Enabled</key>
+        <true/>
+      </dict>
+
+      <!-- Digest challenge/response -->
+      <key>Digest</key>
+      <dict>
+        <key>Enabled</key>
+        <true/>
+        <key>Algorithm</key>
+        <string>md5</string>
+        <key>Qop</key>
+        <string></string>
+      </dict>
+
+      <!-- Kerberos/SPNEGO -->
+      <key>Kerberos</key>
+      <dict>
+        <key>Enabled</key>
+        <false/>
+        <key>ServicePrincipal</key>
+        <string></string>
+      </dict>
+
+      <!-- Wikiserver authentication (Mac OS X) -->
+      <key>Wiki</key>
+      <dict>
+        <key>Enabled</key>
+        <true/>
+        <key>Cookie</key>
+        <string>sessionID</string>
+        <key>URL</key>
+        <string>http://127.0.0.1/RPC2</string>
+        <key>UserMethod</key>
+        <string>userForSession</string>
+        <key>WikiMethod</key>
+        <string>accessLevelForUserWikiCalendar</string>
+      </dict>
+
+    </dict>
+
+
+    <!--
+        Logging
+      -->
+
+    <!-- Apache-style access log -->
+    <key>AccessLogFile</key>
+    <string>logs/access.log</string>
+    <key>RotateAccessLog</key>
+    <false/>
+
+    <!-- Server activity log -->
+    <key>ErrorLogFile</key>
+    <string>logs/error.log</string>
+
+    <!-- Log levels -->
+    <key>DefaultLogLevel</key>
+    <string>info</string> <!-- debug, info, warn, error -->
+
+    <!-- Log level overrides for specific functionality -->
+    <key>LogLevels</key>
+    <dict>
+      <!--
+      <key>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</key>
+      <string>debug</string>
+      -->
+    </dict>
+
+    <!-- Global server stats --> 
+    <key>GlobalStatsSocket</key> 
+    <string>logs/caldavd-stats.sock</string> 
+
+    <!-- Global server stats logging period --> 
+    <key>GlobalStatsLoggingPeriod</key> 
+    <integer>60</integer> 
+
+    <!-- Global server stats logging frequency [0 = disable stats] --> 
+    <key>GlobalStatsLoggingFrequency</key> 
+    <integer>12</integer>
+
+    <!-- Server statistics file -->
+    <key>ServerStatsFile</key>
+    <string>logs/stats.plist</string>
+
+    <!-- Server process ID file -->
+    <key>PIDFile</key>
+    <string>logs/caldavd.pid</string>
+
+
+    <!--
+        Accounting
+      -->
+
+    <!-- Enable accounting for certain operations -->
+    <key>AccountingCategories</key>
+    <dict>
+      <key>iTIP</key>
+      <false/>
+      <key>HTTP</key>
+      <false/>
+    </dict>
+    <!-- Enable accounting for specific principals -->
+    <key>AccountingPrincipals</key>
+    <array>
+      <!-- <string>/principals/__uids__/454D85C0-09F0-4DC6-A3C6-97DFEB4622CD/</string> -->
+    </array>
+
+
+    <!--
+        SSL/TLS
+      -->
+
+    <!-- Public key -->
+    <key>SSLCertificate</key>
+    <string>twistedcaldav/test/data/server.pem</string>
+
+    <!-- SSL authority chain (for intermediate certs) -->
+    <key>SSLAuthorityChain</key>
+    <string></string>
+
+    <!-- Private key -->
+    <key>SSLPrivateKey</key>
+    <string>twistedcaldav/test/data/server.pem</string>
+
+
+    <!--
+        Process management
+      -->
+
+    <key>UserName</key>
+    <string></string>
+
+    <key>GroupName</key>
+    <string></string>
+
+    <key>ProcessType</key>
+    <string>Combined</string>
+
+    <key>MultiProcess</key>
+    <dict>
+      <key>ProcessCount</key>
+      <integer>2</integer> <!-- 0 = larger of: 4 or (2 * CPU count) -->
+    </dict>
+
+
+    <!--
+        Notifications
+      -->
+
+    <key>Notifications</key>
+    <dict>
+      <!-- Time spent coalescing notifications before delivery -->
+      <key>CoalesceSeconds</key>
+      <integer>3</integer>
+
+      <key>InternalNotificationHost</key>
+      <string>localhost</string>
+
+      <key>InternalNotificationPort</key>
+      <integer>62309</integer>
+
+      <key>Services</key>
+      <dict>
+        <key>SimpleLineNotifier</key>
+        <dict>
+          <!-- Simple line notification service (for testing) -->
+          <key>Service</key>
+          <string>twistedcaldav.notify.SimpleLineNotifierService</string>
+          <key>Enabled</key>
+          <false/>
+          <key>Port</key>
+          <integer>62308</integer>
+        </dict>
+
+        <key>XMPPNotifier</key>
+        <dict>
+          <!-- XMPP notification service -->
+          <key>Service</key>
+          <string>twistedcaldav.notify.XMPPNotifierService</string>
+          <key>Enabled</key>
+          <false/>
+
+          <!-- XMPP host and port to contact -->
+          <key>Host</key>
+          <string>xmpp.host.name</string>
+          <key>Port</key>
+          <integer>5222</integer>
+
+          <!-- Jabber ID and password for the server -->
+          <key>JID</key>
+          <string>jid at xmpp.host.name/resource</string>
+          <key>Password</key>
+          <string>password_goes_here</string>
+
+          <!-- PubSub service address -->
+          <key>ServiceAddress</key>
+          <string>pubsub.xmpp.host.name</string>
+
+          <key>NodeConfiguration</key>
+          <dict>
+            <key>pubsub#deliver_payloads</key>
+            <string>1</string>
+            <key>pubsub#persist_items</key>
+            <string>1</string>
+          </dict>
+
+          <!-- Sends a presence notification to XMPP server at this interval (prevents disconnect) -->
+          <key>KeepAliveSeconds</key>
+          <integer>120</integer>
+
+          <!-- Sends a pubsub publish to a particular heartbeat node at this interval -->
+          <key>HeartbeatMinutes</key>
+          <integer>30</integer>
+
+          <!-- List of glob-like expressions defining which XMPP JIDs can converse with the server (for debugging) -->
+          <key>AllowedJIDs</key>
+          <array>
+            <!--
+            <string>*.example.com</string>
+             -->
+          </array>
+        </dict>
+      </dict>
+    </dict>
+
+
+    <!--
+        Server-to-server protocol
+      -->
+
+    <key>Scheduling</key>
+    <dict>
+
+      <!-- CalDAV protocol options -->
+      <key>CalDAV</key>
+      <dict>
+        <key>EmailDomain</key>
+        <string></string>
+        <key>HTTPDomain</key>
+        <string></string>
+        <key>AddressPatterns</key>
+        <array>
+        </array>
+        <key>OldDraftCompatibility</key>
+        <true/>
+        <key>ScheduleTagCompatibility</key>
+        <true/>
+        <key>EnablePrivateComments</key>
+        <true/>
+      </dict>
+
+      <!-- iSchedule protocol options -->
+      <key>iSchedule</key>
+      <dict>
+        <key>Enabled</key>
+        <false/>
+        <key>AddressPatterns</key>
+        <array>
+        </array>
+        <key>Servers</key>
+        <string>conf/servertoserver-test.xml</string>
+      </dict>
+
+      <!-- iMIP protocol options -->
+      <key>iMIP</key>
+      <dict>
+        <key>Enabled</key>
+        <false/>
+        <key>MailGatewayServer</key>
+        <string>localhost</string>
+        <key>MailGatewayPort</key>
+        <integer>62310</integer>
+        <key>Sending</key>
+        <dict>
+          <key>Server</key>
+          <string></string>
+          <key>Port</key>
+          <integer>587</integer>
+          <key>UseSSL</key>
+          <true/>
+          <key>Username</key>
+          <string></string>
+          <key>Password</key>
+          <string></string>
+          <key>Address</key>
+          <string></string> <!-- Address email will be sent from -->
+        </dict>
+        <key>Receiving</key>
+        <dict>
+          <key>Server</key>
+          <string></string>
+          <key>Port</key>
+          <integer>995</integer>
+          <key>Type</key>
+          <string></string> <!-- Either "pop" or "imap" -->
+          <key>UseSSL</key>
+          <true/>
+          <key>Username</key>
+          <string></string>
+          <key>Password</key>
+          <string></string>
+          <key>PollingSeconds</key>
+          <integer>30</integer>
+        </dict>
+        <key>AddressPatterns</key>
+        <array>
+          <string>mailto:.*</string>
+        </array>
+      </dict>
+
+	  <!-- General options for scheduling -->
+	  <key>Options</key>
+	  <dict>
+        <key>AllowGroupAsOrganizer</key>
+        <false/>
+        <key>AllowLocationAsOrganizer</key>
+        <false/>
+        <key>AllowResourceAsOrganizer</key>
+        <false/>
+       </dict>
+
+    </dict>
+
+
+    <!--
+        Free-busy URL protocol
+      -->
+
+    <key>FreeBusyURL</key>
+    <dict>
+      <key>Enabled</key>
+      <true/>
+      <key>TimePeriod</key>
+      <integer>14</integer>
+      <key>AnonymousAccess</key>
+      <false/>
+    </dict>
+
+
+    <!--
+        Non-standard CalDAV extensions
+      -->
+
+    <!-- Calendar Drop Box -->
+    <key>EnableDropBox</key>
+    <true/>
+
+    <!-- Private Events -->
+    <key>EnablePrivateEvents</key>
+    <true/>
+
+    <!-- Timezone Service -->
+    <key>EnableTimezoneService</key>
+    <true/>
+
+
+    <!--
+        Miscellaneous items
+      -->
+
+    <!-- Service ACLs (Mac OS X) -->
+    <key>EnableSACLs</key>
+    <false/>
+
+    <!-- Web-based administration -->
+    <key>EnableWebAdmin</key>
+    <true/>
+
+    <!-- Support for Content-Encoding compression options as specified in RFC2616 Section 3.5 -->
+    <key>ResponseCompression</key>
+    <false/>
+    
+    <!-- The retry-after value (in seconds) to return with a 503 error. -->
+    <key>HTTPRetryAfter</key>
+    <integer>180</integer>
+
+    <!-- A unix socket used for communication between the child and master processes.
+         An empty value tells the server to use a tcp socket instead. -->
+    <key>ControlSocket</key>
+    <string>logs/caldavd.sock</string>
+
+    <!-- Support for Memcached -->
+    <key>Memcached</key>
+    <dict>
+      <key>MaxClients</key>
+      <integer>5</integer>
+      <key>memcached</key>
+      <string>memcached</string> <!-- Find in PATH -->
+      <key>Options</key>
+      <array>
+        <!--<string>-vv</string>-->
+      </array>
+    </dict>
+
+    <!-- Response Caching -->
+    <key>ResponseCacheTimeout</key>
+    <integer>30</integer> <!-- in minutes -->
+
+
+    <!--
+        Twisted
+      -->
+
+    <key>Twisted</key>
+    <dict>
+      <key>twistd</key>
+      <string>../Twisted/bin/twistd</string>
+    </dict>
+
+
+    <key>Localization</key>
+    <dict>
+      <key>LocalesDirectory</key>
+      <string>locales</string>
+      <key>Language</key>
+      <string>English</string>
+    </dict>
+
+
+  </dict>
+</plist>

Added: CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/resources-locations.xml
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/resources-locations.xml	                        (rev 0)
+++ CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/resources-locations.xml	2010-02-08 03:42:50 UTC (rev 5049)
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2010 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.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE accounts SYSTEM "accounts.dtd">
+
+<accounts realm="Test Realm">
+  <location repeat="10">
+    <uid>location%02d</uid>
+    <guid>location%02d</guid>
+    <password>location%02d</password>
+    <name>Room %02d</name>
+  </location>
+  <resource repeat="10">
+    <uid>resource%02d</uid>
+    <guid>resource%02d</guid>
+    <password>resource%02d</password>
+    <name>Resource %02d</name>
+  </resource>
+</accounts>

Added: CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/users-groups.xml
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/users-groups.xml	                        (rev 0)
+++ CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/gateway/users-groups.xml	2010-02-08 03:42:50 UTC (rev 5049)
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2010 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.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE accounts SYSTEM "accounts.dtd">
+
+<accounts realm="Test Realm">
+  <user repeat="10">
+    <uid>user%02d</uid>
+    <guid>user%02d</guid>
+    <password>test</password>
+    <name>Test User %02d</name>
+    <first-name>Test</first-name>
+    <last-name>User %02d</last-name>
+  </user>
+  <group>
+    <uid>testgroup1</uid>
+    <guid>e5a6142c-4189-4e9e-90b0-9cd0268b314b</guid>
+    <password>test</password>
+    <name>Group 01</name>
+    <members>
+      <member type="users">user01</member>
+      <member type="users">user02</member>
+    </members>
+  </group>
+</accounts>

Added: CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/test_gateway.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/test_gateway.py	                        (rev 0)
+++ CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/test/test_gateway.py	2010-02-08 03:42:50 UTC (rev 5049)
@@ -0,0 +1,316 @@
+##
+# Copyright (c) 2005-2010 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import os
+import plistlib
+import xml
+from twistedcaldav.config import config
+from twistedcaldav.test.util import TestCase
+from calendarserver.tools.util import getDirectory
+from twisted.python.filepath import FilePath
+from twistedcaldav.directory.directory import DirectoryError
+from subprocess import Popen, PIPE, STDOUT
+
+
+
+class GatewayTestCase(TestCase):
+
+    def setUp(self):
+        testRoot = os.path.join(os.path.dirname(__file__), "gateway")
+        templateName = os.path.join(testRoot, "caldavd.plist")
+        templateFile = open(templateName)
+        template = templateFile.read()
+        templateFile.close()
+
+        tmpDir = FilePath(self.mktemp())
+        tmpDir.makedirs()
+        dataRoot = tmpDir.child("data")
+        dataRoot.makedirs()
+        docRoot = tmpDir.child("documents")
+        docRoot.makedirs()
+
+        # Copy xml files to a temp directory because they may get modified
+
+        origUsersFile = FilePath(os.path.join(os.path.dirname(__file__),
+            "gateway", "users-groups.xml"))
+        copyUsersFile = tmpDir.child("users-groups.xml")
+        origUsersFile.copyTo(copyUsersFile)
+
+        origResourcesFile = FilePath(os.path.join(os.path.dirname(__file__),
+            "gateway", "resources-locations.xml"))
+        copyResourcesFile = tmpDir.child("resources-locations.xml")
+        origResourcesFile.copyTo(copyResourcesFile)
+
+        origAugmentFile = FilePath(os.path.join(os.path.dirname(__file__),
+            "gateway", "augments.xml"))
+        copyAugmentFile = tmpDir.child("augments.xml")
+        origAugmentFile.copyTo(copyAugmentFile)
+
+        newConfig = template % {
+            'DataRoot' : dataRoot.path,
+            'DocumentRoot' : docRoot.path,
+            'DirectoryXMLFile' : copyUsersFile.path,
+            'ResourceXMLFile' : copyResourcesFile.path,
+            'AugmentXMLFile' : copyAugmentFile.path,
+        }
+        configFilePath = tmpDir.child("caldavd.plist")
+        configFilePath.setContent(newConfig)
+
+        self.configFileName = configFilePath.path
+        config.load(self.configFileName)
+
+        super(GatewayTestCase, self).setUp()
+
+    def runCommand(self, command):
+        sourceRoot = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
+        python = os.path.join(sourceRoot, "python")
+        gateway = os.path.join(sourceRoot, "bin", "calendarserver_command_gateway")
+        child = Popen(
+            args=[python, gateway, "-f", self.configFileName],
+            cwd=sourceRoot,
+            stdin=PIPE, stdout=PIPE, stderr=STDOUT,
+        )
+        output, error = child.communicate(input=command)
+        try:
+            plist = plistlib.readPlistFromString(output)
+        except xml.parsers.expat.ExpatError, e:
+            print "Error (%s) parsing (%s)" % (e, output)
+            raise
+
+        return plist
+
+    def test_getLocationList(self):
+        results = self.runCommand(command_getLocationList)
+        self.assertEquals(len(results['result']), 10)
+
+    def test_getResourceList(self):
+        results = self.runCommand(command_getResourceList)
+        self.assertEquals(len(results['result']), 10)
+
+    def test_createLocation(self):
+        directory = getDirectory()
+
+        record = directory.recordWithUID("createdlocation01")
+        self.assertEquals(record, None)
+
+        results = self.runCommand(command_createLocation)
+
+        directory.flushCaches()
+        record = directory.recordWithUID("createdlocation01")
+        self.assertNotEquals(record, None)
+
+    def test_destroyRecord(self):
+        directory = getDirectory()
+
+        record = directory.recordWithUID("location01")
+        self.assertNotEquals(record, None)
+
+        results = self.runCommand(command_deleteLocation)
+
+        directory.flushCaches()
+        record = directory.recordWithUID("location01")
+        self.assertEquals(record, None)
+
+    def test_addWriteProxy(self):
+        directory = getDirectory()
+
+        results = self.runCommand(command_addWriteProxy)
+        self.assertEquals(len(results['result']['Proxies']), 1)
+
+    def test_removeWriteProxy(self):
+        directory = getDirectory()
+
+        results = self.runCommand(command_addWriteProxy)
+        results = self.runCommand(command_removeWriteProxy)
+        self.assertEquals(len(results['result']['Proxies']), 0)
+
+
+
+command_deleteLocation = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>deleteLocation</string>
+        <key>GeneratedUID</key>
+        <string>guidoffice3</string>
+</dict>
+</plist>
+"""
+
+command_addReadProxy = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>addReadProxy</string>
+        <key>Principal</key>
+        <string>locations:location01</string>
+        <key>Proxy</key>
+        <string>users:user03</string>
+</dict>
+</plist>
+"""
+
+command_addWriteProxy = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>addWriteProxy</string>
+        <key>Principal</key>
+        <string>locations:location01</string>
+        <key>Proxy</key>
+        <string>users:user01</string>
+</dict>
+</plist>
+"""
+
+command_createLocation = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>createLocation</string>
+        <key>AutoSchedule</key>
+        <true/>
+        <key>GeneratedUID</key>
+        <string>createdlocation01</string>
+        <key>RealName</key>
+        <string>Created Location 01</string>
+        <key>RecordName</key>
+        <array>
+                <string>createdlocation01</string>
+        </array>
+</dict>
+</plist>
+"""
+
+command_createResource = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>createResource</string>
+        <key>AutoSchedule</key>
+        <true/>
+        <key>GeneratedUID</key>
+        <string>guidlaptop1</string>
+        <key>RealName</key>
+        <string>Laptop 1</string>
+        <key>RecordName</key>
+        <array>
+                <string>laptop1</string>
+        </array>
+</dict>
+</plist>
+"""
+
+command_deleteLocation = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>deleteLocation</string>
+        <key>GeneratedUID</key>
+        <string>location01</string>
+</dict>
+</plist>
+"""
+
+command_deleteResource = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>deleteResource</string>
+        <key>GeneratedUID</key>
+        <string>guidlaptop1</string>
+</dict>
+</plist>
+"""
+
+command_getLocationList = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>getLocationList</string>
+</dict>
+</plist>
+"""
+
+command_getResourceList = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>getResourceList</string>
+</dict>
+</plist>
+"""
+
+command_listReadProxies = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>listReadProxies</string>
+        <key>Principal</key>
+        <string>locations:location01</string>
+</dict>
+</plist>
+"""
+
+command_listWriteProxies = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>listWriteProxies</string>
+        <key>Principal</key>
+        <string>locations:location01</string>
+</dict>
+</plist>
+"""
+
+command_removeReadProxy = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>removeReadProxy</string>
+        <key>Principal</key>
+        <string>locations:location01</string>
+        <key>Proxy</key>
+        <string>users:user03</string>
+</dict>
+</plist>
+"""
+
+command_removeWriteProxy = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>removeWriteProxy</string>
+        <key>Principal</key>
+        <string>locations:location01</string>
+        <key>Proxy</key>
+        <string>users:user01</string>
+</dict>
+</plist>
+"""

Modified: CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/util.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/util.py	2010-02-07 00:39:01 UTC (rev 5048)
+++ CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/util.py	2010-02-08 03:42:50 UTC (rev 5049)
@@ -24,15 +24,19 @@
 
 import os
 from time import sleep
+import socket
 
 from twisted.python.reflect import namedClass
 
-import socket
+from calendarserver.provision.root import RootResource
+from twistedcaldav import memcachepool
 from twistedcaldav.config import config, ConfigurationError
 from twistedcaldav.directory import augment, calendaruserproxy
+from twistedcaldav.directory.aggregate import AggregateDirectoryService
 from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.notify import installNotificationClient
+from twistedcaldav.static import CalendarHomeProvisioningFile
 from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
-from twistedcaldav.directory.aggregate import AggregateDirectoryService
 
 
 def loadConfig(configFileName):
@@ -112,6 +116,22 @@
         directories.append(resourceDirectory)
 
     aggregate = MyDirectoryService(directories)
+
+    #
+    # Wire up the resource hierarchy
+    #
+    principalCollection = aggregate.getPrincipalCollection()
+    root = RootResource(
+        config.DocumentRoot,
+        principalCollections=(principalCollection,),
+    )
+    root.putChild("principals", principalCollection)
+    calendarCollection = CalendarHomeProvisioningFile(
+        os.path.join(config.DocumentRoot, "calendars"),
+        aggregate, "/calendars/",
+    )
+    root.putChild("calendars", calendarCollection)
+
     return aggregate
 
 class DummyDirectoryService (DirectoryService):
@@ -158,3 +178,25 @@
 
     except socket.error:
         config.Memcached.Pools.Default.ClientEnabled = False
+
+
+def setupMemcached(config):
+    #
+    # Connect to memcached
+    #
+    memcachepool.installPools(
+        config.Memcached.Pools,
+        config.Memcached.MaxClients
+    )
+    autoDisableMemcached(config)
+
+def setupNotifications(config):
+    #
+    # Connect to notifications
+    #
+    if config.Notifications.Enabled:
+        installNotificationClient(
+            config.Notifications.InternalNotificationHost,
+            config.Notifications.InternalNotificationPort,
+        )
+

Modified: CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/aggregate.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/aggregate.py	2010-02-07 00:39:01 UTC (rev 5048)
+++ CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/aggregate.py	2010-02-08 03:42:50 UTC (rev 5049)
@@ -154,6 +154,11 @@
         else:
             return None
 
+    def flushCaches(self):
+        for service in self._recordTypes.values():
+            if hasattr(service, "_initCaches"):
+                service._initCaches()
+
     userRecordTypes = [DirectoryService.recordType_users]
 
     def requestAvatarId(self, credentials):

Modified: CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/cachingdirectory.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/cachingdirectory.py	2010-02-07 00:39:01 UTC (rev 5048)
+++ CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/cachingdirectory.py	2010-02-08 03:42:50 UTC (rev 5049)
@@ -154,7 +154,7 @@
         self.cacheTimeout = cacheTimeout * 60
 
         self.cacheClass = cacheClass
-        self._initCaches(cacheClass)
+        self._initCaches()
 
         super(CachingDirectoryService, self).__init__()
 
@@ -204,9 +204,9 @@
                 raise DirectoryMemcacheError("Failed to read from memcache")
         return record
 
-    def _initCaches(self, cacheClass):
+    def _initCaches(self):
         self._recordCaches = dict([
-            (recordType, cacheClass(self, recordType))
+            (recordType, self.cacheClass(self, recordType))
             for recordType in self.recordTypes()
         ])
             
@@ -333,7 +333,6 @@
                 self.log_debug("Found record for attribute '%s' with value '%s'" % (indexType, indexKey,))
                 return record
 
-
             # Add to negative cache with timestamp
             self.log_debug("Failed to fault record for attribute '%s' with value '%s'" % (indexType, indexKey,))
             self._disabledKeys[indexType][indexKey] = time.time()

Modified: CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/test/test_cachedirectory.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/test/test_cachedirectory.py	2010-02-07 00:39:01 UTC (rev 5048)
+++ CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/test/test_cachedirectory.py	2010-02-08 03:42:50 UTC (rev 5049)
@@ -86,7 +86,7 @@
         self.service.queried = False
 
     def loadRecords(self, records):
-        self.service._initCaches(DictRecordTypeCache)
+        self.service._initCaches()
         self.service.fakerecords = records
         self.service.queried = False
 

Modified: CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/test/test_modify.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/test/test_modify.py	2010-02-07 00:39:01 UTC (rev 5048)
+++ CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/test/test_modify.py	2010-02-08 03:42:50 UTC (rev 5049)
@@ -24,12 +24,6 @@
 
 class ModificationTestCase(TestCase):
 
-    def xmlFile(self):
-        if not hasattr(self, "_xmlFile"):
-            self._xmlFile = FilePath(self.mktemp())
-            xmlFile.copyTo(self._xmlFile)
-        return self._xmlFile
-
     def setUp(self):
         testRoot = os.path.join(os.path.dirname(__file__), "modify")
         configFileName = os.path.join(testRoot, "caldavd.plist")

Modified: CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/xmlfile.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/xmlfile.py	2010-02-07 00:39:01 UTC (rev 5048)
+++ CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/xmlfile.py	2010-02-08 03:42:50 UTC (rev 5049)
@@ -59,13 +59,14 @@
                 self.recordType_locations,
                 self.recordType_resources,
             ),
+            'cacheTimeout' : 30,
         }
         ignored = None
         params = self.getParams(params, defaults, ignored)
 
         self._recordTypes = params['recordTypes']
 
-        super(XMLDirectoryService, self).__init__()
+        super(XMLDirectoryService, self).__init__(params['cacheTimeout'])
 
         xmlFile = params.get("xmlFile")
         if type(xmlFile) is str:
@@ -195,6 +196,10 @@
                     if record:
                         yield record
 
+    def _initCaches(self):
+        super(XMLDirectoryService, self)._initCaches()
+        self._lastCheck = 0
+
     def _accounts(self):
         currentTime = time()
         if self._alwaysStat or currentTime - self._lastCheck > 60:
@@ -259,7 +264,7 @@
         ET.ElementTree(element).write(self.xmlFile.path)
 
         # Reload
-        self._initCaches(self.cacheClass) # nuke local cache
+        self._initCaches() # nuke local cache
         self._lastCheck = 0
         self._accounts()
         # TODO: nuke memcache entries, or prepopulate them

Modified: CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/stdconfig.py	2010-02-07 00:39:01 UTC (rev 5048)
+++ CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/stdconfig.py	2010-02-08 03:42:50 UTC (rev 5049)
@@ -39,6 +39,7 @@
 DEFAULT_SERVICE_PARAMS = {
     "twistedcaldav.directory.xmlfile.XMLDirectoryService": {
         "xmlFile": "/etc/caldavd/accounts.xml",
+        "cacheTimeout": 30,
         "recordTypes": ("users", "groups", "locations", "resources"),
     },
     "twistedcaldav.directory.appleopendirectory.OpenDirectoryService": {
@@ -51,6 +52,7 @@
 DEFAULT_RESOURCE_PARAMS = {
     "twistedcaldav.directory.xmlfile.XMLDirectoryService": {
         "xmlFile": "/etc/caldavd/resources.xml",
+        "cacheTimeout": 30,
         "recordTypes" : ("locations", "resources"),
     },
 }
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100207/3af48668/attachment-0001.html>


More information about the calendarserver-changes mailing list