[CalendarServer-changes] [4529] CalendarServer/branches/users/cdaboo/deployment-partition-4524

source_changes at macosforge.org source_changes at macosforge.org
Tue Sep 8 13:38:34 PDT 2009


Revision: 4529
          http://trac.macosforge.org/projects/calendarserver/changeset/4529
Author:   cdaboo at apple.com
Date:     2009-09-08 13:38:33 -0700 (Tue, 08 Sep 2009)
Log Message:
-----------
Partitioning support for augment DB and redirector back-ported to deployment branch.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/accounts-test.xml
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/accounts.dtd
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/accounts.xml
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/caldavd-test.plist
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/caldavd.plist
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/config.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/appleopendirectory.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/calendaruserproxy.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/directory.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/idirectory.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/principal.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/sudo.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/accounts.xml
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_aggregate.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_calendar.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_digest.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_guidchange.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_opendirectory.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_principal.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_proxyprincipalmembers.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_util.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_xmlfile.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/util.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/xmlaccountsparser.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/xmlfile.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/log.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/method/report_common.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/sql.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/static.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/tap.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_cache.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_config.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_log.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_memcache.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_memcachepool.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_memcacher.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_resource.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_root.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_static.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_tap.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/augments-test.xml
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/augments.dtd
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/partitions-test.plist
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/partitions.plist
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/proxies-test.xml
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/proxies.dtd
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/database.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/augment.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/calendaruserproxyloader.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/augments-test.xml
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/augments.xml
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/proxies.xml
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_augment.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/xmlaugmentsparser.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/partitions.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_database.py

Removed Paths:
-------------
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/apache.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/sqldb.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/basic
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/digest
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/groups
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_apache.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_opendirectoryrecords.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_opendirectoryschema.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_sqldb.py

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/accounts-test.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/accounts-test.xml	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/accounts-test.xml	2009-09-08 20:38:33 UTC (rev 4529)
@@ -36,34 +36,24 @@
     <guid>user%02d</guid>
     <password>user%02d</password>
     <name>User %02d</name>
-    <cuaddr>mailto:user%02d at example.com</cuaddr>
   </user>
   <user repeat="10">
     <uid>public%02d</uid>
     <guid>public%02d</guid>
     <password>public%02d</password>
     <name>Public %02d</name>
-    <cuaddr>mailto:public%02d at example.com</cuaddr>
   </user>
   <location repeat="10">
     <uid>location%02d</uid>
     <guid>location%02d</guid>
     <password>location%02d</password>
     <name>Room %02d</name>
-    <auto-schedule/>
   </location>
   <resource repeat="10">
     <uid>resource%02d</uid>
     <guid>resource%02d</guid>
     <password>resource%02d</password>
     <name>Resource %02d</name>
-    <auto-schedule/>
-    <proxies>
-      <member type="users">user01</member>
-    </proxies>
-    <read-only-proxies>
-      <member type="users">user03</member>
-    </read-only-proxies>
   </resource>
   <group>
     <uid>group01</uid>
@@ -82,6 +72,5 @@
     <members>
       <member type="users">user01</member>
     </members>
-    <disable-calendar/>
   </group>
 </accounts>

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/accounts.dtd
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/accounts.dtd	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/accounts.dtd	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,5 +1,5 @@
 <!--
-Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+Copyright (c) 2006-2009 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.
@@ -19,16 +19,16 @@
 <!ELEMENT accounts (user*, group*, resource*, location*) >
   <!ATTLIST accounts realm CDATA "">
 
-  <!ELEMENT user (uid, guid, password, name, cuaddr*, disable-calendar?)>
+  <!ELEMENT user (uid*, guid, password?, name?, email-address*)>
     <!ATTLIST user repeat CDATA "1">
 
-  <!ELEMENT group (uid, guid, password, name, members, cuaddr*, disable-calendar?)>
+  <!ELEMENT group (uid*, guid, password?, name?, members)>
     <!ATTLIST group repeat CDATA "1">
 
-  <!ELEMENT resource (uid, guid, password, name, cuaddr*, auto-schedule?, proxies?, read-only-proxies?)>
+  <!ELEMENT resource (uid*, guid, password?, name)>
     <!ATTLIST resource repeat CDATA "1">
 
-  <!ELEMENT location (uid, guid, password, name, cuaddr*, auto-schedule?, proxies?, read-only-proxies?)>
+  <!ELEMENT location (uid*, guid, password?, name)>
     <!ATTLIST location repeat CDATA "1">
 
   <!ELEMENT member (#PCDATA)>
@@ -38,10 +38,6 @@
   <!ELEMENT guid              (#PCDATA)>
   <!ELEMENT password          (#PCDATA)>
   <!ELEMENT name              (#PCDATA)>
-  <!ELEMENT cuaddr            (#PCDATA)>
+  <!ELEMENT email-address     (#PCDATA)>
   <!ELEMENT members           (member*)>
-  <!ELEMENT auto-schedule     EMPTY>
-  <!ELEMENT disable-calendar  EMPTY>
-  <!ELEMENT proxies           (member*)>
-  <!ELEMENT read-only-proxies (member*)>
 >

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/accounts.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/accounts.xml	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/accounts.xml	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+Copyright (c) 2006-2009 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,7 +28,6 @@
     <uid>test</uid>
     <password>test</password>
     <name>Test User</name>
-    <cuaddr>mailto:testuser at example.com</cuaddr>
   </user>
   <group>
     <uid>users</uid>
@@ -42,10 +41,6 @@
     <uid>mercury</uid>
     <password>mercury</password>
     <name>Mecury Conference Room, Building 1, 2nd Floor</name>
-    <auto-schedule/>
-    <proxies>
-      <member type="users">test</member>
-    </proxies>
   </location>
 </accounts>
 

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/augments-test.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/augments-test.xml	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/augments-test.xml	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2009 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 augments SYSTEM "augments.dtd">
+
+<augments>
+  <record>
+    <guid>admin</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <guid>apprentice</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record repeat="99">
+    <guid>user%02d</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <cuaddr>mailto:user%02d at example.com</cuaddr>
+  </record>
+  <record repeat="10">
+    <guid>public%02d</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <cuaddr>mailto:public%02d at example.com</cuaddr>
+  </record>
+  <record repeat="10">
+    <guid>location%02d</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <auto-schedule>true</auto-schedule>
+  </record>
+  <record repeat="10">
+    <guid>resource%02d</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <auto-schedule>true</auto-schedule>
+  </record>
+  <record repeat="4">
+    <guid>group%02d</guid>
+    <enable>true</enable>
+  </record>
+  <record>
+    <guid>disabledgroup</guid>
+    <enable>false</enable>
+  </record>
+</augments>

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/augments.dtd
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/augments.dtd	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/augments.dtd	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,28 @@
+<!--
+Copyright (c) 2006-2009 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.
+-->
+
+<!ELEMENT augments (record*) >
+
+  <!ELEMENT record (guid, enable, hosted-at?, enable-calendar?, auto-schedule?, cuaddr*)>
+    <!ATTLIST record repeat CDATA "1">
+
+  <!ELEMENT guid              (#PCDATA)>
+  <!ELEMENT enable            (#PCDATA)>
+  <!ELEMENT hosted-at         (#PCDATA)>
+  <!ELEMENT enable-calendar   (#PCDATA)>
+  <!ELEMENT auto-schedule     (#PCDATA)>
+  <!ELEMENT cuaddr            (#PCDATA)>
+>

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/caldavd-test.plist	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/caldavd-test.plist	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+Copyright (c) 2006-2009 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.
@@ -116,63 +116,53 @@
     <dict>
       <key>node</key>
       <string>/Search</string>
-      <key>requireComputerRecord</key>
-      <true/>
+      <key>cacheTimeout</key>
+      <integer>30</integer>
     </dict>
   </dict>
   -->
 
-  <!--  Apache-style Basic Directory Service -->
-  <!--
-  <key>DirectoryService</key>
-  <dict>
-    <key>type</key>
-    <string>twistedcaldav.directory.apache.BasicDirectoryService</string>
-  
-    <key>params</key>
-    <dict>
-      <key>userFile</key>
-      <string>conf/basic</string>
-      <key>groupFile</key>
-      <string>conf/group</string>
-    </dict>
-  </dict>
-  -->
+    <!--
+        Augment service
 
-  <!--  Apache-style Digest Directory Service -->
-  <!--
-  <key>DirectoryService</key>
-  <dict>
-    <key>type</key>
-    <string>twistedcaldav.directory.apache.DigestDirectoryService</string>
-  
-    <key>params</key>
+        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>userFile</key>
-      <string>conf/digest</string>
-      <key>groupFile</key>
-      <string>conf/group</string>
+      <key>type</key>
+      <string>twistedcaldav.directory.augment.AugmentXMLDB</string>
+      
+      <key>params</key>
+      <dict>
+        <key>xmlFiles</key>
+        <array>
+	      <string>conf/auth/augments-test.xml</string>
+        </array>
+      </dict>
     </dict>
-  </dict>
-  -->
-
-  <!--  SQL Directory Service -->
-  <!--
-  <key>DirectoryService</key>
-  <dict>
-    <key>type</key>
-    <string>twistedcaldav.directory.sqldb.SQLDirectoryService</string>
-  
-    <key>params</key>
+    
+    <!-- Sqlite Augment Service -->
+    <!--
+    <key>AugmentService</key>
     <dict>
-      <key>dbParentPath</key>
-      <string>twistedcaldav/test/data/</string>
-      <key>xmlFile</key>
-      <string>conf/accounts-test.xml</string>
+      <key>type</key>
+      <string>twistedcaldav.directory.augment.AugmentSqliteDB</string>
+      
+      <key>params</key>
+      <dict>
+        <key>dbpath</key>
+        <string>/etc/caldavd/augments.sqlite</string>
+      </dict>
     </dict>
-  </dict>
-  -->
+     -->
 
+	<key>ProxyLoadFromFile</key>
+    <string>conf/auth/proxies-test.xml</string>
 
   <!--
     Special principals

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/caldavd.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/caldavd.plist	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/caldavd.plist	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-    Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+    Copyright (c) 2006-2009 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.
@@ -120,13 +120,51 @@
       <dict>
         <key>node</key>
         <string>/Search</string>
-        <key>requireComputerRecord</key>
-        <true/>
+        <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>/etc/caldavd/augments.xml</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>
+     -->
+
+    <!--
         Special principals
 
         These principals are granted special access and/or perform

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/partitions-test.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/partitions-test.plist	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/partitions-test.plist	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    Copyright (c) 2009 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>
+  <key>partitions</key>
+  <array>
+    <dict>
+      <key>uid</key>
+      <string>00001</string>
+      <key>url</key>
+      <string>http://localhost:8008</string>
+    </dict>
+    <dict>
+      <key>uid</key>
+      <string>00002</string>
+      <key>url</key>
+      <string>http://localhost:8108</string>
+    </dict>
+  </array>
+</dict>
+</plist>

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/partitions.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/partitions.plist	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/partitions.plist	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    Copyright (c) 2009 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>
+  <key>partitions</key>
+  <array>
+  <!--
+    <dict>
+      <key>uid</key>
+      <string>00001</string>
+      <key>url</key>
+      <string>http://localhost:8008</string>
+    </dict>
+  -->
+  </array>
+</dict>
+</plist>

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/proxies-test.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/proxies-test.xml	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/proxies-test.xml	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2009 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 proxies SYSTEM "proxies.dtd">
+
+<proxies>
+  <record repeat="10">
+    <guid>resource%02d</guid>
+    <proxies>
+      <member>user01</member>
+    </proxies>
+    <read-only-proxies>
+      <member>user03</member>
+    </read-only-proxies>
+  </record>
+</proxies>

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/proxies.dtd
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/proxies.dtd	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/conf/proxies.dtd	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,26 @@
+<!--
+Copyright (c) 2006-2009 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.
+-->
+
+<!ELEMENT proxies (record*) >
+
+  <!ELEMENT record (guid, proxies?, read-only-proxies?)>
+    <!ATTLIST record repeat CDATA "1">
+
+  <!ELEMENT guid              (#PCDATA)>
+  <!ELEMENT proxies           (member*)>
+  <!ELEMENT read-only-proxies (member*)>
+  <!ELEMENT member            (#PCDATA)>
+>

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/config.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/config.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -23,7 +23,6 @@
     "config",
 ]
 
-import os
 import copy
 import re
 
@@ -33,6 +32,7 @@
 from twistedcaldav.py.plistlib import readPlist
 from twistedcaldav.log import Logger
 from twistedcaldav.log import clearLogLevels, setLogLevelForNamespace, InvalidLogLevelError
+from twistedcaldav.partitions import partitions
 
 log = Logger()
 
@@ -71,11 +71,20 @@
     },
     "twistedcaldav.directory.appleopendirectory.OpenDirectoryService": {
         "node": "/Search",
-        "requireComputerRecord": True,
         "cacheTimeout": 30,
     },
 }
 
+augmentDefaultParams = {
+    "twistedcaldav.directory.augment.AugmentXMLDB": {
+        "xmlFiles": ["/etc/caldavd/augments.xml",],
+    },
+    "twistedcaldav.directory.augment.AugmentSqliteDB": {
+        "dbpath": "/etc/caldavd/augments.sqlite",
+    },
+}
+
+
 defaultConfig = {
     # Note: Don't use None values below; that confuses the command-line parser.
 
@@ -121,6 +130,23 @@
     },
 
     #
+    # Augment service
+    #
+    #    Augments for the directory service records to add calendar specific attributes.
+    #
+    "AugmentService": {
+        "type": "twistedcaldav.directory.augment.AugmentXMLDB",
+        "params": augmentDefaultParams["twistedcaldav.directory.augment.AugmentXMLDB"],
+    },
+
+    #
+    # Proxy loader
+    #
+    #    Allows for initialization of the proxy database from an XML file.
+    #
+    "ProxyLoadFromFile": "",
+
+    #
     # Special principals
     #
     "AdminPrincipals": [],                       # Principals with "DAV:all" access (relative URLs)
@@ -254,6 +280,13 @@
     },
 
     #
+    # Partitioning
+    #
+    "EnablePartitions":    False,   # Partitioning enabled or not
+    "ServerPartitionID":   "",      # Unique ID for this server's partition instance.
+    "PartitionConfigFile": "/etc/caldavd/partitions.plist", # File path for partition information
+
+    #
     # Performance tuning
     #
 
@@ -351,6 +384,7 @@
             self.updateDropBox,
             self.updateLogLevels,
             self.updateNotifications,
+            self.updatePartitions,
         ]
 
     def __str__(self):
@@ -408,6 +442,12 @@
                 if param not in serviceDefaultParams[self._data.DirectoryService.type]:
                     del self._data.DirectoryService.params[param]
 
+        if self._data.AugmentService.type in augmentDefaultParams:
+            for param in tuple(self._data.AugmentService.params):
+                if param not in augmentDefaultParams[self._data.AugmentService.type]:
+                    log.warn("Parameter %s is not supported by service %s" % (param, self._data.AugmentService.type))
+                    del self._data.AugmentService.params[param]
+
     @staticmethod
     def updateACLs(self, items):
         #
@@ -518,6 +558,19 @@
         except InvalidLogLevelError, e:
             raise ConfigurationError("Invalid log level: %s" % (e.level))
 
+    @staticmethod
+    def updatePartitions(self, items):
+        #
+        # Partitions
+        #
+    
+        if "EnablePartitions" in items:
+            if items["EnablePartitions"]:
+                partitions.setSelfPartition(items.get("ServerPartitionID", ""))
+                partitions.readConfig(items.get("PartitionConfigFile", ""))
+            else:
+                partitions.clear()
+
     def updateDefaults(self, items):
         _mergeData(self._defaults, items)
         self.update(items)

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/database.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/database.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/database.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,366 @@
+##
+# Copyright (c) 2009 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.
+##
+
+from twistedcaldav.log import Logger
+
+from twisted.enterprise.adbapi import ConnectionPool
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.python.threadpool import ThreadPool
+
+import thread
+
+"""
+Generic ADAPI database access object.
+"""
+
+__all__ = [
+    "AbstractADBAPIDatabase",
+]
+
+log = Logger()
+
+class ConnectionClosingThreadPool(ThreadPool):
+    """
+    A ThreadPool that closes connections for each worker thread
+    """
+    
+    def _worker(self, o):
+        log.debug("Starting ADBAPI thread: %s" % (thread.get_ident(),))
+        ThreadPool._worker(self, o)
+        self._closeConnection()
+
+    def _closeConnection(self):
+        
+        tid = thread.get_ident()
+        log.debug("Closing ADBAPI thread: %s" % (tid,))
+
+        conn = self.pool.connections.get(tid)
+        self.pool._close(conn)
+        del self.pool.connections[tid]
+
+class AbstractADBAPIDatabase(object):
+    """
+    A generic SQL database.
+    """
+
+    def __init__(self, dbID, dbapiName, dbapiArgs, persistent, **kwargs):
+        """
+        
+        @param persistent: C{True} if the data in the DB must be perserved during upgrades,
+            C{False} if the DB data can be re-created from an external source.
+        @type persistent: bool
+        """
+        self.dbID = dbID
+        self.dbapiName = dbapiName
+        self.dbapiArgs = dbapiArgs
+        self.dbapikwargs = kwargs
+
+        self.persistent = persistent
+        
+        self.initialized = False
+
+    def __repr__(self):
+        return "<%s %r>" % (self.__class__.__name__, self.pool)
+
+    @inlineCallbacks
+    def open(self):
+        """
+        Access the underlying database.
+        @return: a db2 connection object for this index's underlying data store.
+        """
+        if not self.initialized:
+
+            self.pool = ConnectionPool(self.dbapiName, *self.dbapiArgs, **self.dbapikwargs)
+            
+            # sqlite3 is not thread safe which means we have to close the sqlite3 connections in the same thread that
+            # opened them. We need a special thread pool class that has a thread worker function that does a close
+            # when a thread is closed.
+            if self.dbapiName == "sqlite3":
+                self.pool.threadpool.stop()
+                self.pool.threadpool = ConnectionClosingThreadPool(1, 1)
+                self.pool.threadpool.start()
+                self.pool.threadpool.pool = self.pool
+
+            #
+            # Set up the schema
+            #
+            # Create CALDAV table if needed
+
+            test = (yield self._test_schema_table())
+            if test:
+                version = (yield self._db_value_for_sql("select VALUE from CALDAV where KEY = 'SCHEMA_VERSION'"))
+                dbtype = (yield self._db_value_for_sql("select VALUE from CALDAV where KEY = 'TYPE'"))
+
+                if (version != self._db_version()) or (dbtype != self._db_type()):
+
+                    if dbtype != self._db_type():
+                        log.err("Database %s has different type (%s vs. %s)"
+                                % (self.dbID, dbtype, self._db_type()))
+
+                        # Delete this index and start over
+                        yield self._db_remove()
+                        yield self._db_init()
+
+                    elif version != self._db_version():
+                        log.err("Database %s has different schema (v.%s vs. v.%s)"
+                                % (self.dbID, version, self._db_version()))
+                        
+                        # Upgrade the DB
+                        yield self._db_upgrade(version)
+
+            else:
+                yield self._db_init()
+            self.initialized = True
+
+    def close(self):
+        
+        if self.initialized:
+            self.pool.close()
+            self.pool = None
+            self.initialized = False
+
+    @inlineCallbacks
+    def execute(self, sql, *query_params):
+        
+        if not self.initialized:
+            yield self.open()
+
+        yield self._db_execute(sql, *query_params)
+
+    @inlineCallbacks
+    def executescript(self, script):
+        
+        if not self.initialized:
+            yield self.open()
+
+        yield self._db_execute_script(script)
+
+    @inlineCallbacks
+    def query(self, sql, *query_params):
+        
+        if not self.initialized:
+            yield self.open()
+
+        result = (yield self._db_all_values_for_sql(sql, *query_params))
+        returnValue(result)
+
+    @inlineCallbacks
+    def queryList(self, sql, *query_params):
+        
+        if not self.initialized:
+            yield self.open()
+
+        result = (yield self._db_values_for_sql(sql, *query_params))
+        returnValue(result)
+
+    @inlineCallbacks
+    def queryOne(self, sql, *query_params):
+        
+        if not self.initialized:
+            yield self.open()
+
+        result = (yield self._db_value_for_sql(sql, *query_params))
+        returnValue(result)
+
+    def _db_version(self):
+        """
+        @return: the schema version assigned to this DB.
+        """
+        raise NotImplementedError
+        
+    def _db_type(self):
+        """
+        @return: the collection type assigned to this DB.
+        """
+        raise NotImplementedError
+        
+    @inlineCallbacks
+    def _test_schema_table(self):
+        result = (yield self._db_value_for_sql("""
+        select (1) from SQLITE_MASTER
+         where TYPE = 'table' and NAME = 'CALDAV'
+        """))
+        returnValue(result)
+
+    @inlineCallbacks
+    def _db_init(self):
+        """
+        Initialise the underlying database tables.
+        """
+        log.msg("Initializing database %s" % (self.dbID,))
+
+        # TODO we need an exclusive lock of some kind here to prevent a race condition
+        # in which multiple processes try to create the tables.
+        
+
+        yield self._db_init_schema_table()
+        yield self._db_init_data_tables()
+        yield self._db_recreate()
+
+    @inlineCallbacks
+    def _db_init_schema_table(self):
+        """
+        Initialise the underlying database tables.
+        @param db_filename: the file name of the index database.
+        @param q:           a database cursor to use.
+        """
+
+        #
+        # CALDAV table keeps track of our schema version and type
+        #
+        yield self._db_execute(
+            """
+            create table if not exists CALDAV (
+                KEY text unique,
+                VALUE text unique
+            )
+            """
+        )
+        yield self._db_execute(
+            """
+            insert or ignore into CALDAV (KEY, VALUE)
+            values ('SCHEMA_VERSION', :1)
+            """, (self._db_version(),)
+        )
+        yield self._db_execute(
+            """
+            insert or ignore into CALDAV (KEY, VALUE)
+            values ('TYPE', :1)
+            """, (self._db_type(),)
+        )
+
+    def _db_init_data_tables(self):
+        """
+        Initialise the underlying database tables.
+        """
+        raise NotImplementedError
+
+    def _db_recreate(self):
+        """
+        Recreate the database tables.
+        """
+
+        # Implementations can override this to re-create data
+        pass
+
+    @inlineCallbacks
+    def _db_upgrade(self, old_version):
+        """
+        Upgrade the database tables.
+        """
+        
+        if self.persistent:
+            yield self._db_upgrade_data_tables(old_version)
+            yield self._db_upgrade_schema()
+        else:
+            # Non-persistent DB's by default can be removed and re-created. However, for simple
+            # DB upgrades they SHOULD override this method and handle those for better performance.
+            yield self._db_remove()
+            yield self._db_init()
+    
+    def _db_upgrade_data_tables(self, old_version):
+        """
+        Upgrade the data from an older version of the DB.
+        """
+        # Persistent DB's MUST override this method and do a proper upgrade. Their data
+        # cannot be thrown away.
+        raise NotImplementedError("Persistent databases MUST support an upgrade method.")
+
+    @inlineCallbacks
+    def _db_upgrade_schema(self):
+        """
+        Upgrade the stored schema version to the current one.
+        """
+        yield self._db_execute("insert or replace into CALDAV (KEY, VALUE) values ('SCHEMA_VERSION', :1)", (self._db_version(),))
+
+    @inlineCallbacks
+    def _db_remove(self):
+        """
+        Remove all database information (all the tables)
+        """
+        yield self._db_remove_data_tables()
+        yield self._db_remove_schema()
+
+    def _db_remove_data_tables(self):
+        """
+        Remove all the data from an older version of the DB.
+        """
+        raise NotImplementedError("Each database must remove its own tables.")
+
+    @inlineCallbacks
+    def _db_remove_schema(self):
+        """
+        Remove the stored schema version table.
+        """
+        yield self._db_execute("drop table if exists CALDAV")
+
+    @inlineCallbacks
+    def _db_all_values_for_sql(self, sql, *query_params):
+        """
+        Execute an SQL query and obtain the resulting values.
+        @param sql: the SQL query to execute.
+        @param query_params: parameters to C{sql}.
+        @return: an interable of values in the first column of each row
+            resulting from executing C{sql} with C{query_params}.
+        @raise AssertionError: if the query yields multiple columns.
+        """
+        
+        results = (yield self.pool.runQuery(sql, *query_params))
+        returnValue(tuple(results))
+
+    @inlineCallbacks
+    def _db_values_for_sql(self, sql, *query_params):
+        """
+        Execute an SQL query and obtain the resulting values.
+
+        @param sql: the SQL query to execute.
+        @param query_params: parameters to C{sql}.
+        @return: an interable of values in the first column of each row
+            resulting from executing C{sql} with C{query_params}.
+        @raise AssertionError: if the query yields multiple columns.
+        """
+        
+        results = (yield self.pool.runQuery(sql, *query_params))
+        returnValue(tuple([row[0] for row in results]))
+
+    @inlineCallbacks
+    def _db_value_for_sql(self, sql, *query_params):
+        """
+        Execute an SQL query and obtain a single value.
+
+        @param sql: the SQL query to execute.
+        @param query_params: parameters to C{sql}.
+        @return: the value resulting from the executing C{sql} with
+            C{query_params}.
+        @raise AssertionError: if the query yields multiple rows or columns.
+        """
+        value = None
+        for row in (yield self._db_values_for_sql(sql, *query_params)):
+            assert value is None, "Multiple values in DB for %s %s" % (sql, query_params)
+            value = row
+        returnValue(value)
+
+    def _db_execute(self, sql, *query_params):
+        """
+        Execute an SQL operation that returns None.
+
+        @param sql: the SQL query to execute.
+        @param query_params: parameters to C{sql}.
+        @return: an iterable of tuples for each row resulting from executing
+            C{sql} with C{query_params}.
+        """
+        
+        return self.pool.runOperation(sql, *query_params)

Deleted: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/apache.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/apache.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/apache.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,197 +0,0 @@
-##
-# Copyright (c) 2006-2007 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.
-##
-
-"""
-Apache UserFile/GroupFile compatible directory service implementation.
-"""
-
-__all__ = [
-    "BasicDirectoryService",
-    "DigestDirectoryService",
-]
-
-from crypt import crypt
-
-from twisted.python.filepath import FilePath
-from twisted.cred.credentials import UsernamePassword
-
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
-from twistedcaldav.directory.directory import UnknownRecordTypeError
-
-class AbstractDirectoryService(DirectoryService):
-    """
-    Abstract Apache-compatible implementation of L{IDirectoryService}.
-    """
-    def __repr__(self):
-        return "<%s %r: %r %r>" % (self.__class__.__name__, self.realmName, self.userFile, self.groupFile)
-
-    def __init__(self, realmName, userFile, groupFile=None):
-        super(AbstractDirectoryService, self).__init__()
-
-        if type(userFile) is str:
-            userFile = FilePath(userFile)
-        if type(groupFile) is str:
-            groupFile = FilePath(groupFile)
-
-        self.realmName = realmName
-        self.userFile = userFile
-        self.groupFile = groupFile
-
-    def recordTypes(self):
-        recordTypes = (DirectoryService.recordType_users,)
-        if self.groupFile is not None:
-            recordTypes += (DirectoryService.recordType_groups,)
-        return recordTypes
-
-    def listRecords(self, recordType):
-        for entryShortName, entryData in self.entriesForRecordType(recordType):
-            if recordType == DirectoryService.recordType_users:
-                yield self.userRecordClass(
-                    service       = self,
-                    recordType    = recordType,
-                    shortName     = entryShortName,
-                    cryptPassword = entryData,
-                )
-
-            elif recordType == DirectoryService.recordType_groups:
-                yield GroupRecord(
-                    service    = self,
-                    recordType = recordType,
-                    shortName  = entryShortName,
-                    members    = entryData,
-                )
-
-            else:
-                # Subclass should cover the remaining record types
-                raise AssertionError("Unknown record type: %r" % (recordType,))
-
-    def recordWithShortName(self, recordType, shortName):
-        for entryShortName, entryData in self.entriesForRecordType(recordType):
-            if entryShortName == shortName:
-                if recordType == DirectoryService.recordType_users:
-                    return self.userRecordClass(
-                        service       = self,
-                        recordType    = recordType,
-                        shortName     = entryShortName,
-                        cryptPassword = entryData,
-                    )
-
-                if recordType == DirectoryService.recordType_groups:
-                    return GroupRecord(
-                        service    = self,
-                        recordType = recordType,
-                        shortName  = entryShortName,
-                        members    = entryData,
-                    )
-
-                # Subclass should cover the remaining record types
-                raise AssertionError("Unknown record type: %r" % (recordType,))
-
-        return None
-
-    def entriesForRecordType(self, recordType):
-        if recordType == DirectoryService.recordType_users:
-            recordFile = self.userFile
-        elif recordType == DirectoryService.recordType_groups:
-            recordFile = self.groupFile
-        else:
-            raise UnknownRecordTypeError("Unknown record type: %s" % (recordType,))
-
-        if recordFile is None:
-            return
-
-        for entry in recordFile.open():
-            if entry and entry[0] != "#":
-                shortName, rest = entry.rstrip("\n").split(":", 1)
-                yield shortName, rest
-
-class AbstractDirectoryRecord(DirectoryRecord):
-    """
-    Abstract Apache-compatible implementation of L{IDirectoryRecord}.
-    """
-    def __init__(self, service, recordType, shortName):
-        super(AbstractDirectoryRecord, self).__init__(
-            service               = service,
-            recordType            = recordType,
-            guid                  = None,
-            shortName             = shortName,
-            fullName              = None,
-            calendarUserAddresses = set(),
-            autoSchedule          = False,
-        )
-
-class AbstractUserRecord(AbstractDirectoryRecord):
-    def __init__(self, service, recordType, shortName, cryptPassword=None):
-        super(AbstractUserRecord, self).__init__(service, recordType, shortName)
-
-        self._cryptPassword = cryptPassword
-
-    def groups(self):
-        for group in self.service.listRecords(DirectoryService.recordType_groups):
-            for member in group.members():
-                if member == self:
-                    yield group
-                    continue
-
-class BasicUserRecord(AbstractUserRecord):
-    """
-    Apache UserFile implementation of L{IDirectoryRecord}.
-    """
-    def verifyCredentials(self, credentials):
-        if self._cryptPassword in ("", "*", "x"):
-            return False
-
-        if isinstance(credentials, UsernamePassword):
-            return crypt(credentials.password, self._cryptPassword) == self._cryptPassword
-
-        return super(BasicUserRecord, self).verifyCredentials(credentials)
-
-class BasicDirectoryService(AbstractDirectoryService):
-    """
-    Apache UserFile/GroupFile implementation of L{IDirectoryService}.
-    """
-    baseGUID = "DDF1E45C-CADE-4FCD-8AE6-B4B41D72B325"
-    userRecordClass = BasicUserRecord
-
-class DigestUserRecord(AbstractUserRecord):
-    """
-    Apache DigestUserFile implementation of L{IDirectoryRecord}.
-    """
-    def verifyCredentials(self, credentials):
-        raise NotImplementedError()
-
-class DigestDirectoryService(AbstractDirectoryService):
-    """
-    Apache DigestUserFile/GroupFile implementation of L{IDirectoryService}.
-    """
-    baseGUID = "0C719D1B-0A14-4074-8740-6D96A7D0C787"
-    userRecordClass = DigestUserRecord
-
-class GroupRecord(AbstractDirectoryRecord):
-    """
-    Apache GroupFile implementation of L{IDirectoryRecord}.
-    """
-    def __init__(self, service, recordType, shortName, members=()):
-        super(GroupRecord, self).__init__(service, recordType, shortName)
-
-        if type(members) is str:
-            members = tuple(m.strip() for m in members.split(","))
-
-        self._members = members
-
-    def members(self):
-        for shortName in self._members:
-            yield self.service.recordWithShortName(DirectoryService.recordType_users, shortName)

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/appleopendirectory.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/appleopendirectory.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -26,10 +26,7 @@
 import sys
 import os
 from random import random
-from uuid import UUID
 
-from xml.parsers.expat import ExpatError
-
 import opendirectory
 import dsattributes
 import dsquery
@@ -39,12 +36,10 @@
 from twisted.cred.credentials import UsernamePassword
 from twisted.web2.auth.digest import DigestedCredentials
 
-from twistedcaldav.config import config
+from twistedcaldav.directory import augment
 from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
 from twistedcaldav.directory.directory import DirectoryError, UnknownRecordTypeError
 
-from plistlib import readPlistFromString, readPlist
-
 serverPreferences = '/Library/Preferences/com.apple.servermgr_info.plist'
 saclGroup = 'com.apple.access_calendar'
 
@@ -57,11 +52,9 @@
     def __repr__(self):
         return "<%s %r: %r>" % (self.__class__.__name__, self.realmName, self.node)
 
-    def __init__(self, node="/Search", requireComputerRecord=True, dosetup=True, cacheTimeout=30):
+    def __init__(self, node="/Search", dosetup=True, cacheTimeout=30):
         """
         @param node: an OpenDirectory node name to bind to.
-        @param requireComputerRecord: C{True} if the directory schema is to be used to determine
-            which calendar users are enabled.
         @param dosetup: if C{True} then the directory records are initialized,
                         if C{False} they are not.
                         This should only be set to C{False} when doing unit tests.
@@ -75,9 +68,6 @@
         self.realmName = node
         self.directory = directory
         self.node = node
-        self.requireComputerRecord = requireComputerRecord
-        self.computerRecords = {}
-        self.servicetags = set()
         self.cacheTimeout = cacheTimeout
         self._records = {}
         self._delayedCalls = set()
@@ -85,21 +75,6 @@
         self.isWorkgroupServer = False
 
         if dosetup:
-            if self.requireComputerRecord:
-                try:
-                    self._lookupVHostRecord()
-                except Exception, e:
-                    self.log_error("Unable to locate virtual host record: %s" % (e,))
-                    raise
-
-                if os.path.exists(serverPreferences):
-                    serverInfo = readPlist(serverPreferences)
-
-                    self.isWorkgroupServer = serverInfo.get('ServiceConfig', {}).get('IsWorkgroupServer', False)
-
-                    if self.isWorkgroupServer:
-                        self.log_info("Enabling Workgroup Server compatibility mode")
-
             for recordType in self.recordTypes():
                 self.recordsForType(recordType)
 
@@ -174,216 +149,6 @@
             h = (h + hash(getattr(self, attr))) & sys.maxint
         return h
 
-    def _lookupVHostRecord(self):
-        """
-        Get the OD service record for this host.
-        """
-
-        # The server must have been configured with a virtual hostname.
-        vhostname = config.ServerHostName
-        if not vhostname:
-            raise OpenDirectoryInitError(
-                "There is no virtual hostname configured for the server for use with Open Directory (node=%s)"
-                % (self.realmName,)
-            )
-         
-        # Find a record in /Computers with an apple-serviceinfo attribute value equal to the virtual hostname
-        # and return some useful attributes.
-        attrs = [
-            dsattributes.kDS1AttrGeneratedUID,
-            dsattributes.kDSNAttrRecordName,
-            dsattributes.kDSNAttrMetaNodeLocation,
-            "dsAttrTypeNative:apple-serviceinfo",
-        ]
-
-        self.log_debug("opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r)" % (
-            self.directory,
-            dsquery.match(
-                "dsAttrTypeNative:apple-serviceinfo",
-                vhostname,
-                dsattributes.eDSContains,
-            ).generate(),
-            True,    # case insentive for hostnames
-            dsattributes.kDSStdRecordTypeComputers,
-            attrs
-        ))
-        records = opendirectory.queryRecordsWithAttributes_list(
-            self.directory,
-            dsquery.match(
-                "dsAttrTypeNative:apple-serviceinfo",
-                vhostname,
-                dsattributes.eDSContains,
-            ).generate(),
-            True,    # case insentive for hostnames
-            dsattributes.kDSStdRecordTypeComputers,
-            attrs
-        )
-        self._parseComputersRecords(records, vhostname)
-
-    def _parseComputersRecords(self, records, vhostname):
-        # Must have some results
-        if len(records) == 0:
-            raise OpenDirectoryInitError(
-                "Open Directory (node=%s) has no /Computers records with a virtual hostname: %s"
-                % (self.realmName, vhostname)
-            )
-
-        # Now find all appropriate records and determine the enabled (only) service tags for each.
-        for recordname, record in records:
-            self._parseServiceInfo(vhostname, recordname, record)
-
-        # Log all the matching records
-        for key, value in self.computerRecords.iteritems():
-            _ignore_recordname, enabled, servicetag = value
-            self.log_info("Matched Directory record: %s with ServicesLocator: %s, state: %s" % (
-                key,
-                servicetag,
-                {True:"enabled", False:"disabled"}[enabled]
-            ))
-
-        # Log all the enabled service tags - or generate an error if there are none
-        if self.servicetags:
-            for tag in self.servicetags:
-                self.log_info("Enabled ServicesLocator: %s" % (tag,))
-        else:
-            raise OpenDirectoryInitError(
-                "Open Directory (node=%s) no /Computers records with an enabled and valid "
-                "calendar service were found matching virtual hostname: %s"
-                % (self.realmName, vhostname)
-            )
-
-    def _parseServiceInfo(self, vhostname, recordname, record):
-
-        # Extract some useful attributes
-        recordguid = record[dsattributes.kDS1AttrGeneratedUID]
-        recordlocation = "%s/Computers/%s" % (record[dsattributes.kDSNAttrMetaNodeLocation], recordname)
-
-        # First check for apple-serviceinfo attribute
-        plist = record.get("dsAttrTypeNative:apple-serviceinfo", None)
-        if not plist:
-            return False
-
-        # Parse the plist and look for our special entry
-        plist = readPlistFromString(plist)
-        vhosts = plist.get("com.apple.macosxserver.virtualhosts", None)
-        if not vhosts:
-            self.log_error(
-                "Open Directory (node=%s) %s record does not have a "
-                "com.apple.macosxserver.virtualhosts in its apple-serviceinfo attribute value"
-                % (self.realmName, recordlocation)
-            )
-            return False
-        
-        # Iterate over each vhost and find one that is a calendar service
-        hostguid = None
-        for key, value in vhosts.iteritems():
-            serviceTypes = value.get("serviceType", None)
-            if serviceTypes:
-                for type in serviceTypes:
-                    if type == "calendar":
-                        hostguid = key
-                        break
-                    
-        if not hostguid:
-            # We can get false positives from the query - we ignore those.
-            return False
-            
-        # Get host name
-        hostname = vhosts[hostguid].get("hostname", None)
-        if not hostname:
-            self.log_error(
-                "Open Directory (node=%s) %s record does not have "
-                "any host name in its apple-serviceinfo attribute value"
-                % (self.realmName, recordlocation)
-            )
-            return False
-        if hostname != vhostname:
-            # We can get false positives from the query - we ignore those.
-            return False
-        
-        # Get host details. At this point we only check that it is present. We actually
-        # ignore the details themselves (scheme/port) as we use our own config for that.
-        hostdetails = vhosts[hostguid].get("hostDetails", None)
-        if not hostdetails:
-            self.log_error(
-                "Open Directory (node=%s) %s record does not have "
-                "any host details in its apple-serviceinfo attribute value"
-                % (self.realmName, recordlocation)
-            )
-            return False
-        
-        # Look at the service data
-        serviceInfos = vhosts[hostguid].get("serviceInfo", None)
-        if not serviceInfos or not serviceInfos.has_key("calendar"):
-            self.log_error(
-                "Open Directory (node=%s) %s record does not have a "
-                "calendar service in its apple-serviceinfo attribute value"
-                % (self.realmName, recordlocation)
-            )
-            return False
-        serviceInfo = serviceInfos["calendar"]
-        
-        # Check that this service is enabled
-        enabled = serviceInfo.get("enabled", True)
-
-        # Create the string we will use to match users with accounts on this server
-        servicetag = "%s:%s:calendar" % (recordguid, hostguid)
-        
-        self.computerRecords[recordlocation] = (recordname, enabled, servicetag)
-        
-        if enabled:
-            self.servicetags.add(servicetag)
-        
-        return True
-    
-    def _calendarUserAddresses(self, recordType, recordName, record):
-        """
-        Extract specific attributes from the directory record for use as calendar user address.
-        
-        @param recordName: a C{str} containing the record name being operated on.
-        @param record: a C{dict} containing the attributes retrieved from the directory.
-        @return: a C{set} of C{str} for each expanded calendar user address.
-        """
-        # Now get the addresses
-        result = set()
-        
-        # Add each email address as a mailto URI
-        emails = record.get(dsattributes.kDSNAttrEMailAddress)
-        if emails is not None:
-            if isinstance(emails, str):
-                emails = [emails]
-            for email in emails:
-                result.add("mailto:%s" % (email.lower(),))
-                
-        return result
-
-    def _parseResourceInfo(self, plist, guid, recordType, shortname):
-        """
-        Parse OD ResourceInfo attribute and extract information that the server needs.
-
-        @param plist: the plist that is the attribute value.
-        @type plist: str
-        @param guid: the directory GUID of the record being parsed.
-        @type guid: str
-        @param shortname: the record shortname of the record being parsed.
-        @type shortname: str
-        @return: a C{tuple} of C{bool} for auto-accept, C{str} for proxy GUID, C{str} for read-only proxy GUID.
-        """
-        try:
-            plist = readPlistFromString(plist)
-            wpframework = plist.get("com.apple.WhitePagesFramework", {})
-            autoaccept = wpframework.get("AutoAcceptsInvitation", False)
-            proxy = wpframework.get("CalendaringDelegate", None)
-            read_only_proxy = wpframework.get("ReadOnlyCalendaringDelegate", None)
-        except (ExpatError, AttributeError), e:
-            self.log_error(
-                "Failed to parse ResourceInfo attribute of record (%s)%s (guid=%s): %s\n%s" %
-                (recordType, shortname, guid, e, plist,)
-            )
-            raise ValueError("Invalid ResourceInfo")
-
-        return (autoaccept, proxy, read_only_proxy,)
-
     def recordTypes(self):
         return (
             DirectoryService.recordType_users,
@@ -616,9 +381,6 @@
             
             if recordType == DirectoryService.recordType_groups:
                 groupsForGUID = {}
-            elif recordType in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
-                proxiesForGUID = {}
-                readOnlyProxiesForGUID = {}
         else:
             storage = self._records[recordType]
 
@@ -631,60 +393,22 @@
             
             if recordType == DirectoryService.recordType_groups:
                 groupsForGUID = storage["groupsForGUID"]
-            elif recordType in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
-                proxiesForGUID = storage["proxiesForGUID"]
-                readOnlyProxiesForGUID = storage["readOnlyProxiesForGUID"]
 
-        for (recordShortName, value) in results:
-            enabledForCalendaring = True
-
-            if self.requireComputerRecord:
-                servicesLocators = value.get(dsattributes.kDSNAttrServicesLocator)
-
-                def allowForACLs():
-                    return recordType in (
-                        DirectoryService.recordType_users,
-                        DirectoryService.recordType_groups,
-                    )
-
-                def disableForCalendaring():
-                    self.log_debug(
-                        "Record (%s) %s is not enabled for calendaring but may be used in ACLs"
-                        % (recordType, recordShortName)
-                    )
-
-                def invalidRecord():
-                    self.log_error(
-                        "Directory (incorrectly) returned a record with no applicable "
-                        "ServicesLocator attribute: (%s) %s"
-                        % (recordType, recordShortName)
-                    )
-
-                if servicesLocators:
-                    if type(servicesLocators) is str:
-                        servicesLocators = (servicesLocators,)
-
-                    for locator in servicesLocators:
-                        if locator in self.servicetags:
-                            break
-                    else:
-                        if allowForACLs():
-                            disableForCalendaring()
-                            enabledForCalendaring = False
-                        else:
-                            invalidRecord()
-                            continue
+        def _setFromAttribute(attribute, lower=False):
+            if attribute:
+                if isinstance(attribute, str):
+                    return set((attribute.lower() if lower else attribute,))
                 else:
-                    if allowForACLs():
-                        disableForCalendaring()
-                        enabledForCalendaring = False
-                    else:
-                        invalidRecord()
-                        continue
+                    return set([item.lower() if lower else item for item in attribute])
+            else:
+                return ()
 
+        for (recordShortName, value) in results:
+
             # Now get useful record info.
             recordGUID     = value.get(dsattributes.kDS1AttrGeneratedUID)
             recordFullName = value.get(dsattributes.kDS1AttrDistinguishedName)
+            recordEmailAddresses = _setFromAttribute(value.get(dsattributes.kDSNAttrEMailAddress), lower=True)
             recordNodeName = value.get(dsattributes.kDSNAttrMetaNodeLocation)
 
             if not recordGUID:
@@ -692,12 +416,6 @@
                                % (recordType, recordShortName, recordNodeName))
                 continue
 
-            # Get calendar user addresses from directory record.
-            if enabledForCalendaring:
-                calendarUserAddresses = self._calendarUserAddresses(recordType, recordShortName, value)
-            else:
-                calendarUserAddresses = ()
-
             # Special case for groups, which have members.
             if recordType == DirectoryService.recordType_groups:
                 memberGUIDs = value.get(dsattributes.kDSNAttrGroupMembers)
@@ -713,22 +431,6 @@
             else:
                 memberGUIDs = ()
 
-            # Special case for resources and locations
-            autoSchedule = False
-            proxyGUIDs = ()
-            readOnlyProxyGUIDs = ()
-            if recordType in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
-                resourceInfo = value.get(dsattributes.kDSNAttrResourceInfo)
-                if resourceInfo is not None:
-                    try:
-                        autoSchedule, proxy, read_only_proxy = self._parseResourceInfo(resourceInfo, recordGUID, recordType, recordShortName)
-                    except ValueError:
-                        continue
-                    if proxy:
-                        proxyGUIDs = (proxy,)
-                    if read_only_proxy:
-                        readOnlyProxyGUIDs = (read_only_proxy,)
-
             record = OpenDirectoryRecord(
                 service               = self,
                 recordType            = recordType,
@@ -736,14 +438,16 @@
                 nodeName              = recordNodeName,
                 shortName             = recordShortName,
                 fullName              = recordFullName,
-                calendarUserAddresses = calendarUserAddresses,
-                autoSchedule          = autoSchedule,
-                enabledForCalendaring = enabledForCalendaring,
+                emailAddresses        = recordEmailAddresses,
                 memberGUIDs           = memberGUIDs,
-                proxyGUIDs            = proxyGUIDs,
-                readOnlyProxyGUIDs    = readOnlyProxyGUIDs,
             )
 
+            # Look up augment information
+            # TODO: this needs to be deferred but for now we hard code the deferred result because
+            # we know it is completing immediately.
+            d = augment.AugmentService.getAugmentRecord(record.guid)
+            d.addCallback(lambda x:record.addAugmentInformation(x))
+
             def disableRecord(record):
                 self.log_warn("Record disabled due to conflict (record name and GUID must match): %s" % (record,))
 
@@ -789,11 +493,6 @@
                     if recordType == DirectoryService.recordType_groups:
                         self._indexGroup(record, record._memberGUIDs, groupsForGUID)
 
-                    # Do proxy indexing if needed
-                    elif recordType in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
-                        self._indexGroup(record, record._proxyGUIDs, proxiesForGUID)
-                        self._indexGroup(record, record._readOnlyProxyGUIDs, readOnlyProxiesForGUID)
-
         if shortName is None and guid is None:
             #
             # Replace the entire cache
@@ -811,11 +510,6 @@
             if recordType == DirectoryService.recordType_groups:
                 storage["groupsForGUID"] = groupsForGUID
 
-            # Add proxy indexing if needed
-            elif recordType in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
-                storage["proxiesForGUID"] = proxiesForGUID
-                storage["readOnlyProxiesForGUID"] = readOnlyProxiesForGUID
-
             def rot():
                 storage["status"] = "stale"
                 removals = set()
@@ -861,80 +555,13 @@
 
         elif recordType == DirectoryService.recordType_locations:
             listRecordType = dsattributes.kDSStdRecordTypePlaces
-            attrs.append(dsattributes.kDSNAttrResourceInfo)
         
         elif recordType == DirectoryService.recordType_resources:
             listRecordType = dsattributes.kDSStdRecordTypeResources
-            attrs.append(dsattributes.kDSNAttrResourceInfo)
         
         else:
             raise UnknownRecordTypeError("Unknown Open Directory record type: %s" % (recordType))
 
-        if self.requireComputerRecord:
-            if self.isWorkgroupServer and recordType == DirectoryService.recordType_users:
-                if shortName is None and guid is None:
-                    self.log_debug("opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)" % (
-                        self.directory,
-                        dsattributes.kDSNAttrRecordName,
-                        saclGroup,
-                        dsattributes.eDSExact,
-                        False,
-                        dsattributes.kDSStdRecordTypeGroups,
-                        [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups],
-                    ))
-                    results = opendirectory.queryRecordsWithAttribute_list(
-                        self.directory,
-                        dsattributes.kDSNAttrRecordName,
-                        saclGroup,
-                        dsattributes.eDSExact,
-                        False,
-                        dsattributes.kDSStdRecordTypeGroups,
-                        [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups]
-                    )
-
-                    if len(results) == 1:
-                        members      = results[0][1].get(dsattributes.kDSNAttrGroupMembers, [])
-                        nestedGroups = results[0][1].get(dsattributes.kDSNAttrNestedGroups, [])
-                    else:
-                        members = []
-                        nestedGroups = []
-
-                    guidQueries = []
-
-                    for GUID in self._expandGroupMembership(members, nestedGroups):
-                        guidQueries.append(
-                            dsquery.match(dsattributes.kDS1AttrGeneratedUID, GUID, dsattributes.eDSExact)
-                        )
-
-                    if not guidQueries:
-                        self.log_warn("No SACL enabled users found.")
-                        return ()
-
-                    query = dsquery.expression(dsquery.expression.OR, guidQueries)
-
-            #
-            # For users and groups, we'll load all entries, even if
-            # they don't have a services locator for this server.
-            #
-            elif (
-                recordType != DirectoryService.recordType_users and
-                recordType != DirectoryService.recordType_groups
-            ):
-                tag_queries = []
-
-                for tag in self.servicetags:
-                    tag_queries.append(dsquery.match(dsattributes.kDSNAttrServicesLocator, tag, dsattributes.eDSExact))
-
-                if len(tag_queries) == 1:
-                    subquery = tag_queries[0]
-                else:
-                    subquery = dsquery.expression(dsquery.expression.OR, tag_queries)
-
-                if query is None:
-                    query = subquery
-                else:
-                    query = dsquery.expression(dsquery.expression.AND, (subquery, query))
-
         if shortName is not None:
             subquery = dsquery.match(dsattributes.kDSNAttrRecordName, shortName, dsattributes.eDSExact)
         elif guid is not None:
@@ -1007,8 +634,7 @@
     """
     def __init__(
         self, service, recordType, guid, nodeName, shortName, fullName,
-        calendarUserAddresses, autoSchedule, enabledForCalendaring,
-        memberGUIDs, proxyGUIDs, readOnlyProxyGUIDs,
+        emailAddresses, memberGUIDs,
     ):
         super(OpenDirectoryRecord, self).__init__(
             service               = service,
@@ -1016,14 +642,10 @@
             guid                  = guid,
             shortName             = shortName,
             fullName              = fullName,
-            calendarUserAddresses = calendarUserAddresses,
-            autoSchedule          = autoSchedule,
-            enabledForCalendaring = enabledForCalendaring,
+            emailAddresses        = emailAddresses,
         )
         self.nodeName = nodeName
         self._memberGUIDs = tuple(memberGUIDs)
-        self._proxyGUIDs = tuple(proxyGUIDs)
-        self._readOnlyProxyGUIDs = tuple(readOnlyProxyGUIDs)
 
     def __repr__(self):
         if self.service.realmName == self.nodeName:
@@ -1053,40 +675,6 @@
     def groups(self):
         return self.service.groupsForGUID(self.guid)
 
-    def proxies(self):
-        if self.recordType not in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
-            return
-
-        for guid in self._proxyGUIDs:
-            proxyRecord = self.service.recordWithGUID(guid)
-            if proxyRecord is None:
-                self.log_error("No record for proxy in %s with GUID %s" % (self.shortName, guid))
-            else:
-                yield proxyRecord
-
-    def proxyFor(self):
-        result = set()
-        result.update(self.service.proxiesForGUID(DirectoryService.recordType_resources, self.guid))
-        result.update(self.service.proxiesForGUID(DirectoryService.recordType_locations, self.guid))
-        return result
-
-    def readOnlyProxies(self):
-        if self.recordType not in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
-            return
-
-        for guid in self._readOnlyProxyGUIDs:
-            proxyRecord = self.service.recordWithGUID(guid)
-            if proxyRecord is None:
-                self.log_error("No record for proxy in %s with GUID %s" % (self.shortName, guid))
-            else:
-                yield proxyRecord
-
-    def readOnlyProxyFor(self):
-        result = set()
-        result.update(self.service.readOnlyProxiesForGUID(DirectoryService.recordType_resources, self.guid))
-        result.update(self.service.readOnlyProxiesForGUID(DirectoryService.recordType_locations, self.guid))
-        return result
-
     def verifyCredentials(self, credentials):
         if isinstance(credentials, UsernamePassword):
             # Check cached password

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/augment.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/augment.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/augment.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,285 @@
+##
+# Copyright (c) 2009 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.
+##
+
+
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twistedcaldav.database import AbstractADBAPIDatabase
+from twistedcaldav.directory.xmlaugmentsparser import XMLAugmentsParser
+import time
+
+from twistedcaldav.log import Logger
+
+log = Logger()
+
+class AugmentRecord(object):
+    """
+    Augmented directory record information
+    """
+
+    def __init__(
+        self,
+        guid,
+        enabled=False,
+        hostedAt="",
+        enabledForCalendaring=False,
+        autoSchedule=False,
+        calendarUserAddresses=None,  
+    ):
+        self.guid = guid
+        self.enabled = enabled
+        self.hostedAt = hostedAt
+        self.enabledForCalendaring = enabledForCalendaring
+        self.autoSchedule = autoSchedule
+        self.calendarUserAddresses = calendarUserAddresses if calendarUserAddresses else set()
+
+class AugmentDB(object):
+    """
+    Abstract base class for an augment record database.
+    """
+    
+    def __init__(self):
+        pass
+    
+    def getAugmentRecord(self, guid):
+        """
+        Get an AugmentRecord for the specified GUID.
+
+        @param guid: directory GUID to lookup
+        @type guid: C{str}
+        
+        @return: L{Deferred}
+        """
+        
+        raise NotImplementedError("Child class must define this.")
+
+    def refresh(self):
+        """
+        Refresh any cached data.
+        """
+        pass
+        
+AugmentService = AugmentDB()   # Global augment service
+
+
+class AugmentXMLDB(AugmentDB):
+    """
+    XMLFile based augment database implementation.
+    """
+    
+    def __init__(self, xmlFiles, cacheTimeout=30):
+        
+        self.xmlFiles = xmlFiles
+        self.cacheTimeout = cacheTimeout * 60 # Value is mins we want secs
+        self.lastCached = 0
+        self.db = {}
+        
+        try:
+            self.db = self._parseXML()
+        except RuntimeError:
+            log.error("Failed to parse XML augments file - fatal error on startup")
+            raise
+            
+        self.lastCached = time.time()
+
+    def getAugmentRecord(self, guid):
+        """
+        Get an AugmentRecord for the specified GUID.
+
+        @param guid: directory GUID to lookup
+        @type guid: C{str}
+        
+        @return: L{Deferred}
+        """
+        
+        # May need to re-cache
+        if self.lastCached + self.cacheTimeout <= time.time():
+            self.refresh()
+            
+        return succeed(self.db.get(guid))
+
+    def refresh(self):
+        """
+        Refresh any cached data.
+        """
+        try:
+            self.db = self._parseXML()
+        except RuntimeError:
+            log.error("Failed to parse XML augments file during cache refresh - ignoring")
+        self.lastCached = time.time()
+
+    def _parseXML(self):
+        
+        # Do each file
+        results = {}
+        for xmlFile in self.xmlFiles:
+            
+            # Creating a parser does the parse
+            XMLAugmentsParser(xmlFile, results)
+        
+        return results
+
+class AugmentADAPI(AugmentDB, AbstractADBAPIDatabase):
+    """
+    DBAPI based augment database implementation.
+    """
+
+    schema_version = "1"
+    schema_type    = "AugmentDB"
+    
+    def __init__(self, dbID, dbapiName, dbapiArgs, **kwargs):
+        
+        self.cachedPartitions = {}
+        self.cachedHostedAt = {}
+        
+        AbstractADBAPIDatabase.__init__(self, dbID, dbapiName, dbapiArgs, True, **kwargs)
+        
+    @inlineCallbacks
+    def getAugmentRecord(self, guid):
+        """
+        Get an AugmentRecord for the specified GUID.
+
+        @param guid: directory GUID to lookup
+        @type guid: C{str}
+
+        @return: L{Deferred}
+        """
+        
+        # Query for the record information
+        results = (yield self.query("select GUID, ENABLED, PARTITIONID, CALENDARING, AUTOSCHEDULE, CUADDRS from AUGMENTS where GUID = :1", (guid,)))
+        if not results:
+            returnValue(None)
+        else:
+            guid, enabled, partitionid, enabdledForCalendaring, autoSchedule, cuaddrs = results[0]
+            
+            record = AugmentRecord(
+                guid = guid,
+                enabled = enabled == "T",
+                hostedAt = (yield self._getPartition(partitionid)),
+                enabledForCalendaring = enabdledForCalendaring == "T",
+                autoSchedule = autoSchedule == "T",
+                calendarUserAddresses = set(cuaddrs.split("\t")) if cuaddrs else set(),
+            )
+            
+            returnValue(record)
+
+    @inlineCallbacks
+    def addAugmentRecord(self, record):
+
+        partitionid = (yield self._getPartitionID(record.hostedAt))
+        cuaddrs = "\t".join(record.calendarUserAddresses)
+        
+        yield self.execute(
+            """insert into AUGMENTS
+            (GUID, ENABLED, PARTITIONID, CALENDARING, AUTOSCHEDULE, CUADDRS)
+            values (:1, :2, :3, :4, :5, :6)""",
+            (
+                record.guid,
+                "T" if record.enabled else "F",
+                partitionid,
+                "T" if record.enabledForCalendaring else "F",
+                "T" if record.autoSchedule else "F",
+                cuaddrs,)
+        )
+
+    @inlineCallbacks
+    def _getPartitionID(self, hostedat, createIfMissing=True):
+        
+        # We will use a cache for these as we do not expect changes whilst running
+        try:
+            returnValue(self.cachedHostedAt[hostedat])
+        except KeyError:
+            pass
+
+        partitionid = (yield self.queryOne("select PARTITIONID from PARTITIONS where HOSTEDAT = :1", (hostedat,)))
+        if partitionid == None:
+            yield self.execute("insert into PARTITIONS (HOSTEDAT) values (:1)", (hostedat,))
+            partitionid = (yield self.queryOne("select PARTITIONID from PARTITIONS where HOSTEDAT = :1", (hostedat,)))
+        self.cachedHostedAt[hostedat] = partitionid
+        returnValue(partitionid)
+
+    @inlineCallbacks
+    def _getPartition(self, partitionid):
+        
+        # We will use a cache for these as we do not expect changes whilst running
+        try:
+            returnValue(self.cachedPartitions[partitionid])
+        except KeyError:
+            pass
+
+        partition = (yield self.queryOne("select HOSTEDAT from PARTITIONS where PARTITIONID = :1", (partitionid,)))
+        self.cachedPartitions[partitionid] = partition
+        returnValue(partition)
+
+    @inlineCallbacks
+    def _getCUAddrs(self, augmentid):
+        
+        return self.queryList("select CUADDR from CUADDRS where AUGMENTID = :1", (augmentid,))
+
+    def _db_version(self):
+        """
+        @return: the schema version assigned to this index.
+        """
+        return AugmentADAPI.schema_version
+        
+    def _db_type(self):
+        """
+        @return: the collection type assigned to this index.
+        """
+        return AugmentADAPI.schema_type
+    
+    @inlineCallbacks
+    def _db_init_data_tables(self):
+        """
+        Initialise the underlying database tables.
+        """
+
+        #
+        # TESTTYPE table
+        #
+        yield self._db_execute(
+            """
+            create table AUGMENTS (
+                GUID         text unique,
+                ENABLED      text(1),
+                PARTITIONID  text,
+                CALENDARING  text(1),
+                AUTOSCHEDULE text(1),
+                CUADDRS      text
+            )
+            """
+        )
+        yield self._db_execute(
+            """
+            create table PARTITIONS (
+                PARTITIONID  integer primary key autoincrement,
+                HOSTEDAT     text
+            )
+            """
+        )
+
+    @inlineCallbacks
+    def _db_remove_data_tables(self):
+        yield self._db_execute("drop table if exists AUGMENTS")
+        yield self._db_execute("drop table if exists PARTITIONS")
+
+class AugmentSqliteDB(AugmentADAPI):
+    """
+    Sqlite based augment database implementation.
+    """
+
+    def __init__(self, dbpath):
+        
+        super(AugmentSqliteDB, self).__init__("Augments", "sqlite3", (dbpath,))

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/calendaruserproxy.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/calendaruserproxy.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2009 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.
@@ -38,9 +38,11 @@
 from twistedcaldav.resource import CalDAVComplianceMixIn
 from twistedcaldav.directory.util import NotFilePath
 from twistedcaldav.sql import AbstractSQLDatabase
+from twistedcaldav.log import LoggingMixIn
 
 import itertools
 import os
+import time
 
 class PermissionsMixIn (ReadOnlyWritePropertiesResourceMixIn):
     def defaultAccessControlList(self):
@@ -69,7 +71,7 @@
         return davxml.ACL(*aces)
 
     def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
-        # Permissions here are fixed, and are not subject to inherritance rules, etc.
+        # Permissions here are fixed, and are not subject to inheritance rules, etc.
         return succeed(self.defaultAccessControlList())
 
 class CalendarUserProxyPrincipalResource (CalDAVComplianceMixIn, PermissionsMixIn, DAVPrincipalResource, DAVFile):
@@ -156,15 +158,7 @@
         assert isinstance(property, davxml.WebDAVElement)
 
         if property.qname() == (dav_namespace, "group-member-set"):
-            if self.hasEditableMembership():
-                return self.setGroupMemberSet(property, request)
-            else:
-                raise HTTPError(
-                    StatusResponse(
-                        responsecode.FORBIDDEN,
-                        "Proxies cannot be changed."
-                    )
-                )
+            return self.setGroupMemberSet(property, request)
 
         return super(CalendarUserProxyPrincipalResource, self).writeProperty(property, request)
 
@@ -182,8 +176,9 @@
         # Break out the list into a set of URIs.
         members = [str(h) for h in new_members.children]
 
-        # Map the URIs to principals.
+        # Map the URIs to principals and a set of UIDs.
         principals = []
+        newUIDs = set()
         for uri in members:
             principal = self.pcollection._principalForURI(uri)
             # Invalid principals MUST result in an error.
@@ -193,14 +188,31 @@
                     "Attempt to use a non-existent principal %s as a group member of %s." % (uri, self.principalURL(),)
                 ))
             principals.append(principal)
-            yield principal.cacheNotifier.changed()
+            newUIDs.add(principal.principalUID())
 
+        # Get the old set of UIDs
+        oldUIDs = (yield self._index().getMembers(self.uid))
+        
+        # Change membership
+        yield self.setGroupMemberSetPrincipals(principals)
+        
+        # Invalidate the primary principal's cache, and any principal's whose
+        # membership status changed
+        yield self.parent.cacheNotifier.changed()
+        
+        changedUIDs = newUIDs.symmetric_difference(oldUIDs)
+        for uid in changedUIDs:
+            principal = self.pcollection.principalForUID(uid)
+            if principal:
+                yield principal.cacheNotifier.changed()
+            
+        returnValue(True)
+
+    @inlineCallbacks
+    def setGroupMemberSetPrincipals(self, principals):
         # Map the principals to UIDs.
         uids = [p.principalUID() for p in principals]
-
         yield self._index().setGroupMembers(self.uid, uids)
-        yield self.parent.cacheNotifier.changed()
-        returnValue(True)
 
     ##
     # HTTP
@@ -246,8 +258,7 @@
                 """Principal UID: %s\n"""          % (self.principalUID(),),
                 """Principal URL: %s\n"""          % (format_link(self.principalURL()),),
                 """\nAlternate URIs:\n"""          , format_list(format_link(u) for u in self.alternateURIs()),
-                """\nGroup members (%s):\n""" % ({False:"Locked", True:"Editable"}[self.hasEditableMembership()])
-                                                   , format_principals(closure["members"]),
+                """\nGroup members:\n"""           , format_principals(closure["members"]),
                 """\nGroup memberships:\n"""       , format_principals(closure["memberships"]),
                 """</pre></blockquote></div>""",
                 closure["output"]
@@ -307,17 +318,29 @@
 
     @inlineCallbacks
     def _directGroupMembers(self):
-        if self.hasEditableMembership():
-            # Get member UIDs from database and map to principal resources
-            members = yield self._index().getMembers(self.uid)
-            returnValue([p for p in [self.pcollection.principalForUID(uid) for uid in members] if p])
-        else:
-            # Fixed proxies
-            if self.proxyType == "calendar-proxy-write":
-                returnValue(self.parent.proxies())
+        # Get member UIDs from database and map to principal resources
+        members = yield self._index().getMembers(self.uid)
+        found = []
+        missing = []
+        for uid in members:
+            p = self.pcollection.principalForUID(uid)
+            if p:
+                found.append(p)
+                # Make sure any outstanding deletion timer entries for
+                # existing principals are removed
+                yield self._index()._memcacher.clearDeletionTimer(uid)
             else:
-                returnValue(self.parent.readOnlyProxies())
+                missing.append(uid)
 
+        # Clean-up ones that are missing
+        for uid in missing:
+            cacheTimeout = config.DirectoryService.params.get("cacheTimeout", 30) * 60 # in seconds
+
+            yield self._index().removePrincipal(uid,
+                delay=cacheTimeout*2)
+
+        returnValue(found)
+
     def groupMembers(self):
         return self._expandMemberUIDs()
 
@@ -327,10 +350,7 @@
         memberships = yield self._index().getMemberships(self.uid)
         returnValue([p for p in [self.pcollection.principalForUID(uid) for uid in memberships] if p])
 
-    def hasEditableMembership(self):
-        return self.parent.hasEditableProxyMembership()
-
-class CalendarUserProxyDatabase(AbstractSQLDatabase):
+class CalendarUserProxyDatabase(AbstractSQLDatabase, LoggingMixIn):
     """
     A database to maintain calendar user proxy group memberships.
 
@@ -384,6 +404,33 @@
         def deleteMembership(self, guid):
             return self.delete("memberships:%s" % (guid,))
 
+        def setDeletionTimer(self, guid, delay):
+            return self.set("del:%s" % (str(guid),), str(self.getTime()+delay))
+
+        def checkDeletionTimer(self, guid):
+            # True means it's overdue, False means it's not, None means no timer
+            def _value(value):
+                if value:
+                    if int(value) <= self.getTime():
+                        return True
+                    else:
+                        return False
+                else:
+                    return None
+            d = self.get("del:%s" % (str(guid),))
+            d.addCallback(_value)
+            return d
+
+        def clearDeletionTimer(self, guid):
+            return self.delete("del:%s" % (str(guid),))
+
+        def getTime(self):
+            if hasattr(self, 'theTime'):
+                theTime = self.theTime
+            else:
+                theTime = int(time.time())
+            return theTime
+
     def __init__(self, path):
         path = os.path.join(path, CalendarUserProxyDatabase.dbFilename)
         super(CalendarUserProxyDatabase, self).__init__(path, True)
@@ -405,11 +452,8 @@
             current_members = ()
         current_members = set(current_members)
 
-        # Remove what is there, then add it back.
-        self._delete_from_db(principalUID)
-        self._add_to_db(principalUID, members)
-        self._db_commit()
-        
+        self.setGroupMembersInDatabase(principalUID, members)
+
         # Update cache
         update_members = set(members)
         
@@ -419,6 +463,18 @@
             _ignore = yield self._memcacher.deleteMembership(member)
         _ignore = yield self._memcacher.deleteMember(principalUID)
 
+    def setGroupMembersInDatabase(self, principalUID, members):
+        """
+        A blocking call to add a group membership record in the database.
+
+        @param principalUID: the UID of the group principal to add.
+        @param members: a list UIDs of principals that are members of this group.
+        """
+        # Remove what is there, then add it back.
+        self._delete_from_db(principalUID)
+        self._add_to_db(principalUID, members)
+        self._db_commit()
+        
     @inlineCallbacks
     def removeGroup(self, principalUID):
         """
@@ -427,17 +483,71 @@
         @param principalUID: the UID of the group principal to remove.
         """
 
+        # Need to get the members before we do the delete
+        members = yield self.getMembers(principalUID)
+
         self._delete_from_db(principalUID)
         self._db_commit()
         
         # Update cache
-        members = yield self.getMembers(principalUID)
         if members:
             for member in members:
                 yield self._memcacher.deleteMembership(member)
             yield self._memcacher.deleteMember(principalUID)
 
     @inlineCallbacks
+    def removePrincipal(self, principalUID, delay=None):
+        """
+        Remove a group membership record.
+
+        @param principalUID: the UID of the principal to remove.
+        """
+
+        if delay:
+            # We are going to remove the principal only after <delay> seconds
+            # has passed since we first chose to remove it, to protect against
+            # transient directory problems.
+            # If <delay> is specified, first see if there was a timer set
+            # previously.  If the timer is more than delay seconds old, we
+            # go ahead and remove the principal.  Otherwise, do nothing.
+
+            overdue = yield self._memcacher.checkDeletionTimer(principalUID)
+
+            if overdue == False:
+                # Do nothing
+                returnValue(None)
+
+            elif overdue is None:
+                # No timer was previously set
+                self.log_debug("Delaying removal of missing proxy principal '%s'" %
+                    (principalUID,))
+                self._memcacher.setDeletionTimer(principalUID, delay=delay)
+                returnValue(None)
+
+        self.log_warn("Removing missing proxy principal for '%s'" %
+            (principalUID,))
+
+        for suffix in ("calendar-proxy-read", "calendar-proxy-write",):
+            groupUID = "%s#%s" % (principalUID, suffix,)
+            self._delete_from_db(groupUID)
+
+            # Update cache
+            members = yield self.getMembers(groupUID)
+            if members:
+                for member in members:
+                    yield self._memcacher.deleteMembership(member)
+                yield self._memcacher.deleteMember(groupUID)
+
+        memberships = (yield self.getMemberships(principalUID))
+        for groupUID in memberships:
+            yield self._memcacher.deleteMember(groupUID)
+
+        self._delete_from_db_member(principalUID)
+        yield self._memcacher.deleteMembership(principalUID)
+        self._db_commit()
+        self._memcacher.clearDeletionTimer(principalUID)
+
+    @inlineCallbacks
     def getMembers(self, principalUID):
         """
         Return the list of group member UIDs for the specified principal.
@@ -502,6 +612,14 @@
         """
         self._db_execute("delete from GROUPS where GROUPNAME = :1", principalUID)
 
+    def _delete_from_db_member(self, principalUID):
+        """
+        Deletes the specified member entry from the database.
+
+        @param principalUID: the UID of the member principal to remove.
+        """
+        self._db_execute("delete from GROUPS where MEMBER = :1", principalUID)
+
     def _db_version(self):
         """
         @return: the schema version assigned to this index.

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/calendaruserproxyloader.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/calendaruserproxyloader.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/calendaruserproxyloader.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,138 @@
+##
+# Copyright (c) 2009 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.
+##
+
+from xml.etree.ElementTree import ElementTree
+from xml.parsers.expat import ExpatError
+from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyDatabase
+from twistedcaldav.config import config
+from twisted.internet.defer import inlineCallbacks
+import types
+
+"""
+XML based calendar user proxy loader.
+"""
+
+__all__ = [
+    "XMLCalendarUserProxyLoader",
+]
+
+from twistedcaldav.log import Logger
+
+log = Logger()
+
+ELEMENT_PROXIES           = "proxies"
+ELEMENT_RECORD            = "record"
+
+ELEMENT_GUID              = "guid"
+ELEMENT_PROXIES           = "proxies"
+ELEMENT_READ_ONLY_PROXIES = "read-only-proxies"
+ELEMENT_MEMBER            = "member"
+
+ATTRIBUTE_REPEAT          = "repeat"
+
+class XMLCalendarUserProxyLoader(object):
+    """
+    XML calendar user proxy configuration file parser and loader.
+    """
+    def __repr__(self):
+        return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
+
+    def __init__(self, xmlFile):
+
+        self.items = []
+        self.xmlFile = xmlFile
+
+        # Read in XML
+        try:
+            tree = ElementTree(file=self.xmlFile)
+        except ExpatError, e:
+            log.error("Unable to parse file '%s' because: %s" % (self.xmlFile, e,), raiseException=RuntimeError)
+
+        # Verify that top-level element is correct
+        proxies_node = tree.getroot()
+        if proxies_node.tag != ELEMENT_PROXIES:
+            log.error("Ignoring file '%s' because it is not a proxies file" % (self.xmlFile,), raiseException=RuntimeError)
+
+        self._parseXML(proxies_node)
+
+    def _parseXML(self, rootnode):
+        """
+        Parse the XML root node from the augments configuration document.
+        @param rootnode: the L{Element} to parse.
+        """
+        for child in rootnode.getchildren():
+            
+            if child.tag != ELEMENT_RECORD:
+                log.error("Unknown augment type: '%s' in augment file: '%s'" % (child.tag, self.xmlFile,), raiseException=RuntimeError)
+
+            repeat = int(child.get(ATTRIBUTE_REPEAT, "1"))
+
+            guid = None
+            write_proxies = set()
+            read_proxies = set()
+            for node in child.getchildren():
+                
+                if node.tag == ELEMENT_GUID:
+                    guid = node.text
+
+                elif node.tag in (
+                    ELEMENT_PROXIES,
+                    ELEMENT_READ_ONLY_PROXIES,
+                ):
+                    self._parseMembers(node, write_proxies if node.tag == ELEMENT_PROXIES else read_proxies)
+                else:
+                    log.error("Invalid element '%s' in proxies file: '%s'" % (node.tag, self.xmlFile,), raiseException=RuntimeError)
+                    
+            # Must have at least a guid
+            if not guid:
+                log.error("Invalid record '%s' without a guid in proxies file: '%s'" % (child, self.xmlFile,), raiseException=RuntimeError)
+                
+            if repeat > 1:
+                for i in xrange(1, repeat+1):
+                    self._buildRecord(guid, write_proxies, read_proxies, i)
+            else:
+                self._buildRecord(guid, write_proxies, read_proxies)
+
+    def _parseMembers(self, node, addto):
+        for child in node.getchildren():
+            if child.tag == ELEMENT_MEMBER:
+                addto.add(child.text)
+    
+    def _buildRecord(self, guid, write_proxies, read_proxies, count=None):
+
+        def expandCount(value, count):
+            
+            if type(value) in types.StringTypes:
+                return value % (count,) if count and "%" in value else value
+            else:
+                return value
+        
+        guid = expandCount(guid, count)
+        write_proxies = set([expandCount(member, count) for member in write_proxies])
+        read_proxies = set([expandCount(member, count) for member in read_proxies])
+            
+        self.items.append((guid, write_proxies, read_proxies,))
+
+    @inlineCallbacks
+    def updateProxyDB(self):
+        
+        db = CalendarUserProxyDatabase(config.DataRoot)
+        for item in self.items:
+            guid, write_proxies, read_proxies = item
+            for proxy in write_proxies:
+                yield db.setGroupMembers("%s#%s" % (guid, "calendar-proxy-write"), (proxy,))
+            for proxy in read_proxies:
+                yield db.setGroupMembers("%s#%s" % (guid, "calendar-proxy-read"), (proxy,))

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/directory.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/directory.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -34,9 +34,11 @@
 from twisted.cred.checkers import ICredentialsChecker
 from twisted.web2.dav.auth import IPrincipalCredentials
 
+from twistedcaldav.config import config
 from twistedcaldav.log import LoggingMixIn
 from twistedcaldav.directory.idirectory import IDirectoryService, IDirectoryRecord
 from twistedcaldav.directory.util import uuidFromName
+from twistedcaldav.partitions import partitions
 
 class DirectoryService(LoggingMixIn):
     implements(IDirectoryService, ICredentialsChecker)
@@ -144,19 +146,20 @@
     implements(IDirectoryRecord)
 
     def __repr__(self):
-        return "<%s[%s@%s(%s)] %s(%s) %r>" % (
+        return "<%s[%s@%s(%s)] %s(%s) %r @ %s>" % (
             self.__class__.__name__,
             self.recordType,
             self.service.guid,
             self.service.realmName,
             self.guid,
             self.shortName,
-            self.fullName
+            self.fullName,
+            self.hostedAt,
         )
 
     def __init__(
-        self, service, recordType, guid, shortName, fullName,
-        calendarUserAddresses, autoSchedule, enabledForCalendaring=True,
+        self, service, recordType, guid,
+        shortName, fullName, emailAddresses,
     ):
         assert service.realmName is not None
         assert recordType
@@ -165,19 +168,17 @@
         if not guid:
             guid = uuidFromName(service.guid, "%s:%s" % (recordType, shortName))
 
-        if enabledForCalendaring:
-            calendarUserAddresses.add("urn:uuid:%s" % (guid,))
-        else:
-            assert len(calendarUserAddresses) == 0
-
         self.service               = service
         self.recordType            = recordType
         self.guid                  = guid
+        self.enabled               = False
+        self.hostedAt              = ""
         self.shortName             = shortName
         self.fullName              = fullName
-        self.enabledForCalendaring = enabledForCalendaring
-        self.calendarUserAddresses = calendarUserAddresses
-        self.autoSchedule          = autoSchedule
+        self.emailAddresses        = emailAddresses
+        self.enabledForCalendaring = False
+        self.autoSchedule          = False
+        self.calendarUserAddresses = set()
 
     def __cmp__(self, other):
         if not isinstance(other, DirectoryRecord):
@@ -192,35 +193,51 @@
     def __hash__(self):
         h = hash(self.__class__)
         for attr in ("service", "recordType", "shortName", "guid",
-                     "enabledForCalendaring"):
+                     "enabled", "enabledForCalendaring"):
             h = (h + hash(getattr(self, attr))) & sys.maxint
 
         return h
 
-    def members(self):
-        return ()
+    def addAugmentInformation(self, augment):
+        
+        if augment:
+            self.enabled = augment.enabled
+            self.hostedAt = augment.hostedAt
+            self.enabledForCalendaring = augment.enabledForCalendaring
+            self.autoSchedule = augment.autoSchedule
+            self.calendarUserAddresses = set(augment.calendarUserAddresses)
 
-    def groups(self):
-        return ()
+            if self.enabledForCalendaring and self.recordType == self.service.recordType_groups:
+                raise AssertionError("Groups may not be enabled for calendaring")
+    
+            if self.enabledForCalendaring:
+                for email in self.emailAddresses:
+                    self.calendarUserAddresses.add("mailto:%s" % (email.lower(),))
+                self.calendarUserAddresses.add("urn:uuid:%s" % (self.guid,))
+            else:
+                assert len(self.calendarUserAddresses) == 0
 
-    def proxies(self):
-        return ()
+        else:
+            self.enabled = False
+            self.hostedAt = ""
+            self.enabledForCalendaring = False
+            self.calendarUserAddresses = set()
 
-    def proxyFor(self):
+    def members(self):
         return ()
 
-    def readOnlyProxies(self):
+    def groups(self):
         return ()
 
-    def readOnlyProxyFor(self):
-        return ()
-
-    def hasEditableProxyMembership(self):
-        return self.recordType in (DirectoryService.recordType_users, DirectoryService.recordType_groups)
-
     def verifyCredentials(self, credentials):
         return False
 
+    def locallyHosted(self):
+        return not self.hostedAt or not config.EnablePartitions or self.hostedAt == config.ServerPartitionID
+    
+    def hostedURL(self):
+        return partitions.getPartitionURL(self.hostedAt)
+
 class DirectoryError(RuntimeError):
     """
     Generic directory error.

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/idirectory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/idirectory.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/idirectory.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -79,11 +79,13 @@
     service               = Attribute("The L{IDirectoryService} this record exists in.")
     recordType            = Attribute("The type of this record.")
     guid                  = Attribute("The GUID of this record.")
+    enabled               = Attribute("Determines whether this record should be provisioned as a principal.")
+    hostedAt              = Attribute("Identifies the server that actually hosts data for the record.")
     shortName             = Attribute("The name of this record.")
     fullName              = Attribute("The full name of this record.")
+    emailAddress          = Attribute("The email address of this record.")
+    enabledForCalendaring = Attribute("Determines whether this record should be provisioned with a calendar home.")
     calendarUserAddresses = Attribute("A set of calendar user addresses for this record.")
-    autoSchedule          = Attribute("Principal identified by this record should automatically accept/deny meetings.")
-    enabledForCalendaring = Attribute("Determines whether this record should be provisioned with a calendar home.")
 
     def members():
         """

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/principal.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/principal.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2009 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.
@@ -116,7 +116,7 @@
         raise NotImplementedError("Subclass must implement principalForUID()")
 
     def principalForRecord(self, record):
-        if record is None:
+        if record is None or not record.enabled:
             return None
         return self.principalForUID(record.guid)
 
@@ -145,7 +145,7 @@
         return self.getChild(uidsResourceName).getChild(uid)
 
     def _principalForURI(self, uri):
-        scheme, netloc, path, params, query, fragment = urlparse(uri)
+        scheme, netloc, path, _ignore_params, _ignore_query, _ignore_fragment = urlparse(uri)
 
         if scheme == "":
             pass
@@ -203,7 +203,7 @@
 
         # Next try looking it up in the directory
         record = self.directory.recordWithCalendarUserAddress(address)
-        if record is not None:
+        if record is not None and record.enabled:
             return self.principalForRecord(record)
 
         log.debug("No principal for calendar user address: %r" % (address,))
@@ -275,7 +275,7 @@
 
     def listChildren(self):
         if config.EnablePrincipalListings:
-            return (record.shortName for record in self.directory.listRecords(self.recordType))
+            return (record.shortName for record in self.directory.listRecords(self.recordType) if record.enabled)
         else:
             # Not a listable collection
             raise HTTPError(responsecode.FORBIDDEN)
@@ -332,7 +332,7 @@
 
         record = self.directory.recordWithGUID(primaryUID)
 
-        if record is None:
+        if record is None or not record.enabled:
             log.err("No principal found for UID: %s" % (name,))
             return None
 
@@ -449,6 +449,7 @@
             """Record type: %s\n"""            % (self.record.recordType,),
             """Short name: %s\n"""             % (self.record.shortName,),
             """Full name: %s\n"""              % (self.record.fullName,),
+            """Email addresses:\n"""           , format_list(self.record.emailAddresses),
             """Principal UID: %s\n"""          % (self.principalUID(),),
             """Principal URL: %s\n"""          % (format_link(self.principalURL()),),
             """\nAlternate URIs:\n"""          , format_list(format_link(u) for u in self.alternateURIs()),
@@ -512,7 +513,6 @@
 
         if record not in records:
             records.add(record)
-            myRecordType = self.record.recordType
             for relative in getattr(record, method)():
                 if relative not in records:
                     found = self.parent.principalForRecord(relative)
@@ -538,10 +538,6 @@
         groups = self._getRelatives("groups")
 
         if config.EnableProxyPrincipals:
-            # Get any directory specified proxies
-            groups.update(self._getRelatives("proxyFor", proxy='read-write'))
-            groups.update(self._getRelatives("readOnlyProxyFor", proxy='read-only'))
-
             # Get proxy group UIDs and map to principal resources
             proxies = []
             d = waitForDeferred(self._calendar_user_proxy_index().getMemberships(self.principalUID()))
@@ -551,6 +547,10 @@
                 subprincipal = self.parent.principalForUID(uid)
                 if subprincipal:
                     proxies.append(subprincipal)
+                else:
+                    d = waitForDeferred(self._calendar_user_proxy_index().removeGroup(uid))
+                    yield d
+                    d.getResult()
 
             groups.update(proxies)
 
@@ -569,13 +569,6 @@
                 proxyFors.update(results)
 
         if config.EnableProxyPrincipals:
-            # Get any directory specified proxies
-            if read_write:
-                directoryProxies = self._getRelatives("proxyFor", proxy='read-write')
-            else:
-                directoryProxies = self._getRelatives("readOnlyProxyFor", proxy='read-only')
-            proxyFors.update([subprincipal.parent for subprincipal in directoryProxies])
-
             # Get proxy group UIDs and map to principal resources
             proxies = []
             d = waitForDeferred(self._calendar_user_proxy_index().getMemberships(self.principalUID()))
@@ -596,7 +589,23 @@
     def principalUID(self):
         return self.record.guid
 
+    def locallyHosted(self):
+        return self.record.locallyHosted()
+    
+    def hostedURL(self):
+        return self.record.hostedURL()
+
     ##
+    # Extra resource info
+    ##
+
+    def setAutoSchedule(self, autoSchedule):
+        self.record.autoSchedule = autoSchedule
+
+    def getAutoSchedule(self):
+        return self.record.autoSchedule
+
+    ##
     # Static
     ##
 
@@ -699,15 +708,6 @@
     def autoSchedule(self):
         return self.record.autoSchedule
 
-    def proxies(self):
-        return self._getRelatives("proxies")
-
-    def readOnlyProxies(self):
-        return self._getRelatives("readOnlyProxies")
-
-    def hasEditableProxyMembership(self):
-        return self.record.hasEditableProxyMembership()
-
     def scheduleInbox(self, request):
         home = self._calendarHome()
         if home is None:
@@ -720,11 +720,8 @@
         return succeed(inbox)
 
     def calendarHomeURLs(self):
-        home = self._calendarHome()
-        if home is None:
-            return ()
-        else:
-            return (home.url(),)
+        homeURL = self._homeChildURL(None)
+        return (homeURL,) if homeURL else ()
 
     def scheduleInboxURL(self):
         return self._homeChildURL("inbox/")
@@ -743,7 +740,13 @@
         if home is None:
             return None
         else:
-            return joinURL(home.url(), name)
+            url = home.url()
+            if name:
+                url = joinURL(url, name)
+            if not self.locallyHosted():
+                url = joinURL(self.hostedURL(), url)
+                
+            return url
 
     def _calendarHome(self):
         # FIXME: self.record.service.calendarHomesCollection smells like a hack

Deleted: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/sqldb.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/sqldb.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/sqldb.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,377 +0,0 @@
-##
-# Copyright (c) 2006-2007 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.
-##
-
-"""
-SQL (sqlite) based user/group/resource directory service implementation.
-"""
-
-"""
-SCHEMA:
-
-User Database:
-
-ROW: RECORD_TYPE, SHORT_NAME (unique), PASSWORD, NAME
-
-Group Database:
-
-ROW: SHORT_NAME, MEMBER_SHORT_NAME
-
-CUAddress database:
-
-ROW: ADDRESS (unqiue), SHORT_NAME
-
-"""
-
-__all__ = [
-    "SQLDirectoryService",
-]
-
-from twisted.cred.credentials import UsernamePassword
-from twisted.python.filepath import FilePath
-
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
-from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser
-from twistedcaldav.sql import AbstractSQLDatabase
-from twistedcaldav.sql import db_prefix
-
-import os
-
-class SQLDirectoryManager(AbstractSQLDatabase):
-    """
-    House keeping operations on the SQL DB, including loading from XML file,
-    and record dumping. This can be used as a standalong DB management tool.
-    """
-    dbType = "DIRECTORYSERVICE"
-    dbFilename = db_prefix + "accounts"
-    dbFormatVersion = "3"
-
-    def __init__(self, path):
-        path = os.path.join(path, SQLDirectoryManager.dbFilename)
-        super(SQLDirectoryManager, self).__init__(path, True)
-
-    def loadFromXML(self, xmlFile):
-        parser = XMLAccountsParser(xmlFile)
-       
-        # Totally wipe existing DB and start from scratch
-        if os.path.exists(self.dbpath):
-            os.remove(self.dbpath)
-
-        self._db_execute("insert into SERVICE (REALM) values (:1)", parser.realm)
-
-        # Now add records to db
-        for item in parser.items.values():
-            for entry in item.itervalues():
-                self._add_to_db(entry)
-        self._db_commit()
-
-    def getRealm(self):
-        for realm in self._db_execute("select REALM from SERVICE"):
-            return realm[0].decode("utf-8")
-        else:
-            return ""
-
-    def listRecords(self, recordType):
-        # Get each account record
-        for shortName, guid, password, name in self._db_execute(
-            """
-            select SHORT_NAME, GUID, PASSWORD, NAME
-              from ACCOUNTS
-             where RECORD_TYPE = :1
-            """, recordType
-        ):
-            # See if we have members
-            members = self.members(shortName)
-                
-            # See if we are a member of any groups
-            groups = self.groups(shortName)
-                
-            # Get calendar user addresses
-            calendarUserAddresses = self.calendarUserAddresses(shortName)
-            
-            # TODO: need this for Resources and Locations
-            autoSchedule = False
-                
-            yield shortName, guid, password, name, members, groups, calendarUserAddresses, autoSchedule
-
-    def getRecord(self, recordType, shortName):
-        # Get individual account record
-        for shortName, guid, password, name in self._db_execute(
-            """
-            select SHORT_NAME, GUID, PASSWORD, NAME
-              from ACCOUNTS
-             where RECORD_TYPE = :1
-               and SHORT_NAME  = :2
-            """, recordType, shortName
-        ):
-            break
-        else:
-            return None
-        
-        # See if we have members
-        members = self.members(shortName)
-            
-        # See if we are a member of any groups
-        groups = self.groups(shortName)
-            
-        # Get calendar user addresses
-        calendarUserAddresses = self.calendarUserAddresses(shortName)
-        
-        # TODO: need this for Resources and Locations
-        autoSchedule = False
-            
-        return shortName, guid, password, name, members, groups, calendarUserAddresses, autoSchedule
-            
-    def members(self, shortName):
-        members = set()
-        for member in self._db_execute(
-            """
-            select MEMBER_RECORD_TYPE, MEMBER_SHORT_NAME
-              from GROUPS
-             where SHORT_NAME = :1
-            """, shortName
-        ):
-            members.add(tuple(member))
-        return members
-
-    def groups(self, shortName):
-        groups = set()
-        for (name,) in self._db_execute(
-            """
-            select SHORT_NAME
-              from GROUPS
-             where MEMBER_SHORT_NAME = :1
-            """, shortName
-        ):
-            groups.add(name)
-        return groups
-
-    def calendarUserAddresses(self, shortName):
-        calendarUserAddresses = set()
-        for (address,) in self._db_execute(
-            """
-            select ADDRESS
-              from ADDRESSES
-             where SHORT_NAME = :1
-            """, shortName
-        ):
-            calendarUserAddresses.add(address)
-        return calendarUserAddresses
-
-    def _add_to_db(self, record):
-        # Do regular account entry
-        recordType = record.recordType
-        shortName = record.shortName
-        guid = record.guid
-        password = record.password
-        name = record.name
-
-        self._db_execute(
-            """
-            insert into ACCOUNTS (RECORD_TYPE, SHORT_NAME, GUID, PASSWORD, NAME)
-            values (:1, :2, :3, :4, :5)
-            """, recordType, shortName, guid, password, name
-        )
-        
-        # Check for members
-        for memberRecordType, memberShortName in record.members:
-            self._db_execute(
-                """
-                insert into GROUPS (SHORT_NAME, MEMBER_RECORD_TYPE, MEMBER_SHORT_NAME)
-                values (:1, :2, :3)
-                """, shortName, memberRecordType, memberShortName
-            )
-                
-        # CUAddress
-        for cuaddr in record.calendarUserAddresses:
-            self._db_execute(
-                """
-                insert into ADDRESSES (ADDRESS, SHORT_NAME)
-                values (:1, :2)
-                """, cuaddr, shortName
-            )
-       
-    def _delete_from_db(self, shortName):
-        """
-        Deletes the specified entry from all dbs.
-        @param name: the name of the resource to delete.
-        @param shortName: the short name of the resource to delete.
-        """
-        self._db_execute("delete from ACCOUNTS  where SHORT_NAME        = :1", shortName)
-        self._db_execute("delete from GROUPS    where SHORT_NAME        = :1", shortName)
-        self._db_execute("delete from GROUPS    where MEMBER_SHORT_NAME = :1", shortName)
-        self._db_execute("delete from ADDRESSES where SHORT_NAME        = :1", shortName)
-    
-    def _db_version(self):
-        """
-        @return: the schema version assigned to this index.
-        """
-        return SQLDirectoryManager.dbFormatVersion
-        
-    def _db_type(self):
-        """
-        @return: the collection type assigned to this index.
-        """
-        return SQLDirectoryManager.dbType
-        
-    def _db_init_data_tables(self, q):
-        """
-        Initialise the underlying database tables.
-        @param q:           a database cursor to use.
-        """
-        #
-        # SERVICE table
-        #
-        q.execute("create table SERVICE (REALM text)")
-
-        #
-        # ACCOUNTS table
-        #
-        q.execute(
-            """
-            create table ACCOUNTS (
-                RECORD_TYPE  text,
-                SHORT_NAME   text,
-                GUID         text,
-                PASSWORD     text,
-                NAME         text
-            )
-            """
-        )
-
-        #
-        # GROUPS table
-        #
-        q.execute(
-            """
-            create table GROUPS (
-                SHORT_NAME          text,
-                MEMBER_RECORD_TYPE  text,
-                MEMBER_SHORT_NAME   text
-            )
-            """
-        )
-
-        #
-        # ADDRESSES table
-        #
-        q.execute(
-            """
-            create table ADDRESSES (
-                ADDRESS     text unique,
-                SHORT_NAME  text
-            )
-            """
-        )
-
-class SQLDirectoryService(DirectoryService):
-    """
-    XML based implementation of L{IDirectoryService}.
-    """
-    baseGUID = "8256E464-35E0-4DBB-A99C-F0E30C231675"
-    realmName = None
-
-    def __repr__(self):
-        return "<%s %r: %r>" % (self.__class__.__name__, self.realmName, self.manager.dbpath)
-
-    def __init__(self, dbParentPath, xmlFile=None):
-        super(SQLDirectoryService, self).__init__()
-
-        if type(dbParentPath) is str:
-            dbParentPath = FilePath(dbParentPath)
-            
-        self.manager = SQLDirectoryManager(dbParentPath.path)
-        if xmlFile:
-            self.manager.loadFromXML(xmlFile)
-        self.realmName = self.manager.getRealm()
-
-    def recordTypes(self):
-        recordTypes = (
-            DirectoryService.recordType_users,
-            DirectoryService.recordType_groups,
-            DirectoryService.recordType_locations,
-            DirectoryService.recordType_resources,
-        )
-        return recordTypes
-
-    def listRecords(self, recordType):
-        for result in self.manager.listRecords(recordType):
-            yield SQLDirectoryRecord(
-                service               = self,
-                recordType            = recordType,
-                shortName             = result[0],
-                guid                  = result[1],
-                password              = result[2],
-                name                  = result[3],
-                members               = result[4],
-                groups                = result[5],
-                calendarUserAddresses = result[6],
-                autoSchedule          = result[7],
-            )
-
-    def recordWithShortName(self, recordType, shortName):
-        result = self.manager.getRecord(recordType, shortName)
-        if result:
-            return SQLDirectoryRecord(
-                service               = self,
-                recordType            = recordType,
-                shortName             = result[0],
-                guid                  = result[1],
-                password              = result[2],
-                name                  = result[3],
-                members               = result[4],
-                groups                = result[5],
-                calendarUserAddresses = result[6],
-                autoSchedule          = result[7],
-            )
-
-        return None
-
-class SQLDirectoryRecord(DirectoryRecord):
-    """
-    XML based implementation implementation of L{IDirectoryRecord}.
-    """
-    def __init__(self, service, recordType, shortName, guid, password, name, members, groups, calendarUserAddresses, autoSchedule):
-        super(SQLDirectoryRecord, self).__init__(
-            service               = service,
-            recordType            = recordType,
-            guid                  = guid,
-            shortName             = shortName,
-            fullName              = name,
-            calendarUserAddresses = calendarUserAddresses,
-            autoSchedule          = autoSchedule,
-        )
-
-        self.password = password
-        self._members = members
-        self._groups  = groups
-
-    def members(self):
-        for recordType, shortName in self._members:
-            yield self.service.recordWithShortName(recordType, shortName)
-
-    def groups(self):
-        for shortName in self._groups:
-            yield self.service.recordWithShortName(DirectoryService.recordType_groups, shortName)
-
-    def verifyCredentials(self, credentials):
-        if isinstance(credentials, UsernamePassword):
-            return credentials.password == self.password
-
-        return super(SQLDirectoryRecord, self).verifyCredentials(credentials)
-
-if __name__ == '__main__':
-    mgr = SQLDirectoryManager("./")
-    mgr.loadFromXML("test/accounts.xml")

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/sudo.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/sudo.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/sudo.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -132,12 +132,13 @@
             guid=None,
             shortName=shortName,
             fullName=shortName,
-            calendarUserAddresses=set(),
-            autoSchedule=False,
-            enabledForCalendaring=False)
+            emailAddresses=set(),
+        )
 
         self.password = entry['password']
 
+        self.enabled = True     # Explicitly enabled
+
     def verifyCredentials(self, credentials):
         if IUsernamePassword.providedBy(credentials):
             return credentials.checkPassword(self.password)

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/accounts.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/accounts.xml	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/accounts.xml	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+Copyright (c) 2006-2009 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 @@
 limitations under the License.
  -->
 
-<!DOCTYPE accounts SYSTEM "../../../conf/accounts.dtd">
+<!DOCTYPE accounts SYSTEM "../../../conf/auth/accounts.dtd">
 
 <accounts realm="Test">
   <user>
@@ -30,36 +30,45 @@
     <guid>6423F94A-6B76-4A3A-815B-D52CFD77935D</guid>
     <password>zehcnasw</password>
     <name>Wilfredo Sanchez</name>
-    <cuaddr>mailto:wsanchez at example.com</cuaddr>
+    <email-address>wsanchez at example.com</email-address>
   </user>
   <user>
     <uid>cdaboo</uid>
     <guid>5A985493-EE2C-4665-94CF-4DFEA3A89500</guid>
     <password>oobadc</password>
     <name>Cyrus Daboo</name>
-    <cuaddr>mailto:cdaboo at example.com</cuaddr>
+    <email-address>cdaboo at example.com</email-address>
   </user>
   <user>
     <uid>lecroy</uid>
     <guid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</guid>
     <password>yorcel</password>
     <name>Chris Lecroy</name>
-    <cuaddr>mailto:lecroy at example.com</cuaddr>
+    <email-address>lecroy at example.com</email-address>
   </user>
   <user>
     <uid>dreid</uid>
     <guid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
     <password>dierd</password>
     <name>David Reid</name>
-    <cuaddr>mailto:dreid at example.com</cuaddr>
+    <email-address>dreid at example.com</email-address>
   </user>
+  <user>
+    <uid>nocalendar</uid>
+    <guid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</guid>
+    <password>radnelacon</password>
+    <name>No Calendar</name>
+    <email-address>nocalendar at example.com</email-address>
+  </user>
   <user repeat="2">
     <uid>user%02d</uid>
+    <guid>user%02d</guid>
     <password>%02duser</password>
     <name>User %02d</name>
   </user>
   <group>
     <uid>managers</uid>
+    <guid>9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
     <password>managers</password>
     <name>Managers</name>
     <members>
@@ -68,6 +77,7 @@
   </group>
   <group>
     <uid>admin</uid>
+    <guid>admin</guid>
     <password>admin</password>
     <name>Administrators</name>
     <members>
@@ -76,6 +86,7 @@
   </group>
   <group>
     <uid>grunts</uid>
+    <guid>grunts</guid>
     <password>grunts</password>
     <name>We do all the work</name>
     <members>
@@ -86,6 +97,7 @@
   </group>
   <group>
     <uid>right_coast</uid>
+    <guid>right_coast</guid>
     <password>right_coast</password>
     <name>East Coast</name>
     <members>
@@ -94,6 +106,7 @@
   </group>
   <group>
     <uid>left_coast</uid>
+    <guid>left_coast</guid>
     <password>left_coast</password>
     <name>West Coast</name>
     <members>
@@ -104,6 +117,7 @@
   </group>
   <group>
     <uid>both_coasts</uid>
+    <guid>both_coasts</guid>
     <password>both_coasts</password>
     <name>Both Coasts</name>
     <members>
@@ -113,6 +127,7 @@
   </group>
   <group>
     <uid>recursive1_coasts</uid>
+    <guid>recursive1_coasts</guid>
     <password>recursive1_coasts</password>
     <name>Recursive1 Coasts</name>
     <members>
@@ -122,6 +137,7 @@
   </group>
   <group>
     <uid>recursive2_coasts</uid>
+    <guid>recursive2_coasts</guid>
     <password>recursive2_coasts</password>
     <name>Recursive2 Coasts</name>
     <members>
@@ -131,74 +147,61 @@
   </group>
   <group>
     <uid>non_calendar_group</uid>
+    <guid>non_calendar_group</guid>
     <password>non_calendar_group</password>
     <name>Non-calendar group</name>
     <members>
       <member>cdaboo</member>
       <member>lecroy</member>
     </members>
-    <disable-calendar/>
   </group>
   <location>
     <uid>mercury</uid>
+    <guid>mercury</guid>
     <password>mercury</password>
     <name>Mecury Seven</name>
-    <cuaddr>mailto:mercury at example.com</cuaddr>
-    <proxies>
-      <member type="groups">left_coast</member>
-    </proxies>
+    <email-address>mercury at example.com</email-address>
   </location>
   <location>
     <uid>gemini</uid>
+    <guid>gemini</guid>
     <password>gemini</password>
     <name>Gemini Twelve</name>
-    <cuaddr>mailto:gemini at example.com</cuaddr>
-    <auto-schedule/>
-    <proxies>
-      <member>wsanchez</member>
-    </proxies>
+    <email-address>gemini at example.com</email-address>
   </location>
   <location>
     <uid>apollo</uid>
+    <guid>apollo</guid>
     <password>apollo</password>
     <name>Apollo Eleven</name>
-    <cuaddr>mailto:apollo at example.com</cuaddr>
-    <proxies>
-      <member type="groups">both_coasts</member>
-    </proxies>
+    <email-address>apollo at example.com</email-address>
   </location>
   <location>
     <uid>orion</uid>
+    <guid>orion</guid>
     <password>orion</password>
     <name>Orion</name>
-    <cuaddr>mailto:orion at example.com</cuaddr>
-    <proxies>
-      <member type="groups">recursive1_coasts</member>
-    </proxies>
+    <email-address>orion at example.com</email-address>
   </location>
   <resource>
     <uid>transporter</uid>
+    <guid>transporter</guid>
     <password>transporter</password>
     <name>Mass Transporter</name>
-    <cuaddr>mailto:transporter at example.com</cuaddr>
+    <email-address>transporter at example.com</email-address>
   </resource>
   <resource>
     <uid>ftlcpu</uid>
+    <guid>ftlcpu</guid>
     <password>ftlcpu</password>
     <name>Faster-Than-Light Microprocessor</name>
-    <cuaddr>mailto:ftlcpu at example.com</cuaddr>
+    <email-address>ftlcpu at example.com</email-address>
   </resource>
   <resource>
     <uid>non_calendar_proxy</uid>
     <guid>non_calendar_proxy</guid>
     <password>non_calendar_proxy</password>
     <name>Non-calendar proxy</name>
-    <cuaddr>mailto:non_calendar_proxy at example.com</cuaddr>
-    <proxies>
-      <member type="groups">non_calendar_group</member>
-    </proxies>
-    <read-only-proxies>
-      <member type="groups">recursive2_coasts</member>
-    </read-only-proxies>
+    <email-address>non_calendar_proxy at example.com</email-address>
   </resource>
 </accounts>

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/augments-test.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/augments-test.xml	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/augments-test.xml	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2009 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>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</guid>
+    <enable>true</enable>
+  </record>
+  <record>
+    <guid>6423F94A-6B76-4A3A-815B-D52CFD77935D</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <cuaddr>mailto:wsanchez at example.com</cuaddr>
+  </record>
+  <record>
+    <guid>5A985493-EE2C-4665-94CF-4DFEA3A89500</guid>
+    <enable>false</enable>
+  </record>
+  <record>
+    <guid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</guid>
+    <enable>true</enable>
+    <enable-calendar>false</enable-calendar>
+  </record>
+  <record>
+    <guid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
+    <enable>true</enable>
+    <hosted-at>00001</hosted-at>
+  </record>
+  <record>
+    <guid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</guid>
+    <enable>true</enable>
+    <hosted-at>00002</hosted-at>
+  </record>
+  <record>
+    <guid>6A73326A-F781-47E7-A9F8-AF47364D4152</guid>
+    <enable>true</enable>
+    <hosted-at>00002</hosted-at>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <auto-schedule>true</auto-schedule>
+    <cuaddr>mailto:usera at example.com</cuaddr>
+    <cuaddr>mailto:user.a at example.com</cuaddr>
+    <cuaddr>mailto:user_a at example.com</cuaddr>
+  </record>
+</augments>

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/augments.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/augments.xml	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/augments.xml	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2009 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 augments SYSTEM "../../../conf/augments.dtd">
+
+<augments realm="Test">
+  <record>
+    <guid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <guid>6423F94A-6B76-4A3A-815B-D52CFD77935D</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <guid>5A985493-EE2C-4665-94CF-4DFEA3A89500</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <guid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <guid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <guid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</guid>
+    <enable>true</enable>
+    <enable-calendar>false</enable-calendar>
+  </record>
+  <record repeat="2">
+    <guid>user%02d</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <guid>9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
+    <enable>true</enable>
+  </record>
+  <record>
+    <guid>admin</guid>
+    <enable>true</enable>
+  </record>
+  <record>
+    <guid>grunts</guid>
+    <enable>true</enable>
+  </record>
+  <record>
+    <guid>right_coast</guid>
+    <enable>true</enable>
+  </record>
+  <record>
+    <guid>left_coast</guid>
+    <enable>true</enable>
+  </record>
+  <record>
+    <guid>both_coasts</guid>
+    <enable>true</enable>
+  </record>
+  <record>
+    <guid>recursive1_coasts</guid>
+    <enable>true</enable>
+  </record>
+  <record>
+    <guid>recursive2_coasts</guid>
+    <enable>true</enable>
+  </record>
+  <record>
+    <guid>non_calendar_group</guid>
+    <enable>true</enable>
+  </record>
+  <record>
+    <guid>mercury</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <guid>gemini</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <auto-schedule>true</auto-schedule>
+  </record>
+  <record>
+    <guid>apollo</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <guid>orion</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <guid>transporter</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <guid>ftlcpu</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <guid>non_calendar_proxy</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+</augments>

Deleted: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/basic
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/basic	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/basic	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,4 +0,0 @@
-wsanchez:Cytm0Bwm7CPJs
-cdaboo:I.Ef5FJl5GVh2
-dreid:LVhqAv4qSrYPs
-lecroy:/7/5VDrkrLxY.

Deleted: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/digest
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/digest	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/digest	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,4 +0,0 @@
-wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c
-cdaboo:Test:61164bf3d607d072fe8a7ac420b24aac
-dreid:Test:8ee67801004b2752f72b84e7064889a6
-lecroy:Test:60d4feb424430953be045738041e51be

Deleted: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/groups
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/groups	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/groups	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,4 +0,0 @@
-managers: lecroy
-grunts: wsanchez, cdaboo, dreid
-right_coast: cdaboo
-left_coast: wsanchez, dreid, lecroy

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/proxies.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/proxies.xml	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/proxies.xml	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2009 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 proxies SYSTEM "proxies.dtd">
+
+<proxies>
+  <record>
+    <guid>mercury</guid>
+    <proxies>
+      <member>left_coast</member>
+    </proxies>
+  </record>
+  <record>
+    <guid>gemini</guid>
+    <proxies>
+      <member>6423F94A-6B76-4A3A-815B-D52CFD77935D</member>
+    </proxies>
+  </record>
+  <record>
+    <guid>apollo</guid>
+    <proxies>
+      <member>both_coasts</member>
+    </proxies>
+  </record>
+  <record>
+    <guid>orion</guid>
+    <proxies>
+      <member>recursive1_coasts</member>
+    </proxies>
+  </record>
+  <record>
+    <guid>non_calendar_proxy</guid>
+    <proxies>
+      <member>non_calendar_group</member>
+    </proxies>
+    <read-only-proxies>
+      <member>recursive2_coasts</member>
+    </read-only-proxies>
+  </record>
+</proxies>

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_aggregate.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_aggregate.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_aggregate.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2009 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.
@@ -14,20 +14,18 @@
 # limitations under the License.
 ##
 
-from twistedcaldav.directory.apache import BasicDirectoryService
 from twistedcaldav.directory.xmlfile import XMLDirectoryService
 from twistedcaldav.directory.aggregate import AggregateDirectoryService
 
-from twistedcaldav.directory.test.test_apache import digestRealm, basicUserFile, groupFile
-from twistedcaldav.directory.test.test_xmlfile import xmlFile
+from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
 
 import twistedcaldav.directory.test.util
+from twistedcaldav.directory import augment
 
 apache_prefix = "apache:"
 xml_prefix = "xml:"
 
 testServices = (
-    (apache_prefix, twistedcaldav.directory.test.test_apache.Apache  ),
     (xml_prefix   , twistedcaldav.directory.test.test_xmlfile.XMLFile),
 )
 
@@ -65,10 +63,9 @@
         """
         Returns an IDirectoryService.
         """
-        apacheService = BasicDirectoryService(digestRealm, basicUserFile, groupFile)
-        apacheService.recordTypePrefix = apache_prefix
-
-        xmlService = XMLDirectoryService(xmlFile)
+        xmlService = XMLDirectoryService(xmlFile=xmlFile)
         xmlService.recordTypePrefix = xml_prefix
 
-        return AggregateDirectoryService((apacheService, xmlService))
+        augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
+
+        return AggregateDirectoryService((xmlService,))

Deleted: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_apache.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_apache.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_apache.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,120 +0,0 @@
-##
-# Copyright (c) 2005-2007 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
-
-from twisted.python.filepath import FilePath
-
-import twistedcaldav.directory.test.util
-from twistedcaldav.directory.apache import BasicDirectoryService, DigestDirectoryService
-from twistedcaldav.directory.directory import DirectoryService
-
-digestRealm = "Test"
-
-basicUserFile  = FilePath(os.path.join(os.path.dirname(__file__), "basic"))
-digestUserFile = FilePath(os.path.join(os.path.dirname(__file__), "digest"))
-groupFile      = FilePath(os.path.join(os.path.dirname(__file__), "groups"))
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-class Apache (object):
-    recordTypes = set((
-        DirectoryService.recordType_users,
-        DirectoryService.recordType_groups
-    ))
-
-    users = {
-        "wsanchez": { "password": "foo",  "guid": None, "addresses": () },
-        "cdaboo"  : { "password": "bar",  "guid": None, "addresses": () },
-        "dreid"   : { "password": "baz",  "guid": None, "addresses": () },
-        "lecroy"  : { "password": "quux", "guid": None, "addresses": () },
-    }
-
-    groups = {
-        "managers"   : { "guid": None, "addresses": (), "members": ((DirectoryService.recordType_users, "lecroy"),)                                         },
-        "grunts"     : { "guid": None, "addresses": (), "members": ((DirectoryService.recordType_users, "wsanchez"),
-                                                                    (DirectoryService.recordType_users, "cdaboo"),
-                                                                    (DirectoryService.recordType_users, "dreid")) },
-        "right_coast": { "guid": None, "addresses": (), "members": ((DirectoryService.recordType_users, "cdaboo"),)                                         },
-        "left_coast" : { "guid": None, "addresses": (), "members": ((DirectoryService.recordType_users, "wsanchez"),
-                                                                    (DirectoryService.recordType_users, "dreid"),
-                                                                    (DirectoryService.recordType_users, "lecroy")) },
-    }
-
-    locations = {
-    }
-
-    resources = {
-    }
-
-    def service(self):
-        return self.serviceClass(digestRealm, self.userFile(), self.groupFile())
-
-    userFileName = None
-
-    def userFile(self):
-        if not hasattr(self, "_userFile"):
-            if self.userFileName is None:
-                raise NotImplementedError("Test subclass needs to specify userFileName.")
-            self._userFile = FilePath(self.mktemp())
-            basicUserFile.copyTo(self._userFile)
-        return self._userFile
-
-    def groupFile(self):
-        if not hasattr(self, "_groupFile"):
-            self._groupFile = FilePath(self.mktemp())
-            groupFile.copyTo(self._groupFile)
-        return self._groupFile
-
-    def test_changedGroupFile(self):
-        self.groupFile().open("w").write("grunts: wsanchez\n")
-        self.assertEquals(self.recordNames(DirectoryService.recordType_groups), set(("grunts",)))
-
-    def test_recordTypes_user(self):
-        """
-        IDirectoryService.recordTypes(userFile)
-        """
-        self.assertEquals(set(self.serviceClass(digestRealm, self.userFile()).recordTypes()), set((DirectoryService.recordType_users,)))
-
-    userEntry = None
-
-    def test_changedUserFile(self):
-        if self.userEntry is None:
-            raise NotImplementedError("Test subclass needs to specify userEntry.")
-        self.userFile().open("w").write(self.userEntry[1])
-        self.assertEquals(self.recordNames(DirectoryService.recordType_users), set((self.userEntry[0],)))
-
-class Basic (Apache, twistedcaldav.directory.test.util.BasicTestCase):
-    """
-    Test Apache-Compatible UserFile/GroupFile directory implementation.
-    """
-    serviceClass = BasicDirectoryService
-
-    userFileName = basicUserFile
-    userEntry = ("wsanchez", "wsanchez:Cytm0Bwm7CPJs\n")
-
-class Digest (Apache, twistedcaldav.directory.test.util.DigestTestCase):
-    """
-    Test Apache-Compatible DigestFile/GroupFile directory implementation.
-    """
-    serviceClass = DigestDirectoryService
-
-    userFileName = digestUserFile
-    userEntry = ("wsanchez", "wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c\n")
-
-    def test_verifyCredentials_digest(self):
-        raise NotImplementedError() # Use super's implementation
-    test_verifyCredentials_digest.todo = "unimplemented"

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_augment.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_augment.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_augment.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,108 @@
+##
+# Copyright (c) 2009 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.
+##
+
+from twistedcaldav.test.util import TestCase
+from twistedcaldav.directory.augment import AugmentXMLDB, AugmentSqliteDB
+from twisted.internet.defer import inlineCallbacks
+from twistedcaldav.directory.xmlaugmentsparser import XMLAugmentsParser
+import cStringIO
+import os
+
+xmlFile = os.path.join(os.path.dirname(__file__), "augments-test.xml")
+
+testRecords = (
+    {"guid":"D11F03A0-97EA-48AF-9A6C-FAC7F3975766", "enabled":True,  "hostedAt":"", "enabledForCalendaring":False, "autoSchedule":False, "calendarUserAddresses":set()},
+    {"guid":"6423F94A-6B76-4A3A-815B-D52CFD77935D", "enabled":True,  "hostedAt":"", "enabledForCalendaring":True, "autoSchedule":False, "calendarUserAddresses":set(("mailto:wsanchez at example.com",))},
+    {"guid":"5A985493-EE2C-4665-94CF-4DFEA3A89500", "enabled":False, "hostedAt":"", "enabledForCalendaring":False, "autoSchedule":False, "calendarUserAddresses":set()},
+    {"guid":"8B4288F6-CC82-491D-8EF9-642EF4F3E7D0", "enabled":True,  "hostedAt":"", "enabledForCalendaring":False, "autoSchedule":False, "calendarUserAddresses":set()},
+    {"guid":"5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1", "enabled":True,  "hostedAt":"00001", "enabledForCalendaring":False, "autoSchedule":False, "calendarUserAddresses":set()},
+    {"guid":"543D28BA-F74F-4D5F-9243-B3E3A61171E5", "enabled":True,  "hostedAt":"00002", "enabledForCalendaring":False, "autoSchedule":False, "calendarUserAddresses":set()},
+    {"guid":"6A73326A-F781-47E7-A9F8-AF47364D4152", "enabled":True,  "hostedAt":"00002", "enabledForCalendaring":True, "autoSchedule":True, "calendarUserAddresses":set(("mailto:usera at example.com", "mailto:user.a at example.com", "mailto:user_a at example.com",))},
+)
+
+class AugmentTests(TestCase):
+
+    @inlineCallbacks
+    def _checkRecord(self, db, items):
+        
+        record = (yield db.getAugmentRecord(items["guid"]))
+        self.assertTrue(record is not None)
+        
+        for k,v in items.iteritems():
+            self.assertEqual(getattr(record, k), v)
+
+    @inlineCallbacks
+    def _checkNoRecord(self, db, guid):
+        
+        record = (yield db.getAugmentRecord(guid))
+        self.assertTrue(record is None)
+
+class AugmentXMLTests(AugmentTests):
+
+    @inlineCallbacks
+    def test_read(self):
+        
+        db = AugmentXMLDB((xmlFile,))
+
+        for item in testRecords:
+            yield self._checkRecord(db, item)
+
+        yield self._checkNoRecord(db, "D11F03A0-97EA-48AF-9A6C-FAC7F3975767")
+
+    def test_parseErrors(self):
+        
+        db = {}
+        self.assertRaises(RuntimeError, XMLAugmentsParser, cStringIO.StringIO(""), db)
+        self.assertRaises(RuntimeError, XMLAugmentsParser, cStringIO.StringIO("""<?xml version="1.0" encoding="utf-8"?>
+<accounts>
+    <foo/>
+</accounts>
+"""), db)
+        self.assertRaises(RuntimeError, XMLAugmentsParser, cStringIO.StringIO("""<?xml version="1.0" encoding="utf-8"?>
+<augments>
+    <foo/>
+</augments>
+"""), db)
+        self.assertRaises(RuntimeError, XMLAugmentsParser, cStringIO.StringIO("""<?xml version="1.0" encoding="utf-8"?>
+<augments>
+  <record>
+    <enable>true</enable>
+  </record>
+</augments>
+"""), db)
+        self.assertRaises(RuntimeError, XMLAugmentsParser, cStringIO.StringIO("""<?xml version="1.0" encoding="utf-8"?>
+  <record>
+    <guid>admin</guid>
+    <enable>true</enable>
+    <foo/>
+  </record>
+"""), db)
+
+class AugmentSqliteTests(AugmentTests):
+
+    @inlineCallbacks
+    def test_read(self):
+        
+        db = AugmentSqliteDB(self.mktemp())
+
+        dbxml = AugmentXMLDB((xmlFile,))
+        for record in dbxml.db.values():
+            yield db.addAugmentRecord(record)
+
+        for item in testRecords:
+            yield self._checkRecord(db, item)
+
+        yield self._checkNoRecord(db, "D11F03A0-97EA-48AF-9A6C-FAC7F3975767")

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_calendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_calendar.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_calendar.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2009 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,9 +16,11 @@
 
 import os
 
+from twisted.internet.defer import inlineCallbacks
 from twisted.web2.dav import davxml
 from twisted.web2.test.test_server import SimpleRequest
 
+from twistedcaldav import caldavxml
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
 from twistedcaldav.directory.test.test_xmlfile import xmlFile
 from twistedcaldav.directory.xmlfile import XMLDirectoryService
@@ -34,11 +36,11 @@
         super(ProvisionedCalendars, self).setUp()
         
         # Setup the initial directory
-        self.xmlfile = self.mktemp()
-        fd = open(self.xmlfile, "w")
+        self.xmlFile = self.mktemp()
+        fd = open(self.xmlFile, "w")
         fd.write(open(xmlFile.path, "r").read())
         fd.close()
-        self.directoryService = XMLDirectoryService(self.xmlfile)
+        self.directoryService = XMLDirectoryService(xmlFile=self.xmlFile)
         
         # Set up a principals hierarchy for each service we're testing with
         name = "principals"
@@ -69,3 +71,33 @@
         request = SimpleRequest(self.site, "GET", "/calendars/users/12345/")
         d = request.locateResource(request.uri)
         d.addCallback(_response)
+
+    def test_ExistentCalendarHome(self):
+
+        def _response(resource):
+            if resource is None:
+                self.fail("Incorrect response to GET on existent calendar home.")
+
+        request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
+        d = request.locateResource(request.uri)
+        d.addCallback(_response)
+
+    def test_ExistentCalendar(self):
+
+        def _response(resource):
+            if resource is None:
+                self.fail("Incorrect response to GET on existent calendar.")
+
+        request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/calendar/")
+        d = request.locateResource(request.uri)
+        d.addCallback(_response)
+
+    def test_ExistentInbox(self):
+
+        def _response(resource):
+            if resource is None:
+                self.fail("Incorrect response to GET on existent inbox.")
+
+        request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/inbox/")
+        d = request.locateResource(request.uri)
+        d.addCallback(_response)

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_digest.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_digest.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_digest.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -2,12 +2,12 @@
 
 from twisted.cred import error
 from twisted.internet import address
-from twisted.trial import unittest
 from twisted.web2.auth import digest
 from twisted.web2.auth.wrapper import UnauthorizedResponse
 from twisted.web2.test.test_server import SimpleRequest
 from twisted.web2.dav.fileop import rmdir
 from twistedcaldav.directory.digest import QopDigestCredentialFactory
+from twistedcaldav.test.util import TestCase
 import os
 import md5
 
@@ -71,7 +71,7 @@
 emtpyAttributeAuthRequest = 'realm="",nonce="doesn\'t matter"'
 
 
-class DigestAuthTestCase(unittest.TestCase):
+class DigestAuthTestCase(TestCase):
     """
     Test the behavior of DigestCredentialFactory
     """
@@ -80,6 +80,7 @@
         """
         Create a DigestCredentialFactory for testing
         """
+        TestCase.setUp(self)
         self.path1 = self.mktemp()
         self.path2 = self.mktemp()
         os.mkdir(self.path1)

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_guidchange.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_guidchange.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_guidchange.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2009 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,11 +37,11 @@
         super(ProvisionedPrincipals, self).setUp()
         
         # Setup the initial directory
-        self.xmlfile = self.mktemp()
-        fd = open(self.xmlfile, "w")
+        self.xmlFile = self.mktemp()
+        fd = open(self.xmlFile, "w")
         fd.write(open(xmlFile.path, "r").read())
         fd.close()
-        self.directoryService = XMLDirectoryService(self.xmlfile)
+        self.directoryService = XMLDirectoryService(xmlFile=self.xmlFile)
         
         # Set up a principals hierarchy for each service we're testing with
         name = "principals"
@@ -78,7 +78,7 @@
         
         def privs1(result):
             # Change GUID in record
-            fd = open(self.xmlfile, "w")
+            fd = open(self.xmlFile, "w")
             fd.write(open(xmlFile.path, "r").read().replace(oldUID, newUID))
             fd.close()
             fd = None

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_opendirectory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_opendirectory.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_opendirectory.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2009 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.
@@ -21,8 +21,10 @@
 else:
     import twisted.web2.auth.digest
     import twistedcaldav.directory.test.util
+    from twistedcaldav.directory import augment
     from twistedcaldav.directory.directory import DirectoryService
     from twistedcaldav.directory.appleopendirectory import OpenDirectoryRecord
+    import dsattributes
 
     # Wonky hack to prevent unclean reactor shutdowns
     class DummyReactor(object):
@@ -51,6 +53,7 @@
         def setUp(self):
             super(OpenDirectory, self).setUp()
             self._service = OpenDirectoryService(node="/Search", dosetup=False)
+            augment.AugmentService = augment.AugmentXMLDB(xmlFiles=())
 
         def tearDown(self):
             for call in self._service._delayedCalls:
@@ -67,12 +70,8 @@
                 nodeName              = "/LDAPv2/127.0.0.1",
                 shortName             = "user",
                 fullName              = "Some user",
-                calendarUserAddresses = set(("mailtoguid at example.com",)),
-                autoSchedule          = False,
-                enabledForCalendaring = True,
+                emailAddresses        = set(("someuser at example.com",)),
                 memberGUIDs           = [],
-                proxyGUIDs            = (),
-                readOnlyProxyGUIDs    = (),
             )
 
             digestFields = {}

Deleted: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_opendirectoryrecords.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_opendirectoryrecords.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_opendirectoryrecords.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,483 +0,0 @@
-##
-# Copyright (c) 2005-2007 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 twisted.trial.unittest
-
-try:
-    from twistedcaldav.directory.appleopendirectory import OpenDirectoryService as RealOpenDirectoryService
-    import dsattributes
-except ImportError:
-    pass
-else:
-    from twistedcaldav.directory.directory import DirectoryService
-    from twistedcaldav.directory.util import uuidFromName
-
-    class OpenDirectoryService (RealOpenDirectoryService):
-        def _queryDirectory(self, recordType, shortName=None, guid=None):
-            if shortName is None and guid is None:
-                return self.fakerecords[recordType]
-
-            assert shortName is None or guid is None
-            if guid is not None:
-                guid = guid.lower()
-
-            records = []
-
-            for name, record in self.fakerecords[recordType]:
-                if name == shortName or record[dsattributes.kDS1AttrGeneratedUID] == guid:
-                    records.append((name, record))
-
-            return tuple(records)
-    
-    class ReloadCache(twisted.trial.unittest.TestCase):
-        def setUp(self):
-            super(ReloadCache, self).setUp()
-            self._service = OpenDirectoryService(node="/Search", dosetup=False)
-            self._service.servicetags.add("FE588D50-0514-4DF9-BCB5-8ECA5F3DA274:030572AE-ABEC-4E0F-83C9-FCA304769E5F:calendar")
-            
-        def tearDown(self):
-            for call in self._service._delayedCalls:
-                call.cancel()
-
-        def _verifyRecords(self, recordType, expected):
-            expected = set(expected)
-            found = set(self._service._records[recordType]["records"].keys())
-            
-            missing = expected.difference(found)
-            extras = found.difference(expected)
-
-            self.assertTrue(len(missing) == 0, msg="Directory records not found: %s" % (missing,))
-            self.assertTrue(len(extras) == 0, msg="Directory records not expected: %s" % (extras,))
-                
-        def _verifyRecordsCheckEnabled(self, recordType, expected, enabled):
-            expected = set(expected)
-            found = set([item for item in self._service._records[recordType]["records"].iterkeys()
-                         if self._service._records[recordType]["records"][item].enabledForCalendaring == enabled])
-            
-            missing = expected.difference(found)
-            extras = found.difference(expected)
-
-            self.assertTrue(len(missing) == 0, msg="Directory records not found: %s" % (missing,))
-            self.assertTrue(len(extras) == 0, msg="Directory records not expected: %s" % (extras,))
-                
-        def _verifyDisabledRecords(self, recordType, expectedNames, expectedGUIDs):
-            def check(disabledType, expected):
-                expected = set(expected)
-                found = self._service._records[recordType][disabledType]
-            
-                missing = expected.difference(found)
-                extras = found.difference(expected)
-
-                self.assertTrue(len(missing) == 0, msg="Disabled directory records not found: %s" % (missing,))
-                self.assertTrue(len(extras) == 0, msg="Disabled directory records not expected: %s" % (extras,))
-
-            check("disabled names", expectedNames)
-            check("disabled guids", (guid.lower() for guid in expectedGUIDs))
-
-        def test_normal(self):
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 01"),
-                    fakeODRecord("User 02"),
-                ],
-                DirectoryService.recordType_groups: [
-                    fakeODRecord("Group 01"),
-                    fakeODRecord("Group 02"),
-                ],
-                DirectoryService.recordType_resources: [
-                    fakeODRecord("Resource 01"),
-                    fakeODRecord("Resource 02"),
-                ],
-                DirectoryService.recordType_locations: [
-                    fakeODRecord("Location 01"),
-                    fakeODRecord("Location 02"),
-                ],
-            }
-
-            self._service.reloadCache(DirectoryService.recordType_users)
-            self._service.reloadCache(DirectoryService.recordType_groups)
-            self._service.reloadCache(DirectoryService.recordType_resources)
-            self._service.reloadCache(DirectoryService.recordType_locations)
-
-            self._verifyRecords(DirectoryService.recordType_users, ("user01", "user02"))
-            self._verifyDisabledRecords(DirectoryService.recordType_users, (), ())
-
-            self._verifyRecords(DirectoryService.recordType_groups, ("group01", "group02"))
-            self._verifyDisabledRecords(DirectoryService.recordType_groups, (), ())
-
-            self._verifyRecords(DirectoryService.recordType_resources, ("resource01", "resource02"))
-            self._verifyDisabledRecords(DirectoryService.recordType_resources, (), ())
-
-            self._verifyRecords(DirectoryService.recordType_locations, ("location01", "location02"))
-            self._verifyDisabledRecords(DirectoryService.recordType_locations, (), ())
-
-        def test_normal_disabledusers(self):
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 01"),
-                    fakeODRecord("User 02"),
-                    fakeODRecord("User 03", addLocator=False),
-                    fakeODRecord("User 04", addLocator=False),
-                ],
-                DirectoryService.recordType_groups: [
-                    fakeODRecord("Group 01"),
-                    fakeODRecord("Group 02"),
-                    fakeODRecord("Group 03", addLocator=False),
-                    fakeODRecord("Group 04", addLocator=False),
-                ],
-                DirectoryService.recordType_resources: [
-                    fakeODRecord("Resource 01"),
-                    fakeODRecord("Resource 02"),
-                    fakeODRecord("Resource 03", addLocator=False),
-                    fakeODRecord("Resource 04", addLocator=False),
-                ],
-                DirectoryService.recordType_locations: [
-                    fakeODRecord("Location 01"),
-                    fakeODRecord("Location 02"),
-                    fakeODRecord("Location 03", addLocator=False),
-                    fakeODRecord("Location 04", addLocator=False),
-                ],
-            }
-
-            self._service.reloadCache(DirectoryService.recordType_users)
-            self._service.reloadCache(DirectoryService.recordType_groups)
-            self._service.reloadCache(DirectoryService.recordType_resources)
-            self._service.reloadCache(DirectoryService.recordType_locations)
-
-            self._verifyRecordsCheckEnabled(DirectoryService.recordType_users, ("user01", "user02"), True)
-            self._verifyRecordsCheckEnabled(DirectoryService.recordType_users, ("user03", "user04"), False)
-
-            self._verifyRecordsCheckEnabled(DirectoryService.recordType_groups, ("group01", "group02"), True)
-            self._verifyRecordsCheckEnabled(DirectoryService.recordType_groups, ("group03", "group04"), False)
-
-            self._verifyRecordsCheckEnabled(DirectoryService.recordType_resources, ("resource01", "resource02"), True)
-            self._verifyRecordsCheckEnabled(DirectoryService.recordType_resources, (), False)
-
-            self._verifyRecordsCheckEnabled(DirectoryService.recordType_locations, ("location01", "location02"), True)
-            self._verifyRecordsCheckEnabled(DirectoryService.recordType_locations, (), False)
-
-        def test_normalCacheMiss(self):
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 01"),
-                ],
-            }
-
-            self._service.reloadCache(DirectoryService.recordType_users)
-
-            self._verifyRecords(DirectoryService.recordType_users, ("user01",))
-            self._verifyDisabledRecords(DirectoryService.recordType_users, (), ())
-
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 01"),
-                    fakeODRecord("User 02"),
-                    fakeODRecord("User 03", guid="D10F3EE0-5014-41D3-8488-3819D3EF3B2A"),
-                ],
-            }
-
-            self._service.reloadCache(DirectoryService.recordType_users, shortName="user02")
-            self._service.reloadCache(DirectoryService.recordType_users, guid="D10F3EE0-5014-41D3-8488-3819D3EF3B2A")
-
-            self._verifyRecords(DirectoryService.recordType_users, ("user01", "user02", "user03"))
-            self._verifyDisabledRecords(DirectoryService.recordType_users, (), ())
-
-        def test_duplicateRecords(self):
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 01"),
-                    fakeODRecord("User 02"),
-                    fakeODRecord("User 02"),
-                ],
-            }
-
-            self._service.reloadCache(DirectoryService.recordType_users)
-
-            self._verifyRecords(DirectoryService.recordType_users, ("user01", "user02"))
-            self._verifyDisabledRecords(DirectoryService.recordType_users, (), ())
-            self._verifyDisabledRecords(DirectoryService.recordType_users, (), ())
-
-
-        def test_duplicateName(self):
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 01"),
-                    fakeODRecord("User 02", guid="A25775BB-1281-4606-98C6-2893B2D5CCD7"),
-                    fakeODRecord("User 02", guid="30CA2BB9-C935-4A5D-80E2-79266BCB0255"),
-                ],
-            }
-
-            self._service.reloadCache(DirectoryService.recordType_users)
-
-            self._verifyRecords(DirectoryService.recordType_users, ("user01",))
-            self._verifyDisabledRecords(
-                DirectoryService.recordType_users,
-                ("user02",),
-                ("A25775BB-1281-4606-98C6-2893B2D5CCD7", "30CA2BB9-C935-4A5D-80E2-79266BCB0255"),
-            )
-
-        def test_duplicateGUID(self):
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 01"),
-                    fakeODRecord("User 02", guid="113D7F74-F84A-4F17-8C96-CE8F10D68EF8"),
-                    fakeODRecord("User 03", guid="113D7F74-F84A-4F17-8C96-CE8F10D68EF8"),
-                ],
-            }
-
-            self._service.reloadCache(DirectoryService.recordType_users)
-
-            self._verifyRecords(DirectoryService.recordType_users, ("user01",))
-            self._verifyDisabledRecords(
-                DirectoryService.recordType_users,
-                ("user02", "user03"),
-                ("113D7F74-F84A-4F17-8C96-CE8F10D68EF8",),
-            )
-
-        def test_duplicateCombo(self):
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 01"),
-                    fakeODRecord("User 02", guid="113D7F74-F84A-4F17-8C96-CE8F10D68EF8"),
-                    fakeODRecord("User 02", guid="113D7F74-F84A-4F17-8C96-CE8F10D68EF8", shortName="user03"),
-                    fakeODRecord("User 02", guid="136E369F-DB40-4135-878D-B75D38242D39"),
-                ],
-            }
-
-            self._service.reloadCache(DirectoryService.recordType_users)
-
-            self._verifyRecords(DirectoryService.recordType_users, ("user01",))
-            self._verifyDisabledRecords(
-                DirectoryService.recordType_users,
-                ("user02", "user03"),
-                ("113D7F74-F84A-4F17-8C96-CE8F10D68EF8", "136E369F-DB40-4135-878D-B75D38242D39"),
-            )
-
-        def test_duplicateGUIDCacheMiss(self):
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 01"),
-                    fakeODRecord("User 02", guid="EDB9EE55-31F2-4EA9-B5FB-D8AE2A8BA35E"),
-                    fakeODRecord("User 03", guid="D10F3EE0-5014-41D3-8488-3819D3EF3B2A"),
-                ],
-            }
-
-            self._service.reloadCache(DirectoryService.recordType_users)
-
-            self._verifyRecords(DirectoryService.recordType_users, ("user01", "user02", "user03"))
-            self._verifyDisabledRecords(DirectoryService.recordType_users, (), ())
-            
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 01"),
-                    fakeODRecord("User 02", guid="EDB9EE55-31F2-4EA9-B5FB-D8AE2A8BA35E"),
-                    fakeODRecord("User 02", guid="EDB9EE55-31F2-4EA9-B5FB-D8AE2A8BA35E", shortName="user04"),
-                    fakeODRecord("User 03", guid="62368DDF-0C62-4C97-9A58-DE9FD46131A0"),
-                    fakeODRecord("User 03", guid="62368DDF-0C62-4C97-9A58-DE9FD46131A0", shortName="user05"),
-                ],
-            }
-
-            self._service.reloadCache(DirectoryService.recordType_users, shortName="user04")
-            self._service.reloadCache(DirectoryService.recordType_users, guid="62368DDF-0C62-4C97-9A58-DE9FD46131A0")
-
-            self._verifyRecords(DirectoryService.recordType_users, ("user01",))
-            self._verifyDisabledRecords(
-                DirectoryService.recordType_users,
-                ("user02", "user03", "user04", "user05"),
-                ("EDB9EE55-31F2-4EA9-B5FB-D8AE2A8BA35E", "62368DDF-0C62-4C97-9A58-DE9FD46131A0", "D10F3EE0-5014-41D3-8488-3819D3EF3B2A"),
-            )
-
-        def test_groupmembers(self):
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 01"),
-                    fakeODRecord("User 02"),
-                ],
-                DirectoryService.recordType_groups: [
-                    fakeODRecord("Group 01", members=[
-                        guidForShortName("user01"),
-                        guidForShortName("user02"),
-                    ]),
-                    fakeODRecord("Group 02", members=[
-                        guidForShortName("resource01"),
-                        guidForShortName("user02"),
-                    ]),
-                ],
-                DirectoryService.recordType_resources: [
-                    fakeODRecord("Resource 01"),
-                    fakeODRecord("Resource 02"),
-                ],
-                DirectoryService.recordType_locations: [
-                    fakeODRecord("Location 01"),
-                    fakeODRecord("Location 02"),
-                ],
-            }
-
-            self._service.reloadCache(DirectoryService.recordType_users)
-            self._service.reloadCache(DirectoryService.recordType_groups)
-            self._service.reloadCache(DirectoryService.recordType_resources)
-            self._service.reloadCache(DirectoryService.recordType_locations)
-
-            group1 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group01")
-            self.assertTrue(group1 is not None)
-
-            group2 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group02")
-            self.assertTrue(group2 is not None)
-
-            user1 = self._service.recordWithShortName(DirectoryService.recordType_users, "user01")
-            self.assertTrue(user1 is not None)
-            self.assertEqual(set((group1,)), user1.groups()) 
-            
-            user2 = self._service.recordWithShortName(DirectoryService.recordType_users, "user02")
-            self.assertTrue(user2 is not None)
-            self.assertEqual(set((group1, group2)), user2.groups()) 
-            
-            self._service.fakerecords[DirectoryService.recordType_groups] = [
-                fakeODRecord("Group 01", members=[
-                    guidForShortName("user01"),
-                ]),
-                fakeODRecord("Group 02", members=[
-                    guidForShortName("resource01"),
-                    guidForShortName("user02"),
-                ]),
-            ]
-            self._service.reloadCache(DirectoryService.recordType_groups)
-
-            group1 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group01")
-            self.assertTrue(group1 is not None)
-
-            group2 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group02")
-            self.assertTrue(group2 is not None)
-
-            user1 = self._service.recordWithShortName(DirectoryService.recordType_users, "user01")
-            self.assertTrue(user1 is not None)
-            self.assertEqual(set((group1,)), user1.groups()) 
-            
-            user2 = self._service.recordWithShortName(DirectoryService.recordType_users, "user02")
-            self.assertTrue(user2 is not None)
-            self.assertEqual(set((group2,)), user2.groups()) 
-            
-            self._service.fakerecords[DirectoryService.recordType_groups] = [
-                fakeODRecord("Group 03", members=[
-                    guidForShortName("user01"),
-                    guidForShortName("user02"),
-                ]),
-            ]
-            self._service.reloadCache(DirectoryService.recordType_groups, guid=guidForShortName("group03"))
-
-            group1 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group01")
-            self.assertTrue(group1 is not None)
-
-            group2 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group02")
-            self.assertTrue(group2 is not None)
-
-            group3 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group03")
-            self.assertTrue(group2 is not None)
-
-            user1 = self._service.recordWithShortName(DirectoryService.recordType_users, "user01")
-            self.assertTrue(user1 is not None)
-            self.assertEqual(set((group1, group3)), user1.groups()) 
-            
-            user2 = self._service.recordWithShortName(DirectoryService.recordType_users, "user02")
-            self.assertTrue(user2 is not None)
-            self.assertEqual(set((group2, group3)), user2.groups()) 
-
-        def test_calendaruseraddress(self):
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 01"),
-                    fakeODRecord("User 02"),
-                ],
-                DirectoryService.recordType_groups: [],
-                DirectoryService.recordType_resources: [],
-                DirectoryService.recordType_locations: [],
-            }
-
-            self._service.reloadCache(DirectoryService.recordType_users)
-
-            user1 = self._service.recordWithCalendarUserAddress("mailto:user01 at example.com")
-            self.assertTrue(user1 is not None)
-
-            user3 = self._service.recordWithCalendarUserAddress("mailto:user03 at example.com")
-            self.assertTrue(user3 is None)
-
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 01"),
-                    fakeODRecord("User 02"),
-                    fakeODRecord("User 03"),
-                ],
-                DirectoryService.recordType_groups: [],
-                DirectoryService.recordType_resources: [],
-                DirectoryService.recordType_locations: [],
-            }
-            self._service.reloadCache(DirectoryService.recordType_users)
-
-            user1 = self._service.recordWithCalendarUserAddress("mailto:user01 at example.com")
-            self.assertTrue(user1 is not None)
-
-            user3 = self._service.recordWithCalendarUserAddress("mailto:user03 at example.com")
-            self.assertTrue(user3 is not None)
-
-            self._service.fakerecords = {
-                DirectoryService.recordType_users: [
-                    fakeODRecord("User 02"),
-                    fakeODRecord("User 03"),
-                ],
-                DirectoryService.recordType_groups: [],
-                DirectoryService.recordType_resources: [],
-                DirectoryService.recordType_locations: [],
-            }
-            self._service.reloadCache(DirectoryService.recordType_users)
-
-            user1 = self._service.recordWithCalendarUserAddress("mailto:user01 at example.com")
-            self.assertTrue(user1 is None)
-
-            user3 = self._service.recordWithCalendarUserAddress("mailto:user03 at example.com")
-            self.assertTrue(user3 is not None)
-
-def fakeODRecord(fullName, shortName=None, guid=None, email=None, addLocator=True, members=None):
-    if shortName is None:
-        shortName = shortNameForFullName(fullName)
-
-    if guid is None:
-        guid = guidForShortName(shortName)
-    else:
-        guid = guid.lower()
-
-    if email is None:
-        email = "%s at example.com" % (shortName,)
-
-    attrs = {
-        dsattributes.kDS1AttrDistinguishedName: fullName,
-        dsattributes.kDS1AttrGeneratedUID: guid,
-        dsattributes.kDSNAttrEMailAddress: email,
-        dsattributes.kDSNAttrMetaNodeLocation: "/LDAPv3/127.0.0.1",
-    }
-    
-    if members:
-        attrs[dsattributes.kDSNAttrGroupMembers] = members
-
-    if addLocator:
-        attrs[dsattributes.kDSNAttrServicesLocator] = "FE588D50-0514-4DF9-BCB5-8ECA5F3DA274:030572AE-ABEC-4E0F-83C9-FCA304769E5F:calendar"
-
-    return [ shortName, attrs ]
-
-def shortNameForFullName(fullName):
-    return fullName.lower().replace(" ", "")
-
-def guidForShortName(shortName):
-    return uuidFromName(OpenDirectoryService.baseGUID, shortName)

Deleted: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_opendirectoryschema.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_opendirectoryschema.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_opendirectoryschema.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,1298 +0,0 @@
-##
-# Copyright (c) 2005-2007 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.
-##
-
-try:
-    from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
-    from twistedcaldav.directory.appleopendirectory import OpenDirectoryInitError
-    import dsattributes
-except ImportError:
-    pass
-else:
-    from twistedcaldav.directory.directory import DirectoryService
-    import twisted.trial.unittest
-
-    class PlistParse (twisted.trial.unittest.TestCase):
-        """
-        Test Open Directory service schema.
-        """
-
-        plist_nomacosxserver_key = """<?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>ReplicaName</key>
-        <string>Master</string>
-
-        <key>com.apple.od.role</key>
-        <string>master</string>
-    </dict>
-</plist>
-"""
-
-        plist_nocalendarservice = """<?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>ReplicaName</key>
-        <string>Master</string>
-
-        <key>com.apple.od.role</key>
-        <string>master</string>
-
-        <key>com.apple.macosxserver.virtualhosts</key>
-        <dict>
-            <key>4F088107-51FD-4DE5-904D-2C0AD9C6C893</key>
-            <dict>
-                <key>hostname</key>
-                <string>foo.apple.com</string>
-
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>80</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>443</integer>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>wiki</string>
-                    <string>webCalendar</string>
-                    <string>webMailingList</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>webCalendar</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/webcalendar</string>
-                    </dict>
-                    <key>wiki</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/wiki</string>
-                    </dict>
-                    <key>webMailingList</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/mailinglist</string>
-                    </dict>
-                </dict>
-            </dict>
-
-        </dict>
-    </dict>
-</plist>
-"""
-
-        plist_noserviceinfo = """<?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>ReplicaName</key>
-        <string>Master</string>
-
-        <key>com.apple.od.role</key>
-        <string>master</string>
-
-        <key>com.apple.macosxserver.virtualhosts</key>
-        <dict>
-            <key>4F088107-51FD-4DE5-904D-2C0AD9C6C893</key>
-            <dict>
-                <key>hostname</key>
-                <string>foo.apple.com</string>
-
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>80</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>443</integer>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>wiki</string>
-                    <string>webCalendar</string>
-                    <string>webMailingList</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>webCalendar</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/webcalendar</string>
-                    </dict>
-                    <key>wiki</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/wiki</string>
-                    </dict>
-                    <key>webMailingList</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/mailinglist</string>
-                    </dict>
-                </dict>
-            </dict>
-
-            <key>C18C34AC-3D9E-403C-8A33-BFC303F3840E</key>
-            <dict>
-                <key>hostname</key>
-                <string>calendar.apple.com</string>
-
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>8008</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>8443</integer>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>calendar</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>webCalendar</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/webcalendar</string>
-                    </dict>
-                </dict>
-            </dict>
-
-        </dict>
-    </dict>
-</plist>
-"""
-
-        plist_disabledservice = """<?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>ReplicaName</key>
-        <string>Master</string>
-
-        <key>com.apple.od.role</key>
-        <string>master</string>
-
-        <key>com.apple.macosxserver.virtualhosts</key>
-        <dict>
-            <key>4F088107-51FD-4DE5-904D-2C0AD9C6C893</key>
-            <dict>
-                <key>hostname</key>
-                <string>foo.apple.com</string>
-
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>80</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <string>443</string>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>wiki</string>
-                    <string>webCalendar</string>
-                    <string>webMailingList</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>webCalendar</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/webcalendar</string>
-                    </dict>
-                    <key>wiki</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/wiki</string>
-                    </dict>
-                    <key>webMailingList</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/mailinglist</string>
-                    </dict>
-                </dict>
-            </dict>
-
-            <key>C18C34AC-3D9E-403C-8A33-BFC303F3840E</key>
-            <dict>
-                <key>hostname</key>
-                <string>calendar.apple.com</string>
-
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>8008</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>8443</integer>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>calendar</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>calendar</key>
-                    <dict>
-                        <key>enabled</key>
-                        <false/>
-                        <key>templates</key>
-                        <dict>
-                            <key>principalPath</key>
-                            <string>/principals/%(type)s/%(name)s</string>
-                            <key>calendarUserAddresses</key>
-                            <array>
-                                <string>%(scheme)s://%(hostname)s:%(port)s/principals/%(type)s/%(name)s</string>
-                                <string>mailto:%(email)s</string>
-                                <string>urn:uuid:%(guid)s</string>
-                            </array>
-                        </dict>
-                    </dict>
-                </dict>
-            </dict>
-
-        </dict>
-    </dict>
-</plist>
-"""
-
-        plist_nohostname = """<?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>ReplicaName</key>
-        <string>Master</string>
-
-        <key>com.apple.od.role</key>
-        <string>master</string>
-
-        <key>com.apple.macosxserver.virtualhosts</key>
-        <dict>
-            <key>4F088107-51FD-4DE5-904D-2C0AD9C6C893</key>
-            <dict>
-                <key>hostname</key>
-                <string>foo.apple.com</string>
-
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>80</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <string>443</string>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>wiki</string>
-                    <string>webCalendar</string>
-                    <string>webMailingList</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>webCalendar</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/webcalendar</string>
-                    </dict>
-                    <key>wiki</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/wiki</string>
-                    </dict>
-                    <key>webMailingList</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/mailinglist</string>
-                    </dict>
-                </dict>
-            </dict>
-
-            <key>C18C34AC-3D9E-403C-8A33-BFC303F3840E</key>
-            <dict>
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>8008</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>8443</integer>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>calendar</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>calendar</key>
-                    <dict>
-                        <key>templates</key>
-                        <dict>
-                            <key>principalPath</key>
-                            <string>/principals/%(type)s/%(name)s</string>
-                            <key>calendarUserAddresses</key>
-                            <array>
-                                <string>%(scheme)s://%(hostname)s:%(port)s/principals/%(type)s/%(name)s</string>
-                                <string>mailto:%(email)s</string>
-                                <string>urn:uuid:%(guid)s</string>
-                            </array>
-                        </dict>
-                    </dict>
-                </dict>
-            </dict>
-
-        </dict>
-    </dict>
-</plist>
-"""
-
-        plist_nohostdetails = """<?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>ReplicaName</key>
-        <string>Master</string>
-
-        <key>com.apple.od.role</key>
-        <string>master</string>
-
-        <key>com.apple.macosxserver.virtualhosts</key>
-        <dict>
-            <key>4F088107-51FD-4DE5-904D-2C0AD9C6C893</key>
-            <dict>
-                <key>hostname</key>
-                <string>foo.apple.com</string>
-
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>80</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <string>443</string>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>wiki</string>
-                    <string>webCalendar</string>
-                    <string>webMailingList</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>webCalendar</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/webcalendar</string>
-                    </dict>
-                    <key>wiki</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/wiki</string>
-                    </dict>
-                    <key>webMailingList</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/mailinglist</string>
-                    </dict>
-                </dict>
-            </dict>
-
-            <key>C18C34AC-3D9E-403C-8A33-BFC303F3840E</key>
-            <dict>
-                <key>hostname</key>
-                <string>calendar.apple.com</string>
-
-                <key>serviceType</key>
-                <array>
-                    <string>calendar</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>calendar</key>
-                    <dict>
-                        <key>templates</key>
-                        <dict>
-                            <key>principalPath</key>
-                            <string>/principals/%(type)s/%(name)s</string>
-                            <key>calendarUserAddresses</key>
-                            <array>
-                                <string>%(scheme)s://%(hostname)s:%(port)s/principals/%(type)s/%(name)s</string>
-                                <string>mailto:%(email)s</string>
-                                <string>urn:uuid:%(guid)s</string>
-                            </array>
-                        </dict>
-                    </dict>
-                </dict>
-            </dict>
-
-        </dict>
-    </dict>
-</plist>
-"""
-
-        plist_good = """<?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>ReplicaName</key>
-        <string>Master</string>
-
-        <key>com.apple.od.role</key>
-        <string>master</string>
-
-        <key>com.apple.macosxserver.virtualhosts</key>
-        <dict>
-            <key>4F088107-51FD-4DE5-904D-2C0AD9C6C893</key>
-            <dict>
-                <key>hostname</key>
-                <string>foo.apple.com</string>
-
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>80</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <string>443</string>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>wiki</string>
-                    <string>webCalendar</string>
-                    <string>webMailingList</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>webCalendar</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/webcalendar</string>
-                    </dict>
-                    <key>wiki</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/wiki</string>
-                    </dict>
-                    <key>webMailingList</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/mailinglist</string>
-                    </dict>
-                </dict>
-            </dict>
-
-            <key>C18C34AC-3D9E-403C-8A33-BFC303F3840E</key>
-            <dict>
-                <key>hostname</key>
-                <string>calendar.apple.com</string>
-
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>8008</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>8443</integer>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>calendar</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>calendar</key>
-                    <dict>
-                        <key>templates</key>
-                        <dict>
-                            <key>principalPath</key>
-                            <string>/principals/%(type)s/%(name)s</string>
-                            <key>calendarUserAddresses</key>
-                            <array>
-                                <string>%(scheme)s://%(hostname)s:%(port)s/principals/%(type)s/%(name)s</string>
-                                <string>mailto:%(email)s</string>
-                                <string>urn:uuid:%(guid)s</string>
-                            </array>
-                        </dict>
-                    </dict>
-                </dict>
-            </dict>
-
-        </dict>
-    </dict>
-</plist>
-"""
-
-        plist_good_other = """<?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>ReplicaName</key>
-        <string>Master</string>
-
-        <key>com.apple.od.role</key>
-        <string>master</string>
-
-        <key>com.apple.macosxserver.virtualhosts</key>
-        <dict>
-            <key>4F088107-51FD-4DE5-904D-2C0AD9C6C893</key>
-            <dict>
-                <key>hostname</key>
-                <string>foo.apple.com</string>
-
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>80</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <string>443</string>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>wiki</string>
-                    <string>webCalendar</string>
-                    <string>webMailingList</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>webCalendar</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/webcalendar</string>
-                    </dict>
-                    <key>wiki</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/wiki</string>
-                    </dict>
-                    <key>webMailingList</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/mailinglist</string>
-                    </dict>
-                </dict>
-            </dict>
-
-            <key>C18C34AC-3D9E-403C-8A33-BFC303F3840E</key>
-            <dict>
-                <key>hostname</key>
-                <string>privatecalendar.apple.com</string>
-
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>8008</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>8443</integer>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>calendar</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>calendar</key>
-                    <dict>
-                        <key>templates</key>
-                        <dict>
-                            <key>principalPath</key>
-                            <string>/principals/%(type)s/%(name)s</string>
-                            <key>calendarUserAddresses</key>
-                            <array>
-                                <string>%(scheme)s://%(hostname)s:%(port)s/principals/%(type)s/%(name)s</string>
-                                <string>mailto:%(email)s</string>
-                                <string>urn:uuid:%(guid)s</string>
-                            </array>
-                        </dict>
-                    </dict>
-                </dict>
-            </dict>
-
-        </dict>
-    </dict>
-</plist>
-"""
-
-        plist_duplicate = """<?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>ReplicaName</key>
-        <string>Master</string>
-
-        <key>com.apple.od.role</key>
-        <string>master</string>
-
-        <key>com.apple.macosxserver.virtualhosts</key>
-        <dict>
-            <key>F4088107-51FD-4DE5-904D-C20AD9C6C893</key>
-            <dict>
-                <key>hostname</key>
-                <string>foo.apple.com</string>
-
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>80</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <string>443</string>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>wiki</string>
-                    <string>webCalendar</string>
-                    <string>webMailingList</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>webCalendar</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/webcalendar</string>
-                    </dict>
-                    <key>wiki</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/wiki</string>
-                    </dict>
-                    <key>webMailingList</key>
-                    <dict>
-                        <key>enabled</key>
-                        <true/>
-                        <key>urlMask</key>
-                        <string>%(scheme)s://%(hostname)s:%(port)s/groups/%(name)s/mailinglist</string>
-                    </dict>
-                </dict>
-            </dict>
-
-            <key>1C8C34AC-3D9E-403C-8A33-FBC303F3840E</key>
-            <dict>
-                <key>hostname</key>
-                <string>calendar.apple.com</string>
-
-                <key>hostDetails</key>
-                <dict>
-                    <key>access</key>
-                    <dict>
-                        <key>somethingorother</key>
-                        <string>somethingelse</string>
-                    </dict>
-                    <key>http</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>8008</integer>
-                    </dict>
-                    <key>https</key>
-                    <dict>
-                        <key>port</key>
-                        <integer>8443</integer>
-                    </dict>
-                </dict>
-
-                <key>serviceType</key>
-                <array>
-                    <string>calendar</string>
-                </array>
-
-                <key>serviceInfo</key>
-                <dict>
-                    <key>calendar</key>
-                    <dict>
-                        <key>templates</key>
-                        <dict>
-                            <key>principalPath</key>
-                            <string>/principals/%(type)s/%(name)s</string>
-                            <key>calendarUserAddresses</key>
-                            <array>
-                                <string>%(scheme)s://%(hostname)s:%(port)s/principals/%(type)s/%(name)s</string>
-                                <string>mailto:%(email)s</string>
-                                <string>urn:uuid:%(guid)s</string>
-                            </array>
-                        </dict>
-                    </dict>
-                </dict>
-            </dict>
-
-        </dict>
-    </dict>
-</plist>
-"""
-
-        def test_plist_errors(self):
-            def _doParse(plist, title):
-                service = OpenDirectoryService(node="/Search", dosetup=False)
-                if service._parseServiceInfo("calendar.apple.com", "recordit", {
-                'dsAttrTypeNative:apple-serviceinfo'  : plist,
-                dsattributes.kDS1AttrGeneratedUID:      "GUIDIFY",
-                dsattributes.kDSNAttrMetaNodeLocation:  "/LDAPv3/127.0.0.1"}) and service.servicetags:
-                    self.fail(msg="Plist parse should have failed: %s" % (title,))
-
-            plists = (
-                (PlistParse.plist_nomacosxserver_key, "nomacosxserver_key"),
-                (PlistParse.plist_nocalendarservice,  "nocalendarservice"),
-                (PlistParse.plist_noserviceinfo,      "noserviceinfo"),
-                (PlistParse.plist_disabledservice,    "disabledservice"),
-                (PlistParse.plist_nohostname,         "nohostname"),
-                (PlistParse.plist_nohostdetails,      "nohostdetails"),
-            )
-            for plist, title in plists:
-                _doParse(plist, title)
-
-        def test_goodplist(self):
-            service = OpenDirectoryService(node="/Search", dosetup=False)
-            if not service._parseServiceInfo("calendar.apple.com", "recordit", {
-                'dsAttrTypeNative:apple-serviceinfo'  : PlistParse.plist_good,
-                dsattributes.kDS1AttrGeneratedUID:      "GUIDIFY",
-                dsattributes.kDSNAttrMetaNodeLocation:  "/LDAPv3/127.0.0.1"}):
-                self.fail(msg="Plist parse should not have failed")
-            else:
-                # Verify that we extracted the proper items
-                self.assertEqual(service.servicetags.pop(), "GUIDIFY:C18C34AC-3D9E-403C-8A33-BFC303F3840E:calendar")
-
-        def test_expandcuaddrs(self):
-            def doTest(recordName, record, result, title):
-                service = OpenDirectoryService(node="/Search", dosetup=False)
-                if not service._parseServiceInfo("calendar.apple.com", recordName, {
-                'dsAttrTypeNative:apple-serviceinfo'  : PlistParse.plist_good,
-                dsattributes.kDS1AttrGeneratedUID:      "GUIDIFY",
-                dsattributes.kDSNAttrMetaNodeLocation:  "/LDAPv3/127.0.0.1"}):
-                    self.fail(msg="Plist parse should not have failed: %s" % (recordName,))
-                else:
-                    expanded = service._calendarUserAddresses(DirectoryService.recordType_users, recordName, record)
-
-                    # Verify that we extracted the proper items
-                    self.assertEqual(expanded, result, msg=title % (expanded, result,))
-
-            data = (
-                (
-                 "user01",
-                 {
-                    dsattributes.kDS1AttrGeneratedUID: "GUID-USER-01",
-                    dsattributes.kDSNAttrEMailAddress: "user01 at example.com",
-                 },
-                 set((
-                    "mailto:user01 at example.com",
-                 )),
-                 "User with one email address, %s != %s",
-                ),
-                (
-                 "user02",
-                 {
-                    dsattributes.kDS1AttrGeneratedUID: "GUID-USER-02",
-                    dsattributes.kDSNAttrEMailAddress: ["user02 at example.com", "user02 at calendar.example.com"],
-                 },
-                 set((
-                    "mailto:user02 at example.com",
-                    "mailto:user02 at calendar.example.com",
-                 )),
-                 "User with multiple email addresses, %s != %s",
-                ),
-                (
-                 "user03",
-                 {
-                    dsattributes.kDS1AttrGeneratedUID: "GUID-USER-03",
-                 },
-                 set(()),
-                 "User with no email addresses, %s != %s",
-                ),
-            )
-
-            for recordName, record, result, title in data:
-                doTest(recordName, record, result, title)
-
-    class ODRecordsParse (twisted.trial.unittest.TestCase):
-
-        record_localod_good = ("computer1.apple.com", {
-            dsattributes.kDS1AttrGeneratedUID     : "GUID1",
-            dsattributes.kDSNAttrRecordName       : "computer1.apple.com",
-            'dsAttrTypeNative:apple-serviceinfo'  : PlistParse.plist_good,
-            dsattributes.kDSNAttrMetaNodeLocation : "/LDAPv3/127.0.0.1",
-        })
-        record_localod_good_other = ("computer2.apple.com", {
-            dsattributes.kDS1AttrGeneratedUID     : "GUID1",
-            dsattributes.kDSNAttrRecordName       : "computer2.apple.com",
-            'dsAttrTypeNative:apple-serviceinfo'  : PlistParse.plist_good_other,
-            dsattributes.kDSNAttrMetaNodeLocation : "/LDAPv3/127.0.0.1",
-        })
-        record_localod_duplicate = ("computer1", {
-            dsattributes.kDS1AttrGeneratedUID     : "GUID1_bad",
-            dsattributes.kDSNAttrRecordName       : "computer1",
-            'dsAttrTypeNative:apple-serviceinfo'  : PlistParse.plist_duplicate,
-            dsattributes.kDSNAttrMetaNodeLocation : "/LDAPv3/127.0.0.1",
-        })
-        record_remoteod_good = ("computer3.apple.com", {
-            dsattributes.kDS1AttrGeneratedUID     : "GUID2",
-            dsattributes.kDSNAttrRecordName       : "computer3.apple.com",
-            'dsAttrTypeNative:apple-serviceinfo'  : PlistParse.plist_good,
-            dsattributes.kDSNAttrMetaNodeLocation : "/LDAPv3/directory.apple.com",
-        })
-        record_remoteod_duplicate = ("computer3", {
-            dsattributes.kDS1AttrGeneratedUID     : "GUID2",
-            dsattributes.kDSNAttrRecordName       : "computer3",
-            'dsAttrTypeNative:apple-serviceinfo'  : PlistParse.plist_duplicate,
-            dsattributes.kDSNAttrMetaNodeLocation : "/LDAPv3/directory.apple.com",
-        })
-        record_default_good = ("computer4.apple.com", {
-            dsattributes.kDS1AttrGeneratedUID     : "GUID3",
-            dsattributes.kDSNAttrRecordName       : "computer4.apple.com",
-            'dsAttrTypeNative:apple-serviceinfo'  : PlistParse.plist_good,
-            dsattributes.kDSNAttrMetaNodeLocation : "/Local/Default",
-        })
-        record_default_duplicate = ("computer4", {
-            dsattributes.kDS1AttrGeneratedUID     : "GUID3",
-            dsattributes.kDSNAttrRecordName       : "computer4",
-            'dsAttrTypeNative:apple-serviceinfo'  : PlistParse.plist_duplicate,
-            dsattributes.kDSNAttrMetaNodeLocation : "/Local/Default",
-        })
-
-        def test_odrecords_error(self):
-            def _doParseRecords(recordlist, title):
-                service = OpenDirectoryService(node="/Search", dosetup=False)
-                try:
-                    service._parseComputersRecords(recordlist, "calendar.apple.com")
-                    self.fail(msg="Record parse should have failed: %s" % (title,))
-                except OpenDirectoryInitError:
-                    pass
-
-            records = (
-                ((), "no records found"),
-                ((
-                      (ODRecordsParse.record_localod_good_other[0], ODRecordsParse.record_localod_good_other[1]),
-                 ), "non-matching record found"),
-            )
-
-            for recordlist, title in records:
-                _doParseRecords(recordlist, title)
-
-        def test_odrecords_good(self):
-            def _doParseRecords(recordlist, title):
-                service = OpenDirectoryService(node="/Search", dosetup=False)
-                try:
-                    service._parseComputersRecords(recordlist, "calendar.apple.com")
-                except OpenDirectoryInitError, ex:
-                    self.fail(msg="Record parse should not have failed: \"%s\" with error: %s" % (title, ex))
-
-            records = (
-                ((
-                      (ODRecordsParse.record_localod_good[0],       ODRecordsParse.record_localod_good[1]),
-                 ), "single good plist"),
-                ((
-                      (ODRecordsParse.record_localod_good[0],       ODRecordsParse.record_localod_good[1]),
-                      (ODRecordsParse.record_localod_good_other[0], ODRecordsParse.record_localod_good_other[1]),
-                 ), "multiple plists"),
-            )
-
-            for recordlist, title in records:
-                _doParseRecords(recordlist, title)
-
-        def test_odrecords_multiple(self):
-            def _doParseRecords(recordlist, title, tags):
-                service = OpenDirectoryService(node="/Search", dosetup=False)
-                service._parseComputersRecords(recordlist, "calendar.apple.com")
-
-                self.assertEquals(service.servicetags, set(tags),
-                                  "Got wrong service tags: %s and %s" % (service.servicetags, set(tags),))
-
-            records = (
-                (((ODRecordsParse.record_remoteod_good[0],  ODRecordsParse.record_remoteod_good[1]),
-                  (ODRecordsParse.record_localod_good[0],   ODRecordsParse.record_localod_good[1]),
-                  (ODRecordsParse.record_default_good[0],   ODRecordsParse.record_default_good[1])),
-                 "Three records",
-                 ("GUID2:C18C34AC-3D9E-403C-8A33-BFC303F3840E:calendar",
-                  "GUID1:C18C34AC-3D9E-403C-8A33-BFC303F3840E:calendar",
-                  "GUID3:C18C34AC-3D9E-403C-8A33-BFC303F3840E:calendar")),
-                (((ODRecordsParse.record_localod_good[0],   ODRecordsParse.record_localod_good[1]),
-                  (ODRecordsParse.record_default_good[0],   ODRecordsParse.record_default_good[1])),
-                 "Two records",
-                 ("GUID1:C18C34AC-3D9E-403C-8A33-BFC303F3840E:calendar",
-                  "GUID3:C18C34AC-3D9E-403C-8A33-BFC303F3840E:calendar")),
-                (((ODRecordsParse.record_default_good[0],   ODRecordsParse.record_default_good[1]),),
-                 "One record",
-                 ("GUID3:C18C34AC-3D9E-403C-8A33-BFC303F3840E:calendar",)),
-            )
-
-            for recordlist, title, tags in records:
-                _doParseRecords(recordlist, title, tags)
-
-        def test_odrecords_duplicates(self):
-            def _doParseRecords(recordlist, title, items, tags):
-                service = OpenDirectoryService(node="/Search", dosetup=False)
-                service._parseComputersRecords(recordlist, "calendar.apple.com")
-                self.assertEquals(service.servicetags, set(tags))
-
-            records = (
-                (((ODRecordsParse.record_remoteod_good[0],       ODRecordsParse.record_remoteod_good[1]),
-                  (ODRecordsParse.record_remoteod_duplicate[0],  ODRecordsParse.record_remoteod_duplicate[1]),
-                  (ODRecordsParse.record_localod_good[0],        ODRecordsParse.record_localod_good[1]),
-                  (ODRecordsParse.record_default_good[0],        ODRecordsParse.record_default_good[1])),
-                 "Remote Record Duplicated", ("computer3.apple.com", "computer3",),
-                 ("GUID2:C18C34AC-3D9E-403C-8A33-BFC303F3840E:calendar",
-                  "GUID2:1C8C34AC-3D9E-403C-8A33-FBC303F3840E:calendar",
-                  "GUID1:C18C34AC-3D9E-403C-8A33-BFC303F3840E:calendar",
-                  "GUID3:C18C34AC-3D9E-403C-8A33-BFC303F3840E:calendar")),
-                (((ODRecordsParse.record_localod_good[0],        ODRecordsParse.record_localod_good[1]),
-                  (ODRecordsParse.record_localod_duplicate[0],   ODRecordsParse.record_localod_duplicate[1]),
-                  (ODRecordsParse.record_default_good[0],        ODRecordsParse.record_default_good[1])),
-                 "Local OD Duplicated", ("computer1.apple.com", "computer1",),
-                 ("GUID1:C18C34AC-3D9E-403C-8A33-BFC303F3840E:calendar",
-                  "GUID1_bad:1C8C34AC-3D9E-403C-8A33-FBC303F3840E:calendar",
-                  "GUID3:C18C34AC-3D9E-403C-8A33-BFC303F3840E:calendar")),
-                (((ODRecordsParse.record_default_good[0],        ODRecordsParse.record_default_good[1]),
-                  (ODRecordsParse.record_default_duplicate[0],   ODRecordsParse.record_default_duplicate[1])),
-                 "Local Node Duplicated", ("computer4.apple.com", "computer4",),
-                 ("GUID3:C18C34AC-3D9E-403C-8A33-BFC303F3840E:calendar",
-                  "GUID3:1C8C34AC-3D9E-403C-8A33-FBC303F3840E:calendar")),
-            )
-
-            for recordlist, title, items, tags in records:
-                _doParseRecords(recordlist, title, items, tags)
-
-    class ODResourceInfoParse (twisted.trial.unittest.TestCase):
-
-        plist_good_false = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <key>com.apple.WhitePagesFramework</key>
-    <dict>
-        <key>AutoAcceptsInvitation</key>
-        <false/>
-        <key>Label</key>
-        <string>Location</string>
-        <key>CalendaringDelegate</key>
-        <string>1234-GUID-5678</string>
-        <key>ReadOnlyCalendaringDelegate</key>
-        <string>1234-GUID-5679</string>
-    </dict>
-</dict>
-</plist>
-"""
-
-        plist_good_true = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <key>com.apple.WhitePagesFramework</key>
-    <dict>
-        <key>AutoAcceptsInvitation</key>
-        <true/>
-        <key>Label</key>
-        <string>Location</string>
-        <key>CalendaringDelegate</key>
-        <string></string>
-        <key>ReadOnlyCalendaringDelegate</key>
-        <string></string>
-    </dict>
-</dict>
-</plist>
-"""
-
-        plist_good_missing = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <key>com.apple.WhitePagesFramework</key>
-    <dict>
-        <key>Label</key>
-        <string>Location</string>
-    </dict>
-</dict>
-</plist>
-"""
-
-        plist_bad = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <key>com.apple.WhitePagesFramework</key>
-    <string>bogus</string>
-</dict>
-</plist>
-"""
-
-        plist_wrong = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <key>com.apple.YellowPagesFramework</key>
-    <dict>
-        <key>AutoAcceptsInvitation</key>
-        <true/>
-        <key>Label</key>
-        <string>Location</string>
-        <key>CalendaringDelegate</key>
-        <string>1234-GUID-5678</string>
-        <key>ReadOnlyCalendaringDelegate</key>
-        <string>1234-GUID-5679</string>
-    </dict>
-</dict>
-</plist>
-"""
-
-        plist_invalid = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <key>com.apple.WhitePagesFramework</key>
-    <string>bogus</string>
-    <string>another bogon</string>
-</dict>
-</plist>
-"""
-
-        plist_invalid_xml = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <key>com.apple.WhitePagesFramework</key>
-    <string>R&D</string>
-</dict>
-</plist>
-"""
-
-        test_bool = (
-            (plist_good_false, False, "1234-GUID-5678", "1234-GUID-5679", None),
-            (plist_good_true, True, "", "", None),
-            (plist_good_missing, False, None, None, None),
-            (plist_wrong, False, None, None, None),
-            (plist_bad, False, None, None, ValueError),
-            (plist_invalid, False, None, None, ValueError),
-            (plist_invalid_xml, False, None, None, ValueError),
-        )
-
-        def test_plists(self):
-            service = OpenDirectoryService(node="/Search", dosetup=False)
-            
-            for item in ODResourceInfoParse.test_bool:
-                if item[4] is None:
-                    item1, item2, item3 = service._parseResourceInfo(item[0], "guid", "locations", "name")
-                    self.assertEqual(item1, item[1])
-                    self.assertEqual(item2, item[2])
-                    self.assertEqual(item3, item[3])
-                else:
-                    self.assertRaises(item[4], service._parseResourceInfo, item[0], "guid", "locations", "name")

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_principal.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_principal.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2009 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,19 +16,19 @@
 
 import os
 
+from twisted.cred.credentials import UsernamePassword
 from twisted.internet.defer import inlineCallbacks
 from twisted.web2.dav import davxml
 from twisted.web2.dav.fileop import rmdir
 from twisted.web2.dav.resource import AccessDeniedError
 from twisted.web2.test.test_server import SimpleRequest
-from twisted.web2.dav.test.util import serialize
 
 from twistedcaldav.static import CalendarHomeProvisioningFile
-from twistedcaldav.directory.apache import BasicDirectoryService, DigestDirectoryService
+from twistedcaldav.config import config
+from twistedcaldav.directory import augment
 from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.directory.test.test_apache import basicUserFile, digestUserFile, groupFile, digestRealm
 from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.directory.test.test_xmlfile import xmlFile
+from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
 from twistedcaldav.directory.principal import DirectoryPrincipalTypeProvisioningResource
 from twistedcaldav.directory.principal import DirectoryPrincipalResource
@@ -38,11 +38,6 @@
 
 import twistedcaldav.test.util
 
-directoryServices = (
-    BasicDirectoryService(digestRealm, basicUserFile, groupFile),
-    DigestDirectoryService(digestRealm, digestUserFile, groupFile),
-    XMLDirectoryService(xmlFile),
-)
 
 class ProvisionedPrincipals (twistedcaldav.test.util.TestCase):
     """
@@ -51,9 +46,13 @@
     def setUp(self):
         super(ProvisionedPrincipals, self).setUp()
 
+        self.directoryServices = (
+            XMLDirectoryService(xmlFile=xmlFile),
+        )
+
         # Set up a principals hierarchy for each service we're testing with
         self.principalRootResources = {}
-        for directory in directoryServices:
+        for directory in self.directoryServices:
             name = directory.__class__.__name__
             url = "/" + name + "/"
 
@@ -63,6 +62,8 @@
 
             self.principalRootResources[directory.__class__.__name__] = provisioningResource
 
+        augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
+
     def test_hierarchy(self):
         """
         DirectoryPrincipalProvisioningResource.listChildren(),
@@ -77,7 +78,7 @@
 
         DirectoryPrincipalResource.principalURL(),
         """
-        for directory in directoryServices:
+        for directory in self.directoryServices:
             #print "\n -> %s" % (directory.__class__.__name__,)
             provisioningResource = self.principalRootResources[directory.__class__.__name__]
 
@@ -139,7 +140,7 @@
         """
         DirectoryPrincipalProvisioningResource.principalForUser()
         """
-        for directory in directoryServices:
+        for directory in self.directoryServices:
             provisioningResource = self.principalRootResources[directory.__class__.__name__]
 
             for user in directory.listRecords(DirectoryService.recordType_users):
@@ -162,7 +163,7 @@
         """
         for provisioningResource, recordType, recordResource, record in self._allRecords():
             principal = provisioningResource.principalForRecord(record)
-            self.failIf(principal is None)
+            self.failIf(principal is None, msg=str(record))
             self.assertEquals(record, principal.record)
 
     def test_principalForCalendarUserAddress(self):
@@ -184,6 +185,13 @@
                 else:
                     self.failIf(principal is not None)
 
+        # Explicitly check the disabled record
+        provisioningResource = self.principalRootResources['XMLDirectoryService']
+        self.failIf(provisioningResource.principalForCalendarUserAddress("mailto:nocalendar at example.com") is not None)
+        self.failIf(provisioningResource.principalForCalendarUserAddress("urn:uuid:543D28BA-F74F-4D5F-9243-B3E3A61171E5") is not None)
+        self.failIf(provisioningResource.principalForCalendarUserAddress("/principals/users/nocalendar/") is not None)
+        self.failIf(provisioningResource.principalForCalendarUserAddress("/principals/__uids__/543D28BA-F74F-4D5F-9243-B3E3A61171E5/") is not None)
+
     def test_autoSchedule(self):
         """
         DirectoryPrincipalProvisioningResource.principalForCalendarUserAddress()
@@ -251,24 +259,6 @@
             memberships = yield recordResource.groupMemberships()
             self.failUnless(set(record.groups()).issubset(set(r.record for r in memberships if hasattr(r, "record"))))
 
-    def test_proxies(self):
-        """
-        DirectoryPrincipalResource.proxies()
-        """
-        for provisioningResource, recordType, recordResource, record in self._allRecords():
-            if record.enabledForCalendaring:
-                self.failUnless(set(record.proxies()).issubset(set(r.record for r in recordResource.proxies())))
-                self.assertEqual(record.hasEditableProxyMembership(), recordResource.hasEditableProxyMembership())
-
-    def test_read_only_proxies(self):
-        """
-        DirectoryPrincipalResource.proxies()
-        """
-        for provisioningResource, recordType, recordResource, record in self._allRecords():
-            if record.enabledForCalendaring:
-                self.failUnless(set(record.readOnlyProxies()).issubset(set(r.record for r in recordResource.readOnlyProxies())))
-                self.assertEqual(record.hasEditableProxyMembership(), recordResource.hasEditableProxyMembership())
-
     def test_principalUID(self):
         """
         DirectoryPrincipalResource.principalUID()
@@ -305,7 +295,7 @@
         # Need to create a calendar home provisioner for each service.
         calendarRootResources = {}
 
-        for directory in directoryServices:
+        for directory in self.directoryServices:
             url = "/homes_" + directory.__class__.__name__ + "/"
             path = os.path.join(self.docroot, url[1:])
 
@@ -352,37 +342,29 @@
         """
         Default access controls for principals.
         """
-        def work():
-            for provisioningResource, recordType, recordResource, record in self._allRecords():
-                for args in _authReadOnlyPrivileges(recordResource, recordResource.principalURL()):
-                    yield args
+        for provisioningResource, recordType, recordResource, record in self._allRecords():
+            for args in _authReadOnlyPrivileges(self, recordResource, recordResource.principalURL()):
+                yield self._checkPrivileges(*args)
 
-        for args in work():
-            yield self._checkPrivileges(*args)
-
     @inlineCallbacks
     def test_defaultAccessControlList_provisioners(self):
         """
         Default access controls for principal provisioning resources.
         """
-        def work():
-            for directory in directoryServices:
-                #print "\n -> %s" % (directory.__class__.__name__,)
-                provisioningResource = self.principalRootResources[directory.__class__.__name__]
+        for directory in self.directoryServices:
+            #print "\n -> %s" % (directory.__class__.__name__,)
+            provisioningResource = self.principalRootResources[directory.__class__.__name__]
 
-                for args in _authReadOnlyPrivileges(provisioningResource, provisioningResource.principalCollectionURL()):
-                    yield args
+            for args in _authReadOnlyPrivileges(self, provisioningResource, provisioningResource.principalCollectionURL()):
+                yield self._checkPrivileges(*args)
 
-                for recordType in provisioningResource.listChildren():
-                    #print "   -> %s" % (recordType,)
-                    typeResource = provisioningResource.getChild(recordType)
+            for recordType in provisioningResource.listChildren():
+                #print "   -> %s" % (recordType,)
+                typeResource = provisioningResource.getChild(recordType)
 
-                    for args in _authReadOnlyPrivileges(typeResource, typeResource.principalCollectionURL()):
-                        yield args
+                for args in _authReadOnlyPrivileges(self, typeResource, typeResource.principalCollectionURL()):
+                    yield self._checkPrivileges(*args)
 
-        for args in work():
-            yield self._checkPrivileges(*args)
-
     def _allRecords(self):
         """
         @return: an iterable of tuples
@@ -393,7 +375,7 @@
             C{record} is the directory service record
             for each record in each directory in C{directoryServices}.
         """
-        for directory in directoryServices:
+        for directory in self.directoryServices:
             provisioningResource = self.principalRootResources[directory.__class__.__name__]
             for recordType in directory.recordTypes():
                 for record in directory.listRecords(recordType):
@@ -425,13 +407,17 @@
         d.addCallback(gotResource)
         return d
 
-def _authReadOnlyPrivileges(resource, url):
-    for principal, privilege, allowed in (
-        ( davxml.All()             , davxml.Read()  , False ),
-        ( davxml.All()             , davxml.Write() , False ),
-        ( davxml.Unauthenticated() , davxml.Read()  , False ),
-        ( davxml.Unauthenticated() , davxml.Write() , False ),
-        ( davxml.Authenticated()   , davxml.Read()  , True  ),
-        ( davxml.Authenticated()   , davxml.Write() , False ),
-    ):
+def _authReadOnlyPrivileges(self, resource, url):
+    items = []
+    for provisioningResource, recordType, recordResource, record in self._allRecords():
+        if recordResource == resource:
+            items.append(( davxml.HRef().fromString(recordResource.principalURL()), davxml.Read()  , True ))
+            items.append(( davxml.HRef().fromString(recordResource.principalURL()), davxml.Write() , True ))
+        else:
+            items.append(( davxml.HRef().fromString(recordResource.principalURL()), davxml.Read()  , True ))
+            items.append(( davxml.HRef().fromString(recordResource.principalURL()), davxml.Write() , False ))
+    items.append(( davxml.Unauthenticated() , davxml.Read()  , False ))
+    items.append(( davxml.Unauthenticated() , davxml.Write() , False ))
+            
+    for principal, privilege, allowed in items:
         yield resource, url, principal, privilege, allowed

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_proxyprincipalmembers.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_proxyprincipalmembers.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_proxyprincipalmembers.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2009 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.
@@ -14,39 +14,53 @@
 # limitations under the License.
 ##
 
-from twisted.internet.defer import DeferredList, inlineCallbacks
+from twisted.internet.defer import DeferredList, inlineCallbacks, returnValue,\
+    succeed
 from twisted.web2.dav import davxml
 
 from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.directory.test.test_xmlfile import xmlFile
+from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile,\
+    proxiesFile
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
 from twistedcaldav.directory.principal import DirectoryPrincipalResource
+from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser
+from twistedcaldav.directory.xmlfile import XMLDirectoryService
 
 import twistedcaldav.test.util
+from twistedcaldav.config import config
+from twistedcaldav.directory import augment
+from twistedcaldav.directory.calendaruserproxyloader import XMLCalendarUserProxyLoader
+import os
 
-directoryService = XMLDirectoryService(xmlFile)
-
 class ProxyPrincipals (twistedcaldav.test.util.TestCase):
     """
     Directory service provisioned principals.
     """
+    
+    @inlineCallbacks
     def setUp(self):
         super(ProxyPrincipals, self).setUp()
 
+        self.directoryService = XMLDirectoryService(xmlFile=xmlFile)
+        augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
+
         # Set up a principals hierarchy for each service we're testing with
         self.principalRootResources = {}
-        name = directoryService.__class__.__name__
+        name = self.directoryService.__class__.__name__
         url = "/" + name + "/"
 
-        provisioningResource = DirectoryPrincipalProvisioningResource(url, directoryService)
+        provisioningResource = DirectoryPrincipalProvisioningResource(url, self.directoryService)
 
         self.site.resource.putChild(name, provisioningResource)
 
-        self.principalRootResources[directoryService.__class__.__name__] = provisioningResource
+        self.principalRootResources[self.directoryService.__class__.__name__] = provisioningResource
 
+        config.DataRoot = self.mktemp()
+        os.mkdir(config.DataRoot)
+        yield XMLCalendarUserProxyLoader(proxiesFile.path).updateProxyDB()
+
     def _getPrincipalByShortName(self, type, name):
-        provisioningResource = self.principalRootResources[directoryService.__class__.__name__]
+        provisioningResource = self.principalRootResources[self.directoryService.__class__.__name__]
         return provisioningResource.principalForShortName(type, name)
 
     def _groupMembersTest(self, recordType, recordName, subPrincipalName, expectedMembers):
@@ -76,6 +90,43 @@
         return d
     
     @inlineCallbacks
+    def _addProxy(self, principal, subPrincipalName, proxyPrincipal):
+
+        if isinstance(principal, tuple):
+            principal = self._getPrincipalByShortName(principal[0], principal[1])
+        principal = principal.getChild(subPrincipalName)
+        members = (yield principal.groupMembers())
+
+        if isinstance(proxyPrincipal, tuple):
+            proxyPrincipal = self._getPrincipalByShortName(proxyPrincipal[0], proxyPrincipal[1])
+        members.add(proxyPrincipal)
+        
+        yield principal.setGroupMemberSetPrincipals(members)
+
+    @inlineCallbacks
+    def _removeProxy(self, recordType, recordName, subPrincipalName, proxyRecordType, proxyRecordName):
+
+        principal = self._getPrincipalByShortName(recordType, recordName)
+        principal = principal.getChild(subPrincipalName)
+        members = (yield principal.groupMembers())
+
+        proxyPrincipal = self._getPrincipalByShortName(proxyRecordType, proxyRecordName)
+        for p in members:
+            if p.principalUID() == proxyPrincipal.principalUID():
+                members.remove(p)
+                break
+        
+        yield principal.setGroupMemberSetPrincipals(members)
+
+    @inlineCallbacks
+    def _clearProxy(self, principal, subPrincipalName):
+
+        if isinstance(principal, tuple):
+            principal = self._getPrincipalByShortName(principal[0], principal[1])
+        principal = principal.getChild(subPrincipalName)
+        yield principal.setGroupMemberSetPrincipals(set())
+
+    @inlineCallbacks
     def _proxyForTest(self, recordType, recordName, expectedProxies, read_write):
         principal = self._getPrincipalByShortName(recordType, recordName)
         proxies = (yield principal.proxyFor(read_write))
@@ -201,6 +252,7 @@
         d.addCallback(check)
         return d
 
+    @inlineCallbacks
     def test_setGroupMemberSet(self):
         class StubMemberDB(object):
             def __init__(self):
@@ -213,7 +265,7 @@
                 return self.members
 
 
-        user = self._getPrincipalByShortName(directoryService.recordType_users,
+        user = self._getPrincipalByShortName(self.directoryService.recordType_users,
                                            "cdaboo")
 
         proxyGroup = user.getChild("calendar-proxy-write")
@@ -228,21 +280,22 @@
             davxml.HRef.fromString(
                 "/XMLDirectoryService/__uids__/5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1/"))
 
-        proxyGroup.setGroupMemberSet(new_members, None)
+        yield proxyGroup.setGroupMemberSet(new_members, None)
 
         self.assertEquals(
             set([str(p) for p in memberdb.members]),
             set(["5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1",
                  "8B4288F6-CC82-491D-8EF9-642EF4F3E7D0"]))
 
-
+    @inlineCallbacks
     def test_setGroupMemberSetNotifiesPrincipalCaches(self):
         class StubCacheNotifier(object):
             changedCount = 0
             def changed(self):
                 self.changedCount += 1
+                return succeed(True)
 
-        user = self._getPrincipalByShortName(directoryService.recordType_users, "cdaboo")
+        user = self._getPrincipalByShortName(self.directoryService.recordType_users, "cdaboo")
 
         proxyGroup = user.getChild("calendar-proxy-write")
 
@@ -255,7 +308,7 @@
 
             self.assertEquals(notifier.changedCount, 0)
 
-            proxyGroup.setGroupMemberSet(
+            yield proxyGroup.setGroupMemberSet(
                 davxml.GroupMemberSet(
                     davxml.HRef.fromString(
                         "/XMLDirectoryService/__uids__/5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1/")),
@@ -281,3 +334,186 @@
             False
         )
 
+    @inlineCallbacks
+    def test_UserProxy(self):
+        
+        for proxyType in ("calendar-proxy-read", "calendar-proxy-write"):
+
+            yield self._addProxy(
+                (DirectoryService.recordType_users, "wsanchez",),
+                proxyType,
+                (DirectoryService.recordType_users, "cdaboo",),
+            )
+    
+            yield self._groupMembersTest(
+                DirectoryService.recordType_users, "wsanchez",
+                proxyType,
+                ("Cyrus Daboo",),
+            )
+            
+            yield self._addProxy(
+                (DirectoryService.recordType_users, "wsanchez",),
+                proxyType,
+                (DirectoryService.recordType_users, "lecroy",),
+            )
+    
+            yield self._groupMembersTest(
+                DirectoryService.recordType_users, "wsanchez",
+                proxyType,
+                ("Cyrus Daboo", "Chris Lecroy",),
+            )
+    
+            yield self._removeProxy(
+                DirectoryService.recordType_users, "wsanchez",
+                proxyType,
+                DirectoryService.recordType_users, "cdaboo",
+            )
+    
+            yield self._groupMembersTest(
+                DirectoryService.recordType_users, "wsanchez",
+                proxyType,
+                ("Chris Lecroy",),
+            )
+
+    @inlineCallbacks
+    def test_InvalidUserProxy(self):
+
+
+        # Set up the in-memory (non-null) memcacher:
+        config.ProcessType = "Single"
+        principal = self._getPrincipalByShortName(
+            DirectoryService.recordType_users, "wsanchez")
+        db = principal._calendar_user_proxy_index()
+
+        # Set the clock to the epoch:
+        theTime = 0
+        db._memcacher.theTime = theTime
+
+
+        for doMembershipFirst in (True, False):
+            for proxyType in ("calendar-proxy-read", "calendar-proxy-write"):
+
+                principal = self._getPrincipalByShortName(DirectoryService.recordType_users, "wsanchez")
+                proxyGroup = principal.getChild(proxyType)
+
+                testPrincipal = self._getPrincipalByShortName(DirectoryService.recordType_users, "cdaboo")
+
+                fakePrincipal = self._getPrincipalByShortName(DirectoryService.recordType_users, "dreid")
+                fakeProxyGroup = fakePrincipal.getChild(proxyType)
+
+                yield self._addProxy(
+                    principal,
+                    proxyType,
+                    testPrincipal,
+                )
+
+                members = yield proxyGroup._index().getMembers(proxyGroup.uid)
+                self.assertEquals(len(members), 1)
+
+                yield self._addProxy(
+                    fakePrincipal,
+                    proxyType,
+                    testPrincipal,
+                )
+                members = yield fakeProxyGroup._index().getMembers(fakeProxyGroup.uid)
+                self.assertEquals(len(members), 1)
+
+                uids = [p.principalUID() for p in (yield testPrincipal.groupMemberships())]
+                self.assertTrue("5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1#%s" % (proxyType,) in uids)
+
+                memberships = yield testPrincipal._calendar_user_proxy_index().getMemberships(testPrincipal.principalUID())
+                self.assertEquals(len(memberships), 2)
+
+                yield self._addProxy(
+                    principal,
+                    proxyType,
+                    fakePrincipal,
+                )
+                members = yield proxyGroup._index().getMembers(proxyGroup.uid)
+                self.assertEquals(len(members), 2)
+
+                # Remove the dreid user from the directory service
+                del self.directoryService._accounts()[DirectoryService.recordType_users]["dreid"]
+
+
+                cacheTimeout = config.DirectoryService.params.get("cacheTimeout", 30) * 60 * 2
+
+                @inlineCallbacks
+                def _membershipTest():
+
+                    uids = [p.principalUID() for p in (yield testPrincipal.groupMemberships())]
+                    self.assertTrue("5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1#%s" % (proxyType,) not in uids)
+
+                    memberships = yield testPrincipal._calendar_user_proxy_index().getMemberships(testPrincipal.principalUID())
+                    self.assertEquals(len(memberships), 1)
+
+                @inlineCallbacks
+                def _membersTest(theTime):
+                    yield self._groupMembersTest(
+                        DirectoryService.recordType_users, "wsanchez",
+                        proxyType,
+                        ("Cyrus Daboo",),
+                    )
+
+                    # Trigger the proxy DB clean up, which won't actually
+                    # remove anything because we haven't exceeded the timeout
+                    yield proxyGroup.groupMembers()
+
+                    # Advance 10 seconds
+                    theTime += 10
+                    db._memcacher.theTime = theTime
+
+                    # When we first examine the members, we have not exceeded
+                    # the clean-up timeout, so we'll still have 2:
+                    members = yield proxyGroup._index().getMembers(proxyGroup.uid)
+                    self.assertEquals(len(members), 2)
+
+                    # Restore removed user
+                    parser = XMLAccountsParser(self.directoryService.xmlFile)
+                    self.directoryService._parsedAccounts = parser.items
+                    self.directoryService.recordWithShortName(
+                        DirectoryService.recordType_users, "dreid")
+
+                    # Trigger the proxy DB clean up, which will actually
+                    # remove the deletion timer because the principal has been
+                    # restored
+                    yield proxyGroup.groupMembers()
+
+                    # Verify the deletion timer has been removed
+                    result = yield db._memcacher.checkDeletionTimer("5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1")
+                    self.assertEquals(result, None)
+
+                    # Remove the dreid user from the directory service
+                    del self.directoryService._accounts()[DirectoryService.recordType_users]["dreid"]
+
+                    # Trigger the proxy DB clean up, which won't actually
+                    # remove anything because we haven't exceeded the timeout
+                    yield proxyGroup.groupMembers()
+
+                    # Advance beyond the timeout
+                    theTime += cacheTimeout
+                    db._memcacher.theTime = theTime
+
+                    # Trigger the proxy DB clean up
+                    yield proxyGroup.groupMembers()
+
+                    # The missing principal has now been cleaned out of the
+                    # proxy DB
+                    members = yield proxyGroup._index().getMembers(proxyGroup.uid)
+                    self.assertEquals(len(members), 1)
+                    returnValue(theTime)
+
+
+                if doMembershipFirst:
+                    yield _membershipTest()
+                    theTime = yield _membersTest(theTime)
+                else:
+                    theTime = yield _membersTest(theTime)
+                    yield _membershipTest()
+
+                # Restore removed user
+                parser = XMLAccountsParser(self.directoryService.xmlFile)
+                self.directoryService._parsedAccounts = parser.items
+
+                yield self._clearProxy(principal, proxyType)
+                yield self._clearProxy(fakePrincipal, proxyType)

Deleted: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_sqldb.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_sqldb.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_sqldb.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,51 +0,0 @@
-##
-# Copyright (c) 2005-2007 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
-
-from twisted.python.filepath import FilePath
-
-import twistedcaldav.directory.test.util
-import twistedcaldav.directory.test.test_xmlfile
-from twistedcaldav.directory.sqldb import SQLDirectoryService
-
-xmlFile = FilePath(os.path.join(os.path.dirname(__file__), "accounts.xml"))
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-class SQLDB (
-    twistedcaldav.directory.test.test_xmlfile.XMLFileBase,
-    twistedcaldav.directory.test.util.BasicTestCase,
-    twistedcaldav.directory.test.util.DigestTestCase
-):
-    """
-    Test SQL directory implementation.
-    """
-    def service(self):
-        return SQLDirectoryService(os.getcwd(), self.xmlFile())
-
-    def test_verifyCredentials_digest(self):
-        super(SQLDB, self).test_verifyCredentials_digest()
-    test_verifyCredentials_digest.todo = ""
-
-    def test_verifyRealmFromDB(self):
-        # Make sure the database has been initialized with the XML file
-        self.service()
-
-        # Then get an instance without using the XML file
-        service = SQLDirectoryService(os.getcwd(), None)
-
-        self.assertEquals(service.realmName, "Test")

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_util.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_util.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -15,12 +15,11 @@
 ##
 
 from twistedcaldav.directory.util import uuidFromName
+from twistedcaldav.test.util import TestCase
 
-import twisted.trial.unittest
-
 uuid_namespace_dns = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
 
-class UUID (twisted.trial.unittest.TestCase):
+class UUID (TestCase):
     def test_uuidFromName(self):
         self.assertEquals(
             uuidFromName(uuid_namespace_dns, "python.org"),

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_xmlfile.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_xmlfile.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/test_xmlfile.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2009 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.
@@ -18,11 +18,14 @@
 
 from twisted.python.filepath import FilePath
 
+from twistedcaldav.directory import augment
 from twistedcaldav.directory.directory import DirectoryService
 import twistedcaldav.directory.test.util
 from twistedcaldav.directory.xmlfile import XMLDirectoryService
 
 xmlFile = FilePath(os.path.join(os.path.dirname(__file__), "accounts.xml"))
+augmentsFile = FilePath(os.path.join(os.path.dirname(__file__), "augments.xml"))
+proxiesFile = FilePath(os.path.join(os.path.dirname(__file__), "proxies.xml"))
 
 # FIXME: Add tests for GUID hooey, once we figure out what that means here
 
@@ -35,13 +38,14 @@
     ))
 
     users = {
-        "admin"   : { "password": "nimda",    "guid": "D11F03A0-97EA-48AF-9A6C-FAC7F3975766", "addresses": () },
-        "wsanchez": { "password": "zehcnasw", "guid": "6423F94A-6B76-4A3A-815B-D52CFD77935D", "addresses": ("mailto:wsanchez at example.com",) },
-        "cdaboo"  : { "password": "oobadc",   "guid": "5A985493-EE2C-4665-94CF-4DFEA3A89500", "addresses": ("mailto:cdaboo at example.com",)   },
-        "lecroy"  : { "password": "yorcel",   "guid": "8B4288F6-CC82-491D-8EF9-642EF4F3E7D0", "addresses": ("mailto:lecroy at example.com",)   },
-        "dreid"   : { "password": "dierd",    "guid": "5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1", "addresses": ("mailto:dreid at example.com",)    },
-        "user01"  : { "password": "01user",   "guid": None                                  , "addresses": () },
-        "user02"  : { "password": "02user",   "guid": None                                  , "addresses": () },
+        "admin"      : { "password": "nimda",      "guid": "D11F03A0-97EA-48AF-9A6C-FAC7F3975766", "addresses": () },
+        "wsanchez"   : { "password": "zehcnasw",   "guid": "6423F94A-6B76-4A3A-815B-D52CFD77935D", "addresses": ("mailto:wsanchez at example.com",) },
+        "cdaboo"     : { "password": "oobadc",     "guid": "5A985493-EE2C-4665-94CF-4DFEA3A89500", "addresses": ("mailto:cdaboo at example.com",)   },
+        "lecroy"     : { "password": "yorcel",     "guid": "8B4288F6-CC82-491D-8EF9-642EF4F3E7D0", "addresses": ("mailto:lecroy at example.com",)   },
+        "dreid"      : { "password": "dierd",      "guid": "5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1", "addresses": ("mailto:dreid at example.com",)    },
+        "nocalendar" : { "password": "radnelacon", "guid": "543D28BA-F74F-4D5F-9243-B3E3A61171E5", "addresses": () },
+        "user01"     : { "password": "01user",     "guid": None                                  , "addresses": () },
+        "user02"     : { "password": "02user",     "guid": None                                  , "addresses": () },
     }
 
     groups = {
@@ -83,6 +87,12 @@
             xmlFile.copyTo(self._xmlFile)
         return self._xmlFile
 
+    def augmentsFile(self):
+        if not hasattr(self, "_augmentsFile"):
+            self._augmentsFile = FilePath(self.mktemp())
+            augmentsFile.copyTo(self._augmentsFile)
+        return self._augmentsFile
+
 class XMLFile (
     XMLFileBase,
     twistedcaldav.directory.test.util.BasicTestCase,
@@ -92,7 +102,9 @@
     Test XML file based directory implementation.
     """
     def service(self):
-        return XMLDirectoryService(self.xmlFile())
+        directory = XMLDirectoryService(self.xmlFile())
+        augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(self.augmentsFile().path,))
+        return directory
 
     def test_changedXML(self):
         service = self.service()
@@ -103,6 +115,7 @@
 <accounts realm="Test Realm">
   <user>
     <uid>admin</uid>
+    <guid>admin</guid>
     <password>nimda</password>
     <name>Super User</name>
   </user>
@@ -129,13 +142,28 @@
 <accounts realm="Test Realm">
   <location>
     <uid>my office</uid>
+    <guid>myoffice</guid>
     <password>nimda</password>
     <name>Super User</name>
-    <auto-schedule/>
   </location>
 </accounts>
 """
         )
+        self.augmentsFile().open("w").write(
+"""<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE accounts SYSTEM "accounts.dtd">
+<augments>
+  <record>
+    <guid>myoffice</guid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <auto-schedule>true</auto-schedule>
+  </record>
+</augments>
+"""
+        )
+        augment.AugmentService.refresh()
+
         for recordType, expectedRecords in (
             ( DirectoryService.recordType_users     , ()             ),
             ( DirectoryService.recordType_groups    , ()             ),
@@ -148,28 +176,7 @@
             )
         self.assertTrue(service.recordWithShortName(DirectoryService.recordType_locations, "my office").autoSchedule)
 
-    def test_badAutoSchedule(self):
-        service = self.service()
 
-        self.xmlFile().open("w").write(
-"""<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-<accounts realm="Test Realm">
-  <user>
-    <uid>my office</uid>
-    <password>nimda</password>
-    <name>Super User</name>
-    <auto-schedule/>
-  </user>
-</accounts>
-"""
-        )
-        
-        def _findRecords():
-            set(r.shortName for r in service.listRecords(DirectoryService.recordType_users))
-
-        self.assertRaises(ValueError, _findRecords)
-        
     def test_okDisableCalendar(self):
         service = self.service()
 
@@ -186,11 +193,11 @@
     <uid>disabled</uid>
     <password>disabled</password>
     <name>Disabled</name>
-    <disable-calendar/>
   </group>
 </accounts>
 """
         )
+        
         for recordType, expectedRecords in (
             ( DirectoryService.recordType_users     , ()                       ),
             ( DirectoryService.recordType_groups    , ("enabled", "disabled")  ),
@@ -201,88 +208,5 @@
                 set(r.shortName for r in service.listRecords(recordType)),
                 set(expectedRecords)
             )
-        self.assertTrue(service.recordWithShortName(DirectoryService.recordType_groups, "enabled").enabledForCalendaring)
+        self.assertFalse(service.recordWithShortName(DirectoryService.recordType_groups, "enabled").enabledForCalendaring)
         self.assertFalse(service.recordWithShortName(DirectoryService.recordType_groups, "disabled").enabledForCalendaring)
-
-    def test_badDisableCalendar(self):
-        service = self.service()
-
-        self.xmlFile().open("w").write(
-"""<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-<accounts realm="Test Realm">
-  <location>
-    <uid>my office</uid>
-    <password>nimda</password>
-    <name>Super User</name>
-    <disable-calendar/>
-  </location>
-</accounts>
-"""
-        )
-        
-        def _findRecords():
-            set(r.shortName for r in service.listRecords(DirectoryService.recordType_users))
-
-        self.assertRaises(ValueError, _findRecords)
-
-    def test_okProxies(self):
-        service = self.service()
-
-        self.xmlFile().open("w").write(
-"""<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-<accounts realm="Test Realm">
-  <user>
-    <uid>test</uid>
-    <password>nimda</password>
-    <name>Test</name>
-  </user>
-  <location>
-    <uid>my office</uid>
-    <password>nimda</password>
-    <name>Super User</name>
-    <auto-schedule/>
-    <proxies>
-        <member>test</member>
-    </proxies>
-  </location>
-</accounts>
-"""
-        )
-        for recordType, expectedRecords in (
-            ( DirectoryService.recordType_users     , ("test",)      ),
-            ( DirectoryService.recordType_groups    , ()             ),
-            ( DirectoryService.recordType_locations , ("my office",) ),
-            ( DirectoryService.recordType_resources , ()             ),
-        ):
-            self.assertEquals(
-                set(r.shortName for r in service.listRecords(recordType)),
-                set(expectedRecords)
-            )
-        self.assertEqual(set([("users", "test",)],), service.recordWithShortName(DirectoryService.recordType_locations, "my office")._proxies)
-        self.assertEqual(set([("locations", "my office",)],), service.recordWithShortName(DirectoryService.recordType_users, "test")._proxyFor)
-
-    def test_badProxies(self):
-        service = self.service()
-
-        self.xmlFile().open("w").write(
-"""<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-<accounts realm="Test Realm">
-  <user>
-    <uid>my office</uid>
-    <password>nimda</password>
-    <name>Super User</name>
-    <proxies>
-        <member>12345-GUID-67890</member>
-    </proxies>
-  </user>
-</accounts>
-"""
-        )
-        
-        def _findRecords():
-            set(r.shortName for r in service.listRecords(DirectoryService.recordType_users))
-
-        self.assertRaises(ValueError, _findRecords)

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/util.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/test/util.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -14,17 +14,17 @@
 # limitations under the License.
 ##
 
-import twisted.trial.unittest
 from twisted.trial.unittest import SkipTest
 from twisted.cred.credentials import UsernamePassword
 from twisted.web2.auth.digest import DigestedCredentials, calcResponse, calcHA1
 
 from twistedcaldav.directory.directory import DirectoryService
 from twistedcaldav.directory.directory import UnknownRecordTypeError
+from twistedcaldav.test.util import TestCase
 
 # FIXME: Add tests for GUID hooey, once we figure out what that means here
 
-class DirectoryTestCase (twisted.trial.unittest.TestCase):
+class DirectoryTestCase (TestCase):
     """
     Tests a directory implementation.
     """

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/xmlaccountsparser.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/xmlaccountsparser.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/xmlaccountsparser.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2009 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.
@@ -23,7 +23,6 @@
     "XMLAccountsParser",
 ]
 
-from uuid import UUID
 import xml.dom.minidom
 
 from twisted.python.filepath import FilePath
@@ -43,18 +42,17 @@
 ELEMENT_GUID              = "guid"
 ELEMENT_PASSWORD          = "password"
 ELEMENT_NAME              = "name"
+ELEMENT_EMAIL_ADDRESS     = "email-address"
 ELEMENT_MEMBERS           = "members"
 ELEMENT_MEMBER            = "member"
-ELEMENT_CUADDR            = "cuaddr"
-ELEMENT_AUTOSCHEDULE      = "auto-schedule"
-ELEMENT_DISABLECALENDAR   = "disable-calendar"
-ELEMENT_PROXIES           = "proxies"
-ELEMENT_READ_ONLY_PROXIES = "read-only-proxies"
 
 ATTRIBUTE_REALM           = "realm"
 ATTRIBUTE_REPEAT          = "repeat"
 ATTRIBUTE_RECORDTYPE      = "type"
 
+VALUE_TRUE                = "true"
+VALUE_FALSE               = "false"
+
 RECORD_TYPES = {
     ELEMENT_USER     : DirectoryService.recordType_users,
     ELEMENT_GROUP    : DirectoryService.recordType_groups,
@@ -69,7 +67,8 @@
     def __repr__(self):
         return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
 
-    def __init__(self, xmlFile):
+    def __init__(self, xmlFile, externalUpdate=True):
+
         if type(xmlFile) is str:
             xmlFile = FilePath(xmlFile)
 
@@ -88,7 +87,7 @@
         # Verify that top-level element is correct
         accounts_node = doc._get_documentElement()
         if accounts_node._get_localName() != ELEMENT_ACCOUNTS:
-            self.log("Ignoring file %r because it is not a repository builder file" % (self.xmlFile,))
+            log.error("Ignoring file %r because it is not a repository builder file" % (self.xmlFile,))
             return
         self._parseXML(accounts_node)
         
@@ -103,23 +102,10 @@
         def updateMembership(group):
             # Update group membership
             for recordType, shortName in group.members:
-                item = self.items[recordType].get(shortName, None)
+                item = self.items[recordType].get(shortName)
                 if item is not None:
                     item.groups.add(group.shortName)
 
-        def updateProxyFor(proxier):
-            # Update proxy membership
-            for recordType, shortName in proxier.proxies:
-                item = self.items[recordType].get(shortName, None)
-                if item is not None:
-                    item.proxyFor.add((proxier.recordType, proxier.shortName))
-
-            # Update read-only proxy membership
-            for recordType, shortName in proxier.readOnlyProxies:
-                item = self.items[recordType].get(shortName, None)
-                if item is not None:
-                    item.readOnlyProxyFor.add((proxier.recordType, proxier.shortName))
-
         for child in node._get_childNodes():
             child_name = child._get_localName()
             if child_name is None:
@@ -148,7 +134,6 @@
         for records in self.items.itervalues():
             for principal in records.itervalues():
                 updateMembership(principal)
-                updateProxyFor(principal)
                 
 class XMLAccountRecord (object):
     """
@@ -163,15 +148,9 @@
         self.guid = None
         self.password = None
         self.name = None
+        self.emailAddresses = set()
         self.members = set()
         self.groups = set()
-        self.calendarUserAddresses = set()
-        self.autoSchedule = False
-        self.enabledForCalendaring = True
-        self.proxies = set()
-        self.proxyFor = set()
-        self.readOnlyProxies = set()
-        self.readOnlyProxyFor = set()
 
     def repeat(self, ctr):
         """
@@ -195,12 +174,12 @@
             name = self.name % ctr
         else:
             name = self.name
-        calendarUserAddresses = set()
-        for cuaddr in self.calendarUserAddresses:
-            if cuaddr.find("%") != -1:
-                calendarUserAddresses.add(cuaddr % ctr)
+        emailAddresses = set()
+        for emailAddr in self.emailAddresses:
+            if emailAddr.find("%") != -1:
+                emailAddresses.add(emailAddr % ctr)
             else:
-                calendarUserAddresses.add(cuaddr)
+                emailAddresses.add(emailAddr)
         
         result = XMLAccountRecord(self.recordType)
         result.shortName = shortName
@@ -208,11 +187,6 @@
         result.password = password
         result.name = name
         result.members = self.members
-        result.calendarUserAddresses = calendarUserAddresses
-        result.autoSchedule = self.autoSchedule
-        result.enabledForCalendaring = self.enabledForCalendaring
-        result.proxies = self.proxies
-        result.readOnlyProxies = self.readOnlyProxies
         return result
 
     def parseXML(self, node):
@@ -225,43 +199,20 @@
                     self.shortName = child.firstChild.data.encode("utf-8")
             elif child_name == ELEMENT_GUID:
                 if child.firstChild is not None:
-                    guid = child.firstChild.data.encode("utf-8")
-                    try:
-                        UUID(guid)
-                    except:
-                        log.error("Invalid GUID in accounts XML: %r" % (guid,))
-                    self.guid = guid
+                    self.guid = child.firstChild.data.encode("utf-8")
+                    if len(self.guid) < 4:
+                        self.guid += "?" * (4 - len(self.guid))
             elif child_name == ELEMENT_PASSWORD:
                 if child.firstChild is not None:
                     self.password = child.firstChild.data.encode("utf-8")
             elif child_name == ELEMENT_NAME:
                 if child.firstChild is not None:
                     self.name = child.firstChild.data.encode("utf-8")
+            elif child_name == ELEMENT_EMAIL_ADDRESS:
+                if child.firstChild is not None:
+                    self.emailAddresses.add(child.firstChild.data.encode("utf-8").lower())
             elif child_name == ELEMENT_MEMBERS:
                 self._parseMembers(child, self.members)
-            elif child_name == ELEMENT_CUADDR:
-                if child.firstChild is not None:
-                    self.calendarUserAddresses.add(child.firstChild.data.encode("utf-8").lower())
-            elif child_name == ELEMENT_AUTOSCHEDULE:
-                # Only Resources & Locations
-                if self.recordType not in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
-                    raise ValueError("<auto-schedule> element only allowed for Resources and Locations: %s" % (child_name,))
-                self.autoSchedule = True
-            elif child_name == ELEMENT_DISABLECALENDAR:
-                # Only Users or Groups
-                if self.recordType not in (DirectoryService.recordType_users, DirectoryService.recordType_groups):
-                    raise ValueError("<disable-calendar> element only allowed for Users or Groups: %s" % (child_name,))
-                self.enabledForCalendaring = False
-            elif child_name == ELEMENT_PROXIES:
-                # Only Resources & Locations
-                if self.recordType not in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
-                    raise ValueError("<auto-schedule> element only allowed for Resources and Locations: %s" % (child_name,))
-                self._parseMembers(child, self.proxies)
-            elif child_name == ELEMENT_READ_ONLY_PROXIES:
-                # Only Resources & Locations
-                if self.recordType not in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
-                    raise ValueError("<auto-schedule> element only allowed for Resources and Locations: %s" % (child_name,))
-                self._parseMembers(child, self.readOnlyProxies)
             else:
                 raise RuntimeError("Unknown account attribute: %s" % (child_name,))
 

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/xmlaugmentsparser.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/xmlaugmentsparser.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/xmlaugmentsparser.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,141 @@
+##
+# Copyright (c) 2009 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.
+##
+
+from xml.etree.ElementTree import ElementTree
+from xml.parsers.expat import ExpatError
+import types
+
+"""
+XML based augment configuration file handling.
+"""
+
+__all__ = [
+    "XMLAugmentsParser",
+]
+
+from twistedcaldav.log import Logger
+
+log = Logger()
+
+ELEMENT_AUGMENTS          = "augments"
+ELEMENT_RECORD            = "record"
+
+ELEMENT_GUID              = "guid"
+ELEMENT_ENABLE            = "enable"
+ELEMENT_HOSTEDAT          = "hosted-at"
+ELEMENT_ENABLECALENDAR    = "enable-calendar"
+ELEMENT_AUTOSCHEDULE      = "auto-schedule"
+ELEMENT_CUADDR            = "cuaddr"
+
+ATTRIBUTE_REPEAT          = "repeat"
+
+VALUE_TRUE                = "true"
+VALUE_FALSE               = "false"
+
+ELEMENT_AUGMENTRECORD_MAP = {
+    ELEMENT_GUID:           "guid",
+    ELEMENT_ENABLE:         "enabled",
+    ELEMENT_HOSTEDAT:       "hostedAt",
+    ELEMENT_ENABLECALENDAR: "enabledForCalendaring",
+    ELEMENT_AUTOSCHEDULE:   "autoSchedule",
+    ELEMENT_CUADDR:         "calendarUserAddresses",
+}
+
+class XMLAugmentsParser(object):
+    """
+    XML augments configuration file parser.
+    """
+    def __repr__(self):
+        return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
+
+    def __init__(self, xmlFile, items):
+
+        self.items = items
+        self.xmlFile = xmlFile
+
+        # Read in XML
+        try:
+            tree = ElementTree(file=self.xmlFile)
+        except ExpatError, e:
+            log.error("Unable to parse file '%s' because: %s" % (self.xmlFile, e,), raiseException=RuntimeError)
+
+        # Verify that top-level element is correct
+        augments_node = tree.getroot()
+        if augments_node.tag != ELEMENT_AUGMENTS:
+            log.error("Ignoring file '%s' because it is not a augments file" % (self.xmlFile,), raiseException=RuntimeError)
+
+        self._parseXML(augments_node)
+
+    def _parseXML(self, rootnode):
+        """
+        Parse the XML root node from the augments configuration document.
+        @param rootnode: the L{Element} to parse.
+        """
+        for child in rootnode.getchildren():
+            
+            if child.tag != ELEMENT_RECORD:
+                log.error("Unknown augment type: '%s' in augment file: '%s'" % (child.tag, self.xmlFile,), raiseException=RuntimeError)
+
+            repeat = int(child.get(ATTRIBUTE_REPEAT, "1"))
+
+            fields = {}
+            for node in child.getchildren():
+                
+                if node.tag in (
+                    ELEMENT_GUID,
+                    ELEMENT_HOSTEDAT,
+                ):
+                    fields[node.tag] = node.text
+                elif node.tag in (
+                    ELEMENT_ENABLE,
+                    ELEMENT_ENABLECALENDAR,
+                    ELEMENT_AUTOSCHEDULE,
+                ):
+                    fields[node.tag] = node.text == VALUE_TRUE
+                elif node.tag == ELEMENT_CUADDR:
+                    fields.setdefault(node.tag, set()).add(node.text)
+                else:
+                    log.error("Invalid element '%s' in augment file: '%s'" % (node.tag, self.xmlFile,), raiseException=RuntimeError)
+                    
+            # Must have at least a guid
+            if ELEMENT_GUID not in fields:
+                log.error("Invalid record '%s' without a guid in augment file: '%s'" % (child, self.xmlFile,), raiseException=RuntimeError)
+                
+            if repeat > 1:
+                for i in xrange(1, repeat+1):
+                    self.buildRecord(fields, i)
+            else:
+                self.buildRecord(fields)
+    
+    def buildRecord(self, fields, count=None):
+        
+        from twistedcaldav.directory.augment import AugmentRecord
+
+        def expandCount(value, count):
+            
+            if type(value) in types.StringTypes:
+                return value % (count,) if count and "%" in value else value
+            elif type(value) == set:
+                return set([item % (count,) if count and "%" in item else item for item in value])
+            else:
+                return value
+        
+        actualFields = {}
+        for k,v in fields.iteritems():
+            actualFields[ELEMENT_AUGMENTRECORD_MAP[k]] = expandCount(v, count)
+
+        record = AugmentRecord(**actualFields)
+        self.items[record.guid] = record

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/xmlfile.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/xmlfile.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/directory/xmlfile.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2009 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.
@@ -26,6 +26,7 @@
 from twisted.web2.auth.digest import DigestedCredentials
 from twisted.python.filepath import FilePath
 
+from twistedcaldav.directory import augment
 from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
 from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser
 
@@ -61,23 +62,39 @@
 
     def listRecords(self, recordType):
         for entryShortName, xmlPrincipal in self._entriesForRecordType(recordType):
-            yield XMLDirectoryRecord(
+            record = XMLDirectoryRecord(
                 service       = self,
                 recordType    = recordType,
                 shortName     = entryShortName,
                 xmlPrincipal  = xmlPrincipal,
             )
 
+            # Look up augment information
+            # TODO: this needs to be deferred but for now we hard code the deferred result because
+            # we know it is completing immediately.
+            d = augment.AugmentService.getAugmentRecord(record.guid)
+            d.addCallback(lambda x:record.addAugmentInformation(x))
+
+            yield record
+
     def recordWithShortName(self, recordType, shortName):
         for entryShortName, xmlprincipal in self._entriesForRecordType(recordType):
             if entryShortName == shortName:
-                return XMLDirectoryRecord(
+                record = XMLDirectoryRecord(
                     service       = self,
                     recordType    = recordType,
                     shortName     = entryShortName,
                     xmlPrincipal  = xmlprincipal,
                 )
 
+                # Look up augment information
+                # TODO: this needs to be deferred but for now we hard code the deferred result because
+                # we know it is completing immediately.
+                d = augment.AugmentService.getAugmentRecord(record.guid)
+                d.addCallback(lambda x:record.addAugmentInformation(x))
+
+                return record
+
         return None
 
     def _entriesForRecordType(self, recordType):
@@ -108,18 +125,12 @@
             guid                  = xmlPrincipal.guid,
             shortName             = shortName,
             fullName              = xmlPrincipal.name,
-            calendarUserAddresses = xmlPrincipal.calendarUserAddresses,
-            autoSchedule          = xmlPrincipal.autoSchedule,
-            enabledForCalendaring = xmlPrincipal.enabledForCalendaring,
+            emailAddresses        = xmlPrincipal.emailAddresses,
         )
 
         self.password          = xmlPrincipal.password
         self._members          = xmlPrincipal.members
         self._groups           = xmlPrincipal.groups
-        self._proxies          = xmlPrincipal.proxies
-        self._proxyFor         = xmlPrincipal.proxyFor
-        self._readOnlyProxies  = xmlPrincipal.readOnlyProxies
-        self._readOnlyProxyFor = xmlPrincipal.readOnlyProxyFor
 
     def members(self):
         for recordType, shortName in self._members:
@@ -129,26 +140,11 @@
         for shortName in self._groups:
             yield self.service.recordWithShortName(DirectoryService.recordType_groups, shortName)
 
-    def proxies(self):
-        for recordType, shortName in self._proxies:
-            yield self.service.recordWithShortName(recordType, shortName)
-
-    def proxyFor(self, read_write=True):
-        for recordType, shortName in self._proxyFor:
-            yield self.service.recordWithShortName(recordType, shortName)
-
-    def readOnlyProxies(self):
-        for recordType, shortName in self._readOnlyProxies:
-            yield self.service.recordWithShortName(recordType, shortName)
-
-    def readOnlyProxyFor(self, read_write=True):
-        for recordType, shortName in self._readOnlyProxyFor:
-            yield self.service.recordWithShortName(recordType, shortName)
-
     def verifyCredentials(self, credentials):
-        if isinstance(credentials, UsernamePassword):
-            return credentials.password == self.password
-        if isinstance(credentials, DigestedCredentials):
-            return credentials.checkPassword(self.password)
+        if self.enabled:
+            if isinstance(credentials, UsernamePassword):
+                return credentials.password == self.password
+            if isinstance(credentials, DigestedCredentials):
+                return credentials.checkPassword(self.password)
 
         return super(XMLDirectoryRecord, self).verifyCredentials(credentials)

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/log.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/log.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/log.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2009 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.
@@ -224,8 +224,10 @@
     #
     # Attach methods to Logger
     #
-    def log_emit(self, message, level=level, **kwargs):
+    def log_emit(self, message, level=level, raiseException=None, **kwargs):
         self.emit(level, message, **kwargs)
+        if raiseException:
+            raise raiseException(message)
 
     log_emit.__doc__ = doc
 
@@ -234,8 +236,10 @@
     #
     # Attach methods to LoggingMixIn
     #
-    def log_emit(self, message, level=level, **kwargs):
+    def log_emit(self, message, level=level, raiseException=None, **kwargs):
         self.logger.emit(level, message, **kwargs)
+        if raiseException:
+            raise raiseException(message)
 
     log_emit.__doc__ = doc
 

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/method/report_common.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/method/report_common.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -284,7 +284,7 @@
 _namedPropertiesForResource = deferredGenerator(_namedPropertiesForResource)
     
 def generateFreeBusyInfo(request, calresource, fbinfo, timerange, matchtotal,
-                         excludeuid=None, organizer=None, same_calendar_user=False):
+                         excludeuid=None, organizer=None, organizerPrincipal=None, same_calendar_user=False):
     """
     Run a free busy report on the specified calendar collection
     accumulating the free busy info for later processing.
@@ -303,7 +303,7 @@
     
     # First check the privilege on this collection
     try:
-        d = waitForDeferred(calresource.checkPrivileges(request, (caldavxml.ReadFreeBusy(),)))
+        d = waitForDeferred(calresource.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), principal=organizerPrincipal))
         yield d
         d.getResult()
     except AccessDeniedError:
@@ -354,7 +354,7 @@
         child = child.getResult()
 
         try:
-            d = waitForDeferred(child.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), inherited_aces=filteredaces))
+            d = waitForDeferred(child.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), inherited_aces=filteredaces, principal=organizerPrincipal))
             yield d
             d.getResult()
         except AccessDeniedError:

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/partitions.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/partitions.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/partitions.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,56 @@
+##
+# Copyright (c) 2009 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.
+##
+
+from twistedcaldav.log import Logger
+from twistedcaldav.py.plistlib import readPlist
+
+"""
+Collection of classes for managing partition information for a group of servers.
+"""
+
+log = Logger()
+
+class Partitions(object):
+
+    def __init__(self):
+        
+        self.clear()
+
+    def clear(self):
+        self.partitions = {}
+        self.ownUID = ""
+
+    def readConfig(self, plistpath):
+        try:
+            dataDict = readPlist(plistpath)
+        except (IOError, OSError):                                    
+            log.error("Configuration file does not exist or is inaccessible: %s" % (self._configFileName,))
+            return
+        
+        for partition in dataDict.get("partitions", ()):
+            uid = partition.get("uid", None)
+            url = partition.get("url", None)
+            if uid and url:
+                self.partitions[uid] = url
+
+    def setSelfPartition(self, uid):
+        self.ownUID = uid
+
+    def getPartitionURL(self, uid):
+        # When the UID matches this server return an empty string
+        return self.partitions.get(uid, None) if uid != self.ownUID else ""
+
+partitions = Partitions()

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/sql.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/sql.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -172,7 +172,7 @@
             self._db_init_data_tables(q)
             self._db_recreate()
 
-        q.execute("commit")
+        #q.execute("commit")
         self._db_connection.isolation_level = old_isolation
 
     def _db_init_schema_table(self, q):

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/static.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/static.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -37,6 +37,7 @@
 import os
 import errno
 from urlparse import urlsplit
+import urlparse
 
 from twisted.internet.defer import fail, succeed, inlineCallbacks, returnValue
 from twisted.python.failure import Failure
@@ -48,7 +49,8 @@
 from twisted.web2.dav.idav import IDAVResource
 from twisted.web2.dav.resource import AccessDeniedError
 from twisted.web2.dav.resource import davPrivilegeSet
-from twisted.web2.dav.util import parentForURL, bindMethods
+from twisted.web2.dav.util import parentForURL, bindMethods, joinURL
+from twisted.web2.resource import RedirectResource
 
 from twistedcaldav import caldavxml
 from twistedcaldav import customxml
@@ -597,60 +599,78 @@
 
         assert len(name) > 4, "Directory record has an invalid GUID: %r" % (name,)
         
-        childPath = self.fp.child(name[0:2]).child(name[2:4]).child(name)
-        child = self.homeResourceClass(childPath.path, self, record)
-
-        if not child.exists():
-            self.provision()
-
-            if not childPath.parent().isdir():
-                childPath.parent().makedirs()
-
-            for oldPath in (
-                # Pre 2.0: All in one directory
-                self.fp.child(name),
-                # Pre 1.2: In types hierarchy instead of the GUID hierarchy
-                self.parent.getChild(record.recordType).fp.child(record.shortName),
-            ):
-                if oldPath.exists():
-                    # The child exists at an old location.  Move to new location.
-                    log.msg("Moving calendar home from old location %r to new location %r." % (oldPath, childPath))
-                    try:
-                        oldPath.moveTo(childPath)
-                    except (OSError, IOError), e:
-                        log.err("Error moving calendar home %r: %s" % (oldPath, e))
+        if record.locallyHosted():
+            childPath = self.fp.child(name[0:2]).child(name[2:4]).child(name)
+            child = self.homeResourceClass(childPath.path, self, record)
+    
+            if not child.exists():
+                self.provision()
+    
+                if not childPath.parent().isdir():
+                    childPath.parent().makedirs()
+    
+                for oldPath in (
+                    # Pre 2.0: All in one directory
+                    self.fp.child(name),
+                    # Pre 1.2: In types hierarchy instead of the GUID hierarchy
+                    self.parent.getChild(record.recordType).fp.child(record.shortName),
+                ):
+                    if oldPath.exists():
+                        # The child exists at an old location.  Move to new location.
+                        log.msg("Moving calendar home from old location %r to new location %r." % (oldPath, childPath))
+                        try:
+                            oldPath.moveTo(childPath)
+                        except (OSError, IOError), e:
+                            log.err("Error moving calendar home %r: %s" % (oldPath, e))
+                            raise HTTPError(StatusResponse(
+                                responsecode.INTERNAL_SERVER_ERROR,
+                                "Unable to move calendar home."
+                            ))
+                        child.fp.restat(False)
+                        break
+                else:
+                    #
+                    # NOTE: provisionDefaultCalendars() returns a deferred, which we are ignoring.
+                    # The result being that the default calendars will be present at some point
+                    # in the future, not necessarily right now, and we don't have a way to wait
+                    # on that to finish.
+                    #
+                    child.provisionDefaultCalendars()
+    
+                    #
+                    # Try to work around the above a little by telling the client that something
+                    # when wrong temporarily if the child isn't provisioned right away.
+                    #
+                    if not child.exists():
                         raise HTTPError(StatusResponse(
-                            responsecode.INTERNAL_SERVER_ERROR,
-                            "Unable to move calendar home."
+                            responsecode.SERVICE_UNAVAILABLE,
+                            "Provisioning calendar home."
                         ))
-                    child.fp.restat(False)
-                    break
-            else:
-                #
-                # NOTE: provisionDefaultCalendars() returns a deferred, which we are ignoring.
-                # The result being that the default calendars will be present at some point
-                # in the future, not necessarily right now, and we don't have a way to wait
-                # on that to finish.
-                #
-                child.provisionDefaultCalendars()
+    
+                assert child.exists()
+        
+        else:
+            childPath = self.fp.child(name[0:2]).child(name[2:4]).child(name)
+            child = CalendarHomeRedirectFile(childPath.path, self, record)
 
-                #
-                # Try to work around the above a little by telling the client that something
-                # when wrong temporarily if the child isn't provisioned right away.
-                #
-                if not child.exists():
-                    raise HTTPError(StatusResponse(
-                        responsecode.SERVICE_UNAVAILABLE,
-                        "Provisioning calendar home."
-                    ))
-
-            assert child.exists()
-
         return child
 
     def createSimilarFile(self, path):
         raise HTTPError(responsecode.NOT_FOUND)
 
+class CalendarHomeRedirectFile(RedirectResource):
+    
+    def __init__(self, path, parent, record):
+        self.path = path
+        self.parent = parent
+        self.record = record
+        
+        parsedURL = urlparse.urlparse(self.record.hostedURL())
+        super(CalendarHomeRedirectFile, self).__init__(scheme=parsedURL.scheme, host=parsedURL.hostname, port=parsedURL.port)
+    
+    def url(self):
+        return joinURL(self.parent.url(), self.record.guid)
+
 class CalendarHomeFile (PropfindCacheMixin, AutoProvisioningFileMixIn, DirectoryCalendarHomeResource, CalDAVFile):
     """
     Calendar home collection resource.

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/tap.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/tap.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/tap.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -19,6 +19,7 @@
 
 from zope.interface import implements
 
+from twisted.internet import reactor
 from twisted.internet.address import IPv4Address
 
 from twisted.python.log import FileLogObserver
@@ -45,6 +46,8 @@
 from twistedcaldav.config import config, parseConfig, defaultConfig, ConfigurationError
 from twistedcaldav.root import RootResource
 from twistedcaldav.resource import CalDAVResource
+from twistedcaldav.directory import augment
+from twistedcaldav.directory.calendaruserproxyloader import XMLCalendarUserProxyLoader
 from twistedcaldav.directory.digest import QopDigestCredentialFactory
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
 from twistedcaldav.directory.aggregate import AggregateDirectoryService
@@ -446,6 +449,19 @@
         setLogLevelForNamespace(None, "info")
 
         #
+        # Setup the Augment Service
+        #
+        augmentClass = namedClass(config.AugmentService.type)
+
+        log.info("Configuring augment service of type: %s" % (augmentClass,))
+
+        try:
+            augment.AugmentService = augmentClass(**config.AugmentService.params)
+        except IOError, e:
+            log.error("Could not start augment service")
+            raise
+
+        #
         # Setup the Directory
         #
         directories = []
@@ -481,6 +497,16 @@
                 SudoDirectoryService.recordType_sudoers)
 
         #
+        # Make sure proxies get initialized
+        #
+        if config.ProxyLoadFromFile:
+            def _doProxyUpdate():
+                loader = XMLCalendarUserProxyLoader(config.ProxyLoadFromFile)
+                return loader.updateProxyDB()
+            
+            reactor.addSystemEventTrigger("before", "startup", _doProxyUpdate)
+
+        #
         # Configure Memcached Client Pool
         #
         if config.Memcached["ClientEnabled"]:

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_cache.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_cache.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_cache.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -18,7 +18,6 @@
 import hashlib
 import cPickle
 
-from twisted.trial.unittest import TestCase
 from twisted.internet.defer import succeed, maybeDeferred
 
 from twisted.web2.dav import davxml
@@ -31,6 +30,7 @@
 from twistedcaldav.cache import PropfindCacheMixin
 
 from twistedcaldav.test.util import InMemoryMemcacheProtocol
+from twistedcaldav.test.util import TestCase
 
 
 def _newCacheToken(self):
@@ -87,6 +87,7 @@
 
 class MemCacheChangeNotifierTests(TestCase):
     def setUp(self):
+        TestCase.setUp(self)
         self.memcache = InMemoryMemcacheProtocol()
         self.ccn = MemcacheChangeNotifier(
             StubURLResource(':memory:'),
@@ -438,6 +439,7 @@
     Test the PropfindCacheMixin
     """
     def setUp(self):
+        TestCase.setUp(self)
         self.resource = TestCachingResource(StubResponse(200, {}, "foobar"))
         self.responseCache = StubResponseCacheResource()
 

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_config.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_config.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_config.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -158,7 +158,6 @@
         self.assertEquals(config.DirectoryService.type, "twistedcaldav.directory.appleopendirectory.OpenDirectoryService")
         self.assertNotIn("xmlFile", config.DirectoryService.params)
         self.assertEquals(config.DirectoryService.params.node, "/Search")
-        self.assertEquals(config.DirectoryService.params.requireComputerRecord, True)
         self.assertEquals(config.DirectoryService.params.cacheTimeout, 30)
 
     def testDirectoryService_newParam(self):
@@ -167,13 +166,11 @@
 
         config.update({"DirectoryService": {"type": "twistedcaldav.directory.appleopendirectory.OpenDirectoryService"}})
         config.update({"DirectoryService": {"params": {
-            "requireComputerRecord": False,
             "cacheTimeout": 12345,
         }}})
 
         self.assertEquals(config.DirectoryService.type, "twistedcaldav.directory.appleopendirectory.OpenDirectoryService")
         self.assertEquals(config.DirectoryService.params.node, "/Search")
-        self.assertEquals(config.DirectoryService.params.requireComputerRecord, False)
         self.assertEquals(config.DirectoryService.params.cacheTimeout, 12345)
 
     def testDirectoryService_unknownType(self):

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_database.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_database.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_database.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -0,0 +1,208 @@
+##
+# Copyright (c) 2009 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.
+##
+
+from twistedcaldav.database import AbstractADBAPIDatabase
+import twistedcaldav.test.util
+
+from twisted.internet.defer import inlineCallbacks
+
+import os
+import time
+
+class Database (twistedcaldav.test.util.TestCase):
+    """
+    Test abstract SQL DB class
+    """
+    
+    class TestDB(AbstractADBAPIDatabase):
+        
+        def __init__(self, path, persistent=False, version="1"):
+            self.version = version
+            self.dbpath = path
+            super(Database.TestDB, self).__init__("sqlite", "sqlite3", (path,), persistent, cp_min=3, cp_max=3)
+
+        def _db_version(self):
+            """
+            @return: the schema version assigned to this index.
+            """
+            return self.version
+            
+        def _db_type(self):
+            """
+            @return: the collection type assigned to this index.
+            """
+            return "TESTTYPE"
+            
+        def _db_init_data_tables(self):
+            """
+            Initialise the underlying database tables.
+            @param q:           a database cursor to use.
+            """
+    
+            #
+            # TESTTYPE table
+            #
+            return self._db_execute(
+                """
+                create table TESTTYPE (
+                    KEY         text unique,
+                    VALUE       text
+                )
+                """
+            )
+
+        def _db_remove_data_tables(self):
+            return self._db_execute("drop table TESTTYPE")
+
+    class TestDBRecreateUpgrade(TestDB):
+        
+        class RecreateDBException(Exception):
+            pass
+        class UpgradeDBException(Exception):
+            pass
+
+        def __init__(self, path, persistent=False):
+            super(Database.TestDBRecreateUpgrade, self).__init__(path, persistent, version="2")
+
+        def _db_recreate(self):
+            raise self.RecreateDBException()
+
+    class TestDBCreateIndexOnUpgrade(TestDB):
+        
+        def __init__(self, path, persistent=False):
+            super(Database.TestDBCreateIndexOnUpgrade, self).__init__(path, persistent, version="2")
+
+        def _db_upgrade_data_tables(self, old_version):
+            return self._db_execute(
+                """
+                create index TESTING on TESTTYPE (VALUE)
+                """
+            )
+
+    class TestDBPauseInInit(TestDB):
+        
+        def _db_init(self):
+            
+            time.sleep(1)
+            super(Database.TestDBPauseInInit, self)._db_init()
+
+    @inlineCallbacks
+    def inlineCallbackRaises(self, exc, f, *args, **kwargs):
+        try:
+            yield f(*args, **kwargs)
+        except exc:
+            pass
+        except Exception, e:
+            self.fail("Wrong exception raised: %s" % (e,))
+        else:
+            self.fail("%s not raised" % (exc,))
+
+    @inlineCallbacks
+    def test_connect(self):
+        """
+        Connect to database and create table
+        """
+        db = Database.TestDB(self.mktemp())
+        self.assertFalse(db.initialized)
+        yield db.open()
+        self.assertTrue(db.initialized)
+
+    @inlineCallbacks
+    def test_readwrite(self):
+        """
+        Add a record, search for it
+        """
+        db = Database.TestDB(self.mktemp())
+        yield db.execute("INSERT into TESTTYPE (KEY, VALUE) values (:1, :2)", ("FOO", "BAR",))
+        items = (yield db.query("SELECT * from TESTTYPE"))
+        self.assertEqual(items, (("FOO", "BAR"),))
+        items = (yield db.queryList("SELECT * from TESTTYPE"))
+        self.assertEqual(items, ("FOO",))
+
+    @inlineCallbacks
+    def test_close(self):
+        """
+        Close database
+        """
+        db = Database.TestDB(self.mktemp())
+        self.assertFalse(db.initialized)
+        yield db.open()
+        db.close()
+        self.assertFalse(db.initialized)
+        db.close()
+        
+    @inlineCallbacks
+    def test_version_upgrade_nonpersistent(self):
+        """
+        Connect to database and create table
+        """
+        
+        db_file = self.mktemp()
+
+        db = Database.TestDB(db_file)
+        yield db.open()
+        yield db.execute("INSERT into TESTTYPE (KEY, VALUE) values (:1, :2)", ("FOO", "BAR",))
+        items = (yield db.query("SELECT * from TESTTYPE"))
+        self.assertEqual(items, (("FOO", "BAR"),))
+        db.close()
+        db = None
+
+        db = Database.TestDBRecreateUpgrade(db_file)
+        yield self.inlineCallbackRaises(Database.TestDBRecreateUpgrade.RecreateDBException, db.open)
+        items = (yield db.query("SELECT * from TESTTYPE"))
+        self.assertEqual(items, ())
+
+    def test_version_upgrade_persistent(self):
+        """
+        Connect to database and create table
+        """
+        db_file = self.mktemp()
+        db = Database.TestDB(db_file, persistent=True)
+        yield db.open()
+        yield db.execute("INSERT into TESTTYPE (KEY, VALUE) values (:1, :2)", "FOO", "BAR")
+        items = (yield db.query("SELECT * from TESTTYPE"))
+        self.assertEqual(items, (("FOO", "BAR")))
+        db.close()
+        db = None
+
+        db = Database.TestDBRecreateUpgrade(db_file, persistent=True)
+        yield self.inlineCallbackRaises(NotImplementedError, db.open)
+        self.assertTrue(os.path.exists(db_file))
+        db.close()
+        db = None
+
+        db = Database.TestDB(db_file, persistent=True, autocommit=True)
+        yield db.open()
+        items = (yield db.query("SELECT * from TESTTYPE"))
+        self.assertEqual(items, (("FOO", "BAR")))
+
+    def test_version_upgrade_persistent_add_index(self):
+        """
+        Connect to database and create table
+        """
+        db_file = self.mktemp()
+        db = Database.TestDB(db_file, persistent=True, autocommit=True)
+        yield db.open()
+        yield db.execute("INSERT into TESTTYPE (KEY, VALUE) values (:1, :2)", "FOO", "BAR")
+        items = (yield db.query("SELECT * from TESTTYPE"))
+        self.assertEqual(items, (("FOO", "BAR")))
+        db.close()
+        db = None
+
+        db = Database.TestDBCreateIndexOnUpgrade(db_file, persistent=True, autocommit=True)
+        yield db.open()
+        items = (yield db.query("SELECT * from TESTTYPE"))
+        self.assertEqual(items, (("FOO", "BAR")))

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_log.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_log.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_log.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -17,7 +17,7 @@
 from twistedcaldav.log import *
 from twistedcaldav.log import logLevelsByNamespace
 
-import twisted.trial.unittest
+from twistedcaldav.test.util import TestCase
 
 defaultLogLevel = logLevelsByNamespace[None]
 
@@ -37,7 +37,7 @@
 class LoggingEnabledObject (LoggingMixIn):
     pass
 
-class Logging (twisted.trial.unittest.TestCase):
+class Logging (TestCase):
     def test_cmpLogLevels(self):
         self.assertEquals(cmpLogLevels("info" , "error"), -1)
         self.assertEquals(cmpLogLevels("debug", "debug"),  0)

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_memcache.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_memcache.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_memcache.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -7,14 +7,12 @@
 
 from twistedcaldav.memcache import MemCacheProtocol, NoSuchCommand
 from twistedcaldav.memcache import ClientError, ServerError
+from twistedcaldav.test.util import TestCase
 
-from twisted.trial.unittest import TestCase
 from twisted.test.proto_helpers import StringTransportWithDisconnection
 from twisted.internet.task import Clock
 from twisted.internet.defer import Deferred, gatherResults, TimeoutError
 
-
-
 class MemCacheTestCase(TestCase):
     """
     Test client protocol class L{MemCacheProtocol}.
@@ -25,6 +23,7 @@
         Create a memcache client, connect it to a string protocol, and make it
         use a deterministic clock.
         """
+        TestCase.setUp(self)
         self.proto = MemCacheProtocol()
         self.clock = Clock()
         self.proto.callLater = self.clock.callLater

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_memcachepool.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_memcachepool.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_memcachepool.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -16,8 +16,6 @@
 
 from zope.interface import implements
 
-from twisted.trial.unittest import TestCase
-
 from twisted.internet.interfaces import IConnector, IReactorTCP
 from twisted.internet.address import IPv4Address
 
@@ -25,8 +23,8 @@
 from twistedcaldav.memcachepool import PooledMemCacheProtocol
 from twistedcaldav.memcachepool import MemCacheClientFactory
 from twistedcaldav.memcachepool import MemCachePool
+from twistedcaldav.test.util import TestCase
 
-
 MC_ADDRESS = IPv4Address('TCP', '127.0.0.1', 11211)
 
 
@@ -137,6 +135,7 @@
         Create a L{MemCacheClientFactory} instance and and give it a
         L{StubConnectionPool} instance.
         """
+        TestCase.setUp(self)
         self.pool = StubConnectionPool()
         self.factory = MemCacheClientFactory()
         self.factory.connectionPool = self.pool
@@ -196,6 +195,7 @@
         """
         Create a L{MemCachePool}.
         """
+        TestCase.setUp(self)
         self.reactor = StubReactor()
         self.pool = MemCachePool(MC_ADDRESS,
                                  maxClients=5,

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_memcacher.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_memcacher.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_memcacher.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -6,10 +6,10 @@
 """
 
 from twisted.internet.defer import inlineCallbacks
-from twisted.trial.unittest import TestCase
 
 from twistedcaldav.config import config
 from twistedcaldav.memcacher import Memcacher
+from twistedcaldav.test.util import TestCase
 
 class MemcacherTestCase(TestCase):
     """

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_resource.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_resource.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -14,13 +14,11 @@
 # limitations under the License.
 ##
 
-from twisted.trial.unittest import TestCase
-
 from twistedcaldav.resource import CalDAVResource
 
 from twistedcaldav.test.util import InMemoryPropertyStore
+from twistedcaldav.test.util import TestCase
 
-
 class StubProperty(object):
     def qname(self):
         return "StubQname"
@@ -28,6 +26,7 @@
 
 class CalDAVResourceTests(TestCase):
     def setUp(self):
+        TestCase.setUp(self)
         self.resource = CalDAVResource()
         self.resource._dead_properties = InMemoryPropertyStore()
 

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_root.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_root.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_root.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -18,9 +18,10 @@
 
 from twistedcaldav.root import RootResource
 from twistedcaldav.test.util import TestCase
+from twistedcaldav.directory import augment
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
 from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.directory.test.test_xmlfile import xmlFile
+from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
 
 from twisted.cred.portal import Portal
 
@@ -52,6 +53,7 @@
 
 class RootTests(TestCase):
     def setUp(self):
+        TestCase.setUp(self)
         self.docroot = self.mktemp()
         os.mkdir(self.docroot)
 
@@ -59,6 +61,7 @@
                 'calendar': ['dreid']})
 
         directory = XMLDirectoryService(xmlFile)
+        augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
 
         principals = DirectoryPrincipalProvisioningResource('/principals/', directory)
 

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_static.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_static.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -14,11 +14,10 @@
 # limitations under the License.
 ##
 
-from twisted.trial.unittest import TestCase
-
 from twistedcaldav.static import CalendarHomeFile, CalDAVFile
 from twistedcaldav.cache import DisabledCacheNotifier
 from twistedcaldav.test.util import StubCacheChangeNotifier
+from twistedcaldav.test.util import TestCase
 
 class StubParentResource(object):
     def principalCollections(self):
@@ -27,6 +26,7 @@
 
 class CalendarHomeFileTests(TestCase):
     def setUp(self):
+        TestCase.setUp(self)
         self.calendarHome = CalendarHomeFile(self.mktemp(),
                                              StubParentResource(),
                                              object())
@@ -44,6 +44,7 @@
 
 class CalDAVFileTests(TestCase):
     def setUp(self):
+        TestCase.setUp(self)
         self.caldavFile = CalDAVFile(self.mktemp())
         self.caldavFile.fp.createDirectory()
         self.caldavFile.cacheNotifier = StubCacheChangeNotifier()

Modified: CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_tap.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_tap.py	2009-09-08 19:12:31 UTC (rev 4528)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4524/twistedcaldav/test/test_tap.py	2009-09-08 20:38:33 UTC (rev 4529)
@@ -17,8 +17,6 @@
 import os
 from copy import deepcopy
 
-from twisted.trial import unittest
-
 from twisted.python.usage import Options, UsageError
 from twisted.python.util import sibpath
 from twisted.python.reflect import namedAny
@@ -39,6 +37,7 @@
 from twistedcaldav.directory.sudo import SudoDirectoryService
 from twistedcaldav.directory.directory import UnknownRecordTypeError
 
+from twistedcaldav.test.util import TestCase
 
 class TestCalDAVOptions(CalDAVOptions):
     """
@@ -53,7 +52,7 @@
         pass
 
 
-class CalDAVOptionsTest(unittest.TestCase):
+class CalDAVOptionsTest(TestCase):
     """
     Test various parameters of our usage.Options subclass
     """
@@ -63,6 +62,7 @@
         Set up our options object, giving it a parent, and forcing the
         global config to be loaded from defaults.
         """
+        TestCase.setUp(self)
         self.config = TestCalDAVOptions()
         self.config.parent = Options()
         self.config.parent['uid'] = 0
@@ -163,7 +163,7 @@
 
         self.assertEquals(config.MultiProcess['ProcessCount'], 102)
 
-class BaseServiceMakerTests(unittest.TestCase):
+class BaseServiceMakerTests(TestCase):
     """
     Utility class for ServiceMaker tests.
     """
@@ -171,6 +171,7 @@
     configOptions = None
 
     def setUp(self):
+        TestCase.setUp(self)
         self.options = TestCalDAVOptions()
         self.options.parent = Options()
         self.options.parent['gid'] = None
@@ -181,12 +182,19 @@
 
         accountsFile = sibpath(os.path.dirname(__file__),
                                'directory/test/accounts.xml')
+        augmentsFile = sibpath(os.path.dirname(__file__),
+                               'directory/test/augments.xml')
 
         self.config['DirectoryService'] = {
             'params': {'xmlFile': accountsFile},
             'type': 'twistedcaldav.directory.xmlfile.XMLDirectoryService'
             }
 
+        self.config["AugmentService"] = {
+            "params": {"xmlFiles": [augmentsFile]},
+            "type": "twistedcaldav.directory.augment.AugmentXMLDB"
+        }
+
         self.config['DocumentRoot'] = self.mktemp()
         self.config['DataRoot'] = self.mktemp()
         self.config['ProcessType'] = 'Slave'
@@ -195,6 +203,9 @@
 
         self.config['SudoersFile'] = ''
 
+        self.config['Memcached']['ClientEnabled'] = False
+        self.config['Memcached']['ServerEnabled'] = False
+
         if self.configOptions:
             config_mod._mergeData(self.config, self.configOptions)
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20090908/dc1c5329/attachment-0001.html>


More information about the calendarserver-changes mailing list