[CalendarServer-changes] [9979] CalendarServer/branches/users/cdaboo/ischedule-dkim

source_changes at macosforge.org source_changes at macosforge.org
Wed Oct 24 12:57:08 PDT 2012


Revision: 9979
          http://trac.calendarserver.org//changeset/9979
Author:   cdaboo at apple.com
Date:     2012-10-24 12:57:08 -0700 (Wed, 24 Oct 2012)
Log Message:
-----------
Merge from trunk.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/provision/root.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/shell/vfs.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/contrib/migration/calendarpromotion.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/doc/Admin/ExtendedLogItems.rst
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/resource.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/method/mkcalendar.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/method/mkcol.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/resource.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/icaldiff.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/imip/mailgateway.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/implicit.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/test/test_icaldiff.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/storebridge.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_wrapping.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/sql.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/common.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/test_file.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/icalendarstore.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/datastore/test/common.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/datastore/test/test_file.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/iaddressbookstore.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/file.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/sql.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/test/test_sql.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/xml/__init__.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/xml/base.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/ischedule-dkim/contrib/migration/test/test_promotion.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/common.py

Property Changed:
----------------
    CalendarServer/branches/users/cdaboo/ischedule-dkim/


Property changes on: CalendarServer/branches/users/cdaboo/ischedule-dkim
___________________________________________________________________
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/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/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/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/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:9747-9936
   + /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/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/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/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/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:9747-9978

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/provision/root.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/provision/root.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/provision/root.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -384,14 +384,6 @@
                             "Your client software (%s) is not allowed to access this service." % (agent,)
                         ))
 
-        # Look for forwarding
-        remote_ip = request.headers.getRawHeaders('x-forwarded-for')
-        if remote_ip and len(remote_ip) == 1:
-            request.forwarded_for = remote_ip[0]
-            if not hasattr(request, "extendedLogItems"):
-                request.extendedLogItems = {}
-            request.extendedLogItems["xff"] = remote_ip[0]
-
         if config.EnableResponseCache and request.method == "PROPFIND" and not getattr(request, "notInCache", False) and len(segments) > 1:
             try:
                 authnUser, authzUser = (yield self.authenticate(request))

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/shell/vfs.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/shell/vfs.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/shell/vfs.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -256,19 +256,43 @@
 
     @inlineCallbacks
     def list(self):
-        result = set()
+        results = {}
 
         # FIXME: This should be the merged total of calendar homes and address book homes.
         # FIXME: Merge in directory UIDs also?
-        # FIXME: Add directory info (eg. name) to listing
+        # FIXME: Add directory info (eg. name) to list entry
 
-        for txn, home in (yield self.service.store.eachCalendarHome()):
-            result.add(ListEntry(self, PrincipalHomeFolder, home.uid()))
+        def addResult(uid):
+            if uid in results:
+                return
 
-        returnValue(result)
+            record = self.service.directory.recordWithUID(uid)
+            if record:
+                info = {
+                    "Record Type": record.recordType,
+                    "Short Name" : record.shortNames[0],
+                    "Full Name"  : record.fullName,
+                }
+            else:
+                info = {}
 
+            results[uid] = ListEntry(self, PrincipalHomeFolder, uid, **info)
 
+        txn = self.service.store.newTransaction()
+        try:
+            for home in (yield txn.calendarHomes()):
+                addResult(home.uid())
+            for home in (yield txn.addressbookHomes()):
+                addResult(home.uid())
+        finally:
+            (yield txn.abort())
 
+        returnValue(results.itervalues())
+
+        list.fieldNames = ("Record Name", "Short Name", "Full Name")
+
+
+
 class RecordFolder(Folder):
     def _recordForName(self, name):
         recordTypeAttr = "recordType_" + self.recordType
@@ -358,67 +382,76 @@
         if not hasattr(self, "_didInitChildren"):
             txn = self.service.store.newTransaction()
 
-            if (
-                self.record is not None and
-                self.service.config.EnableCalDAV and 
-                self.record.enabledForCalendaring
-            ):
-                create = True
-            else:
-                create = False
+            try:
+                if (
+                    self.record is not None and
+                    self.service.config.EnableCalDAV and 
+                    self.record.enabledForCalendaring
+                ):
+                    create = True
+                else:
+                    create = False
 
-            # Try assuming it exists
-            home = (yield txn.calendarHomeWithUID(self.uid, create=False))
+                # Try assuming it exists
+                home = (yield txn.calendarHomeWithUID(self.uid, create=False))
 
-            if home is None and create:
-                # Doesn't exist, so create it in a different
-                # transaction, to avoid having to commit the live
-                # transaction.
-                txnTemp = self.service.store.newTransaction()
-                home = (yield txnTemp.calendarHomeWithUID(self.uid, create=True))
-                (yield txnTemp.commit())
+                if home is None and create:
+                    # Doesn't exist, so create it in a different
+                    # transaction, to avoid having to commit the live
+                    # transaction.
+                    txnTemp = self.service.store.newTransaction()
+                    try:
+                        home = (yield txnTemp.calendarHomeWithUID(self.uid, create=True))
+                        (yield txnTemp.commit())
 
-                # Fetch the home again. This time we expect it to be there.
-                home = (yield txn.calendarHomeWithUID(self.uid, create=False))
-                assert home
+                        # Fetch the home again. This time we expect it to be there.
+                        home = (yield txn.calendarHomeWithUID(self.uid, create=False))
+                        assert home
+                    finally:
+                        (yield txn.abort())
 
-            if home:
-                self._children["calendars"] = CalendarHomeFolder(
-                    self.service,
-                    self.path + ("calendars",),
-                    home,
-                    self.record,
-                )
+                if home:
+                    self._children["calendars"] = CalendarHomeFolder(
+                        self.service,
+                        self.path + ("calendars",),
+                        home,
+                        self.record,
+                    )
 
-            if (
-                self.record is not None and
-                self.service.config.EnableCardDAV and 
-                self.record.enabledForAddressBooks
-            ):
-                create = True
-            else:
-                create = False
+                if (
+                    self.record is not None and
+                    self.service.config.EnableCardDAV and 
+                    self.record.enabledForAddressBooks
+                ):
+                    create = True
+                else:
+                    create = False
 
-            # Again, assume it exists
-            home = (yield txn.addressbookHomeWithUID(self.uid))
+                # Again, assume it exists
+                home = (yield txn.addressbookHomeWithUID(self.uid))
 
-            if not home and create:
-                # Repeat the above dance.
-                txnTemp = self.service.store.newTransaction()
-                home = (yield txnTemp.addressbookHomeWithUID(self.uid, create=True))
-                (yield txnTemp.commit())
+                if not home and create:
+                    # Repeat the above dance.
+                    txnTemp = self.service.store.newTransaction()
+                    try:
+                        home = (yield txnTemp.addressbookHomeWithUID(self.uid, create=True))
+                        (yield txnTemp.commit())
 
-                # Fetch the home again. This time we expect it to be there.
-                home = (yield txn.addressbookHomeWithUID(self.uid, create=False))
-                assert home
+                        # Fetch the home again. This time we expect it to be there.
+                        home = (yield txn.addressbookHomeWithUID(self.uid, create=False))
+                        assert home
+                    finally:
+                        (yield txn.abort())
 
-            if home:
-                self._children["addressbooks"] = AddressBookHomeFolder(
-                    self.service,
-                    self.path + ("addressbooks",),
-                    home,
-                    self.record,
-                )
+                if home:
+                    self._children["addressbooks"] = AddressBookHomeFolder(
+                        self.service,
+                        self.path + ("addressbooks",),
+                        home,
+                        self.record,
+                    )
+            finally:
+                (yield txn.abort())
 
         self._didInitChildren = True
 
@@ -462,8 +495,19 @@
     @inlineCallbacks
     def list(self):
         calendars = (yield self.home.calendars())
-        returnValue((ListEntry(self, CalendarFolder, c.name()) for c in calendars))
+        result = []
+        for calendar in calendars:
+            displayName = calendar.displayName()
+            if displayName is None:
+                displayName = "(unset)"
 
+            info = {
+                "Display Name": displayName,
+                "Sync Token"  : (yield calendar.syncToken()),
+            }
+            result.append(ListEntry(self, CalendarFolder, calendar.name(), **info))
+        returnValue(result)
+
     @inlineCallbacks
     def describe(self):
         description = ["Calendar home:\n"]

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/contrib/migration/calendarpromotion.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/contrib/migration/calendarpromotion.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/contrib/migration/calendarpromotion.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -24,6 +24,23 @@
 GROUP_NAME = "calendar"
 LOG_DIR = "/var/log/caldavd"
 
+
+def updatePlist(plistData):
+    """
+    Update the passed-in plist data with new values for disabling the XMPPNotifier, and
+    to set the DSN to use the server-specific Postgres.
+
+    @param plistData: the plist data to update in place
+    @type plistData: C{dict}
+    """
+    try:
+        if plistData["Notifications"]["Services"]["XMPPNotifier"]["Enabled"]:
+            plistData["Notifications"]["Services"]["XMPPNotifier"]["Enabled"] = False
+    except KeyError:
+        pass
+    plistData["DSN"] = "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::"
+
+
 def main():
 
     try:
@@ -45,17 +62,11 @@
     if os.path.exists(plistPath):
         try:
             plistData = readPlist(plistPath)
+            updatePlist(plistData)
+            writePlist(plistData, plistPath)
 
-            # Disable XMPPNotifier now that we're directly talking to APNS
-            try:
-                if plistData["Notifications"]["Services"]["XMPPNotifier"]["Enabled"]:
-                    plistData["Notifications"]["Services"]["XMPPNotifier"]["Enabled"] = False
-                writePlist(plistData, plistPath)
-            except KeyError:
-                pass
-
         except Exception, e:
-            print "Unable to disable XMPP in %s: %s" % (plistPath, e)
+            print "Unable to disable update values in %s: %s" % (plistPath, e)
 
     else:
         # Copy configuration

Copied: CalendarServer/branches/users/cdaboo/ischedule-dkim/contrib/migration/test/test_promotion.py (from rev 9978, CalendarServer/trunk/contrib/migration/test/test_promotion.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/contrib/migration/test/test_promotion.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/contrib/migration/test/test_promotion.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -0,0 +1,60 @@
+##
+# Copyright (c) 2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import twistedcaldav.test.util
+from contrib.migration.calendarpromotion import updatePlist
+
+class PromotionTests(twistedcaldav.test.util.TestCase):
+    """
+    Calendar Server Promotion Tests
+    """
+
+    def test_updatePlist(self):
+        """
+        Verify XMPPNotifier is disabled and DSN is updated
+        """
+
+        orig = {
+            "ignored" : "ignored",
+        }
+        expected = {
+            "ignored" : "ignored",
+            "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
+        }
+        updatePlist(orig)
+        self.assertEquals(orig, expected)
+
+        orig = {
+            "Notifications" : {
+                "Services" : {
+                    "XMPPNotifier" : {
+                        "Enabled" : True
+                    }
+                }
+            }
+        }
+        expected = {
+            "Notifications" : {
+                "Services" : {
+                    "XMPPNotifier" : {
+                        "Enabled" : False
+                    }
+                }
+            },
+            "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
+        }
+        updatePlist(orig)
+        self.assertEquals(orig, expected)

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/doc/Admin/ExtendedLogItems.rst
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/doc/Admin/ExtendedLogItems.rst	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/doc/Admin/ExtendedLogItems.rst	2012-10-24 19:57:08 UTC (rev 9979)
@@ -21,7 +21,7 @@
 
   ``i``
 
-    the port number of the server instance emitting the log
+    the index number of the server instance emitting the log; corresponds to the slave number shown in process title.
 
   ``t``
 
@@ -71,17 +71,27 @@
 
     the value of the X-Forwarded-For header, if present
 
-In the following example, we see a ``CalDAV:calendar-multiget``
-``REPORT`` for 32 resources in a user's calendar, which was handled by
-instance ``8459`` in 183.0ms, with one outstanding request (the one
-being logged):
+  ``fb-cached``
 
+    When doing free-busy queries, this is the number of calendars queried for which free-busy info was already cached
+
+  ``fb-uncached``
+
+    When doing free-busy queries, this is the number of calendars queried for which free-busy info was NOT already cached
+
+  ``cl``
+
+    Content length, in bytes
+
+In the following example, we see a free-busy ``POST``
+requesting availability for two users, which was handled by
+instance ``1`` in 782.6i ms. This instance was only processing one request at the time this was logged (or=1). Of the two calendars targeted by the free-busy query, one already had free-busy info cached, while the other was not cached. (fb-cached=1, fb-uncached=1)
+
 ::
 
-  17.108.160.37 - scastillo [15/Sep/2009:20:10:23 +0000] "REPORT(CalDAV:calendar-multiget) /calendars/__uids__/B8CE9430-965B-11DE-B626-EC2E9DB52B69/calendar/ HTTP/1.1" 207 149285 "-" "DAVKit/4.0 (729); CalendarStore/4.0 (965); iCal/4.0 (1362); Mac OS X/10.6.1 (10B504)" i=8459 t=183.0 or=1 rcount=32
+  10.1.5.43 - user5 [23/Oct/2012:13:42:56 -0700] "POST /calendars/__uids__/B2302CB9-D28F-4CB4-B3D9-0AF0FEDB8110/outbox/ HTTP/1.1" 200 1490 "-" "CalendarStore/5.0.2 (1166); iCal/5.0.2 (1571); Mac OS X/10.7.3 (11D50)" i=1 or=1 t=782.6 fb-uncached=1 fb-cached=1 recipients=2 cl=577
 
 
-
 **Fine-grained request time logging**
 
 If the configuration key EnableExtendedTimingAccessLog is set to true, additional key-value pairs will be logged with each request. The overall request time "t" is broken into four phases, and the elapsed time for each phase is logged. The new keys representing the four request phases are:
@@ -107,4 +117,4 @@
 
 ::
 
-  17.209.103.42 - wsanchez [24/Jul/2012:17:51:29 +0000] "REPORT(CalDAV:calendar-multiget) /calendars/__uids__/F114CA1D-295F-42A5-A5BD-D1A1B19FC049/60E68E32-4C87-4E63-9BF2-12A25E8F2623/ HTTP/1.1" 207 114349 "-" "CalendarStore/5.0.2 (1166); iCal/5.0.2 (1571); Mac OS X/10.7.3 (11D50d)" i=7 or=1 t=764.7 t-req-proc=4.8 t-resp-gen=754.5 t-resp-wr=5.1 t-log=0.2 rcount=2
\ No newline at end of file
+  17.209.103.42 - wsanchez [24/Jul/2012:17:51:29 +0000] "REPORT(CalDAV:calendar-multiget) /calendars/__uids__/F114CA1D-295F-42A5-A5BD-D1A1B19FC049/60E68E32-4C87-4E63-9BF2-12A25E8F2623/ HTTP/1.1" 207 114349 "-" "CalendarStore/5.0.2 (1166); iCal/5.0.2 (1571); Mac OS X/10.7.3 (11D50d)" i=7 or=1 t=764.7 t-req-proc=4.8 t-resp-gen=754.5 t-resp-wr=5.1 t-log=0.2 rcount=2

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/resource.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/resource.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -34,6 +34,7 @@
 
 from twext.web2 import iweb, http, server, responsecode
 
+from twisted.internet.defer import maybeDeferred
 class RenderMixin(object):
     """
     Mix-in class for L{iweb.IResource} which provides a dispatch mechanism for
@@ -106,9 +107,20 @@
             returnValue(response)
 
         yield self.checkPreconditions(request)
-        returnValue((yield method(request)))
+        result = maybeDeferred(method, request)
+        result.addErrback(self.methodRaisedException)
+        returnValue((yield result))
 
 
+    def methodRaisedException(self, failure):
+        """
+        An C{http_METHOD} method raised an exception; this is an errback for
+        that exception.  By default, simply propagate the error up; subclasses
+        may override this for top-level exception handling.
+        """
+        return failure
+
+
     def http_OPTIONS(self, request):
         """
         Respond to a OPTIONS request.

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/method/mkcalendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/method/mkcalendar.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/method/mkcalendar.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -106,7 +106,6 @@
     
         if got_an_error:
             # Force a transaction error and proper clean-up
-            self.transactionError()
             errors.error()
             raise HTTPError(MultiStatusResponse([errors.response()]))
         

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/method/mkcol.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/method/mkcol.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/method/mkcol.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -182,7 +182,6 @@
 
         if got_an_error:
             # Clean up
-            self.transactionError()
             errors.error()
             raise HTTPError(Response(
                     code=responsecode.FORBIDDEN,

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/resource.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/resource.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -318,8 +318,14 @@
         otherResource.associateWithTransaction(self._associatedTransaction)
 
 
-    def transactionError(self):
+    def methodRaisedException(self, failure):
+        """
+        An C{http_METHOD} method raised an exception.  Any type of exception,
+        including those that result in perfectly valid HTTP responses, should
+        abort the transaction.
+        """
         self._transactionError = True
+        return super(CalDAVResource, self).methodRaisedException(failure)
 
 
     @inlineCallbacks

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/icaldiff.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/icaldiff.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/icaldiff.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -669,6 +669,7 @@
         prop = component.getProperty("X-APPLE-NEEDS-REPLY")
         if prop:
             component.removeProperty(prop)
+        component.replaceProperty(Property("TRANSP", "TRANSPARENT"))
         return partstatChanged
 
 

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/imip/mailgateway.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/imip/mailgateway.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/imip/mailgateway.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -1880,5 +1880,5 @@
 
     def clientConnectionFailed(self, connector, reason):
         self.connector = connector
-        self.log_error("IMAP factory connection failed")
+        self.log_warn("IMAP factory connection failed")
         self.retry(connector)

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/implicit.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/implicit.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -812,16 +812,18 @@
 
         # Test/fix ones removed
         for rid in valid_old_rids:
-            # Compare the old one to the new master
-            # Note it is hard to recover from this state so raise instead
-            self.compareAttendeePartstats(
+            # Compare the old one to a derived instance, and if there is a change
+            # add the derived instance to the new data
+            newcomp = self.calendar.deriveInstance(rid)
+            changed = self.compareAttendeePartstats(
                 self.oldcalendar.overriddenComponent(rid),
-                self.calendar.overriddenComponent(None),
-                raiseOnMisMatch=True
+                newcomp,
             )
+            if changed:
+                self.calendar.addComponent(newcomp)
 
 
-    def compareAttendeePartstats(self, old_component, new_component, raiseOnMisMatch=False):
+    def compareAttendeePartstats(self, old_component, new_component):
         """
         Compare two components, old and new, and make sure the Organizer has not changed the PARTSTATs
         in the new one to anything other than NEEDS-ACTION. If there is a change, undo it.
@@ -830,6 +832,7 @@
         old_attendees = dict([(normalizeCUAddr(attendee.value()), attendee) for attendee in old_component.getAllAttendeeProperties()])
         new_attendees = dict([(normalizeCUAddr(attendee.value()), attendee) for attendee in new_component.getAllAttendeeProperties()])
 
+        changed = False
         for cuaddr, newattendee in new_attendees.items():
             # Don't adjust ORGANIZER's ATTENDEE
             if newattendee.value() in self.organizerPrincipal.calendarUserAddresses():
@@ -839,16 +842,12 @@
                 old_attendee = old_attendees.get(cuaddr)
                 old_partstat = old_attendee.parameterValue("PARTSTAT", "NEEDS-ACTION").upper() if old_attendee else "NEEDS-ACTION"
                 if old_attendee is None or old_partstat != new_partstat:
-                    if raiseOnMisMatch:
-                        raise HTTPError(ErrorResponse(
-                            responsecode.FORBIDDEN,
-                            (caldav_namespace, "valid-organizer-change"),
-                            "Organizer cannot change Attendee PARTSTAT",
-                        ))
-                    else:
-                        newattendee.setParameter("PARTSTAT", old_partstat)
+                    newattendee.setParameter("PARTSTAT", old_partstat)
+                    changed = True
 
+        return changed
 
+
     @inlineCallbacks
     def scheduleWithAttendees(self):
 

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/test/test_icaldiff.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/test/test_icaldiff.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/test/test_icaldiff.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -1738,6 +1738,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
 X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 END:VCALENDAR
@@ -1795,6 +1796,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
 X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 BEGIN:VEVENT
@@ -1805,6 +1807,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
 X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 END:VCALENDAR
@@ -1871,6 +1874,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
 X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 END:VCALENDAR
@@ -1938,6 +1942,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
 X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 BEGIN:VEVENT
@@ -1948,6 +1953,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
 X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 END:VCALENDAR

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/storebridge.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/storebridge.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -1611,8 +1611,6 @@
             yield readStream(request.stream, t.write)
         except Exception, e:
             log.error("Unable to store attachment: %s" % (e,))
-            # Signal to abort in twistedcaldav.resource.CalDAVResource.RenderHTTP
-            self.transactionError()
             raise HTTPError(SERVICE_UNAVAILABLE)
 
         try:

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_wrapping.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_wrapping.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -53,6 +53,7 @@
 from twistedcaldav.directory.test.test_xmlfile import XMLFileBase
 from txdav.caldav.icalendarstore import ICalendarHome
 from txdav.carddav.iaddressbookstore import IAddressBookHome
+
 from txdav.caldav.datastore.file import Calendar
 
 
@@ -108,6 +109,7 @@
 
         @param objectName: The name of a calendar object.
         @type objectName: str
+
         @param objectText: Some iCalendar text to populate it with.
         @type objectText: str
         """
@@ -167,7 +169,6 @@
 
         @param path: the path from the root of the site (not starting with a
             slash)
-
         @type path: C{str}
 
         @param method: the HTTP method to initialize the request with.
@@ -266,8 +267,8 @@
 
     def test_createStore(self):
         """
-        Creating a DirectoryCalendarHomeProvisioningResource will create a paired
-        CalendarStore.
+        Creating a DirectoryCalendarHomeProvisioningResource will create a
+        paired CalendarStore.
         """
         assertProvides(self, IDataStore, self.calendarCollection._newStore)
 
@@ -518,7 +519,20 @@
                           frozenset([self.principalsResource]))
 
 
+    @inlineCallbacks
+    def assertCalendarEmpty(self, user, calendarName="calendar"):
+        """
+        Assert that a user's calendar is empty (their default calendar by default).
+        """
+        txn = self.calendarStore.newTransaction()
+        self.addCleanup(txn.commit)
+        home = yield txn.calendarHomeWithUID(user, create=True)
+        cal = yield home.calendarWithName(calendarName)
+        objects = yield cal.calendarObjects()
+        self.assertEquals(len(objects), 0)
 
+
+
 class DatabaseWrappingTests(WrappingTests):
 
     @inlineCallbacks
@@ -531,4 +545,73 @@
         return self.calendarStore
 
 
+    @inlineCallbacks
+    def test_invalidCalendarPUT(self):
+        """
+        Exceeding quota on an attachment returns an HTTP error code.
+        """
+        # yield self.populateOneObject("1.ics", test_event_text)
+        @inlineCallbacks
+        def putEvt(txt):
+            calendarObject = yield self.getResource(
+                "/calendars/users/wsanchez/calendar/1.ics",
+                "PUT", "wsanchez"
+            )
+            self.requestUnderTest.stream = MemoryStream(txt)
+            returnValue(
+                ((yield calendarObject.renderHTTP(self.requestUnderTest)),
+                 self.requestUnderTest)
+            )
+        # see twistedcaldav/directory/test/accounts.xml
+        wsanchez = '6423F94A-6B76-4A3A-815B-D52CFD77935D'
+        cdaboo = '5A985493-EE2C-4665-94CF-4DFEA3A89500'
+        eventTemplate="""\
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+UID:20060110T231240Z-4011c71-187-6f73
+ORGANIZER:urn:uuid:{wsanchez}
+ATTENDEE:urn:uuid:{wsanchez}
+DTSTART:20110101T050000Z
+DTSTAMP:20110309T185105Z
+DURATION:PT1H
+SUMMARY:Test
+RRULE:FREQ=DAILY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:20060110T231240Z-4011c71-187-6f73
+RECURRENCE-ID:20110102T050000Z
+ORGANIZER:urn:uuid:{wsanchez}
+ATTENDEE:urn:uuid:{wsanchez}
+ATTENDEE:urn:uuid:{cdaboo}
+DTSTART:20110102T050000Z
+DTSTAMP:20110309T185105Z
+DURATION:PT1H
+SUMMARY:Test
+END:VEVENT{0}
+END:VCALENDAR
+"""
+        CR = "\n"
+        CRLF = "\r\n"
+        #validEvent = eventTemplate.format("", wsanchez=wsanchez, cdaboo=cdaboo).replace(CR, CRLF)
+        invalidInstance = """
+BEGIN:VEVENT
+UID:20060110T231240Z-4011c71-187-6f73
+RECURRENCE-ID:20110110T050000Z
+ORGANIZER:urn:uuid:{wsanchez}
+ATTENDEE:urn:uuid:{wsanchez}
+DTSTART:20110110T050000Z
+DTSTAMP:20110309T185105Z
+DURATION:PT1H
+SUMMARY:Test
+END:VEVENT""".format(wsanchez=wsanchez, cdaboo=cdaboo)
+        #txn = self.requestUnderTest._newStoreTransaction
+        invalidEvent = eventTemplate.format(invalidInstance, wsanchez=wsanchez, cdaboo=cdaboo).replace(CR, CRLF)
+        resp2, rsrc2 = yield putEvt(invalidEvent)
+        self.requestUnderTest = None
+        yield self.assertCalendarEmpty(wsanchez)
+        yield self.assertCalendarEmpty(cdaboo)
 
+

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/sql.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/sql.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -30,6 +30,8 @@
     Select, Parameter, Update, Insert, TableSyntax, Delete)
 
 from txdav.xml.parser import WebDAVDocument
+from txdav.common.icommondatastore import AllRetriesFailed
+from twext.python.log import LoggingMixIn
 from txdav.common.datastore.sql_tables import schema
 from txdav.base.propertystore.base import (AbstractPropertyStore,
                                            PropertyName, validKey)
@@ -39,7 +41,7 @@
 
 prop = schema.RESOURCE_PROPERTY
 
-class PropertyStore(AbstractPropertyStore):
+class PropertyStore(AbstractPropertyStore, LoggingMixIn):
 
     _cacher = Memcacher("SQL.props", pickle=True, key_normalization=False)
 
@@ -255,7 +257,10 @@
         if hasattr(self, "_notifyCallback") and self._notifyCallback is not None:
             self._notifyCallback()
 
-        self._txn.subtransaction(trySetItem)
+        def justLogIt(f):
+            f.trap(AllRetriesFailed)
+            self.log_error("setting a property failed; probably nothing.")
+        self._txn.subtransaction(trySetItem).addErrback(justLogIt)
 
 
 

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/common.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/common.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -21,7 +21,7 @@
 
 from StringIO import StringIO
 
-from twisted.internet.defer import Deferred, inlineCallbacks, returnValue,\
+from twisted.internet.defer import Deferred, inlineCallbacks, returnValue, \
     maybeDeferred
 from twisted.internet.protocol import Protocol
 from twisted.python import hashlib
@@ -427,7 +427,7 @@
     def test_notificationSyncToken(self):
         """
         L{ICalendar.resourceNamesSinceToken} will return the names of calendar
-        objects changed or deleted since 
+        objects changed or deleted since
         """
         txn = self.transactionUnderTest()
         coll = yield txn.notificationsWithUID("home1")
@@ -574,12 +574,14 @@
         self.assertEquals(calendar.notifierID(), "CalDAV|home1")
         self.assertEquals(calendar.notifierID(label="collection"), "CalDAV|home1/calendar_1")
 
+
     @inlineCallbacks
     def test_nodeNameSuccess(self):
         home = yield self.homeUnderTest()
         name = yield home.nodeName()
         self.assertEquals(name, "/CalDAV/example.com/home1/")
 
+
     @inlineCallbacks
     def test_nodeNameFailure(self):
         # The StubNodeCacher is set up to fail when the node name has the
@@ -589,7 +591,66 @@
         name = yield home.nodeName()
         self.assertEquals(name, None)
 
+
     @inlineCallbacks
+    def test_calendarHomes(self):
+        """
+        Finding all existing calendar homes.
+        """
+        calendarHomes = (yield self.transactionUnderTest().calendarHomes())
+        self.assertEquals(
+            [home.name() for home in calendarHomes],
+            [
+                "home1",
+                "home_no_splits",
+                "home_splits",
+                "home_splits_shared",
+            ]
+        )
+
+
+    @inlineCallbacks
+    def test_displayNameNone(self):
+        """
+        L{ICalendarHome.calendarWithName} returns C{None} for calendars which
+        do not exist.
+        """
+        home = (yield self.homeUnderTest())
+        calendar = (yield home.calendarWithName("calendar_1"))
+        name = (yield calendar.displayName())
+        self.assertEquals(name, None)
+
+
+    @inlineCallbacks
+    def test_setDisplayName(self):
+        """
+        L{ICalendarHome.calendarWithName} returns C{None} for calendars which
+        do not exist.
+        """
+        home = (yield self.homeUnderTest())
+        calendar = (yield home.calendarWithName("calendar_1"))
+
+        calendar.setDisplayName(u"quux")
+        name = calendar.displayName()
+        self.assertEquals(name, u"quux")
+
+        calendar.setDisplayName(None)
+        name = calendar.displayName()
+        self.assertEquals(name, None)
+
+
+    @inlineCallbacks
+    def test_setDisplayNameBytes(self):
+        """
+        L{ICalendarHome.calendarWithName} returns C{None} for calendars which
+        do not exist.
+        """
+        home = (yield self.homeUnderTest())
+        calendar = (yield home.calendarWithName("calendar_1"))
+        self.assertRaises(ValueError, calendar.setDisplayName, "quux")
+
+
+    @inlineCallbacks
     def test_calendarHomeWithUID_exists(self):
         """
         Finding an existing calendar home by UID results in an object that
@@ -640,6 +701,7 @@
             self.assertProvides(ICalendar, calendar)
             self.assertEquals(calendar.name(), name)
 
+
     @inlineCallbacks
     def test_calendarWithName_exists(self):
         """
@@ -736,7 +798,7 @@
         """
         L{ICalendarHome.createCalendarWithName} raises
         L{CalendarAlreadyExistsError} when the name conflicts with an already-
-        existing 
+        existing
         """
         home = yield self.homeUnderTest()
         for name in home1_calendarNames:
@@ -806,23 +868,25 @@
         result = yield maybeDeferred(calendar.getSupportedComponents)
         self.assertEquals(result, None)
 
+
     @inlineCallbacks
     def test_countComponentTypes(self):
         """
         Test Calendar._countComponentTypes to make sure correct counts are returned.
         """
-        
+
         tests = (
             ("calendar_1", (("VEVENT", 3),)),
             ("calendar_2", (("VEVENT", 3), ("VTODO", 2))),
         )
-        
+
         for calname, results in tests:
             testalendar = yield (yield self.transactionUnderTest().calendarHomeWithUID(
                 "home_splits")).calendarWithName(calname)
             result = yield maybeDeferred(testalendar._countComponentTypes)
             self.assertEquals(result, results)
 
+
     @inlineCallbacks
     def test_calendarObjects(self):
         """
@@ -947,6 +1011,7 @@
             ]
         )
 
+
     @inlineCallbacks
     def test_removeCalendarObjectWithName_exists(self):
         """
@@ -1044,6 +1109,7 @@
         invitedCals = yield cal.asShared()
         self.assertEqual(len(invitedCals), 0)
 
+
     @inlineCallbacks
     def test_unshareSharerSide(self, commit=False):
         """
@@ -1063,6 +1129,7 @@
         invitedCals = yield cal.asShared()
         self.assertEqual(len(invitedCals), 0)
 
+
     @inlineCallbacks
     def test_unshareShareeSide(self, commit=False):
         """
@@ -1082,6 +1149,7 @@
         invitedCals = yield cal.asShared()
         self.assertEqual(len(invitedCals), 0)
 
+
     @inlineCallbacks
     def test_unshareWithInDifferentTransaction(self):
         """
@@ -1130,7 +1198,7 @@
 
         result = (yield home.hasCalendarResourceUIDSomewhereElse("uid2", object, "schedule"))
         self.assertTrue(result)
-        
+
         # FIXME:  do this without legacy calls
         '''
         from twistedcaldav.sharing import SharedCollectionRecord
@@ -1247,7 +1315,6 @@
         self.assertEquals(component.mainType(), "VEVENT")
         self.assertEquals(component.resourceUID(), "uid1")
 
-
     perUserComponent = lambda self: VComponent.fromString("""BEGIN:VCALENDAR
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
 VERSION:2.0
@@ -1282,7 +1349,6 @@
 END:VCALENDAR
 """.replace("\n", "\r\n"))
 
-
     asSeenByOwner = lambda self: VComponent.fromString("""BEGIN:VCALENDAR
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
 VERSION:2.0
@@ -1302,7 +1368,6 @@
 END:VCALENDAR
 """.replace("\n", "\r\n"))
 
-
     asSeenByOther = lambda self: VComponent.fromString("""BEGIN:VCALENDAR
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
 VERSION:2.0
@@ -1420,6 +1485,7 @@
             set(home1_calendarNames)
         )
 
+
     @inlineCallbacks
     def test_loadAllCalendars(self):
         """
@@ -1441,7 +1507,7 @@
             set(c.name() for c in calendars),
             set(home1_calendarNames)
         )
-        
+
         for c in calendars:
             self.assertTrue(c.properties() is not None)
 
@@ -1526,6 +1592,7 @@
             InvalidObjectResourceError,
         )
 
+
     @inlineCallbacks
     def test_setComponent_invalid(self):
         """
@@ -1695,7 +1762,7 @@
             self.assertNotEquals(event1_text, event1_text_withDifferentSubject)
             newComponent = VComponent.fromString(event1_text_withDifferentSubject)
             yield obj.setComponent(newComponent)
-    
+
             # Putting everything into a separate transaction to account for any
             # caching that may take place.
             yield self.commit()
@@ -1704,7 +1771,6 @@
                 propertyContent
             )
 
-
     eventWithDropbox = "\r\n".join("""
 BEGIN:VCALENDAR
 CALSCALE:GREGORIAN
@@ -1813,7 +1879,7 @@
     def test_collectionSyncToken(self):
         """
         L{ICalendar.resourceNamesSinceToken} will return the names of calendar
-        objects changed or deleted since 
+        objects changed or deleted since
         """
         cal = yield self.calendarUnderTest()
         st = yield cal.syncToken()
@@ -2037,7 +2103,7 @@
         that fails with L{QuotaExceeded}.
         """
         home = yield self.homeUnderTest()
-        attachment = yield getit() 
+        attachment = yield getit()
         t = attachment.store(MimeType("text", "x-fixture"))
         sample = "all work and no play makes jack a dull boy"
         chunk = (sample * (home.quotaAllowedBytes() / len(sample)))
@@ -2097,7 +2163,7 @@
         yield checkOriginal()
 
 
-    def test_removeAttachmentWithName(self, refresh=lambda x:x):
+    def test_removeAttachmentWithName(self, refresh=lambda x: x):
         """
         L{ICalendarObject.removeAttachmentWithName} will remove the calendar
         object with the given name.
@@ -2152,7 +2218,7 @@
 
     @inlineCallbacks
     def test_finishedOnCommit(self):
-        """ 
+        """
         Calling L{ITransaction.abort} or L{ITransaction.commit} after
         L{ITransaction.commit} has already been called raises an
         L{AlreadyFinishedError}.
@@ -2234,4 +2300,3 @@
         additionalUIDs.add("home_attachments")
         expectedUIDs = additionalUIDs.union(requiredUIDs)
         self.assertEquals(foundUIDs, expectedUIDs)
-

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/test_file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/test_file.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/test_file.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -67,7 +67,7 @@
     calendarPath.parent().makedirs()
     storePath.copyTo(calendarPath)
 
-    # Set year values to current year    
+    # Set year values to current year
     nowYear = PyCalendarDateTime.getToday().getYear()
     for home in calendarPath.child("ho").child("me").children():
         if not home.basename().startswith("."):
@@ -75,7 +75,7 @@
                 if not calendar.basename().startswith("."):
                     for resource in calendar.children():
                         if resource.basename().endswith(".ics"):
-                            resource.setContent(resource.getContent() % {"now":nowYear})
+                            resource.setContent(resource.getContent() % {"now": nowYear})
 
     testID = test.id()
     test.calendarStore = CalendarStore(storeRootPath, test.notifierFactory,
@@ -314,8 +314,8 @@
             self.calendar1.removeCalendarObjectWithName, name
         )
 
+    counter = 0
 
-    counter = 0
     @inlineCallbacks
     def _refresh(self):
         """
@@ -442,7 +442,7 @@
         L{CalendarObject} has instance attributes, C{_path} and C{_calendar},
         which refer to its position in the filesystem and the calendar in which
         it is contained, respectively.
-        """ 
+        """
         self.failUnless(
             isinstance(self.object1._path, FilePath),
             self.object1._path
@@ -482,7 +482,6 @@
         Overridden to be skipped.
         """
 
-
     # TODO: ideally the file store would support all of this sharing stuff.
     test_shareWith.skip = "Not implemented for file store yet."
     test_shareAgainChangesMode = test_shareWith
@@ -503,6 +502,25 @@
 
 
     @inlineCallbacks
+    def test_calendarHomes(self):
+        """
+        Finding all existing calendar homes.
+        """
+        calendarHomes = (yield self.transactionUnderTest().calendarHomes())
+        self.assertEquals(
+            [home.name() for home in calendarHomes],
+            [
+                "home1",
+                "home_attachments",
+                "home_bad",
+                "home_no_splits",
+                "home_splits",
+                "home_splits_shared",
+            ]
+        )
+
+
+    @inlineCallbacks
     def test_calendarObjectsWithDotFile(self):
         """
         Adding a dotfile to the calendar home should not increase the number of

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/icalendarstore.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/icalendarstore.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -72,6 +72,7 @@
         self.limit = lowerLimit
 
 
+
 class TimeRangeUpperLimit(Exception):
     """
     A request for time-range information too far in the future cannot be satisfied.
@@ -81,11 +82,20 @@
         self.limit = upperLimit
 
 
+
 class ICalendarTransaction(ICommonTransaction):
     """
     Transaction functionality required to be implemented by calendar stores.
     """
 
+    def calendarHomes():
+        """
+        Retrieve each calendar home in the store.
+
+        @return: a L{Deferred} which fires with a list of L{ICalendarHome}.
+        """
+
+
     def calendarHomeWithUID(uid, create=False):
         """
         Retrieve the calendar home for the principal with the given C{uid}.
@@ -115,6 +125,7 @@
         """
 
 
+
 #
 # Interfaces
 #
@@ -134,6 +145,7 @@
         @return: a string.
         """
 
+
     def calendars():
         """
         Retrieve calendars contained in this calendar home.
@@ -141,6 +153,7 @@
         @return: an iterable of L{ICalendar}s.
         """
 
+
     def loadCalendars():
         """
         Pre-load all calendars Depth:1.
@@ -148,6 +161,7 @@
         @return: an iterable of L{ICalendar}s.
         """
 
+
     def calendarWithName(name):
         """
         Retrieve the calendar with the given C{name} contained in this
@@ -185,6 +199,7 @@
             given C{name} already exists.
         """
 
+
     def removeCalendarWithName(name):
         """
         Remove the calendar with the given C{name} from this calendar
@@ -260,7 +275,20 @@
         Change the name of this calendar.
         """
 
+    def displayName():
+        """
+        Get the display name of this calendar.
 
+        @return: a unicode string.
+        """
+
+    def setDisplayName(name):
+        """
+        Set the display name of this calendar.
+
+        @param name: a C{unicode}.
+        """
+
     def ownerCalendarHome():
         """
         Retrieve the calendar home for the owner of this calendar.  Calendars
@@ -275,6 +303,7 @@
         @return: an L{ICalendarHome}.
         """
 
+
     def calendarObjects():
         """
         Retrieve the calendar objects contained in this calendar.
@@ -282,6 +311,7 @@
         @return: an iterable of L{ICalendarObject}s.
         """
 
+
     def calendarObjectWithName(name):
         """
         Retrieve the calendar object with the given C{name} contained
@@ -292,6 +322,7 @@
             object exists.
         """
 
+
     def calendarObjectWithUID(uid):
         """
         Retrieve the calendar object with the given C{uid} contained
@@ -303,6 +334,7 @@
             such calendar object exists.
         """
 
+
     def createCalendarObjectWithName(name, component):
         """
         Create a calendar component with the given C{name} in this
@@ -320,6 +352,7 @@
             a calendar object.
         """
 
+
     def removeCalendarObjectWithName(name):
         """
         Remove the calendar object with the given C{name} from this
@@ -330,6 +363,7 @@
             exists.
         """
 
+
     def removeCalendarObjectWithUID(uid):
         """
         Remove the calendar object with the given C{uid} from this
@@ -340,6 +374,7 @@
             not exist.
         """
 
+
     def syncToken():
         """
         Retrieve the current sync token for this calendar.
@@ -347,6 +382,7 @@
         @return: a string containing a sync token.
         """
 
+
     def calendarObjectsInTimeRange(start, end, timeZone):
         """
         Retrieve all calendar objects in this calendar which have
@@ -359,6 +395,7 @@
         @return: an iterable of L{ICalendarObject}s.
         """
 
+
     def calendarObjectsSinceToken(token):
         """
         Retrieve all calendar objects in this calendar that have
@@ -417,6 +454,8 @@
         """
         # TODO: implement this for the file store.
 
+
+
 class ICalendarObject(IDataStoreObject):
     """
     Calendar object
@@ -431,6 +470,7 @@
         @rtype: L{ICalendar}
         """
 
+
     def setComponent(component):
         """
         Rewrite this calendar object to match the given C{component}.
@@ -443,6 +483,7 @@
             a calendar object.
         """
 
+
     def component():
         """
         Retrieve the calendar component for this calendar object.
@@ -454,6 +495,7 @@
         @return: a C{VCALENDAR} L{VComponent}.
         """
 
+
     def uid():
         """
         Retrieve the UID for this calendar object.
@@ -461,6 +503,7 @@
         @return: a string containing a UID.
         """
 
+
     def componentType():
         """
         Retrieve the iCalendar component type for the main component
@@ -484,6 +527,7 @@
         @return: a URI string.
         """
 
+
     def dropboxID():
         """
         An identifier, unique to the calendar home, that specifies a location
@@ -616,5 +660,3 @@
             that the stream is complete to its C{connectionLost} method.
         @type protocol: L{IProtocol}
         """
-
-

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/datastore/test/common.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/datastore/test/common.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -234,6 +234,20 @@
 
 
     @inlineCallbacks
+    def test_addressbookHomes(self):
+        """
+        Finding all existing addressbook homes.
+        """
+        addressbookHomes = (yield self.transactionUnderTest().addressbookHomes())
+        self.assertEquals(
+            [home.name() for home in addressbookHomes],
+            [
+                "home1",
+            ]
+        )
+
+
+    @inlineCallbacks
     def test_addressbookHomeWithUID_exists(self):
         """
         Finding an existing addressbook home by UID results in an object that
@@ -664,7 +678,7 @@
             set(c.name() for c in addressbooks),
             set(home1_addressbookNames)
         )
-        
+
         for c in addressbooks:
             self.assertTrue(c.properties() is not None)
 
@@ -910,7 +924,7 @@
             self.assertNotEquals(vcard1_text, vcard1_text_withDifferentNote)
             newComponent = VComponent.fromString(vcard1_text_withDifferentNote)
             yield obj.setComponent(newComponent)
-    
+
             # Putting everything into a separate transaction to account for any
             # caching that may take place.
             yield self.commit()
@@ -979,6 +993,3 @@
         additionalUIDs.add("home_bad")
         expectedUIDs = additionalUIDs.union(requiredUIDs)
         self.assertEquals(foundUIDs, expectedUIDs)
-
-
-

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/datastore/test/test_file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/datastore/test/test_file.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/datastore/test/test_file.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -434,7 +434,7 @@
         L{AddressBookObject} has instance attributes, C{_path} and C{_addressbook},
         which refer to its position in the filesystem and the addressbook in which
         it is contained, respectively.
-        """ 
+        """
         self.failUnless(
             isinstance(self.object1._path, FilePath),
             self.object1._path
@@ -445,6 +445,7 @@
         )
 
 
+
 class FileStorageTests(CommonTests, unittest.TestCase):
     """
     File storage tests.
@@ -471,6 +472,21 @@
 
 
     @inlineCallbacks
+    def test_addressbookHomes(self):
+        """
+        Finding all existing addressbook homes.
+        """
+        addressbookHomes = (yield self.transactionUnderTest().addressbookHomes())
+        self.assertEquals(
+            [home.name() for home in addressbookHomes],
+            [
+                "home1",
+                "home_bad",
+            ]
+        )
+
+
+    @inlineCallbacks
     def test_addressbookObjectsWithDotFile(self):
         """
         Adding a dotfile to the addressbook home should not create a new
@@ -489,5 +505,3 @@
         ((yield self.addressbookUnderTest())._path.child("not-a-vcard")
          .createDirectory())
         yield self.test_addressbookObjects()
-
-

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/iaddressbookstore.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/iaddressbookstore.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/iaddressbookstore.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -19,7 +19,7 @@
 Address book store interfaces
 """
 
-from txdav.common.icommondatastore import ICommonTransaction,\
+from txdav.common.icommondatastore import ICommonTransaction, \
     IShareableCollection
 from txdav.idav import INotifier
 from txdav.idav import IDataStoreObject
@@ -37,6 +37,14 @@
     Transaction interface that addressbook stores must provide.
     """
 
+    def addressbookHomes():
+        """
+        Retrieve each addressbook home in the store.
+
+        @return: a L{Deferred} which fires with a list of L{ICalendarHome}.
+        """
+
+
     def addressbookHomeWithUID(uid, create=False):
         """
         Retrieve the addressbook home for the principal with the given C{uid}.
@@ -79,6 +87,7 @@
         @return: an iterable of L{IAddressBook}s.
         """
 
+
     def loadAddressbooks():
         """
         Pre-load all addressbooks Depth:1.
@@ -86,6 +95,7 @@
         @return: an iterable of L{IAddressBook}s.
         """
 
+
     def addressbookWithName(name):
         """
         Retrieve the addressbook with the given C{name} contained in this
@@ -96,6 +106,7 @@
             exists.
         """
 
+
     def createAddressBookWithName(name):
         """
         Create an addressbook with the given C{name} in this addressbook
@@ -106,6 +117,7 @@
             given C{name} already exists.
         """
 
+
     def removeAddressBookWithName(name):
         """
         Remove the addressbook with the given C{name} from this addressbook
@@ -117,6 +129,7 @@
         """
 
 
+
 class IAddressBook(INotifier, IShareableCollection, IDataStoreObject):
     """
     AddressBook
@@ -132,6 +145,7 @@
         Change the name of this addressbook.
         """
 
+
     def ownerAddressBookHome():
         """
         Retrieve the addressbook home for the owner of this addressbook.
@@ -141,6 +155,7 @@
         @return: an L{IAddressBookHome}.
         """
 
+
     def addressbookObjects():
         """
         Retrieve the addressbook objects contained in this addressbook.
@@ -148,13 +163,14 @@
         @return: an iterable of L{IAddressBookObject}s.
         """
 
+
     def addressbookObjectWithName(name):
         """
         Retrieve the addressbook object with the given C{name} contained
         in this addressbook.
 
         @param name: a string.
-        
+
         @return: a L{Deferred} that fires with an L{IAddressBookObject} or
             C{None} if no such addressbook object exists.
         """
@@ -169,6 +185,7 @@
             object exists.
         """
 
+
     def createAddressBookObjectWithName(name, component):
         """
         Create an addressbook component with the given C{name} in this
@@ -186,6 +203,7 @@
             an addressbook object.
         """
 
+
     def removeAddressBookObjectWithName(name):
         """
         Remove the addressbook object with the given C{name} from this
@@ -196,6 +214,7 @@
             exists.
         """
 
+
     def removeAddressBookObjectWithUID(uid):
         """
         Remove the addressbook object with the given C{uid} from this
@@ -206,6 +225,7 @@
             not exist.
         """
 
+
     def syncToken():
         """
         Retrieve the current sync token for this addressbook.
@@ -213,6 +233,7 @@
         @return: a string containing a sync token.
         """
 
+
     def addressbookObjectsSinceToken(token):
         """
         Retrieve all addressbook objects in this addressbook that have
@@ -225,6 +246,7 @@
         """
 
 
+
 class IAddressBookObject(IDataStoreObject):
     """
     AddressBook object
@@ -250,6 +272,7 @@
             an addressbook object.
         """
 
+
     def component():
         """
         Retrieve the addressbook component for this addressbook object.
@@ -261,6 +284,7 @@
         @return: a C{VCARD} L{VComponent}.
         """
 
+
     def uid():
         """
         Retrieve the UID for this addressbook object.

Copied: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/common.py (from rev 9978, CalendarServer/trunk/txdav/common/datastore/common.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/common.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/common.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -0,0 +1,55 @@
+# -*- test-case-name: txdav -*-
+##
+# Copyright (c) 2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# 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.
+##
+
+"""
+Common functionality that is the same for different data store
+implementations.
+"""
+
+__all__ = [
+]
+
+
+from txdav.xml.element import DisplayName
+from txdav.base.propertystore.base import PropertyName
+
+
+class HomeChildBase(object):
+    """
+    Home child (address book or calendar) common functionality.
+    """
+
+    def displayName(self):
+        name = self.properties().get(PropertyName.fromElement(DisplayName), None)
+        if name is None:
+            return None
+        else:
+            return name.toString()
+
+
+    def setDisplayName(self, name):
+        if name is None:
+            del self.properties()[PropertyName.fromElement(DisplayName)]
+        else:
+            if not isinstance(name, unicode):
+                raise ValueError("Display name must be unicode: %r" % (name,))
+
+            self.properties()[
+                PropertyName.fromElement(DisplayName)
+            ] = DisplayName.fromString(name)
+
+        return None

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/file.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/file.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -24,7 +24,7 @@
 from txdav.xml.rfc2518 import ResourceType, GETContentType, HRef
 from txdav.xml.rfc5842 import ResourceID
 from twext.web2.http_headers import generateContentType, MimeType
-from twext.web2.dav.resource import TwistedGETContentMD5,\
+from twext.web2.dav.resource import TwistedGETContentMD5, \
     TwistedQuotaUsedProperty
 
 from twisted.internet.defer import succeed, inlineCallbacks, returnValue
@@ -38,6 +38,7 @@
 from twistedcaldav.sharing import SharedCollectionsDatabase
 from txdav.caldav.icalendarstore import ICalendarStore, BIND_OWN
 
+from txdav.common.datastore.common import HomeChildBase
 from txdav.common.icommondatastore import HomeChildNameNotAllowedError, \
     HomeChildNameAlreadyExistsError, NoSuchHomeChildError, \
     InternalDataStoreError, ObjectResourceNameNotAllowedError, \
@@ -138,6 +139,7 @@
         self._migrating = state
         self._enableNotifications = not state
 
+
     def setUpgrading(self, state):
         """
         Set the "upgrading" state
@@ -178,6 +180,7 @@
         return self._homesOfType(EADDRESSBOOKTYPE)
 
 
+
 class CommonStoreTransaction(DataStoreTransaction):
     """
     In-memory implementation of
@@ -225,14 +228,45 @@
         CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = AddressBookHome
 
 
-    @memoizedKey('uid', '_calendarHomes')
+    def calendarHomes(self):
+        return self.homes(ECALENDARTYPE)
+
+
     def calendarHomeWithUID(self, uid, create=False):
         return self.homeWithUID(ECALENDARTYPE, uid, create=create)
 
-    @memoizedKey("uid", "_addressbookHomes")
+
+    def addressbookHomes(self):
+        return self.homes(EADDRESSBOOKTYPE)
+
+
     def addressbookHomeWithUID(self, uid, create=False):
         return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
 
+
+    def _determineMemo(self, storeType, uid, create=False):
+        """
+        Determine the memo dictionary to use for homeWithUID.
+        """
+        if storeType == ECALENDARTYPE:
+            return self._calendarHomes
+        else:
+            return self._addressbookHomes
+
+
+    def homes(self, storeType):
+        """
+        Load all calendar or addressbook homes.
+        """
+        uids = self._homeClass[storeType].listHomes(self)
+        for uid in uids:
+            self.homeWithUID(storeType, uid, create=False)
+
+        # Return the memoized list directly
+        returnValue([kv[1] for kv in sorted(self._determineMemo(storeType, None).items(), key=lambda x: x[0])])
+
+
+    @memoizedKey("uid", _determineMemo)
     def homeWithUID(self, storeType, uid, create=False):
         if uid.startswith("."):
             return None
@@ -242,6 +276,7 @@
 
         return self._homeClass[storeType].homeWithUID(self, uid, create, storeType == ECALENDARTYPE)
 
+
     @memoizedKey("uid", "_notificationHomes")
     def notificationsWithUID(self, uid, home=None):
 
@@ -254,27 +289,35 @@
     def addAPNSubscription(self, token, key, timestamp, subscriber, userAgent, ipAddr):
         return NotImplementedError
 
+
     def removeAPNSubscription(self, token, key):
         return NotImplementedError
 
+
     def purgeOldAPNSubscriptions(self, purgeSeconds):
         return NotImplementedError
 
+
     def apnSubscriptionsByToken(self, token):
         return NotImplementedError
 
+
     def apnSubscriptionsByKey(self, key):
         return NotImplementedError
 
+
     def apnSubscriptionsBySubscriber(self, guid):
         return NotImplementedError
 
+
     def isNotifiedAlready(self, obj):
         return obj in self._notifiedAlready
 
+
     def notificationAddedForObject(self, obj):
         self._notifiedAlready.add(obj)
 
+
     def isBumpedAlready(self, obj):
         """
         Indicates whether or not bumpAddedForObject has already been
@@ -283,6 +326,7 @@
         """
         return obj in self._bumpedAlready
 
+
     def bumpAddedForObject(self, obj):
         """
         Records the fact that a bumpModified( ) call has already been
@@ -331,6 +375,31 @@
 
 
     @classmethod
+    def listHomes(cls, txn):
+        """
+        Retrieve the owner UIDs of all existing homes.
+
+        @return: an iterable of C{str}s.
+        """
+        results = []
+        top = txn._dataStore._path.child(cls._topPath)
+        if top.exists() and top.isdir() and top.child(UIDPATH).exists():
+            for firstPrefix in top.child(UIDPATH).children():
+                if not isValidName(firstPrefix.basename()):
+                    continue
+                for secondPrefix in firstPrefix.children():
+                    if not isValidName(secondPrefix.basename()):
+                        continue
+                    for actualHome in secondPrefix.children():
+                        uid = actualHome.basename()
+                        if not isValidName(uid):
+                            continue
+                        results.append(uid)
+
+        return results
+
+
+    @classmethod
     def homeWithUID(cls, txn, uid, create=False, withNotifications=False):
 
         assert len(uid) >= 4
@@ -390,6 +459,7 @@
 
         return home
 
+
     def __repr__(self):
         return "<%s: %s>" % (self.__class__.__name__, self._path)
 
@@ -401,6 +471,7 @@
     def transaction(self):
         return self._transaction
 
+
     def retrieveOldShares(self):
         """
         Retrieve the old Index object.
@@ -433,7 +504,7 @@
         ) | set(
             name
             for name in self._path.listdir()
-            if not name.startswith(".") and 
+            if not name.startswith(".") and
                 self._path.child(name).isdir() and
                 name not in self._removedChildren
         ))
@@ -515,6 +586,7 @@
         self.notifyChanged()
         return c
 
+
     @writeOperation
     def removeChildWithName(self, name):
         if name.startswith(".") or name in self._removedChildren:
@@ -532,13 +604,14 @@
             else:
                 self._removedChildren.add(name)
 
+
     @inlineCallbacks
     def syncToken(self):
-        
+
         maxrev = 0
         for child in self.children():
             maxrev = max(int((yield child.syncToken()).split("_")[1]), maxrev)
-            
+
         try:
             urnuuid = str(self.properties()[PropertyName.fromElement(ResourceID)].children[0])
         except KeyError:
@@ -564,6 +637,7 @@
         self._transaction.addOperation(props.flush, "flush home properties")
         return props
 
+
     def objectResourcesWithUID(self, uid, ignore_children=()):
         """
         Return all child object resources with the specified UID, ignoring any in the
@@ -578,6 +652,7 @@
                 results.append(object)
         return results
 
+
     def quotaUsedBytes(self):
 
         try:
@@ -585,31 +660,34 @@
         except KeyError:
             return 0
 
+
     def adjustQuotaUsedBytes(self, delta):
         """
         Adjust quota used. We need to get a lock on the row first so that the adjustment
         is done atomically.
         """
-        
+
         old_used = self.quotaUsedBytes()
         new_used = old_used + delta
         if new_used < 0:
             self.log_error("Fixing quota adjusted below zero to %s by change amount %s" % (new_used, delta,))
             new_used = 0
         self.properties()[PropertyName.fromElement(TwistedQuotaUsedProperty)] = TwistedQuotaUsedProperty(str(new_used))
-            
 
+
     def addNotifier(self, notifier):
         if self._notifiers is None:
             self._notifiers = ()
         self._notifiers += (notifier,)
- 
+
+
     def notifierID(self, label="default"):
         if self._notifiers:
             return self._notifiers[0].getID(label)
         else:
             return None
 
+
     @inlineCallbacks
     def nodeName(self, label="default"):
         if self._notifiers:
@@ -620,6 +698,7 @@
         else:
             returnValue(None)
 
+
     def notifyChanged(self):
         """
         Trigger a notification of a change
@@ -632,7 +711,8 @@
             self._transaction.notificationAddedForObject(self)
 
 
-class CommonHomeChild(FileMetaDataMixin, LoggingMixIn, FancyEqMixin):
+
+class CommonHomeChild(FileMetaDataMixin, LoggingMixIn, FancyEqMixin, HomeChildBase):
     """
     Common ancestor class of AddressBooks and Calendars.
     """
@@ -683,6 +763,7 @@
     def objectWithName(cls, home, name, owned):
         return cls(name, home, owned) if home._path.child(name).isdir() else None
 
+
     @property
     def _path(self):
         return self._home._path.child(self._name)
@@ -691,12 +772,14 @@
     def resourceType(self):
         return NotImplementedError
 
+
     def retrieveOldIndex(self):
         """
         Retrieve the old Index object.
         """
         return self._index._oldIndex
 
+
     def retrieveOldInvites(self):
         """
         Retrieve the old Invites DB object.
@@ -721,6 +804,7 @@
         """
         return BIND_OWN
 
+
     def owned(self):
         return self._owned
 
@@ -742,6 +826,7 @@
 
         self.notifyChanged()
 
+
     @writeOperation
     def remove(self):
 
@@ -855,7 +940,7 @@
         rname = self.retrieveOldIndex().resourceNameForUID(uid)
         if rname and rname not in self._removedObjectResources:
             return self.objectResourceWithName(rname)
-        
+
         return None
 
 
@@ -863,7 +948,7 @@
     def createObjectResourceWithName(self, name, component, metadata=None):
         """
         Create a new resource with component data and optional metadata. We create the
-        python object using the metadata then create the actual store object with setComponent. 
+        python object using the metadata then create the actual store object with setComponent.
         """
         if name.startswith("."):
             raise ObjectResourceNameNotAllowedError(name)
@@ -934,6 +1019,7 @@
         """
         return True
 
+
     # FIXME: property writes should be a write operation
     @cached
     def properties(self):
@@ -957,17 +1043,20 @@
         """
         pass
 
+
     def addNotifier(self, notifier):
         if self._notifiers is None:
             self._notifiers = ()
         self._notifiers += (notifier,)
- 
+
+
     def notifierID(self, label="default"):
         if self._notifiers:
             return self._notifiers[0].getID(label)
         else:
             return None
 
+
     @inlineCallbacks
     def nodeName(self, label="default"):
         if self._notifiers:
@@ -978,6 +1067,7 @@
         else:
             returnValue(None)
 
+
     def notifyChanged(self):
         """
         Trigger a notification of a change
@@ -990,6 +1080,7 @@
             self._transaction.notificationAddedForObject(self)
 
 
+
 class CommonObjectResource(FileMetaDataMixin, LoggingMixIn, FancyEqMixin):
     """
     @ivar _path: The path of the file on disk
@@ -1017,9 +1108,11 @@
     def __repr__(self):
         return "<%s: %s>" % (self.__class__.__name__, self._path.path)
 
+
     def transaction(self):
         return self._transaction
 
+
     @writeOperation
     def setComponent(self, component, inserting=False):
         raise NotImplementedError
@@ -1036,6 +1129,7 @@
     def uid(self):
         raise NotImplementedError
 
+
     @cached
     def properties(self):
         home = self._parentCollection._home
@@ -1049,6 +1143,7 @@
         self._transaction.addOperation(props.flush, "object properties flush")
         return props
 
+
     def initPropertyStore(self, props):
         """
         A hook for subclasses to override in order to set up their property
@@ -1058,6 +1153,8 @@
         """
         pass
 
+
+
 class CommonStubResource(object):
     """
     Just enough resource to keep the collection sql DB classes going.
@@ -1066,6 +1163,7 @@
         self.resource = resource
         self.fp = self.resource._path
 
+
     def bumpSyncToken(self, reset=False):
         # FIXME: needs direct tests
         return self.resource._updateSyncToken(reset)
@@ -1076,6 +1174,7 @@
         self.bumpSyncToken(True)
 
 
+
 class NotificationCollection(CommonHomeChild):
     """
     File-based implementation of L{INotificationCollection}.
@@ -1100,6 +1199,7 @@
         self._invites = None
         self._objectResourceClass = NotificationObject
 
+
     @classmethod
     def notificationsFromHome(cls, txn, home):
 
@@ -1143,6 +1243,7 @@
         props[PropertyName(*ResourceType.qname())] = c.resourceType()
         return c
 
+
     def resourceType(self):
         return ResourceType.notification #@UndefinedVariable
 
@@ -1155,6 +1256,7 @@
         name = uid + ".xml"
         return self.notificationObjectWithName(name)
 
+
     def writeNotificationObject(self, uid, xmltype, xmldata):
         name = uid + ".xml"
         if name.startswith("."):
@@ -1166,9 +1268,10 @@
 
         # Update database
         self.retrieveOldIndex().addOrUpdateRecord(NotificationRecord(uid, name, xmltype.name))
-        
+
         self.notifyChanged()
 
+
     @writeOperation
     def removeNotificationObjectWithName(self, name):
         if name.startswith("."):
@@ -1185,17 +1288,19 @@
                 return lambda: None
             self._transaction.addOperation(do, "remove object resource object %r" %
                                            (name,))
-        
+
             self.notifyChanged()
         else:
             raise NoSuchObjectResourceError(name)
 
+
     @writeOperation
     def removeNotificationObjectWithUID(self, uid):
         name = uid + ".xml"
         self.removeNotificationObjectWithName(name)
 
 
+
 class NotificationObject(CommonObjectResource):
     """
     """
@@ -1205,6 +1310,7 @@
         super(NotificationObject, self).__init__(name, notifications)
         self._uid = name[:-4]
 
+
     def notificationCollection(self):
         return self._parentCollection
 
@@ -1215,6 +1321,7 @@
             return int(reactor.seconds())
         return super(NotificationObject, self).created()
 
+
     def modified(self):
         if not self._path.exists():
             from twisted.internet import reactor
@@ -1260,11 +1367,10 @@
         self.properties().update(self.properties())
 
         props = self.properties()
-        props[PropertyName(*GETContentType.qname())] = GETContentType.fromString(generateContentType(MimeType("text", "xml", params={"charset":"utf-8"})))
+        props[PropertyName(*GETContentType.qname())] = GETContentType.fromString(generateContentType(MimeType("text", "xml", params={"charset": "utf-8"})))
         props[PropertyName.fromElement(NotificationType)] = NotificationType(xmltype)
         props[PropertyName.fromElement(TwistedGETContentMD5)] = TwistedGETContentMD5.fromString(md5)
 
-
         # FIXME: the property store's flush() method may already have been
         # added to the transaction, but we need to add it again to make sure it
         # happens _after_ the new file has been written.  we may end up doing
@@ -1272,7 +1378,6 @@
         # manipulation methods won't work.
         self._transaction.addOperation(self.properties().flush, "post-update property flush")
 
-
     _xmldata = None
 
     def xmldata(self):
@@ -1297,10 +1402,12 @@
     def uid(self):
         return self._uid
 
+
     def xmlType(self):
         # NB This is the NotificationType property element
         return self.properties()[PropertyName.fromElement(NotificationType)]
 
+
     def initPropertyStore(self, props):
         # Setup peruser special properties
         props.setSpecialProperties(
@@ -1312,6 +1419,7 @@
         )
 
 
+
 class NotificationIndex(object):
     #
     # OK, here's where we get ugly.
@@ -1321,4 +1429,3 @@
         self.notificationCollection = notificationCollection
         stubResource = CommonStubResource(notificationCollection)
         self._oldIndex = OldNotificationIndex(stubResource)
-

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/sql.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/sql.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -31,7 +31,8 @@
 
 from twext.python.log import Logger, LoggingMixIn
 from twisted.python.log import msg as log_msg, err as log_err
-from txdav.xml.rfc2518 import ResourceType
+
+from txdav.xml.element import ResourceType
 from txdav.xml.parser import WebDAVDocument
 from twext.web2.http_headers import MimeType
 
@@ -53,6 +54,7 @@
 
 from txdav.carddav.iaddressbookstore import IAddressBookTransaction
 
+from txdav.common.datastore.common import HomeChildBase
 from txdav.common.datastore.sql_tables import schema
 from txdav.common.datastore.sql_tables import _BIND_MODE_OWN, \
     _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED, \
@@ -463,10 +465,18 @@
         raise RuntimeError("Database key %s cannot be determined." % (key,))
 
 
+    def calendarHomes(self):
+        return self.homes(ECALENDARTYPE)
+
+
     def calendarHomeWithUID(self, uid, create=False):
         return self.homeWithUID(ECALENDARTYPE, uid, create=create)
 
 
+    def addressbookHomes(self):
+        return self.homes(EADDRESSBOOKTYPE)
+
+
     def addressbookHomeWithUID(self, uid, create=False):
         return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
 
@@ -481,6 +491,21 @@
             return self._addressbookHomes
 
 
+    @inlineCallbacks
+    def homes(self, storeType):
+        """
+        Load all calendar or addressbook homes.
+        """
+
+        # Get all UIDs and load them - this will memoize all existing ones
+        uids = (yield self._homeClass[storeType].listHomes(self))
+        for uid in uids:
+            yield self.homeWithUID(storeType, uid, create=False)
+
+        # Return the memoized list directly
+        returnValue([kv[1] for kv in sorted(self._determineMemo(storeType, None).items(), key=lambda x: x[0])])
+
+
     @memoizedKey("uid", _determineMemo)
     def homeWithUID(self, storeType, uid, create=False):
         if storeType not in (ECALENDARTYPE, EADDRESSBOOKTYPE):
@@ -726,6 +751,13 @@
         block = self._sqlTxn.commandBlock()
         sp = self._savepoint()
         failuresToMaybeLog = []
+        def end():
+            block.end()
+            for f in failuresToMaybeLog:
+                # TODO: direct tests, to make sure error logging
+                # happens correctly in all cases.
+                log.err(f)
+            raise AllRetriesFailed()
         triesLeft = retries
         try:
             while True:
@@ -733,8 +765,9 @@
                 try:
                     result = yield thunk(block)
                 except:
+                    f = Failure()
                     if not failureOK:
-                        failuresToMaybeLog.append(Failure())
+                        failuresToMaybeLog.append(f)
                     yield sp.rollback(block)
                     if triesLeft:
                         triesLeft -= 1
@@ -749,12 +782,7 @@
                         block = newBlock
                         sp = self._savepoint()
                     else:
-                        block.end()
-                        for f in failuresToMaybeLog:
-                            # TODO: direct tests, to make sure error logging
-                            # happens correctly in all cases.
-                            log.err(f)
-                        raise AllRetriesFailed()
+                        end()
                 else:
                     yield sp.release(block)
                     block.end()
@@ -765,9 +793,10 @@
             # and only that case - acquire() or release() or commandBlock() may
             # raise an AlreadyFinishedError (either synchronously, or in the
             # case of the first two, possibly asynchronously as well).  We can
-            # safely ignore this, because it can't have any real effect; our
-            # caller shouldn't be paying attention anyway.
-            block.end()
+            # safely ignore this error, because it can't have any effect on what
+            # gets written; our caller will just get told that it failed in a
+            # way they have to be prepared for anyway.
+            end()
 
 
     @inlineCallbacks
@@ -1101,6 +1130,22 @@
 
     @classmethod
     @inlineCallbacks
+    def listHomes(cls, txn):
+        """
+        Retrieve the owner UIDs of all existing homes.
+
+        @return: an iterable of C{str}s.
+        """
+        rows = yield Select(
+            [cls._homeSchema.OWNER_UID],
+            From=cls._homeSchema,
+        ).on(txn)
+        rids = [row[0] for row in rows]
+        returnValue(rids)
+
+
+    @classmethod
+    @inlineCallbacks
     def homeWithUID(cls, txn, uid, create=False):
         if txn._notifierFactory:
             notifiers = (txn._notifierFactory.newNotifier(
@@ -1982,7 +2027,7 @@
 
 
 
-class CommonHomeChild(LoggingMixIn, FancyEqMixin, _SharedSyncLogic):
+class CommonHomeChild(LoggingMixIn, FancyEqMixin, _SharedSyncLogic, HomeChildBase):
     """
     Common ancestor class of AddressBooks and Calendars.
     """

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/test/test_sql.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/test/test_sql.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -24,6 +24,7 @@
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.internet.task import Clock
 from twisted.trial.unittest import TestCase
+from twisted.internet.defer import Deferred
 
 from txdav.common.datastore.sql import log, CommonStoreTransactionMonitor,\
     CommonHome, CommonHomeChild, ECALENDARTYPE
@@ -227,7 +228,8 @@
     @inlineCallbacks
     def test_subtransactionFailSomeRetries(self):
         """
-        txn.subtransaction runs loop three times when all fail and two retries requested.
+        txn.subtransaction runs loop three times when all fail and two retries
+        requested.
         """
         
         txn = self.transactionUnderTest()
@@ -251,7 +253,35 @@
             self.fail("AllRetriesFailed not raised")
         self.assertEqual(ctr[0], 3)
 
+
     @inlineCallbacks
+    def test_subtransactionAbortOuterTransaction(self):
+        """
+        If an outer transaction that is holding a subtransaction open is
+        aborted, then the L{Deferred} returned by L{subtransaction} raises
+        L{AllRetriesFailed}.
+        """
+        txn = self.transactionUnderTest()
+        cs = schema.CALENDARSERVER
+        waitAMoment = Deferred()
+        @inlineCallbacks
+        def later(subtxn):
+            yield waitAMoment
+            value = yield Select([cs.VALUE], From=cs).on(subtxn)
+            returnValue(value)
+        started = txn.subtransaction(later)
+        txn.abort()
+        waitAMoment.callback(True)
+        try:
+            result = yield started
+        except AllRetriesFailed:
+            pass
+        else:
+            self.fail("AllRetriesFailed not raised, %r returned instead" %
+                      (result,))
+
+
+    @inlineCallbacks
     def test_changeRevision(self):
         """
         CommonHomeChild._changeRevision actions.

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/xml/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/xml/__init__.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/xml/__init__.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -52,4 +52,6 @@
 import txdav.xml.rfc6578
 import txdav.xml.extensions
 
+# FIXME: add symbols to __all__
+
 txdav # Shhh pyflakes

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/xml/base.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/xml/base.py	2012-10-24 19:10:28 UTC (rev 9978)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/xml/base.py	2012-10-24 19:57:08 UTC (rev 9979)
@@ -589,15 +589,24 @@
     def fromString(clazz, string):
         if string is None:
             return clazz()
-        elif isinstance(string, (str, unicode)):
+        elif isinstance(string, (unicode, str)):
             return clazz(PCDATAElement(string))
         else:
             return clazz(PCDATAElement(str(string)))
 
     allowed_children = { PCDATAElement: (0, None) }
 
+    def toString(self):
+        """
+        @return: a unicode string containing the text in this element.
+        """
+        return self.__str__().decode("utf-8")
+
     def __str__(self):
-        return "".join([c.data for c in self.children])
+        """
+        @return: a byte string containing the text in this element.
+        """
+        return b"".join([c.data for c in self.children])
 
     def __repr__(self):
         content = str(self)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20121024/93e13a51/attachment-0001.html>


More information about the calendarserver-changes mailing list