[CalendarServer-changes] [5105] CalendarServer/branches/users/glyph/use-system-twisted

source_changes at macosforge.org source_changes at macosforge.org
Fri Feb 12 14:49:11 PST 2010


Revision: 5105
          http://trac.macosforge.org/projects/calendarserver/changeset/5105
Author:   glyph at apple.com
Date:     2010-02-12 14:49:09 -0800 (Fri, 12 Feb 2010)
Log Message:
-----------
Integrate changes from trunk.

Modified Paths:
--------------
    CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_load_augmentdb
    CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_make_partition
    CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_manage_augments
    CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tap/caldav.py
    CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/gateway.py
    CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/loadaugmentdb.py
    CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/manageaugments.py
    CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/managepostgres.py
    CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/principals.py
    CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/test/gateway/augments.xml
    CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/test/test_gateway.py
    CalendarServer/branches/users/glyph/use-system-twisted/conf/auth/augments-default.xml
    CalendarServer/branches/users/glyph/use-system-twisted/conf/auth/augments-test.xml
    CalendarServer/branches/users/glyph/use-system-twisted/conf/auth/augments.dtd
    CalendarServer/branches/users/glyph/use-system-twisted/conf/caldavd-apple.plist
    CalendarServer/branches/users/glyph/use-system-twisted/setup.py
    CalendarServer/branches/users/glyph/use-system-twisted/support/Makefile.Apple
    CalendarServer/branches/users/glyph/use-system-twisted/support/submit
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/database.py
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/aggregate.py
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/augment.py
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/cachingdirectory.py
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/calendaruserproxy.py
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/directory.py
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/augments-test-default.xml
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/augments-test.xml
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/augments.xml
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/modify/augments.xml
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/resources/augments.xml
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_augment.py
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_cachedirectory.py
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_modify.py
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_xmlfile.py
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/xmlaccountsparser.py
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/xmlaugmentsparser.py
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/xmlfile.py
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/mail.py

Added Paths:
-----------
    CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_manage_postgres
    CalendarServer/branches/users/glyph/use-system-twisted/conf/servertoserver.xml
    CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/xmlutil.py

Property Changed:
----------------
    CalendarServer/branches/users/glyph/use-system-twisted/


Property changes on: CalendarServer/branches/users/glyph/use-system-twisted
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
   + /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/trunk:5084-5104

Modified: CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_load_augmentdb
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_load_augmentdb	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_load_augmentdb	2010-02-12 22:49:09 UTC (rev 5105)
@@ -32,6 +32,8 @@
         child = Popen((run, "-p"), stdout=PIPE)
         path, stderr = child.communicate()
 
+        path = path.rstrip("\n")
+
         if child.wait() == 0:
             sys.path[0:0] = path.split(":")
 

Modified: CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_make_partition
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_make_partition	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_make_partition	2010-02-12 22:49:09 UTC (rev 5105)
@@ -32,6 +32,8 @@
         child = Popen((run, "-p"), stdout=PIPE)
         path, stderr = child.communicate()
 
+        path = path.rstrip("\n")
+
         if child.wait() == 0:
             sys.path[0:0] = path.split(":")
 

Modified: CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_manage_augments
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_manage_augments	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_manage_augments	2010-02-12 22:49:09 UTC (rev 5105)
@@ -20,6 +20,7 @@
 #PYTHONPATH
 
 if __name__ == "__main__":
+
     if "PYTHONPATH" in globals():
         sys.path.insert(0, PYTHONPATH)
     else:
@@ -30,8 +31,10 @@
         run = join(home, "run")
 
         child = Popen((run, "-p"), stdout=PIPE)
-        path, stderr = child.communicate()
+        path, _ignore_stderr = child.communicate()
 
+        path = path.rstrip("\n")
+
         if child.wait() == 0:
             sys.path[0:0] = path.split(":")
 

Copied: CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_manage_postgres (from rev 5104, CalendarServer/trunk/bin/calendarserver_manage_postgres)
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_manage_postgres	                        (rev 0)
+++ CalendarServer/branches/users/glyph/use-system-twisted/bin/calendarserver_manage_postgres	2010-02-12 22:49:09 UTC (rev 5105)
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import sys
+
+#PYTHONPATH
+
+if __name__ == "__main__":
+
+    if "PYTHONPATH" in globals():
+        sys.path.insert(0, PYTHONPATH)
+    else:
+        from os.path import dirname, abspath, join
+        from subprocess import Popen, PIPE
+
+        home = dirname(dirname(abspath(__file__)))
+        run = join(home, "run")
+
+        child = Popen((run, "-p"), stdout=PIPE)
+        path, _ignore_stderr = child.communicate()
+
+        path = path.rstrip("\n")
+
+        if child.wait() == 0:
+            sys.path[0:0] = path.split(":")
+
+    from calendarserver.tools.managepostgres import main
+    main()

Modified: CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tap/caldav.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tap/caldav.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -647,14 +647,9 @@
                 credentialFactories.append(credFactory)
 
 
-        # Set up a digest credential factory for use on the /inbox iMIP
+        # Set up a basic credential factory for use on the /inbox iMIP
         # injection resource
-        schemeConfig = config.Authentication.Digest
-        digestCredentialFactory = QopDigestCredentialFactory(
-            schemeConfig["Algorithm"],
-            schemeConfig["Qop"],
-            realm,
-        )
+        inboxCredentialFactory = BasicCredentialFactory(realm)
 
         #
         # Setup Resource hierarchy
@@ -728,7 +723,7 @@
                           % (self.imipResourceClass,))
 
             # The authenticationWrapper below will be configured to always
-            # allow digest auth on /inbox
+            # allow basic auth on /inbox
             root.putChild("inbox", self.imipResourceClass(root))
 
         #
@@ -772,7 +767,7 @@
             credentialFactories,
             (auth.IPrincipal,),
             overrides = {
-                "/inbox" : (digestCredentialFactory,),
+                "/inbox" : (inboxCredentialFactory,),
             }
         )
 

Modified: CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/gateway.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/gateway.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/gateway.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -93,11 +93,13 @@
         try:
             config.directory = getDirectory()
         except DirectoryError, e:
-            abort(e)
+            respondWithError(str(e))
+            return
         setupMemcached(config)
         setupNotifications(config)
     except ConfigurationError, e:
-        abort(e)
+        respondWithError(e)
+        return
 
     #
     # Read commands from stdin
@@ -106,7 +108,8 @@
     try:
         plist = plistlib.readPlistFromString(rawInput)
     except xml.parsers.expat.ExpatError, e:
-        abort(str(e))
+        respondWithError(str(e))
+        return
 
     # If the plist is an array, each element of the array is a separate
     # command dictionary.
@@ -116,7 +119,8 @@
         commands = [plist]
 
     runner = Runner(config.directory, commands)
-    runner.validate()
+    if not runner.validate():
+        return
 
     #
     # Start the reactor
@@ -125,6 +129,24 @@
     reactor.run()
 
 
+attrMap = {
+    'GeneratedUID' : { 'attr' : 'guid', },
+    'RealName' : { 'attr' : 'fullName', },
+    'RecordName' : { 'attr' : 'shortNames', },
+    'Comment' : { 'extras' : True, 'attr' : 'comment', },
+    'Description' : { 'extras' : True, 'attr' : 'description', },
+    'Type' : { 'extras' : True, 'attr' : 'type', },
+    'Capacity' : { 'extras' : True, 'attr' : 'capacity', },
+    'Building' : { 'extras' : True, 'attr' : 'building', },
+    'Floor' : { 'extras' : True, 'attr' : 'floor', },
+    'Street' : { 'extras' : True, 'attr' : 'street', },
+    'City' : { 'extras' : True, 'attr' : 'city', },
+    'State' : { 'extras' : True, 'attr' : 'state', },
+    'ZIP' : { 'extras' : True, 'attr' : 'zip', },
+    'Country' : { 'extras' : True, 'attr' : 'country', },
+    'Phone' : { 'extras' : True, 'attr' : 'phone', },
+}
+
 class Runner(object):
 
     def __init__(self, directory, commands):
@@ -135,24 +157,33 @@
         # Make sure commands are valid
         for command in self.commands:
             if not command.has_key('command'):
-                abort("'command' missing from plist")
+                respondWithError("'command' missing from plist")
+                return False
             commandName = command['command']
             methodName = "command_%s" % (commandName,)
             if not hasattr(self, methodName):
-                abort("Unknown command '%s'" % (commandName,))
+                respondWithError("Unknown command '%s'" % (commandName,))
+                return False
+        return True
 
     @inlineCallbacks
     def run(self):
-        for command in self.commands:
-            commandName = command['command']
-            methodName = "command_%s" % (commandName,)
-            if hasattr(self, methodName):
-                (yield getattr(self, methodName)(command))
-            else:
-                abort("Unknown command '%s'" % (commandName,))
+        try:
+            for command in self.commands:
+                commandName = command['command']
+                methodName = "command_%s" % (commandName,)
+                if hasattr(self, methodName):
+                    (yield getattr(self, methodName)(command))
+                else:
+                    respondWithError("Unknown command '%s'" % (commandName,))
 
-        reactor.stop()
+        except Exception, e:
+            respondWithError("Command failed: '%s'" % (str(e),))
+            raise
 
+        finally:
+            reactor.stop()
+
     # Locations
 
     def command_getLocationList(self, command):
@@ -160,18 +191,53 @@
 
     def command_createLocation(self, command):
 
+        kwargs = {}
+        for key, info in attrMap.iteritems():
+            if command.has_key(key):
+                kwargs[info['attr']] = command[key]
         try:
-            self.dir.createRecord("locations", guid=command['GeneratedUID'],
-                shortNames=command['RecordName'], fullName=command['RealName'])
+            self.dir.createRecord("locations", **kwargs)
         except DirectoryError, e:
-            abort(str(e))
+            respondWithError(str(e))
+            return
         respondWithRecordsOfType(self.dir, command, "locations")
 
+    def command_getLocationAttributes(self, command):
+        guid = command['GeneratedUID']
+        record = self.dir.recordWithGUID(guid)
+        recordDict = recordToDict(record)
+        # principal = principalForPrincipalID(guid, directory=self.dir)
+        # recordDict['AutoSchedule'] = principal.getAutoSchedule()
+        respond(command, recordDict)
+
+    def command_setLocationAttributes(self, command):
+
+        kwargs = {}
+        for key, info in attrMap.iteritems():
+            if command.has_key(key):
+                kwargs[info['attr']] = command[key]
+        try:
+            self.dir.updateRecord("locations", **kwargs)
+        except DirectoryError, e:
+            respondWithError(str(e))
+            return
+
+        # principal = principalForPrincipalID(command['GeneratedUID'],
+        #     directory=self.dir)
+        # principal.setAutoSchedule(command.get('AutoSchedule', False))
+
+        self.command_getLocationAttributes(command)
+
     def command_deleteLocation(self, command):
+        kwargs = {}
+        for key, info in attrMap.iteritems():
+            if command.has_key(key):
+                kwargs[info['attr']] = command[key]
         try:
-            self.dir.destroyRecord("locations", guid=command['GeneratedUID'])
+            self.dir.destroyRecord("locations", **kwargs)
         except DirectoryError, e:
-            abort(str(e))
+            respondWithError(str(e))
+            return
         respondWithRecordsOfType(self.dir, command, "locations")
 
     # Resources
@@ -180,18 +246,53 @@
         respondWithRecordsOfType(self.dir, command, "resources")
 
     def command_createResource(self, command):
+        kwargs = {}
+        for key, info in attrMap.iteritems():
+            if command.has_key(key):
+                kwargs[info['attr']] = command[key]
         try:
-            self.dir.createRecord("resources", guid=command['GeneratedUID'],
-                shortNames=command['RecordName'], fullName=command['RealName'])
+            self.dir.createRecord("resources", **kwargs)
         except DirectoryError, e:
-            abort(str(e))
+            respondWithError(str(e))
+            return
         respondWithRecordsOfType(self.dir, command, "resources")
 
+    def command_getResourceAttributes(self, command):
+        guid = command['GeneratedUID']
+        record = self.dir.recordWithGUID(guid)
+        recordDict = recordToDict(record)
+        # principal = principalForPrincipalID(guid, directory=self.dir)
+        # recordDict['AutoSchedule'] = principal.getAutoSchedule()
+        respond(command, recordDict)
+
+    def command_setResourceAttributes(self, command):
+
+        kwargs = {}
+        for key, info in attrMap.iteritems():
+            if command.has_key(key):
+                kwargs[info['attr']] = command[key]
+        try:
+            self.dir.updateRecord("resources", **kwargs)
+        except DirectoryError, e:
+            respondWithError(str(e))
+            return
+
+        # principal = principalForPrincipalID(command['GeneratedUID'],
+        #     directory=self.dir)
+        # principal.setAutoSchedule(command.get('AutoSchedule', False))
+
+        self.command_getResourceAttributes(command)
+
     def command_deleteResource(self, command):
+        kwargs = {}
+        for key, info in attrMap.iteritems():
+            if command.has_key(key):
+                kwargs[info['attr']] = command[key]
         try:
-            self.dir.destroyRecord("resources", guid=command['GeneratedUID'])
+            self.dir.destroyRecord("resources", **kwargs)
         except DirectoryError, e:
-            abort(str(e))
+            respondWithError(str(e))
+            return
         respondWithRecordsOfType(self.dir, command, "resources")
 
     # Proxies
@@ -199,16 +300,28 @@
     @inlineCallbacks
     def command_listWriteProxies(self, command):
         principal = principalForPrincipalID(command['Principal'], directory=self.dir)
+        if principal is None:
+            respondWithError("Principal not found: %s" % (command['Principal'],))
+            return
         (yield respondWithProxies(self.dir, command, principal, "write"))
 
     @inlineCallbacks
     def command_addWriteProxy(self, command):
-        principal = principalForPrincipalID(command['Principal'], directory=self.dir)
+        principal = principalForPrincipalID(command['Principal'],
+            directory=self.dir)
+        if principal is None:
+            respondWithError("Principal not found: %s" % (command['Principal'],))
+            return
+
         proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
+        if proxy is None:
+            respondWithError("Proxy not found: %s" % (command['Proxy'],))
+            return
         try:
             (yield addProxy(principal, "write", proxy))
         except ProxyError, e:
-            abort(str(e))
+            respondWithError(str(e))
+            return
         except ProxyWarning, e:
             pass
         (yield respondWithProxies(self.dir, command, principal, "write"))
@@ -216,11 +329,18 @@
     @inlineCallbacks
     def command_removeWriteProxy(self, command):
         principal = principalForPrincipalID(command['Principal'], directory=self.dir)
+        if principal is None:
+            respondWithError("Principal not found: %s" % (command['Principal'],))
+            return
         proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
+        if proxy is None:
+            respondWithError("Proxy not found: %s" % (command['Proxy'],))
+            return
         try:
             (yield removeProxy(principal, proxy, proxyTypes=("write",)))
         except ProxyError, e:
-            abort(str(e))
+            respondWithError(str(e))
+            return
         except ProxyWarning, e:
             pass
         (yield respondWithProxies(self.dir, command, principal, "write"))
@@ -228,16 +348,26 @@
     @inlineCallbacks
     def command_listReadProxies(self, command):
         principal = principalForPrincipalID(command['Principal'], directory=self.dir)
+        if principal is None:
+            respondWithError("Principal not found: %s" % (command['Principal'],))
+            return
         (yield respondWithProxies(self.dir, command, principal, "read"))
 
     @inlineCallbacks
     def command_addReadProxy(self, command):
         principal = principalForPrincipalID(command['Principal'], directory=self.dir)
+        if principal is None:
+            respondWithError("Principal not found: %s" % (command['Principal'],))
+            return
         proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
+        if proxy is None:
+            respondWithError("Proxy not found: %s" % (command['Proxy'],))
+            return
         try:
             (yield addProxy(principal, "read", proxy))
         except ProxyError, e:
-            abort(str(e))
+            respondWithError(str(e))
+            return
         except ProxyWarning, e:
             pass
         (yield respondWithProxies(self.dir, command, principal, "read"))
@@ -245,11 +375,18 @@
     @inlineCallbacks
     def command_removeReadProxy(self, command):
         principal = principalForPrincipalID(command['Principal'], directory=self.dir)
+        if principal is None:
+            respondWithError("Principal not found: %s" % (command['Principal'],))
+            return
         proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
+        if proxy is None:
+            respondWithError("Proxy not found: %s" % (command['Proxy'],))
+            return
         try:
             (yield removeProxy(principal, proxy, proxyTypes=("read",)))
         except ProxyError, e:
-            abort(str(e))
+            respondWithError(str(e))
+            return
         except ProxyWarning, e:
             pass
         (yield respondWithProxies(self.dir, command, principal, "read"))
@@ -271,27 +408,38 @@
     })
 
 
+def recordToDict(record):
+    recordDict = {}
+    for key, info in attrMap.iteritems():
+        try:
+            if info.get('extras', False):
+                value = record.extras[info['attr']]
+            else:
+                value = getattr(record, info['attr'])
+            recordDict[key] = value
+        except KeyError:
+            pass
+    return recordDict
+
 def respondWithRecordsOfType(directory, command, recordType):
     result = []
     for record in directory.listRecords(recordType):
-        result.append( {
-            'GeneratedUID' : record.guid,
-            'RecordName' : [n for n in record.shortNames],
-            'RealName' : record.fullName,
-            'AutoSchedule' : record.autoSchedule,
-        } )
+        recordDict = recordToDict(record)
+        result.append(recordDict)
     respond(command, result)
 
 def respond(command, result):
     sys.stdout.write(plistlib.writePlistToString( { 'command' : command['command'], 'result' : result } ) )
 
-def abort(msg, status=1):
+def respondWithError(msg, status=1):
     sys.stdout.write(plistlib.writePlistToString( { 'error' : msg, } ) )
+    """
     try:
         reactor.stop()
     except RuntimeError:
         pass
     sys.exit(status)
+    """
 
 if __name__ == "__main__":
     main()

Modified: CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/loadaugmentdb.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/loadaugmentdb.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/loadaugmentdb.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -160,23 +160,30 @@
 def run(dbxml):
     
     try:
-        guids = set((yield augment.AugmentService.getAllGUIDs()))
+        uids = set((yield augment.AugmentService.getAllUIDs()))
         added = 0
         updated = 0
         removed = 0
         if dbxml:
+            add_records = list()
+            modify_records = list()
             for record in dbxml.db.values():
-                yield augment.AugmentService.addAugmentRecord(record, record.guid in guids)
-                if record.guid in guids:
-                    updated += 1
+                if record.uid in uids:
+                    modify_records.append(record)
                 else:
-                    added += 1
-            for guid in guids.difference(dbxml.db.keys()):
-                yield augment.AugmentService.removeAugmentRecord(guid)
-                removed += 1
+                    add_records.append(record)
+            yield augment.AugmentService.addAugmentRecords(add_records, False)
+            added = len(add_records)
+
+            yield augment.AugmentService.addAugmentRecords(modify_records, True)
+            updated = len(modify_records)
+
+            remove_uids = uids.difference(dbxml.db.keys())
+            yield augment.AugmentService.removeAugmentRecords(remove_uids)
+            removed = len(remove_uids)
         else:
             yield augment.AugmentService.clean()
-            removed = len(guids)
+            removed = len(uids)
             
         print "Changes:"
         print "  Added: %d" % (added,)

Modified: CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/manageaugments.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/manageaugments.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/manageaugments.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -15,145 +15,26 @@
 # limitations under the License.
 ##
 
+from calendarserver.tools.loadaugmentdb import StandardIOObserver
+from calendarserver.tools.util import loadConfig, getDirectory,\
+    autoDisableMemcached
+from grp import getgrnam
 from optparse import OptionParser
-from twistedcaldav.directory import xmlaugmentsparser
-from xml.etree.ElementTree import ElementTree, tostring, SubElement
-from xml.parsers.expat import ExpatError
-import sys
+from pwd import getpwnam
+from twext.log import setLogLevelForNamespace
+from twisted.internet import reactor
+from twisted.python.util import switchUID
+from twistedcaldav.config import config, ConfigurationError
+from twistedcaldav.directory import augment
+from twistedcaldav.directory.augment import AugmentRecord
 import os
+import sys
+from twisted.internet.defer import inlineCallbacks
 
 def error(s):
     print s
     sys.exit(1)
 
-def readXML(xmlfile):
-
-    # Read in XML
-    try:
-        tree = ElementTree(file=xmlfile)
-    except ExpatError, e:
-        error("Unable to parse file '%s' because: %s" % (xmlfile, e,))
-
-    # Verify that top-level element is correct
-    augments_node = tree.getroot()
-    if augments_node.tag != xmlaugmentsparser.ELEMENT_AUGMENTS:
-        error("Ignoring file '%s' because it is not a augments file" % (xmlfile,))
-
-    return augments_node
-
-def writeXML(xmlfile, root):
-    
-    data = """<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE augments SYSTEM "augments.dtd">
-
-""" + tostring(root)
-
-    with open(xmlfile, "w") as f:
-        f.write(data)
-
-def addSubElement(parent, tag, text=None, indent=0):
-    
-    child = SubElement(parent, tag)
-    child.text = text
-    child.tail = "\n" + " " * indent
-    return child
-
-def changeSubElementText(parent, tag, text):
-    
-    child = parent.find(tag)
-    child.text = text
-
-def doAdd(xmlfile, guid, host, enable_calendar, auto_schedule):
-
-    augments_node = readXML(xmlfile)
-
-    # Make sure GUID is not already present
-    for child in augments_node.getchildren():
-        
-        if child.tag != xmlaugmentsparser.ELEMENT_RECORD:
-            error("Unknown augment type: '%s' in augment file: '%s'" % (child.tag, xmlfile,))
-
-        for node in child.getchildren():
-            
-            if node.tag == xmlaugmentsparser.ELEMENT_GUID and node.text == guid:
-                error("Cannot add guid '%s' because it already exists in augment file: '%s'" % (guid, xmlfile,))
-    
-    # Create new record
-    if len(augments_node.getchildren()):
-        augments_node.getchildren()[-1].tail = "\n  "
-    record = addSubElement(augments_node, xmlaugmentsparser.ELEMENT_RECORD, "\n    ")
-    addSubElement(record, xmlaugmentsparser.ELEMENT_GUID, guid, 4)
-    addSubElement(record, xmlaugmentsparser.ELEMENT_ENABLE, "true", 4)
-    addSubElement(record, xmlaugmentsparser.ELEMENT_HOSTEDAT, host, 4)
-    addSubElement(record, xmlaugmentsparser.ELEMENT_ENABLECALENDAR, "true" if enable_calendar else "false", 4)
-    addSubElement(record, xmlaugmentsparser.ELEMENT_AUTOSCHEDULE, "true" if auto_schedule else "false", 2)
-    
-    # Modify xmlfile
-    writeXML(xmlfile, augments_node)
-    print "Added guid '%s' in augment file: '%s'" % (guid, xmlfile,)
-    
-def doModify(xmlfile, guid, host, enable_calendar, auto_schedule):
-
-    augments_node = readXML(xmlfile)
-
-    # Make sure GUID is present
-    for child in augments_node.getchildren():
-        
-        if child.tag != xmlaugmentsparser.ELEMENT_RECORD:
-            error("Unknown augment type: '%s' in augment file: '%s'" % (child.tag, xmlfile,))
-
-        for node in child.getchildren():
-            
-            if node.tag == xmlaugmentsparser.ELEMENT_GUID and node.text == guid:
-                break
-        else:
-            continue
-        break
-    else:
-        error("Cannot modify guid '%s' because it does not exist in augment file: '%s'" % (guid, xmlfile,))
-    
-    # Modify record
-    if host is not None:
-        child.find(xmlaugmentsparser.ELEMENT_HOSTEDAT).text = host
-    child.find(xmlaugmentsparser.ELEMENT_ENABLECALENDAR).text = "true" if enable_calendar else "false"
-    child.find(xmlaugmentsparser.ELEMENT_AUTOSCHEDULE).text = "true" if auto_schedule else "false"
-    
-    # Modify xmlfile
-    writeXML(xmlfile, augments_node)
-    print "Modified guid '%s' in augment file: '%s'" % (guid, xmlfile,)
-
-def doRemove(xmlfile, guid):
-
-    augments_node = readXML(xmlfile)
-
-    # Make sure GUID is present
-    for child in augments_node.getchildren():
-        
-        if child.tag != xmlaugmentsparser.ELEMENT_RECORD:
-            error("Unknown augment type: '%s' in augment file: '%s'" % (child.tag, xmlfile,))
-
-        for node in child.getchildren():
-            
-            if node.tag == xmlaugmentsparser.ELEMENT_GUID and node.text == guid:
-                break
-        else:
-            continue
-        augments_node.remove(child)
-        break
-    else:
-        error("Cannot remove guid '%s' because it does not exist in augment file: '%s'" % (guid, xmlfile,))
-    
-    # Modify xmlfile
-    writeXML(xmlfile, augments_node)
-    print "Removed guid '%s' from augment file: '%s'" % (guid, xmlfile,)
-    
-def doPrint(xmlfile):
-
-    # Read in XML
-    augments_node = readXML(xmlfile)
-
-    print tostring(augments_node)
-
 def main():
 
     usage = "%prog [options] ACTION"
@@ -163,7 +44,6 @@
   add:    add a user record
   modify: modify a user record
   remove: remove a user record
-  print:  print all user records
 """
     description = "Tool to manipulate CalendarServer augments XML file"
     version = "%prog v1.0"
@@ -171,47 +51,97 @@
     parser.epilog = epilog
     parser.format_epilog = lambda _:epilog
 
-    parser.add_option("-f", "--file", dest="xmlfilename",
-                      help="XML augment file to manipulate", metavar="FILE")
-    parser.add_option("-g", "--guid", dest="guid",
-                      help="OD GUID to manipulate", metavar="GUID")
-    parser.add_option("-i", "--guidfile", dest="guidfile",
-                      help="File containing a list of GUIDs to manipulate", metavar="GUIDFILE")
+    parser.add_option("-f", "--file", dest="configfilename",
+                      help="caldavd.plist defining Augment Service", metavar="FILE")
+    parser.add_option("-u", "--uid", dest="uid",
+                      help="OD GUID to manipulate", metavar="UID")
+    parser.add_option("-i", "--uidfile", dest="uidfile",
+                      help="File containing a list of GUIDs to manipulate", metavar="UIDFILE")
     parser.add_option("-n", "--node", dest="node",
-                      help="Partition node to assign to GUID", metavar="NODE")
+                      help="Partition node to assign to UID", metavar="NODE")
     parser.add_option("-c", "--enable-calendar", action="store_true", dest="enable_calendar",
-                      default=True, help="Enable calendaring for this GUID: %default")
-    parser.add_option("-a", "--auto-schedule", action="store_true", dest="auto_schedule",
-                      default=False, help="Enable auto-schedule for this GUID: %default")
+                      default=True, help="Enable calendaring for this UID: %default")
+    parser.add_option("-a", "--enable-addressbooks", action="store_true", dest="enable_addressbook",
+                      default=True, help="Enable calendaring for this UID: %default")
+    parser.add_option("-s", "--auto-schedule", action="store_true", dest="auto_schedule",
+                      default=False, help="Enable auto-schedule for this UID: %default")
 
     (options, args) = parser.parse_args()
 
     if len(args) != 1:
         parser.error("incorrect number of arguments")
 
-    guids = []
-    if options.guid:
-        guids.append(options.guid)
-    elif options.guidfile:
-        if not os.path.exists(options.guidfile):
-            parser.error("File containing list of GUIDs does not exist")
-        with open(options.guidfile) as f:
-            for line in f:
-                guids.append(line[:-1])
-        
-    if args[0] == "add":
-        if not options.node:
-            parser.error("Partition node must be specified when adding")
-        for guid in guids:
-            doAdd(options.xmlfilename, guid, options.node, options.enable_calendar, options.auto_schedule)
-    elif args[0] == "modify":
-        for guid in guids:
-            doModify(options.xmlfilename, guid, options.node, options.enable_calendar, options.auto_schedule)
-    elif args[0] == "remove":
-        for guid in guids:
-            doRemove(options.xmlfilename, guid)
-    elif args[0] == "print":
-        doPrint(options.xmlfilename)
+    observer = StandardIOObserver()
+    observer.start()
 
+    #
+    # Get configuration
+    #
+    try:
+        loadConfig(options.configfilename)
+        setLogLevelForNamespace(None, "warn")
+
+        # Shed privileges
+        if config.UserName and config.GroupName and os.getuid() == 0:
+            uid = getpwnam(config.UserName).pw_uid
+            gid = getgrnam(config.GroupName).gr_gid
+            switchUID(uid, uid, gid)
+
+        os.umask(config.umask)
+
+        config.directory = getDirectory()
+        autoDisableMemcached(config)
+    except ConfigurationError, e:
+        usage("Unable to start: %s" % (e,))
+
+    #
+    # Start the reactor
+    #
+    reactor.callLater(0, run, parser, options, args)
+    reactor.run()
+
+def makeRecord(uid, options):
+    return AugmentRecord(
+        uid = uid,
+        enabled = True,
+        hostedAt = options.node,
+        enabledForCalendaring = options.enable_calendar,
+        enabledForAddressBooks = options.enable_addressbook,
+        autoSchedule = options.auto_schedule,
+    )
+
+ at inlineCallbacks
+def run(parser, options, args):
+    
+    try:
+        uids = []
+        if options.uid:
+            uids.append(options.uid)
+        elif options.uidfile:
+            if not os.path.exists(options.uidfile):
+                parser.error("File containing list of UIDs does not exist")
+            with open(options.uidfile) as f:
+                for line in f:
+                    uids.append(line[:-1])
+            
+        if args[0] == "add":
+            if not options.node:
+                parser.error("Partition node must be specified when adding")
+            yield augment.AugmentService.addAugmentRecords([makeRecord(uid, options) for uid in uids], False)
+            for uid in uids:
+                print "Added uid '%s' to augment database" % (uid,)
+        elif args[0] == "modify":
+            yield augment.AugmentService.addAugmentRecords([makeRecord(uid, options) for uid in uids], True)
+            for uid in uids:
+                print "Modified uid '%s' in augment database" % (uid,)
+        elif args[0] == "remove":
+            yield augment.AugmentService.removeAugmentRecords(uids)
+            for uid in uids:
+                print "Removed uid '%s' from augment database" % (uid,)
+        else:
+            parser.error("Unknown argument")
+    finally:
+        reactor.stop()
+
 if __name__ == '__main__':
     main()

Modified: CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/managepostgres.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/managepostgres.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/managepostgres.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -69,9 +69,9 @@
 
 def doClean(basedir):
     
-    cmd("rm -rf %s/data" % (basedir, basedir,))
+    cmd("rm -rf %s/data" % (basedir,))
 
-if __name__ == '__main__':
+def main():
 
     usage = "%prog [options] ACTION"
     epilog = """
@@ -93,7 +93,7 @@
     parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
                       default=True, help="Use debug logging for PostgreSQL")
     parser.add_option("-d", "--base-dir", dest="basedir",
-                      default="%s/../postgresql-8.4.1/_root" % (os.getcwd(),), help="Base directory for PostgreSQL install")
+                      default="%s/../postgresql-8.4.2/_root" % (os.getcwd(),), help="Base directory for PostgreSQL install")
 
     (options, args) = parser.parse_args()
 
@@ -112,3 +112,6 @@
         doClean(options.basedir)
     else:
         parser.error("incorrect argument '%s'" % (args[0],))
+
+if __name__ == '__main__':
+    main()

Modified: CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/principals.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/principals.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -334,7 +334,7 @@
         if checkOnly:
             return None
 
-        return directory.principalCollection.principalForUID(str(guid))
+        return directory.principalCollection.principalForUID(principalID)
     except ValueError:
         pass
 

Modified: CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/test/gateway/augments.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/test/gateway/augments.xml	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/test/gateway/augments.xml	2010-02-12 22:49:09 UTC (rev 5105)
@@ -20,17 +20,17 @@
 
 <augments>
   <record>
-    <guid>user01</guid>
+    <uid>user01</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
   </record>
   <record>
-    <guid>user02</guid>
+    <uid>user02</uid>
     <enable>false</enable>
     <enable-calendar>false</enable-calendar>
   </record>
   <record>
-    <guid>location01</guid>
+    <uid>location01</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
   </record>

Modified: CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/test/test_gateway.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/test/test_gateway.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/calendarserver/tools/test/test_gateway.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -185,27 +185,81 @@
         self.assertEquals(len(results['result']), 10)
 
     @inlineCallbacks
+    def test_getLocationAttributes(self):
+        results = yield self.runCommand(command_createLocation)
+        results = yield self.runCommand(command_getLocationAttributes)
+        self.assertEquals(results['result']['Building'], "Test Building")
+        self.assertEquals(results['result']['City'], "Cupertino")
+        self.assertEquals(results['result']['Capacity'], "40")
+        self.assertEquals(results['result']['Description'], "Test Description")
+        self.assertEquals(results['result']['ZIP'], "95014")
+        self.assertEquals(results['result']['Floor'], "First")
+        self.assertEquals(results['result']['RecordName'], ['createdlocation01'])
+        self.assertEquals(results['result']['State'], "CA")
+        self.assertEquals(results['result']['Street'], "1 Infinite Loop")
+        self.assertEquals(results['result']['RealName'], "Created Location 01")
+        self.assertEquals(results['result']['Comment'], "Test Comment")
+
+    @inlineCallbacks
     def test_getResourceList(self):
         results = yield self.runCommand(command_getResourceList)
         self.assertEquals(len(results['result']), 10)
 
     @inlineCallbacks
+    def test_getResourceAttributes(self):
+        results = yield self.runCommand(command_createResource)
+        results = yield self.runCommand(command_getResourceAttributes)
+        self.assertEquals(results['result']['Comment'], "Test Comment")
+
+    @inlineCallbacks
     def test_createLocation(self):
         directory = getDirectory()
 
-        record = directory.recordWithUID("createdlocation01")
+        record = directory.recordWithUID("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
         self.assertEquals(record, None)
 
         yield self.runCommand(command_createLocation)
 
         directory.flushCaches()
-        record = directory.recordWithUID("createdlocation01")
+        record = directory.recordWithUID("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
         self.assertNotEquals(record, None)
 
+        self.assertEquals(record.extras['comment'], "Test Comment")
+        self.assertEquals(record.extras['building'], "Test Building")
+        self.assertEquals(record.extras['floor'], "First")
+        self.assertEquals(record.extras['capacity'], "40")
+        self.assertEquals(record.extras['street'], "1 Infinite Loop")
+        self.assertEquals(record.extras['city'], "Cupertino")
+        self.assertEquals(record.extras['state'], "CA")
+        self.assertEquals(record.extras['zip'], "95014")
+        self.assertEquals(record.extras['country'], "USA")
+        self.assertEquals(record.extras['phone'], "(408) 555-1212")
+
     @inlineCallbacks
-    def test_destroyRecord(self):
+    def test_setLocationAttributes(self):
         directory = getDirectory()
 
+        yield self.runCommand(command_createLocation)
+        record = directory.recordWithUID("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
+        yield self.runCommand(command_setLocationAttributes)
+        directory.flushCaches()
+        record = directory.recordWithUID("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
+
+        self.assertEquals(record.extras['comment'], "Updated Test Comment")
+        self.assertEquals(record.extras['building'], "Updated Test Building")
+        self.assertEquals(record.extras['floor'], "Second")
+        self.assertEquals(record.extras['capacity'], "41")
+        self.assertEquals(record.extras['street'], "2 Infinite Loop")
+        self.assertEquals(record.extras['city'], "Updated Cupertino")
+        self.assertEquals(record.extras['state'], "Updated CA")
+        self.assertEquals(record.extras['zip'], "95015")
+        self.assertEquals(record.extras['country'], "Updated USA")
+        self.assertEquals(record.extras['phone'], "(408) 555-1213")
+
+    @inlineCallbacks
+    def test_destroyLocation(self):
+        directory = getDirectory()
+
         record = directory.recordWithUID("location01")
         self.assertNotEquals(record, None)
 
@@ -216,6 +270,46 @@
         self.assertEquals(record, None)
 
     @inlineCallbacks
+    def test_createResource(self):
+        directory = getDirectory()
+
+        record = directory.recordWithUID("AF575A61-CFA6-49E1-A0F6-B5662C9D9801")
+        self.assertEquals(record, None)
+
+        yield self.runCommand(command_createResource)
+
+        directory.flushCaches()
+        record = directory.recordWithUID("AF575A61-CFA6-49E1-A0F6-B5662C9D9801")
+        self.assertNotEquals(record, None)
+
+    @inlineCallbacks
+    def test_setResourceAttributes(self):
+        directory = getDirectory()
+
+        yield self.runCommand(command_createResource)
+        record = directory.recordWithUID("AF575A61-CFA6-49E1-A0F6-B5662C9D9801")
+        self.assertEquals(record.fullName, "Laptop 1")
+
+        yield self.runCommand(command_setResourceAttributes)
+
+        directory.flushCaches()
+        record = directory.recordWithUID("AF575A61-CFA6-49E1-A0F6-B5662C9D9801")
+        self.assertEquals(record.fullName, "Updated Laptop 1")
+
+    @inlineCallbacks
+    def test_destroyResource(self):
+        directory = getDirectory()
+
+        record = directory.recordWithUID("resource01")
+        self.assertNotEquals(record, None)
+
+        yield self.runCommand(command_deleteResource)
+
+        directory.flushCaches()
+        record = directory.recordWithUID("resource01")
+        self.assertEquals(record, None)
+
+    @inlineCallbacks
     def test_addWriteProxy(self):
         results = yield self.runCommand(command_addWriteProxy)
         self.assertEquals(len(results['result']['Proxies']), 1)
@@ -228,18 +322,6 @@
 
 
 
-command_deleteLocation = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-        <key>command</key>
-        <string>deleteLocation</string>
-        <key>GeneratedUID</key>
-        <string>guidoffice3</string>
-</dict>
-</plist>
-"""
-
 command_addReadProxy = """<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
@@ -277,17 +359,40 @@
         <key>AutoSchedule</key>
         <true/>
         <key>GeneratedUID</key>
-        <string>createdlocation01</string>
+        <string>836B1B66-2E9A-4F46-8B1C-3DD6772C20B2</string>
         <key>RealName</key>
         <string>Created Location 01</string>
         <key>RecordName</key>
         <array>
                 <string>createdlocation01</string>
         </array>
+        <key>Comment</key>
+        <string>Test Comment</string>
+        <key>Description</key>
+        <string>Test Description</string>
+        <key>Building</key>
+        <string>Test Building</string>
+        <key>Floor</key>
+        <string>First</string>
+        <key>Capacity</key>
+        <string>40</string>
+        <key>Street</key>
+        <string>1 Infinite Loop</string>
+        <key>City</key>
+        <string>Cupertino</string>
+        <key>State</key>
+        <string>CA</string>
+        <key>ZIP</key>
+        <string>95014</string>
+        <key>Country</key>
+        <string>USA</string>
+        <key>Phone</key>
+        <string>(408) 555-1212</string>
 </dict>
 </plist>
 """
 
+
 command_createResource = """<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
@@ -297,13 +402,15 @@
         <key>AutoSchedule</key>
         <true/>
         <key>GeneratedUID</key>
-        <string>guidlaptop1</string>
+        <string>AF575A61-CFA6-49E1-A0F6-B5662C9D9801</string>
         <key>RealName</key>
         <string>Laptop 1</string>
         <key>RecordName</key>
         <array>
                 <string>laptop1</string>
         </array>
+        <key>Comment</key>
+        <string>Test Comment</string>
 </dict>
 </plist>
 """
@@ -327,7 +434,7 @@
         <key>command</key>
         <string>deleteResource</string>
         <key>GeneratedUID</key>
-        <string>guidlaptop1</string>
+        <string>resource01</string>
 </dict>
 </plist>
 """
@@ -403,3 +510,89 @@
 </dict>
 </plist>
 """
+
+command_setLocationAttributes = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>setLocationAttributes</string>
+        <key>AutoSchedule</key>
+        <false/>
+        <key>GeneratedUID</key>
+        <string>836B1B66-2E9A-4F46-8B1C-3DD6772C20B2</string>
+        <key>RealName</key>
+        <string>Updated Location 01</string>
+        <key>RecordName</key>
+        <array>
+                <string>createdlocation01</string>
+        </array>
+        <key>Comment</key>
+        <string>Updated Test Comment</string>
+        <key>Description</key>
+        <string>Updated Test Description</string>
+        <key>Building</key>
+        <string>Updated Test Building</string>
+        <key>Floor</key>
+        <string>Second</string>
+        <key>Capacity</key>
+        <string>41</string>
+        <key>Street</key>
+        <string>2 Infinite Loop</string>
+        <key>City</key>
+        <string>Updated Cupertino</string>
+        <key>State</key>
+        <string>Updated CA</string>
+        <key>ZIP</key>
+        <string>95015</string>
+        <key>Country</key>
+        <string>Updated USA</string>
+        <key>Phone</key>
+        <string>(408) 555-1213</string>
+</dict>
+</plist>
+"""
+
+command_getLocationAttributes = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>getLocationAttributes</string>
+        <key>GeneratedUID</key>
+        <string>836B1B66-2E9A-4F46-8B1C-3DD6772C20B2</string>
+</dict>
+</plist>
+"""
+
+command_setResourceAttributes = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>setResourceAttributes</string>
+        <key>AutoSchedule</key>
+        <false/>
+        <key>GeneratedUID</key>
+        <string>AF575A61-CFA6-49E1-A0F6-B5662C9D9801</string>
+        <key>RealName</key>
+        <string>Updated Laptop 1</string>
+        <key>RecordName</key>
+        <array>
+                <string>laptop1</string>
+        </array>
+</dict>
+</plist>
+"""
+
+command_getResourceAttributes = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>command</key>
+        <string>getResourceAttributes</string>
+        <key>GeneratedUID</key>
+        <string>AF575A61-CFA6-49E1-A0F6-B5662C9D9801</string>
+</dict>
+</plist>
+"""

Modified: CalendarServer/branches/users/glyph/use-system-twisted/conf/auth/augments-default.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/conf/auth/augments-default.xml	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/conf/auth/augments-default.xml	2010-02-12 22:49:09 UTC (rev 5105)
@@ -20,8 +20,9 @@
 
 <augments>
   <record>
-    <guid>Default</guid>
+    <uid>Default</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
 </augments>

Modified: CalendarServer/branches/users/glyph/use-system-twisted/conf/auth/augments-test.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/conf/auth/augments-test.xml	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/conf/auth/augments-test.xml	2010-02-12 22:49:09 UTC (rev 5105)
@@ -20,31 +20,31 @@
 
 <augments>
   <record>
-    <guid>Default</guid>
+    <uid>Default</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
     <enable-addressbook>true</enable-addressbook>
   </record>
   <record repeat="10">
-    <guid>location%02d</guid>
+    <uid>location%02d</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
     <enable-addressbook>true</enable-addressbook>
     <auto-schedule>true</auto-schedule>
   </record>
   <record repeat="10">
-    <guid>resource%02d</guid>
+    <uid>resource%02d</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
     <enable-addressbook>true</enable-addressbook>
     <auto-schedule>true</auto-schedule>
   </record>
   <record repeat="4">
-    <guid>group%02d</guid>
+    <uid>group%02d</uid>
     <enable>true</enable>
   </record>
   <record>
-    <guid>disabledgroup</guid>
+    <uid>disabledgroup</uid>
     <enable>false</enable>
   </record>
 </augments>

Modified: CalendarServer/branches/users/glyph/use-system-twisted/conf/auth/augments.dtd
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/conf/auth/augments.dtd	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/conf/auth/augments.dtd	2010-02-12 22:49:09 UTC (rev 5105)
@@ -16,12 +16,13 @@
 
 <!ELEMENT augments (record*) >
 
-  <!ELEMENT record (guid, enable, hosted-at?, enable-calendar?, auto-schedule?)>
+  <!ELEMENT record (guid, enable, hosted-at?, enable-calendar?, enable-addressbook?, auto-schedule?)>
     <!ATTLIST record repeat CDATA "1">
 
-  <!ELEMENT guid              (#PCDATA)>
-  <!ELEMENT enable            (#PCDATA)>
-  <!ELEMENT hosted-at         (#PCDATA)>
-  <!ELEMENT enable-calendar   (#PCDATA)>
-  <!ELEMENT auto-schedule     (#PCDATA)>
+  <!ELEMENT uid                (#PCDATA)>
+  <!ELEMENT enable             (#PCDATA)>
+  <!ELEMENT hosted-at          (#PCDATA)>
+  <!ELEMENT enable-calendar    (#PCDATA)>
+  <!ELEMENT enable-addressbook (#PCDATA)>
+  <!ELEMENT auto-schedule      (#PCDATA)>
 >

Modified: CalendarServer/branches/users/glyph/use-system-twisted/conf/caldavd-apple.plist
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/conf/caldavd-apple.plist	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/conf/caldavd-apple.plist	2010-02-12 22:49:09 UTC (rev 5105)
@@ -149,10 +149,6 @@
       <dict>
         <key>node</key>
         <string>/Search</string>
-        <key>restrictEnabledRecords</key>
-        <false/>
-        <key>restrictToGroup</key>
-        <string></string>
         <key>cacheTimeout</key>
         <integer>30</integer>
       </dict>

Copied: CalendarServer/branches/users/glyph/use-system-twisted/conf/servertoserver.xml (from rev 5104, CalendarServer/trunk/conf/servertoserver.xml)
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/conf/servertoserver.xml	                        (rev 0)
+++ CalendarServer/branches/users/glyph/use-system-twisted/conf/servertoserver.xml	2010-02-12 22:49:09 UTC (rev 5105)
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+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.
+ -->
+
+<!DOCTYPE servers SYSTEM "servertoserver.dtd">
+
+<servers>
+  <!--
+  <server>
+    <uri>https://localhost:8543/inbox</uri>
+    <allow-requests-from/>
+    <allow-requests-to/>
+    <domains>
+    	<domain>example.org</domain>
+    </domains>
+    <hosts>
+    	<host>127.0.0.1</host>
+    </hosts>
+  </server>
+  -->
+</servers>

Modified: CalendarServer/branches/users/glyph/use-system-twisted/setup.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/setup.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/setup.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 ##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -46,9 +46,9 @@
 # Options
 #
 
-description = "CardDAV protocol extensions to twext.web2.dav",
+description = "CalDAV/CardDAV protocol extensions to twext.web2.dav",
 long_description = """
-Extends twext.web2.dav to implement CardDAV-aware resources and methods.
+Extends twisted.web2.dav to implement CalDAV/CardDAV-aware resources and methods.
 """
 
 classifiers = None
@@ -83,88 +83,91 @@
 # Run setup
 #
 
-from distutils.core import setup
+if __name__ == "__main__":
+    from distutils.core import setup
 
-dist = setup(
-    name             = "twistedcaldav",
-    version          = version_string,
-    description      = description,
-    long_description = long_description,
-    url              = None,
-    classifiers      = classifiers,
-    author           = "Apple Inc.",
-    author_email     = None,
-    license          = None,
-    platforms        = [ "all" ],
-    packages         = find_modules(),
-    package_data     = {
-                         "twistedcaldav": [
-                           "zoneinfo/*.ics",
-                           "zoneinfo/*/*.ics",
-                           "zoneinfo/*/*/*.ics",
-                           "images/*/*.jpg",
-                         ],
-                       },
-    scripts          = [
-                         "bin/caldavd",
-                         "bin/calendarserver_export",
-                         "bin/calendarserver_manage_principals",
-                         "bin/calendarserver_command_gateway",
-                         "bin/carddavd",
-                       ],
-    data_files       = [ ("caldavd", ["conf/caldavd.plist"]),
-                         ("carddavd", ["conf/carddavd.plist.default" ])],
-    ext_modules      = extensions,
-    py_modules       = ["kqreactor", "memcacheclient"],
-)
+    dist = setup(
+        name             = "Calendar and Contacts Server",
+        version          = version_string,
+        description      = description,
+        long_description = long_description,
+        url              = None,
+        classifiers      = classifiers,
+        author           = "Apple Inc.",
+        author_email     = None,
+        license          = None,
+        platforms        = [ "all" ],
+        packages         = find_modules(),
+        package_data     = {
+                             "twistedcaldav": [
+                               "zoneinfo/*.ics",
+                               "zoneinfo/*/*.ics",
+                               "zoneinfo/*/*/*.ics",
+                               "images/*/*.jpg",
+                             ],
+                           },
+        scripts          = [
+                             "bin/caldavd",
+                             "bin/calendarserver_export",
+                             "bin/calendarserver_load_augmentdb",
+                             "bin/calendarserver_make_partition",
+                             "bin/calendarserver_manage_augments",
+                             "bin/calendarserver_manage_principals",
+                             "bin/calendarserver_command_gateway",
+                             "bin/carddavd",
+                           ],
+        data_files       = [ ("caldavd", ["conf/caldavd.plist"]),
+                             ("carddavd", ["conf/carddavd.plist.default" ])],
+        ext_modules      = extensions,
+        py_modules       = ["kqreactor", "memcacheclient"],
+    )
 
-if "install" in dist.commands:
-    import os
-    install_scripts = dist.command_obj["install"].install_scripts
-    install_lib = dist.command_obj["install"].install_lib
-    root = dist.command_obj["install"].root
-    base = dist.command_obj["install"].install_base
+    if "install" in dist.commands:
+        install_scripts = dist.command_obj["install"].install_scripts
+        install_lib = dist.command_obj["install"].install_lib
+        root = dist.command_obj["install"].root
+        base = dist.command_obj["install"].install_base
 
-    if root:
-        install_lib = install_lib[len(root):]
+        if root:
+            install_lib = install_lib[len(root):]
 
-    for script in dist.scripts:
-        scriptPath = os.path.join(install_scripts, os.path.basename(script))
+        for script in dist.scripts:
+            scriptPath = os.path.join(install_scripts, os.path.basename(script))
 
-        print "rewriting %s" % (scriptPath,)
+            print "rewriting %s" % (scriptPath,)
 
-        script = []
-    
-        fileType = None
+            script = []
 
-        for line in file(scriptPath, "r"):
-            if not fileType:
-                if line.startswith("#!"):
-                    if "python" in line.lower():
-                        fileType = "python"
-                    elif "sh" in line.lower():
-                        fileType = "sh"
+            fileType = None
 
-            line = line.rstrip("\n")
-            if fileType == "sh":
-                if line == "#PYTHONPATH":
-                    script.append('PYTHONPATH="%s:$PYTHONPATH"' % (install_lib,))
-                elif line == "#PATH":
-                    script.append('PATH="%s:$PATH"' % (os.path.join(base, "bin"),))
-                else:
-                    script.append(line)
+            for line in file(scriptPath, "r"):
+                if not fileType:
+                    if line.startswith("#!"):
+                        if "python" in line.lower():
+                            fileType = "python"
+                        elif "sh" in line.lower():
+                            fileType = "sh"
 
-            elif fileType == "python":
-                if line == "#PYTHONPATH":
-                    script.append('PYTHONPATH="%s"' % (install_lib,))
-                elif line == "#PATH":
-                    script.append('PATH="%s"' % (os.path.join(base, "bin"),))
+                line = line.rstrip("\n")
+                if fileType == "sh":
+                    if line == "#PYTHONPATH":
+                        script.append('PYTHONPATH="%s:$PYTHONPATH"' % (install_lib,))
+                    elif line == "#PATH":
+                        script.append('PATH="%s:$PATH"' % (os.path.join(base, "bin"),))
+                    else:
+                        script.append(line)
+
+                elif fileType == "python":
+                    if line == "#PYTHONPATH":
+                        script.append('PYTHONPATH="%s"' % (install_lib,))
+                    elif line == "#PATH":
+                        script.append('PATH="%s"' % (os.path.join(base, "bin"),))
+                    else:
+                        script.append(line)
+
                 else:
                     script.append(line)
 
-            else:
-                script.append(line)
-
-        newScript = open(scriptPath, "w")
-        newScript.write("\n".join(script))
-        newScript.close()
+            newScript = open(scriptPath, "w")
+            newScript.write("\n".join(script))
+            newScript.close()

Modified: CalendarServer/branches/users/glyph/use-system-twisted/support/Makefile.Apple
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/support/Makefile.Apple	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/support/Makefile.Apple	2010-02-12 22:49:09 UTC (rev 5105)
@@ -94,14 +94,19 @@
 	$(_v) rm -f  "$(DSTROOT)$(PY_HOME)/lib/python/twisted/python/zshcomp.pyc"
 	$(_v) rm -f  "$(DSTROOT)$(PY_HOME)/lib/python/twisted/python/_twisted_zsh_stub"
 	$(_v) $(INSTALL_FILE) "$(Sources)/conf/caldavd-apple.plist" "$(DSTROOT)$(ETCDIR)/caldavd/caldavd.plist"
+	$(_v) $(INSTALL_FILE) "$(Sources)/conf/caldavd-partitioning-primary.plist" "$(DSTROOT)$(ETCDIR)/caldavd/caldavd-partitioning-primary.plist"
+	$(_v) $(INSTALL_FILE) "$(Sources)/conf/caldavd-partitioning-secondary.plist" "$(DSTROOT)$(ETCDIR)/caldavd/caldavd-partitioning-secondary.plist"
+	$(_v) $(INSTALL_FILE) "$(Sources)/conf/partitions.plist" "$(DSTROOT)$(ETCDIR)/caldavd/partitions.plist"
+	$(_v) $(INSTALL_FILE) "$(Sources)/conf/servertoserver.xml" "$(DSTROOT)$(ETCDIR)/caldavd/servertoserver.xml"
 	$(_v) chmod -R ugo+r "$(DSTROOT)$(PY_HOME)"
 	$(_v) for f in $$(find "$(DSTROOT)$(ETCDIR)" -type f ! -name '*.default'); do cp "$${f}" "$${f}.default"; done
+	$(_v) $(INSTALL_FILE) "$(Sources)/conf/servertoserver.dtd" "$(DSTROOT)$(ETCDIR)/caldavd/servertoserver.dtd"
 
 install::
 	@echo "Installing manual pages..."
 	$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(MANDIR)/man8"
 	$(_v) $(INSTALL_FILE) "$(Sources)/doc/caldavd.8"                          "$(DSTROOT)$(MANDIR)/man8"
-	$(_v) $(INSTALL_FILE) "$(Sources)/doc/carddavd.8"                          "$(DSTROOT)$(MANDIR)/man8"
+	$(_v) $(INSTALL_FILE) "$(Sources)/doc/carddavd.8"                         "$(DSTROOT)$(MANDIR)/man8"
 	$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_export.8"            "$(DSTROOT)$(MANDIR)/man8"
 	$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_manage_principals.8" "$(DSTROOT)$(MANDIR)/man8"
 	$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_command_gateway.8"   "$(DSTROOT)$(MANDIR)/man8"

Modified: CalendarServer/branches/users/glyph/use-system-twisted/support/submit
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/support/submit	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/support/submit	2010-02-12 22:49:09 UTC (rev 5105)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 ##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -85,7 +85,7 @@
 
 if [ $# != 0 ]; then usage "Unrecognized arguments:" "$@"; fi;
 
- project="AddressBookServer";
+ project="CalendarServer";
      uri="$(svn info "${src}" --xml | sed -n 's|^.*<url>\(.*\)</url>.*$|\1|p')";
 revision="$(svnversion "${src}")";
 
@@ -106,8 +106,7 @@
     revision="$(echo "${revision}" | sed 's|M$||g')";
   fi;
 
-#  project_version="${project}-$((${version} + ${revision} / 10000 % 100)).$((${revision} / 100 % 100)).$((${revision} % 100))";
-  project_version="${project}-${version}"
+  project_version="${project}-$((${version} + ${revision} / 10000 % 100)).$((${revision} / 100 % 100)).$((${revision} % 100))";
 
   #
   # Make sure changes are checked in.
@@ -121,13 +120,13 @@
 # Do submission
 #
 
-tmp="$(mktemp -d -t AddressBookServer_build)";
+tmp="$(mktemp -d -t CalendarServer_build)";
 wc="${tmp}/${project_version}";
 
 if "${build}"; then
   echo "";
   echo "Copying ${src}...";
-  ignores="$(mktemp -t AddressBookServer_ignores)";
+  ignores="$(mktemp -t CalendarServer_ignores)";
   svn st --no-ignore | sed -n -e 's|^I......||p' > "${ignores}";
   rsync -av --exclude=".svn" --exclude="_trial_temp" --exclude-from="${ignores}" "${src}/" "${wc}";
   rm "${ignores}";
@@ -159,7 +158,7 @@
 
 echo "";
 echo "Preparing sources for ${project_version}...";
-make SRCROOT="${wc}" -C "${wc}" prep;
+make -C "${wc}" prep;
 
 if "${build}"; then
   echo "";

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/database.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/database.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/database.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -408,6 +408,9 @@
     def _test_table(self, name):
         raise NotImplementedError
 
+    def _create_index(self, name, ontable, columns, ifnotexists=False):
+        raise NotImplementedError
+
     def _prepare_statement(self, sql):
         raise NotImplementedError
         
@@ -453,6 +456,17 @@
         """ % (name,)))
         returnValue(result)
 
+    @inlineCallbacks
+    def _create_index(self, name, ontable, columns, ifnotexists=False):
+        
+        statement = "create index %s%s on %s (%s)" % (
+            "if not exists " if ifnotexists else "",
+            name,
+            ontable,
+            ", ".join(columns),
+        )
+        yield self._db_execute(statement)
+
     def _prepare_statement(self, sql):
         # We are going to use the sqlite syntax of :1, :2 etc for our
         # internal statements so we do not need to remap those
@@ -512,6 +526,26 @@
             returnValue(result)
     
         @inlineCallbacks
+        def _create_index(self, name, ontable, columns, ifnotexists=False):
+            
+            statement = "create index %s on %s (%s)" % (
+                name,
+                ontable,
+                ", ".join(columns),
+            )
+            
+            try:
+                yield self._db_execute(statement)
+            except pgdb.DatabaseError:
+                
+                if not ifnotexists:
+                    raise
+                
+                result = (yield self._test_table(name))
+                if not result:
+                    raise 
+    
+        @inlineCallbacks
         def _db_init_schema_table(self):
             """
             Initialise the underlying database tables.

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/aggregate.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/aggregate.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/aggregate.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -192,25 +192,26 @@
 
     def createRecord(self, recordType, guid=None, shortNames=(), authIDs=set(),
         fullName=None, firstName=None, lastName=None, emailAddresses=set(),
-        uid=None, password=None, **kwds):
+        uid=None, password=None, **kwargs):
         service = self.serviceForRecordType(recordType)
         return service.createRecord(recordType, guid=guid,
             shortNames=shortNames, authIDs=authIDs, fullName=fullName,
             firstName=firstName, lastName=lastName,
-            emailAddresses=emailAddresses, uid=uid, password=password, **kwds)
+            emailAddresses=emailAddresses, uid=uid, password=password, **kwargs)
 
-    def updateRecord(self, recordType, guid, shortNames=(), authIDs=set(),
+    def updateRecord(self, recordType, guid=None, shortNames=(), authIDs=set(),
         fullName=None, firstName=None, lastName=None, emailAddresses=set(),
-        uid=None, password=None, **kwds):
+        uid=None, password=None, **kwargs):
         service = self.serviceForRecordType(recordType)
-        return service.updateRecord(recordType, guid, shortNames=shortNames,
+        return service.updateRecord(recordType, guid=guid,
+            shortNames=shortNames,
             authIDs=authIDs, fullName=fullName, firstName=firstName,
             lastName=lastName, emailAddresses=emailAddresses, uid=uid,
-            password=password, **kwds)
+            password=password, **kwargs)
 
-    def destroyRecord(self, recordType, guid):
+    def destroyRecord(self, recordType, guid=None):
         service = self.serviceForRecordType(recordType)
-        return service.destroyRecord(recordType, guid)
+        return service.destroyRecord(recordType, guid=guid)
 
 class DuplicateRecordTypeError(DirectoryError):
     """

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/augment.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/augment.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/augment.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -24,6 +24,10 @@
 from twistedcaldav.database import AbstractADBAPIDatabase, ADBAPISqliteMixin,\
     ADBAPIPostgreSQLMixin
 from twistedcaldav.directory.xmlaugmentsparser import XMLAugmentsParser
+import os
+from twistedcaldav.directory import xmlaugmentsparser
+from twistedcaldav.xmlutil import newElementTreeWithRoot, addSubElement,\
+    writeXML, readXML
 
 
 log = Logger()
@@ -35,14 +39,14 @@
 
     def __init__(
         self,
-        guid,
+        uid,
         enabled=False,
         hostedAt="",
         enabledForCalendaring=False,
         autoSchedule=False,
         enabledForAddressBooks=False,
     ):
-        self.guid = guid
+        self.uid = uid
         self.enabled = enabled
         self.hostedAt = hostedAt
         self.enabledForCalendaring = enabledForCalendaring
@@ -58,47 +62,73 @@
         pass
     
     @inlineCallbacks
-    def getAugmentRecord(self, guid):
+    def getAugmentRecord(self, uid):
         """
-        Get an AugmentRecord for the specified GUID or the default.
+        Get an AugmentRecord for the specified UID or the default.
 
-        @param guid: directory GUID to lookup
-        @type guid: C{str}
+        @param uid: directory UID to lookup
+        @type uid: C{str}
         
         @return: L{Deferred}
         """
         
-        result = (yield self._lookupAugmentRecord(guid))
+        result = (yield self._lookupAugmentRecord(uid))
         if result is None:
             if not hasattr(self, "_defaultRecord"):
                 self._defaultRecord = (yield self._lookupAugmentRecord("Default"))
             if self._defaultRecord is not None:
                 result = copy.deepcopy(self._defaultRecord)
-                result.guid = guid
+                result.uid = uid
         returnValue(result)
 
     @inlineCallbacks
-    def getAllGUIDs(self):
+    def getAllUIDs(self):
         """
-        Get all AugmentRecord GUIDs.
+        Get all AugmentRecord UIDs.
 
         @return: L{Deferred}
         """
         
         raise NotImplementedError("Child class must define this.")
 
-    def _lookupAugmentRecord(self, guid):
+    def _lookupAugmentRecord(self, uid):
         """
-        Get an AugmentRecord for the specified GUID.
+        Get an AugmentRecord for the specified UID.
 
-        @param guid: directory GUID to lookup
-        @type guid: C{str}
+        @param uid: directory UID to lookup
+        @type uid: C{str}
         
         @return: L{Deferred}
         """
         
         raise NotImplementedError("Child class must define this.")
 
+    def addAugmentRecords(self, records, update=False):
+        """
+        Add an AugmentRecord to the DB.
+
+        @param record: augment records to add
+        @type record: C{list} of L{AugmentRecord}
+        @param update: C{True} if changing an existing record
+        @type update: C{bool}
+        
+        @return: L{Deferred}
+        """
+
+        raise NotImplementedError("Child class must define this.")
+
+    def removeAugmentRecords(self, uids):
+        """
+        Remove AugmentRecords with the specified UIDs.
+
+        @param uid: directory UIDs to remove
+        @type uid: C{list} of C{str}
+        
+        @return: L{Deferred}
+        """
+
+        raise NotImplementedError("Child class must define this.")
+
     def refresh(self):
         """
         Refresh any cached data.
@@ -120,6 +150,29 @@
         self.lastCached = 0
         self.db = {}
         
+        # Preflight existence of files
+        missing = list()
+        for xmlFile in self.xmlFiles:
+            if not os.path.exists(xmlFile):
+                missing.append(xmlFile)
+                
+        # For each missing one create an empty xml file
+        if missing:
+            # If all files are missing, then create one augment file that defaults
+            # to all records being enabled
+            doDefault = (len(missing) == len(self.xmlFiles))
+            for missedFile in missing:
+                
+                _ignore_etree, root = newElementTreeWithRoot(xmlaugmentsparser.ELEMENT_AUGMENTS)
+                if doDefault:
+                    record = addSubElement(root, xmlaugmentsparser.ELEMENT_RECORD)
+                    addSubElement(record, xmlaugmentsparser.ELEMENT_UID, "Default")
+                    addSubElement(record, xmlaugmentsparser.ELEMENT_ENABLE, "true")
+                    addSubElement(record, xmlaugmentsparser.ELEMENT_ENABLECALENDAR, "true")
+                    addSubElement(record, xmlaugmentsparser.ELEMENT_ENABLEADDRESSBOOK, "true")
+                    doDefault = False
+                writeXML(missedFile, root)
+            
         try:
             self.db = self._parseXML()
         except RuntimeError:
@@ -129,21 +182,21 @@
         self.lastCached = time.time()
 
     @inlineCallbacks
-    def getAllGUIDs(self):
+    def getAllUIDs(self):
         """
-        Get all AugmentRecord GUIDs.
+        Get all AugmentRecord UIDs.
 
         @return: L{Deferred}
         """
         
         return succeed(self.db.keys())
 
-    def _lookupAugmentRecord(self, guid):
+    def _lookupAugmentRecord(self, uid):
         """
-        Get an AugmentRecord for the specified GUID.
+        Get an AugmentRecord for the specified UID.
 
-        @param guid: directory GUID to lookup
-        @type guid: C{str}
+        @param uid: directory UID to lookup
+        @type uid: C{str}
         
         @return: L{Deferred}
         """
@@ -152,8 +205,117 @@
         if self.lastCached + self.cacheTimeout <= time.time():
             self.refresh()
             
-        return succeed(self.db.get(guid))
+        return succeed(self.db.get(uid))
 
+    def addAugmentRecords(self, records, update=False):
+        """
+        Add an AugmentRecord to the DB.
+
+        @param records: augment records to add
+        @type records: C{list} of L{AugmentRecord}
+        @param update: C{True} if changing an existing record
+        @type update: C{bool}
+        
+        @return: L{Deferred}
+        """
+
+        if update:
+            # Now look at each file and modify the UIDs
+            for xmlFile in self.xmlFiles:
+                self._doModifyInFile(xmlFile, records)
+        else:
+            # Add to first file in list
+            self._doAddToFile(self.xmlFiles[0], records)
+
+    def _doAddToFile(self, xmlfile, records):
+    
+        _ignore_etree, augments_node = readXML(xmlfile)
+    
+        # Create new record
+        for record in records:
+            record_node = addSubElement(augments_node, xmlaugmentsparser.ELEMENT_RECORD)
+            addSubElement(record_node, xmlaugmentsparser.ELEMENT_UID, record.uid)
+            addSubElement(record_node, xmlaugmentsparser.ELEMENT_ENABLE, "true" if record.enabled else "false")
+            addSubElement(record_node, xmlaugmentsparser.ELEMENT_HOSTEDAT, record.hostedAt)
+            addSubElement(record_node, xmlaugmentsparser.ELEMENT_ENABLECALENDAR, "true" if record.enabledForCalendaring else "false")
+            addSubElement(record_node, xmlaugmentsparser.ELEMENT_ENABLEADDRESSBOOK, "true" if record.enabledForAddressBooks else "false")
+            addSubElement(record_node, xmlaugmentsparser.ELEMENT_AUTOSCHEDULE, "true" if record.autoSchedule else "false")
+        
+        # Modify xmlfile
+        writeXML(xmlfile, augments_node)
+        
+    def _doModifyInFile(self, xmlfile, records):
+    
+        _ignore_etree, augments_node = readXML(xmlfile)
+    
+        # Map uid->record for fast lookup
+        recordMap = dict([(record.uid, record) for record in records])
+
+        # Make sure UID is present
+        changed = False
+        for child in augments_node.getchildren():
+            
+            if child.tag != xmlaugmentsparser.ELEMENT_RECORD:
+                continue
+    
+            uid = child.find(xmlaugmentsparser.ELEMENT_UID).text
+            if uid in recordMap:
+                # Modify record
+                record = recordMap[uid]
+                child.find(xmlaugmentsparser.ELEMENT_ENABLE).text = "true" if record.enabled else "false"
+                child.find(xmlaugmentsparser.ELEMENT_HOSTEDAT).text = record.hostedAt
+                child.find(xmlaugmentsparser.ELEMENT_ENABLECALENDAR).text = "true" if record.enabledForCalendaring else "false"
+                child.find(xmlaugmentsparser.ELEMENT_ENABLEADDRESSBOOK).text = "true" if record.enabledForAddressBooks else "false"
+                child.find(xmlaugmentsparser.ELEMENT_AUTOSCHEDULE).text = "true" if record.autoSchedule else "false"
+                changed = True
+        
+        
+        # Modify xmlfile
+        if changed:
+            writeXML(xmlfile, augments_node)
+
+    def removeAugmentRecords(self, uids):
+        """
+        Remove AugmentRecords with the specified UIDs.
+
+        @param uid: directory UID to lookup
+        @type uid: C{list} of C{str}
+        
+        @return: L{Deferred}
+        """
+
+        # Remove from cache first
+        removed = set()
+        for uid in uids:
+            if uid in self.db:
+                del self.db[uid]
+                removed.add(uid)
+
+        # Now look at each file and remove the UIDs
+        for xmlFile in self.xmlFiles:
+            self._doRemoveFromFile(xmlFile, removed)
+
+        return succeed(None)
+
+    def _doRemoveFromFile(self, xmlfile, uids):
+    
+        _ignore_etree, augments_node = readXML(xmlfile)
+    
+        # Remove all UIDs present
+        changed = False
+        for child in tuple(augments_node.getchildren()):
+            
+            if child.tag != xmlaugmentsparser.ELEMENT_RECORD:
+                continue
+
+            if child.find(xmlaugmentsparser.ELEMENT_UID).text in uids:
+                augments_node.remove(child)
+                changed = True
+        
+        # Modify xmlfile
+        if changed:
+            writeXML(xmlfile, augments_node)
+        
     def refresh(self):
         """
         Refresh any cached data.
@@ -191,81 +353,87 @@
         AbstractADBAPIDatabase.__init__(self, dbID, dbapiName, dbapiArgs, True, **kwargs)
         
     @inlineCallbacks
-    def getAllGUIDs(self):
+    def getAllUIDs(self):
         """
-        Get all AugmentRecord GUIDs.
+        Get all AugmentRecord UIDs.
 
         @return: L{Deferred}
         """
         
         # Query for the record information
-        results = (yield self.queryList("select GUID from AUGMENTS", ()))
+        results = (yield self.queryList("select UID from AUGMENTS", ()))
         returnValue(results)
 
     @inlineCallbacks
-    def _lookupAugmentRecord(self, guid):
+    def _lookupAugmentRecord(self, uid):
         """
-        Get an AugmentRecord for the specified GUID.
+        Get an AugmentRecord for the specified UID.
 
-        @param guid: directory GUID to lookup
-        @type guid: C{str}
+        @param uid: directory UID to lookup
+        @type uid: C{str}
 
         @return: L{Deferred}
         """
         
         # Query for the record information
-        results = (yield self.query("select GUID, ENABLED, PARTITIONID, CALENDARING, AUTOSCHEDULE from AUGMENTS where GUID = :1", (guid,)))
+        results = (yield self.query("select UID, ENABLED, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE from AUGMENTS where UID = :1", (uid,)))
         if not results:
             returnValue(None)
         else:
-            guid, enabled, partitionid, enabdledForCalendaring, autoSchedule = results[0]
+            uid, enabled, partitionid, enabledForCalendaring, enabledForAddressBooks, autoSchedule = results[0]
             
             record = AugmentRecord(
-                guid = guid,
+                uid = uid,
                 enabled = enabled == "T",
                 hostedAt = (yield self._getPartition(partitionid)),
-                enabledForCalendaring = enabdledForCalendaring == "T",
+                enabledForCalendaring = enabledForCalendaring == "T",
+                enabledForAddressBooks = enabledForAddressBooks == "T",
                 autoSchedule = autoSchedule == "T",
             )
             
             returnValue(record)
 
     @inlineCallbacks
-    def addAugmentRecord(self, record, update=False):
+    def addAugmentRecords(self, records, update=False):
 
-        partitionid = (yield self._getPartitionID(record.hostedAt))
-        
-        if update:
-            yield self.execute(
-                """update AUGMENTS set
-                (GUID, ENABLED, PARTITIONID, CALENDARING, AUTOSCHEDULE) =
-                (:1, :2, :3, :4, :5) where GUID = :6""",
-                (
-                    record.guid,
-                    "T" if record.enabled else "F",
-                    partitionid,
-                    "T" if record.enabledForCalendaring else "F",
-                    "T" if record.autoSchedule else "F",
-                    record.guid,
+        for record in records:
+            partitionid = (yield self._getPartitionID(record.hostedAt))
+            
+            if update:
+                yield self.execute(
+                    """update AUGMENTS set
+                    (UID, ENABLED, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE) =
+                    (:1, :2, :3, :4, :5, :6) where UID = :7""",
+                    (
+                        record.uid,
+                        "T" if record.enabled else "F",
+                        partitionid,
+                        "T" if record.enabledForCalendaring else "F",
+                        "T" if record.enabledForAddressBooks else "F",
+                        "T" if record.autoSchedule else "F",
+                        record.uid,
+                    )
                 )
-            )
-        else:
-            yield self.execute(
-                """insert into AUGMENTS
-                (GUID, ENABLED, PARTITIONID, CALENDARING, AUTOSCHEDULE)
-                values (:1, :2, :3, :4, :5)""",
-                (
-                    record.guid,
-                    "T" if record.enabled else "F",
-                    partitionid,
-                    "T" if record.enabledForCalendaring else "F",
-                    "T" if record.autoSchedule else "F",
+            else:
+                yield self.execute(
+                    """insert into AUGMENTS
+                    (UID, ENABLED, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE)
+                    values (:1, :2, :3, :4, :5, :6)""",
+                    (
+                        record.uid,
+                        "T" if record.enabled else "F",
+                        partitionid,
+                        "T" if record.enabledForCalendaring else "F",
+                        "T" if record.enabledForAddressBooks else "F",
+                        "T" if record.autoSchedule else "F",
+                    )
                 )
-            )
 
-    def removeAugmentRecord(self, guid):
+    @inlineCallbacks
+    def removeAugmentRecords(self, uids):
 
-        return self.query("delete from AUGMENTS where GUID = :1", (guid,))
+        for uid in uids:
+            yield self.execute("delete from AUGMENTS where UID = :1", (uid,))
 
     @inlineCallbacks
     def _getPartitionID(self, hostedat, createIfMissing=True):
@@ -315,20 +483,29 @@
         """
 
         #
-        # TESTTYPE table
+        # AUGMENTS table
         #
-        yield self._create_table("AUGMENTS", (
-            ("GUID",         "text unique"),
-            ("ENABLED",      "text(1)"),
-            ("PARTITIONID",  "text"),
-            ("CALENDARING",  "text(1)"),
-            ("AUTOSCHEDULE", "text(1)"),
-        ))
+        yield self._create_table(
+            "AUGMENTS",
+            (
+                ("UID",          "text unique"),
+                ("ENABLED",      "text(1)"),
+                ("PARTITIONID",  "text"),
+                ("CALENDARING",  "text(1)"),
+                ("ADDRESSBOOKS", "text(1)"),
+                ("AUTOSCHEDULE", "text(1)"),
+            ),
+            ifnotexists=True,
+        )
 
-        yield self._create_table("PARTITIONS", (
-            ("PARTITIONID",   "serial"),
-            ("HOSTEDAT",      "text"),
-        ))
+        yield self._create_table(
+            "PARTITIONS",
+            (
+                ("PARTITIONID",   "serial"),
+                ("HOSTEDAT",      "text"),
+            ),
+            ifnotexists=True,
+        )
 
     @inlineCallbacks
     def _db_empty_data_tables(self):

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/cachingdirectory.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/cachingdirectory.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/cachingdirectory.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -357,12 +357,12 @@
         self, service, recordType, guid,
         shortNames=(), authIDs=set(),
         fullName=None, firstName=None, lastName=None, emailAddresses=set(),
-        uid=None,
+        uid=None, **kwargs
     ):
         super(CachingDirectoryRecord, self).__init__(
-            service               = service,
-            recordType            = recordType,
-            guid                  = guid,
+            service,
+            recordType,
+            guid,
             shortNames            = shortNames,
             authIDs               = authIDs,
             fullName              = fullName,
@@ -370,6 +370,7 @@
             lastName              = lastName,
             emailAddresses        = emailAddresses,
             uid                   = uid,
+            **kwargs
         )
         
         self.cachedTime = time.time()

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/calendaruserproxy.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/calendaruserproxy.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -719,15 +719,17 @@
             ifnotexists=True,
         )
 
-        yield self._db_execute(
-            """
-            create index if not exists GROUPNAMES on GROUPS (GROUPNAME)
-            """
+        yield self._create_index(
+            "GROUPNAMES",
+            "GROUPS",
+            ("GROUPNAME",),
+            ifnotexists=True,
         )
-        yield self._db_execute(
-            """
-            create index if not exists MEMBERS on GROUPS (MEMBER)
-            """
+        yield self._create_index(
+            "MEMBERS",
+            "GROUPS",
+            ("MEMBER",),
+            ifnotexists=True,
         )
 
     @inlineCallbacks
@@ -740,15 +742,17 @@
 
         # Add index if old version is less than "4"
         if int(old_version) < 4:
-            yield self._db_execute(
-                """
-                create index if not exists GROUPNAMES on GROUPS (GROUPNAME)
-                """
+            yield self._create_index(
+                "GROUPNAMES",
+                "GROUPS",
+                ("GROUPNAME",),
+                ifnotexists=True,
             )
-            yield self._db_execute(
-                """
-                create index if not exists MEMBERS on GROUPS (MEMBER)
-                """
+            yield self._create_index(
+                "MEMBERS",
+                "GROUPS",
+                ("MEMBER",),
+                ifnotexists=True,
             )
 
     def _db_empty_data_tables(self):

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/directory.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/directory.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -299,21 +299,21 @@
 
     def createRecord(self, recordType, guid=None, shortNames=(), authIDs=set(),
         fullName=None, firstName=None, lastName=None, emailAddresses=set(),
-        uid=None, password=None, **kwds):
+        uid=None, password=None, **kwargs):
         """
         Create/persist a directory record based on the given values
         """
         raise NotImplementedError("Subclass must implement createRecord")
 
-    def updateRecord(self, recordType, guid, shortNames=(), authIDs=set(),
+    def updateRecord(self, recordType, guid=None, shortNames=(), authIDs=set(),
         fullName=None, firstName=None, lastName=None, emailAddresses=set(),
-        uid=None, password=None, **kwds):
+        uid=None, password=None, **kwargs):
         """
         Update/persist a directory record based on the given values
         """
         raise NotImplementedError("Subclass must implement updateRecord")
 
-    def destroyRecord(self, recordType, guid):
+    def destroyRecord(self, recordType, guid=None):
         """
         Remove a directory record from the directory
         """
@@ -342,6 +342,7 @@
         calendarUserAddresses=set(), autoSchedule=False, enabledForCalendaring=None,
         enabledForAddressBooks=None,
         uid=None,
+        **kwargs
     ):
         assert service.realmName is not None
         assert recordType
@@ -356,23 +357,25 @@
         if fullName is None:
             fullName = ""
 
-        self.service               = service
-        self.recordType            = recordType
-        self.guid                  = guid
-        self.uid                   = uid
-        self.enabled               = False
-        self.hostedAt              = ""
-        self.shortNames            = shortNames
-        self.authIDs               = authIDs
-        self.fullName              = fullName
-        self.firstName             = firstName
-        self.lastName              = lastName
-        self.emailAddresses        = emailAddresses
-        self.enabledForCalendaring = enabledForCalendaring
-        self.autoSchedule          = autoSchedule
+        self.service                = service
+        self.recordType             = recordType
+        self.guid                   = guid
+        self.uid                    = uid
+        self.enabled                = False
+        self.hostedAt               = ""
+        self.shortNames             = shortNames
+        self.authIDs                = authIDs
+        self.fullName               = fullName
+        self.firstName              = firstName
+        self.lastName               = lastName
+        self.emailAddresses         = emailAddresses
+        self.enabledForCalendaring  = enabledForCalendaring
+        self.autoSchedule           = autoSchedule
         self.enabledForAddressBooks = enabledForAddressBooks
+        self.extras                 = kwargs
 
 
+
     def get_calendarUserAddresses(self):
         """
         Dynamically construct a calendarUserAddresses attribute which describes

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/augments-test-default.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/augments-test-default.xml	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/augments-test-default.xml	2010-02-12 22:49:09 UTC (rev 5105)
@@ -20,45 +20,49 @@
 
 <augments>
   <record>
-    <guid>Default</guid>
+    <uid>Default</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
     <hosted-at>00001</hosted-at>
   </record>
   <record>
-    <guid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</guid>
+    <uid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</uid>
     <enable>true</enable>
   </record>
   <record>
-    <guid>6423F94A-6B76-4A3A-815B-D52CFD77935D</guid>
+    <uid>6423F94A-6B76-4A3A-815B-D52CFD77935D</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>5A985493-EE2C-4665-94CF-4DFEA3A89500</guid>
+    <uid>5A985493-EE2C-4665-94CF-4DFEA3A89500</uid>
     <enable>false</enable>
   </record>
   <record>
-    <guid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</guid>
+    <uid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</uid>
     <enable>true</enable>
     <enable-calendar>false</enable-calendar>
+    <enable-addressbook>false</enable-addressbook>
   </record>
   <record>
-    <guid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
+    <uid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</uid>
     <enable>true</enable>
     <hosted-at>00001</hosted-at>
   </record>
   <record>
-    <guid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</guid>
+    <uid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</uid>
     <enable>true</enable>
     <hosted-at>00002</hosted-at>
   </record>
   <record>
-    <guid>6A73326A-F781-47E7-A9F8-AF47364D4152</guid>
+    <uid>6A73326A-F781-47E7-A9F8-AF47364D4152</uid>
     <enable>true</enable>
     <hosted-at>00002</hosted-at>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
     <auto-schedule>true</auto-schedule>
   </record>
 </augments>

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/augments-test.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/augments-test.xml	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/augments-test.xml	2010-02-12 22:49:09 UTC (rev 5105)
@@ -20,39 +20,42 @@
 
 <augments>
   <record>
-    <guid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</guid>
+    <uid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</uid>
     <enable>true</enable>
   </record>
   <record>
-    <guid>6423F94A-6B76-4A3A-815B-D52CFD77935D</guid>
+    <uid>6423F94A-6B76-4A3A-815B-D52CFD77935D</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>5A985493-EE2C-4665-94CF-4DFEA3A89500</guid>
+    <uid>5A985493-EE2C-4665-94CF-4DFEA3A89500</uid>
     <enable>false</enable>
   </record>
   <record>
-    <guid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</guid>
+    <uid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</uid>
     <enable>true</enable>
     <enable-calendar>false</enable-calendar>
+    <enable-addressbook>false</enable-addressbook>
   </record>
   <record>
-    <guid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
+    <uid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</uid>
     <enable>true</enable>
     <hosted-at>00001</hosted-at>
   </record>
   <record>
-    <guid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</guid>
+    <uid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</uid>
     <enable>true</enable>
     <hosted-at>00002</hosted-at>
   </record>
   <record>
-    <guid>6A73326A-F781-47E7-A9F8-AF47364D4152</guid>
+    <uid>6A73326A-F781-47E7-A9F8-AF47364D4152</uid>
     <enable>true</enable>
     <hosted-at>00002</hosted-at>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
     <auto-schedule>true</auto-schedule>
   </record>
 </augments>

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/augments.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/augments.xml	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/augments.xml	2010-02-12 22:49:09 UTC (rev 5105)
@@ -20,109 +20,123 @@
 
 <augments realm="Test">
   <record>
-    <guid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</guid>
+    <uid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>6423F94A-6B76-4A3A-815B-D52CFD77935D</guid>
+    <uid>6423F94A-6B76-4A3A-815B-D52CFD77935D</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>5A985493-EE2C-4665-94CF-4DFEA3A89500</guid>
+    <uid>5A985493-EE2C-4665-94CF-4DFEA3A89500</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</guid>
+    <uid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
+    <uid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</guid>
+    <uid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</uid>
     <enable>true</enable>
     <enable-calendar>false</enable-calendar>
+    <enable-addressbook>false</enable-addressbook>
   </record>
   <record repeat="2">
-    <guid>user%02d</guid>
+    <uid>user%02d</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
+    <uid>9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</uid>
     <enable>true</enable>
   </record>
   <record>
-    <guid>admin</guid>
+    <uid>admin</uid>
     <enable>true</enable>
   </record>
   <record>
-    <guid>grunts</guid>
+    <uid>grunts</uid>
     <enable>true</enable>
   </record>
   <record>
-    <guid>right_coast</guid>
+    <uid>right_coast</uid>
     <enable>true</enable>
   </record>
   <record>
-    <guid>left_coast</guid>
+    <uid>left_coast</uid>
     <enable>true</enable>
   </record>
   <record>
-    <guid>both_coasts</guid>
+    <uid>both_coasts</uid>
     <enable>true</enable>
   </record>
   <record>
-    <guid>recursive1_coasts</guid>
+    <uid>recursive1_coasts</uid>
     <enable>true</enable>
   </record>
   <record>
-    <guid>recursive2_coasts</guid>
+    <uid>recursive2_coasts</uid>
     <enable>true</enable>
   </record>
   <record>
-    <guid>non_calendar_group</guid>
+    <uid>non_calendar_group</uid>
     <enable>true</enable>
   </record>
   <record>
-    <guid>mercury</guid>
+    <uid>mercury</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>gemini</guid>
+    <uid>gemini</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>apollo</guid>
+    <uid>apollo</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>orion</guid>
+    <uid>orion</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>transporter</guid>
+    <uid>transporter</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>ftlcpu</guid>
+    <uid>ftlcpu</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>non_calendar_proxy</guid>
+    <uid>non_calendar_proxy</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
 </augments>

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/modify/augments.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/modify/augments.xml	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/modify/augments.xml	2010-02-12 22:49:09 UTC (rev 5105)
@@ -20,13 +20,15 @@
 
 <augments>
   <record>
-    <guid>user01</guid>
+    <uid>user01</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>user02</guid>
+    <uid>user02</uid>
     <enable>false</enable>
     <enable-calendar>false</enable-calendar>
+    <enable-addressbook>false</enable-addressbook>
   </record>
 </augments>

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/resources/augments.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/resources/augments.xml	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/resources/augments.xml	2010-02-12 22:49:09 UTC (rev 5105)
@@ -20,25 +20,29 @@
 
 <augments>
   <record>
-    <guid>user01</guid>
+    <uid>user01</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
-    <guid>user02</guid>
+    <uid>user02</uid>
     <enable>false</enable>
     <enable-calendar>false</enable-calendar>
+    <enable-addressbook>false</enable-addressbook>
   </record>
   <record>
-    <guid>resource01</guid>
+    <uid>resource01</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
     <auto-schedule>true</auto-schedule>
   </record>
   <record>
-    <guid>resource02</guid>
+    <uid>resource02</uid>
     <enable>false</enable>
     <enable-calendar>false</enable-calendar>
+    <enable-addressbook>false</enable-addressbook>
     <auto-schedule>false</auto-schedule>
   </record>
 </augments>

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_augment.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_augment.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_augment.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -26,32 +26,32 @@
 xmlFileDefault = os.path.join(os.path.dirname(__file__), "augments-test-default.xml")
 
 testRecords = (
-    {"guid":"D11F03A0-97EA-48AF-9A6C-FAC7F3975766", "enabled":True,  "hostedAt":"", "enabledForCalendaring":False, "autoSchedule":False},
-    {"guid":"6423F94A-6B76-4A3A-815B-D52CFD77935D", "enabled":True,  "hostedAt":"", "enabledForCalendaring":True, "autoSchedule":False},
-    {"guid":"5A985493-EE2C-4665-94CF-4DFEA3A89500", "enabled":False, "hostedAt":"", "enabledForCalendaring":False, "autoSchedule":False},
-    {"guid":"8B4288F6-CC82-491D-8EF9-642EF4F3E7D0", "enabled":True,  "hostedAt":"", "enabledForCalendaring":False, "autoSchedule":False},
-    {"guid":"5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1", "enabled":True,  "hostedAt":"00001", "enabledForCalendaring":False, "autoSchedule":False},
-    {"guid":"543D28BA-F74F-4D5F-9243-B3E3A61171E5", "enabled":True,  "hostedAt":"00002", "enabledForCalendaring":False, "autoSchedule":False},
-    {"guid":"6A73326A-F781-47E7-A9F8-AF47364D4152", "enabled":True,  "hostedAt":"00002", "enabledForCalendaring":True, "autoSchedule":True},
+    {"uid":"D11F03A0-97EA-48AF-9A6C-FAC7F3975766", "enabled":True,  "hostedAt":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
+    {"uid":"6423F94A-6B76-4A3A-815B-D52CFD77935D", "enabled":True,  "hostedAt":"", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":False},
+    {"uid":"5A985493-EE2C-4665-94CF-4DFEA3A89500", "enabled":False, "hostedAt":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
+    {"uid":"8B4288F6-CC82-491D-8EF9-642EF4F3E7D0", "enabled":True,  "hostedAt":"", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
+    {"uid":"5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1", "enabled":True,  "hostedAt":"00001", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
+    {"uid":"543D28BA-F74F-4D5F-9243-B3E3A61171E5", "enabled":True,  "hostedAt":"00002", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
+    {"uid":"6A73326A-F781-47E7-A9F8-AF47364D4152", "enabled":True,  "hostedAt":"00002", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":True},
 )
 
-testRecordDefault = {"guid":"A4318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True,  "hostedAt":"00001", "enabledForCalendaring":True, "autoSchedule":False}
+testRecordDefault = {"uid":"A4318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True,  "hostedAt":"00001", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":False}
 
 class AugmentTests(TestCase):
 
     @inlineCallbacks
     def _checkRecord(self, db, items):
         
-        record = (yield db.getAugmentRecord(items["guid"]))
+        record = (yield db.getAugmentRecord(items["uid"]))
         self.assertTrue(record is not None)
         
         for k,v in items.iteritems():
             self.assertEqual(getattr(record, k), v)
 
     @inlineCallbacks
-    def _checkNoRecord(self, db, guid):
+    def _checkNoRecord(self, db, uid):
         
-        record = (yield db.getAugmentRecord(guid))
+        record = (yield db.getAugmentRecord(uid))
         self.assertTrue(record is None)
 
 class AugmentXMLTests(AugmentTests):
@@ -99,7 +99,7 @@
 """), db)
         self.assertRaises(RuntimeError, XMLAugmentsParser, cStringIO.StringIO("""<?xml version="1.0" encoding="utf-8"?>
   <record>
-    <guid>admin</guid>
+    <uid>admin</uid>
     <enable>true</enable>
     <foo/>
   </record>
@@ -113,8 +113,7 @@
         db = AugmentSqliteDB(self.mktemp())
 
         dbxml = AugmentXMLDB((xmlFile,))
-        for record in dbxml.db.values():
-            yield db.addAugmentRecord(record)
+        yield db.addAugmentRecords(dbxml.db.values())
 
         for item in testRecords:
             yield self._checkRecord(db, item)
@@ -127,8 +126,7 @@
         db = AugmentSqliteDB(self.mktemp())
 
         dbxml = AugmentXMLDB((xmlFileDefault,))
-        for record in dbxml.db.values():
-            yield db.addAugmentRecord(record)
+        yield db.addAugmentRecords(dbxml.db.values())
 
         for item in testRecords:
             yield self._checkRecord(db, item)
@@ -144,8 +142,7 @@
         yield db.clean()
 
         dbxml = AugmentXMLDB((xmlFile,))
-        for record in dbxml.db.values():
-            yield db.addAugmentRecord(record)
+        yield db.addAugmentRecords(dbxml.db.values())
 
         for item in testRecords:
             yield self._checkRecord(db, item)
@@ -159,8 +156,7 @@
         yield db.clean()
 
         dbxml = AugmentXMLDB((xmlFileDefault,))
-        for record in dbxml.db.values():
-            yield db.addAugmentRecord(record)
+        yield db.addAugmentRecords(dbxml.db.values())
 
         for item in testRecords:
             yield self._checkRecord(db, item)

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_cachedirectory.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_cachedirectory.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_cachedirectory.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -68,7 +68,7 @@
                     ) 
                     
                     augmentRecord = AugmentRecord(
-                        guid = cacheRecord.guid,
+                        uid = cacheRecord.guid,
                         enabled=True,
                         enabledForCalendaring = True,
                     )

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_modify.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_modify.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_modify.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -52,13 +52,19 @@
         record = directory.recordWithUID("resource01")
         self.assertEquals(record, None)
 
-        directory.createRecord("resources", "resource01", shortNames=("resource01",), uid="resource01")
+        directory.createRecord("resources", guid="resource01",
+            shortNames=("resource01",), uid="resource01",
+            emailAddresses=("res1 at example.com", "res2 at example.com"),
+            comment="Test Comment")
 
         record = directory.recordWithUID("resource01")
         self.assertNotEquals(record, None)
 
-        directory.createRecord("resources", "resource02", shortNames=("resource02",), uid="resource02")
+        self.assertEquals(len(record.emailAddresses), 2)
+        self.assertEquals(record.extras['comment'], "Test Comment")
 
+        directory.createRecord("resources", guid="resource02", shortNames=("resource02",), uid="resource02")
+
         record = directory.recordWithUID("resource02")
         self.assertNotEquals(record, None)
 
@@ -74,12 +80,12 @@
         record = directory.recordWithUID("resource01")
         self.assertEquals(record, None)
 
-        directory.createRecord("resources", "resource01", shortNames=("resource01",), uid="resource01")
+        directory.createRecord("resources", guid="resource01", shortNames=("resource01",), uid="resource01")
 
         record = directory.recordWithUID("resource01")
         self.assertNotEquals(record, None)
 
-        directory.destroyRecord("resources", "resource01")
+        directory.destroyRecord("resources", guid="resource01")
 
         record = directory.recordWithUID("resource01")
         self.assertEquals(record, None)
@@ -91,17 +97,18 @@
     def test_updateRecord(self):
         directory = getDirectory()
 
-        directory.createRecord("resources", "resource01",
+        directory.createRecord("resources", guid="resource01",
             shortNames=("resource01",), uid="resource01",
             fullName="Resource number 1")
 
         record = directory.recordWithUID("resource01")
         self.assertEquals(record.fullName, "Resource number 1")
 
-        directory.updateRecord("resources", "resource01",
+        directory.updateRecord("resources", guid="resource01",
             shortNames=("resource01", "r01"), uid="resource01",
             fullName="Resource #1", firstName="First", lastName="Last",
-            emailAddresses=("resource01 at example.com", "r01 at example.com"))
+            emailAddresses=("resource01 at example.com", "r01 at example.com"),
+            comment="Test Comment")
 
         record = directory.recordWithUID("resource01")
         self.assertEquals(record.fullName, "Resource #1")
@@ -110,6 +117,7 @@
         self.assertEquals(set(record.shortNames), set(["resource01", "r01"]))
         self.assertEquals(record.emailAddresses,
             set(["resource01 at example.com", "r01 at example.com"]))
+        self.assertEquals(record.extras['comment'], "Test Comment")
 
         # Make sure old records are still there:
         record = directory.recordWithUID("location01")
@@ -118,5 +126,5 @@
     def test_createDuplicateRecord(self):
         directory = getDirectory()
 
-        directory.createRecord("resources", "resource01", shortNames=("resource01",), uid="resource01")
-        self.assertRaises(DirectoryError, directory.createRecord, "resources", "resource01", shortNames=("resource01",), uid="resource01")
+        directory.createRecord("resources", guid="resource01", shortNames=("resource01",), uid="resource01")
+        self.assertRaises(DirectoryError, directory.createRecord, "resources", guid="resource01", shortNames=("resource01",), uid="resource01")

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_xmlfile.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_xmlfile.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/test/test_xmlfile.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -159,7 +159,7 @@
 <!DOCTYPE accounts SYSTEM "accounts.dtd">
 <augments>
   <record>
-    <guid>myoffice</guid>
+    <uid>myoffice</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
     <auto-schedule>true</auto-schedule>
@@ -229,6 +229,71 @@
         self.assertFalse(service.recordWithShortName(DirectoryService.recordType_groups, "disabled").enabledForCalendaring)
 
 
+    def test_readExtras(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>
+    <guid>myoffice</guid>
+    <name>My Office</name>
+    <extras>
+        <comment>This is the comment</comment>
+        <capacity>40</capacity>
+    </extras>
+  </location>
+</accounts>
+"""
+        )
+        
+        record = service.recordWithShortName(
+            DirectoryService.recordType_locations, "my office")
+        self.assertEquals(record.guid, "myoffice")
+        self.assertEquals(record.extras["comment"], "This is the comment")
+        self.assertEquals(record.extras["capacity"], "40")
+
+    def test_writeExtras(self):
+        service = self.service()
+
+        service.createRecord(DirectoryService.recordType_locations, "newguid",
+            shortNames=("New office",),
+            fullName="My New Office",
+            address="1 Infinite Loop, Cupertino, CA",
+            capacity="10",
+            comment="Test comment",
+        )
+
+        record = service.recordWithShortName(
+            DirectoryService.recordType_locations, "New office")
+        self.assertEquals(record.extras["comment"], "Test comment")
+        self.assertEquals(record.extras["capacity"], "10")
+
+
+        service.updateRecord(DirectoryService.recordType_locations, "newguid",
+            shortNames=("New office",),
+            fullName="My Newer Office",
+            address="2 Infinite Loop, Cupertino, CA",
+            capacity="20",
+            comment="Test comment updated",
+        )
+
+        record = service.recordWithShortName(
+            DirectoryService.recordType_locations, "New office")
+        self.assertEquals(record.fullName, "My Newer Office")
+        self.assertEquals(record.extras["address"], "2 Infinite Loop, Cupertino, CA")
+        self.assertEquals(record.extras["comment"], "Test comment updated")
+        self.assertEquals(record.extras["capacity"], "20")
+
+        service.destroyRecord(DirectoryService.recordType_locations, "newguid")
+
+        record = service.recordWithShortName(
+            DirectoryService.recordType_locations, "New office")
+        self.assertEquals(record, None)
+
+
 class XMLFileSubset (XMLFileBase, TestCase):
     """
     Test the recordTypes subset feature of XMLFile service.

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/xmlaccountsparser.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/xmlaccountsparser.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/xmlaccountsparser.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -48,6 +48,7 @@
 ELEMENT_EMAIL_ADDRESS     = "email-address"
 ELEMENT_MEMBERS           = "members"
 ELEMENT_MEMBER            = "member"
+ELEMENT_EXTRAS            = "extras"
 
 ATTRIBUTE_REALM           = "realm"
 ATTRIBUTE_REPEAT          = "repeat"
@@ -156,6 +157,7 @@
         self.emailAddresses = set()
         self.members = set()
         self.groups = set()
+        self.extras = {}
 
     def repeat(self, ctr):
         """
@@ -205,6 +207,7 @@
         result.lastName = lastName
         result.emailAddresses = emailAddresses
         result.members = self.members
+        result.extras = self.extras
         return result
 
     def parseXML(self, node):
@@ -237,6 +240,8 @@
                     self.emailAddresses.add(child.firstChild.data.encode("utf-8").lower())
             elif child_name == ELEMENT_MEMBERS:
                 self._parseMembers(child, self.members)
+            elif child_name == ELEMENT_EXTRAS:
+                self._parseExtras(child, self.extras)
             else:
                 raise RuntimeError("Unknown account attribute: %s" % (child_name,))
 
@@ -252,3 +257,10 @@
                     recordType = DirectoryService.recordType_users
                 if child.firstChild is not None:
                     addto.add((recordType, child.firstChild.data.encode("utf-8")))
+
+    def _parseExtras(self, node, addto):
+        for child in node._get_childNodes():
+            key = child._get_localName()
+            if key:
+                value = child.firstChild.data.encode("utf-8")
+                addto[key.encode("utf-8")] = value

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/xmlaugmentsparser.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/xmlaugmentsparser.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/xmlaugmentsparser.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -33,11 +33,11 @@
 ELEMENT_AUGMENTS          = "augments"
 ELEMENT_RECORD            = "record"
 
-ELEMENT_GUID              = "guid"
+ELEMENT_UID               = "uid"
 ELEMENT_ENABLE            = "enable"
 ELEMENT_HOSTEDAT          = "hosted-at"
+ELEMENT_ENABLECALENDAR    = "enable-calendar"
 ELEMENT_ENABLEADDRESSBOOK = "enable-addressbook"
-ELEMENT_ENABLECALENDAR    = "enable-calendar"
 ELEMENT_AUTOSCHEDULE      = "auto-schedule"
 
 ATTRIBUTE_REPEAT          = "repeat"
@@ -46,7 +46,7 @@
 VALUE_FALSE               = "false"
 
 ELEMENT_AUGMENTRECORD_MAP = {
-    ELEMENT_GUID:              "guid",
+    ELEMENT_UID:               "uid",
     ELEMENT_ENABLE:            "enabled",
     ELEMENT_HOSTEDAT:          "hostedAt",
     ELEMENT_ENABLECALENDAR:    "enabledForCalendaring",
@@ -95,23 +95,23 @@
             for node in child.getchildren():
                 
                 if node.tag in (
-                    ELEMENT_GUID,
+                    ELEMENT_UID,
                     ELEMENT_HOSTEDAT,
                 ):
                     fields[node.tag] = node.text
                 elif node.tag in (
                     ELEMENT_ENABLE,
                     ELEMENT_ENABLECALENDAR,
+                    ELEMENT_ENABLEADDRESSBOOK,
                     ELEMENT_AUTOSCHEDULE,
-                    ELEMENT_ENABLEADDRESSBOOK
                 ):
                     fields[node.tag] = node.text == VALUE_TRUE
                 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)
+            # Must have at least a uid
+            if ELEMENT_UID not in fields:
+                log.error("Invalid record '%s' without a uid in augment file: '%s'" % (child, self.xmlFile,), raiseException=RuntimeError)
                 
             if repeat > 1:
                 for i in xrange(1, repeat+1):
@@ -137,4 +137,4 @@
             actualFields[ELEMENT_AUGMENTRECORD_MAP[k]] = expandCount(v, count)
 
         record = AugmentRecord(**actualFields)
-        self.items[record.guid] = record
+        self.items[record.uid] = record

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/xmlfile.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/xmlfile.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/directory/xmlfile.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -276,6 +276,10 @@
         ET.SubElement(element, "last-name").text = principal.lastName
         for value in principal.emailAddresses:
             ET.SubElement(element, "email-address").text = value
+        if principal.extras:
+            extrasElement = ET.SubElement(element, "extras")
+            for key, value in principal.extras.iteritems():
+                ET.SubElement(extrasElement, key).text = value
 
         return element
 
@@ -311,7 +315,7 @@
 
     def createRecord(self, recordType, guid=None, shortNames=(), authIDs=set(),
         fullName=None, firstName=None, lastName=None, emailAddresses=set(),
-        uid=None, password=None, **kwds):
+        uid=None, password=None, **kwargs):
         """
         Create and persist a record using the provided information.  In this
         XML-based implementation, the xml accounts are read in and converted
@@ -341,12 +345,13 @@
         xmlPrincipal.firstName = firstName
         xmlPrincipal.lastName = lastName
         xmlPrincipal.emailAddresses = emailAddresses
+        xmlPrincipal.extras = kwargs
         self._addElement(accountsElement, xmlPrincipal)
 
         self._persistRecords(accountsElement)
 
 
-    def destroyRecord(self, recordType, guid):
+    def destroyRecord(self, recordType, guid=None):
         """
         Remove the record matching guid.  In this XML-based implementation,
         the xml accounts are read in and those not matching the given guid are
@@ -368,9 +373,9 @@
         self._persistRecords(accountsElement)
 
 
-    def updateRecord(self, recordType, guid, shortNames=(), authIDs=set(),
+    def updateRecord(self, recordType, guid=None, shortNames=(), authIDs=set(),
         fullName=None, firstName=None, lastName=None, emailAddresses=set(),
-        uid=None, password=None, **kwds):
+        uid=None, password=None, **kwargs):
         """
         Update the record matching guid.  In this XML-based implementation,
         the xml accounts are read in and converted to elementtree elements.
@@ -394,6 +399,7 @@
                     xmlPrincipal.firstName = firstName
                     xmlPrincipal.lastName = lastName
                     xmlPrincipal.emailAddresses = emailAddresses
+                    xmlPrincipal.extras = kwargs
                     self._addElement(accountsElement, xmlPrincipal)
                 else:
                     self._addElement(accountsElement, xmlPrincipal)
@@ -415,6 +421,7 @@
             firstName             = xmlPrincipal.firstName,
             lastName              = xmlPrincipal.lastName,
             emailAddresses        = xmlPrincipal.emailAddresses,
+            **xmlPrincipal.extras
         )
 
         self.password          = xmlPrincipal.password

Modified: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/mail.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/mail.py	2010-02-12 22:27:11 UTC (rev 5104)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/mail.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -46,6 +46,7 @@
 from twisted.python.reflect import namedClass
 from twisted.python.usage import Options, UsageError
 from twisted.web import client
+
 from twext.web2 import server, responsecode
 from twext.web2.channel.http import HTTPFactory
 from twext.web2.dav import auth
@@ -53,13 +54,13 @@
 from twext.web2.dav.noneprops import NonePropertyStore
 from twext.web2.http import Response, HTTPError
 from twext.web2.http_headers import MimeType
+from twext.web2.auth.basic import BasicCredentialFactory
 
 from twext.log import Logger, LoggingMixIn
 
 from twistedcaldav import ical, caldavxml
 from twistedcaldav import memcachepool
 from twistedcaldav.config import config
-from twistedcaldav.directory.digest import QopDigestCredentialFactory
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
 from twistedcaldav.directory.util import NotFilePath
 from twistedcaldav.ical import Property
@@ -70,8 +71,6 @@
 from twistedcaldav.static import CalDAVFile, deliverSchedulePrivilegeSet
 from twistedcaldav.util import AuthorizedHTTPGetter
 from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
-from twistedcaldav.sql import AbstractSQLDatabase
-from twistedcaldav.localization import translationTo
 
 from calendarserver.provision.root import RootResource
 
@@ -605,17 +604,11 @@
         portal = Portal(auth.DavRealm())
         portal.registerChecker(directory)
         realm = directory.realmName or ""
-        schemeConfig = config.Authentication.Digest
-        digestCredentialFactory = QopDigestCredentialFactory(
-            schemeConfig["Algorithm"],
-            schemeConfig["Qop"],
-            realm,
-        )
         root.putChild('inbox',
             auth.AuthenticationWrapper(
                 IMIPInvitationInboxResource(root, mailer),
                 portal,
-                (digestCredentialFactory,),
+                (BasicCredentialFactory(realm),),
                 (auth.IPrincipal,),
             )
         )

Copied: CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/xmlutil.py (from rev 5104, CalendarServer/trunk/twistedcaldav/xmlutil.py)
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/xmlutil.py	                        (rev 0)
+++ CalendarServer/branches/users/glyph/use-system-twisted/twistedcaldav/xmlutil.py	2010-02-12 22:49:09 UTC (rev 5105)
@@ -0,0 +1,99 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from xml.etree.ElementTree import Element, ElementTree, SubElement, tostring
+from xml.parsers.expat import ExpatError
+
+# Utilities for working with ElementTree
+
+def readXML(xmlfile, expectedRootTag=None):
+    """
+    Read in XML data from a file and parse into ElementTree. Optionally verify
+    the root node is what we expect.
+    
+    @param xmlfile: file to read from
+    @type xmlfile: C{File}
+    @param expectedRootTag: root tag (qname) to test or C{None}
+    @type expectedRootTag: C{str}
+    @return: C{tuple} of C{ElementTree}, C{Element}
+    """
+
+    # Read in XML
+    try:
+        etree = ElementTree(file=xmlfile)
+    except ExpatError, e:
+        ValueError("Unable to parse file '%s' because: %s" % (xmlfile, e,))
+
+    if expectedRootTag:
+        root = etree.getroot()
+        if root.tag != expectedRootTag:
+            ValueError("Ignoring file '%s' because it is not a %s file" % (xmlfile, expectedRootTag,))
+    
+    return etree, etree.getroot()
+
+def writeXML(xmlfile, root):
+    
+    data = """<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE %s SYSTEM "%s.dtd">
+
+""" % (root.tag, root.tag)
+
+    INDENT = 2
+
+    # Generate indentation
+    def _indentNode(node, level=0):
+        
+        if node.text is not None and node.text.strip():
+            return
+        elif len(node.getchildren()):
+            indent = "\n" + " " * (level + 1) * INDENT
+            node.text = indent
+            for child in node.getchildren():
+                child.tail = indent
+                _indentNode(child, level + 1)
+            if len(node.getchildren()):
+                node.getchildren()[-1].tail = "\n" + " " * level * INDENT
+
+    _indentNode(root, 0)
+    data += tostring(root) + "\n"
+
+    with open(xmlfile, "w") as f:
+        f.write(data)
+
+def newElementTreeWithRoot(roottag):
+
+    root = createElement(roottag)
+    etree = ElementTree(root)
+    
+    return etree, root
+
+def createElement(tag, text=None):
+
+    child = Element(tag)
+    child.text = text
+    return child
+
+def addSubElement(parent, tag, text=None):
+
+    child = SubElement(parent, tag)
+    child.text = text
+    return child
+
+def changeSubElementText(parent, tag, text):
+    
+    child = parent.find(tag)
+    child.text = text
+
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100212/b80a8871/attachment-0001.html>


More information about the calendarserver-changes mailing list