[CalendarServer-changes] [7377] CalendarServer/branches/users/cdaboo/pods

source_changes at macosforge.org source_changes at macosforge.org
Wed Apr 27 14:09:24 PDT 2011


Revision: 7377
          http://trac.macosforge.org/projects/calendarserver/changeset/7377
Author:   cdaboo at apple.com
Date:     2011-04-27 14:09:24 -0700 (Wed, 27 Apr 2011)
Log Message:
-----------
Merged from trunk and fixed some tests.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/pods/bin/caldavd
    CalendarServer/branches/users/cdaboo/pods/calendarserver/platform/darwin/od/opendirectory.py
    CalendarServer/branches/users/cdaboo/pods/calendarserver/platform/darwin/od/test/test_opendirectory.py
    CalendarServer/branches/users/cdaboo/pods/calendarserver/tap/caldav.py
    CalendarServer/branches/users/cdaboo/pods/calendarserver/tools/test/test_gateway.py
    CalendarServer/branches/users/cdaboo/pods/calendarserver/tools/test/test_principals.py
    CalendarServer/branches/users/cdaboo/pods/conf/auth/accounts-test.xml
    CalendarServer/branches/users/cdaboo/pods/conf/caldavd-apple.plist
    CalendarServer/branches/users/cdaboo/pods/conf/caldavd-test.plist
    CalendarServer/branches/users/cdaboo/pods/conf/caldavd.plist
    CalendarServer/branches/users/cdaboo/pods/contrib/migration/calendarmigrator.py
    CalendarServer/branches/users/cdaboo/pods/contrib/migration/test/test_migrator.py
    CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/config.plist
    CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/ical.py
    CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/population.py
    CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/profiles.py
    CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/sim.py
    CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/test_profiles.py
    CalendarServer/branches/users/cdaboo/pods/doc/calendarserver_export.8
    CalendarServer/branches/users/cdaboo/pods/python
    CalendarServer/branches/users/cdaboo/pods/support/Makefile.Apple
    CalendarServer/branches/users/cdaboo/pods/support/build.sh
    CalendarServer/branches/users/cdaboo/pods/support/py.sh
    CalendarServer/branches/users/cdaboo/pods/support/shell.sh
    CalendarServer/branches/users/cdaboo/pods/testserver
    CalendarServer/branches/users/cdaboo/pods/twext/enterprise/adbapi2.py
    CalendarServer/branches/users/cdaboo/pods/twext/enterprise/dal/model.py
    CalendarServer/branches/users/cdaboo/pods/twext/enterprise/dal/syntax.py
    CalendarServer/branches/users/cdaboo/pods/twext/enterprise/dal/test/test_sqlsyntax.py
    CalendarServer/branches/users/cdaboo/pods/twext/enterprise/test/test_adbapi2.py
    CalendarServer/branches/users/cdaboo/pods/twext/enterprise/util.py
    CalendarServer/branches/users/cdaboo/pods/twistedcaldav/config.py
    CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/appleopendirectory.py
    CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/augment.py
    CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/cachingdirectory.py
    CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/test/test_augment.py
    CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/test/test_opendirectory.py
    CalendarServer/branches/users/cdaboo/pods/twistedcaldav/resource.py
    CalendarServer/branches/users/cdaboo/pods/twistedcaldav/scheduling/ischedule.py
    CalendarServer/branches/users/cdaboo/pods/twistedcaldav/servers.py
    CalendarServer/branches/users/cdaboo/pods/twistedcaldav/stdconfig.py
    CalendarServer/branches/users/cdaboo/pods/twistedcaldav/test/test_config.py
    CalendarServer/branches/users/cdaboo/pods/twistedcaldav/test/test_upgrade.py
    CalendarServer/branches/users/cdaboo/pods/twistedcaldav/upgrade.py
    CalendarServer/branches/users/cdaboo/pods/txdav/base/datastore/dbapiclient.py
    CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/calendar_store/ho/me/home1/calendar_1/1.ics
    CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/common.py
    CalendarServer/branches/users/cdaboo/pods/txdav/common/datastore/sql_tables.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/
    CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/__init__.py
    CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/calendarcertupdate.py
    CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/
    CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/__init__.py
    CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/test_certupdate.py
    CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/logger.py

Removed Paths:
-------------
    CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/__init__.py
    CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/calendarcertupdate.py
    CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/
    CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/__init__.py
    CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/test_certupdate.py

Property Changed:
----------------
    CalendarServer/branches/users/cdaboo/pods/
    CalendarServer/branches/users/cdaboo/pods/contrib/performance/sim
    CalendarServer/branches/users/cdaboo/pods/support/build.sh
    CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/index_file.py
    CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/test_index_file.py
    CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/index_file.py
    CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/test/test_index_file.py


Property changes on: CalendarServer/branches/users/cdaboo/pods
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/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/branches/users/wsanchez/transations:5515-5593
   + /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/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/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:7297-7364

Modified: CalendarServer/branches/users/cdaboo/pods/bin/caldavd
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/bin/caldavd	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/bin/caldavd	2011-04-27 21:09:24 UTC (rev 7377)
@@ -51,7 +51,7 @@
   return 0;
 }
 
-for v in "" "2.6" "2.5"; do
+for v in "2.7" "2.6" "2.5" ""; do
   for p in                                                              \
     "${PYTHON:=}"                                                       \
     "python${v}"                                                        \

Modified: CalendarServer/branches/users/cdaboo/pods/calendarserver/platform/darwin/od/opendirectory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/calendarserver/platform/darwin/od/opendirectory.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/calendarserver/platform/darwin/od/opendirectory.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -23,7 +23,23 @@
 import dsattributes
 import base64
 from twext.python.log import Logger
+import Foundation
 
+
+def autoPooled(f):
+    """
+    A decorator which creates an autorelease pool and deletes it, causing it
+    to drain
+    """
+    def autoPooledFunction(*args, **kwds):
+        pool = Foundation.NSAutoreleasePool.alloc().init()
+        try:
+            return f(*args, **kwds)
+        finally:
+            del pool
+    return autoPooledFunction
+
+
 log = Logger()
 
 NUM_TRIES = 3
@@ -136,6 +152,7 @@
     return names, encodings
 
 
+ at autoPooled
 def odInit(nodeName):
     """
     Create an Open Directory object to operate on the specified directory service node name.
@@ -167,6 +184,7 @@
 
 
 
+ at autoPooled
 def getNodeAttributes(directory, nodeName, attributes):
     """
     Return key attributes for the specified directory node. The attributes
@@ -198,6 +216,7 @@
     raise ODError(error)
 
 
+ at autoPooled
 def listAllRecordsWithAttributes_list(directory, recordType, attributes, count=0):
     """
     List records in Open Directory, and return key attributes for each one.
@@ -212,7 +231,6 @@
         for each record found, or C{None} otherwise.
     """
     results = []
-
     attributeNames, encodings = attributeNamesFromList(attributes)
 
     tries = NUM_TRIES
@@ -246,6 +264,8 @@
     log.error(error)
     raise ODError(error)
 
+
+ at autoPooled
 def queryRecordsWithAttribute_list(directory, attr, value, matchType, casei, recordType, attributes, count=0):
     """
     List records in Open Directory matching specified attribute/value, and return key attributes for each one.
@@ -263,9 +283,7 @@
     @return: C{list} containing a C{list} of C{str} (record name) and C{dict} attributes
         for each record found, or C{None} otherwise.
     """
-
     results = []
-
     attributeNames, encodings = attributeNamesFromList(attributes)
 
     tries = NUM_TRIES
@@ -301,6 +319,7 @@
     raise ODError(error)
 
 
+ at autoPooled
 def queryRecordsWithAttributes_list(directory, compound, casei, recordType, attributes, count=0):
     """
     List records in Open Directory matching specified criteria, and return key attributes for each one.
@@ -385,6 +404,7 @@
     raise ODError(error)
 
 
+ at autoPooled
 def authenticateUserBasic(directory, nodeName, user, password):
     """
     Authenticate a user with a password to Open Directory.
@@ -428,6 +448,7 @@
     raise ODError(error)
 
 
+ at autoPooled
 def authenticateUserDigest(directory, nodeName, user, challenge, response, method):
     """
     Authenticate using HTTP Digest credentials to Open Directory.

Modified: CalendarServer/branches/users/cdaboo/pods/calendarserver/platform/darwin/od/test/test_opendirectory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/calendarserver/platform/darwin/od/test/test_opendirectory.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/calendarserver/platform/darwin/od/test/test_opendirectory.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -898,3 +898,13 @@
                 (["a", "b"], {"b":"base64"}),
                 opendirectory.attributeNamesFromList(["a", ("b", "base64")])
             )
+
+        def test_autoPooled(self):
+            """
+            Make sure no exception is raised by an autoPooled method
+            """
+            @opendirectory.autoPooled
+            def method(x):
+                return x + 1
+
+            self.assertEquals(2, method(1))

Modified: CalendarServer/branches/users/cdaboo/pods/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/calendarserver/tap/caldav.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/calendarserver/tap/caldav.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -120,16 +120,35 @@
     return (uid, gid)
 
 
-PARENT_ENVIRONMENT = {
-    "PATH": os.environ.get("PATH", ""),
-    "PYTHONPATH": os.environ.get("PYTHONPATH", ""),
-    "LD_LIBRARY_PATH": os.environ.get("LD_LIBRARY_PATH", ""),
-    "DYLD_LIBRARY_PATH": os.environ.get("DYLD_LIBRARY_PATH", ""),
-}
 
-if "KRB5_KTNAME" in os.environ:
-    PARENT_ENVIRONMENT["KRB5_KTNAME"] = os.environ["KRB5_KTNAME"]
+def _computeEnvVars(parent):
+    """
+    Compute environment variables to be propagated to child processes.
+    """
+    result = {}
+    requiredVars = [
+        "PATH",
+        "PYTHONPATH",
+        "LD_LIBRARY_PATH",
+        "DYLD_LIBRARY_PATH",
+    ]
 
+    optionalVars = [
+        "KRB5_KTNAME",
+        "ORACLE_HOME",
+    ]
+
+    for varname in requiredVars:
+        result[varname] = parent.get(varname, "")
+    for varname in optionalVars:
+        if varname in parent:
+            result[varname] = parent[varname]
+    return result
+
+PARENT_ENVIRONMENT = _computeEnvVars(os.environ)
+
+
+
 class CalDAVStatisticsProtocol (Protocol):
 
     def connectionMade(self):

Modified: CalendarServer/branches/users/cdaboo/pods/calendarserver/tools/test/test_gateway.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/calendarserver/tools/test/test_gateway.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/calendarserver/tools/test/test_gateway.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -15,6 +15,7 @@
 ##
 
 import os
+import sys
 from twext.python.plistlib import readPlistFromString
 import xml
 
@@ -81,7 +82,7 @@
             command = command.encode("utf-8")
 
         sourceRoot = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
-        python = os.path.join(sourceRoot, "python")
+        python = sys.executable
         gateway = os.path.join(sourceRoot, "bin", "calendarserver_command_gateway")
 
         args = [python, gateway, "-f", self.configFileName]

Modified: CalendarServer/branches/users/cdaboo/pods/calendarserver/tools/test/test_principals.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/calendarserver/tools/test/test_principals.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/calendarserver/tools/test/test_principals.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -15,6 +15,7 @@
 ##
 
 import os
+import sys
 
 from twext.python.filepath import CachingFilePath as FilePath
 from twisted.internet import reactor
@@ -83,7 +84,7 @@
         Run calendarserver_manage_principals, passing additional as args.
         """
         sourceRoot = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
-        python = os.path.join(sourceRoot, "python")
+        python = sys.executable
         script = os.path.join(sourceRoot, "bin", "calendarserver_manage_principals")
 
         args = [python, script, "-f", self.configFileName]

Modified: CalendarServer/branches/users/cdaboo/pods/conf/auth/accounts-test.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/conf/auth/accounts-test.xml	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/conf/auth/accounts-test.xml	2011-04-27 21:09:24 UTC (rev 7377)
@@ -38,7 +38,7 @@
   <user>
     <uid>wsanchez</uid>
     <guid>wsanchez</guid>
-    <email-address>wsanchez at apple.com</email-address>
+    <email-address>wsanchez at example.com</email-address>
     <password>test</password>
     <name>Wilfredo Sanchez Vega</name>
     <first-name>Wilfredo</first-name>
@@ -47,7 +47,7 @@
   <user>
     <uid>cdaboo</uid>
     <guid>cdaboo</guid>
-    <email-address>cdaboo at apple.com</email-address>
+    <email-address>cdaboo at example.com</email-address>
     <password>test</password>
     <name>Cyrus Daboo</name>
     <first-name>Cyrus</first-name>
@@ -56,7 +56,7 @@
   <user>
     <uid>sagen</uid>
     <guid>sagen</guid>
-    <email-address>sagen at apple.com</email-address>
+    <email-address>sagen at example.com</email-address>
     <password>test</password>
     <name>Morgen Sagen</name>
     <first-name>Morgen</first-name>
@@ -65,7 +65,7 @@
   <user>
     <uid>dre</uid>
     <guid>andre</guid>
-    <email-address>dre at apple.com</email-address>
+    <email-address>dre at example.com</email-address>
     <password>test</password>
     <name>Andre LaBranche</name>
     <first-name>Andre</first-name>
@@ -74,12 +74,21 @@
   <user>
     <uid>glyph</uid>
     <guid>glyph</guid>
-    <email-address>glyph at apple.com</email-address>
+    <email-address>glyph at example.com</email-address>
     <password>test</password>
     <name>Glyph Lefkowitz</name>
     <first-name>Glyph</first-name>
     <last-name>Lefkowitz</last-name>
   </user>
+  <user>
+    <uid>i18nuser</uid>
+    <guid>i18nuser</guid>
+    <email-address>i18nuser at example.com</email-address>
+    <password>i18nuser</password>
+    <name>まだ</name>
+    <first-name>ま</first-name>
+    <last-name>だ</last-name>
+  </user>
   <user repeat="99">
     <uid>user%02d</uid>
     <uid>User %02d</uid>

Modified: CalendarServer/branches/users/cdaboo/pods/conf/caldavd-apple.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/conf/caldavd-apple.plist	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/conf/caldavd-apple.plist	2011-04-27 21:09:24 UTC (rev 7377)
@@ -171,21 +171,6 @@
         A variety of directory services are available for use.
       -->
 
-    <!-- XML File Directory Service -->
-    <!--
-    <key>DirectoryService</key>
-    <dict>
-      <key>type</key>
-      <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
-      
-      <key>params</key>
-      <dict>
-        <key>xmlFile</key>
-        <string>accounts.xml</string>
-      </dict>
-    </dict>
-    -->
-    
     <!-- Open Directory Service (Mac OS X) -->
     <key>DirectoryService</key>
     <dict>

Modified: CalendarServer/branches/users/cdaboo/pods/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/conf/caldavd-test.plist	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/conf/caldavd-test.plist	2011-04-27 21:09:24 UTC (rev 7377)
@@ -94,6 +94,14 @@
     <key>ServerRoot</key>
     <string>./data</string>
 
+    <!-- Database connection -->
+    <!--
+    <key>DBType</key>
+    <string>postgres</string>
+    <key>DSN</key>
+    <string>:caldav:caldav:::</string>
+     -->
+
     <!-- Data root -->
     <key>DataRoot</key>
     <string>Data</string>

Modified: CalendarServer/branches/users/cdaboo/pods/conf/caldavd.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/conf/caldavd.plist	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/conf/caldavd.plist	2011-04-27 21:09:24 UTC (rev 7377)
@@ -80,6 +80,14 @@
     <key>ServerRoot</key>
     <string>/var/db/caldavd</string>
 
+    <!-- Database connection -->
+    <!--
+    <key>DBType</key>
+    <string>postgres</string>
+    <key>DSN</key>
+    <string>:caldav:caldav:::</string>
+     -->
+
     <!-- Data root -->
     <key>DataRoot</key>
     <string>Data</string>

Deleted: CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/__init__.py
===================================================================
--- CalendarServer/trunk/contrib/certupdate/__init__.py	2011-04-27 18:04:16 UTC (rev 7364)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/__init__.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -1,15 +0,0 @@
-##
-# Copyright (c) 2011 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.
-##

Copied: CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/__init__.py (from rev 7364, CalendarServer/trunk/contrib/certupdate/__init__.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/__init__.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/__init__.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2011 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.
+##

Deleted: CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/calendarcertupdate.py
===================================================================
--- CalendarServer/trunk/contrib/certupdate/calendarcertupdate.py	2011-04-27 18:04:16 UTC (rev 7364)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/calendarcertupdate.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -1,154 +0,0 @@
-#!/usr/bin/env python
-#
-# CertUpdate script for calendar / addresbook service.
-#
-# This script will be called with the path to the cert file in
-# /etc/certificates and also the keychain persistent reference if
-# we have one available. For the remove command, the handler
-# returns 0 = don't care, 1 = please keep, 2 = an error occurred.
-# For the replace command the handler returns
-# 0 = don't care/ cert replaced, 2 = an error occurred.
-#
-# Copyright (c) 2011 Apple Inc.  All Rights Reserved.
-#
-# IMPORTANT NOTE:  This file is licensed only for use on Apple-labeled
-# computers and is subject to the terms and conditions of the Apple
-# Software License Agreement accompanying the package this file is a
-# part of.  You may not port this file to another platform without
-# Apple's written consent.
-
-import datetime
-import os
-import subprocess
-import sys
-
-from plistlib import readPlist, readPlistFromString, writePlist
-
-LOG = "/var/log/caldavd/certupdate.log"
-SERVICE_NAME = "calendar"
-CALDAVD_PLIST = "/etc/caldavd/caldavd.plist"
-SERVER_ADMIN = "/usr/sbin/serveradmin"
-
-def main():
-
-    log(sys.argv)
-    numArgs = len(sys.argv) - 1
-    if numArgs == 3:
-        if sys.argv[1] != "remove":
-            die("Bad command line; 'remove' expected", 2)
-        if isThisMyCert(CALDAVD_PLIST, sys.argv[2]):
-            die("%s is in use by calendar" % (sys.argv[2],), 1)
-        else:
-            die("%s is not in use by calendar" % (sys.argv[2],), 0)
-
-    elif numArgs == 5:
-        if sys.argv[1] != "replace":
-            die("Bad command line; 'replace' expected", 2)
-        if isThisMyCert(CALDAVD_PLIST, sys.argv[2]):
-            try:
-                replaceCert(CALDAVD_PLIST, sys.argv[4])
-                restartService(CALDAVD_PLIST)
-                die("Replaced calendar cert with %s" % (sys.argv[4],), 0)
-            except Exception, e:
-                die("Error replacing calendar cert with %s: %s" % (sys.argv[4], e), 2)
-
-        else:
-            die("%s is not in use by calendar" % (sys.argv[2],), 0)
-
-    else:
-        # Wrong number of args
-        die("Bad command line; incorrect number of arguments", 2)
-
-
-def getMyCert(plistPath):
-    """
-    Return SSLCertificate from the plist at plistPath
-    """
-    plist = readPlist(plistPath)
-    return plist.get("SSLCertificate", None)
-
-
-def isThisMyCert(plistPath, otherCert):
-    """
-    Compare otherCert against SSLCertificate from the plist at plistPath
-    """
-    myCert = getMyCert(plistPath)
-    return otherCert == myCert
-
-
-def replaceCert(plistPath, otherCert):
-    """
-    Replace SSL settings in plist at plistPath based on otherCert path
-    """
-    log("Reading plist %s" % (plistPath,))
-    plist = readPlist(plistPath)
-    log("Read in plist %s" % (plistPath,))
-
-    basePath = otherCert[:-len("cert.pem")]
-    log("Base path is %s" % (basePath,))
-
-    log("Setting SSLCertificate to %s" % (otherCert,))
-    plist["SSLCertificate"] = otherCert
-
-    otherChain = basePath + "chain.pem"
-    log("Setting SSLAuthorityChain to %s" % (otherChain,))
-    plist["SSLAuthorityChain"] = otherChain
-
-    otherKey = basePath + "key.pem"
-    log("Setting SSLPrivateKey to %s" % (otherKey,))
-    plist["SSLPrivateKey"] = otherKey
-
-    log("Writing plist %s" % (plistPath,))
-    writePlist(plist, plistPath)
-
-
-def restartService(plistPath):
-    """
-    Use serveradmin to restart the service.
-    """
-
-    plist = readPlist(plistPath)
-
-    if not plist.get("EnableSSL", False):
-        log("SSL is not enabled, so no need to restart")
-        return
-
-    if plist.get("EnableCardDAV", False):
-        log("Stopping addressbook service via serveradmin")
-        ret = subprocess.call([SERVER_ADMIN, "stop", "addressbook"])
-        log("serveradmin exited with %d" % (ret,))
-        log("Starting addressbook service via serveradmin")
-        ret = subprocess.call([SERVER_ADMIN, "start", "addressbook"])
-        log("serveradmin exited with %d" % (ret,))
-    elif plist.get("EnableCalDAV", False):
-        log("Stopping calendar service via serveradmin")
-        ret = subprocess.call([SERVER_ADMIN, "stop", "calendar"])
-        log("serveradmin exited with %d" % (ret,))
-        log("Starting calendar service via serveradmin")
-        ret = subprocess.call([SERVER_ADMIN, "start", "calendar"])
-        log("serveradmin exited with %d" % (ret,))
-    else:
-        log("Neither calendar nor addressbook services were running")
-
-
-def log(msg):
-    try:
-        timestamp = datetime.datetime.now().strftime("%b %d %H:%M:%S")
-        msg = "calendarcertupdate: %s %s" % (timestamp, msg)
-        with open(LOG, 'a') as output:
-            output.write("%s\n" % (msg,)) # so it appears in our log
-    except IOError:
-        # Could not write to log
-        pass
-
-
-def die(msg, exitCode):
-    """
-    Log msg and exit with exitCode
-    """
-    log(msg)
-    sys.exit(exitCode)
-
-
-if __name__ == '__main__':
-    main()

Copied: CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/calendarcertupdate.py (from rev 7364, CalendarServer/trunk/contrib/certupdate/calendarcertupdate.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/calendarcertupdate.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/calendarcertupdate.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+#
+# CertUpdate script for calendar / addresbook service.
+#
+# This script will be called with the path to the cert file in
+# /etc/certificates and also the keychain persistent reference if
+# we have one available. For the remove command, the handler
+# returns 0 = don't care, 1 = please keep, 2 = an error occurred.
+# For the replace command the handler returns
+# 0 = don't care/ cert replaced, 2 = an error occurred.
+#
+# Copyright (c) 2011 Apple Inc.  All Rights Reserved.
+#
+# IMPORTANT NOTE:  This file is licensed only for use on Apple-labeled
+# computers and is subject to the terms and conditions of the Apple
+# Software License Agreement accompanying the package this file is a
+# part of.  You may not port this file to another platform without
+# Apple's written consent.
+
+import datetime
+import os
+import subprocess
+import sys
+
+from plistlib import readPlist, readPlistFromString, writePlist
+
+LOG = "/var/log/caldavd/certupdate.log"
+SERVICE_NAME = "calendar"
+CALDAVD_PLIST = "/etc/caldavd/caldavd.plist"
+SERVER_ADMIN = "/usr/sbin/serveradmin"
+
+def main():
+
+    log(sys.argv)
+    numArgs = len(sys.argv) - 1
+    if numArgs == 3:
+        if sys.argv[1] != "remove":
+            die("Bad command line; 'remove' expected", 2)
+        if isThisMyCert(CALDAVD_PLIST, sys.argv[2]):
+            die("%s is in use by calendar" % (sys.argv[2],), 1)
+        else:
+            die("%s is not in use by calendar" % (sys.argv[2],), 0)
+
+    elif numArgs == 5:
+        if sys.argv[1] != "replace":
+            die("Bad command line; 'replace' expected", 2)
+        if isThisMyCert(CALDAVD_PLIST, sys.argv[2]):
+            try:
+                replaceCert(CALDAVD_PLIST, sys.argv[4])
+                restartService(CALDAVD_PLIST)
+                die("Replaced calendar cert with %s" % (sys.argv[4],), 0)
+            except Exception, e:
+                die("Error replacing calendar cert with %s: %s" % (sys.argv[4], e), 2)
+
+        else:
+            die("%s is not in use by calendar" % (sys.argv[2],), 0)
+
+    else:
+        # Wrong number of args
+        die("Bad command line; incorrect number of arguments", 2)
+
+
+def getMyCert(plistPath):
+    """
+    Return SSLCertificate from the plist at plistPath
+    """
+    plist = readPlist(plistPath)
+    return plist.get("SSLCertificate", None)
+
+
+def isThisMyCert(plistPath, otherCert):
+    """
+    Compare otherCert against SSLCertificate from the plist at plistPath
+    """
+    myCert = getMyCert(plistPath)
+    return otherCert == myCert
+
+
+def replaceCert(plistPath, otherCert):
+    """
+    Replace SSL settings in plist at plistPath based on otherCert path
+    """
+    log("Reading plist %s" % (plistPath,))
+    plist = readPlist(plistPath)
+    log("Read in plist %s" % (plistPath,))
+
+    basePath = otherCert[:-len("cert.pem")]
+    log("Base path is %s" % (basePath,))
+
+    log("Setting SSLCertificate to %s" % (otherCert,))
+    plist["SSLCertificate"] = otherCert
+
+    otherChain = basePath + "chain.pem"
+    log("Setting SSLAuthorityChain to %s" % (otherChain,))
+    plist["SSLAuthorityChain"] = otherChain
+
+    otherKey = basePath + "key.pem"
+    log("Setting SSLPrivateKey to %s" % (otherKey,))
+    plist["SSLPrivateKey"] = otherKey
+
+    log("Writing plist %s" % (plistPath,))
+    writePlist(plist, plistPath)
+
+
+def restartService(plistPath):
+    """
+    Use serveradmin to restart the service.
+    """
+
+    plist = readPlist(plistPath)
+
+    if not plist.get("EnableSSL", False):
+        log("SSL is not enabled, so no need to restart")
+        return
+
+    if plist.get("EnableCardDAV", False):
+        log("Stopping addressbook service via serveradmin")
+        ret = subprocess.call([SERVER_ADMIN, "stop", "addressbook"])
+        log("serveradmin exited with %d" % (ret,))
+        log("Starting addressbook service via serveradmin")
+        ret = subprocess.call([SERVER_ADMIN, "start", "addressbook"])
+        log("serveradmin exited with %d" % (ret,))
+    elif plist.get("EnableCalDAV", False):
+        log("Stopping calendar service via serveradmin")
+        ret = subprocess.call([SERVER_ADMIN, "stop", "calendar"])
+        log("serveradmin exited with %d" % (ret,))
+        log("Starting calendar service via serveradmin")
+        ret = subprocess.call([SERVER_ADMIN, "start", "calendar"])
+        log("serveradmin exited with %d" % (ret,))
+    else:
+        log("Neither calendar nor addressbook services were running")
+
+
+def log(msg):
+    try:
+        timestamp = datetime.datetime.now().strftime("%b %d %H:%M:%S")
+        msg = "calendarcertupdate: %s %s" % (timestamp, msg)
+        with open(LOG, 'a') as output:
+            output.write("%s\n" % (msg,)) # so it appears in our log
+    except IOError:
+        # Could not write to log
+        pass
+
+
+def die(msg, exitCode):
+    """
+    Log msg and exit with exitCode
+    """
+    log(msg)
+    sys.exit(exitCode)
+
+
+if __name__ == '__main__':
+    main()

Deleted: CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/__init__.py
===================================================================
--- CalendarServer/trunk/contrib/certupdate/test/__init__.py	2011-04-27 18:04:16 UTC (rev 7364)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/__init__.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -1,15 +0,0 @@
-##
-# Copyright (c) 2011 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.
-##

Copied: CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/__init__.py (from rev 7364, CalendarServer/trunk/contrib/certupdate/test/__init__.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/__init__.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/__init__.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2011 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.
+##

Deleted: CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/test_certupdate.py
===================================================================
--- CalendarServer/trunk/contrib/certupdate/test/test_certupdate.py	2011-04-27 18:04:16 UTC (rev 7364)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/test_certupdate.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -1,65 +0,0 @@
-##
-# Copyright (c) 2011 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 tempfile import mkstemp
-import os
-import twistedcaldav.test.util
-from plistlib import readPlist
-from contrib.certupdate.calendarcertupdate import (
-    getMyCert, isThisMyCert, replaceCert
-)
-
-samplePlist = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <key>SSLAuthorityChain</key>
-    <string>/etc/certificates/original.chain.pem</string>
-    <key>SSLCertificate</key>
-    <string>/etc/certificates/original.cert.pem</string>
-    <key>SSLPrivateKey</key>
-    <string>/etc/certificates/original.key.pem</string>
-</dict>
-</plist>
-"""
-
-class CertUpdateTests(twistedcaldav.test.util.TestCase):
-    """
-    Calendar Server Certificate Update Tests
-    """
-
-    def setUp(self):
-        self.fd, self.path = mkstemp(suffix=".plist")
-        out = os.fdopen(self.fd, "w")
-        out.write(samplePlist)
-        out.close()
-
-    def tearDown(self):
-        os.remove(self.path)
-
-    def test_getMyCert(self):
-        self.assertEquals("/etc/certificates/original.cert.pem", getMyCert(self.path))
-
-    def test_isThisMyCert(self):
-        self.assertTrue(isThisMyCert(self.path, "/etc/certificates/original.cert.pem"))
-        self.assertFalse(isThisMyCert(self.path, "/etc/certificates/not.cert.pem"))
-
-    def test_replaceCert(self):
-        replaceCert(self.path, "/etc/certificates/new.cert.pem")
-        plist = readPlist(self.path)
-        self.assertEquals(plist["SSLAuthorityChain"], "/etc/certificates/new.chain.pem")
-        self.assertEquals(plist["SSLCertificate"], "/etc/certificates/new.cert.pem")
-        self.assertEquals(plist["SSLPrivateKey"], "/etc/certificates/new.key.pem")

Copied: CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/test_certupdate.py (from rev 7364, CalendarServer/trunk/contrib/certupdate/test/test_certupdate.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/test_certupdate.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/certupdate/test/test_certupdate.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -0,0 +1,65 @@
+##
+# Copyright (c) 2011 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 tempfile import mkstemp
+import os
+import twistedcaldav.test.util
+from plistlib import readPlist
+from contrib.certupdate.calendarcertupdate import (
+    getMyCert, isThisMyCert, replaceCert
+)
+
+samplePlist = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>SSLAuthorityChain</key>
+    <string>/etc/certificates/original.chain.pem</string>
+    <key>SSLCertificate</key>
+    <string>/etc/certificates/original.cert.pem</string>
+    <key>SSLPrivateKey</key>
+    <string>/etc/certificates/original.key.pem</string>
+</dict>
+</plist>
+"""
+
+class CertUpdateTests(twistedcaldav.test.util.TestCase):
+    """
+    Calendar Server Certificate Update Tests
+    """
+
+    def setUp(self):
+        self.fd, self.path = mkstemp(suffix=".plist")
+        out = os.fdopen(self.fd, "w")
+        out.write(samplePlist)
+        out.close()
+
+    def tearDown(self):
+        os.remove(self.path)
+
+    def test_getMyCert(self):
+        self.assertEquals("/etc/certificates/original.cert.pem", getMyCert(self.path))
+
+    def test_isThisMyCert(self):
+        self.assertTrue(isThisMyCert(self.path, "/etc/certificates/original.cert.pem"))
+        self.assertFalse(isThisMyCert(self.path, "/etc/certificates/not.cert.pem"))
+
+    def test_replaceCert(self):
+        replaceCert(self.path, "/etc/certificates/new.cert.pem")
+        plist = readPlist(self.path)
+        self.assertEquals(plist["SSLAuthorityChain"], "/etc/certificates/new.chain.pem")
+        self.assertEquals(plist["SSLCertificate"], "/etc/certificates/new.cert.pem")
+        self.assertEquals(plist["SSLPrivateKey"], "/etc/certificates/new.key.pem")

Modified: CalendarServer/branches/users/cdaboo/pods/contrib/migration/calendarmigrator.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/contrib/migration/calendarmigrator.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/migration/calendarmigrator.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -29,7 +29,7 @@
 import subprocess
 import sys
 
-from plistlib import readPlist, writePlist
+from plistlib import readPlist, readPlistFromString, writePlist
 
 CALDAV_LAUNCHD_KEY = "org.calendarserver.calendarserver"
 CARDDAV_LAUNCHD_KEY = "org.addressbookserver.addressbookserver"
@@ -45,6 +45,7 @@
 RESOURCE_MIGRATION_TRIGGER = "trigger_resource_migration"
 SERVER_ADMIN = "/usr/sbin/serveradmin"
 LAUNCHCTL = "/bin/launchctl"
+DITTO = "/usr/bin/ditto"
 
 
 verbatimKeys = """
@@ -214,13 +215,50 @@
             if enableCalDAV:
                 unloadService(options, CALDAV_LAUNCHD_KEY)
 
-            newServerRootValue = migrateData(options)
-            migrateConfiguration(options, newServerRootValue, enableCalDAV,
-                enableCardDAV)
+            # Pull values out of previous plists
+            (
+                oldServerRootValue,
+                oldCalDocumentRootValue,
+                oldCalDataRootValue,
+                oldABDocumentRootValue,
+                uid,
+                gid
+            ) = examinePreviousSystem(
+                options.sourceRoot,
+                options.targetRoot
+            )
 
+            # Copy data as needed
+            (
+                newServerRoot,
+                newServerRootValue,
+                newDocumentRootValue,
+                newDataRootValue
+            ) = relocateData(
+                options.sourceRoot,
+                options.targetRoot,
+                oldServerRootValue,
+                oldCalDocumentRootValue,
+                oldCalDataRootValue,
+                oldABDocumentRootValue,
+                uid,
+                gid
+            )
+
+            # Combine old and new plists
+            migrateConfiguration(
+                options,
+                newServerRootValue,
+                newDocumentRootValue,
+                newDataRootValue,
+                enableCalDAV,
+                enableCardDAV
+            )
+
             configureNotifications()
 
-            triggerResourceMigration(newServerRootValue)
+            triggerResourceMigration(newServerRoot)
+
             setRunState(options, enableCalDAV, enableCardDAV)
 
     else:
@@ -310,7 +348,8 @@
         open(triggerPath, "w").close()
 
 
-def migrateConfiguration(options, newServerRootValue, enableCalDAV, enableCardDAV):
+def migrateConfiguration(options, newServerRootValue,
+    newDocumentRootValue, newDataRootValue, enableCalDAV, enableCardDAV):
     """
     Copy files/directories/symlinks from previous system's /etc/caldavd
     and /etc/carddavd
@@ -325,7 +364,6 @@
         log("New configuration directory does not exist: %s" % (newConfigDir,))
         return
 
-
     for configDir in (CALDAVD_CONFIG_DIR, CARDDAVD_CONFIG_DIR):
 
         oldConfigDir = os.path.join(options.sourceRoot, configDir)
@@ -387,8 +425,8 @@
     mergePlist(oldCalDAVDPlist, oldCardDAVDPlist, newCalDAVDPlist)
 
     newCalDAVDPlist["ServerRoot"] = newServerRootValue
-    newCalDAVDPlist["DocumentRoot"] = "Documents"
-    newCalDAVDPlist["DataRoot"] = "Data"
+    newCalDAVDPlist["DocumentRoot"] = newDocumentRootValue
+    newCalDAVDPlist["DataRoot"] = newDataRootValue
 
     newCalDAVDPlist["EnableCalDAV"] = enableCalDAV
     newCalDAVDPlist["EnableCardDAV"] = enableCardDAV
@@ -516,250 +554,207 @@
 
 def log(msg):
     try:
+        timestamp = datetime.datetime.now().strftime("%b %d %H:%M:%S")
+        msg = "calendarmigrator: %s %s" % (timestamp, msg)
+        print msg # so it appears in Setup.log
         with open(LOG, 'a') as output:
-            timestamp = datetime.datetime.now().strftime("%b %d %H:%M:%S")
-            msg = "calendarmigrator: %s %s" % (timestamp, msg)
             output.write("%s\n" % (msg,)) # so it appears in our log
-            print msg # so it appears in Setup.log
     except IOError:
         # Could not write to log
         pass
 
-def migrateData(options):
+def examinePreviousSystem(sourceRoot, targetRoot, diskAccessor=None):
     """
     Examines the old caldavd.plist and carddavd.plist to see where data
-    lives in the previous system.  If there is old data, calls relocateData( )
+    lives in the previous system.
     """
 
-    oldCalDocuments = None
-    oldCalData = None
-    oldABDocuments = None
-    calendarDataInDefaultLocation = True
-    addressbookDataInDefaultLocation = True
-    uid = -1
-    gid = -1
-    newServerRoot = None # actual path
-    newServerRootValue = NEW_SERVER_ROOT # value to put in plist
+    if diskAccessor is None:
+        diskAccessor = DiskAccessor()
 
-    oldCalConfigDir = os.path.join(options.sourceRoot, CALDAVD_CONFIG_DIR)
+    oldServerRootValue = None
+    oldCalDocumentRootValue = None
+    oldCalDataRootValue = None
+    oldABDocumentRootValue = None
+
+    # Get uid and gid from new caldavd.plist
+    newCalConfigDir = os.path.join(targetRoot, CALDAVD_CONFIG_DIR)
+    newCalPlistPath = os.path.join(newCalConfigDir, CALDAVD_PLIST)
+    if diskAccessor.exists(newCalPlistPath):
+        contents = diskAccessor.readFile(newCalPlistPath)
+        newCalPlist = readPlistFromString(contents)
+        uid, gid = getServerIDs(newCalPlist)
+        log("ServerIDs from %s: %d, %d" % (newCalPlistPath, uid, gid))
+    else:
+        uid = gid = -1
+        log("Can't find new calendar plist at %s" % (newCalPlistPath,))
+
+    # Try and read old caldavd.plist
+    oldCalConfigDir = os.path.join(sourceRoot, CALDAVD_CONFIG_DIR)
     oldCalPlistPath = os.path.join(oldCalConfigDir, CALDAVD_PLIST)
-    if os.path.exists(oldCalPlistPath):
-        oldCalPlist = readPlist(oldCalPlistPath)
-        uid, gid = getServerIDs(oldCalPlist)
-        log("ServerIDs: %d, %d" % (uid, gid))
+    if diskAccessor.exists(oldCalPlistPath):
+        contents = diskAccessor.readFile(oldCalPlistPath)
+        oldCalPlist = readPlistFromString(contents)
+        log("Found previous caldavd plist at %s" % (oldCalPlistPath,))
+
+        oldServerRootValue = oldCalPlist.get("ServerRoot", None)
+        oldCalDocumentRootValue = oldCalPlist.get("DocumentRoot", None)
+        oldCalDataRootValue = oldCalPlist.get("DataRoot", None)
+
     else:
         log("Can't find previous calendar plist at %s" % (oldCalPlistPath,))
         oldCalPlist = None
-        newCalConfigDir = os.path.join(options.targetRoot, CALDAVD_CONFIG_DIR)
-        newCalPlistPath = os.path.join(newCalConfigDir, CALDAVD_PLIST)
-        if os.path.exists(newCalPlistPath):
-            newCalPlist = readPlist(newCalPlistPath)
-            uid, gid = getServerIDs(newCalPlist)
-            log("ServerIDs: %d, %d" % (uid, gid))
 
+    # Try and read old carddavd.plist
+    oldABConfigDir = os.path.join(sourceRoot, CARDDAVD_CONFIG_DIR)
+    oldABPlistPath = os.path.join(oldABConfigDir, CARDDAVD_PLIST)
+    if diskAccessor.exists(oldABPlistPath):
+        contents = diskAccessor.readFile(oldABPlistPath)
+        oldABPlist = readPlistFromString(contents)
+        log("Found previous carddavd plist at %s" % (oldABPlistPath,))
 
-    oldABConfigDir = os.path.join(options.sourceRoot, CARDDAVD_CONFIG_DIR)
-    oldABPlistPath = os.path.join(oldABConfigDir, CARDDAVD_PLIST)
-    if os.path.exists(oldABPlistPath):
-        oldABPlist = readPlist(oldABPlistPath)
+        oldABDocumentRootValue = oldABPlist.get("DocumentRoot", None)
     else:
-        log("Can't find previous addressbook plist at %s" % (oldABPlistPath,))
+        log("Can't find previous carddavd plist at %s" % (oldABPlistPath,))
         oldABPlist = None
 
-    if oldCalPlist is not None:
-        # See if there is actually any calendar data
+    return (
+        oldServerRootValue,
+        oldCalDocumentRootValue,
+        oldCalDataRootValue,
+        oldABDocumentRootValue,
+        uid,
+        gid
+    )
 
-        oldDocumentRoot = oldCalPlist["DocumentRoot"]
-        if oldDocumentRoot.rstrip("/") != "/Library/CalendarServer/Documents":
-            log("Calendar data in non-standard location: %s" % (oldDocumentRoot,))
-            calendarDataInDefaultLocation = False
-        else:
-            log("Calendar data in standard location: %s" % (oldDocumentRoot,))
 
-        oldDataRoot = oldCalPlist["DataRoot"]
+def relocateData(sourceRoot, targetRoot, oldServerRootValue,
+    oldCalDocumentRootValue, oldCalDataRootValue, oldABDocumentRootValue,
+    uid, gid, diskAccessor=None):
+    """
+    Copy data from sourceRoot to targetRoot, except when data is on another
+    volume in which case we just refer to it there.
+    """
 
-        oldCalendarsPath = os.path.join(oldDocumentRoot, "calendars")
-        if os.path.exists(oldCalendarsPath):
-            # There is calendar data
-            oldCalDocuments = oldDocumentRoot
-            oldCalData = oldDataRoot
-            log("Calendar data to migrate from %s and %s" %
-                (oldCalDocuments, oldCalData))
+    if diskAccessor is None:
+        diskAccessor = DiskAccessor()
 
-            if calendarDataInDefaultLocation:
-                newServerRoot = absolutePathWithRoot(options.targetRoot,
-                    NEW_SERVER_ROOT)
-                newServerRootValue = NEW_SERVER_ROOT
-            else:
-                newServerRoot = absolutePathWithRoot(options.targetRoot,
-                    oldDocumentRoot)
-                newServerRootValue = oldDocumentRoot
-        else:
-            log("No calendar data to migrate")
+    log("RelocateData: sourceRoot=%s, targetRoot=%s, oldServerRootValue=%s, oldCalDocumentRootValue=%s, oldCalDataRootValue=%s, oldABDocumentRootValue=%s, uid=%d, gid=%d" % (sourceRoot, targetRoot, oldServerRootValue, oldCalDocumentRootValue, oldCalDataRootValue, oldABDocumentRootValue, uid, gid))
 
-    if oldABPlist is not None:
-        # See if there is actually any addressbook data
 
-        oldDocumentRoot = oldABPlist["DocumentRoot"]
-        if oldDocumentRoot.rstrip("/") != "/Library/AddressBookServer/Documents":
-            log("AddressBook data in non-standard location: %s" % (oldDocumentRoot,))
-            addressbookDataInDefaultLocation = False
+    if oldServerRootValue:
+        newServerRootValue = oldServerRootValue
+        # Source is Lion; see if ServerRoot refers to an external volume
+        # or a directory in sourceRoot
+        if diskAccessor.exists(oldServerRootValue):
+            # refers to an external volume
+            newServerRoot = newServerRootValue
+        elif diskAccessor.exists(os.path.join(sourceRoot, oldServerRootValue)):
+            # refers to a directory on sourceRoot
+            newServerRoot = absolutePathWithRoot(targetRoot, newServerRootValue)
         else:
-            log("AddressBook data in standard location: %s" % (oldDocumentRoot,))
+            # It doesn't exist, so use default
+            newServerRootValue = NEW_SERVER_ROOT
+            newServerRoot = absolutePathWithRoot(targetRoot, newServerRootValue)
 
-        oldAddressbooksPath = os.path.join(oldDocumentRoot, "addressbooks")
-        if os.path.exists(oldAddressbooksPath):
-            # There is addressbook data
-            oldABDocuments = oldDocumentRoot
-            log("AddressBook data to migrate from %s" % (oldABDocuments,))
+        # If there was an old ServerRoot value, process DocumentRoot and
+        # DataRoot because those could be relative to ServerRoot
+        oldCalDocumentRootValueProcessed = os.path.join(oldServerRootValue,
+            oldCalDocumentRootValue)
+        oldCalDataRootValueProcessed = os.path.join(oldServerRootValue,
+            oldCalDataRootValue)
+    else:
+        newServerRootValue = NEW_SERVER_ROOT
+        newServerRoot = absolutePathWithRoot(targetRoot, newServerRootValue)
+        oldCalDocumentRootValueProcessed = oldCalDocumentRootValue
+        oldCalDataRootValueProcessed = oldCalDataRootValue
 
-            if newServerRoot is None:
-                # don't override server root computed from calendar
-                if addressbookDataInDefaultLocation:
-                    newServerRoot = absolutePathWithRoot(options.targetRoot,
-                        NEW_SERVER_ROOT)
-                    newServerRootValue = NEW_SERVER_ROOT
-                else:
-                    newServerRoot = absolutePathWithRoot(options.targetRoot,
-                        oldDocumentRoot)
-                    newServerRootValue = oldDocumentRoot
-        else:
-            log("No addressbook data to migrate")
+    # Set default values for these, possibly overridden below:
+    newDocumentRootValue = "Documents"
+    newDocumentRoot = absolutePathWithRoot(
+        targetRoot,
+        os.path.join(newServerRootValue, newDocumentRootValue)
+    )
+    newDataRootValue = "Data"
+    newDataRoot = absolutePathWithRoot(
+        targetRoot,
+        os.path.join(newServerRootValue, newDataRootValue)
+    )
 
-    if (oldCalDocuments or oldABDocuments) and newServerRoot:
-        relocateData(oldCalDocuments, oldCalData, oldABDocuments, uid, gid,
-            calendarDataInDefaultLocation, addressbookDataInDefaultLocation,
-            newServerRoot)
+    # Old Calendar DocumentRoot
+    if oldCalDocumentRootValueProcessed:
+        if diskAccessor.exists(oldCalDocumentRootValueProcessed):
+            # Must be on an external volume if we see it existing at the point
+            # so don't copy it
+            newDocumentRoot = newDocumentRootValue = oldCalDocumentRootValueProcessed
+        elif diskAccessor.exists(absolutePathWithRoot(sourceRoot, oldCalDocumentRootValueProcessed)):
+            diskAccessor.ditto(
+                absolutePathWithRoot(sourceRoot, oldCalDocumentRootValueProcessed),
+                newDocumentRoot
+            )
+            diskAccessor.chown(newDocumentRoot, uid, gid, recursive=True)
 
-    return newServerRootValue
+    # Old Calendar DataRoot
+    if oldCalDataRootValueProcessed:
+        if diskAccessor.exists(oldCalDataRootValueProcessed):
+            # Must be on an external volume if we see it existing at the point
+            # so don't copy it
+            newDataRootValue = oldCalDataRootValueProcessed
+        elif diskAccessor.exists(
+            absolutePathWithRoot(sourceRoot, oldCalDataRootValueProcessed)
+        ):
+            diskAccessor.ditto(
+                absolutePathWithRoot(sourceRoot, oldCalDataRootValueProcessed),
+                newDataRoot
+            )
+            diskAccessor.chown(newDataRoot, uid, gid, recursive=True)
 
-def relocateData(oldCalDocuments, oldCalData, oldABDocuments, uid, gid,
-    calendarDataInDefaultLocation, addressbookDataInDefaultLocation,
-    newServerRoot):
-    """
-    Relocates existing calendar data to the new default location iff the data
-    was previously in the old default location; otherwise the old calendar
-    DocumentRoot becomes the new ServerRoot directory, the contents of the
-    old DocumentRoot are moved into ServerRoot/Documents and the contents of
-    old DataRoot are copied/moved into ServerRoot/Data.  If there is addressbook
-    data, a symlink is created as ServerRoot/Documents/addressbooks pointing
-    to the old addressbook directory so that the import-to-PostgreSQL will
-    find it.
-    """
+    # Old AddressBook DocumentRoot
+    if oldABDocumentRootValue:
+        newAddressBooks = os.path.join(newDocumentRoot, "addressbooks")
+        if diskAccessor.exists(oldABDocumentRootValue):
+            # Must be on an external volume if we see it existing at the point
+            diskAccessor.ditto(
+                os.path.join(oldABDocumentRootValue, "addressbooks"),
+                newAddressBooks
+            )
+        elif diskAccessor.exists(
+            absolutePathWithRoot(sourceRoot, oldABDocumentRootValue)
+        ):
+            diskAccessor.ditto(
+                absolutePathWithRoot(
+                    sourceRoot,
+                    os.path.join(oldABDocumentRootValue, "addressbooks")
+                ),
+                os.path.join(newDocumentRoot, "addressbooks")
+            )
+        if diskAccessor.exists(newAddressBooks):
+            diskAccessor.chown(newAddressBooks, uid, gid, recursive=True)
 
-    log("RelocateData: cal documents=%s, cal data=%s, ab documents=%s, new server root=%s"
-        % (oldCalDocuments, oldCalData, oldABDocuments, newServerRoot))
 
-    if oldCalDocuments and os.path.exists(oldCalDocuments):
+    newServerRootValue, newDocumentRootValue = relativize(newServerRootValue,
+        newDocumentRootValue)
+    newServerRootValue, newDataRootValue = relativize(newServerRootValue,
+        newDataRootValue)
 
-        if calendarDataInDefaultLocation:
-            # We're in the default location, relocate to new location
-            newCalDocuments = os.path.join(newServerRoot, "Documents")
-            if not os.path.exists(newCalDocuments):
-                os.mkdir(newCalDocuments)
-            newCalData = os.path.join(newServerRoot, "Data")
-            if not os.path.exists(newCalData):
-                os.mkdir(newCalData)
-            if os.path.exists(oldCalDocuments):
-                # Move evertying from oldCalDocuments
-                for item in list(os.listdir(oldCalDocuments)):
-                    source = os.path.join(oldCalDocuments, item)
-                    dest = os.path.join(newCalDocuments, item)
-                    log("Relocating %s to %s" % (source, dest))
-                    os.rename(source, dest)
-            else:
-                log("Warning: %s does not exist; nothing to migrate" % (oldCalDocuments,))
-        else:
-            # The admin has moved calendar data to a non-standard location so
-            # we're going to leave it there, but move things down a level so
-            # that the old DocumentRoot becomes new ServerRoot
+    return (
+        newServerRoot,
+        newServerRootValue,
+        newDocumentRootValue,
+        newDataRootValue
+    )
 
-            # Create "Documents" directory with same ownership as oldCalDocuments
-            newCalDocuments = os.path.join(newServerRoot, "Documents")
-            log("New documents directory: %s" % (newCalDocuments,))
-            newCalData = os.path.join(newServerRoot, "Data")
-            log("New data directory: %s" % (newCalData,))
-            os.mkdir(newCalDocuments)
-            os.mkdir(newCalData)
-            for item in list(os.listdir(newServerRoot)):
-                if item not in ("Documents", "Data"):
-                    source = os.path.join(newServerRoot, item)
-                    dest = os.path.join(newCalDocuments, item)
-                    log("Relocating %s to %s" % (source, dest))
-                    os.rename(source, dest)
 
-        # Relocate calendar DataRoot, copying all files
-        if os.path.exists(oldCalData):
-            if not os.path.exists(newCalData):
-                os.mkdir(newCalData)
-            for item in list(os.listdir(oldCalData)):
-                source = os.path.join(oldCalData, item)
-                if not os.path.isfile(source):
-                    continue
-                dest = os.path.join(newCalData, item)
-                log("Relocating %s to %s" % (source, dest))
-                try:
-                    os.rename(source, dest)
-                except OSError:
-                    # Can't rename because it's cross-volume; must copy/delete
-                    shutil.copy2(source, dest)
-                    os.remove(source)
+def relativize(parent, child):
+    """
+    If child is really a child of parent, make child relative to parent.
+    """
+    if child.startswith(parent):
+        parent = parent.rstrip("/")
+        child = child[len(parent):].strip("/")
+    return parent.rstrip("/"), child.rstrip("/")
 
-        # Symlink to AB document root so server will find it an import to
-        # PostgreSQL
-        if oldABDocuments and os.path.exists(oldABDocuments):
-            oldAddressBooks = os.path.join(oldABDocuments, "addressbooks")
-            newAddressBooks = os.path.join(newCalDocuments, "addressbooks")
-            log("Symlinking AddressBook data: %s to %s" % (newAddressBooks, oldAddressBooks))
-            os.symlink(oldAddressBooks, newAddressBooks)
 
-
-    elif oldABDocuments and os.path.exists(oldABDocuments):
-        # No calendar data, only addressbook data
-
-        if addressbookDataInDefaultLocation:
-            # We're in the default location, relocate to new location
-            newABDocuments = os.path.join(newServerRoot, "Documents")
-            if os.path.exists(newABDocuments):
-                # Move evertying from oldABDocuments
-                for item in list(os.listdir(oldABDocuments)):
-                    source = os.path.join(oldABDocuments, item)
-                    dest = os.path.join(newABDocuments, item)
-                    log("Relocating %s to %s" % (source, dest))
-                    os.rename(source, dest)
-            else:
-                log("Error: %s does not exist" % (newABDocuments,))
-        else:
-            # The admin has moved addressbook data to a non-standard location so
-            # we're going to leave it there, but move things down a level so
-            # that the old DocumentRoot becomes new ServerRoot
-
-            # Create "Documents" directory with same ownership as oldABDocuments
-            newABDocuments = os.path.join(newServerRoot, "Documents")
-            newABData = os.path.join(newServerRoot, "Data")
-            log("New documents directory: %s" % (newABDocuments,))
-            os.mkdir(newABDocuments)
-            os.mkdir(newABData)
-            for item in list(os.listdir(newServerRoot)):
-                if item not in ("Documents", "Data"):
-                    source = os.path.join(newServerRoot, item)
-                    dest = os.path.join(newABDocuments, item)
-                    log("Relocating %s to %s" % (source, dest))
-                    os.rename(source, dest)
-
-    if newServerRoot and os.path.exists(newServerRoot):
-        """
-        Change onwnership of entire ServerRoot
-        """
-        os.chown(newServerRoot, uid, gid)
-        for root, dirs, files in os.walk(newServerRoot, followlinks=True):
-            for name in dirs:
-                os.chown(os.path.join(root, name), uid, gid)
-            for name in files:
-                os.chown(os.path.join(root, name), uid, gid)
-
-
-
 def getServerIDs(plist):
     """
     Given a caldavd.plist, return the userid and groupid for the UserName and
@@ -773,6 +768,7 @@
         gid = grp.getgrnam(plist["GroupName"]).gr_gid
     return uid, gid
 
+
 def absolutePathWithRoot(root, path):
     """
     Combine root and path as long as path does not start with /Volumes/
@@ -783,5 +779,59 @@
         path = path.strip("/")
         return os.path.join(root, path)
 
+
+class DiskAccessor(object):
+    """
+    A wrapper around various disk access methods so that unit tests can easily
+    replace these with a stub that doesn't actually require disk access.
+    """
+
+    def exists(self, path):
+        return os.path.exists(path)
+
+    def readFile(self, path):
+        input = file(path)
+        contents = input.read()
+        input.close()
+        return contents
+
+    def mkdir(self, path):
+        return os.mkdir(path)
+
+    def rename(self, before, after):
+        try:
+            return os.rename(before, after)
+        except OSError:
+            # Can't rename because it's cross-volume; must copy/delete
+            shutil.copy2(before, after)
+            return os.remove(before)
+
+    def isfile(self, path):
+        return os.path.isfile(path)
+
+    def symlink(self, orig, link):
+        return os.symlink(orig, link)
+
+    def chown(self, path, uid, gid, recursive=False):
+        os.chown(path, uid, gid)
+        if recursive:
+            for root, dirs, files in os.walk(path, followlinks=True):
+                for name in dirs:
+                    os.chown(os.path.join(root, name), uid, gid)
+                for name in files:
+                    os.chown(os.path.join(root, name), uid, gid)
+
+
+    def walk(self, path, followlinks=True):
+        return os.walk(path, followlinks=followlinks)
+
+    def listdir(self, path):
+        return list(os.listdir(path))
+
+    def ditto(self, src, dest):
+        log("Copying with ditto: %s to %s" % (src, dest))
+        return subprocess.call([DITTO, src, dest])
+
+
 if __name__ == '__main__':
     main()

Modified: CalendarServer/branches/users/cdaboo/pods/contrib/migration/test/test_migrator.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/contrib/migration/test/test_migrator.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/migration/test/test_migrator.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -15,13 +15,21 @@
 ##
 
 import twistedcaldav.test.util
-from contrib.migration.calendarmigrator import mergePlist
+from contrib.migration.calendarmigrator import (
+    mergePlist, examinePreviousSystem, relocateData, relativize
+)
+import contrib.migration.calendarmigrator
 
 class MigrationTests(twistedcaldav.test.util.TestCase):
     """
     Calendar Server Migration Tests
     """
 
+    def setUp(self):
+        # Disable logging during tests
+        self.patch(contrib.migration.calendarmigrator, "log", lambda _: None)
+
+
     def test_mergeSSL(self):
 
         # SSL on for both services
@@ -237,6 +245,33 @@
         mergePlist(oldCalDAV, oldCardDAV, newCombined)
         self.assertEquals(newCombined, expected)
 
+        # Only CalDAV (Lion -> Lion)
+        oldCalDAV = {
+            "BindHTTPPorts": [],
+            "BindSSLPorts": [],
+            "HTTPPort": 8008,
+            "RedirectHTTPToHTTPS": False,
+            "SSLAuthorityChain": "/etc/certificates/test.chain.pem",
+            "SSLCertificate": "/etc/certificates/test.cert.pem",
+            "SSLPort": 8443,
+            "SSLPrivateKey": "/etc/certificates/test.key.pem",
+        }
+        oldCardDAV = {
+        }
+        expected = {
+            "BindHTTPPorts": [8008, 8800],
+            "BindSSLPorts": [8443, 8843],
+            "EnableSSL" : True,
+            "HTTPPort": 8008,
+            "RedirectHTTPToHTTPS": True,
+            "SSLAuthorityChain": "/etc/certificates/test.chain.pem",
+            "SSLCertificate": "/etc/certificates/test.cert.pem",
+            "SSLPort": 8443,
+            "SSLPrivateKey": "/etc/certificates/test.key.pem",
+        }
+        newCombined = { }
+        mergePlist(oldCalDAV, oldCardDAV, newCombined)
+        self.assertEquals(newCombined, expected)
 
 
         # All settings missing!
@@ -256,3 +291,1055 @@
         newCombined = { }
         mergePlist(oldCalDAV, oldCardDAV, newCombined)
         self.assertEquals(newCombined, expected)
+
+
+    def test_examinePreviousSystem(self):
+        """
+        Set up a virtual system in various configurations, then ensure the
+        examinePreviousSystem( ) method detects/returns the expected values.
+
+        'info' is an array of tuples, each tuple containing:
+            - Description of configuration
+            - Layout of disk as a dictionary of paths plus file contents
+            - Expected return values
+        """
+
+        info = [
+
+        (
+            "Snow -> Lion Migration, all in default locations",
+            ("/Volumes/old", "/"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/Library/CalendarServer/Documents/calendars/" : True,
+                "/Volumes/old/Library/CalendarServer/Data/" : True,
+                "/Volumes/old/Library/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/Library/AddressBookServer/Data/" : True,
+            },
+            (
+                None, # Old ServerRoot value
+                "/Library/CalendarServer/Documents", # Old Cal DocRoot value
+                "/Library/CalendarServer/Data", # Old Cal DataRoot value
+                "/Library/AddressBookServer/Documents", # Old AB DocRoot value
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Snow -> Lion Migration, all in default locations, non-/ target",
+            ("/Volumes/old", "/Volumes/new"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/Library/CalendarServer/Documents/calendars/" : True,
+                "/Volumes/old/Library/CalendarServer/Data/" : True,
+                "/Volumes/old/Library/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/Library/AddressBookServer/Data/" : True,
+            },
+            (
+                None, # Old ServerRoot value
+                "/Library/CalendarServer/Documents", # Old Cal DocRoot value
+                "/Library/CalendarServer/Data", # Old Cal DataRoot value
+                "/Library/AddressBookServer/Documents", # Old AB DocRoot value
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Snow -> Lion Migration, not in default locations",
+            ("/Volumes/old", "/"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/NonStandard/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/NonStandard/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/NonStandard/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/NonStandard/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/NonStandard/CalendarServer/Documents/calendars/" : True,
+                "/Volumes/old/NonStandard/CalendarServer/Data/" : True,
+                "/Volumes/old/NonStandard/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/NonStandard/AddressBookServer/Data/" : True,
+            },
+            (
+                None, # Old ServerRoot value
+                "/NonStandard/CalendarServer/Documents", # Old Cal DocRoot Value
+                "/NonStandard/CalendarServer/Data", # Old Cal DataRoot Value
+                "/NonStandard/AddressBookServer/Documents", # Old AB DocRoot Value
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Snow -> Lion Migration, in internal/external locations",
+            ("/Volumes/old", "/"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Volumes/External/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Volumes/External/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/External/CalendarServer/Documents/calendars/" : True,
+                "/Volumes/External/CalendarServer/Data/" : True,
+                "/Volumes/old/Library/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/Library/AddressBookServer/Data/" : True,
+            },
+            (
+                None, # Old ServerRoot value
+                "/Volumes/External/CalendarServer/Documents", # Old Cal DocRoot Value
+                "/Volumes/External/CalendarServer/Data", # Old Cal DataRoot Value
+                "/Library/AddressBookServer/Documents", # Old AB DocRoot Value
+                93, 93, # user id, group id
+            )
+        ),
+
+
+        (
+            "Snow -> Lion Migration, only AddressBook data",
+            ("/Volumes/old", "/"),
+            {
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/Library/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/Library/AddressBookServer/Data/" : True,
+            },
+            (
+                None, # Old ServerRoot value
+                None, # Old Cal DocRoot value
+                None, # Old Cal DataRoot value
+                "/Library/AddressBookServer/Documents", # Old AB DocRoot value
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Lion -> Lion Migration, all in default locations",
+            ("/Volumes/old", "/"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/Library/Server/Calendar and Contacts/Documents/" : True,
+                "/Volumes/old/Library/Server/Calendar and Contacts/Data/" : True,
+            },
+            (
+                "/Library/Server/Calendar and Contacts", # Old ServerRoot value
+                "Documents", # Old Cal DocRoot value
+                "Data", # Old Cal DataRoot value
+                None, # Old AB Docs
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Lion -> Lion Migration, not in default locations",
+            ("/Volumes/old", "/"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/NonStandard/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>/Volumes/External/Calendar/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Volumes/External/Calendar/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/NonStandard/Calendar and Contacts/Documents/" : True,
+                "/Volumes/old/NonStandard/Calendar and Contacts/Data/" : True,
+            },
+            (
+                "/NonStandard/Calendar and Contacts", # Old ServerRoot value
+                "/Volumes/External/Calendar/Documents", # Old Cal DocRoot value
+                "/Volumes/External/Calendar/Data", # Old Cal DataRoot value
+                None, # Old AB Docs
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Lion -> Lion Migration, non-/ targetRoot",
+            ("/Volumes/old", "/Volumes/new"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/Library/Server/Calendar and Contacts/Documents/" : True,
+                "/Volumes/old/Library/Server/Calendar and Contacts/Data/" : True,
+            },
+            (
+                "/Library/Server/Calendar and Contacts", # Old ServerRoot value
+                "Documents", # Old Cal DocRoot value
+                "Data", # Old Cal DocRoot value
+                None, # Old AB Docs
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Lion -> Lion Migration, external ServerRoot with absolute external DocumentRoot and internal DataRoot",
+            ("/Volumes/old", "/Volumes/new"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Volumes/External/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>/Volumes/External/CalendarDocuments/</string>
+                        <key>DataRoot</key>
+                        <string>/CalendarData</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/External/Library/Server/Calendar and Contacts/" : True,
+                "/Volumes/External/CalendarDocuments/" : True,
+                "/Volumes/old/CalendarData" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts/" : True,
+            },
+            (
+                "/Volumes/External/Server/Calendar and Contacts", # Old ServerRoot value
+                "/Volumes/External/CalendarDocuments/", # Old Cal DocRoot value
+                "/CalendarData", # Old Cal DocRoot value
+                None, # Old AB Docs
+                93, 93, # user id, group id
+            )
+        ),
+
+
+
+        (
+            "Empty migration, nothing exists",
+            ("/Volumes/old", "/Volumes/new"),
+            {
+            },
+            (
+                None, # Old ServerRoot value
+                None, # Old Cal DocRoot value
+                None, # Old Cal DocRoot value
+                None, # Old AB Docs
+                -1, -1, # user id, group id
+            )
+        ),
+
+
+        ]
+
+        for description, (source, target), paths, expected in info:
+            # print "-=-=-=- %s -=-=-=-" % (description,)
+            accessor = StubDiskAccessor(paths)
+            actual = examinePreviousSystem(source, target, diskAccessor=accessor)
+            self.assertEquals(expected, actual)
+
+
+    def test_relocateData(self):
+
+        info = [
+
+        (
+            "Snow -> Lion Migration, all in default locations",
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/Library/CalendarServer/Documents/calendars/" : True,
+                "/Volumes/old/Library/CalendarServer/Data/" : True,
+                "/Volumes/old/Library/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/Library/AddressBookServer/Data/" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts" : True,
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                None, # oldServerRootValue
+                "/Library/CalendarServer/Documents", # oldCalDocumentRootValue
+                "/Library/CalendarServer/Data", # oldCalDataRootValue
+                "/Library/AddressBookServer/Documents", # oldABDocumentRootValue
+                93, 93, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/new/Library/Server/Calendar and Contacts",
+                "/Library/Server/Calendar and Contacts",
+                "Documents",
+                "Data"
+            ),
+            [   # expected DiskAccessor history
+                ('ditto', '/Volumes/old/Library/CalendarServer/Documents', '/Volumes/new/Library/Server/Calendar and Contacts/Documents'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents', 93, 93),
+                ('ditto', '/Volumes/old/Library/CalendarServer/Data', '/Volumes/new/Library/Server/Calendar and Contacts/Data'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Data', 93, 93),
+                ('ditto', '/Volumes/old/Library/AddressBookServer/Documents/addressbooks', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks', 93, 93),
+            ]
+        ),
+
+        (
+            "Snow -> Lion Migration, in non-standard locations",
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/NonStandard/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/NonStandard/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/NonStandard/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/NonStandard/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/NonStandard/CalendarServer/Documents/calendars/" : True,
+                "/Volumes/old/NonStandard/CalendarServer/Data/" : True,
+                "/Volumes/old/NonStandard/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/NonStandard/AddressBookServer/Data/" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts" : True,
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                None, # oldServerRootValue
+                "/NonStandard/CalendarServer/Documents", # oldCalDocumentRootValue
+                "/NonStandard/CalendarServer/Data", # oldCalDataRootValue
+                "/NonStandard/AddressBookServer/Documents", # oldABDocumentRootValue
+                93, 93, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/new/Library/Server/Calendar and Contacts",
+                "/Library/Server/Calendar and Contacts",
+                "Documents",
+                "Data"
+            ),
+            [
+                ('ditto', '/Volumes/old/NonStandard/CalendarServer/Documents', '/Volumes/new/Library/Server/Calendar and Contacts/Documents'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents', 93, 93),
+                ('ditto', '/Volumes/old/NonStandard/CalendarServer/Data', '/Volumes/new/Library/Server/Calendar and Contacts/Data'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Data', 93, 93),
+                ('ditto', '/Volumes/old/NonStandard/AddressBookServer/Documents/addressbooks', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks', 93, 93),
+            ]
+        ),
+
+        (
+            "Snow -> Lion Migration, internal AB, external Cal",
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Volumes/External/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Volumes/External/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/External/CalendarServer/Documents" : True,
+                "/Volumes/External/CalendarServer/Data" : True,
+                "/Volumes/old/Library/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/Library/AddressBookServer/Data/" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts" : True,
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                None, # oldServerRootValue
+                "/Volumes/External/CalendarServer/Documents", # oldCalDocumentRootValue
+                "/Volumes/External/CalendarServer/Data", # oldCalDataRootValue
+                "/Library/AddressBookServer/Documents", # oldABDocumentRootValue
+                93, 93, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/new/Library/Server/Calendar and Contacts",
+                "/Library/Server/Calendar and Contacts",
+                "/Volumes/External/CalendarServer/Documents",
+                "/Volumes/External/CalendarServer/Data"
+            ),
+            [
+                ('ditto', '/Volumes/old/Library/AddressBookServer/Documents/addressbooks', '/Volumes/External/CalendarServer/Documents/addressbooks'),
+                ('chown-recursive', '/Volumes/External/CalendarServer/Documents/addressbooks', 93, 93),
+            ]
+        ),
+
+        (
+            "Lion -> Lion Migration, all in default locations",
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/Library/Server/Calendar and Contacts/Documents/" : True,
+                "/Volumes/old/Library/Server/Calendar and Contacts/Data/" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts/" : True,
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                "/Library/Server/Calendar and Contacts", # oldServerRootValue
+                "Documents", # oldCalDocumentRootValue
+                "Data", # oldCalDataRootValue
+                None, # oldABDocumentRootValue
+                93, 93, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/new/Library/Server/Calendar and Contacts",
+                "/Library/Server/Calendar and Contacts",
+                "Documents",
+                "Data"
+            ),
+            [
+                ('ditto', '/Volumes/old/Library/Server/Calendar and Contacts/Documents', '/Volumes/new/Library/Server/Calendar and Contacts/Documents'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents', 93, 93),
+                ('ditto', '/Volumes/old/Library/Server/Calendar and Contacts/Data', '/Volumes/new/Library/Server/Calendar and Contacts/Data'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Data', 93, 93),
+            ]
+        ),
+
+        (
+            "Lion -> Lion Migration, external ServerRoot with relative DocumentRoot and DataRoot",
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Volumes/External/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/External/Library/Server/Calendar and Contacts/Documents/" : True,
+                "/Volumes/External/Library/Server/Calendar and Contacts/Data/" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts/" : True,
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                "/Volumes/External/Library/Server/Calendar and Contacts", # oldServerRootValue
+                "Documents", # oldCalDocumentRootValue
+                "Data", # oldCalDataRootValue
+                None, # oldABDocumentRootValue
+                93, 93, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/External/Library/Server/Calendar and Contacts",
+                "/Volumes/External/Library/Server/Calendar and Contacts",
+                "Documents",
+                "Data"
+            ),
+            [
+            ]
+        ),
+
+
+        (
+            "Lion -> Lion Migration, external ServerRoot with absolute external DocumentRoot and internal DataRoot",
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Volumes/External/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>/Volumes/External/CalendarDocuments/</string>
+                        <key>DataRoot</key>
+                        <string>/CalendarData</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/External/Library/Server/Calendar and Contacts/" : True,
+                "/Volumes/External/CalendarDocuments/" : True,
+                "/Volumes/old/CalendarData" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts/" : True,
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                "/Volumes/External/Library/Server/Calendar and Contacts", # oldServerRootValue
+                "/Volumes/External/CalendarDocuments/", # oldCalDocumentRootValue
+                "/CalendarData", # oldCalDataRootValue
+                None, # oldABDocumentRootValue
+                93, 93, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/External/Library/Server/Calendar and Contacts",
+                "/Volumes/External/Library/Server/Calendar and Contacts",
+                "/Volumes/External/CalendarDocuments",
+                "Data" # Note that DataRoot was copied over to external volume
+            ),
+            [
+                ('ditto', '/Volumes/old/CalendarData', '/Volumes/External/Library/Server/Calendar and Contacts/Data'),
+                ('chown-recursive', '/Volumes/External/Library/Server/Calendar and Contacts/Data', 93, 93),
+            ]
+        ),
+
+        (
+            "Empty migration",
+            {   # no files
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                None, # oldServerRootValue
+                None, # oldCalDocumentRootValue
+                None, # oldCalDataRootValue
+                None, # oldABDocumentRootValue
+                -1, -1, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/new/Library/Server/Calendar and Contacts",
+                "/Library/Server/Calendar and Contacts",
+                "Documents",
+                "Data"
+            ),
+            [   # no history
+            ]
+        ),
+
+        ]
+
+        for description, paths, args, expected, history in info:
+            # print "-=-=-=- %s -=-=-=-" % (description,)
+            accessor = StubDiskAccessor(paths)
+            actual = relocateData(*args, diskAccessor=accessor)
+            self.assertEquals(expected, actual)
+            self.assertEquals(history, accessor.history)
+
+
+    def test_stubDiskAccessor(self):
+
+        paths = {
+            "/a/b/c/d" : "foo",
+            "/a/b/c/e" : "bar",
+            "/x/y/z/" : True,
+        }
+        accessor = StubDiskAccessor(paths)
+
+        shouldExist = ["/a", "/a/", "/a/b", "/a/b/", "/a/b/c/d", "/x/y/z"]
+        shouldNotExist = ["/b", "/x/y/z/Z"]
+
+        for path in shouldExist:
+            self.assertTrue(accessor.exists(path))
+        for path in shouldNotExist:
+            self.assertFalse(accessor.exists(path))
+
+        for key, value in paths.iteritems():
+            if value is not True:
+                self.assertEquals(accessor.readFile(key), value)
+
+
+    def test_relativize(self):
+        """
+        Make sure child paths are made relative to their parent
+        """
+        info = [
+            (("/abc/", "/abc/def"), ("/abc", "def")),
+            (("/abc", "/abc/def"), ("/abc", "def")),
+            (("/abc", "/def"), ("/abc", "/def")),
+        ]
+        for args, expected in info:
+            self.assertEquals(expected, relativize(*args))
+
+
+class StubDiskAccessor(object):
+    """
+    A stub which allows testing without actually having real files
+    """
+
+    def __init__(self, paths):
+        self.paths = paths
+        self._fillInDirectories()
+
+        self.reset()
+
+    def _fillInDirectories(self):
+        for key in self.paths.keys():
+            parts = key.split("/")
+            for i in xrange(len(parts)):
+                path = "/".join(parts[:i])
+                self.paths[path] = True
+
+    def addPath(self, path, value):
+        self.paths[path] = value
+        self._fillInDirectories()
+
+    def reset(self):
+        self.history = []
+
+    def exists(self, path):
+        return self.paths.has_key(path.rstrip("/"))
+
+    def readFile(self, path):
+        return self.paths[path]
+
+    def mkdir(self, path):
+        self.history.append(("mkdir", path))
+        self.addPath(path, True)
+
+    def rename(self, before, after):
+        self.history.append(("rename", before, after))
+
+    def isfile(self, path):
+        # FIXME: probably want a better way to denote a directory than "True"
+        return self.exists(path) and self.paths[path] is not True
+
+    def symlink(self, orig, link):
+        self.history.append(("symlink", orig, link))
+
+    def chown(self, path, uid, gid, recursive=False):
+        self.history.append(("chown-recursive" if recursive else "chown", path, uid, gid))
+
+    def walk(self, path, followlinks=True):
+        yield [], [], []
+
+    def listdir(self, path):
+        return []
+
+    def ditto(self, src, dest):
+        self.history.append(("ditto", src, dest))
+        self.addPath(dest, True)
+

Modified: CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/config.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/config.plist	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/config.plist	2011-04-27 21:09:24 UTC (rev 7377)
@@ -66,6 +66,8 @@
     <key>observers</key>
     <array>
       <string>loadtest.population.ReportStatistics</string>
+      <string>loadtest.ical.RequestLogger</string>
+      <string>loadtest.profiles.OperationLogger</string>
     </array>
 
   </dict>

Modified: CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/ical.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/ical.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -19,6 +19,7 @@
 from operator import getitem
 from pprint import pformat
 from datetime import datetime
+from urlparse import urlparse, urlunparse
 
 from xml.etree import ElementTree
 ElementTree.QName.__repr__ = lambda self: '<QName %r>' % (self.text,)
@@ -196,17 +197,14 @@
             # not both.
             after = self.reactor.seconds()
 
-            # XXX If the response code is wrong, there's probably not
-            # point passing the response down the callback chain.
-            # errback?
             success = response.code == expectedResponseCode
 
             # if not success:
             #     import pdb; pdb.set_trace()
             msg(
                 type="response", success=success, method=method,
-                headers=headers, body=body,
-                duration=(after - before), url=url)
+                headers=headers, body=body, code=response.code,
+                user=self.user, duration=(after - before), url=url)
 
             if success:
                 return response
@@ -665,11 +663,24 @@
 
 
 class RequestLogger(object):
+    format = u"%(user)s request %(code)s%(success)s[%(duration)5.2f s] %(method)8s %(url)s"
+    success = u"\N{CHECK MARK}"
+    failure = u"\N{BALLOT X}"
+
     def observe(self, event):
-        if event.get("type") == "request":
-            print event["user"], event["method"], event["url"]
+        if event.get("type") == "response":
+            event['url'] = urlunparse(('', '') + urlparse(event['url'])[2:])
+            if event['success']:
+                event['success'] = self.success
+            else:
+                event['success'] = self.failure
+            print (self.format % event).encode('utf-8')
 
 
+    def report(self):
+        pass
+
+
     
 def main():
     from urllib2 import HTTPDigestAuthHandler

Copied: CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/logger.py (from rev 7364, CalendarServer/trunk/contrib/performance/loadtest/logger.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/logger.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/logger.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -0,0 +1,52 @@
+from stats import mean, median, stddev, mad
+
+class SummarizingMixin(object):
+    def printHeader(self, fields):
+        """
+        Print a header for the summarization data which will be reported.
+
+        @param fields: A C{list} of two-tuples.  Each tuple describes one
+            column in the summary.  The first element gives a label to appear
+            at the top of the column.  The second element gives the width of
+            the column.
+        """
+        format = []
+        labels = []
+        for (label, width) in fields:
+            format.append('%%%ds' % (width,))
+            labels.append(label)
+        print ' '.join(format) % tuple(labels)
+
+
+    def _summarizeData(self, operation, data):
+        failed = 0
+        threesec = 0
+        durations = []
+        for (success, duration) in data:
+            if not success:
+                failed += 1
+            if duration > 3:
+                threesec += 1
+            durations.append(duration)
+
+        return operation, len(data), failed, threesec, mean(durations), median(durations)
+
+
+    def _printRow(self, formats, values):
+        format = ' '.join(formats)
+        print format % values
+
+
+    def printData(self, formats, perOperationTimes):
+        """
+        Print one or more rows of data with the given formatting.
+
+        @param formats: A C{list} of C{str} giving formats into which each
+            data field will be interpolated.
+
+        @param perOperationTimes: A C{list} of all of the data to summarize.
+            Each element is a two-tuple of whether the operation succeeded
+            (C{True} if so, C{False} if not) and how long the operation took.
+        """
+        for method, data in perOperationTimes:
+            self._printRow(formats, self._summarizeData(method, data))

Modified: CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/population.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/population.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/population.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -26,10 +26,10 @@
 from twisted.python.log import msg, err
 
 from stats import mean, median, stddev, mad
+from loadtest.logger import SummarizingMixin
 from loadtest.ical import SnowLeopard, RequestLogger
 from loadtest.profiles import Eventer, Inviter, Accepter
 
-
 class ClientType(object, FancyEqMixin):
     """
     @ivar clientType: An L{ICalendarClient} implementation
@@ -206,7 +206,7 @@
 
 
 
-class ReportStatistics(StatisticsBase):
+class ReportStatistics(StatisticsBase, SummarizingMixin):
     _fields = [
         ('operation', 10, '%10s'),
         ('count', 8, '%8s'),
@@ -225,39 +225,15 @@
         dataset.append((event['success'], event['duration']))
 
 
-    def _printHeader(self):
-        format = []
-        labels = []
-        for (label, width, fmt) in self._fields:
-            format.append('%%%ds' % (width,))
-            labels.append(label)
-        print ''.join(format) % tuple(labels)
-
-
-    def _summarizeData(self, method, data):
-        failed = 0
-        threesec = 0
-        durations = []
-        for (success, duration) in data:
-            if not success:
-                failed += 1
-            if duration > 3:
-                threesec += 1
-            durations.append(duration)
-
-        return method, len(data), failed, threesec, mean(durations), median(durations)
-
-
-    def _printData(self, *values):
-        format = ''.join(fmt for (label, width, fmt) in self._fields)
-        print format % values
-
-
     def report(self):
         print
-        self._printHeader()
-        for method, data in self._perMethodTimes.iteritems():
-            self._printData(*self._summarizeData(method, data))
+        self.printHeader([
+                (label, width)
+                for (label, width, fmt)
+                in self._fields])
+        self.printData(
+            [fmt for (label, width, fmt) in self._fields],
+            sorted(self._perMethodTimes.items()))
 
 
 def main():

Modified: CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/profiles.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/profiles.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/profiles.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -30,11 +30,14 @@
 
 from protocol.caldav.definitions import caldavxml
 
+from twisted.python import context
 from twisted.python.log import msg
+from twisted.python.failure import Failure
 from twisted.internet.defer import succeed, fail
 from twisted.internet.task import LoopingCall
 from twisted.web.http import PRECONDITION_FAILED
 
+from loadtest.logger import SummarizingMixin
 from loadtest.ical import IncorrectResponseCode
 
 
@@ -59,6 +62,41 @@
             if cal.resourceType == calendarType]
 
 
+    def _isSelfAttendee(self, attendee):
+        """
+        Try to match one of the attendee's identifiers against one of
+        C{self._client}'s identifiers.  Return C{True} if something matches,
+        C{False} otherwise.
+        """
+        return attendee.params[u'EMAIL'][0] == self._client.email[len('mailto:'):]
+
+
+    def _newOperation(self, label, deferred):
+        """
+        Helper to emit a log event when a new operation is started and
+        another one when it completes.
+        """
+        # If this is a scheduled request, record the lag in the
+        # scheduling now so it can be reported when the response is
+        # received.
+        lag = context.get('lag', None)
+
+        before = self._reactor.seconds()
+        msg(type="operation", phase="start",
+            user=self._client.user, label=label, lag=lag)
+
+        def finished(passthrough):
+            success = not isinstance(passthrough, Failure)
+            after = self._reactor.seconds()
+            msg(type="operation", phase="end", duration=after - before,
+                user=self._client.user, label=label, success=success)
+            return passthrough
+        deferred.addBoth(finished)
+        return deferred
+        
+
+
+
 class CannotAddAttendee(Exception):
     """
     Indicates no new attendees can be invited to a particular event.
@@ -143,10 +181,16 @@
                 if event is None:
                     continue
 
+                vevent = event.contents[u'vevent'][0]
+                organizer = vevent.contents.get('organizer', [None])[0]
+                if organizer is not None and not self._isSelfAttendee(organizer):
+                    # This event was organized by someone else, don't try to invite someone to it.
+                    continue
+
                 href = calendar.url + uuid
 
                 # Find out who might attend
-                attendees = event.contents['vevent'][0].contents.get('attendee', [])
+                attendees = vevent.contents.get('attendee', [])
 
                 d = self._addAttendee(event, attendees)
                 d.addCallbacks(
@@ -154,7 +198,7 @@
                         self._client.addEventAttendee(
                             href, attendee),
                     lambda reason: reason.trap(CannotAddAttendee))
-                return d
+                return self._newOperation("invite", d)
 
 
 
@@ -189,14 +233,13 @@
         # NEEDS-ACTION PARTSTAT.
         attendees = vevent.contents['vevent'][0].contents.get('attendee', [])
         for attendee in attendees:
-            if attendee.params[u'EMAIL'][0] == self._client.email[len('mailto:'):]:
+            if self._isSelfAttendee(attendee):
                 if attendee.params[u'PARTSTAT'][0] == 'NEEDS-ACTION':
                     # XXX Base this on something real
                     delay = self.random.gauss(10, 2)
                     self._accepting.add(href)
                     self._reactor.callLater(
                         delay, self._acceptInvitation, href, attendee)
-                    return
 
 
     def _acceptInvitation(self, href, attendee):
@@ -234,7 +277,7 @@
             self._accepting.remove(href)
             return passthrough
         d.addBoth(finished)
-        return d
+        return self._newOperation("accept", d)
 
 
     def _makeAcceptedAttendee(self, attendee):
@@ -315,4 +358,54 @@
 
             href = '%s%s.ics' % (
                 calendar.url, vevent.contents[u'uid'][0].value)
-            return self._client.addEvent(href, vcalendar)
+            d = self._client.addEvent(href, vcalendar)
+            return self._newOperation("create", d)
+
+
+class OperationLogger(SummarizingMixin):
+    """
+    Profiles will initiate operations which may span multiple requests.  Start
+    and stop log messages are emitted for these operations and logged by this
+    logger.
+    """
+    formats = {
+        u"start": u"%(user)s - - - - - - - - - - - %(label)8s BEGIN %(lag)s",
+        u"end"  : u"%(user)s - - - - - - - - - - - %(label)8s END [%(duration)5.2f s]",
+        }
+
+    lagFormat = u'{lag %5.2f ms}'
+
+    _fields = [
+        ('operation', 10, '%10s'),
+        ('count', 8, '%8s'),
+        ('failed', 8, '%8s'),
+        ('>3sec', 8, '%8s'),
+        ('mean', 8, '%8.4f'),
+        ('median', 8, '%8.4f'),
+        ]
+
+    def __init__(self):
+        self._perOperationTimes = {}
+
+
+    def observe(self, event):
+        if event.get("type") == "operation":
+            if event.get('lag') is None:
+                event['lag'] = ''
+            else:
+                event['lag'] = self.lagFormat % (event['lag'] * 1000.0,)
+            print (self.formats[event[u'phase']] % event).encode('utf-8')
+            if event[u'phase'] == u'end':
+                dataset = self._perOperationTimes.setdefault(event[u'label'], [])
+                dataset.append((event[u'success'], event[u'duration']))
+
+
+    def report(self):
+        print
+        self.printHeader([
+                (label, width)
+                for (label, width, fmt)
+                in self._fields])
+        self.printData(
+            [fmt for (label, width, fmt) in self._fields],
+            sorted(self._perOperationTimes.items()))

Modified: CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/sim.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/sim.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/sim.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -20,6 +20,7 @@
 from plistlib import readPlist
 from collections import namedtuple
 
+from twisted.python import context
 from twisted.python.filepath import FilePath
 from twisted.python.log import addObserver
 from twisted.python.usage import UsageError, Options
@@ -32,6 +33,30 @@
     CalendarClientSimulator)
 
 
+class LagTrackingReactor(object):
+    """
+    This reactor wraps another reactor and proxies all attribute
+    access (including method calls).  It only changes the behavior of
+    L{IReactorTime.callLater} to insert a C{"lag"} key into the
+    context which delayed function calls are invoked with.  This key
+    has a float value which gives the difference in time between when
+    the call was original scheduled and when the call actually took
+    place.
+    """
+    def __init__(self, reactor):
+        self._reactor = reactor
+
+    def __getattr__(self, name):
+        return getattr(self._reactor, name)
+
+    def callLater(self, delay, function, *args, **kwargs):
+        expected = self._reactor.seconds() + delay
+        def modifyContext():
+            now = self._reactor.seconds()
+            context.call({'lag': now - expected}, function, *args, **kwargs)
+        return self._reactor.callLater(delay, modifyContext)
+
+
 class SimOptions(Options):
     """
     Command line configuration options for the load simulator.
@@ -103,7 +128,7 @@
         self.arrival = arrival
         self.parameters = parameters
         self.observers = observers
-        self.reactor = reactor
+        self.reactor = LagTrackingReactor(reactor)
 
 
     @classmethod

Modified: CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/test_profiles.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/test_profiles.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/contrib/performance/loadtest/test_profiles.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -334,7 +334,7 @@
         If the inviter randomly selects a user which is already an
         invitee on the event, a different user is added instead.
         """
-        selfNumber = 13
+        selfNumber = 1
         vevent, event, calendar, client = self._simpleAccount(
             selfNumber, INVITED_EVENT)
 
@@ -376,7 +376,27 @@
         self.assertEquals(len(attendees), 2)
 
 
+    def test_doNotInviteToSomeoneElsesEvent(self):
+        """
+        If there are events on our calendar which are being organized
+        by someone else, the inviter does not attempt to invite new
+        users to them.
+        """
+        selfNumber = 2
+        vevent, event, calendar, client = self._simpleAccount(
+            selfNumber, INVITED_EVENT)
+        inviter = Inviter(None, client, selfNumber)
+        # Try to send an invitation, but with only one event on the
+        # calendar, of which we are not the organizer.  It should be
+        # unchanged afterwards.
+        inviter._invite()
+        attendees = event.vevent.contents[u'vevent'][0].contents[u'attendee']
+        self.assertEqual(len(attendees), 2)
+        self.assertEqual(attendees[0].params['CN'], [u'User 01'])
+        self.assertEqual(attendees[1].params['CN'], [u'User 02'])
 
+
+
 class AccepterTests(TestCase):
     """
     Tests for loadtest.profiles.Accepter.


Property changes on: CalendarServer/branches/users/cdaboo/pods/contrib/performance/sim
___________________________________________________________________
Added: svn:executable
   + *

Modified: CalendarServer/branches/users/cdaboo/pods/doc/calendarserver_export.8
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/doc/calendarserver_export.8	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/doc/calendarserver_export.8	2011-04-27 21:09:24 UTC (rev 7377)
@@ -31,7 +31,7 @@
 .Sh DESCRIPTION
 .Nm
 is a tool that generates a single iCalendar file containing all of the
-iCalendar components found from all specifies input sources, providing
+iCalendar components found from all specified input sources, providing
 server administrators a means by which to export data from the Darwin
 Calandar Server into a format that can be viewed and/or manipulated by
 other tools.  Multiple input sources may be specified; the resulting

Modified: CalendarServer/branches/users/cdaboo/pods/python
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/python	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/python	2011-04-27 21:09:24 UTC (rev 7377)
@@ -1,7 +1,8 @@
-#!/bin/sh
+#!/bin/bash
 
 wd="$(cd "$(dirname "$0")" && pwd)";
 
-export PYTHONPATH="$("${wd}/run" -p)";
+. "${wd}/support/shell.sh"
 
-exec python "$@";
+exec "${python}" "$@";
+

Modified: CalendarServer/branches/users/cdaboo/pods/support/Makefile.Apple
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/support/Makefile.Apple	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/support/Makefile.Apple	2011-04-27 21:09:24 UTC (rev 7377)
@@ -108,6 +108,10 @@
 	$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)/usr/libexec/changeip"
 	$(_v) $(INSTALL_FILE) "$(Sources)/calendarserver/tools/changeip_calendar.py" "$(DSTROOT)/usr/libexec/changeip/changeip_calendar.py"
 	$(_v) chmod ugo+x "$(DSTROOT)/usr/libexec/changeip/changeip_calendar.py"
+	@echo "Installing certificate update scripts..."
+	$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)/usr/libexec/certupdate"
+	$(_v) $(INSTALL_FILE) "$(Sources)/contrib/certupdate/calendarcertupdate.py" "$(DSTROOT)/usr/libexec/certupdate/calendarcertupdate.py"
+	$(_v) chmod ugo+x "$(DSTROOT)/usr/libexec/certupdate/calendarcertupdate.py"
 
 install::
 	@echo "Installing CalDAVTester package..."

Modified: CalendarServer/branches/users/cdaboo/pods/support/build.sh
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/support/build.sh	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/support/build.sh	2011-04-27 21:09:24 UTC (rev 7377)
@@ -644,7 +644,7 @@
     "http://svn.osafoundation.org/vobject/trunk";
 
   # XXX actually PyCalendar should be imported in-place.
-  py_dependency -fe -i "src" -r 154 \
+  py_dependency -fe -i "src" -r 156 \
     "pycalendar" "pycalendar" "pycalendar" \
     "http://svn.mulberrymail.com/repos/PyCalendar/branches/server";
 


Property changes on: CalendarServer/branches/users/cdaboo/pods/support/build.sh
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/support/build.sh:4379-4443
/CalendarServer/branches/egg-info-351/support/build.sh:4589-4615
/CalendarServer/branches/generic-sqlstore/support/build.sh:6167-6191
/CalendarServer/branches/new-store/support/build.sh:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/support/build.sh:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/support/build.sh:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/support/build.sh:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/support/build.sh:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/support/build.sh:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/support/build.sh:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/support/build.sh:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/support/build.sh:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/support/build.sh:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/support/build.sh:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/support/build.sh:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/support/build.sh:4971-5080
/CalendarServer/branches/users/glyph/dalify/support/build.sh:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect/support/build.sh:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres/support/build.sh:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/support/build.sh:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/support/build.sh:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/support/build.sh:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/support/build.sh:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/support/build.sh:6490-6550
/CalendarServer/branches/users/glyph/sql-store/support/build.sh:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/support/build.sh:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/support/build.sh:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/support/build.sh:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/support/build.sh:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/support/build.sh:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/support/build.sh:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/support/build.sh:4068-4075
/CalendarServer/branches/users/sagen/resources-2/support/build.sh:5084-5093
/CalendarServer/branches/users/wsanchez/transations/support/build.sh:5515-5593
   + /CalendarServer/branches/config-separation/support/build.sh:4379-4443
/CalendarServer/branches/egg-info-351/support/build.sh:4589-4615
/CalendarServer/branches/generic-sqlstore/support/build.sh:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/support/build.sh:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/support/build.sh:5911-5935
/CalendarServer/branches/new-store/support/build.sh:5594-5934
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/support/build.sh:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/support/build.sh:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/support/build.sh:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/support/build.sh:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/support/build.sh:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/support/build.sh:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/support/build.sh:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/support/build.sh:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/support/build.sh:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/support/build.sh:4971-5080
/CalendarServer/branches/users/glyph/dalify/support/build.sh:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect/support/build.sh:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres/support/build.sh:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/support/build.sh:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/support/build.sh:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/support/build.sh:6369-6445
/CalendarServer/branches/users/glyph/oracle-nulls/support/build.sh:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/support/build.sh:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/support/build.sh:6490-6550
/CalendarServer/branches/users/glyph/sql-store/support/build.sh:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/support/build.sh:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/support/build.sh:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/support/build.sh:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/support/build.sh:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/support/build.sh:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/support/build.sh:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/support/build.sh:4068-4075
/CalendarServer/branches/users/sagen/resources-2/support/build.sh:5084-5093
/CalendarServer/branches/users/wsanchez/transations/support/build.sh:5515-5593
/CalendarServer/trunk/support/build.sh:7297-7364

Modified: CalendarServer/branches/users/cdaboo/pods/support/py.sh
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/support/py.sh	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/support/py.sh	2011-04-27 21:09:24 UTC (rev 7377)
@@ -48,7 +48,7 @@
 # Detect which version of Python to use, then print out which one was detected.
 
 detect_python_version () {
-  for v in "" "2.6" "2.5"
+  for v in "2.7" "2.6" "2.5" ""
   do
     for p in								\
       "${PYTHON:=}"							\
@@ -120,12 +120,12 @@
 # Compare version numbers
 
 cmp_version () {
-  local result=0;
-
   local  v="$1"; shift;
   local mv="$1"; shift;
 
-  while [ $result != 1 ]; do
+  local result;
+
+  while true; do
      vh="${v%%.*}"; # Get highest-order segment
     mvh="${mv%%.*}";
 
@@ -134,8 +134,14 @@
       break;
     fi;
 
+    if [ "${vh}" -lt "${mvh}" ]; then
+      result=0;
+      break;
+    fi;
+
     if [ "${v}" == "${v#*.}" ]; then
       # No dots left, so we're ok
+      result=0;
       break;
     fi;
 

Modified: CalendarServer/branches/users/cdaboo/pods/support/shell.sh
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/support/shell.sh	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/support/shell.sh	2011-04-27 21:09:24 UTC (rev 7377)
@@ -21,7 +21,10 @@
 # set up by the CalendarServer run script and are not otherwise installed on
 # your system.
 
-wd="$(pwd)";
+if [ -z "${wd}" ]; then
+    wd="$(pwd)";
+fi;
+
 . ${wd}/support/build.sh;
 do_setup=false;
 do_get=false;

Modified: CalendarServer/branches/users/cdaboo/pods/testserver
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/testserver	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/testserver	2011-04-27 21:09:24 UTC (rev 7377)
@@ -61,12 +61,13 @@
 # Do The Right Thing
 ##
 
-export PYTHONPATH=$("${wd}/run" -p);
+. "${wd}/support/shell.sh"
 
 if [ ! -e "${documentroot}/calendars/__uids__/user09" ]; then
   curl "http://localhost:8008/calendars/__uids__/user09/";
 fi;
 
-python twistedcaldav/test/data/makelargecalendars.py -o 9 -d "${documentroot}";
+"${python}" twistedcaldav/test/data/makelargecalendars.py -o 9 -d "${documentroot}";
 
-cd "${cdt}" && python testcaldav.py -s "${serverinfo}" "$@";
+cd "${cdt}" && "${python}" testcaldav.py -s "${serverinfo}" "$@";
+

Modified: CalendarServer/branches/users/cdaboo/pods/twext/enterprise/adbapi2.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twext/enterprise/adbapi2.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twext/enterprise/adbapi2.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -703,8 +703,13 @@
 
     def startService(self):
         """
-        No startup necessary.
+        Increase the thread pool size of the reactor by the number of threads
+        that this service may consume.  This is important because unlike most
+        L{IReactorThreads} users, the connection work units are very long-lived
+        and block until this service has been stopped.
         """
+        tp = self.reactor.getThreadPool()
+        self.reactor.suggestThreadPoolSize(tp.max + self.maxConnections)
 
 
     @inlineCallbacks
@@ -742,7 +747,10 @@
             # independently submitted from .abort() / .close().
             yield self._free.pop()._releaseConnection()
 
+        tp = self.reactor.getThreadPool()
+        self.reactor.suggestThreadPoolSize(tp.max - self.maxConnections)
 
+
     def _createHolder(self):
         """
         Create a L{ThreadHolder}.  (Test hook.)

Modified: CalendarServer/branches/users/cdaboo/pods/twext/enterprise/dal/model.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twext/enterprise/dal/model.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twext/enterprise/dal/model.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -188,7 +188,8 @@
 
     def needsValue(self):
         """
-        Does this column require a value in INSERT statements which create rows?
+        Does this column require a value in C{INSERT} statements which create
+        rows?
 
         @return: C{True} for L{Column}s with no default specified which also
             cannot be NULL, C{False} otherwise.

Modified: CalendarServer/branches/users/cdaboo/pods/twext/enterprise/dal/syntax.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twext/enterprise/dal/syntax.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twext/enterprise/dal/syntax.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -19,7 +19,7 @@
 Syntax wrappers and generators for SQL.
 """
 
-import itertools
+from itertools import count, repeat
 
 from zope.interface import implements
 
@@ -72,7 +72,7 @@
 
     def __init__(self, dialect):
         super(NumericPlaceholder, self).__init__(dialect)
-        self._next = itertools.count(1).next
+        self._next = count(1).next
 
 
     def placeholder(self):
@@ -121,10 +121,37 @@
 
 
     def _extraVars(self, txn, metadata):
+        """
+        A hook for subclasses to provide additional keyword arguments to the
+        C{bind} call when L{_Statement.on} is executed.  Currently this is used
+        only for 'out' parameters to capture results when executing statements
+        that do not normally have a result (L{Insert}, L{Delete}, L{Update}).
+        """
         return {}
 
 
     def _extraResult(self, result, outvars, metadata):
+        """
+        A hook for subclasses to manipulate the results of 'on', after they've
+        been retrieved by the database but before they've been given to
+        application code.
+
+        @param result: a L{Deferred} that will fire with the rows as returned by
+            the database.
+        @type result: C{list} of rows, which are C{list}s or C{tuple}s.
+
+        @param outvars: a dictionary of extra variables returned by
+            C{self._extraVars}.
+
+        @param metadata: information about the connection where the statement
+            was executed.
+
+        @type metadata: L{ConnectionMetadata} (a subclass thereof)
+
+        @return: the result to be returned from L{_Statement.on}.
+
+        @rtype: L{Deferred} firing result rows
+        """
         return result
 
 
@@ -132,6 +159,19 @@
         """
         Execute this statement on a given L{IAsyncTransaction} and return the
         resulting L{Deferred}.
+
+        @param txn: the L{IAsyncTransaction} to execute this on.
+
+        @param raiseOnZeroRowCount: the exception to raise if no data was
+            affected or returned by this query.
+
+        @param kw: keyword arguments, mapping names of L{Parameter} objects
+            located somewhere in C{self}
+
+        @return: results from the database.
+
+        @rtype: a L{Deferred} firing a C{list} of records (C{tuple}s or
+            C{list}s)
         """
         metadata = self._paramstyles[txn.paramstyle](txn.dialect)
         outvars = self._extraVars(txn, metadata)
@@ -139,10 +179,60 @@
         fragment = self.toSQL(metadata).bind(**kw)
         result = txn.execSQL(fragment.text, fragment.parameters,
                              raiseOnZeroRowCount)
-        return self._extraResult(result, outvars, metadata)
+        result = self._extraResult(result, outvars, metadata)
+        if metadata.dialect == ORACLE_DIALECT and result:
+            result.addCallback(self._fixOracleNulls)
+        return result
 
 
+    def _resultColumns(self):
+        """
+        Subclasses must implement this to return a description of the columns
+        expected to be returned.  This is a list of L{ColumnSyntax} objects, and
+        possibly other expression syntaxes which will be converted to C{None}.
+        """
+        raise NotImplementedError(
+            "Each statement subclass must describe its result"
+        )
 
+
+    def _resultShape(self):
+        """
+        Process the result of the subclass's C{_resultColumns}, as described in
+        the docstring above.
+        """
+        for expectation in self._resultColumns():
+            if isinstance(expectation, ColumnSyntax):
+                yield expectation.model
+            else:
+                yield None
+
+
+    def _fixOracleNulls(self, rows):
+        """
+        Oracle treats empty strings as C{NULL}.  Fix this by looking at the
+        columns we expect to have returned, and replacing any C{None}s with
+        empty strings in the appropriate position.
+        """
+        if rows is None:
+            return None
+        newRows = []
+        for row in rows:
+            newRow = []
+            for column, description in zip(row, self._resultShape()):
+                if ((description is not None and
+                     # FIXME: "is the python type str" is what I mean; this list
+                     # should be more centrally maintained
+                     description.type.name in ('varchar', 'text', 'char') and
+                     column is None
+                    )):
+                    column = ''
+                newRow.append(column)
+            newRows.append(newRow)
+        return newRows
+
+
+
 class Syntax(object):
     """
     Base class for syntactic convenience.
@@ -618,7 +708,6 @@
 
 
 
-
 class Select(_Statement):
     """
     'select' statement.
@@ -719,7 +808,25 @@
         return result
 
 
+    def _resultColumns(self):
+        """
+        Determine the list of L{ColumnSyntax} objects that will represent the
+        result.  Normally just the list of selected columns; if wildcard syntax
+        is used though, determine the ordering from the database.
+        """
+        if self.columns is ALL_COLUMNS:
+            # TODO: Possibly this rewriting should always be done, before even
+            # executing the query, so that if we develop a schema mismatch with
+            # the database (additional columns), the application will still see
+            # the right rows.
+            for table in self.From.tables():
+                for column in table:
+                    yield column
+        else:
+            for column in self.columns.columns:
+                yield column
 
+
 def _commaJoined(stmts):
     first = True
     cstatement = SQLFragment()
@@ -783,7 +890,9 @@
         Add a dialect-appropriate 'returning' clause to the end of the given SQL
         statement.
 
-        @param metadata: describes the database we are generating the statement for.
+        @param metadata: describes the database we are generating the statement
+            for.
+
         @type metadata: L{ConnectionMetadata}
 
         @param stmt: the SQL fragment generated without the 'returning' clause
@@ -839,8 +948,16 @@
             return result
 
 
+    def _resultColumns(self):
+        return self._returnAsList()
 
+
+
 class _OracleOutParam(object):
+    """
+    A parameter that will be populated using the cx_Oracle API for host
+    variables.
+    """
     implements(IDerivedParameter)
 
     def __init__(self, columnSyntax):
@@ -848,9 +965,8 @@
 
 
     def preQuery(self, cursor):
-        self.columnSyntax
         typeMap = {'integer': cx_Oracle.NUMBER,
-                   'text': cx_Oracle.CLOB,
+                   'text': cx_Oracle.NCLOB,
                    'varchar': cx_Oracle.STRING,
                    'timestamp': cx_Oracle.TIMESTAMP}
         typeID = self.columnSyntax.model.type.name.lower()
@@ -993,8 +1109,20 @@
 
 
 
-class Lock(_Statement):
+class _LockingStatement(_Statement):
     """
+    A statement related to lock management, which implicitly has no results.
+    """
+    def _resultColumns(self):
+        """
+        No columns should be expected, so return an infinite iterator of None.
+        """
+        return repeat(None)
+
+
+
+class Lock(_LockingStatement):
+    """
     An SQL 'lock' statement.
     """
 
@@ -1015,7 +1143,7 @@
 
 
 
-class Savepoint(_Statement):
+class Savepoint(_LockingStatement):
     """
     An SQL 'savepoint' statement.
     """
@@ -1028,7 +1156,7 @@
         return SQLFragment('savepoint %s' % (self.name,))
 
 
-class RollbackToSavepoint(_Statement):
+class RollbackToSavepoint(_LockingStatement):
     """
     An SQL 'rollback to savepoint' statement.
     """
@@ -1041,7 +1169,7 @@
         return SQLFragment('rollback to savepoint %s' % (self.name,))
 
 
-class ReleaseSavepoint(_Statement):
+class ReleaseSavepoint(_LockingStatement):
     """
     An SQL 'release savepoint' statement.
     """

Modified: CalendarServer/branches/users/cdaboo/pods/twext/enterprise/dal/test/test_sqlsyntax.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twext/enterprise/dal/test/test_sqlsyntax.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twext/enterprise/dal/test/test_sqlsyntax.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -34,6 +34,7 @@
 from twext.enterprise.adbapi2 import ConnectionPool
 from twext.enterprise.test.test_adbapi2 import resultOf
 from twext.enterprise.test.test_adbapi2 import FakeThreadHolder
+from twisted.internet.defer import succeed
 from twisted.trial.unittest import TestCase
 
 
@@ -49,6 +50,25 @@
 
 
 
+class FakeCXOracleModule(object):
+    NUMBER = 'the NUMBER type'
+    STRING = 'a string type (for varchars)'
+    NCLOB = 'the NCLOB type. (for text)'
+    TIMESTAMP = 'for timestamps!'
+
+
+
+class NullTestingOracleTxn(object):
+    """
+    Fake transaction for testing oracle NULL behavior.
+    """
+
+    dialect = ORACLE_DIALECT
+    paramstyle = 'numeric'
+
+    def execSQL(self, text, params, exc):
+        return succeed([[None, None]])
+
 class GenerationTests(TestCase):
     """
     Tests for syntactic helpers to generate SQL queries.
@@ -63,7 +83,10 @@
                        create table OTHER (BAR integer,
                                            FOO_BAR integer not null);
                        create table TEXTUAL (MYTEXT varchar(255));
-                       create table LEVELS (ACCESS integer, USERNAME varchar(255));
+                       create table LEVELS (ACCESS integer,
+                                            USERNAME varchar(255));
+                       create table NULLCHECK (ASTRING varchar(255) not null,
+                                               ANUMBER integer);
                        """)
         self.schema = SchemaSyntax(s)
 
@@ -121,7 +144,7 @@
         self.assertRaises(ValueError, sampleComparison)
 
 
-    def test_nullComparison(self):
+    def test_compareWithNULL(self):
         """
         Comparing a column with None results in the generation of an 'is null'
         or 'is not null' SQL statement.
@@ -510,18 +533,14 @@
         )
 
 
-    def test_insertMultiReturnOnOracleTxn(self):
+    def simulateOracleConnection(self):
         """
-        As described in L{test_insertMultiReturnOracle}, Oracle deals with
-        'returning' clauses by using out parameters.  However, this is not quite
-        enough, as the code needs to actually retrieve the values from the out
-        parameters.
+        Create a fake oracle-ish connection pool without using real threads or a
+        real database.
+
+        @return: a 3-tuple of L{IAsyncTransaction}, L{ConnectionPool},
+            L{ConnectionFactory}.
         """
-        class FakeCXOracleModule(object):
-            NUMBER = 'the NUMBER type'
-            STRING = 'a string type (for varchars)'
-            CLOB = 'the clob type. (for text)'
-            TIMESTAMP = 'for timestamps!'
         self.patch(syntax, 'cx_Oracle', FakeCXOracleModule)
         factory    = ConnectionFactory()
         pool       = ConnectionPool(factory.connect, maxConnections=2,
@@ -531,6 +550,17 @@
         pool._createHolder = lambda : FakeThreadHolder(self)
         pool.startService()
         conn = pool.connection()
+        return conn, pool, factory
+
+
+    def test_insertMultiReturnOnOracleTxn(self):
+        """
+        As described in L{test_insertMultiReturnOracle}, Oracle deals with
+        'returning' clauses by using out parameters.  However, this is not quite
+        enough, as the code needs to actually retrieve the values from the out
+        parameters.
+        """
+        conn, pool, factory = self.simulateOracleConnection()
         i = Insert({self.schema.FOO.BAR: 40,
                     self.schema.FOO.BAZ: 50},
                    Return=(self.schema.FOO.BAR, self.schema.FOO.BAZ))
@@ -828,6 +858,57 @@
         )
 
 
+    def test_rewriteOracleNULLs_Select(self):
+        """
+        Oracle databases cannot distinguish between the empty string and
+        C{NULL}.  When you insert an empty string, C{cx_Oracle} therefore treats
+        it as a C{None} and will return that when you select it back again.  We
+        address this in the schema by dropping 'not null' constraints.
+
+        Therefore, when executing a statement which includes a string column,
+        'on' should rewrite None return values from C{cx_Oracle} to be empty
+        bytestrings, but only for string columns.
+        """
+
+        rows = resultOf(
+            Select([self.schema.NULLCHECK.ASTRING,
+                    self.schema.NULLCHECK.ANUMBER],
+                   From=self.schema.NULLCHECK).on(NullTestingOracleTxn()))[0]
+
+        self.assertEquals(rows, [['', None]])
+
+
+    def test_rewriteOracleNULLs_SelectAllColumns(self):
+        """
+        Same as L{test_rewriteOracleNULLs_Select}, but with the L{ALL_COLUMNS}
+        shortcut.
+        """
+        rows = resultOf(
+            Select(From=self.schema.NULLCHECK).on(NullTestingOracleTxn())
+        )[0]
+        self.assertEquals(rows, [['', None]])
+
+
+    def test_rewriteOracleNULLs_Insert(self):
+        """
+        The behavior described in L{test_rewriteOracleNULLs_Select} applies to
+        other statement types as well, specifically those with 'returning'
+        clauses.
+        """
+        conn, pool, factory = self.simulateOracleConnection()
+        # Add 2 cursor variable values so that these will be used by
+        # FakeVariable.getvalue.
+        factory.varvals.extend([None, None])
+        rows = resultOf(
+            Insert({self.schema.NULLCHECK.ASTRING: '',
+                    self.schema.NULLCHECK.ANUMBER: None},
+                   Return=[self.schema.NULLCHECK.ASTRING,
+                           self.schema.NULLCHECK.ANUMBER]
+                  ).on(conn))[0]
+
+        self.assertEquals(rows, [['', None]])
+
+
     def test_nestedLogicalExpressions(self):
         """
         Make sure that logical operator precedence inserts proper parenthesis

Modified: CalendarServer/branches/users/cdaboo/pods/twext/enterprise/test/test_adbapi2.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twext/enterprise/test/test_adbapi2.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twext/enterprise/test/test_adbapi2.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -20,6 +20,9 @@
 
 from itertools import count
 
+from zope.interface.verify import verifyClass
+
+from twisted.python.threadpool import ThreadPool
 from twisted.trial.unittest import TestCase
 
 from twisted.internet.defer import execute
@@ -28,6 +31,8 @@
 from twisted.internet.defer import Deferred
 from twext.enterprise.ienterprise import ConnectionError
 from twext.enterprise.ienterprise import AlreadyFinishedError
+from twisted.internet.interfaces import IReactorThreads
+from zope.interface.declarations import implements
 from twext.enterprise.adbapi2 import ConnectionPool
 
 
@@ -192,6 +197,9 @@
 
 
     def getvalue(self):
+        vv = self.cursor.connection.parent.varvals
+        if vv:
+            return vv.pop(0)
         return self.cursor.variables.index(self) + 300
 
 
@@ -206,6 +214,7 @@
         self.idcounter = count(1)
         self._connectResultQueue = []
         self.defaultConnect()
+        self.varvals = []
 
 
     @property
@@ -324,6 +333,81 @@
 
 
 
+class ClockWithThreads(Clock):
+    """
+    A testing reactor that supplies L{IReactorTime} and L{IReactorThreads}.
+    """
+    implements(IReactorThreads)
+
+    def __init__(self):
+        super(ClockWithThreads, self).__init__()
+        self._pool = ThreadPool()
+
+
+    def getThreadPool(self):
+        """
+        Get the threadpool.
+        """
+        return self._pool
+
+
+    def suggestThreadPoolSize(self, size):
+        """
+        Approximate the behavior of a 'real' reactor.
+        """
+        self._pool.adjustPoolsize(maxthreads=size)
+
+
+    def callInThread(self, thunk, *a, **kw):
+        """
+        No implementation.
+        """
+
+
+    def callFromThread(self, thunk, *a, **kw):
+        """
+        No implementation.
+        """
+
+
+verifyClass(IReactorThreads, ClockWithThreads)
+
+
+
+class ConnectionPoolBootTests(TestCase):
+    """
+    Tests for the start-up phase of L{ConnectionPool}.
+    """
+
+    def test_threadCount(self):
+        """
+        The reactor associated with a L{ConnectionPool} will have its maximum
+        thread count adjusted when L{ConnectionPool.startService} is called, to
+        accomodate for L{ConnectionPool.maxConnections} additional threads.
+
+        Stopping the service should restore it to its original value, so that a
+        repeatedly re-started L{ConnectionPool} will not cause the thread
+        ceiling to grow without bound.
+        """
+        defaultMax = 27
+        connsMax = 45
+        combinedMax = defaultMax + connsMax
+        pool = ConnectionPool(None, maxConnections=connsMax)
+        pool.reactor = ClockWithThreads()
+        threadpool = pool.reactor.getThreadPool()
+        pool.reactor.suggestThreadPoolSize(defaultMax)
+        self.assertEquals(threadpool.max, defaultMax)
+        pool.startService()
+        self.assertEquals(threadpool.max, combinedMax)
+        justChecking = []
+        pool.stopService().addCallback(justChecking.append)
+        # No SQL run, so no threads started, so this deferred should fire
+        # immediately.  If not, we're in big trouble, so sanity check.
+        self.assertEquals(justChecking, [None])
+        self.assertEquals(threadpool.max, defaultMax)
+
+
+
 class ConnectionPoolTests(TestCase):
     """
     Tests for L{ConnectionPool}.
@@ -340,7 +424,7 @@
         self.pool               = ConnectionPool(self.factory.connect,
                                                  maxConnections=2)
         self.pool._createHolder = self.makeAHolder
-        self.clock              = self.pool.reactor = Clock()
+        self.clock              = self.pool.reactor = ClockWithThreads()
         self.pool.startService()
 
 

Modified: CalendarServer/branches/users/cdaboo/pods/twext/enterprise/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twext/enterprise/util.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twext/enterprise/util.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -30,8 +30,10 @@
     @param column: a single value from a column.
 
     @return: a converted value based on the type of the input; oracle CLOBs and
-        datetime timestamps will be converted to strings, all other types will
-        be left alone.
+        datetime timestamps will be converted to strings, unicode values will be
+        converted to UTF-8 encoded byte sequences (C{str}s), and floating point
+        numbers will be converted to integer types if they are integers.  Any
+        other types will be left alone.
     """
     if hasattr(column, 'read'):
         # Try to detect large objects and format convert them to
@@ -40,7 +42,7 @@
         # http://cx-oracle.sourceforge.net/html/lob.html - in
         # particular, the part where it says "In particular, do not
         # use the fetchall() method".
-        return column.read()
+        column = column.read()
     elif isinstance(column, datetime):
         # cx_Oracle properly maps the type of timestamps to datetime
         # objects.  However, our code is mostly written against
@@ -50,12 +52,20 @@
         # we'll do that.
         return column.strftime(SQL_TIMESTAMP_FORMAT)
     elif isinstance(column, float):
+        # cx_Oracle maps _all_ nubmers to float types, which is more consistent,
+        # but we expect the database to be able to store integers as integers
+        # (in fact almost all the values in our schema are integers), so we map
+        # those values which exactly match back into integers.
         if int(column) == column:
             return int(column)
         else:
             return column
-    else:
-        return column
+    if isinstance(column, unicode):
+        # Finally, we process all data as UTF-8 bytestrings in order to reduce
+        # memory consumption.  Pass any unicode string values back to the
+        # application as unicode.
+        column = column.encode('utf-8')
+    return column
 
 
 

Modified: CalendarServer/branches/users/cdaboo/pods/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twistedcaldav/config.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twistedcaldav/config.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -135,6 +135,8 @@
         else:
             self._provider = provider
         self._updating = False
+        self._beforeResetHook = None
+        self._afterResetHook = None
         self._preUpdateHooks = []
         self._postUpdateHooks = []
         self.reset()
@@ -179,15 +181,25 @@
             lastDict[configItem] = defaultValue
             return defaultValue
 
+    def addResetHooks(self, before, after):
+        """
+        Hooks for preserving config across reload( ) + reset( )
+
+        Each hook will be passed the config data; whatever the before hook
+        returns will be passed as the second arg to the after hook.
+        """
+        self._beforeResetHook = before
+        self._afterResetHook = after
+
     def addPreUpdateHooks(self, hooks):
         self._preUpdateHooks.extend(hooks)
-        
+
     def addPostUpdateHooks(self, hooks):
         self._postUpdateHooks.extend(hooks)
 
     def getProvider(self):
         return self._provider
-    
+
     def setProvider(self, provider):
         self._provider = provider
         self.reset()
@@ -231,7 +243,16 @@
         configDict = ConfigDict(self._provider.loadConfig())
         configDict._reloading = True
         if not self._provider.hasErrors():
+            if self._beforeResetHook:
+                # Give the beforeResetHook a chance to stash away values we want
+                # to preserve across the reload( )
+                preserved = self._beforeResetHook(self._data)
+            else:
+                preserved = None
             self.reset()
+            if preserved and self._afterResetHook:
+                # Pass the preserved data back to the afterResetHook
+                self._afterResetHook(self._data, preserved)
             self.update(configDict)
         else:
             raise ConfigurationError("Invalid configuration in %s"

Modified: CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/appleopendirectory.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/appleopendirectory.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -422,12 +422,6 @@
 
                     recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
 
-                    # Skip if group restriction is in place and guid is not
-                    # a member
-                    if self.restrictedGUIDs is not None:
-                        if str(recordGUID) not in self.restrictedGUIDs:
-                            continue
-
                     recordType = value.get(dsattributes.kDSNAttrRecordType)
                     if isinstance(recordType, list):
                         recordType = recordType[0]
@@ -435,6 +429,13 @@
                         continue
                     recordType = self._fromODRecordTypes[recordType]
 
+                    # Skip if group restriction is in place and guid is not
+                    # a member (but don't skip any groups)
+                    if (recordType != self.recordType_groups and
+                        self.restrictedGUIDs is not None):
+                        if str(recordGUID) not in self.restrictedGUIDs:
+                            continue
+
                     recordAuthIDs = self._setFromAttribute(
                         value.get(dsattributes.kDSNAttrAltSecurityIdentities))
                     recordFullName = value.get(

Modified: CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/augment.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/augment.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/augment.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -420,7 +420,10 @@
         """
         super(AugmentXMLDB, self).refresh()
         try:
-            self.db = self._parseXML()
+            results = self._parseXML()
+            # Only update the cache if _parseXML( ) returns anything
+            if results:
+                self.db = results
         except RuntimeError:
             log.error("Failed to parse XML augments file during cache refresh - ignoring")
         self.lastCached = time.time()
@@ -435,16 +438,18 @@
         self.removeAugmentRecords(self.db.keys())
         return succeed(None)
 
-    def _shouldReparse(self, xmlFile):
+    def _shouldReparse(self, xmlFiles):
         """
-        Check to see whether the given file has been modified since we last
-        parsed it.
+        Check to see whether any of the given files have been modified since
+        we last parsed them.
         """
-        oldModTime, oldSize = self.xmlFileStats.get(xmlFile, (0, 0))
-        newModTime = os.path.getmtime(xmlFile)
-        newSize = os.path.getsize(xmlFile)
-        if (oldModTime != newModTime) or (oldSize != newSize):
-            return True
+        for xmlFile in xmlFiles:
+            if os.path.exists(xmlFile):
+                oldModTime, oldSize = self.xmlFileStats.get(xmlFile, (0, 0))
+                newModTime = os.path.getmtime(xmlFile)
+                newSize = os.path.getsize(xmlFile)
+                if (oldModTime != newModTime) or (oldSize != newSize):
+                    return True
         return False
 
     def _parseXML(self):
@@ -454,23 +459,13 @@
         If none of the xmlFiles exist, create a default record.
         """
 
-        # Do each file
         results = {}
 
-        allMissing = True
+        # If all augments files are missing, return a default record
         for xmlFile in self.xmlFiles:
             if os.path.exists(xmlFile):
-                # Compare previously seen modification time and size of each
-                # xml file.  If unchanged, skip.
-                if self._shouldReparse(xmlFile):
-                    # Creating a parser does the parse
-                    XMLAugmentsParser(xmlFile, results)
-                    newModTime = os.path.getmtime(xmlFile)
-                    newSize = os.path.getsize(xmlFile)
-                    self.xmlFileStats[xmlFile] = (newModTime, newSize)
-                allMissing = False
-
-        if allMissing:
+                break
+        else:
             results["Default"] = AugmentRecord(
                 "Default",
                 enabled=True,
@@ -478,6 +473,17 @@
                 enabledForAddressBooks=True,
             )
 
+        # Compare previously seen modification time and size of each
+        # xml file.  If all are unchanged, skip.
+        if self._shouldReparse(self.xmlFiles):
+            for xmlFile in self.xmlFiles:
+                if os.path.exists(xmlFile):
+                    # Creating a parser does the parse
+                    XMLAugmentsParser(xmlFile, results)
+                    newModTime = os.path.getmtime(xmlFile)
+                    newSize = os.path.getsize(xmlFile)
+                    self.xmlFileStats[xmlFile] = (newModTime, newSize)
+
         return results
 
 class AugmentADAPI(AugmentDB, AbstractADBAPIDatabase):

Modified: CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/cachingdirectory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/cachingdirectory.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/cachingdirectory.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -74,6 +74,7 @@
             CachingDirectoryService.INDEX_TYPE_AUTHID   : {},
         }
         self.directoryService = directoryService
+        self.lastPurgedTime = time.time()
 
     def addRecord(self, record, indexType, indexKey, useMemcache=True,
         neverExpire=False):
@@ -121,8 +122,22 @@
                         self.log_debug("Missing record index item; type: %s, item: %s" % (indexType, item))
         
     def findRecord(self, indexType, indexKey):
+        self.purgeExpiredRecords()
         return self.recordsIndexedBy[indexType].get(indexKey)
 
+
+    def purgeExpiredRecords(self):
+        """
+        Scan the cached records and remove any that have expired.
+        Does nothing if we've scanned within the past cacheTimeout seconds.
+        """
+        if time.time() - self.lastPurgedTime > self.directoryService.cacheTimeout:
+            for record in list(self.records):
+                if record.isExpired():
+                    self.removeRecord(record)
+            self.lastPurgedTime = time.time()
+
+            
 class CachingDirectoryService(DirectoryService):
     """
     Caching Directory implementation of L{IDirectoryService}.
@@ -272,10 +287,7 @@
                 record = self.recordCacheForType(recordType).findRecord(indexType, indexKey)
 
                 if record:
-                    if (
-                        record.cachedTime != 0 and
-                        time.time() - record.cachedTime > self.cacheTimeout
-                    ):
+                    if record.isExpired():
                         self.recordCacheForType(recordType).removeRecord(record)
                         return None
                     else:
@@ -386,6 +398,20 @@
     def neverExpire(self):
         self.cachedTime = 0
 
+    def isExpired(self):
+        """
+        Returns True if this record was created more than cacheTimeout
+        seconds ago
+        """
+        if (
+            self.cachedTime != 0 and
+            time.time() - self.cachedTime > self.service.cacheTimeout
+        ):
+            return True
+        else:
+            return False
+
+
 class DirectoryMemcacheError(DirectoryError):
     """
     Error communicating with memcached.

Modified: CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/test/test_augment.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/test/test_augment.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/test/test_augment.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -281,10 +281,20 @@
         newxmlfile = FilePath(self.mktemp())
         FilePath(xmlFile).copyTo(newxmlfile)
         db = AugmentXMLDB((newxmlfile.path,))
-        self.assertFalse(db._shouldReparse(newxmlfile.path)) # No need to parse
+        self.assertFalse(db._shouldReparse([newxmlfile.path])) # No need to parse
         newxmlfile.setContent("") # Change the file
-        self.assertTrue(db._shouldReparse(newxmlfile.path)) # Need to parse
+        self.assertTrue(db._shouldReparse([newxmlfile.path])) # Need to parse
 
+    def test_refresh(self):
+        """
+        Ensure that a refresh without any file changes doesn't zero out the
+        cache
+        """
+        dbxml = AugmentXMLDB((xmlFile,))
+        keys = dbxml.db.keys()
+        dbxml.refresh()
+        self.assertEquals(keys, dbxml.db.keys())
+
 class AugmentSqliteTests(AugmentTests, AugmentTestsMixin):
 
     def _db(self, dbpath=None):

Modified: CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/test/test_opendirectory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/test/test_opendirectory.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twistedcaldav/directory/test/test_opendirectory.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -322,6 +322,8 @@
                             dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeGroups,
                         },
                     ),
+                    dsattributes.kDSStdRecordTypePlaces : (),
+                    dsattributes.kDSStdRecordTypeResources : (),
                 }
 
                 def attributeMatches(fieldValue, value, caseless, matchType):

Modified: CalendarServer/branches/users/cdaboo/pods/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twistedcaldav/resource.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twistedcaldav/resource.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -1164,7 +1164,7 @@
             if principal is None:
                 return (None, None, None)
             else:
-                return (principal.record.fullName.decode("utf-8"),
+                return (principal.record.fullName,
                     principal.record.guid,
                     principal.record.calendarUserAddresses)
 

Modified: CalendarServer/branches/users/cdaboo/pods/twistedcaldav/scheduling/ischedule.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twistedcaldav/scheduling/ischedule.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twistedcaldav/scheduling/ischedule.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -303,7 +303,7 @@
                 if principal is None:
                     return (None, None, None)
                 else:
-                    return (principal.record.fullName.decode("utf-8"),
+                    return (principal.record.fullName,
                         principal.record.guid,
                         principal.record.calendarUserAddresses)
 

Modified: CalendarServer/branches/users/cdaboo/pods/twistedcaldav/servers.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twistedcaldav/servers.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twistedcaldav/servers.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -114,12 +114,20 @@
                     self.thisServer = parsed_uri.port in (config.SSLPort,) + tuple(config.BindSSLPorts)
         
         # Need to cache IP addresses
-        _ignore_host, _ignore_aliases, ips = socket.gethostbyname_ex(parsed_uri.hostname)
+        try:
+            _ignore_host, _ignore_aliases, ips = socket.gethostbyname_ex(parsed_uri.hostname)
+        except socket.gaierror, e:
+            log.error("Unable to lookup ip-addr for server '%s': %s" % (parsed_uri.hostname, str(e)))
+            ips = ()
         self.ips = set(ips)
 
         for uri in self.partitions.values():
             parsed_uri = urlparse.urlparse(uri)
-            _ignore_host, _ignore_aliases, ips = socket.gethostbyname_ex(parsed_uri.hostname)
+            try:
+                _ignore_host, _ignore_aliases, ips = socket.gethostbyname_ex(parsed_uri.hostname)
+            except socket.gaierror, e:
+                log.error("Unable to lookup ip-addr for partition '%s': %s" % (parsed_uri.hostname, str(e)))
+                ips = ()
             self.partitions_ips.update(ips)
     
     def checkThisIP(self, ip):

Modified: CalendarServer/branches/users/cdaboo/pods/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twistedcaldav/stdconfig.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twistedcaldav/stdconfig.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -644,7 +644,7 @@
     },
 
     # Umask
-    "umask": 0027,
+    "umask": 0022,
 
     # A TCP port used for communication between the child and master
     # processes (bound to 127.0.0.1). Specify 0 to let OS assign a port.
@@ -1263,3 +1263,34 @@
 config.setProvider(PListConfigProvider(DEFAULT_CONFIG))
 config.addPreUpdateHooks(PRE_UPDATE_HOOKS)
 config.addPostUpdateHooks(POST_UPDATE_HOOKS)
+
+
+def _preserveConfig(configDict):
+    """
+    Preserve certain config keys across reset( ) because these can't be
+    re-fetched after the process has shed privileges
+    """
+    iMIP = configDict.Scheduling.iMIP
+    XMPP = configDict.Notifications.Services.XMPPNotifier
+    preserved = {
+        "iMIPPassword" : iMIP.Password,
+        "MailSendingPassword" : iMIP.Sending.Password,
+        "MailReceivingPassword" : iMIP.Receiving.Password,
+        "XMPPPassword" : XMPP.Password,
+    }
+    return preserved
+
+def _restoreConfig(configDict, preserved):
+    """
+    Restore certain config keys across reset( ) because these can't be
+    re-fetched after the process has shed privileges
+    """
+    iMIP = configDict.Scheduling.iMIP
+    XMPP = configDict.Notifications.Services.XMPPNotifier
+    iMIP.Password = preserved["iMIPPassword"]
+    iMIP.Sending.Password = preserved["MailSendingPassword"]
+    iMIP.Receiving.Password = preserved["MailReceivingPassword"]
+    XMPP.Password = preserved["XMPPPassword"]
+
+
+config.addResetHooks(_preserveConfig, _restoreConfig)

Modified: CalendarServer/branches/users/cdaboo/pods/twistedcaldav/test/test_config.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twistedcaldav/test/test_config.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twistedcaldav/test/test_config.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -43,6 +43,37 @@
     <string>debug</string>
   </dict>
 
+  <key>Notifications</key>
+  <dict>
+    <key>Services</key>
+    <dict>
+      <key>XMPPNotifier</key>
+      <dict>
+          <key>Password</key>
+          <string>xmpp</string>
+      </dict>
+    </dict>
+  </dict>
+
+  <key>Scheduling</key>
+  <dict>
+    <key>iMIP</key>
+    <dict>
+      <key>Password</key>
+      <string>imip</string>
+      <key>Sending</key>
+      <dict>
+          <key>Password</key>
+          <string>sending</string>
+      </dict>
+      <key>Receiving</key>
+      <dict>
+          <key>Password</key>
+          <string>receiving</string>
+      </dict>
+    </dict>
+  </dict>
+
 </dict>
 </plist>
 """
@@ -127,6 +158,28 @@
 
         self.assertEquals(config.HTTPPort, 8008)
 
+    def testPreserveAcrossReload(self):
+        self.assertEquals(config.Scheduling.iMIP.Password, "")
+        self.assertEquals(config.Scheduling.iMIP.Sending.Password, "")
+        self.assertEquals(config.Scheduling.iMIP.Receiving.Password, "")
+        self.assertEquals(config.Notifications.Services.XMPPNotifier.Password, "")
+
+        config.load(self.testConfig)
+
+        self.assertEquals(config.Scheduling.iMIP.Password, "imip")
+        self.assertEquals(config.Scheduling.iMIP.Sending.Password, "sending")
+        self.assertEquals(config.Scheduling.iMIP.Receiving.Password, "receiving")
+        self.assertEquals(config.Notifications.Services.XMPPNotifier.Password, "xmpp")
+
+        writePlist({}, self.testConfig)
+
+        config.reload()
+
+        self.assertEquals(config.Scheduling.iMIP.Password, "imip")
+        self.assertEquals(config.Scheduling.iMIP.Sending.Password, "sending")
+        self.assertEquals(config.Scheduling.iMIP.Receiving.Password, "receiving")
+        self.assertEquals(config.Notifications.Services.XMPPNotifier.Password, "xmpp")
+
     def testSetAttr(self):
         self.assertNotIn("BindAddresses", config.__dict__)
 

Modified: CalendarServer/branches/users/cdaboo/pods/twistedcaldav/test/test_upgrade.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twistedcaldav/test/test_upgrade.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twistedcaldav/test/test_upgrade.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -30,6 +30,7 @@
 
 import hashlib
 import os, zlib, cPickle
+from txdav.caldav.datastore.index_file import db_basename
 
 
 
@@ -290,7 +291,11 @@
             "calendars": {
                 "users": {
                     "wsanchez": {
-                        "calendar" : {},
+                        "calendar" : {
+                            db_basename : {
+                                "@contents": "",
+                            },
+                         },
                         "notifications": {
                             "sample-notification.xml": {
                                 "@contents":  "<?xml version='1.0'>\n<should-be-ignored />"
@@ -307,7 +312,11 @@
                     "64" : {
                         "23" : {
                             "6423F94A-6B76-4A3A-815B-D52CFD77935D" : {
-                                "calendar": {},
+                                "calendar": {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
+                                },
                             }
                         }
                     }
@@ -342,6 +351,9 @@
                     {
                         "calendar" :
                         {
+                            db_basename : {
+                                "@contents": "",
+                            },
                             "1E238CA1-3C95-4468-B8CD-C8A399F78C72.ics" :
                             {
                                 "@contents" : event01_before,
@@ -357,6 +369,9 @@
                         },
                         "inbox" :
                         {
+                            db_basename : {
+                                "@contents": "",
+                            },
                             "@xattrs" :
                             {
                                 # Pickled XML Doc
@@ -371,6 +386,9 @@
                     {
                         "calendar" :
                         {
+                            db_basename : {
+                                "@contents": "",
+                            },
                         },
                     },
                 },
@@ -407,6 +425,9 @@
                             {
                                 "calendar" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "1E238CA1-3C95-4468-B8CD-C8A399F78C72.ics" :
                                     {
                                         "@contents" : event01_after,
@@ -422,6 +443,9 @@
                                 },
                                 "inbox" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "@xattrs" :
                                     {
                                         freeBusyAttr : zlib.compress("<?xml version='1.0' encoding='UTF-8'?>\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n  <href xmlns='DAV:'>/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar/</href>\r\n</calendar-free-busy-set>"),
@@ -438,6 +462,9 @@
                             {
                                 "calendar" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                 },
                             },
                         },
@@ -645,13 +672,16 @@
                 "23" : {
                     "6423F94A-6B76-4A3A-815B-D52CFD77935D" : {
                         "calendar" : {
+                            db_basename : {
+                                "@contents": "",
+                            },
                         },
                         "garbage.ics" : {
                             "@contents": "Oops, not actually an ICS file.",
                         },
                         "other-file.txt": {
                             "@contents": "Also not a calendar collection."
-                        }
+                        },
                     }
                 }
             },
@@ -727,6 +757,122 @@
 
 
     @inlineCallbacks
+    def test_calendarsUpgradeWithNestedCollections(self):
+        """
+        Unknown files, including .DS_Store files at any point in the hierarchy,
+        as well as non-directory in a user's calendar home, will be ignored and not
+        interrupt an upgrade.
+        """
+
+        self.setUpXMLDirectory()
+
+        beforeUIDContents = {
+            "64" : {
+                "23" : {
+                    "6423F94A-6B76-4A3A-815B-D52CFD77935D" : {
+                        "calendar" : {
+                            db_basename : {
+                                "@contents": "",
+                            },
+                        },
+                        "nested1": {
+                            "nested2": {},
+                        },
+                    }
+                }
+            },
+            ".DS_Store" : {
+                "@contents" : "",
+            }
+        }
+
+        afterUIDContents = {
+            "64" : {
+                "23" : {
+                    "6423F94A-6B76-4A3A-815B-D52CFD77935D" : {
+                        "calendar" : {
+                            db_basename : {
+                                "@contents": "",
+                            },
+                        },
+                        ".collection.nested1": {
+                            "nested2": {},
+                        },
+                    }
+                }
+            },
+            ".DS_Store" : {
+                "@contents" : "",
+            }
+        }
+
+        before = {
+            ".DS_Store" :
+            {
+                "@contents" : "",
+            },
+            "calendars" :
+            {
+                ".DS_Store" :
+                {
+                    "@contents" : "",
+                },
+                "__uids__" :beforeUIDContents,
+            },
+            "principals" :
+            {
+                ".DS_Store" :
+                {
+                    "@contents" : "",
+                },
+                OLDPROXYFILE :
+                {
+                    "@contents" : "",
+                }
+            }
+        }
+
+        after = {
+            ".DS_Store" :
+            {
+                "@contents" : "",
+            },
+            "tasks" :
+            {
+                "incoming" :
+                {
+                },
+            },
+            ".calendarserver_version" :
+            {
+                "@contents" : "2",
+            },
+            "calendars" :
+            {
+                ".DS_Store" :
+                {
+                    "@contents" : "",
+                },
+                "__uids__" : afterUIDContents,
+            },
+            NEWPROXYFILE :
+            {
+                "@contents" : None,
+            },
+            MailGatewayTokensDatabase.dbFilename :
+            {
+                "@contents" : None,
+            },
+            "%s-journal" % (MailGatewayTokensDatabase.dbFilename,) :
+            {
+                "@contents" : None
+            },
+        }
+
+        (yield self.verifyDirectoryComparison(before, after, reverify=True))
+
+
+    @inlineCallbacks
     def test_calendarsUpgradeWithUIDs(self):
         """
         Verify that calendar homes in the /calendars/__uids__/<guid>/ form
@@ -744,6 +890,9 @@
                     {
                         "calendar" :
                         {
+                            db_basename : {
+                                "@contents": "",
+                            },
                             "1E238CA1-3C95-4468-B8CD-C8A399F78C72.ics" :
                             {
                                 "@contents" : event01_before,
@@ -751,6 +900,9 @@
                         },
                         "inbox" :
                         {
+                            db_basename : {
+                                "@contents": "",
+                            },
                             "@xattrs" :
                             {
                                 # Plain XML
@@ -792,6 +944,9 @@
                             {
                                 "calendar" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "1E238CA1-3C95-4468-B8CD-C8A399F78C72.ics" :
                                     {
                                         "@contents" : event01_after,
@@ -803,6 +958,9 @@
                                 },
                                 "inbox" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "@xattrs" :
                                     {
                                         freeBusyAttr : zlib.compress("<?xml version='1.0' encoding='UTF-8'?>\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n  <href xmlns='DAV:'>/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar/</href>\r\n</calendar-free-busy-set>"),
@@ -852,6 +1010,9 @@
                             {
                                 "calendar" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "1E238CA1-3C95-4468-B8CD-C8A399F78C72.ics" :
                                     {
                                         "@contents" : event01_before,
@@ -868,6 +1029,9 @@
                                 },
                                 "inbox" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "@xattrs" :
                                     {
                                         # Zlib compressed XML
@@ -908,6 +1072,9 @@
                             {
                                 "calendar" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "1E238CA1-3C95-4468-B8CD-C8A399F78C72.ics" :
                                     {
                                         "@contents" : event01_after,
@@ -924,6 +1091,9 @@
                                 },
                                 "inbox" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "@xattrs" :
                                     {
                                         freeBusyAttr : zlib.compress("<?xml version='1.0' encoding='UTF-8'?>\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n  <href xmlns='DAV:'>/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar/</href>\r\n</calendar-free-busy-set>"),
@@ -972,6 +1142,9 @@
                             {
                                 "calendar" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "1E238CA1-3C95-4468-B8CD-C8A399F78C72.ics" :
                                     {
                                         "@contents" : event01_after,
@@ -988,6 +1161,9 @@
                                 },
                                 "inbox" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "@xattrs" :
                                     {
                                         # Zlib compressed XML
@@ -1028,6 +1204,9 @@
                             {
                                 "calendar" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "1E238CA1-3C95-4468-B8CD-C8A399F78C72.ics" :
                                     {
                                         "@contents" : event01_after,
@@ -1044,6 +1223,9 @@
                                 },
                                 "inbox" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "@xattrs" :
                                     {
                                         freeBusyAttr : zlib.compress("<?xml version='1.0' encoding='UTF-8'?>\r\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n  <href xmlns='DAV:'>/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar/</href>\r\n</calendar-free-busy-set>\r\n"),
@@ -1093,6 +1275,9 @@
                             {
                                 "calendar" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "1E238CA1-3C95-4468-B8CD-C8A399F78C72.ics" :
                                     {
                                         "@contents" : event01_before,
@@ -1133,6 +1318,9 @@
                             {
                                 "calendar" :
                                 {
+                                    db_basename : {
+                                        "@contents": "",
+                                    },
                                     "1E238CA1-3C95-4468-B8CD-C8A399F78C72.ics" :
                                     {
                                         "@contents" : event01_after,

Modified: CalendarServer/branches/users/cdaboo/pods/twistedcaldav/upgrade.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/twistedcaldav/upgrade.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/twistedcaldav/upgrade.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -116,7 +116,7 @@
             if principal is None:
                 return (None, None, None)
             else:
-                return (principal.record.fullName.decode("utf-8"),
+                return (principal.record.fullName,
                     principal.record.guid,
                     principal.record.calendarUserAddresses)
 
@@ -588,16 +588,23 @@
         docRoot = config.DocumentRoot
         if os.path.exists(docRoot):
             calRoot = os.path.join(docRoot, "calendars")
-            if os.path.exists(calRoot):
+            if os.path.exists(calRoot) and os.path.isdir(calRoot):
                 uidHomes = os.path.join(calRoot, "__uids__")
-                for path1 in os.listdir(uidHomes):
-                    uidLevel1 = os.path.join(uidHomes, path1)
-                    for path2 in os.listdir(uidLevel1):
-                        uidLevel2 = os.path.join(uidLevel1, path2)
-                        for home in os.listdir(uidLevel2):
-                            calHome = os.path.join(uidLevel2, home)
-                            if not flattenHome(calHome):
-                                errorOccurred = True
+                if os.path.isdir(uidHomes): 
+                    for path1 in os.listdir(uidHomes):
+                        uidLevel1 = os.path.join(uidHomes, path1)
+                        if not os.path.isdir(uidLevel1):
+                            continue
+                        for path2 in os.listdir(uidLevel1):
+                            uidLevel2 = os.path.join(uidLevel1, path2)
+                            if not os.path.isdir(uidLevel2):
+                                continue
+                            for home in os.listdir(uidLevel2):
+                                calHome = os.path.join(uidLevel2, home)
+                                if not os.path.isdir(calHome):
+                                    continue
+                                if not flattenHome(calHome):
+                                    errorOccurred = True
         
         return errorOccurred
         

Modified: CalendarServer/branches/users/cdaboo/pods/txdav/base/datastore/dbapiclient.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/txdav/base/datastore/dbapiclient.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/txdav/base/datastore/dbapiclient.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -99,7 +99,13 @@
     def execute(self, sql, args=()):
         realArgs = []
         for arg in args:
-            if isinstance(arg, (str, unicode)) and len(arg) > 1024:
+            if isinstance(arg, str):
+                # We use NCLOB everywhere, so cx_Oracle requires a unicode-type
+                # input.  But we mostly pass around utf-8 encoded bytes at the
+                # application layer as they consume less memory, so do the
+                # conversion here.
+                arg = arg.decode('utf-8')
+            if isinstance(arg, unicode) and len(arg) > 1024:
                 # This *may* cause a type mismatch, but none of the non-CLOB
                 # strings that we're passing would allow a value this large
                 # anyway.  Smaller strings will be automatically converted by
@@ -107,7 +113,7 @@
                 # sure why cx_Oracle itself doesn't just do the following hack
                 # automatically and internally for larger values too, but, here
                 # it is:
-                v = self.var(cx_Oracle.CLOB, len(arg) + 1)
+                v = self.var(cx_Oracle.NCLOB, len(arg) + 1)
                 v.setvalue(0, arg)
             else:
                 v = arg


Property changes on: CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394
/CalendarServer/trunk/txdav/caldav/datastore/index_file.py:7297-7364

Modified: CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/calendar_store/ho/me/home1/calendar_1/1.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/calendar_store/ho/me/home1/calendar_1/1.ics	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/calendar_store/ho/me/home1/calendar_1/1.ics	2011-04-27 21:09:24 UTC (rev 7377)
@@ -21,12 +21,12 @@
 END:VTIMEZONE
 BEGIN:VEVENT
 ATTENDEE;CN="Wilfredo Sanchez";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailt
- o:wsanchez at apple.com
+ o:wsanchez at example.com
 ATTENDEE;CN="Cyrus Daboo";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:cda
- boo at apple.com
+ boo at example.com
 DTEND;TZID=US/Pacific:20090324T124500
 TRANSP:OPAQUE
-ORGANIZER;CN="Wilfredo Sanchez":mailto:wsanchez at apple.com
+ORGANIZER;CN="Wilfredo Sanchez":mailto:wsanchez at example.com
 UID:uid1
 DTSTAMP:20090326T145447Z
 LOCATION:Wilfredo's Office

Modified: CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/common.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/common.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -944,7 +944,7 @@
         """
         self.assertEquals(
             (yield self.calendarObjectUnderTest()).organizer(),
-            "mailto:wsanchez at apple.com"
+            "mailto:wsanchez at example.com"
         )
 
 


Property changes on: CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394
/CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py:7297-7364


Property changes on: CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394
/CalendarServer/trunk/txdav/carddav/datastore/index_file.py:7297-7364


Property changes on: CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394
/CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py:7297-7364

Modified: CalendarServer/branches/users/cdaboo/pods/txdav/common/datastore/sql_tables.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pods/txdav/common/datastore/sql_tables.py	2011-04-27 20:10:49 UTC (rev 7376)
+++ CalendarServer/branches/users/cdaboo/pods/txdav/common/datastore/sql_tables.py	2011-04-27 21:09:24 UTC (rev 7377)
@@ -206,14 +206,13 @@
     emit in oracle format.
     """
     for sequence in schema.model.sequences:
-        out.write('drop sequence %s; create sequence %s;\n' % (
-            sequence.name, sequence.name))
+        out.write('create sequence %s;\n' % (sequence.name,))
     for table in schema:
         # The only table name which actually exceeds the length limit right now
         # is CALENDAR_OBJECT_ATTACHMENTS_MODE, which isn't actually _used_
         # anywhere, so we can fake it for now.
-        out.write('drop table %s; create table %s (\n' % (
-            table.model.name[:30], table.model.name[:30],))
+        shortName = table.model.name[:30]
+        out.write('create table %s (\n' % (shortName,))
         first = True
         for column in table:
             if first:
@@ -222,7 +221,7 @@
                 out.write(",\n")
             typeName = column.model.type.name
             if typeName == 'text':
-                typeName = 'clob'
+                typeName = 'nclob'
             if typeName == 'boolean':
                 typeName = 'integer'
             out.write('    "%s" %s' % (column.model.name, typeName))
@@ -247,7 +246,11 @@
                         elif default is False:
                             default = 0
                         out.write(" " + repr(default))
-            if not column.model.canBeNull():
+            if ( (not column.model.canBeNull())
+                 # Oracle treats empty strings as NULLs, so we have to accept
+                 # NULL values in columns of a string type.  Other types should
+                 # be okay though.
+                 and typeName not in ('text', 'varchar') ):
                 out.write(' not null')
             if set([column.model]) in list(table.model.uniques()):
                 out.write(' unique')
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110427/272f42cd/attachment-0001.html>


More information about the calendarserver-changes mailing list