[CalendarServer-changes] [6186] CalendarServer/branches/generic-sqlstore

source_changes at macosforge.org source_changes at macosforge.org
Wed Aug 25 18:34:39 PDT 2010


Revision: 6186
          http://trac.macosforge.org/projects/calendarserver/changeset/6186
Author:   cdaboo at apple.com
Date:     2010-08-25 18:34:38 -0700 (Wed, 25 Aug 2010)
Log Message:
-----------
Push txcaldav and txcarddav modules into txdav and rename for consistency.

Modified Paths:
--------------
    CalendarServer/branches/generic-sqlstore/pyflakes
    CalendarServer/branches/generic-sqlstore/setup.py
    CalendarServer/branches/generic-sqlstore/support/Makefile.Apple
    CalendarServer/branches/generic-sqlstore/test
    CalendarServer/branches/generic-sqlstore/twistedcaldav/resource.py
    CalendarServer/branches/generic-sqlstore/twistedcaldav/storebridge.py
    CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_calendarquery.py
    CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_sharing.py
    CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_wrapping.py
    CalendarServer/branches/generic-sqlstore/txdav/base/datastore/util.py
    CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/sql.py
    CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_sql.py
    CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/xattr.py
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/scheduling.py
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/__init__.py
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_file.py
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_scheduling.py
    CalendarServer/branches/generic-sqlstore/txdav/caldav/icalendarstore.py
    CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/__init__.py
    CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_file.py
    CalendarServer/branches/generic-sqlstore/txdav/carddav/iaddressbookstore.py
    CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py
    CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py
    CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_legacy.py
    CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_tables.py
    CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/__init__.py
    CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/util.py

Added Paths:
-----------
    CalendarServer/branches/generic-sqlstore/txdav/caldav/
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/sql.py
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_sql.py
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py
    CalendarServer/branches/generic-sqlstore/txdav/carddav/
    CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/
    CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py
    CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/sql.py
    CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py
    CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_sql.py
    CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/util.py

Removed Paths:
-------------
    CalendarServer/branches/generic-sqlstore/txcaldav/
    CalendarServer/branches/generic-sqlstore/txcarddav/
    CalendarServer/branches/generic-sqlstore/txdav/caldav/calendarstore/
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/postgres.py
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/postgres_schema_v1.sql
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_postgres.py
    CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py
    CalendarServer/branches/generic-sqlstore/txdav/carddav/addressbookstore/
    CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py
    CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py

Modified: CalendarServer/branches/generic-sqlstore/pyflakes
===================================================================
--- CalendarServer/branches/generic-sqlstore/pyflakes	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/pyflakes	2010-08-26 01:34:38 UTC (rev 6186)
@@ -9,7 +9,7 @@
 export PYTHONPATH="${flakes}:${PYTHONPATH:-}";
 
 if [ $# -eq 0 ]; then
-  set - calendarserver twistedcaldav twext txdav txcaldav txcarddav;
+  set - calendarserver twistedcaldav twext txdav;
 fi;
 
 cd "${wd}" && "${flakes}/bin/pyflakes" "$@" | sed                     \

Modified: CalendarServer/branches/generic-sqlstore/setup.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/setup.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/setup.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -108,7 +108,7 @@
                                "zoneinfo/*/*/*.ics",
                                "images/*/*.jpg",
                              ],
-                             "txcaldav.calendarstore": [
+                             "txdav.common.datastore": [
                                "*.sql",
                              ],
                            },

Modified: CalendarServer/branches/generic-sqlstore/support/Makefile.Apple
===================================================================
--- CalendarServer/branches/generic-sqlstore/support/Makefile.Apple	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/support/Makefile.Apple	2010-08-26 01:34:38 UTC (rev 6186)
@@ -127,7 +127,7 @@
 $(BuildDirectory)/$(Project):
 	@echo "Copying source for $(Project)..."
 	$(_v) $(MKDIR) -p "$@"
-	$(_v) pax -rw bin conf Makefile lib-patches setup.py calendarserver twistedcaldav twext txdav txcaldav txcarddav twisted support "$@/"
+	$(_v) pax -rw bin conf Makefile lib-patches setup.py calendarserver twistedcaldav twext txdav twisted support "$@/"
 
 $(BuildDirectory)/%: %.tgz
 	@echo "Extracting source for $(notdir $<)..."

Modified: CalendarServer/branches/generic-sqlstore/test
===================================================================
--- CalendarServer/branches/generic-sqlstore/test	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/test	2010-08-26 01:34:38 UTC (rev 6186)
@@ -78,7 +78,7 @@
 if [ $# -gt 0 ]; then
     test_modules="$@";
 else
-    test_modules="calendarserver twistedcaldav twext txdav txcaldav txcarddav ${m_twisted}";
+    test_modules="calendarserver twistedcaldav twext txdav ${m_twisted}";
 fi;
 
 cd "${wd}" && "${python}" "${trial}" --rterrors ${random} ${until_fail} ${no_colour} ${coverage} ${test_modules};

Modified: CalendarServer/branches/generic-sqlstore/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/twistedcaldav/resource.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/twistedcaldav/resource.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -240,14 +240,14 @@
 
     def associateWithTransaction(self, transaction):
         """
-        Associate this resource with a L{txcaldav.idav.ITransaction}; when this
+        Associate this resource with a L{txdav.caldav.idav.ITransaction}; when this
         resource (or any of its children) are rendered successfully, commit the
         transaction.  Otherwise, abort the transaction.
 
         @param transaction: the transaction to associate this resource and its
             children with.
 
-        @type transaction: L{txcaldav.idav.ITransaction} 
+        @type transaction: L{txdav.caldav.idav.ITransaction} 
         """
         # FIXME: needs to reject association with transaction if it's already
         # got one (resources associated with a transaction are not reusable)

Modified: CalendarServer/branches/generic-sqlstore/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/twistedcaldav/storebridge.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/twistedcaldav/storebridge.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -16,8 +16,8 @@
 ##
 
 """
-Wrappers to translate between the APIs in L{txcaldav.icalendarstore} and
-L{txcarddav.iaddressbookstore} and those in L{twistedcaldav}.
+Wrappers to translate between the APIs in L{txdav.caldav.icalendarstore} and
+L{txdav.carddav.iaddressbookstore} and those in L{twistedcaldav}.
 """
 
 import hashlib
@@ -206,10 +206,10 @@
         Initialize with a calendar.
 
         @param calendar: the wrapped calendar.
-        @type calendar: L{txcaldav.icalendarstore.ICalendar}
+        @type calendar: L{txdav.caldav.icalendarstore.ICalendar}
 
         @param home: the home through which the given calendar was accessed.
-        @type home: L{txcaldav.icalendarstore.ICalendarHome}
+        @type home: L{txdav.caldav.icalendarstore.ICalendarHome}
         """
         self._newStoreCalendar = calendar
         self._newStoreParentHome = home
@@ -664,12 +664,12 @@
 
 class CalendarCollectionResource(_CalendarChildHelper, CalDAVResource):
     """
-    Wrapper around a L{txcaldav.icalendar.ICalendar}.
+    Wrapper around a L{txdav.caldav.icalendar.ICalendar}.
     """
 
     def __init__(self, calendar, home, *args, **kw):
         """
-        Create a CalendarCollectionResource from a L{txcaldav.icalendar.ICalendar}
+        Create a CalendarCollectionResource from a L{txdav.caldav.icalendar.ICalendar}
         and the arguments required for L{CalDAVResource}.
         """
         super(CalendarCollectionResource, self).__init__(*args, **kw)
@@ -882,7 +882,7 @@
         @param home: The calendar home which will be this resource's parent,
             when it exists.
 
-        @type home: L{txcaldav.icalendarstore.ICalendarHome}
+        @type home: L{txdav.caldav.icalendarstore.ICalendarHome}
         """
         super(ProtoCalendarCollectionResource, self).__init__(*args, **kw)
         self._newStoreParentHome = home
@@ -957,7 +957,7 @@
         Construct a L{CalendarObjectResource} from an L{ICalendarObject}.
 
         @param calendarObject: The storage for the calendar object.
-        @type calendarObject: L{txcaldav.icalendarstore.ICalendarObject}
+        @type calendarObject: L{txdav.caldav.icalendarstore.ICalendarObject}
         """
         super(CalendarObjectResource, self).__init__(*args, **kw)
         self._initializeWithObject(calendarObject)
@@ -1221,10 +1221,10 @@
         Initialize with a addressbook.
 
         @param addressbook: the wrapped addressbook.
-        @type addressbook: L{txcarddav.iaddressbookstore.IAddressBook}
+        @type addressbook: L{txdav.carddav.iaddressbookstore.IAddressBook}
 
         @param home: the home through which the given addressbook was accessed.
-        @type home: L{txcarddav.iaddressbookstore.IAddressBookHome}
+        @type home: L{txdav.carddav.iaddressbookstore.IAddressBookHome}
         """
         self._newStoreAddressBook = addressbook
         self._newStoreParentHome = home
@@ -1307,12 +1307,12 @@
 
 class AddressBookCollectionResource(_AddressBookChildHelper, CalDAVResource):
     """
-    Wrapper around a L{txcarddav.iaddressbook.IAddressBook}.
+    Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
     """
 
     def __init__(self, addressbook, home, *args, **kw):
         """
-        Create a AddressBookCollectionResource from a L{txcarddav.iaddressbook.IAddressBook}
+        Create a AddressBookCollectionResource from a L{txdav.carddav.iaddressbook.IAddressBook}
         and the arguments required for L{CalDAVResource}.
         """
         super(AddressBookCollectionResource, self).__init__(*args, **kw)
@@ -1486,7 +1486,7 @@
         @param home: The addressbook home which will be this resource's parent,
             when it exists.
 
-        @type home: L{txcarddav.iaddressbookstore.IAddressBookHome}
+        @type home: L{txdav.carddav.iaddressbookstore.IAddressBookHome}
         """
         super(ProtoAddressBookCollectionResource, self).__init__(*args, **kw)
         self._newStoreParentHome = home
@@ -1552,7 +1552,7 @@
 
 class GlobalAddressBookCollectionResource(GlobalAddressBookResource, AddressBookCollectionResource):
     """
-    Wrapper around a L{txcarddav.iaddressbook.IAddressBook}.
+    Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
     """
     pass
 
@@ -1575,7 +1575,7 @@
         Construct a L{AddressBookObjectResource} from an L{IAddressBookObject}.
 
         @param Object: The storage for the addressbook object.
-        @type Object: L{txcarddav.iaddressbookstore.IAddressBookObject}
+        @type Object: L{txdav.carddav.iaddressbookstore.IAddressBookObject}
         """
         super(AddressBookObjectResource, self).__init__(*args, **kw)
         self._initializeWithObject(Object)
@@ -1812,12 +1812,12 @@
 class StoreNotificationCollectionResource(_NotificationChildHelper,
                                       NotificationCollectionResource):
     """
-    Wrapper around a L{txcaldav.icalendar.ICalendar}.
+    Wrapper around a L{txdav.caldav.icalendar.ICalendar}.
     """
 
     def __init__(self, notifications, home, *args, **kw):
         """
-        Create a CalendarCollectionResource from a L{txcaldav.icalendar.ICalendar}
+        Create a CalendarCollectionResource from a L{txdav.caldav.icalendar.ICalendar}
         and the arguments required for L{CalDAVResource}.
         """
         super(StoreNotificationCollectionResource, self).__init__(*args, **kw)
@@ -1855,7 +1855,7 @@
         @param home: The calendar home which will be this resource's parent,
             when it exists.
 
-        @type home: L{txcaldav.icalendarstore.ICalendarHome}
+        @type home: L{txdav.caldav.icalendarstore.ICalendarHome}
         """
         self._newStoreParentHome = home
         super(StoreProtoNotificationCollectionResource, self).__init__(*args, **kw)
@@ -1925,7 +1925,7 @@
         Construct a L{CalendarObjectResource} from an L{ICalendarObject}.
 
         @param calendarObject: The storage for the calendar object.
-        @type calendarObject: L{txcaldav.icalendarstore.ICalendarObject}
+        @type calendarObject: L{txdav.caldav.icalendarstore.ICalendarObject}
         """
         super(StoreNotificationObjectFile, self).__init__(*args, **kw)
         self._initializeWithObject(notificationObject)

Modified: CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_calendarquery.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_calendarquery.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_calendarquery.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -34,8 +34,8 @@
 from twistedcaldav.config import config
 from twistedcaldav.test.util import HomeTestCase
 from twisted.internet.defer import inlineCallbacks, returnValue
-from txcaldav.calendarstore.test.test_postgres import buildStore
-from txcaldav.calendarstore.test.common import StubNotifierFactory
+from txdav.caldav.datastore.test.test_postgres import buildStore
+from txdav.caldav.datastore.test.common import StubNotifierFactory
 
 
 @inlineCallbacks

Modified: CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_sharing.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_sharing.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -25,8 +25,8 @@
 from twistedcaldav.config import config
 from twistedcaldav.test.util import HomeTestCase, norequest
 from twistedcaldav.resource import CalDAVResource
-from txcaldav.calendarstore.test.test_postgres import buildStore
-from txcaldav.calendarstore.test.common import StubNotifierFactory
+from txdav.caldav.datastore.test.test_postgres import buildStore
+from txdav.caldav.datastore.test.common import StubNotifierFactory
 
 
 class SharingTests(HomeTestCase):

Modified: CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_wrapping.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_wrapping.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -37,15 +37,15 @@
 
 from twistedcaldav.test.util import TestCase
 
-from txcaldav.calendarstore.test.test_file import event4_text
+from txdav.caldav.datastore.test.test_file import event4_text
 
-from txcarddav.addressbookstore.test.test_file import vcard4_text
+from txdav.carddav.datastore.test.test_file import vcard4_text
 
-from txcaldav.calendarstore.test.test_postgres import buildStore
-from txcaldav.calendarstore.test.common import StubNotifierFactory, \
+from txdav.caldav.datastore.test.test_postgres import buildStore
+from txdav.caldav.datastore.test.common import StubNotifierFactory, \
     assertProvides
-from txcaldav.icalendarstore import ICalendarHome
-from txcarddav.iaddressbookstore import IAddressBookHome
+from txdav.caldav.icalendarstore import ICalendarHome
+from txdav.carddav.iaddressbookstore import IAddressBookHome
 
 
 
@@ -73,7 +73,7 @@
 class WrappingTests(TestCase):
     """
     Tests for L{twistedcaldav.static.CalDAVResource} creating the appropriate type
-    of txcaldav.calendarstore.file underlying object when it can determine what
+    of txdav.caldav.datastore.file underlying object when it can determine what
     type it really represents.
     """
 

Modified: CalendarServer/branches/generic-sqlstore/txdav/base/datastore/util.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/datastore/util.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/datastore/util.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_file -*-
+# -*- test-case-name: txdav.caldav.datastore.test.test_file -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #

Modified: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/sql.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/sql.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_postgres -*-
+# -*- test-case-name: txdav.caldav.datastore.test.test_postgres -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #

Modified: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_sql.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_sql.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -15,8 +15,8 @@
 ##
 
 """
-Tests for txcaldav.calendarstore.postgres, mostly based on
-L{txcaldav.calendarstore.test.common}.
+Tests for txdav.caldav.datastore.postgres, mostly based on
+L{txdav.caldav.datastore.test.common}.
 """
 
 
@@ -28,7 +28,7 @@
 from twisted.python import log
 
 from txdav.common.datastore.sql import v1_schema
-from txcaldav.calendarstore.test.common import StubNotifierFactory
+from txdav.caldav.datastore.test.common import StubNotifierFactory
 
 from txdav.common.datastore.sql import CommonDataStore
 from txdav.base.datastore.subpostgres import PostgresService

Modified: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/xattr.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/xattr.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/xattr.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txdav.base.propertystore.test.test_xattr,txcaldav.calendarstore,txcarddav.addressbookstore -*-
+# -*- test-case-name: txdav.base.propertystore.test.test_xattr,txdav.caldav.datastore,txdav.carddav.datastore -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #

Deleted: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/file.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,542 +0,0 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_file -*-
-##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-File calendar store.
-"""
-
-__all__ = [
-    "CalendarStore",
-    "CalendarStoreTransaction",
-    "CalendarHome",
-    "Calendar",
-    "CalendarObject",
-]
-
-import hashlib
-
-from errno import ENOENT
-
-from twisted.internet.interfaces import ITransport
-from twisted.python.failure import Failure
-
-from txdav.propertystore.xattr import PropertyStore
-
-from twext.python.vcomponent import InvalidICalendarDataError
-from twext.python.vcomponent import VComponent
-from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType
-from twext.web2.dav.resource import TwistedGETContentMD5
-from twext.web2.http_headers import generateContentType
-
-from twistedcaldav import caldavxml, customxml
-from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
-from twistedcaldav.index import Index as OldIndex, IndexSchedule as OldInboxIndex
-from twistedcaldav.sharing import InvitesDatabase
-
-from txcaldav.icalendarstore import IAttachment
-from txcaldav.icalendarstore import ICalendar, ICalendarObject
-from txcaldav.icalendarstore import ICalendarHome
-
-from txcaldav.calendarstore.util import (
-    validateCalendarComponent, dropboxIDFromCalendarObject
-)
-
-from txdav.common.datastore.file import (
-    CommonDataStore, CommonStoreTransaction, CommonHome, CommonHomeChild,
-    CommonObjectResource
-, CommonStubResource)
-
-from txdav.common.icommondatastore import (NoSuchObjectResourceError,
-    InternalDataStoreError)
-from txdav.datastore.file import writeOperation, hidden, FileMetaDataMixin
-from txdav.propertystore.base import PropertyName
-
-from zope.interface import implements
-
-CalendarStore = CommonDataStore
-
-CalendarStoreTransaction = CommonStoreTransaction
-
-class CalendarHome(CommonHome):
-    implements(ICalendarHome)
-
-    def __init__(self, uid, path, calendarStore, transaction, notifier):
-        super(CalendarHome, self).__init__(uid, path, calendarStore, transaction, notifier)
-
-        self._childClass = Calendar
-
-
-    def calendarWithName(self, name):
-        if name in ('dropbox', 'notifications', 'freebusy'):
-            # "dropbox" is a file storage area, not a calendar.
-            return None
-        else:
-            return self.childWithName(name)
-
-
-    createCalendarWithName = CommonHome.createChildWithName
-    removeCalendarWithName = CommonHome.removeChildWithName
-
-    def calendars(self):
-        """
-        Return a generator of the child resource objects.
-        """
-        for child in self.children():
-            if child.name() in ('dropbox', 'notification'):
-                continue
-            yield child
-
-    def listCalendars(self):
-        """
-        Return a generator of the child resource names.
-        """
-        for name in self.listChildren():
-            if name in ('dropbox', 'notification'):
-                continue
-            yield name
-
-
-    def calendarObjectWithDropboxID(self, dropboxID):
-        """
-        Implement lookup with brute-force scanning.
-        """
-        for calendar in self.calendars():
-            for calendarObject in calendar.calendarObjects():
-                if dropboxID == calendarObject.dropboxID():
-                    return calendarObject
-
-
-    @property
-    def _calendarStore(self):
-        return self._dataStore
-
-
-    def created(self):
-        self.createCalendarWithName("calendar")
-        defaultCal = self.calendarWithName("calendar")
-        props = defaultCal.properties()
-        props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
-            Opaque())
-        self.createCalendarWithName("inbox")
-
-
-
-class Calendar(CommonHomeChild):
-    """
-    File-based implementation of L{ICalendar}.
-    """
-    implements(ICalendar)
-
-    def __init__(self, name, calendarHome, notifier, realName=None):
-        """
-        Initialize a calendar pointing at a path on disk.
-
-        @param name: the subdirectory of calendarHome where this calendar
-            resides.
-        @type name: C{str}
-
-        @param calendarHome: the home containing this calendar.
-        @type calendarHome: L{CalendarHome}
-
-        @param realName: If this calendar was just created, the name which it
-        will eventually have on disk.
-        @type realName: C{str}
-        """
-        super(Calendar, self).__init__(name, calendarHome, notifier,
-            realName=realName)
-
-        self._index = Index(self)
-        self._invites = Invites(self)
-        self._objectResourceClass = CalendarObject
-
-
-    @property
-    def _calendarHome(self):
-        return self._home
-
-
-    def resourceType(self):
-        return ResourceType.calendar #@UndefinedVariable
-
-
-    ownerCalendarHome = CommonHomeChild.ownerHome
-    calendarObjects = CommonHomeChild.objectResources
-    listCalendarObjects = CommonHomeChild.listObjectResources
-    calendarObjectWithName = CommonHomeChild.objectResourceWithName
-    calendarObjectWithUID = CommonHomeChild.objectResourceWithUID
-    createCalendarObjectWithName = CommonHomeChild.createObjectResourceWithName
-    removeCalendarObjectWithName = CommonHomeChild.removeObjectResourceWithName
-    removeCalendarObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
-    calendarObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
-
-
-    def calendarObjectsInTimeRange(self, start, end, timeZone):
-        raise NotImplementedError()
-
-
-    def initPropertyStore(self, props):
-        # Setup peruser special properties
-        props.setSpecialProperties(
-            (
-                PropertyName.fromElement(caldavxml.CalendarDescription),
-                PropertyName.fromElement(caldavxml.CalendarTimeZone),
-            ),
-            (
-                PropertyName.fromElement(customxml.GETCTag),
-                PropertyName.fromElement(caldavxml.SupportedCalendarComponentSet),
-                PropertyName.fromElement(caldavxml.ScheduleCalendarTransp),
-            ),
-        )
-
-
-
-class CalendarObject(CommonObjectResource):
-    """
-    @ivar _path: The path of the .ics file on disk
-
-    @type _path: L{FilePath}
-    """
-    implements(ICalendarObject)
-
-    def __init__(self, name, calendar):
-        super(CalendarObject, self).__init__(name, calendar)
-        self._attachments = {}
-
-
-    @property
-    def _calendar(self):
-        return self._parentCollection
-
-
-    def calendar(self):
-        return self._calendar
-
-
-    @writeOperation
-    def setComponent(self, component):
-        validateCalendarComponent(self, self._calendar, component)
-
-        self._calendar.retrieveOldIndex().addResource(
-            self.name(), component
-        )
-
-        self._component = component
-        # FIXME: needs to clear text cache
-
-        def do():
-            # Mark all properties as dirty, so they can be added back
-            # to the newly updated file.
-            self.properties().update(self.properties())
-
-            backup = None
-            if self._path.exists():
-                backup = hidden(self._path.temporarySibling())
-                self._path.moveTo(backup)
-            fh = self._path.open("w")
-            try:
-                # FIXME: concurrency problem; if this write is interrupted
-                # halfway through, the underlying file will be corrupt.
-                fh.write(str(component))
-            finally:
-                fh.close()
-
-            # Now re-write the original properties on the updated file
-            self.properties().flush()
-
-            def undo():
-                if backup:
-                    backup.moveTo(self._path)
-                else:
-                    self._path.remove()
-            return undo
-        self._transaction.addOperation(do, "set calendar component %r" % (self.name(),))
-        if self._calendar._notifier:
-            self._transaction.postCommit(self._calendar._notifier.notify)
-
-    def component(self):
-        if self._component is not None:
-            return self._component
-        text = self.text()
-
-        try:
-            component = VComponent.fromString(text)
-        except InvalidICalendarDataError, e:
-            raise InternalDataStoreError(
-                "File corruption detected (%s) in file: %s"
-                % (e, self._path.path)
-            )
-        return component
-
-
-    def text(self):
-        if self._component is not None:
-            return str(self._component)
-        try:
-            fh = self._path.open()
-        except IOError, e:
-            if e[0] == ENOENT:
-                raise NoSuchObjectResourceError(self)
-            else:
-                raise
-
-        try:
-            text = fh.read()
-        finally:
-            fh.close()
-
-        if not (
-            text.startswith("BEGIN:VCALENDAR\r\n") or
-            text.endswith("\r\nEND:VCALENDAR\r\n")
-        ):
-            raise InternalDataStoreError(
-                "File corruption detected (improper start) in file: %s"
-                % (self._path.path,)
-            )
-        return text
-
-    iCalendarText = text
-
-    def uid(self):
-        if not hasattr(self, "_uid"):
-            self._uid = self.component().resourceUID()
-        return self._uid
-
-    def componentType(self):
-        if not hasattr(self, "_componentType"):
-            self._componentType = self.component().mainType()
-        return self._componentType
-
-    def organizer(self):
-        return self.component().getOrganizer()
-
-
-    def createAttachmentWithName(self, name, contentType):
-        """
-        Implement L{ICalendarObject.removeAttachmentWithName}.
-        """
-        # Make a (FIXME: temp, remember rollbacks) file in dropbox-land
-        attachment = Attachment(self, name)
-        self._attachments[name] = attachment
-        return attachment.store(contentType)
-
-
-    def removeAttachmentWithName(self, name):
-        """
-        Implement L{ICalendarObject.removeAttachmentWithName}.
-        """
-        # FIXME: rollback, tests for rollback
-        self._dropboxPath().child(name).remove()
-        if name in self._attachments:
-            del self._attachments[name]
-
-
-    def attachmentWithName(self, name):
-        # Attachments can be local or remote, but right now we only care about
-        # local.  So we're going to base this on the listing of files in the
-        # dropbox and not on the calendar data.  However, we COULD examine the
-        # 'attach' properties.
-
-        if name in self._attachments:
-            return self._attachments[name]
-        # FIXME: cache consistently (put it in self._attachments)
-        if self._dropboxPath().child(name).exists():
-            return Attachment(self, name)
-        else:
-            # FIXME: test for non-existent attachment.
-            return None
-
-
-    def attendeesCanManageAttachments(self):
-        return self.component().hasPropertyInAnyComponent("X-APPLE-DROPBOX")
-
-
-    def dropboxID(self):
-        return dropboxIDFromCalendarObject(self)
-
-
-    def _dropboxPath(self):
-        dropboxPath = self._parentCollection._home._path.child(
-            "dropbox"
-        ).child(self.dropboxID())
-        if not dropboxPath.isdir():
-            dropboxPath.makedirs()
-        return dropboxPath
-
-
-    def attachments(self):
-        # See comment on attachmentWithName.
-        return [Attachment(self, name)
-                for name in self._dropboxPath().listdir()]
-
-    def initPropertyStore(self, props):
-        # Setup peruser special properties
-        props.setSpecialProperties(
-            (
-            ),
-            (
-                PropertyName.fromElement(customxml.TwistedCalendarAccessProperty),
-                PropertyName.fromElement(customxml.TwistedSchedulingObjectResource),
-                PropertyName.fromElement(caldavxml.ScheduleTag),
-                PropertyName.fromElement(customxml.TwistedScheduleMatchETags),
-                PropertyName.fromElement(customxml.TwistedCalendarHasPrivateCommentsProperty),
-                PropertyName.fromElement(caldavxml.Originator),
-                PropertyName.fromElement(caldavxml.Recipient),
-                PropertyName.fromElement(customxml.ScheduleChanges),
-            ),
-        )
-
-
-contentTypeKey = PropertyName.fromElement(GETContentType)
-md5key = PropertyName.fromElement(TwistedGETContentMD5)
-
-class AttachmentStorageTransport(object):
-
-    implements(ITransport)
-
-    def __init__(self, attachment, contentType):
-        """
-        
-        @param attachment:
-        @type attachment:
-        """
-        self._attachment = attachment
-        self._contentType = contentType
-        self._file = self._attachment._path.open("w")
-
-
-    def write(self, data):
-        # FIXME: multiple chunks
-        self._file.write(data)
-
-
-    def loseConnection(self):
-        # FIXME: do anything
-        self._file.close()
-
-        md5 = hashlib.md5(self._attachment._path.getContent()).hexdigest()
-        props = self._attachment.properties()
-        props[contentTypeKey] = GETContentType(generateContentType(self._contentType))
-        props[md5key] = TwistedGETContentMD5.fromString(md5)
-        props.flush()
-
-
-
-class Attachment(FileMetaDataMixin):
-    """
-    An L{Attachment} is a container for the data associated with a I{locally-
-    stored} calendar attachment.  That is to say, there will only be
-    L{Attachment} objects present on the I{organizer's} copy of and event, and
-    only for C{ATTACH} properties where this server is storing the resource.
-    (For example, the organizer may specify an C{ATTACH} property that
-    references an URI on a remote server.)
-    """
-
-    implements(IAttachment)
-
-    def __init__(self, calendarObject, name):
-        self._calendarObject = calendarObject
-        self._name = name
-
-
-    def name(self):
-        return self._name
-
-
-    def properties(self):
-        uid = self._calendarObject._parentCollection._home.uid()
-        return PropertyStore(uid, lambda :self._path)
-
-
-    def store(self, contentType):
-        return AttachmentStorageTransport(self, contentType)
-
-    def retrieve(self, protocol):
-        # FIXME: makeConnection
-        # FIXME: actually stream
-        # FIMXE: connectionLost
-        protocol.dataReceived(self._path.getContent())
-        # FIXME: ConnectionDone, not NotImplementedError
-        protocol.connectionLost(Failure(NotImplementedError()))
-
-    @property
-    def _path(self):
-        dropboxPath = self._calendarObject._dropboxPath()
-        return dropboxPath.child(self.name())
-
-
-
-class CalendarStubResource(CommonStubResource):
-    """
-    Just enough resource to keep the calendar's sql DB classes going.
-    """
-
-    def isCalendarCollection(self):
-        return True
-
-
-    def getChild(self, name):
-        calendarObject = self.resource.calendarObjectWithName(name)
-        if calendarObject:
-            class ChildResource(object):
-                def __init__(self, calendarObject):
-                    self.calendarObject = calendarObject
-
-                def iCalendar(self):
-                    return self.calendarObject.component()
-
-            return ChildResource(calendarObject)
-        else:
-            return None
-
-
-
-class Index(object):
-    #
-    # OK, here's where we get ugly.
-    # The index code needs to be rewritten also, but in the meantime...
-    #
-    def __init__(self, calendar):
-        self.calendar = calendar
-        stubResource = CalendarStubResource(calendar)
-        if self.calendar.name() == 'inbox':
-            indexClass = OldInboxIndex
-        else:
-            indexClass = OldIndex
-        self._oldIndex = indexClass(stubResource)
-
-
-    def calendarObjects(self):
-        calendar = self.calendar
-        for name, uid, componentType in self._oldIndex.bruteForceSearch():
-            calendarObject = calendar.calendarObjectWithName(name)
-
-            # Precache what we found in the index
-            calendarObject._uid = uid
-            calendarObject._componentType = componentType
-
-            yield calendarObject
-
-
-class Invites(object):
-    #
-    # OK, here's where we get ugly.
-    # The index code needs to be rewritten also, but in the meantime...
-    #
-    def __init__(self, calendar):
-        self.calendar = calendar
-        stubResource = CalendarStubResource(calendar)
-        self._oldInvites = InvitesDatabase(stubResource)

Copied: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py (from rev 6185, CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/file.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,542 @@
+# -*- test-case-name: txdav.caldav.datastore.test.test_file -*-
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+File calendar store.
+"""
+
+__all__ = [
+    "CalendarStore",
+    "CalendarStoreTransaction",
+    "CalendarHome",
+    "Calendar",
+    "CalendarObject",
+]
+
+import hashlib
+
+from errno import ENOENT
+
+from twisted.internet.interfaces import ITransport
+from twisted.python.failure import Failure
+
+from txdav.base.propertystore.xattr import PropertyStore
+
+from twext.python.vcomponent import InvalidICalendarDataError
+from twext.python.vcomponent import VComponent
+from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType
+from twext.web2.dav.resource import TwistedGETContentMD5
+from twext.web2.http_headers import generateContentType
+
+from twistedcaldav import caldavxml, customxml
+from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
+from twistedcaldav.index import Index as OldIndex, IndexSchedule as OldInboxIndex
+from twistedcaldav.sharing import InvitesDatabase
+
+from txdav.caldav.icalendarstore import IAttachment
+from txdav.caldav.icalendarstore import ICalendar, ICalendarObject
+from txdav.caldav.icalendarstore import ICalendarHome
+
+from txdav.caldav.datastore.util import (
+    validateCalendarComponent, dropboxIDFromCalendarObject
+)
+
+from txdav.common.datastore.file import (
+    CommonDataStore, CommonStoreTransaction, CommonHome, CommonHomeChild,
+    CommonObjectResource
+, CommonStubResource)
+
+from txdav.common.icommondatastore import (NoSuchObjectResourceError,
+    InternalDataStoreError)
+from txdav.base.datastore.file import writeOperation, hidden, FileMetaDataMixin
+from txdav.base.propertystore.base import PropertyName
+
+from zope.interface import implements
+
+CalendarStore = CommonDataStore
+
+CalendarStoreTransaction = CommonStoreTransaction
+
+class CalendarHome(CommonHome):
+    implements(ICalendarHome)
+
+    def __init__(self, uid, path, calendarStore, transaction, notifier):
+        super(CalendarHome, self).__init__(uid, path, calendarStore, transaction, notifier)
+
+        self._childClass = Calendar
+
+
+    def calendarWithName(self, name):
+        if name in ('dropbox', 'notifications', 'freebusy'):
+            # "dropbox" is a file storage area, not a calendar.
+            return None
+        else:
+            return self.childWithName(name)
+
+
+    createCalendarWithName = CommonHome.createChildWithName
+    removeCalendarWithName = CommonHome.removeChildWithName
+
+    def calendars(self):
+        """
+        Return a generator of the child resource objects.
+        """
+        for child in self.children():
+            if child.name() in ('dropbox', 'notification'):
+                continue
+            yield child
+
+    def listCalendars(self):
+        """
+        Return a generator of the child resource names.
+        """
+        for name in self.listChildren():
+            if name in ('dropbox', 'notification'):
+                continue
+            yield name
+
+
+    def calendarObjectWithDropboxID(self, dropboxID):
+        """
+        Implement lookup with brute-force scanning.
+        """
+        for calendar in self.calendars():
+            for calendarObject in calendar.calendarObjects():
+                if dropboxID == calendarObject.dropboxID():
+                    return calendarObject
+
+
+    @property
+    def _calendarStore(self):
+        return self._dataStore
+
+
+    def createdHome(self):
+        self.createCalendarWithName("calendar")
+        defaultCal = self.calendarWithName("calendar")
+        props = defaultCal.properties()
+        props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
+            Opaque())
+        self.createCalendarWithName("inbox")
+
+
+
+class Calendar(CommonHomeChild):
+    """
+    File-based implementation of L{ICalendar}.
+    """
+    implements(ICalendar)
+
+    def __init__(self, name, calendarHome, notifier, realName=None):
+        """
+        Initialize a calendar pointing at a path on disk.
+
+        @param name: the subdirectory of calendarHome where this calendar
+            resides.
+        @type name: C{str}
+
+        @param calendarHome: the home containing this calendar.
+        @type calendarHome: L{CalendarHome}
+
+        @param realName: If this calendar was just created, the name which it
+        will eventually have on disk.
+        @type realName: C{str}
+        """
+        super(Calendar, self).__init__(name, calendarHome, notifier,
+            realName=realName)
+
+        self._index = Index(self)
+        self._invites = Invites(self)
+        self._objectResourceClass = CalendarObject
+
+
+    @property
+    def _calendarHome(self):
+        return self._home
+
+
+    def resourceType(self):
+        return ResourceType.calendar #@UndefinedVariable
+
+
+    ownerCalendarHome = CommonHomeChild.ownerHome
+    calendarObjects = CommonHomeChild.objectResources
+    listCalendarObjects = CommonHomeChild.listObjectResources
+    calendarObjectWithName = CommonHomeChild.objectResourceWithName
+    calendarObjectWithUID = CommonHomeChild.objectResourceWithUID
+    createCalendarObjectWithName = CommonHomeChild.createObjectResourceWithName
+    removeCalendarObjectWithName = CommonHomeChild.removeObjectResourceWithName
+    removeCalendarObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
+    calendarObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
+
+
+    def calendarObjectsInTimeRange(self, start, end, timeZone):
+        raise NotImplementedError()
+
+
+    def initPropertyStore(self, props):
+        # Setup peruser special properties
+        props.setSpecialProperties(
+            (
+                PropertyName.fromElement(caldavxml.CalendarDescription),
+                PropertyName.fromElement(caldavxml.CalendarTimeZone),
+            ),
+            (
+                PropertyName.fromElement(customxml.GETCTag),
+                PropertyName.fromElement(caldavxml.SupportedCalendarComponentSet),
+                PropertyName.fromElement(caldavxml.ScheduleCalendarTransp),
+            ),
+        )
+
+
+
+class CalendarObject(CommonObjectResource):
+    """
+    @ivar _path: The path of the .ics file on disk
+
+    @type _path: L{FilePath}
+    """
+    implements(ICalendarObject)
+
+    def __init__(self, name, calendar):
+        super(CalendarObject, self).__init__(name, calendar)
+        self._attachments = {}
+
+
+    @property
+    def _calendar(self):
+        return self._parentCollection
+
+
+    def calendar(self):
+        return self._calendar
+
+
+    @writeOperation
+    def setComponent(self, component, inserting=False):
+        validateCalendarComponent(self, self._calendar, component, inserting)
+
+        self._calendar.retrieveOldIndex().addResource(
+            self.name(), component
+        )
+
+        self._component = component
+        # FIXME: needs to clear text cache
+
+        def do():
+            # Mark all properties as dirty, so they can be added back
+            # to the newly updated file.
+            self.properties().update(self.properties())
+
+            backup = None
+            if self._path.exists():
+                backup = hidden(self._path.temporarySibling())
+                self._path.moveTo(backup)
+            fh = self._path.open("w")
+            try:
+                # FIXME: concurrency problem; if this write is interrupted
+                # halfway through, the underlying file will be corrupt.
+                fh.write(str(component))
+            finally:
+                fh.close()
+
+            # Now re-write the original properties on the updated file
+            self.properties().flush()
+
+            def undo():
+                if backup:
+                    backup.moveTo(self._path)
+                else:
+                    self._path.remove()
+            return undo
+        self._transaction.addOperation(do, "set calendar component %r" % (self.name(),))
+        if self._calendar._notifier:
+            self._transaction.postCommit(self._calendar._notifier.notify)
+
+    def component(self):
+        if self._component is not None:
+            return self._component
+        text = self.text()
+
+        try:
+            component = VComponent.fromString(text)
+        except InvalidICalendarDataError, e:
+            raise InternalDataStoreError(
+                "File corruption detected (%s) in file: %s"
+                % (e, self._path.path)
+            )
+        return component
+
+
+    def text(self):
+        if self._component is not None:
+            return str(self._component)
+        try:
+            fh = self._path.open()
+        except IOError, e:
+            if e[0] == ENOENT:
+                raise NoSuchObjectResourceError(self)
+            else:
+                raise
+
+        try:
+            text = fh.read()
+        finally:
+            fh.close()
+
+        if not (
+            text.startswith("BEGIN:VCALENDAR\r\n") or
+            text.endswith("\r\nEND:VCALENDAR\r\n")
+        ):
+            raise InternalDataStoreError(
+                "File corruption detected (improper start) in file: %s"
+                % (self._path.path,)
+            )
+        return text
+
+    iCalendarText = text
+
+    def uid(self):
+        if not hasattr(self, "_uid"):
+            self._uid = self.component().resourceUID()
+        return self._uid
+
+    def componentType(self):
+        if not hasattr(self, "_componentType"):
+            self._componentType = self.component().mainType()
+        return self._componentType
+
+    def organizer(self):
+        return self.component().getOrganizer()
+
+
+    def createAttachmentWithName(self, name, contentType):
+        """
+        Implement L{ICalendarObject.removeAttachmentWithName}.
+        """
+        # Make a (FIXME: temp, remember rollbacks) file in dropbox-land
+        attachment = Attachment(self, name)
+        self._attachments[name] = attachment
+        return attachment.store(contentType)
+
+
+    def removeAttachmentWithName(self, name):
+        """
+        Implement L{ICalendarObject.removeAttachmentWithName}.
+        """
+        # FIXME: rollback, tests for rollback
+        self._dropboxPath().child(name).remove()
+        if name in self._attachments:
+            del self._attachments[name]
+
+
+    def attachmentWithName(self, name):
+        # Attachments can be local or remote, but right now we only care about
+        # local.  So we're going to base this on the listing of files in the
+        # dropbox and not on the calendar data.  However, we COULD examine the
+        # 'attach' properties.
+
+        if name in self._attachments:
+            return self._attachments[name]
+        # FIXME: cache consistently (put it in self._attachments)
+        if self._dropboxPath().child(name).exists():
+            return Attachment(self, name)
+        else:
+            # FIXME: test for non-existent attachment.
+            return None
+
+
+    def attendeesCanManageAttachments(self):
+        return self.component().hasPropertyInAnyComponent("X-APPLE-DROPBOX")
+
+
+    def dropboxID(self):
+        return dropboxIDFromCalendarObject(self)
+
+
+    def _dropboxPath(self):
+        dropboxPath = self._parentCollection._home._path.child(
+            "dropbox"
+        ).child(self.dropboxID())
+        if not dropboxPath.isdir():
+            dropboxPath.makedirs()
+        return dropboxPath
+
+
+    def attachments(self):
+        # See comment on attachmentWithName.
+        return [Attachment(self, name)
+                for name in self._dropboxPath().listdir()]
+
+    def initPropertyStore(self, props):
+        # Setup peruser special properties
+        props.setSpecialProperties(
+            (
+            ),
+            (
+                PropertyName.fromElement(customxml.TwistedCalendarAccessProperty),
+                PropertyName.fromElement(customxml.TwistedSchedulingObjectResource),
+                PropertyName.fromElement(caldavxml.ScheduleTag),
+                PropertyName.fromElement(customxml.TwistedScheduleMatchETags),
+                PropertyName.fromElement(customxml.TwistedCalendarHasPrivateCommentsProperty),
+                PropertyName.fromElement(caldavxml.Originator),
+                PropertyName.fromElement(caldavxml.Recipient),
+                PropertyName.fromElement(customxml.ScheduleChanges),
+            ),
+        )
+
+
+contentTypeKey = PropertyName.fromElement(GETContentType)
+md5key = PropertyName.fromElement(TwistedGETContentMD5)
+
+class AttachmentStorageTransport(object):
+
+    implements(ITransport)
+
+    def __init__(self, attachment, contentType):
+        """
+        
+        @param attachment:
+        @type attachment:
+        """
+        self._attachment = attachment
+        self._contentType = contentType
+        self._file = self._attachment._path.open("w")
+
+
+    def write(self, data):
+        # FIXME: multiple chunks
+        self._file.write(data)
+
+
+    def loseConnection(self):
+        # FIXME: do anything
+        self._file.close()
+
+        md5 = hashlib.md5(self._attachment._path.getContent()).hexdigest()
+        props = self._attachment.properties()
+        props[contentTypeKey] = GETContentType(generateContentType(self._contentType))
+        props[md5key] = TwistedGETContentMD5.fromString(md5)
+        props.flush()
+
+
+
+class Attachment(FileMetaDataMixin):
+    """
+    An L{Attachment} is a container for the data associated with a I{locally-
+    stored} calendar attachment.  That is to say, there will only be
+    L{Attachment} objects present on the I{organizer's} copy of and event, and
+    only for C{ATTACH} properties where this server is storing the resource.
+    (For example, the organizer may specify an C{ATTACH} property that
+    references an URI on a remote server.)
+    """
+
+    implements(IAttachment)
+
+    def __init__(self, calendarObject, name):
+        self._calendarObject = calendarObject
+        self._name = name
+
+
+    def name(self):
+        return self._name
+
+
+    def properties(self):
+        uid = self._calendarObject._parentCollection._home.uid()
+        return PropertyStore(uid, lambda :self._path)
+
+
+    def store(self, contentType):
+        return AttachmentStorageTransport(self, contentType)
+
+    def retrieve(self, protocol):
+        # FIXME: makeConnection
+        # FIXME: actually stream
+        # FIMXE: connectionLost
+        protocol.dataReceived(self._path.getContent())
+        # FIXME: ConnectionDone, not NotImplementedError
+        protocol.connectionLost(Failure(NotImplementedError()))
+
+    @property
+    def _path(self):
+        dropboxPath = self._calendarObject._dropboxPath()
+        return dropboxPath.child(self.name())
+
+
+
+class CalendarStubResource(CommonStubResource):
+    """
+    Just enough resource to keep the calendar's sql DB classes going.
+    """
+
+    def isCalendarCollection(self):
+        return True
+
+
+    def getChild(self, name):
+        calendarObject = self.resource.calendarObjectWithName(name)
+        if calendarObject:
+            class ChildResource(object):
+                def __init__(self, calendarObject):
+                    self.calendarObject = calendarObject
+
+                def iCalendar(self):
+                    return self.calendarObject.component()
+
+            return ChildResource(calendarObject)
+        else:
+            return None
+
+
+
+class Index(object):
+    #
+    # OK, here's where we get ugly.
+    # The index code needs to be rewritten also, but in the meantime...
+    #
+    def __init__(self, calendar):
+        self.calendar = calendar
+        stubResource = CalendarStubResource(calendar)
+        if self.calendar.name() == 'inbox':
+            indexClass = OldInboxIndex
+        else:
+            indexClass = OldIndex
+        self._oldIndex = indexClass(stubResource)
+
+
+    def calendarObjects(self):
+        calendar = self.calendar
+        for name, uid, componentType in self._oldIndex.bruteForceSearch():
+            calendarObject = calendar.calendarObjectWithName(name)
+
+            # Precache what we found in the index
+            calendarObject._uid = uid
+            calendarObject._componentType = componentType
+
+            yield calendarObject
+
+
+class Invites(object):
+    #
+    # OK, here's where we get ugly.
+    # The index code needs to be rewritten also, but in the meantime...
+    #
+    def __init__(self, calendar):
+        self.calendar = calendar
+        stubResource = CalendarStubResource(calendar)
+        self._oldInvites = InvitesDatabase(stubResource)

Deleted: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/postgres.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/postgres.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/postgres.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,3283 +0,0 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_postgres -*-
-##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-PostgreSQL data store.
-"""
-
-__all__ = [
-    "PostgresStore",
-    "PostgresCalendarHome",
-    "PostgresCalendar",
-    "PostgresCalendarObject",
-    "PostgresAddressBookHome",
-    "PostgresAddressBook",
-    "PostgresAddressBookObject",
-]
-
-import datetime
-import StringIO
-
-from twistedcaldav.sharing import SharedCollectionRecord #@UnusedImport
-
-from inspect import getargspec
-from zope.interface.declarations import implements
-
-from twisted.application.service import Service
-from twisted.internet.error import ConnectionLost
-from twisted.internet.interfaces import ITransport
-from twisted.python import hashlib
-from twisted.python.failure import Failure
-from twisted.internet.defer import succeed
-from twisted.python.modules import getModule
-
-from twext.web2.dav.element.rfc2518 import ResourceType
-
-from txdav.idav import IDataStore, AlreadyFinishedError
-from txdav.common.inotifications import (INotificationCollection,
-    INotificationObject)
-
-from txdav.common.icommondatastore import (
-    ObjectResourceNameAlreadyExistsError, HomeChildNameAlreadyExistsError,
-    NoSuchHomeChildError, NoSuchObjectResourceError)
-from txcaldav.calendarstore.util import (validateCalendarComponent,
-    validateAddressBookComponent, dropboxIDFromCalendarObject, CalendarSyncTokenHelper,
-    AddressbookSyncTokenHelper)
-from txdav.datastore.file import cached
-
-from txcaldav.icalendarstore import (ICalendarTransaction, ICalendarHome,
-    ICalendar, ICalendarObject, IAttachment)
-from txcarddav.iaddressbookstore import (IAddressBookTransaction,
-    IAddressBookHome, IAddressBook, IAddressBookObject)
-from txdav.propertystore.base import AbstractPropertyStore, PropertyName
-from txdav.propertystore.none import PropertyStore
-
-from twext.web2.http_headers import MimeType, generateContentType
-from twext.web2.dav.element.parser import WebDAVDocument
-
-from twext.python.log import Logger, LoggingMixIn
-from twext.python.vcomponent import VComponent
-
-from twistedcaldav import carddavxml
-from twistedcaldav.config import config
-from twistedcaldav.customxml import NotificationType
-from twistedcaldav.dateops import normalizeForIndex
-from twistedcaldav.index import IndexedSearchException, ReservationError,\
-    SyncTokenValidException
-from twistedcaldav.instance import InvalidOverriddenInstanceError
-from twistedcaldav.memcachepool import CachePoolUserMixIn
-from twistedcaldav.notifications import NotificationRecord
-from twistedcaldav.query import calendarqueryfilter, calendarquery, \
-    addressbookquery
-from twistedcaldav.query.sqlgenerator import sqlgenerator
-from twistedcaldav.sharing import Invite
-from twistedcaldav.vcard import Component as VCard
-
-from vobject.icalendar import utc
-
-v1_schema = getModule(__name__).filePath.sibling(
-    "postgres_schema_v1.sql").getContent()
-
-log = Logger()
-
-# FIXME: these constants are in the schema, and should probably be discovered
-# from there somehow.
-
-_BIND_STATUS_INVITED = 0
-_BIND_STATUS_ACCEPTED = 1
-_BIND_STATUS_DECLINED = 2
-_BIND_STATUS_INVALID = 3
-
-_ATTACHMENTS_MODE_WRITE = 1
-
-_BIND_MODE_OWN = 0
-_BIND_MODE_READ = 1
-_BIND_MODE_WRITE = 2
-
-
-#
-# Duration into the future through which recurrences are expanded in the index
-# by default.  This is a caching parameter which affects the size of the index;
-# it does not affect search results beyond this period, but it may affect
-# performance of such a search.
-#
-default_future_expansion_duration = datetime.timedelta(days=365 * 1)
-
-#
-# Maximum duration into the future through which recurrences are expanded in the
-# index.  This is a caching parameter which affects the size of the index; it
-# does not affect search results beyond this period, but it may affect
-# performance of such a search.
-#
-# When a search is performed on a time span that goes beyond that which is
-# expanded in the index, we have to open each resource which may have data in
-# that time period.  In order to avoid doing that multiple times, we want to
-# cache those results.  However, we don't necessarily want to cache all
-# occurrences into some obscenely far-in-the-future date, so we cap the caching
-# period.  Searches beyond this period will always be relatively expensive for
-# resources with occurrences beyond this period.
-#
-maximum_future_expansion_duration = datetime.timedelta(days=365 * 5)
-
-icalfbtype_to_indexfbtype = {
-    "UNKNOWN"         : 0,
-    "FREE"            : 1,
-    "BUSY"            : 2,
-    "BUSY-UNAVAILABLE": 3,
-    "BUSY-TENTATIVE"  : 4,
-}
-
-indexfbtype_to_icalfbtype = {
-    0: '?',
-    1: 'F',
-    2: 'B',
-    3: 'U',
-    4: 'T',
-}
-
-
-def _getarg(argname, argspec, args, kw):
-    """
-    Get an argument from some arguments.
-
-    @param argname: The name of the argument to retrieve.
-
-    @param argspec: The result of L{inspect.getargspec}.
-
-    @param args: positional arguments passed to the function specified by
-        argspec.
-
-    @param kw: keyword arguments passed to the function specified by
-        argspec.
-
-    @return: The value of the argument named by 'argname'.
-    """
-    argnames = argspec[0]
-    try:
-        argpos = argnames.index(argname)
-    except ValueError:
-        argpos = None
-    if argpos is not None:
-        if len(args) > argpos:
-            return args[argpos]
-    if argname in kw:
-        return kw[argname]
-    else:
-        raise TypeError("could not find key argument %r in %r/%r (%r)" %
-            (argname, args, kw, argpos)
-        )
-
-
-
-def memoized(keyArgument, memoAttribute):
-    """
-    Decorator which memoizes the result of a method on that method's instance.
-
-    @param keyArgument: The name of the 'key' argument.
-
-    @type keyArgument: C{str}
-
-    @param memoAttribute: The name of the attribute on the instance which
-        should be used for memoizing the result of this method; the attribute
-        itself must be a dictionary.
-
-    @type memoAttribute: C{str}
-    """
-    def decorate(thunk):
-        spec = getargspec(thunk)
-        def outer(*a, **kw):
-            self = a[0]
-            memo = getattr(self, memoAttribute)
-            key = _getarg(keyArgument, spec, a, kw)
-            if key in memo:
-                return memo[key]
-            result = thunk(*a, **kw)
-            if result is not None:
-                memo[key] = result
-            return result
-        return outer
-    return decorate
-
-
-
-class PropertyStore(AbstractPropertyStore):
-
-    def __init__(self, defaultuser, txn, resourceID):
-        super(PropertyStore, self).__init__(defaultuser)
-        self._txn = txn
-        self._resourceID = resourceID
-
-
-    def _getitem_uid(self, key, uid):
-        rows = self._txn.execSQL(
-            "select VALUE from RESOURCE_PROPERTY where "
-            "RESOURCE_ID = %s and NAME = %s and VIEWER_UID = %s",
-            [self._resourceID, key.toString(), uid])
-        if not rows:
-            raise KeyError(key)
-        return WebDAVDocument.fromString(rows[0][0]).root_element
-
-
-    def _setitem_uid(self, key, value, uid):
-        self._delitem_uid(key, uid)
-        self._txn.execSQL(
-            "insert into RESOURCE_PROPERTY "
-            "(RESOURCE_ID, NAME, VALUE, VIEWER_UID) values (%s, %s, %s, %s)",
-            [self._resourceID, key.toString(), value.toxml(), uid])
-
-
-    def _delitem_uid(self, key, uid):
-        self._txn.execSQL(
-            "delete from RESOURCE_PROPERTY where VIEWER_UID = %s"
-            "and RESOURCE_ID = %s AND NAME = %s",
-            [uid, self._resourceID, key.toString()])
-
-
-    def _keys_uid(self, uid):
-        rows = self._txn.execSQL(
-            "select NAME from RESOURCE_PROPERTY where "
-            "VIEWER_UID = %s and RESOURCE_ID = %s",
-            [uid, self._resourceID]
-        )
-        for row in rows:
-            yield PropertyName.fromString(row[0])
-
-
-
-class PostgresCalendarObject(object):
-    implements(ICalendarObject)
-
-    def __init__(self, calendar, name, resid):
-        self._calendar = calendar
-        self._name = name
-        self._resourceID = resid
-        self._calendarText = None
-
-
-    @property
-    def _txn(self):
-        return self._calendar._txn
-
-
-    def uid(self):
-        return self.component().resourceUID()
-
-
-    def organizer(self):
-        return self.component().getOrganizer()
-
-
-    def dropboxID(self):
-        return dropboxIDFromCalendarObject(self)
-
-
-    def name(self):
-        return self._name
-
-
-    def calendar(self):
-        return self._calendar
-
-
-    def iCalendarText(self):
-        if self._calendarText is None:
-            text = self._txn.execSQL(
-                "select ICALENDAR_TEXT from CALENDAR_OBJECT where "
-                "RESOURCE_ID = %s", [self._resourceID]
-            )[0][0]
-            self._calendarText = text
-            return text
-        else:
-            return self._calendarText
-
-
-    def component(self):
-        return VComponent.fromString(self.iCalendarText())
-
-
-    def componentType(self):
-        return self.component().mainType()
-
-
-    @cached
-    def properties(self):
-        return PropertyStore(
-            self.uid(),
-            self._txn,
-            self._resourceID
-        )
-
-
-    def setComponent(self, component):
-        validateCalendarComponent(self, self._calendar, component)
-
-        self.updateDatabase(component)
-        self._calendar._updateRevision(self._name)
-
-        if self._calendar._notifier:
-            self._calendar._home._txn.postCommit(self._calendar._notifier.notify)
-
-    def updateDatabase(self, component, expand_until=None, reCreate=False, inserting=False):
-        """
-        Update the database tables for the new data being written.
-
-        @param component: calendar data to store
-        @type component: L{Component}
-        """
-
-        # Decide how far to expand based on the component
-        master = component.masterComponent()
-        if master is None or not component.isRecurring() and not component.isRecurringUnbounded():
-            # When there is no master we have a set of overridden components - index them all.
-            # When there is one instance - index it.
-            # When bounded - index all.
-            expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
-        else:
-            if expand_until:
-                expand = expand_until
-            else:
-                expand = datetime.date.today() + default_future_expansion_duration
-
-            if expand > (datetime.date.today() + maximum_future_expansion_duration):
-                raise IndexedSearchException
-
-        try:
-            instances = component.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
-        except InvalidOverriddenInstanceError, e:
-            log.err("Invalid instance %s when indexing %s in %s" % (e.rid, self._name, self.resource,))
-            raise
-
-        componentText = str(component)
-        self._calendarText = componentText
-        organizer = component.getOrganizer()
-        if not organizer:
-            organizer = ""
-
-        # CALENDAR_OBJECT table update
-        if inserting:
-            self._resourceID = self._txn.execSQL(
-                """
-                insert into CALENDAR_OBJECT
-                (CALENDAR_RESOURCE_ID, RESOURCE_NAME, ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX)
-                 values
-                (%s, %s, %s, %s, %s, %s, %s, %s)
-                 returning RESOURCE_ID
-                """,
-                # FIXME: correct ATTACHMENTS_MODE based on X-APPLE-
-                # DROPBOX
-                [
-                    self._calendar._resourceID,
-                    self._name,
-                    componentText,
-                    component.resourceUID(),
-                    component.resourceType(),
-                    _ATTACHMENTS_MODE_WRITE,
-                    organizer,
-                    normalizeForIndex(instances.limit) if instances.limit else None,
-                ]
-            )[0][0]
-        else:
-            self._txn.execSQL(
-                """
-                update CALENDAR_OBJECT set
-                (ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, MODIFIED)
-                 =
-                (%s, %s, %s, %s, %s, %s, timezone('UTC', CURRENT_TIMESTAMP))
-                 where RESOURCE_ID = %s
-                """,
-                # should really be filling out more fields: ORGANIZER,
-                # ORGANIZER_OBJECT, a correct ATTACHMENTS_MODE based on X-APPLE-
-                # DROPBOX
-                [
-                    componentText,
-                    component.resourceUID(),
-                    component.resourceType(),
-                    _ATTACHMENTS_MODE_WRITE,
-                    organizer,
-                    normalizeForIndex(instances.limit) if instances.limit else None,
-                    self._resourceID
-                ]
-            )
-
-            # Need to wipe the existing time-range for this and rebuild
-            self._txn.execSQL(
-                """
-                delete from TIME_RANGE where CALENDAR_OBJECT_RESOURCE_ID = %s
-                """,
-                [
-                    self._resourceID,
-                ],
-            )
-
-
-        # CALENDAR_OBJECT table update
-        for key in instances:
-            instance = instances[key]
-            start = instance.start.replace(tzinfo=utc)
-            end = instance.end.replace(tzinfo=utc)
-            float = instance.start.tzinfo is None
-            transp = instance.component.propertyValue("TRANSP") == "TRANSPARENT"
-            instanceid = self._txn.execSQL(
-                """
-                insert into TIME_RANGE
-                (CALENDAR_RESOURCE_ID, CALENDAR_OBJECT_RESOURCE_ID, FLOATING, START_DATE, END_DATE, FBTYPE, TRANSPARENT)
-                 values
-                (%s, %s, %s, %s, %s, %s, %s)
-                 returning
-                INSTANCE_ID
-                """,
-                [
-                    self._calendar._resourceID,
-                    self._resourceID,
-                    float,
-                    start,
-                    end,
-                    icalfbtype_to_indexfbtype.get(instance.component.getFBType(), icalfbtype_to_indexfbtype["FREE"]),
-                    transp,
-                ],
-            )[0][0]
-            peruserdata = component.perUserTransparency(instance.rid)
-            for useruid, transp in peruserdata:
-                self._txn.execSQL(
-                    """
-                    insert into TRANSPARENCY
-                    (TIME_RANGE_INSTANCE_ID, USER_ID, TRANSPARENT)
-                     values
-                    (%s, %s, %s)
-                    """,
-                    [
-                        instanceid,
-                        useruid,
-                        transp,
-                    ],
-                )
-
-        # Special - for unbounded recurrence we insert a value for "infinity"
-        # that will allow an open-ended time-range to always match it.
-        if component.isRecurringUnbounded():
-            start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
-            end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
-            float = False
-            instanceid = self._txn.execSQL(
-                """
-                insert into TIME_RANGE
-                (CALENDAR_RESOURCE_ID, CALENDAR_OBJECT_RESOURCE_ID, FLOATING, START_DATE, END_DATE, FBTYPE, TRANSPARENT)
-                 values
-                (%s, %s, %s, %s, %s, %s, %s)
-                 returning
-                INSTANCE_ID
-                """,
-                [
-                    self._calendar._resourceID,
-                    self._resourceID,
-                    float,
-                    start,
-                    end,
-                    icalfbtype_to_indexfbtype["UNKNOWN"],
-                    True,
-                ],
-            )[0][0]
-            peruserdata = component.perUserTransparency(None)
-            for useruid, transp in peruserdata:
-                self._txn.execSQL(
-                    """
-                    insert into TRANSPARENCY
-                    (TIME_RANGE_INSTANCE_ID, USER_ID, TRANSPARENT)
-                     values
-                    (%s, %s, %s)
-                    """,
-                    [
-                        instanceid,
-                        useruid,
-                        transp,
-                    ],
-                )
-
-    def _attachmentPath(self, name):
-        attachmentRoot = self._calendar._home._txn._store.attachmentsPath
-        try:
-            attachmentRoot.createDirectory()
-        except:
-            pass
-        return attachmentRoot.child(
-            "%s-%s-%s-%s.attachment" % (
-                self._calendar._home.uid(), self._calendar.name(),
-                self.name(), name
-            )
-        )
-
-
-    def createAttachmentWithName(self, name, contentType):
-        path = self._attachmentPath(name)
-        attachment = PostgresAttachment(self, path)
-        self._txn.execSQL("""
-            insert into ATTACHMENT (CALENDAR_OBJECT_RESOURCE_ID, CONTENT_TYPE,
-            SIZE, MD5, PATH)
-            values (%s, %s, %s, %s, %s)
-            """,
-            [
-                self._resourceID, generateContentType(contentType), 0, "",
-                attachment._pathValue()
-            ]
-        )
-        return attachment.store(contentType)
-
-
-    def attachments(self):
-        rows = self._txn.execSQL("""
-        select PATH from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s 
-        """, [self._resourceID])
-        for row in rows:
-            demangledName = _pathToName(row[0])
-            yield self.attachmentWithName(demangledName)
-
-
-    def attachmentWithName(self, name):
-        attachment = PostgresAttachment(self, self._attachmentPath(name))
-        if attachment._populate():
-            return attachment
-        else:
-            return None
-
-
-    def removeAttachmentWithName(self, name):
-        attachment = PostgresAttachment(self, self._attachmentPath(name))
-        self._calendar._home._txn.postCommit(attachment._path.remove)
-        self._txn.execSQL("""
-        delete from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s AND
-        PATH = %s
-        """, [self._resourceID, attachment._pathValue()])
-
-
-    # IDataStoreResource
-    def contentType(self):
-        """
-        The content type of Calendar objects is text/calendar.
-        """
-        return MimeType.fromString("text/calendar; charset=utf-8")
-
-
-    def md5(self):
-        return None
-
-
-    def size(self):
-        size = self._txn.execSQL(
-            "select character_length(ICALENDAR_TEXT) from CALENDAR_OBJECT where "
-            "RESOURCE_ID = %s", [self._resourceID]
-        )[0][0]
-        return size
-
-
-    def created(self):
-        created = self._txn.execSQL(
-            "select extract(EPOCH from CREATED) from CALENDAR_OBJECT where "
-            "RESOURCE_ID = %s", [self._resourceID]
-        )[0][0]
-        return int(created)
-
-    def modified(self):
-        modified = self._txn.execSQL(
-            "select extract(EPOCH from MODIFIED) from CALENDAR_OBJECT where "
-            "RESOURCE_ID = %s", [self._resourceID]
-        )[0][0]
-        return int(modified)
-
-
-    def attendeesCanManageAttachments(self):
-        return self.component().hasPropertyInAnyComponent("X-APPLE-DROPBOX")
-
-
-
-def _pathToName(path):
-    return path.rsplit(".", 1)[0].split("-", 3)[-1]
-
-
-
-class PostgresAttachment(object):
-
-    implements(IAttachment)
-
-    def __init__(self, calendarObject, path):
-        self._calendarObject = calendarObject
-        self._path = path
-
-
-    @property
-    def _txn(self):
-        return self._calendarObject._txn
-
-
-    def _populate(self):
-        """
-        Execute necessary SQL queries to retrieve attributes.
-
-        @return: C{True} if this attachment exists, C{False} otherwise.
-        """
-        rows = self._txn.execSQL(
-            """
-            select CONTENT_TYPE, SIZE, MD5, extract(EPOCH from CREATED), extract(EPOCH from MODIFIED) from ATTACHMENT where PATH = %s
-            """, [self._pathValue()])
-        if not rows:
-            return False
-        self._contentType = MimeType.fromString(rows[0][0])
-        self._size = rows[0][1]
-        self._md5 = rows[0][2]
-        self._created = int(rows[0][3])
-        self._modified = int(rows[0][4])
-        return True
-
-
-    def store(self, contentType):
-        return PostgresAttachmentStorageTransport(self, contentType)
-
-
-    def retrieve(self, protocol):
-        protocol.dataReceived(self._path.getContent())
-        protocol.connectionLost(Failure(ConnectionLost()))
-
-
-    def properties(self):
-        pass # stub
-
-
-    # IDataStoreResource
-    def contentType(self):
-        return self._contentType
-
-
-    def md5(self):
-        return self._md5
-
-
-    def size(self):
-        return self._size
-
-
-    def created(self):
-        return self._created
-
-    def modified(self):
-        return self._modified
-
-
-    def name(self):
-        return _pathToName(self._pathValue())
-
-
-    def _pathValue(self):
-        """
-        Compute the value which should go into the 'path' column for this
-        attachment.
-        """
-        root = self._calendarObject._calendar._home._txn._store.attachmentsPath
-        return '/'.join(self._path.segmentsFrom(root))
-
-
-
-class PostgresAttachmentStorageTransport(object):
-
-    implements(ITransport)
-
-    def __init__(self, attachment, contentType):
-        self.attachment = attachment
-        self.contentType = contentType
-        self.buf = ''
-        self.hash = hashlib.md5()
-
-
-    @property
-    def _txn(self):
-        return self.attachment._txn
-
-
-    def write(self, data):
-        self.buf += data
-        self.hash.update(data)
-
-
-    def loseConnection(self):
-        self.attachment._path.setContent(self.buf)
-        pathValue = self.attachment._pathValue()
-        contentTypeString = generateContentType(self.contentType)
-        self._txn.execSQL(
-            "update ATTACHMENT set CONTENT_TYPE = %s, SIZE = %s, MD5 = %s, MODIFIED = timezone('UTC', CURRENT_TIMESTAMP) "
-            "WHERE PATH = %s",
-            [contentTypeString, len(self.buf), self.hash.hexdigest(), pathValue]
-        )
-
-
-
-class PostgresLegacyInvitesEmulator(object):
-    """
-    Emulator for the implicit interface specified by
-    L{twistedcaldav.sharing.InvitesDatabase}.
-    """
-
-
-    def __init__(self, calendar):
-        self._calendar = calendar
-
-
-    @property
-    def _txn(self):
-        return self._calendar._txn
-
-
-    def create(self):
-        "No-op, because the index implicitly always exists in the database."
-
-
-    def remove(self):
-        "No-op, because the index implicitly always exists in the database."
-
-
-    def allRecords(self):
-        for row in self._txn.execSQL(
-                """
-                select
-                    INVITE.INVITE_UID, INVITE.NAME, INVITE.RECIPIENT_ADDRESS,
-                    CALENDAR_HOME.OWNER_UID, CALENDAR_BIND.BIND_MODE,
-                    CALENDAR_BIND.BIND_STATUS, CALENDAR_BIND.MESSAGE
-                from
-                    INVITE, CALENDAR_HOME, CALENDAR_BIND
-                where
-                    INVITE.RESOURCE_ID = %s and
-                    INVITE.HOME_RESOURCE_ID = 
-                        CALENDAR_HOME.RESOURCE_ID and
-                    CALENDAR_BIND.CALENDAR_RESOURCE_ID =
-                        INVITE.RESOURCE_ID and
-                    CALENDAR_BIND.CALENDAR_HOME_RESOURCE_ID =
-                        INVITE.HOME_RESOURCE_ID
-                order by
-                    INVITE.NAME asc
-                """, [self._calendar._resourceID]):
-            [inviteuid, common_name, userid, ownerUID,
-                bindMode, bindStatus, summary] = row
-            # FIXME: this is really the responsibility of the protocol layer.
-            state = {
-                _BIND_STATUS_INVITED: "NEEDS-ACTION",
-                _BIND_STATUS_ACCEPTED: "ACCEPTED",
-                _BIND_STATUS_DECLINED: "DECLINED",
-                _BIND_STATUS_INVALID: "INVALID",
-            }[bindStatus]
-            access = {
-                _BIND_MODE_READ: "read-only",
-                _BIND_MODE_WRITE: "read-write"
-            }[bindMode]
-            principalURL = "/principals/__uids__/%s/" % (ownerUID,)
-            yield Invite(
-                inviteuid, userid, principalURL, common_name,
-                access, state, summary
-            )
-
-
-    def recordForUserID(self, userid):
-        for record in self.allRecords():
-            if record.userid == userid:
-                return record
-
-
-    def recordForPrincipalURL(self, principalURL):
-        for record in self.allRecords():
-            if record.principalURL == principalURL:
-                return record
-
-
-    def recordForInviteUID(self, inviteUID):
-        for record in self.allRecords():
-            if record.inviteuid == inviteUID:
-                return record
-
-
-    def addOrUpdateRecord(self, record):
-        bindMode = {'read-only': _BIND_MODE_READ,
-                    'read-write': _BIND_MODE_WRITE}[record.access]
-        bindStatus = {
-            "NEEDS-ACTION": _BIND_STATUS_INVITED,
-            "ACCEPTED": _BIND_STATUS_ACCEPTED,
-            "DECLINED": _BIND_STATUS_DECLINED,
-            "INVALID": _BIND_STATUS_INVALID,
-        }[record.state]
-        # principalURL is derived from a directory record's principalURL() so
-        # it will always contain the UID.  The form is '/principals/__uids__/x'
-        # (and may contain a trailing slash).
-        principalUID = record.principalURL.split("/")[3]
-        shareeHome = self._txn.calendarHomeWithUID(principalUID, create=True)
-        rows = self._txn.execSQL(
-            "select RESOURCE_ID, HOME_RESOURCE_ID from INVITE where RECIPIENT_ADDRESS = %s",
-            [record.userid]
-        )
-        if rows:
-            [[resourceID, homeResourceID]] = rows
-            # Invite(inviteuid, userid, principalURL, common_name, access, state, summary)
-            self._txn.execSQL("""
-                update CALENDAR_BIND set BIND_MODE = %s,
-                BIND_STATUS = %s, MESSAGE = %s
-                where
-                    CALENDAR_RESOURCE_ID = %s and
-                    CALENDAR_HOME_RESOURCE_ID = %s
-            """, [bindMode, bindStatus, record.summary,
-                resourceID, homeResourceID])
-            self._txn.execSQL("""
-                update INVITE set NAME = %s, INVITE_UID = %s
-                where RECIPIENT_ADDRESS = %s
-                """,
-                [record.name, record.inviteuid, record.userid]
-            )
-        else:
-            self._txn.execSQL(
-                """
-                insert into INVITE (
-                    INVITE_UID, NAME,
-                    HOME_RESOURCE_ID, RESOURCE_ID,
-                    RECIPIENT_ADDRESS
-                )
-                values (%s, %s, %s, %s, %s)
-                """,
-                [
-                    record.inviteuid, record.name,
-                    shareeHome._resourceID, self._calendar._resourceID,
-                    record.userid
-                ])
-            self._txn.execSQL(
-                """
-                insert into CALENDAR_BIND
-                (
-                    CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID, 
-                    CALENDAR_RESOURCE_NAME, BIND_MODE, BIND_STATUS,
-                    SEEN_BY_OWNER, SEEN_BY_SHAREE, MESSAGE
-                )
-                values (%s, %s, %s, %s, %s, %s, %s, %s)
-                """,
-                [
-                    shareeHome._resourceID,
-                    self._calendar._resourceID,
-                    None, # this is NULL because it is not bound yet, let's be
-                          # explicit about that.
-                    bindMode,
-                    bindStatus,
-                    False,
-                    False,
-                    record.summary
-                ])
-
-
-    def removeRecordForUserID(self, userid):
-        rec = self.recordForUserID(userid)
-        self.removeRecordForInviteUID(rec.inviteuid)
-
-
-    def removeRecordForPrincipalURL(self, principalURL):
-        raise NotImplementedError("removeRecordForPrincipalURL")
-
-
-    def removeRecordForInviteUID(self, inviteUID):
-        rows = self._txn.execSQL("""
-                select HOME_RESOURCE_ID, RESOURCE_ID from INVITE where
-                INVITE_UID = %s
-            """, [inviteUID])
-        if rows:
-            [[homeID, resourceID]] = rows
-            self._txn.execSQL(
-                "delete from CALENDAR_BIND where "
-                "CALENDAR_HOME_RESOURCE_ID = %s and CALENDAR_RESOURCE_ID = %s",
-                [homeID, resourceID])
-            self._txn.execSQL("delete from INVITE where INVITE_UID = %s",
-                [inviteUID])
-
-
-
-class PostgresLegacySharesEmulator(object):
-
-    def __init__(self, home):
-        self._home = home
-
-
-    @property
-    def _txn(self):
-        return self._home._txn
-
-
-    def create(self):
-        pass
-
-
-    def remove(self):
-        pass
-
-
-    def allRecords(self):
-        # This should have been a smart join that got all these columns at
-        # once, but let's not bother to fix it, since the actual query we
-        # _want_ to do (just look for calendar binds in a particular homes) is
-        # much simpler anyway; we should just do that.
-        shareRows = self._txn.execSQL(
-            """
-            select CALENDAR_RESOURCE_ID, CALENDAR_RESOURCE_NAME, MESSAGE
-            from CALENDAR_BIND
-                where CALENDAR_HOME_RESOURCE_ID = %s and
-                BIND_MODE != %s and
-                CALENDAR_RESOURCE_NAME is not null
-            """, [self._home._resourceID, _BIND_MODE_OWN])
-        for resourceID, resourceName, summary in shareRows:
-            [[shareuid]] = self._txn.execSQL(
-                """
-                select INVITE_UID
-                from INVITE
-                where RESOURCE_ID = %s and HOME_RESOURCE_ID = %s
-                """, [resourceID, self._home._resourceID])
-            sharetype = 'I'
-            [[ownerHomeID, ownerResourceName]] = self._txn.execSQL(
-                """
-                select CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_NAME
-                from CALENDAR_BIND
-                where CALENDAR_RESOURCE_ID = %s and
-                    BIND_MODE = %s
-                """, [resourceID, _BIND_MODE_OWN]
-                )
-            [[ownerUID]] = self._txn.execSQL(
-                "select OWNER_UID from CALENDAR_HOME where RESOURCE_ID = %s",
-                [ownerHomeID])
-            hosturl = '/calendars/__uids__/%s/%s' % (
-                ownerUID, ownerResourceName
-            )
-            localname = resourceName
-            record = SharedCollectionRecord(
-                shareuid, sharetype, hosturl, localname, summary
-            )
-            yield record
-
-
-    def _search(self, **kw):
-        [[key, value]] = kw.items()
-        for record in self.allRecords():
-            if getattr(record, key) == value:
-                return record
-
-    def recordForLocalName(self, localname):
-        return self._search(localname=localname)
-
-    def recordForShareUID(self, shareUID):
-        return self._search(shareuid=shareUID)
-
-
-    def addOrUpdateRecord(self, record):
-#        print '*** SHARING***: Adding or updating this record:'
-#        import pprint
-#        pprint.pprint(record.__dict__)
-        # record.hosturl -> /calendars/__uids__/<uid>/<calendarname>
-        splithost = record.hosturl.split('/')
-        ownerUID = splithost[3]
-        ownerCalendarName = splithost[4]
-        ownerHome = self._txn.calendarHomeWithUID(ownerUID)
-        ownerCalendar = ownerHome.calendarWithName(ownerCalendarName)
-        calendarResourceID = ownerCalendar._resourceID
-
-        # There needs to be a bind already, one that corresponds to the
-        # invitation.  The invitation's UID is the same as the share UID.  I
-        # just need to update its 'localname', i.e.
-        # CALENDAR_BIND.CALENDAR_RESOURCE_NAME.
-
-        self._txn.execSQL(
-            """
-            update CALENDAR_BIND set CALENDAR_RESOURCE_NAME = %s
-            where CALENDAR_HOME_RESOURCE_ID = %s and CALENDAR_RESOURCE_ID = %s
-            """,
-            [record.localname, self._home._resourceID, calendarResourceID]
-        )
-
-
-    def removeRecordForLocalName(self, localname):
-        self._txn.execSQL(
-            "delete from CALENDAR_BIND where CALENDAR_RESOURCE_NAME = %s "
-            "and CALENDAR_HOME_RESOURCE_ID = %s",
-            [localname, self._home._resourceID]
-        )
-
-
-    def removeRecordForShareUID(self, shareUID):
-        pass
-#        c = self._home._cursor()
-#        c.execute(
-#            "delete from CALENDAR_BIND where CALENDAR_RESOURCE_NAME = %s "
-#            "and CALENDAR_HOME_RESOURCE_ID = %s",
-#            [self._home._resourceID]
-#        )
-
-
-
-class postgresqlgenerator(sqlgenerator):
-    """
-    Query generator for postgreSQL indexed searches.  (Currently unused: work
-    in progress.)
-    """
-
-    ISOP = " = "
-    CONTAINSOP = " LIKE "
-    NOTCONTAINSOP = " NOT LIKE "
-    FIELDS = {
-        "TYPE": "CALENDAR_OBJECT.ICALENDAR_TYPE",
-        "UID":  "CALENDAR_OBJECT.ICALENDAR_UID",
-    }
-
-    def __init__(self, expr, calendarid, userid):
-        self.RESOURCEDB = "CALENDAR_OBJECT"
-        self.TIMESPANDB = "TIME_RANGE"
-        self.TIMESPANTEST = "((TIME_RANGE.FLOATING = FALSE AND TIME_RANGE.START_DATE < %s AND TIME_RANGE.END_DATE > %s) OR (TIME_RANGE.FLOATING = TRUE AND TIME_RANGE.START_DATE < %s AND TIME_RANGE.END_DATE > %s))"
-        self.TIMESPANTEST_NOEND = "((TIME_RANGE.FLOATING = FALSE AND TIME_RANGE.END_DATE > %s) OR (TIME_RANGE.FLOATING = TRUE AND TIME_RANGE.END_DATE > %s))"
-        self.TIMESPANTEST_NOSTART = "((TIME_RANGE.FLOATING = FALSE AND TIME_RANGE.START_DATE < %s) OR (TIME_RANGE.FLOATING = TRUE AND TIME_RANGE.START_DATE < %s))"
-        self.TIMESPANTEST_TAIL_PIECE = " AND TIME_RANGE.CALENDAR_OBJECT_RESOURCE_ID = CALENDAR_OBJECT.RESOURCE_ID AND CALENDAR_OBJECT.CALENDAR_RESOURCE_ID = %s"
-        self.TIMESPANTEST_JOIN_ON_PIECE = "TIME_RANGE.INSTANCE_ID = TRANSPARENCY.TIME_RANGE_INSTANCE_ID AND TRANSPARENCY.USER_ID = %s"
-
-        super(postgresqlgenerator, self).__init__(expr, calendarid, userid)
-
-
-    def generate(self):
-        """
-        Generate the actual SQL 'where ...' expression from the passed in
-        expression tree.
-        
-        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the
-            partial SQL statement, and the C{list} is the list of argument
-            substitutions to use with the SQL API execute method.
-        """
-
-        # Init state
-        self.sout = StringIO.StringIO()
-        self.arguments = []
-        self.substitutions = []
-        self.usedtimespan = False
-
-        # Generate ' where ...' partial statement
-        self.sout.write(self.WHERE)
-        self.generateExpression(self.expression)
-
-        # Prefix with ' from ...' partial statement
-        select = self.FROM + self.RESOURCEDB
-        if self.usedtimespan:
-            self.frontArgument(self.userid)
-            select += ", %s LEFT OUTER JOIN %s ON (%s)" % (
-                self.TIMESPANDB,
-                self.TRANSPARENCYDB,
-                self.TIMESPANTEST_JOIN_ON_PIECE
-            )
-        select += self.sout.getvalue()
-
-        select = select % tuple(self.substitutions)
-
-        return select, self.arguments
-
-
-    def addArgument(self, arg):
-        self.arguments.append(arg)
-        self.substitutions.append("%s")
-        self.sout.write("%s")
-
-    def setArgument(self, arg):
-        self.arguments.append(arg)
-        self.substitutions.append("%s")
-
-    def frontArgument(self, arg):
-        self.arguments.insert(0, arg)
-        self.substitutions.insert(0, "%s")
-
-    def containsArgument(self, arg):
-        return "%%%s%%" % (arg,)
-
-
-class MemcachedUIDReserver(CachePoolUserMixIn, LoggingMixIn):
-    def __init__(self, index, cachePool=None):
-        self.index = index
-        self._cachePool = cachePool
-
-    def _key(self, uid):
-        return 'reservation:%s' % (
-            hashlib.md5('%s:%s' % (uid,
-                                   self.index.resource._resourceID)).hexdigest())
-
-    def reserveUID(self, uid):
-        uid = uid.encode('utf-8')
-        self.log_debug("Reserving UID %r @ %r" % (
-                uid,
-                self.index.resource))
-
-        def _handleFalse(result):
-            if result is False:
-                raise ReservationError(
-                    "UID %s already reserved for calendar collection %s."
-                    % (uid, self.index.resource._name)
-                    )
-
-        d = self.getCachePool().add(self._key(uid),
-                                    'reserved',
-                                    expireTime=config.UIDReservationTimeOut)
-        d.addCallback(_handleFalse)
-        return d
-
-
-    def unreserveUID(self, uid):
-        uid = uid.encode('utf-8')
-        self.log_debug("Unreserving UID %r @ %r" % (
-                uid,
-                self.index.resource))
-
-        def _handleFalse(result):
-            if result is False:
-                raise ReservationError(
-                    "UID %s is not reserved for calendar collection %s."
-                    % (uid, self.index.resource._resourceID)
-                    )
-
-        d = self.getCachePool().delete(self._key(uid))
-        d.addCallback(_handleFalse)
-        return d
-
-
-    def isReservedUID(self, uid):
-        uid = uid.encode('utf-8')
-        self.log_debug("Is reserved UID %r @ %r" % (
-                uid,
-                self.index.resource))
-
-        def _checkValue((flags, value)):
-            if value is None:
-                return False
-            else:
-                return True
-
-        d = self.getCachePool().get(self._key(uid))
-        d.addCallback(_checkValue)
-        return d
-
-class DummyUIDReserver(LoggingMixIn):
-
-    def __init__(self, index):
-        self.index = index
-        self.reservations = set()
-
-    def _key(self, uid):
-        return 'reservation:%s' % (
-            hashlib.md5('%s:%s' % (uid,
-                                   self.index.resource._resourceID)).hexdigest())
-
-    def reserveUID(self, uid):
-        uid = uid.encode('utf-8')
-        self.log_debug("Reserving UID %r @ %r" % (
-                uid,
-                self.index.resource))
-
-        key = self._key(uid)
-        if key in self.reservations:
-            raise ReservationError(
-                "UID %s already reserved for calendar collection %s."
-                % (uid, self.index.resource._name)
-                )
-        self.reservations.add(key)
-        return succeed(None)
-
-
-    def unreserveUID(self, uid):
-        uid = uid.encode('utf-8')
-        self.log_debug("Unreserving UID %r @ %r" % (
-                uid,
-                self.index.resource))
-
-        key = self._key(uid)
-        if key in self.reservations:
-            self.reservations.remove(key)
-        return succeed(None)
-
-
-    def isReservedUID(self, uid):
-        uid = uid.encode('utf-8')
-        self.log_debug("Is reserved UID %r @ %r" % (
-                uid,
-                self.index.resource))
-        key = self._key(uid)
-        return succeed(key in self.reservations)
-
-class PostgresLegacyIndexEmulator(LoggingMixIn):
-    """
-    Emulator for L{twistedcaldv.index.Index} and
-    L{twistedcaldv.index.IndexSchedule}.
-    """
-
-    def __init__(self, calendar):
-        self.resource = self.calendar = calendar
-        if (
-            hasattr(config, "Memcached") and
-            config.Memcached.Pools.Default.ClientEnabled
-        ):
-            self.reserver = MemcachedUIDReserver(self)
-        else:
-            # This is only used with unit tests
-            self.reserver = DummyUIDReserver(self)
-
-    @property
-    def _txn(self):
-        return self.calendar._txn
-
-
-    def reserveUID(self, uid):
-        if self.calendar._name == "inbox":
-            return succeed(None)
-        else:
-            return self.reserver.reserveUID(uid)
-
-
-    def unreserveUID(self, uid):
-        if self.calendar._name == "inbox":
-            return succeed(None)
-        else:
-            return self.reserver.unreserveUID(uid)
-
-
-    def isReservedUID(self, uid):
-        if self.calendar._name == "inbox":
-            return succeed(False)
-        else:
-            return self.reserver.isReservedUID(uid)
-
-
-    def isAllowedUID(self, uid, *names):
-        """
-        Checks to see whether to allow an operation which would add the
-        specified UID to the index.  Specifically, the operation may not
-        violate the constraint that UIDs must be unique.
-        @param uid: the UID to check
-        @param names: the names of resources being replaced or deleted by the
-            operation; UIDs associated with these resources are not checked.
-        @return: True if the UID is not in the index and is not reserved,
-            False otherwise.
-        """
-        if self.calendar._name == "inbox":
-            return True
-        else:
-            rname = self.resourceNameForUID(uid)
-            return (rname is None or rname in names)
-
-    def resourceUIDForName(self, name):
-        obj = self.calendar.calendarObjectWithName(name)
-        if obj is None:
-            return None
-        return obj.uid()
-
-
-    def resourceNameForUID(self, uid):
-        obj = self.calendar.calendarObjectWithUID(uid)
-        if obj is None:
-            return None
-        return obj.name()
-
-
-    def notExpandedBeyond(self, minDate):
-        """
-        Gives all resources which have not been expanded beyond a given date
-        in the database.  (Unused; see above L{postgresqlgenerator}.
-        """
-        return [row[0] for row in self._txn.execSQL(
-            "select RESOURCE_NAME from CALENDAR_OBJECT "
-            "where RECURRANCE_MAX < %s and CALENDAR_RESOURCE_ID = %s",
-            [normalizeForIndex(minDate), self.calendar._resourceID]
-        )]
-
-
-    def reExpandResource(self, name, expand_until):
-        """
-        Given a resource name, remove it from the database and re-add it
-        with a longer expansion.
-        """
-        obj = self.calendar.calendarObjectWithName(name)
-        obj.updateDatabase(obj.component(), expand_until=expand_until, reCreate=True)
-
-    def testAndUpdateIndex(self, minDate):
-        # Find out if the index is expanded far enough
-        names = self.notExpandedBeyond(minDate)
-
-        # Actually expand recurrence max
-        for name in names:
-            self.log_info("Search falls outside range of index for %s %s" % (name, minDate))
-            self.reExpandResource(name, minDate)
-
-    def whatchanged(self, revision):
-
-        results = [
-            (name.encode("utf-8"), deleted)
-            for name, deleted in
-            self._txn.execSQL(
-                """select RESOURCE_NAME, DELETED from CALENDAR_OBJECT_REVISIONS
-                   where REVISION > %s and CALENDAR_RESOURCE_ID = %s""",
-                [revision, self.calendar._resourceID],
-            )
-        ]
-        results.sort(key=lambda x:x[1])
-        
-        changed = []
-        deleted = []
-        for name, wasdeleted in results:
-            if name:
-                if wasdeleted:
-                    if revision:
-                        deleted.append(name)
-                else:
-                    changed.append(name)
-            else:
-                raise SyncTokenValidException
-        
-        return changed, deleted,
-
-    def indexedSearch(self, filter, useruid='', fbtype=False):
-        """
-        Finds resources matching the given qualifiers.
-        @param filter: the L{Filter} for the calendar-query to execute.
-        @return: an iterable of tuples for each resource matching the
-            given C{qualifiers}. The tuples are C{(name, uid, type)}, where
-            C{name} is the resource name, C{uid} is the resource UID, and
-            C{type} is the resource iCalendar component type.x
-        """
-
-        # Make sure we have a proper Filter element and get the partial SQL
-        # statement to use.
-        if isinstance(filter, calendarqueryfilter.Filter):
-            qualifiers = calendarquery.sqlcalendarquery(filter, self.calendar._resourceID, useruid, generator=postgresqlgenerator)
-            if qualifiers is not None:
-                # Determine how far we need to extend the current expansion of
-                # events. If we have an open-ended time-range we will expand one
-                # year past the start. That should catch bounded recurrences - unbounded
-                # will have been indexed with an "infinite" value always included.
-                maxDate, isStartDate = filter.getmaxtimerange()
-                if maxDate:
-                    maxDate = maxDate.date()
-                    if isStartDate:
-                        maxDate += datetime.timedelta(days=365)
-                    self.testAndUpdateIndex(maxDate)
-            else:
-                # We cannot handler this filter in an indexed search
-                raise IndexedSearchException()
-
-        else:
-            qualifiers = None
-
-        # Perform the search
-        if qualifiers is None:
-            rowiter = self._txn.execSQL(
-                "select RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = %s",
-                [self.calendar._resourceID, ],
-            )
-        else:
-            if fbtype:
-                # For a free-busy time-range query we return all instances
-                rowiter = self._txn.execSQL(
-                    """select DISTINCT
-                        CALENDAR_OBJECT.RESOURCE_NAME, CALENDAR_OBJECT.ICALENDAR_UID, CALENDAR_OBJECT.ICALENDAR_TYPE, CALENDAR_OBJECT.ORGANIZER,
-                        TIME_RANGE.FLOATING, TIME_RANGE.START_DATE, TIME_RANGE.END_DATE, TIME_RANGE.FBTYPE, TIME_RANGE.TRANSPARENT, TRANSPARENCY.TRANSPARENT""" +
-                    qualifiers[0],
-                    qualifiers[1]
-                )
-            else:
-                rowiter = self._txn.execSQL(
-                    "select DISTINCT CALENDAR_OBJECT.RESOURCE_NAME, CALENDAR_OBJECT.ICALENDAR_UID, CALENDAR_OBJECT.ICALENDAR_TYPE" +
-                    qualifiers[0],
-                    qualifiers[1]
-                )
-
-        # Check result for missing resources
-
-        for row in rowiter:
-            if fbtype:
-                row = list(row)
-                row[4] = 'Y' if row[4] else 'N'
-                row[7] = indexfbtype_to_icalfbtype[row[7]]
-                row[8] = 'T' if row[9] else 'F'
-                del row[9]
-            yield row
-
-
-    def bruteForceSearch(self):
-        return self._txn.execSQL(
-            "select RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from "
-            "CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = %s",
-            [self.calendar._resourceID]
-        )
-
-
-    def resourcesExist(self, names):
-        return list(set(names).intersection(
-            set(self.calendar.listCalendarObjects())))
-
-
-    def resourceExists(self, name):
-        return bool(
-            self._txn.execSQL(
-                "select RESOURCE_NAME from CALENDAR_OBJECT where "
-                "RESOURCE_NAME = %s and CALENDAR_RESOURCE_ID = %s",
-                [name, self.calendar._resourceID]
-            )
-        )
-
-
-
-class PostgresCalendar(CalendarSyncTokenHelper):
-
-    implements(ICalendar)
-
-    def __init__(self, home, name, resourceID, notifier):
-        self._home = home
-        self._name = name
-        self._resourceID = resourceID
-        self._objects = {}
-        self._notifier = notifier
-
-
-    @property
-    def _txn(self):
-        return self._home._txn
-
-
-    def setSharingUID(self, uid):
-        self.properties()._setPerUserUID(uid)
-
-
-    def retrieveOldInvites(self):
-        return PostgresLegacyInvitesEmulator(self)
-
-    def retrieveOldIndex(self):
-        return PostgresLegacyIndexEmulator(self)
-
-
-    def notifierID(self, label="default"):
-        if self._notifier:
-            return self._notifier.getID(label)
-        else:
-            return None
-
-
-    def name(self):
-        return self._name
-
-
-    def rename(self, name):
-        oldName = self._name
-        self._txn.execSQL(
-            "update CALENDAR_BIND set CALENDAR_RESOURCE_NAME = %s "
-            "where CALENDAR_RESOURCE_ID = %s AND "
-            "CALENDAR_HOME_RESOURCE_ID = %s",
-            [name, self._resourceID, self._home._resourceID]
-        )
-        self._name = name
-        # update memos
-        del self._home._calendars[oldName]
-        self._home._calendars[name] = self
-        self._updateSyncToken()
-
-
-    def ownerCalendarHome(self):
-        return self._home
-
-
-    def listCalendarObjects(self):
-        # FIXME: see listChildren
-        rows = self._txn.execSQL(
-            "select RESOURCE_NAME from "
-            "CALENDAR_OBJECT where "
-            "CALENDAR_RESOURCE_ID = %s",
-            [self._resourceID])
-        return [row[0] for row in rows]
-
-
-    def calendarObjects(self):
-        for name in self.listCalendarObjects():
-            yield self.calendarObjectWithName(name)
-
-
-    @memoized('name', '_objects')
-    def calendarObjectWithName(self, name):
-        rows = self._txn.execSQL(
-            "select RESOURCE_ID from CALENDAR_OBJECT where "
-            "RESOURCE_NAME = %s and CALENDAR_RESOURCE_ID = %s",
-            [name, self._resourceID]
-        )
-        if not rows:
-            return None
-        resid = rows[0][0]
-        return PostgresCalendarObject(self, name, resid)
-
-
-    @memoized('uid', '_objects')
-    def calendarObjectWithUID(self, uid):
-        rows = self._txn.execSQL(
-            "select RESOURCE_ID, RESOURCE_NAME from CALENDAR_OBJECT where "
-            "ICALENDAR_UID = %s and CALENDAR_RESOURCE_ID = %s",
-            [uid, self._resourceID]
-        )
-        if not rows:
-            return None
-        resid = rows[0][0]
-        name = rows[0][1]
-        return PostgresCalendarObject(self, name, resid)
-
-
-    def createCalendarObjectWithName(self, name, component):
-        rows = self._txn.execSQL(
-            "select RESOURCE_NAME from CALENDAR_OBJECT where "
-            " RESOURCE_NAME = %s AND CALENDAR_RESOURCE_ID = %s",
-            [name, self._resourceID]
-        )
-        if rows:
-            raise ObjectResourceNameAlreadyExistsError()
-
-        calendarObject = PostgresCalendarObject(self, name, None)
-        calendarObject.component = lambda : component
-
-        validateCalendarComponent(calendarObject, self, component)
-
-        calendarObject.updateDatabase(component, inserting=True)
-        self._insertRevision(name)
-
-        if self._notifier:
-            self._home._txn.postCommit(self._notifier.notify)
-
-
-    def removeCalendarObjectWithName(self, name):
-        self._txn.execSQL(
-            "delete from CALENDAR_OBJECT where RESOURCE_NAME = %s and "
-            "CALENDAR_RESOURCE_ID = %s",
-            [name, self._resourceID]
-        )
-        if self._txn._cursor.rowcount == 0:
-            raise NoSuchObjectResourceError()
-        self._objects.pop(name, None)
-        self._deleteRevision(name)
-
-        if self._notifier:
-            self._txn.postCommit(self._notifier.notify)
-
-
-    def removeCalendarObjectWithUID(self, uid):
-        rows = self._txn.execSQL(
-            "select RESOURCE_NAME from CALENDAR_OBJECT where "
-            "ICALENDAR_UID = %s AND CALENDAR_RESOURCE_ID = %s",
-            [uid, self._resourceID]
-        )
-        if not rows:
-            raise NoSuchObjectResourceError()
-        name = rows[0][0]
-        self._txn.execSQL(
-            "delete from CALENDAR_OBJECT where ICALENDAR_UID = %s and "
-            "CALENDAR_RESOURCE_ID = %s",
-            [uid, self._resourceID]
-        )
-        self._objects.pop(name, None)
-        self._objects.pop(uid, None)
-        self._deleteRevision(name)
-
-        if self._notifier:
-            self._home._txn.postCommit(self._notifier.notify)
-
-
-    def calendarObjectsInTimeRange(self, start, end, timeZone):
-        raise NotImplementedError()
-
-
-    def calendarObjectsSinceToken(self, token):
-        raise NotImplementedError()
-
-
-    @cached
-    def properties(self):
-        return PropertyStore(
-            self.ownerCalendarHome().uid(),
-            self._txn,
-            self._resourceID
-        )
-
-
-    # IDataStoreResource
-    def contentType(self):
-        """
-        The content type of Calendar objects is text/calendar.
-        """
-        return MimeType.fromString("text/calendar; charset=utf-8")
-
-
-    def md5(self):
-        return None
-
-
-    def size(self):
-        return 0
-
-
-    def created(self):
-        created = self._txn.execSQL(
-            "select extract(EPOCH from CREATED) from CALENDAR where "
-            "RESOURCE_ID = %s", [self._resourceID]
-        )[0][0]
-        return int(created)
-
-    def modified(self):
-        modified = self._txn.execSQL(
-            "select extract(EPOCH from MODIFIED) from CALENDAR where "
-            "RESOURCE_ID = %s", [self._resourceID]
-        )[0][0]
-        return int(modified)
-
-
-class PostgresCalendarHome(object):
-
-    implements(ICalendarHome)
-
-    def __init__(self, transaction, ownerUID, resourceID, notifier):
-        self._txn = transaction
-        self._ownerUID = ownerUID
-        self._resourceID = resourceID
-        self._calendars = {}
-        self._notifier = notifier
-
-
-    def retrieveOldShares(self):
-        return PostgresLegacySharesEmulator(self)
-
-
-    def uid(self):
-        """
-        Retrieve the unique identifier for this calendar home.
-
-        @return: a string.
-        """
-        return self._ownerUID
-
-
-    def name(self):
-        """
-        Implement L{IDataStoreResource.name} to return the uid.
-        """
-        return self.uid()
-
-
-    def transaction(self):
-        return self._txn
-
-
-    def listChildren(self):
-        """
-        Retrieve the names of the children in this calendar home.
-
-        @return: an iterable of C{str}s.
-        """
-        # FIXME: not specified on the interface or exercised by the tests, but
-        # required by clients of the implementation!
-        rows = self._txn.execSQL(
-            "select CALENDAR_RESOURCE_NAME from CALENDAR_BIND where "
-            "CALENDAR_HOME_RESOURCE_ID = %s "
-            "and BIND_MODE = %s ",
-            # Right now, we only show owned calendars.
-            [self._resourceID, _BIND_MODE_OWN]
-        )
-        names = [row[0] for row in rows]
-        return names
-
-
-    def calendars(self):
-        """
-        Retrieve calendars contained in this calendar home.
-
-        @return: an iterable of L{ICalendar}s.
-        """
-        names = self.listChildren()
-        for name in names:
-            yield self.calendarWithName(name)
-
-
-    @memoized('name', '_calendars')
-    def calendarWithName(self, name):
-        """
-        Retrieve the calendar with the given C{name} contained in this
-        calendar home.
-
-        @param name: a string.
-        @return: an L{ICalendar} or C{None} if no such calendar
-            exists.
-        """
-        data = self._txn.execSQL(
-            "select CALENDAR_RESOURCE_ID from CALENDAR_BIND where "
-            "CALENDAR_RESOURCE_NAME = %s and CALENDAR_HOME_RESOURCE_ID = %s "
-            "and BIND_MODE = %s",
-            [name, self._resourceID, _BIND_MODE_OWN]
-        )
-        if not data:
-            return None
-        resourceID = data[0][0]
-        if self._notifier:
-            childID = "%s/%s" % (self.uid(), name)
-            notifier = self._notifier.clone(label="collection", id=childID)
-        else:
-            notifier = None
-        return PostgresCalendar(self, name, resourceID, notifier)
-
-
-    def calendarObjectWithDropboxID(self, dropboxID):
-        """
-        Implement lookup with brute-force scanning.
-        """
-        for calendar in self.calendars():
-            for calendarObject in calendar.calendarObjects():
-                if dropboxID == calendarObject.dropboxID():
-                    return calendarObject
-
-
-    def createCalendarWithName(self, name):
-        rows = self._txn.execSQL(
-            "select CALENDAR_RESOURCE_NAME from CALENDAR_BIND where "
-            "CALENDAR_RESOURCE_NAME = %s AND "
-            "CALENDAR_HOME_RESOURCE_ID = %s",
-            [name, self._resourceID]
-        )
-        if rows:
-            raise HomeChildNameAlreadyExistsError()
-        rows = self._txn.execSQL("select nextval('RESOURCE_ID_SEQ')")
-        resourceID = rows[0][0]
-        self._txn.execSQL(
-            "insert into CALENDAR (RESOURCE_ID) values "
-            "(%s)",
-            [resourceID])
-
-        self._txn.execSQL("""
-            insert into CALENDAR_BIND (
-                CALENDAR_HOME_RESOURCE_ID,
-                CALENDAR_RESOURCE_ID, CALENDAR_RESOURCE_NAME, BIND_MODE,
-                SEEN_BY_OWNER, SEEN_BY_SHAREE, BIND_STATUS) values (
-            %s, %s, %s, %s, %s, %s, %s)
-            """,
-            [self._resourceID, resourceID, name, _BIND_MODE_OWN, True, True,
-             _BIND_STATUS_ACCEPTED]
-        )
-
-        calendarType = ResourceType.calendar #@UndefinedVariable
-        newCalendar = self.calendarWithName(name)
-        newCalendar.properties()[
-            PropertyName.fromElement(ResourceType)] = calendarType
-        newCalendar._updateSyncToken()
-
-        if self._notifier:
-            self._txn.postCommit(self._notifier.notify)
-
-
-    def removeCalendarWithName(self, name):
-        self._txn.execSQL(
-            "delete from CALENDAR_BIND where CALENDAR_RESOURCE_NAME = %s and "
-            "CALENDAR_HOME_RESOURCE_ID = %s",
-            [name, self._resourceID]
-        )
-        self._calendars.pop(name, None)
-        if self._txn._cursor.rowcount == 0:
-            raise NoSuchHomeChildError()
-        # FIXME: the schema should probably cascade the calendar delete when
-        # the last bind is deleted.
-        if self._notifier:
-            self._txn.postCommit(self._notifier.notify)
-
-
-    @cached
-    def properties(self):
-        return PropertyStore(
-            self.uid(),
-            self._txn,
-            self._resourceID
-        )
-
-
-    # IDataStoreResource
-    def contentType(self):
-        """
-        The content type of Calendar objects is text/calendar.
-        """
-        return None
-
-
-    def md5(self):
-        return None
-
-
-    def size(self):
-        return 0
-
-
-    def created(self):
-        return None
-
-
-    def modified(self):
-        return None
-
-
-    def notifierID(self, label="default"):
-        if self._notifier:
-            return self._notifier.getID(label)
-        else:
-            return None
-
-
-
-class PostgresNotificationObject(object):
-    implements(INotificationObject)
-
-    def __init__(self, home, resourceID):
-        self._home = home
-        self._resourceID = resourceID
-
-
-    def notificationCollection(self):
-        return self._home
-
-
-    def name(self):
-        return self.uid() + ".xml"
-
-
-    def contentType(self):
-        """
-        The content type of NotificationObjects is text/xml.
-        """
-        return MimeType.fromString("text/xml")
-
-
-    @property
-    def _txn(self):
-        return self._home._txn
-
-
-    def setData(self, uid, xmltype, xmldata):
-        self.properties()[PropertyName.fromElement(NotificationType)] = NotificationType(xmltype)
-        return self._txn.execSQL(
-            """
-            update NOTIFICATION set NOTIFICATION_UID = %s, XML_TYPE = %s,
-            XML_DATA = %s where RESOURCE_ID = %s
-            """,
-            [uid, xmltype, xmldata, self._resourceID]
-        )
-
-
-    def _fieldQuery(self, field):
-        [[data]] = self._txn.execSQL(
-            "select " + field + " from NOTIFICATION where "
-            "RESOURCE_ID = %s",
-            [self._resourceID])
-        return data
-
-
-    def xmldata(self):
-        return self._fieldQuery("XML_DATA")
-
-
-    def uid(self):
-        return self._fieldQuery("NOTIFICATION_UID")
-
-
-    @cached
-    def properties(self):
-        return PropertyStore(
-            self._home.uid(),
-            self._txn,
-            self._resourceID
-        )
-
-
-    def md5(self):
-        return hashlib.md5(self.xmldata()).hexdigest()
-
-
-    def modified(self):
-        modified = self._txn.execSQL(
-            "select extract(EPOCH from MODIFIED) from NOTIFICATION where "
-            "RESOURCE_ID = %s", [self._resourceID]
-        )[0][0]
-        return int(modified)
-
-
-    def created(self):
-        return None
-
-
-    def size(self):
-        return len(self.xmldata())
-
-
-
-class PostgresLegacyNotificationsEmulator(object):
-    def __init__(self, notificationsCollection):
-        self._collection = notificationsCollection
-
-
-    def _recordForObject(self, notificationObject):
-        return NotificationRecord(
-            notificationObject.uid(),
-            notificationObject.name(),
-            notificationObject._fieldQuery("XML_TYPE"))
-
-
-    def recordForName(self, name):
-        return self._recordForObject(
-            self._collection.notificationObjectWithName(name)
-        )
-
-
-    def recordForUID(self, uid):
-        return self._recordForObject(
-            self._collection.notificationObjectWithUID(uid)
-        )
-
-
-    def removeRecordForUID(self, uid):
-        self._collection.removeNotificationObjectWithUID(uid)
-
-
-    def removeRecordForName(self, name):
-        self._collection.removeNotificationObjectWithName(name)
-
-
-
-class PostgresNotificationCollection(object):
-
-    implements(INotificationCollection)
-
-    def __init__(self, txn, uid, resourceID):
-        self._txn = txn
-        self._uid = uid
-        self._resourceID = resourceID
-        self._notifications = {}
-
-
-    def retrieveOldIndex(self):
-        return PostgresLegacyNotificationsEmulator(self)
-
-
-    def name(self):
-        return 'notification'
-
-
-    def uid(self):
-        return self._uid
-
-
-    def notificationObjects(self):
-        for [uid] in self._txn.execSQL(
-                "select (NOTIFICATION_UID) "
-                "from NOTIFICATION "
-                "where NOTIFICATION_HOME_RESOURCE_ID = %s",
-                [self._resourceID]):
-            yield self.notificationObjectWithUID(uid)
-
-
-    def _nameToUID(self, name):
-        """
-        Based on the file-backed implementation, the 'name' is just uid +
-        ".xml".
-        """
-        return name.rsplit(".", 1)[0]
-
-
-    def notificationObjectWithName(self, name):
-        return self.notificationObjectWithUID(self._nameToUID(name))
-
-
-    @memoized('uid', '_notifications')
-    def notificationObjectWithUID(self, uid):
-        rows = self._txn.execSQL(
-            "select RESOURCE_ID from NOTIFICATION where NOTIFICATION_UID = %s"
-            " and NOTIFICATION_HOME_RESOURCE_ID = %s",
-            [uid, self._resourceID])
-        if rows:
-            [[resourceID]] = rows
-            return PostgresNotificationObject(self, resourceID)
-        else:
-            return None
-
-
-    def writeNotificationObject(self, uid, xmltype, xmldata):
-        xmltypeString = xmltype.toxml()
-        existing = self._txn.execSQL("select NOTIFICATION_UID from NOTIFICATION where NOTIFICATION_HOME_RESOURCE_ID = %s and NOTIFICATION_UID = %s",
-            [self._resourceID, uid])
-        if existing:
-            self._txn.execSQL(
-                "update NOTIFICATION set XML_TYPE = %s, XML_DATA = %s where NOTIFICATION_HOME_RESOURCE_ID = %s and NOTIFICATION_UID = %s",
-                [xmltypeString, xmldata, self._resourceID, uid])
-        else:
-            self._txn.execSQL(
-                "insert into NOTIFICATION (NOTIFICATION_HOME_RESOURCE_ID, NOTIFICATION_UID, XML_TYPE, XML_DATA) "
-                "values (%s, %s, %s, %s)", [self._resourceID, uid, xmltypeString, xmldata])
-        notificationObject = self.notificationObjectWithUID(uid)
-        notificationObject.properties()[PropertyName.fromElement(NotificationType)] = NotificationType(xmltype)
-
-
-    def removeNotificationObjectWithName(self, name):
-        self.removeNotificationObjectWithUID(self._nameToUID(name))
-
-
-    def removeNotificationObjectWithUID(self, uid):
-        self._txn.execSQL(
-            "delete from NOTIFICATION where NOTIFICATION_UID = %s and "
-            "NOTIFICATION_HOME_RESOURCE_ID = %s",
-            [uid, self._resourceID])
-
-
-    def syncToken(self):
-        return 'dummy-sync-token'
-
-
-    def notificationObjectsSinceToken(self, token):
-        changed = []
-        removed = []
-        token = self.syncToken()
-        return (changed, removed, token)
-
-
-    @cached
-    def properties(self):
-        return PropertyStore(
-            self._uid,
-            self._txn,
-            self._resourceID
-        )
-
-
-
-class PostgresTransaction(object):
-    """
-    Transaction implementation for postgres database.
-    """
-    implements(ICalendarTransaction, IAddressBookTransaction)
-
-    def __init__(self, store, connection, notifierFactory, label):
-        # print 'STARTING', label
-        self._store = store
-        self._connection = connection
-        self._cursor = connection.cursor()
-        self._completed = False
-        self._calendarHomes = {}
-        self._addressbookHomes = {}
-        self._notificationHomes = {}
-        self._postCommitOperations = []
-        self._notifierFactory = notifierFactory
-        self._label = label
-
-
-    def store(self):
-        return self._store
-
-
-    def __repr__(self):
-        return 'PG-TXN<%s>' % (self._label,)
-
-
-    def execSQL(self, sql, args=[]):
-        # print 'EXECUTE %s: %s' % (self._label, sql)
-        self._cursor.execute(sql, args)
-        if self._cursor.description:
-            return self._cursor.fetchall()
-        else:
-            return None
-
-
-    def __del__(self):
-        if not self._completed:
-            self._connection.rollback()
-            self._connection.close()
-
-
-    @memoized('uid', '_calendarHomes')
-    def calendarHomeWithUID(self, uid, create=False):
-        data = self.execSQL(
-            "select RESOURCE_ID from CALENDAR_HOME where OWNER_UID = %s",
-            [uid]
-        )
-        if not data:
-            if not create:
-                return None
-            self.execSQL(
-                "insert into CALENDAR_HOME (OWNER_UID) values (%s)",
-                [uid]
-            )
-            home = self.calendarHomeWithUID(uid)
-            home.createCalendarWithName("calendar")
-            return home
-        resid = data[0][0]
-
-        if self._notifierFactory:
-            notifier = self._notifierFactory.newNotifier(id=uid)
-        else:
-            notifier = None
-
-        return PostgresCalendarHome(self, uid, resid, notifier)
-
-
-    @memoized('uid', '_addressbookHomes')
-    def addressbookHomeWithUID(self, uid, create=False):
-        data = self.execSQL(
-            "select RESOURCE_ID from ADDRESSBOOK_HOME where OWNER_UID = %s",
-            [uid]
-        )
-        if not data:
-            if not create:
-                return None
-            self.execSQL(
-                "insert into ADDRESSBOOK_HOME (OWNER_UID) values (%s)",
-                [uid]
-            )
-            home = self.addressbookHomeWithUID(uid)
-            home.createAddressBookWithName("addressbook")
-            return home
-        resid = data[0][0]
-
-        if self._notifierFactory:
-            notifier = self._notifierFactory.newNotifier(id=uid)
-        else:
-            notifier = None
-
-        return PostgresAddressBookHome(self, uid, resid, notifier)
-
-
-    @memoized('uid', '_notificationHomes')
-    def notificationsWithUID(self, uid):
-        """
-        Implement notificationsWithUID.
-        """
-        rows = self.execSQL(
-            """
-            select RESOURCE_ID from NOTIFICATION_HOME where
-            OWNER_UID = %s
-            """, [uid])
-        if rows:
-            [[resourceID]] = rows
-        else:
-            [[resourceID]] = self.execSQL("select nextval('RESOURCE_ID_SEQ')")
-            resourceID = str(resourceID)
-            self.execSQL(
-                "insert into NOTIFICATION_HOME (RESOURCE_ID, OWNER_UID) "
-                "values (%s, %s)", [resourceID, uid])
-        return PostgresNotificationCollection(self, uid, resourceID)
-
-
-    def abort(self):
-        if not self._completed:
-            # print 'ABORTING', self._label
-            self._completed = True
-            self._connection.rollback()
-            self._connection.close()
-        else:
-            raise AlreadyFinishedError()
-
-
-    def commit(self):
-        if not self._completed:
-            # print 'COMPLETING', self._label
-            self._completed = True
-            self._connection.commit()
-            self._connection.close()
-            for operation in self._postCommitOperations:
-                operation()
-        else:
-            raise AlreadyFinishedError()
-
-
-    def postCommit(self, operation):
-        """
-        Run things after 'commit.'
-        """
-        self._postCommitOperations.append(operation)
-        # FIXME: implement.
-
-# CARDDAV
-
-class PostgresAddressBookObject(object):
-
-    implements(IAddressBookObject)
-
-    def __init__(self, addressbook, name, resid):
-        self._addressbook = addressbook
-        self._name = name
-        self._resourceID = resid
-        self._vCardText = None
-
-
-    @property
-    def _txn(self):
-        return self._addressbook._txn
-
-
-    def uid(self):
-        return self.component().resourceUID()
-
-
-    def name(self):
-        return self._name
-
-
-    def addressbook(self):
-        return self._addressbook
-
-
-    def vCardText(self):
-        if self._vCardText is None:
-            text = self._txn.execSQL(
-                "select VCARD_TEXT from ADDRESSBOOK_OBJECT where "
-                "RESOURCE_ID = %s", [self._resourceID]
-            )[0][0]
-            self._vCardText = text
-            return text
-        else:
-            return self._vCardText
-
-
-    def component(self):
-        return VCard.fromString(self.vCardText())
-
-
-    def componentType(self):
-        return self.component().mainType()
-
-
-    @cached
-    def properties(self):
-        return PropertyStore(
-            self.uid(),
-            self._txn,
-            self._resourceID
-        )
-
-
-    def setComponent(self, component):
-        validateAddressBookComponent(self, self._addressbook, component)
-
-        self.updateDatabase(component)
-        self._addressbook._updateRevision(self._name)
-
-        if self._addressbook._notifier:
-            self._addressbook._home._txn.postCommit(self._addressbook._notifier.notify)
-
-    def updateDatabase(self, component, expand_until=None, reCreate=False, inserting=False):
-        """
-        Update the database tables for the new data being written.
-
-        @param component: calendar data to store
-        @type component: L{Component}
-        """
-
-        componentText = str(component)
-        self._vCardText = componentText
-
-        # CALENDAR_OBJECT table update
-        if inserting:
-            self._resourceID = self._txn.execSQL(
-                """
-                insert into ADDRESSBOOK_OBJECT
-                (ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME, VCARD_TEXT, VCARD_UID)
-                 values
-                (%s, %s, %s, %s)
-                 returning RESOURCE_ID
-                """,
-                [
-                    self._addressbook._resourceID,
-                    self._name,
-                    componentText,
-                    component.resourceUID(),
-                ]
-            )[0][0]
-        else:
-            self._txn.execSQL(
-                """
-                update ADDRESSBOOK_OBJECT set
-                (VCARD_TEXT, VCARD_UID, MODIFIED)
-                 =
-                (%s, %s, timezone('UTC', CURRENT_TIMESTAMP))
-                 where RESOURCE_ID = %s
-                """,
-                [
-                    componentText,
-                    component.resourceUID(),
-                    self._resourceID
-                ]
-            )
-
-    # IDataStoreResource
-    def contentType(self):
-        """
-        The content type of Addressbook objects is text/x-vcard.
-        """
-        return MimeType.fromString("text/vcard; charset=utf-8")
-
-
-    def md5(self):
-        return None
-
-
-    def size(self):
-        size = self._txn.execSQL(
-            "select character_length(VCARD_TEXT) from ADDRESSBOOK_OBJECT where "
-            "RESOURCE_ID = %s", [self._resourceID]
-        )[0][0]
-        return size
-
-
-    def created(self):
-        created = self._txn.execSQL(
-            "select extract(EPOCH from CREATED) from ADDRESSBOOK_OBJECT where "
-            "RESOURCE_ID = %s", [self._resourceID]
-        )[0][0]
-        return int(created)
-
-
-    def modified(self):
-        modified = self._txn.execSQL(
-            "select extract(EPOCH from MODIFIED) from ADDRESSBOOK_OBJECT where "
-            "RESOURCE_ID = %s", [self._resourceID]
-        )[0][0]
-        return int(modified)
-
-
-
-class postgresqladbkgenerator(sqlgenerator):
-    """
-    Query generator for postgreSQL indexed searches.  (Currently unused: work
-    in progress.)
-    """
-
-    ISOP = " = "
-    CONTAINSOP = " LIKE "
-    NOTCONTAINSOP = " NOT LIKE "
-    FIELDS = {
-        "UID":  "ADDRESSBOOK_OBJECT.VCARD_UID",
-    }
-
-    def __init__(self, expr, addressbookid):
-        self.RESOURCEDB = "ADDRESSBOOK_OBJECT"
-
-        super(postgresqladbkgenerator, self).__init__(expr, addressbookid)
-
-
-    def generate(self):
-        """
-        Generate the actual SQL 'where ...' expression from the passed in
-        expression tree.
-        
-        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the
-            partial SQL statement, and the C{list} is the list of argument
-            substitutions to use with the SQL API execute method.
-        """
-
-        # Init state
-        self.sout = StringIO.StringIO()
-        self.arguments = []
-        self.substitutions = []
-
-        # Generate ' where ...' partial statement
-        self.sout.write(self.WHERE)
-        self.generateExpression(self.expression)
-
-        # Prefix with ' from ...' partial statement
-        select = self.FROM + self.RESOURCEDB
-        select += self.sout.getvalue()
-
-        select = select % tuple(self.substitutions)
-
-        return select, self.arguments
-
-
-    def addArgument(self, arg):
-        self.arguments.append(arg)
-        self.substitutions.append("%s")
-        self.sout.write("%s")
-
-    def setArgument(self, arg):
-        self.arguments.append(arg)
-        self.substitutions.append("%s")
-
-    def frontArgument(self, arg):
-        self.arguments.insert(0, arg)
-        self.substitutions.insert(0, "%s")
-
-    def containsArgument(self, arg):
-        return "%%%s%%" % (arg,)
-
-
-class PostgresLegacyABIndexEmulator(object):
-    """
-    Emulator for L{twistedcaldv.index.Index} and
-    L{twistedcaldv.index.IndexSchedule}.
-    """
-
-    def __init__(self, addressbook):
-        self.resource = self.addressbook = addressbook
-        if (
-            hasattr(config, "Memcached") and
-            config.Memcached.Pools.Default.ClientEnabled
-        ):
-            self.reserver = MemcachedUIDReserver(self)
-        else:
-            # This is only used with unit tests
-            self.reserver = DummyUIDReserver(self)
-
-
-    @property
-    def _txn(self):
-        return self.addressbook._txn
-
-
-    def reserveUID(self, uid):
-        return self.reserver.reserveUID(uid)
-
-
-    def unreserveUID(self, uid):
-        return self.reserver.unreserveUID(uid)
-
-
-    def isReservedUID(self, uid):
-        return self.reserver.isReservedUID(uid)
-
-
-    def isAllowedUID(self, uid, *names):
-        """
-        Checks to see whether to allow an operation which would add the
-        specified UID to the index.  Specifically, the operation may not
-        violate the constraint that UIDs must be unique.
-        @param uid: the UID to check
-        @param names: the names of resources being replaced or deleted by the
-            operation; UIDs associated with these resources are not checked.
-        @return: True if the UID is not in the index and is not reserved,
-            False otherwise.
-        """
-        rname = self.resourceNameForUID(uid)
-        return (rname is None or rname in names)
-
-
-    def resourceUIDForName(self, name):
-        obj = self.addressbook.addressbookObjectWithName(name)
-        if obj is None:
-            return None
-        return obj.uid()
-
-
-    def resourceNameForUID(self, uid):
-        obj = self.addressbook.addressbookObjectWithUID(uid)
-        if obj is None:
-            return None
-        return obj.name()
-
-
-    def whatchanged(self, revision):
-
-        results = [
-            (name.encode("utf-8"), deleted)
-            for name, deleted in
-            self._txn.execSQL(
-                """select RESOURCE_NAME, DELETED from ADDRESSBOOK_OBJECT_REVISIONS
-                   where REVISION > %s and ADDRESSBOOK_RESOURCE_ID = %s""",
-                [revision, self.addressbook._resourceID],
-            )
-        ]
-        results.sort(key=lambda x:x[1])
-        
-        changed = []
-        deleted = []
-        for name, wasdeleted in results:
-            if name:
-                if wasdeleted:
-                    if revision:
-                        deleted.append(name)
-                else:
-                    changed.append(name)
-            else:
-                raise SyncTokenValidException
-        
-        return changed, deleted,
-
-    def searchValid(self, filter):
-        if isinstance(filter, carddavxml.Filter):
-            qualifiers = addressbookquery.sqladdressbookquery(filter)
-        else:
-            qualifiers = None
-
-        return qualifiers is not None
-
-    def search(self, filter):
-        """
-        Finds resources matching the given qualifiers.
-        @param filter: the L{Filter} for the addressbook-query to execute.
-        @return: an iterable of tuples for each resource matching the
-            given C{qualifiers}. The tuples are C{(name, uid, type)}, where
-            C{name} is the resource name, C{uid} is the resource UID, and
-            C{type} is the resource iCalendar component type.x
-        """
-
-        # Make sure we have a proper Filter element and get the partial SQL statement to use.
-        if isinstance(filter, carddavxml.Filter):
-            qualifiers = addressbookquery.sqladdressbookquery(filter, self.addressbook._resourceID, generator=postgresqladbkgenerator)
-        else:
-            qualifiers = None
-        if qualifiers is not None:
-            rowiter = self._txn.execSQL(
-                "select DISTINCT ADDRESSBOOK_OBJECT.RESOURCE_NAME, ADDRESSBOOK_OBJECT.VCARD_UID" +
-                qualifiers[0],
-                qualifiers[1]
-            )
-        else:
-            rowiter = self._txn.execSQL(
-                "select RESOURCE_NAME, VCARD_UID from ADDRESSBOOK_OBJECT where ADDRESSBOOK_RESOURCE_ID = %s",
-                [self.addressbook._resourceID, ],
-            )
-
-        for row in rowiter:
-            yield row
-
-    def indexedSearch(self, filter, useruid='', fbtype=False):
-        """
-        Always raise L{IndexedSearchException}, since these indexes are not
-        fully implemented yet.
-        """
-        raise IndexedSearchException()
-
-
-    def bruteForceSearch(self):
-        return self._txn.execSQL(
-            "select RESOURCE_NAME, VCARD_UID from "
-            "ADDRESSBOOK_OBJECT where ADDRESSBOOK_RESOURCE_ID = %s",
-            [self.addressbook._resourceID]
-        )
-
-
-    def resourcesExist(self, names):
-        return list(set(names).intersection(
-            set(self.addressbook.listAddressbookObjects())))
-
-
-    def resourceExists(self, name):
-        return bool(
-            self._txn.execSQL(
-                "select RESOURCE_NAME from ADDRESSBOOK_OBJECT where "
-                "RESOURCE_NAME = %s and ADDRESSBOOK_RESOURCE_ID = %s",
-                [name, self.addressbook._resourceID]
-            )
-        )
-
-
-class PostgresLegacyABInvitesEmulator(object):
-    """
-    Emulator for the implicit interface specified by
-    L{twistedcaldav.sharing.InvitesDatabase}.
-    """
-
-
-    def __init__(self, addressbook):
-        self._addressbook = addressbook
-
-
-    @property
-    def _txn(self):
-        return self._addressbook._txn
-
-
-    def create(self):
-        "No-op, because the index implicitly always exists in the database."
-
-
-    def remove(self):
-        "No-op, because the index implicitly always exists in the database."
-
-
-    def allRecords(self):
-        for row in self._txn.execSQL(
-                """
-                select
-                    INVITE.INVITE_UID, INVITE.NAME, INVITE.RECIPIENT_ADDRESS,
-                    ADDRESSBOOK_HOME.OWNER_UID, ADDRESSBOOK_BIND.BIND_MODE,
-                    ADDRESSBOOK_BIND.BIND_STATUS, ADDRESSBOOK_BIND.MESSAGE
-                from
-                    INVITE, ADDRESSBOOK_HOME, ADDRESSBOOK_BIND
-                where
-                    INVITE.RESOURCE_ID = %s and
-                    INVITE.HOME_RESOURCE_ID = 
-                        ADDRESSBOOK_HOME.RESOURCE_ID and
-                    ADDRESSBOOK_BIND.ADDRESSBOOK_RESOURCE_ID =
-                        INVITE.RESOURCE_ID and
-                    ADDRESSBOOK_BIND.ADDRESSBOOK_HOME_RESOURCE_ID =
-                        INVITE.HOME_RESOURCE_ID
-                order by
-                    INVITE.NAME asc
-                """, [self._addressbook._resourceID]):
-            [inviteuid, common_name, userid, ownerUID,
-                bindMode, bindStatus, summary] = row
-            # FIXME: this is really the responsibility of the protocol layer.
-            state = {
-                _BIND_STATUS_INVITED: "NEEDS-ACTION",
-                _BIND_STATUS_ACCEPTED: "ACCEPTED",
-                _BIND_STATUS_DECLINED: "DECLINED",
-                _BIND_STATUS_INVALID: "INVALID",
-            }[bindStatus]
-            access = {
-                _BIND_MODE_READ: "read-only",
-                _BIND_MODE_WRITE: "read-write"
-            }[bindMode]
-            principalURL = "/principals/__uids__/%s/" % (ownerUID,)
-            yield Invite(
-                inviteuid, userid, principalURL, common_name,
-                access, state, summary
-            )
-
-
-    def recordForUserID(self, userid):
-        for record in self.allRecords():
-            if record.userid == userid:
-                return record
-
-
-    def recordForPrincipalURL(self, principalURL):
-        for record in self.allRecords():
-            if record.principalURL == principalURL:
-                return record
-
-
-    def recordForInviteUID(self, inviteUID):
-        for record in self.allRecords():
-            if record.inviteuid == inviteUID:
-                return record
-
-
-    def addOrUpdateRecord(self, record):
-        bindMode = {'read-only': _BIND_MODE_READ,
-                    'read-write': _BIND_MODE_WRITE}[record.access]
-        bindStatus = {
-            "NEEDS-ACTION": _BIND_STATUS_INVITED,
-            "ACCEPTED": _BIND_STATUS_ACCEPTED,
-            "DECLINED": _BIND_STATUS_DECLINED,
-            "INVALID": _BIND_STATUS_INVALID,
-        }[record.state]
-        # principalURL is derived from a directory record's principalURL() so
-        # it will always contain the UID.  The form is '/principals/__uids__/x'
-        # (and may contain a trailing slash).
-        principalUID = record.principalURL.split("/")[3]
-        shareeHome = self._txn.addressbookHomeWithUID(principalUID, create=True)
-        rows = self._txn.execSQL(
-            "select RESOURCE_ID, HOME_RESOURCE_ID from INVITE where RECIPIENT_ADDRESS = %s",
-            [record.userid]
-        )
-        if rows:
-            [[resourceID, homeResourceID]] = rows
-            # Invite(inviteuid, userid, principalURL, common_name, access, state, summary)
-            self._txn.execSQL("""
-                update ADDRESSBOOK_BIND set BIND_MODE = %s,
-                BIND_STATUS = %s, MESSAGE = %s
-                where
-                    ADDRESSBOOK_RESOURCE_ID = %s and
-                    ADDRESSBOOK_HOME_RESOURCE_ID = %s
-            """, [bindMode, bindStatus, record.summary,
-                resourceID, homeResourceID])
-            self._txn.execSQL("""
-                update INVITE set NAME = %s, INVITE_UID = %s
-                where RECIPIENT_ADDRESS = %s
-                """,
-                [record.name, record.inviteuid, record.userid]
-            )
-        else:
-            self._txn.execSQL(
-                """
-                insert into INVITE (
-                    INVITE_UID, NAME,
-                    HOME_RESOURCE_ID, RESOURCE_ID,
-                    RECIPIENT_ADDRESS
-                )
-                values (%s, %s, %s, %s, %s)
-                """,
-                [
-                    record.inviteuid, record.name,
-                    shareeHome._resourceID, self._addressbook._resourceID,
-                    record.userid
-                ])
-            self._txn.execSQL(
-                """
-                insert into ADDRESSBOOK_BIND
-                (
-                    ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_ID, 
-                    ADDRESSBOOK_RESOURCE_NAME, BIND_MODE, BIND_STATUS,
-                    SEEN_BY_OWNER, SEEN_BY_SHAREE, MESSAGE
-                )
-                values (%s, %s, %s, %s, %s, %s, %s, %s)
-                """,
-                [
-                    shareeHome._resourceID,
-                    self._addressbook._resourceID,
-                    None, # this is NULL because it is not bound yet, let's be
-                          # explicit about that.
-                    bindMode,
-                    bindStatus,
-                    False,
-                    False,
-                    record.summary
-                ])
-
-
-    def removeRecordForUserID(self, userid):
-        rec = self.recordForUserID(userid)
-        self.removeRecordForInviteUID(rec.inviteuid)
-
-
-    def removeRecordForPrincipalURL(self, principalURL):
-        raise NotImplementedError("removeRecordForPrincipalURL")
-
-
-    def removeRecordForInviteUID(self, inviteUID):
-        rows = self._txn.execSQL("""
-                select HOME_RESOURCE_ID, RESOURCE_ID from INVITE where
-                INVITE_UID = %s
-            """, [inviteUID])
-        if rows:
-            [[homeID, resourceID]] = rows
-            self._txn.execSQL(
-                "delete from ADDRESSBOOK_BIND where "
-                "ADDRESSBOOK_HOME_RESOURCE_ID = %s and ADDRESSBOOK_RESOURCE_ID = %s",
-                [homeID, resourceID])
-            self._txn.execSQL("delete from INVITE where INVITE_UID = %s",
-                [inviteUID])
-
-
-
-class PostgresLegacyABSharesEmulator(object):
-
-    def __init__(self, home):
-        self._home = home
-
-
-    @property
-    def _txn(self):
-        return self._home._txn
-
-
-    def create(self):
-        pass
-
-
-    def remove(self):
-        pass
-
-
-    def allRecords(self):
-        # This should have been a smart join that got all these columns at
-        # once, but let's not bother to fix it, since the actual query we
-        # _want_ to do (just look for addressbook binds in a particular homes) is
-        # much simpler anyway; we should just do that.
-        shareRows = self._txn.execSQL(
-            """
-            select ADDRESSBOOK_RESOURCE_ID, ADDRESSBOOK_RESOURCE_NAME, MESSAGE
-            from ADDRESSBOOK_BIND
-                where ADDRESSBOOK_HOME_RESOURCE_ID = %s and
-                BIND_MODE != %s and
-                ADDRESSBOOK_RESOURCE_NAME is not null
-            """, [self._home._resourceID, _BIND_MODE_OWN])
-        for resourceID, resourceName, summary in shareRows:
-            [[shareuid]] = self._txn.execSQL(
-                """
-                select INVITE_UID
-                from INVITE
-                where RESOURCE_ID = %s and HOME_RESOURCE_ID = %s
-                """, [resourceID, self._home._resourceID])
-            sharetype = 'I'
-            [[ownerHomeID, ownerResourceName]] = self._txn.execSQL(
-                """
-                select ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_NAME
-                from ADDRESSBOOK_BIND
-                where ADDRESSBOOK_RESOURCE_ID = %s and
-                    BIND_MODE = %s
-                """, [resourceID, _BIND_MODE_OWN]
-                )
-            [[ownerUID]] = self._txn.execSQL(
-                "select OWNER_UID from ADDRESSBOOK_HOME where RESOURCE_ID = %s",
-                [ownerHomeID])
-            hosturl = '/addressbooks/__uids__/%s/%s' % (
-                ownerUID, ownerResourceName
-            )
-            localname = resourceName
-            record = SharedCollectionRecord(
-                shareuid, sharetype, hosturl, localname, summary
-            )
-            yield record
-
-
-    def _search(self, **kw):
-        [[key, value]] = kw.items()
-        for record in self.allRecords():
-            if getattr(record, key) == value:
-                return record
-
-    def recordForLocalName(self, localname):
-        return self._search(localname=localname)
-
-    def recordForShareUID(self, shareUID):
-        return self._search(shareuid=shareUID)
-
-
-    def addOrUpdateRecord(self, record):
-#        print '*** SHARING***: Adding or updating this record:'
-#        import pprint
-#        pprint.pprint(record.__dict__)
-        # record.hosturl -> /addressbooks/__uids__/<uid>/<addressbookname>
-        splithost = record.hosturl.split('/')
-        ownerUID = splithost[3]
-        ownerAddressBookName = splithost[4]
-        ownerHome = self._txn.addressbookHomeWithUID(ownerUID)
-        ownerAddressBook = ownerHome.addressbookWithName(ownerAddressBookName)
-        addressbookResourceID = ownerAddressBook._resourceID
-
-        # There needs to be a bind already, one that corresponds to the
-        # invitation.  The invitation's UID is the same as the share UID.  I
-        # just need to update its 'localname', i.e.
-        # ADDRESSBOOK_BIND.ADDRESSBOOK_RESOURCE_NAME.
-
-        self._txn.execSQL(
-            """
-            update ADDRESSBOOK_BIND set ADDRESSBOOK_RESOURCE_NAME = %s
-            where ADDRESSBOOK_HOME_RESOURCE_ID = %s and ADDRESSBOOK_RESOURCE_ID = %s
-            """,
-            [record.localname, self._home._resourceID, addressbookResourceID]
-        )
-
-
-    def removeRecordForLocalName(self, localname):
-        self._txn.execSQL(
-            "delete from ADDRESSBOOK_BIND where ADDRESSBOOK_RESOURCE_NAME = %s "
-            "and ADDRESSBOOK_HOME_RESOURCE_ID = %s",
-            [localname, self._home._resourceID]
-        )
-
-
-    def removeRecordForShareUID(self, shareUID):
-        pass
-#        c = self._home._cursor()
-#        c.execute(
-#            "delete from ADDRESSBOOK_BIND where ADDRESSBOOK_RESOURCE_NAME = %s "
-#            "and ADDRESSBOOK_HOME_RESOURCE_ID = %s",
-#            [self._home._resourceID]
-#        )
-
-
-
-
-class PostgresAddressBook(AddressbookSyncTokenHelper):
-
-    implements(IAddressBook)
-
-    def __init__(self, home, name, resourceID, notifier):
-        self._home = home
-        self._name = name
-        self._resourceID = resourceID
-        self._objects = {}
-        self._notifier = notifier
-
-
-    @property
-    def _txn(self):
-        return self._home._txn
-
-
-    def setSharingUID(self, uid):
-        self.properties()._setPerUserUID(uid)
-
-
-    def retrieveOldInvites(self):
-        return PostgresLegacyABInvitesEmulator(self)
-
-    def retrieveOldIndex(self):
-        return PostgresLegacyABIndexEmulator(self)
-
-
-    def notifierID(self, label="default"):
-        if self._notifier:
-            return self._notifier.getID(label)
-        else:
-            return None
-
-
-    def name(self):
-        return self._name
-
-
-    def rename(self, name):
-        oldName = self._name
-        self._txn.execSQL(
-            "update ADDRESSBOOK_BIND set ADDRESSBOOK_RESOURCE_NAME = %s "
-            "where ADDRESSBOOK_RESOURCE_ID = %s AND "
-            "ADDRESSBOOK_HOME_RESOURCE_ID = %s",
-            [name, self._resourceID, self._home._resourceID]
-        )
-        self._name = name
-        # update memos
-        del self._home._addressbooks[oldName]
-        self._home._addressbooks[name] = self
-        self._updateSyncToken()
-
-
-    def ownerAddressBookHome(self):
-        return self._home
-
-
-    def listAddressbookObjects(self):
-        # FIXME: see listChildren
-        rows = self._txn.execSQL(
-            "select RESOURCE_NAME from "
-            "ADDRESSBOOK_OBJECT where "
-            "ADDRESSBOOK_RESOURCE_ID = %s",
-            [self._resourceID])
-        return [row[0] for row in rows]
-
-
-    def addressbookObjects(self):
-        for name in self.listAddressbookObjects():
-            yield self.addressbookObjectWithName(name)
-
-
-    @memoized('name', '_objects')
-    def addressbookObjectWithName(self, name):
-        rows = self._txn.execSQL(
-            "select RESOURCE_ID from ADDRESSBOOK_OBJECT where "
-            "RESOURCE_NAME = %s and ADDRESSBOOK_RESOURCE_ID = %s",
-            [name, self._resourceID]
-        )
-        if not rows:
-            return None
-        resid = rows[0][0]
-        return PostgresAddressBookObject(self, name, resid)
-
-
-    @memoized('uid', '_objects')
-    def addressbookObjectWithUID(self, uid):
-        rows = self._txn.execSQL(
-            "select RESOURCE_ID, RESOURCE_NAME from ADDRESSBOOK_OBJECT where "
-            "VCARD_UID = %s and ADDRESSBOOK_RESOURCE_ID = %s",
-            [uid, self._resourceID]
-        )
-        if not rows:
-            return None
-        resid = rows[0][0]
-        name = rows[0][1]
-        return PostgresAddressBookObject(self, name, resid)
-
-
-    def createAddressBookObjectWithName(self, name, component):
-        rows = self._txn.execSQL(
-            "select RESOURCE_NAME from ADDRESSBOOK_OBJECT where "
-            " RESOURCE_NAME = %s AND ADDRESSBOOK_RESOURCE_ID = %s",
-            [name, self._resourceID]
-        )
-        if rows:
-            raise ObjectResourceNameAlreadyExistsError()
-
-        addressbookObject = PostgresAddressBookObject(self, name, None)
-        addressbookObject.component = lambda : component
-
-        validateAddressBookComponent(addressbookObject, self, component)
-
-        addressbookObject.updateDatabase(component, inserting=True)
-        self._insertRevision(name)
-
-        if self._notifier:
-            self._home._txn.postCommit(self._notifier.notify)
-
-
-    def removeAddressBookObjectWithName(self, name):
-        self._txn.execSQL(
-            "delete from ADDRESSBOOK_OBJECT where RESOURCE_NAME = %s and "
-            "ADDRESSBOOK_RESOURCE_ID = %s",
-            [name, self._resourceID]
-        )
-        if self._txn._cursor.rowcount == 0:
-            raise NoSuchObjectResourceError()
-        self._objects.pop(name, None)
-        self._deleteRevision(name)
-
-        if self._notifier:
-            self._txn.postCommit(self._notifier.notify)
-
-
-    def removeAddressBookObjectWithUID(self, uid):
-        rows = self._txn.execSQL(
-            "select RESOURCE_NAME from ADDRESSBOOK_OBJECT where "
-            "VCARD_UID = %s AND ADDRESSBOOK_RESOURCE_ID = %s",
-            [uid, self._resourceID]
-        )
-        if not rows:
-            raise NoSuchObjectResourceError()
-        name = rows[0][0]
-        self._txn.execSQL(
-            "delete from ADDRESSBOOK_OBJECT where VCARD_UID = %s and "
-            "ADDRESSBOOK_RESOURCE_ID = %s",
-            [uid, self._resourceID]
-        )
-        self._objects.pop(name, None)
-        self._objects.pop(uid, None)
-        self._deleteRevision(name)
-
-        if self._notifier:
-            self._home._txn.postCommit(self._notifier.notify)
-
-
-    def addressbookObjectsSinceToken(self, token):
-        raise NotImplementedError()
-
-
-    @cached
-    def properties(self):
-        return PropertyStore(
-            self.ownerAddressBookHome().uid(),
-            self._txn,
-            self._resourceID
-        )
-
-
-    # IDataStoreResource
-    def contentType(self):
-        """
-        The content type of Addressbook objects is ???
-        """
-        return None # FIXME: verify
-
-
-    def md5(self):
-        return None
-
-
-    def size(self):
-        return 0
-
-
-    def created(self):
-        created = self._txn.execSQL(
-            "select extract(EPOCH from CREATED) from ADDRESSBOOK where "
-            "RESOURCE_ID = %s", [self._resourceID]
-        )[0][0]
-        return int(created)
-
-
-    def modified(self):
-        modified = self._txn.execSQL(
-            "select extract(EPOCH from MODIFIED) from ADDRESSBOOK where "
-            "RESOURCE_ID = %s", [self._resourceID]
-        )[0][0]
-        return int(modified)
-
-
-
-
-class PostgresAddressBookHome(object):
-
-    implements(IAddressBookHome)
-
-    def __init__(self, transaction, ownerUID, resourceID, notifier):
-        self._txn = transaction
-        self._ownerUID = ownerUID
-        self._resourceID = resourceID
-        self._addressbooks = {}
-        self._notifier = notifier
-
-
-    def retrieveOldShares(self):
-        return PostgresLegacyABSharesEmulator(self)
-
-
-    def uid(self):
-        """
-        Retrieve the unique identifier for this calendar home.
-
-        @return: a string.
-        """
-        return self._ownerUID
-
-
-    def name(self):
-        """
-        Implement L{IDataStoreResource.name} to return the uid.
-        """
-        return self.uid()
-
-
-    def listChildren(self):
-        """
-        Retrieve the names of the children in this addressbook home.
-
-        @return: an iterable of C{str}s.
-        """
-        # FIXME: not specified on the interface or exercised by the tests, but
-        # required by clients of the implementation!
-        rows = self._txn.execSQL(
-            "select ADDRESSBOOK_RESOURCE_NAME from ADDRESSBOOK_BIND where "
-            "ADDRESSBOOK_HOME_RESOURCE_ID = %s "
-            "AND BIND_STATUS != %s",
-            [self._resourceID, _BIND_STATUS_DECLINED]
-        )
-        names = [row[0] for row in rows]
-        return names
-
-
-    def addressbooks(self):
-        """
-        Retrieve addressbooks contained in this addressbook home.
-
-        @return: an iterable of L{IAddressBook}s.
-        """
-        names = self.listChildren()
-        for name in names:
-            yield self.addressbookWithName(name)
-
-
-    @memoized('name', '_addressbooks')
-    def addressbookWithName(self, name):
-        """
-        Retrieve the addressbook with the given C{name} contained in this
-        addressbook home.
-
-        @param name: a string.
-        @return: an L{IAddressBook} or C{None} if no such addressbook
-            exists.
-        """
-        data = self._txn.execSQL(
-            "select ADDRESSBOOK_RESOURCE_ID from ADDRESSBOOK_BIND where "
-            "ADDRESSBOOK_RESOURCE_NAME = %s and "
-            "ADDRESSBOOK_HOME_RESOURCE_ID = %s",
-            [name, self._resourceID]
-        )
-        if not data:
-            return None
-        resourceID = data[0][0]
-        if self._notifier:
-            childID = "%s/%s" % (self.uid(), name)
-            notifier = self._notifier.clone(label="collection", id=childID)
-        else:
-            notifier = None
-        return PostgresAddressBook(self, name, resourceID, notifier)
-
-
-    def createAddressBookWithName(self, name):
-        rows = self._txn.execSQL(
-            "select ADDRESSBOOK_RESOURCE_NAME from ADDRESSBOOK_BIND where "
-            "ADDRESSBOOK_RESOURCE_NAME = %s AND "
-            "ADDRESSBOOK_HOME_RESOURCE_ID = %s",
-            [name, self._resourceID]
-        )
-        if rows:
-            raise HomeChildNameAlreadyExistsError()
-        rows = self._txn.execSQL("select nextval('RESOURCE_ID_SEQ')")
-        resourceID = rows[0][0]
-        self._txn.execSQL(
-            "insert into ADDRESSBOOK (RESOURCE_ID) values "
-            "(%s)",
-            [resourceID])
-
-        self._txn.execSQL("""
-            insert into ADDRESSBOOK_BIND (
-                ADDRESSBOOK_HOME_RESOURCE_ID,
-                ADDRESSBOOK_RESOURCE_ID, ADDRESSBOOK_RESOURCE_NAME, BIND_MODE,
-                SEEN_BY_OWNER, SEEN_BY_SHAREE, BIND_STATUS) values (
-            %s, %s, %s, %s, %s, %s, %s)
-            """,
-            [self._resourceID, resourceID, name, _BIND_MODE_OWN, True, True,
-             _BIND_STATUS_ACCEPTED]
-        )
-
-        addressbookType = ResourceType.addressbook #@UndefinedVariable
-        newAddressbook = self.addressbookWithName(name)
-        newAddressbook.properties()[
-            PropertyName.fromElement(ResourceType)] = addressbookType
-        newAddressbook._updateSyncToken()
-
-        if self._notifier:
-            self._txn.postCommit(self._notifier.notify)
-
-
-    def removeAddressBookWithName(self, name):
-        self._txn.execSQL(
-            "delete from ADDRESSBOOK_BIND where ADDRESSBOOK_RESOURCE_NAME = %s and "
-            "ADDRESSBOOK_HOME_RESOURCE_ID = %s",
-            [name, self._resourceID]
-        )
-        self._addressbooks.pop(name, None)
-        if self._txn._cursor.rowcount == 0:
-            raise NoSuchHomeChildError()
-        # FIXME: the schema should probably cascade the addressbook delete when
-        # the last bind is deleted.
-        if self._notifier:
-            self._txn.postCommit(self._notifier.notify)
-
-
-    @cached
-    def properties(self):
-        return PropertyStore(
-            self.uid(),
-            self._txn,
-            self._resourceID
-        )
-
-
-    # IDataStoreResource
-    def contentType(self):
-        """
-        The content type of Addressbook home objects is ???
-        """
-        return None # FIXME: verify
-
-
-    def md5(self):
-        return None
-
-
-    def size(self):
-        return 0
-
-
-    def created(self):
-        return None
-
-
-    def modified(self):
-        return None
-
-
-    def notifierID(self, label="default"):
-        if self._notifier:
-            return self._notifier.getID(label)
-        else:
-            return None
-
-
-#
-
-
-class PostgresStore(Service, object):
-
-    implements(IDataStore)
-
-    def __init__(self, connectionFactory, notifierFactory, attachmentsPath):
-        self.connectionFactory = connectionFactory
-        self.notifierFactory = notifierFactory
-        self.attachmentsPath = attachmentsPath
-
-
-    def newTransaction(self, label="unlabeled"):
-        return PostgresTransaction(
-            self,
-            self.connectionFactory(),
-            self.notifierFactory,
-            label
-        )
-

Deleted: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/postgres_schema_v1.sql
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/postgres_schema_v1.sql	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/postgres_schema_v1.sql	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,342 +0,0 @@
------------------
--- Resource ID --
------------------
-
-create sequence RESOURCE_ID_SEQ;
-
-
--------------------
--- Calendar Home --
--------------------
-
-create table CALENDAR_HOME (
-  RESOURCE_ID integer      primary key default nextval('RESOURCE_ID_SEQ'),
-  OWNER_UID   varchar(255) not null unique
-);
-
-
---------------
--- Calendar --
---------------
-
-create table CALENDAR (
-  RESOURCE_ID integer   primary key default nextval('RESOURCE_ID_SEQ'),
-  REVISION    integer   default 0,
-  CREATED     timestamp default timezone('UTC', CURRENT_TIMESTAMP),
-  MODIFIED    timestamp default timezone('UTC', CURRENT_TIMESTAMP)
-);
-
-
-------------------------
--- Sharing Invitation --
-------------------------
-
-create table INVITE (
-    INVITE_UID         varchar(255) not null,
-    NAME               varchar(255) not null,
-    RECIPIENT_ADDRESS  varchar(255) not null,
-    HOME_RESOURCE_ID   integer      not null,
-    RESOURCE_ID        integer      not null
-);
-
-
----------------------------
--- Sharing Notifications --
----------------------------
-
-create table NOTIFICATION_HOME (
-  RESOURCE_ID integer      primary key default nextval('RESOURCE_ID_SEQ'),
-  OWNER_UID   varchar(255) not null unique
-);
-
-
-create table NOTIFICATION (
-  RESOURCE_ID                   integer      primary key default nextval('RESOURCE_ID_SEQ'),
-  NOTIFICATION_HOME_RESOURCE_ID integer      not null references NOTIFICATION_HOME,
-  NOTIFICATION_UID              varchar(255) not null,
-  XML_TYPE                      varchar      not null,
-  XML_DATA                      varchar      not null,
-  CREATED                       timestamp default timezone('UTC', CURRENT_TIMESTAMP),
-  MODIFIED                      timestamp default timezone('UTC', CURRENT_TIMESTAMP),
-
-  unique(NOTIFICATION_UID, NOTIFICATION_HOME_RESOURCE_ID)
-);
-
-
--------------------
--- Calendar Bind --
--------------------
-
--- Joins CALENDAR_HOME and CALENDAR
-
-create table CALENDAR_BIND (
-  CALENDAR_HOME_RESOURCE_ID integer      not null references CALENDAR_HOME,
-  CALENDAR_RESOURCE_ID      integer      not null references CALENDAR,
-  
-  -- An invitation which hasn't been accepted yet will not yet have a resource
-  -- name, so this field may be null.
-  
-  CALENDAR_RESOURCE_NAME    varchar(255),
-  BIND_MODE                 integer      not null, -- enum CALENDAR_BIND_MODE
-  BIND_STATUS               integer      not null, -- enum CALENDAR_BIND_STATUS
-  SEEN_BY_OWNER             boolean      not null,
-  SEEN_BY_SHAREE            boolean      not null,
-  MESSAGE                   text,
-
-  primary key(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID),
-  unique(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_NAME)
-);
-
--- Enumeration of calendar bind modes
-
-create table CALENDAR_BIND_MODE (
-  ID          integer     primary key,
-  DESCRIPTION varchar(16) not null unique
-);
-
-insert into CALENDAR_BIND_MODE values (0, 'own'  );
-insert into CALENDAR_BIND_MODE values (1, 'read' );
-insert into CALENDAR_BIND_MODE values (2, 'write');
-
--- Enumeration of statuses
-
-create table CALENDAR_BIND_STATUS (
-  ID          integer     primary key,
-  DESCRIPTION varchar(16) not null unique
-);
-
-insert into CALENDAR_BIND_STATUS values (0, 'invited' );
-insert into CALENDAR_BIND_STATUS values (1, 'accepted');
-insert into CALENDAR_BIND_STATUS values (2, 'declined');
-insert into CALENDAR_BIND_STATUS values (3, 'invalid');
-
-
----------------------
--- Calendar Object --
----------------------
-
-create table CALENDAR_OBJECT (
-  RESOURCE_ID          integer      primary key default nextval('RESOURCE_ID_SEQ'),
-  CALENDAR_RESOURCE_ID integer      not null references CALENDAR,
-  RESOURCE_NAME        varchar(255) not null,
-  ICALENDAR_TEXT       text         not null,
-  ICALENDAR_UID        varchar(255) not null,
-  ICALENDAR_TYPE       varchar(255) not null,
-  ATTACHMENTS_MODE     integer      not null, -- enum CALENDAR_OBJECT_ATTACHMENTS_MODE
-  ORGANIZER            varchar(255),
-  ORGANIZER_OBJECT     integer      references CALENDAR_OBJECT,
-  RECURRANCE_MAX       date,        -- maximum date that recurrences have been expanded to.
-  CREATED              timestamp default timezone('UTC', CURRENT_TIMESTAMP),
-  MODIFIED             timestamp default timezone('UTC', CURRENT_TIMESTAMP),
-
-  unique(CALENDAR_RESOURCE_ID, RESOURCE_NAME)
-
-  -- since the 'inbox' is a 'calendar resource' for the purpose of storing
-  -- calendar objects, this constraint has to be selectively enforced by the
-  -- application layer.
-
-  -- unique(CALENDAR_RESOURCE_ID, ICALENDAR_UID)
-);
-
--- Enumeration of attachment modes
-
-create table CALENDAR_OBJECT_ATTACHMENTS_MODE (
-  ID          integer     primary key,
-  DESCRIPTION varchar(16) not null unique
-);
-
-insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (0, 'read' );
-insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (1, 'write');
-
-
------------------
--- Instance ID --
------------------
-
-create sequence INSTANCE_ID_SEQ;
-
-
-----------------
--- Time Range --
-----------------
-
-create table TIME_RANGE (
-  INSTANCE_ID                 integer        primary key default nextval('INSTANCE_ID_SEQ'),
-  CALENDAR_RESOURCE_ID        integer        not null references CALENDAR,
-  CALENDAR_OBJECT_RESOURCE_ID integer        not null references CALENDAR_OBJECT on delete cascade,
-  FLOATING                    boolean        not null,
-  START_DATE                  timestamp      not null,
-  END_DATE                    timestamp      not null,
-  FBTYPE                      integer        not null,
-  TRANSPARENT                 boolean        not null
-);
-
--- Enumeration of free/busy types
-
-create table FREE_BUSY_TYPE (
-  ID          integer     primary key,
-  DESCRIPTION varchar(16) not null unique
-);
-
-insert into FREE_BUSY_TYPE values (0, 'unknown'         );
-insert into FREE_BUSY_TYPE values (1, 'free'            );
-insert into FREE_BUSY_TYPE values (2, 'busy'            );
-insert into FREE_BUSY_TYPE values (3, 'busy-unavailable');
-insert into FREE_BUSY_TYPE values (4, 'busy-tentative'  );
-
-
-------------------
--- Transparency --
-------------------
-
-create table TRANSPARENCY (
-  TIME_RANGE_INSTANCE_ID      integer      not null references TIME_RANGE on delete cascade,
-  USER_ID                     varchar(255) not null,
-  TRANSPARENT                 boolean      not null
-);
-
-
-----------------
--- Attachment --
-----------------
-
-create table ATTACHMENT (
-  CALENDAR_OBJECT_RESOURCE_ID integer       not null references CALENDAR_OBJECT on delete cascade,
-  CONTENT_TYPE                varchar(255)  not null,
-  SIZE                        integer       not null,
-  MD5                         char(32)      not null,
-  CREATED                     timestamp default timezone('UTC', CURRENT_TIMESTAMP),
-  MODIFIED                    timestamp default timezone('UTC', CURRENT_TIMESTAMP),
-  PATH                        varchar(1024) not null unique
-);
-
-
-------------------------------
--- Calendar Object Revision --
-------------------------------
-
-create sequence CALENDAR_OBJECT_REVISION_SEQ;
-
-
--------------------------------
--- Calendar Object Revisions --
--------------------------------
-
-create table CALENDAR_OBJECT_REVISIONS (
-  CALENDAR_RESOURCE_ID integer      not null references CALENDAR on delete cascade,
-  RESOURCE_NAME        varchar(255) not null,
-  REVISION             integer      not null,
-  DELETED              boolean      not null,
-
-  unique(CALENDAR_RESOURCE_ID, RESOURCE_NAME)
-);
-
-
-------------------
--- iTIP Message --
-------------------
-
-create table ITIP_MESSAGE (
-  CALENDAR_RESOURCE_ID integer      not null references CALENDAR,
-  ICALENDAR_TEXT       text         not null,
-  ICALENDAR_UID        varchar(255) not null,
-  MD5                  char(32)     not null,
-  CHANGES              text         not null
-);
-
-
------------------------
--- Resource Property --
------------------------
-
-create table RESOURCE_PROPERTY (
-  RESOURCE_ID integer      not null, -- foreign key: *.RESOURCE_ID
-  NAME        varchar(255) not null,
-  VALUE       text         not null, -- FIXME: xml?
-  VIEWER_UID  varchar(255),
-
-  primary key(RESOURCE_ID, NAME, VIEWER_UID)
-);
-
-
-----------------------
--- AddressBook Home --
-----------------------
-
-create table ADDRESSBOOK_HOME (
-  RESOURCE_ID integer      primary key default nextval('RESOURCE_ID_SEQ'),
-  OWNER_UID   varchar(255) not null unique
-);
-
-
------------------
--- AddressBook --
------------------
-
-create table ADDRESSBOOK (
-  RESOURCE_ID integer   primary key default nextval('RESOURCE_ID_SEQ'),
-  REVISION    integer   default 0,
-  CREATED     timestamp default timezone('UTC', CURRENT_TIMESTAMP),
-  MODIFIED    timestamp default timezone('UTC', CURRENT_TIMESTAMP)
-);
-
-
-----------------------
--- AddressBook Bind --
-----------------------
-
--- Joins ADDRESSBOOK_HOME and ADDRESSBOOK
-
-create table ADDRESSBOOK_BIND (
-  ADDRESSBOOK_HOME_RESOURCE_ID integer      not null references ADDRESSBOOK_HOME,
-  ADDRESSBOOK_RESOURCE_ID      integer      not null references ADDRESSBOOK,
-
-  -- An invitation which hasn't been accepted yet will not yet have a resource
-  -- name, so this field may be null.
-
-  ADDRESSBOOK_RESOURCE_NAME    varchar(255),
-  BIND_MODE                    integer      not null, -- enum CALENDAR_BIND_MODE
-  BIND_STATUS                  integer      not null, -- enum CALENDAR_BIND_STATUS
-  SEEN_BY_OWNER                boolean      not null,
-  SEEN_BY_SHAREE               boolean      not null,
-  MESSAGE                      text,                  -- FIXME: xml?
-
-  primary key(ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_ID),
-  unique(ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_NAME)
-);
-
-
-create table ADDRESSBOOK_OBJECT (
-  RESOURCE_ID             integer      primary key default nextval('RESOURCE_ID_SEQ'),
-  ADDRESSBOOK_RESOURCE_ID integer      not null references ADDRESSBOOK,
-  RESOURCE_NAME           varchar(255) not null,
-  VCARD_TEXT              text         not null,
-  VCARD_UID               varchar(255) not null,
-  CREATED                 timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
-  MODIFIED                timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
-
-  unique(ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME),
-  unique(ADDRESSBOOK_RESOURCE_ID, VCARD_UID)
-);
-
-------------------------------
--- AddressBook Object Revision --
-------------------------------
-
-create sequence ADDRESSBOOK_OBJECT_REVISION_SEQ;
-
-
--------------------------------
--- AddressBook Object Revisions --
--------------------------------
-
-create table ADDRESSBOOK_OBJECT_REVISIONS (
-  ADDRESSBOOK_RESOURCE_ID integer      not null references ADDRESSBOOK on delete cascade,
-  RESOURCE_NAME           varchar(255) not null,
-  REVISION                integer      not null,
-  DELETED                 boolean      not null,
-
-  unique(ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME)
-);
-
-

Modified: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/scheduling.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/scheduling.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/scheduling.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_scheduling -*-
+# -*- test-case-name: txdav.caldav.datastore.test.test_scheduling -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #
@@ -15,7 +15,7 @@
 # limitations under the License.
 ##
 from zope.interface.declarations import implements
-from txcaldav.icalendarstore import ICalendarHome, ICalendar, ICalendarObject,\
+from txdav.caldav.icalendarstore import ICalendarHome, ICalendar, ICalendarObject,\
     ICalendarTransaction
 from txdav.idav import IDataStore
 from twisted.python.util import FancyEqMixin

Copied: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/sql.py (from rev 6185, CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/sql.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/sql.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/sql.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,634 @@
+# -*- test-case-name: txdav.caldav.datastore.test.test_sql -*-
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+__all__ = [
+    "CalendarHome",
+    "Calendar",
+    "CalendarObject",
+]
+
+from twext.python.vcomponent import VComponent
+from twext.web2.dav.element.rfc2518 import ResourceType
+from twext.web2.http_headers import MimeType, generateContentType
+
+from twisted.internet.error import ConnectionLost
+from twisted.internet.interfaces import ITransport
+from twisted.python import hashlib
+from twisted.python.failure import Failure
+
+from twistedcaldav import caldavxml, customxml
+from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
+from twistedcaldav.dateops import normalizeForIndex
+from twistedcaldav.index import IndexedSearchException
+from twistedcaldav.instance import InvalidOverriddenInstanceError
+
+from txdav.caldav.datastore.util import validateCalendarComponent,\
+    dropboxIDFromCalendarObject
+from txdav.caldav.icalendarstore import ICalendarHome, ICalendar, ICalendarObject,\
+    IAttachment
+
+from txdav.common.datastore.sql import CommonHome, CommonHomeChild,\
+    CommonObjectResource
+from txdav.common.datastore.sql_legacy import \
+    PostgresLegacyIndexEmulator, PostgresLegacyInvitesEmulator,\
+    PostgresLegacySharesEmulator
+from txdav.common.datastore.sql_tables import CALENDAR_TABLE,\
+    CALENDAR_BIND_TABLE, CALENDAR_OBJECT_REVISIONS_TABLE, CALENDAR_OBJECT_TABLE,\
+    _ATTACHMENTS_MODE_WRITE
+from txdav.base.propertystore.base import PropertyName
+
+from vobject.icalendar import utc
+
+import datetime
+
+from zope.interface.declarations import implements
+
+class CalendarHome(CommonHome):
+
+    implements(ICalendarHome)
+
+    def __init__(self, transaction, ownerUID, resourceID, notifier):
+        super(CalendarHome, self).__init__(transaction, ownerUID, resourceID, notifier)
+
+        self._shares = PostgresLegacySharesEmulator(self)
+        self._childClass = Calendar
+        self._childTable = CALENDAR_TABLE
+        self._bindTable = CALENDAR_BIND_TABLE
+
+    createCalendarWithName = CommonHome.createChildWithName
+    removeCalendarWithName = CommonHome.removeChildWithName
+    calendarWithName = CommonHome.childWithName
+    calendars = CommonHome.children
+    listCalendars = CommonHome.listChildren
+
+    def calendarObjectWithDropboxID(self, dropboxID):
+        """
+        Implement lookup with brute-force scanning.
+        """
+        for calendar in self.calendars():
+            for calendarObject in calendar.calendarObjects():
+                if dropboxID == calendarObject.dropboxID():
+                    return calendarObject
+
+
+    def createdHome(self):
+        self.createCalendarWithName("calendar")
+        defaultCal = self.calendarWithName("calendar")
+        props = defaultCal.properties()
+        props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
+            Opaque())
+        self.createCalendarWithName("inbox")
+
+class Calendar(CommonHomeChild):
+    """
+    File-based implementation of L{ICalendar}.
+    """
+    implements(ICalendar)
+
+    def __init__(self, home, name, resourceID, notifier):
+        """
+        Initialize a calendar pointing at a path on disk.
+
+        @param name: the subdirectory of calendarHome where this calendar
+            resides.
+        @type name: C{str}
+
+        @param calendarHome: the home containing this calendar.
+        @type calendarHome: L{CalendarHome}
+
+        @param realName: If this calendar was just created, the name which it
+        will eventually have on disk.
+        @type realName: C{str}
+        """
+        super(Calendar, self).__init__(home, name, resourceID, notifier)
+
+        self._index = PostgresLegacyIndexEmulator(self)
+        self._invites = PostgresLegacyInvitesEmulator(self)
+        self._objectResourceClass = CalendarObject
+        self._bindTable = CALENDAR_BIND_TABLE
+        self._homeChildTable = CALENDAR_TABLE
+        self._revisionsTable = CALENDAR_OBJECT_REVISIONS_TABLE
+        self._objectTable = CALENDAR_OBJECT_TABLE
+
+
+    @property
+    def _calendarHome(self):
+        return self._home
+
+
+    def resourceType(self):
+        return ResourceType.calendar #@UndefinedVariable
+
+
+    ownerCalendarHome = CommonHomeChild.ownerHome
+    calendarObjects = CommonHomeChild.objectResources
+    listCalendarObjects = CommonHomeChild.listObjectResources
+    calendarObjectWithName = CommonHomeChild.objectResourceWithName
+    calendarObjectWithUID = CommonHomeChild.objectResourceWithUID
+    createCalendarObjectWithName = CommonHomeChild.createObjectResourceWithName
+    removeCalendarObjectWithName = CommonHomeChild.removeObjectResourceWithName
+    removeCalendarObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
+    calendarObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
+
+
+    def calendarObjectsInTimeRange(self, start, end, timeZone):
+        raise NotImplementedError()
+
+
+    def initPropertyStore(self, props):
+        # Setup peruser special properties
+        props.setSpecialProperties(
+            (
+                PropertyName.fromElement(caldavxml.CalendarDescription),
+                PropertyName.fromElement(caldavxml.CalendarTimeZone),
+            ),
+            (
+                PropertyName.fromElement(customxml.GETCTag),
+                PropertyName.fromElement(caldavxml.SupportedCalendarComponentSet),
+                PropertyName.fromElement(caldavxml.ScheduleCalendarTransp),
+            ),
+        )
+
+    def contentType(self):
+        """
+        The content type of Calendar objects is text/calendar.
+        """
+        return MimeType.fromString("text/calendar; charset=utf-8")
+
+#
+# Duration into the future through which recurrences are expanded in the index
+# by default.  This is a caching parameter which affects the size of the index;
+# it does not affect search results beyond this period, but it may affect
+# performance of such a search.
+#
+default_future_expansion_duration = datetime.timedelta(days=365 * 1)
+
+#
+# Maximum duration into the future through which recurrences are expanded in the
+# index.  This is a caching parameter which affects the size of the index; it
+# does not affect search results beyond this period, but it may affect
+# performance of such a search.
+#
+# When a search is performed on a time span that goes beyond that which is
+# expanded in the index, we have to open each resource which may have data in
+# that time period.  In order to avoid doing that multiple times, we want to
+# cache those results.  However, we don't necessarily want to cache all
+# occurrences into some obscenely far-in-the-future date, so we cap the caching
+# period.  Searches beyond this period will always be relatively expensive for
+# resources with occurrences beyond this period.
+#
+maximum_future_expansion_duration = datetime.timedelta(days=365 * 5)
+
+icalfbtype_to_indexfbtype = {
+    "UNKNOWN"         : 0,
+    "FREE"            : 1,
+    "BUSY"            : 2,
+    "BUSY-UNAVAILABLE": 3,
+    "BUSY-TENTATIVE"  : 4,
+}
+
+indexfbtype_to_icalfbtype = {
+    0: '?',
+    1: 'F',
+    2: 'B',
+    3: 'U',
+    4: 'T',
+}
+
+def _pathToName(path):
+    return path.rsplit(".", 1)[0].split("-", 3)[-1]
+
+class CalendarObject(CommonObjectResource):
+    implements(ICalendarObject)
+
+    def __init__(self, name, calendar, resid):
+        super(CalendarObject, self).__init__(name, calendar, resid)
+
+        self._objectTable = CALENDAR_OBJECT_TABLE
+
+    @property
+    def _calendar(self):
+        return self._parentCollection
+
+    def calendar(self):
+        return self._calendar
+
+    def setComponent(self, component, inserting=False):
+        validateCalendarComponent(self, self._calendar, component, inserting)
+
+        self.updateDatabase(component, inserting=inserting)
+        if inserting:
+            self._calendar._insertRevision(self._name)
+        else:
+            self._calendar._updateRevision(self._name)
+
+        if self._calendar._notifier:
+            self._txn.postCommit(self._calendar._notifier.notify)
+
+    def updateDatabase(self, component, expand_until=None, reCreate=False, inserting=False):
+        """
+        Update the database tables for the new data being written.
+
+        @param component: calendar data to store
+        @type component: L{Component}
+        """
+
+        # Decide how far to expand based on the component
+        master = component.masterComponent()
+        if master is None or not component.isRecurring() and not component.isRecurringUnbounded():
+            # When there is no master we have a set of overridden components - index them all.
+            # When there is one instance - index it.
+            # When bounded - index all.
+            expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
+        else:
+            if expand_until:
+                expand = expand_until
+            else:
+                expand = datetime.date.today() + default_future_expansion_duration
+
+            if expand > (datetime.date.today() + maximum_future_expansion_duration):
+                raise IndexedSearchException
+
+        try:
+            instances = component.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
+        except InvalidOverriddenInstanceError, e:
+            self.log_err("Invalid instance %s when indexing %s in %s" % (e.rid, self._name, self.resource,))
+            raise
+
+        componentText = str(component)
+        self._objectText = componentText
+        organizer = component.getOrganizer()
+        if not organizer:
+            organizer = ""
+
+        # CALENDAR_OBJECT table update
+        if inserting:
+            self._resourceID = self._txn.execSQL(
+                """
+                insert into CALENDAR_OBJECT
+                (CALENDAR_RESOURCE_ID, RESOURCE_NAME, ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX)
+                 values
+                (%s, %s, %s, %s, %s, %s, %s, %s)
+                 returning RESOURCE_ID
+                """,
+                # FIXME: correct ATTACHMENTS_MODE based on X-APPLE-
+                # DROPBOX
+                [
+                    self._calendar._resourceID,
+                    self._name,
+                    componentText,
+                    component.resourceUID(),
+                    component.resourceType(),
+                    _ATTACHMENTS_MODE_WRITE,
+                    organizer,
+                    normalizeForIndex(instances.limit) if instances.limit else None,
+                ]
+            )[0][0]
+        else:
+            self._txn.execSQL(
+                """
+                update CALENDAR_OBJECT set
+                (ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, MODIFIED)
+                 =
+                (%s, %s, %s, %s, %s, %s, timezone('UTC', CURRENT_TIMESTAMP))
+                 where RESOURCE_ID = %s
+                """,
+                # should really be filling out more fields: ORGANIZER,
+                # ORGANIZER_OBJECT, a correct ATTACHMENTS_MODE based on X-APPLE-
+                # DROPBOX
+                [
+                    componentText,
+                    component.resourceUID(),
+                    component.resourceType(),
+                    _ATTACHMENTS_MODE_WRITE,
+                    organizer,
+                    normalizeForIndex(instances.limit) if instances.limit else None,
+                    self._resourceID
+                ]
+            )
+
+            # Need to wipe the existing time-range for this and rebuild
+            self._txn.execSQL(
+                """
+                delete from TIME_RANGE where CALENDAR_OBJECT_RESOURCE_ID = %s
+                """,
+                [
+                    self._resourceID,
+                ],
+            )
+
+
+        # CALENDAR_OBJECT table update
+        for key in instances:
+            instance = instances[key]
+            start = instance.start.replace(tzinfo=utc)
+            end = instance.end.replace(tzinfo=utc)
+            float = instance.start.tzinfo is None
+            transp = instance.component.propertyValue("TRANSP") == "TRANSPARENT"
+            instanceid = self._txn.execSQL(
+                """
+                insert into TIME_RANGE
+                (CALENDAR_RESOURCE_ID, CALENDAR_OBJECT_RESOURCE_ID, FLOATING, START_DATE, END_DATE, FBTYPE, TRANSPARENT)
+                 values
+                (%s, %s, %s, %s, %s, %s, %s)
+                 returning
+                INSTANCE_ID
+                """,
+                [
+                    self._calendar._resourceID,
+                    self._resourceID,
+                    float,
+                    start,
+                    end,
+                    icalfbtype_to_indexfbtype.get(instance.component.getFBType(), icalfbtype_to_indexfbtype["FREE"]),
+                    transp,
+                ],
+            )[0][0]
+            peruserdata = component.perUserTransparency(instance.rid)
+            for useruid, transp in peruserdata:
+                self._txn.execSQL(
+                    """
+                    insert into TRANSPARENCY
+                    (TIME_RANGE_INSTANCE_ID, USER_ID, TRANSPARENT)
+                     values
+                    (%s, %s, %s)
+                    """,
+                    [
+                        instanceid,
+                        useruid,
+                        transp,
+                    ],
+                )
+
+        # Special - for unbounded recurrence we insert a value for "infinity"
+        # that will allow an open-ended time-range to always match it.
+        if component.isRecurringUnbounded():
+            start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
+            end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
+            float = False
+            instanceid = self._txn.execSQL(
+                """
+                insert into TIME_RANGE
+                (CALENDAR_RESOURCE_ID, CALENDAR_OBJECT_RESOURCE_ID, FLOATING, START_DATE, END_DATE, FBTYPE, TRANSPARENT)
+                 values
+                (%s, %s, %s, %s, %s, %s, %s)
+                 returning
+                INSTANCE_ID
+                """,
+                [
+                    self._calendar._resourceID,
+                    self._resourceID,
+                    float,
+                    start,
+                    end,
+                    icalfbtype_to_indexfbtype["UNKNOWN"],
+                    True,
+                ],
+            )[0][0]
+            peruserdata = component.perUserTransparency(None)
+            for useruid, transp in peruserdata:
+                self._txn.execSQL(
+                    """
+                    insert into TRANSPARENCY
+                    (TIME_RANGE_INSTANCE_ID, USER_ID, TRANSPARENT)
+                     values
+                    (%s, %s, %s)
+                    """,
+                    [
+                        instanceid,
+                        useruid,
+                        transp,
+                    ],
+                )
+
+    def component(self):
+        return VComponent.fromString(self.iCalendarText())
+
+    def text(self):
+        if self._objectText is None:
+            text = self._txn.execSQL(
+                "select ICALENDAR_TEXT from CALENDAR_OBJECT where "
+                "RESOURCE_ID = %s", [self._resourceID]
+            )[0][0]
+            self._objectText = text
+            return text
+        else:
+            return self._objectText
+
+    iCalendarText = text
+
+    def uid(self):
+        return self.component().resourceUID()
+
+    def name(self):
+        return self._name
+
+    def componentType(self):
+        return self.component().mainType()
+
+    def organizer(self):
+        return self.component().getOrganizer()
+
+    def createAttachmentWithName(self, name, contentType):
+        path = self._attachmentPath(name)
+        attachment = Attachment(self, path)
+        self._txn.execSQL("""
+            insert into ATTACHMENT (CALENDAR_OBJECT_RESOURCE_ID, CONTENT_TYPE,
+            SIZE, MD5, PATH)
+            values (%s, %s, %s, %s, %s)
+            """,
+            [
+                self._resourceID, generateContentType(contentType), 0, "",
+                attachment._pathValue()
+            ]
+        )
+        return attachment.store(contentType)
+
+    def removeAttachmentWithName(self, name):
+        attachment = Attachment(self, self._attachmentPath(name))
+        self._txn.postCommit(attachment._path.remove)
+        self._txn.execSQL("""
+        delete from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s AND
+        PATH = %s
+        """, [self._resourceID, attachment._pathValue()])
+
+    def attachmentWithName(self, name):
+        attachment = Attachment(self, self._attachmentPath(name))
+        if attachment._populate():
+            return attachment
+        else:
+            return None
+
+    def attendeesCanManageAttachments(self):
+        return self.component().hasPropertyInAnyComponent("X-APPLE-DROPBOX")
+
+    def dropboxID(self):
+        return dropboxIDFromCalendarObject(self)
+
+    def _attachmentPath(self, name):
+        attachmentRoot = self._txn._store.attachmentsPath
+        try:
+            attachmentRoot.createDirectory()
+        except:
+            pass
+        return attachmentRoot.child(
+            "%s-%s-%s-%s.attachment" % (
+                self._calendar._home.uid(), self._calendar.name(),
+                self.name(), name
+            )
+        )
+
+    def attachments(self):
+        rows = self._txn.execSQL("""
+        select PATH from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s 
+        """, [self._resourceID])
+        for row in rows:
+            demangledName = _pathToName(row[0])
+            yield self.attachmentWithName(demangledName)
+
+    def initPropertyStore(self, props):
+        # Setup peruser special properties
+        props.setSpecialProperties(
+            (
+            ),
+            (
+                PropertyName.fromElement(customxml.TwistedCalendarAccessProperty),
+                PropertyName.fromElement(customxml.TwistedSchedulingObjectResource),
+                PropertyName.fromElement(caldavxml.ScheduleTag),
+                PropertyName.fromElement(customxml.TwistedScheduleMatchETags),
+                PropertyName.fromElement(customxml.TwistedCalendarHasPrivateCommentsProperty),
+                PropertyName.fromElement(caldavxml.Originator),
+                PropertyName.fromElement(caldavxml.Recipient),
+                PropertyName.fromElement(customxml.ScheduleChanges),
+            ),
+        )
+
+    # IDataStoreResource
+    def contentType(self):
+        """
+        The content type of Calendar objects is text/calendar.
+        """
+        return MimeType.fromString("text/calendar; charset=utf-8")
+
+class AttachmentStorageTransport(object):
+
+    implements(ITransport)
+
+    def __init__(self, attachment, contentType):
+        self.attachment = attachment
+        self.contentType = contentType
+        self.buf = ''
+        self.hash = hashlib.md5()
+
+
+    @property
+    def _txn(self):
+        return self.attachment._txn
+
+
+    def write(self, data):
+        self.buf += data
+        self.hash.update(data)
+
+
+    def loseConnection(self):
+        self.attachment._path.setContent(self.buf)
+        pathValue = self.attachment._pathValue()
+        contentTypeString = generateContentType(self.contentType)
+        self._txn.execSQL(
+            "update ATTACHMENT set CONTENT_TYPE = %s, SIZE = %s, MD5 = %s, MODIFIED = timezone('UTC', CURRENT_TIMESTAMP) "
+            "WHERE PATH = %s",
+            [contentTypeString, len(self.buf), self.hash.hexdigest(), pathValue]
+        )
+
+class Attachment(object):
+
+    implements(IAttachment)
+
+    def __init__(self, calendarObject, path):
+        self._calendarObject = calendarObject
+        self._path = path
+
+
+    @property
+    def _txn(self):
+        return self._calendarObject._txn
+
+
+    def _populate(self):
+        """
+        Execute necessary SQL queries to retrieve attributes.
+
+        @return: C{True} if this attachment exists, C{False} otherwise.
+        """
+        rows = self._txn.execSQL(
+            """
+            select CONTENT_TYPE, SIZE, MD5, extract(EPOCH from CREATED), extract(EPOCH from MODIFIED) from ATTACHMENT where PATH = %s
+            """, [self._pathValue()])
+        if not rows:
+            return False
+        self._contentType = MimeType.fromString(rows[0][0])
+        self._size = rows[0][1]
+        self._md5 = rows[0][2]
+        self._created = int(rows[0][3])
+        self._modified = int(rows[0][4])
+        return True
+
+
+    def name(self):
+        return _pathToName(self._pathValue())
+
+
+    def _pathValue(self):
+        """
+        Compute the value which should go into the 'path' column for this
+        attachment.
+        """
+        root = self._txn._store.attachmentsPath
+        return '/'.join(self._path.segmentsFrom(root))
+
+    def properties(self):
+        pass # stub
+
+
+    def store(self, contentType):
+        return AttachmentStorageTransport(self, contentType)
+
+
+    def retrieve(self, protocol):
+        protocol.dataReceived(self._path.getContent())
+        protocol.connectionLost(Failure(ConnectionLost()))
+
+
+    # IDataStoreResource
+    def contentType(self):
+        return self._contentType
+
+
+    def md5(self):
+        return self._md5
+
+
+    def size(self):
+        return self._size
+
+
+    def created(self):
+        return self._created
+
+    def modified(self):
+        return self._modified

Modified: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/__init__.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/__init__.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/__init__.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test -*-
+# -*- test-case-name: txdav.caldav.datastore.test -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #

Deleted: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/common.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,1202 +0,0 @@
-# -*- test-case-name: txcaldav.calendarstore -*-
-##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-Tests for common calendar store API functions.
-"""
-
-from zope.interface.verify import verifyObject
-from zope.interface.exceptions import (
-    BrokenMethodImplementation, DoesNotImplement)
-
-from twisted.internet.defer import Deferred, inlineCallbacks
-from twisted.internet.protocol import Protocol
-
-from txdav.idav import IPropertyStore, IDataStore, AlreadyFinishedError
-from txdav.propertystore.base import PropertyName
-
-from txdav.common.icommondatastore import HomeChildNameAlreadyExistsError, \
-    ICommonTransaction
-from txdav.common.icommondatastore import InvalidObjectResourceError
-from txdav.common.icommondatastore import NoSuchHomeChildError
-from txdav.common.icommondatastore import NoSuchObjectResourceError
-from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
-from txdav.common.inotifications import INotificationObject
-
-from txcaldav.icalendarstore import (
-    ICalendarObject, ICalendarHome,
-    ICalendar, IAttachment, ICalendarTransaction)
-
-from twext.python.filepath import CachingFilePath as FilePath
-from twext.web2.dav import davxml
-from twext.web2.http_headers import MimeType
-from twext.web2.dav.element.base import WebDAVUnknownElement
-from twext.python.vcomponent import VComponent
-
-from twistedcaldav.notify import Notifier
-from twistedcaldav.customxml import InviteNotification, InviteSummary
-
-storePath = FilePath(__file__).parent().child("calendar_store")
-
-homeRoot = storePath.child("ho").child("me").child("home1")
-
-cal1Root = homeRoot.child("calendar_1")
-
-calendar1_objectNames = [
-    "1.ics",
-    "2.ics",
-    "3.ics",
-]
-
-
-home1_calendarNames = [
-    "calendar_1",
-    "calendar_2",
-    "calendar_empty",
-]
-
-
-event4_text = (
-    "BEGIN:VCALENDAR\r\n"
-      "VERSION:2.0\r\n"
-      "PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
-      "CALSCALE:GREGORIAN\r\n"
-      "BEGIN:VTIMEZONE\r\n"
-        "TZID:US/Pacific\r\n"
-        "BEGIN:DAYLIGHT\r\n"
-          "TZOFFSETFROM:-0800\r\n"
-          "RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\n"
-          "DTSTART:20070311T020000\r\n"
-          "TZNAME:PDT\r\n"
-          "TZOFFSETTO:-0700\r\n"
-        "END:DAYLIGHT\r\n"
-        "BEGIN:STANDARD\r\n"
-          "TZOFFSETFROM:-0700\r\n"
-          "RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\n"
-          "DTSTART:20071104T020000\r\n"
-          "TZNAME:PST\r\n"
-          "TZOFFSETTO:-0800\r\n"
-        "END:STANDARD\r\n"
-      "END:VTIMEZONE\r\n"
-      "BEGIN:VEVENT\r\n"
-        "CREATED:20100203T013849Z\r\n"
-        "UID:uid4\r\n"
-        "DTEND;TZID=US/Pacific:20100207T173000\r\n"
-        "TRANSP:OPAQUE\r\n"
-        "SUMMARY:New Event\r\n"
-        "DTSTART;TZID=US/Pacific:20100207T170000\r\n"
-        "DTSTAMP:20100203T013909Z\r\n"
-        "SEQUENCE:3\r\n"
-        "BEGIN:VALARM\r\n"
-          "X-WR-ALARMUID:1377CCC7-F85C-4610-8583-9513D4B364E1\r\n"
-          "TRIGGER:-PT20M\r\n"
-          "ATTACH;VALUE=URI:Basso\r\n"
-          "ACTION:AUDIO\r\n"
-        "END:VALARM\r\n"
-      "END:VEVENT\r\n"
-    "END:VCALENDAR\r\n"
-)
-
-
-
-event4notCalDAV_text = (
-    "BEGIN:VCALENDAR\r\n"
-      "VERSION:2.0\r\n"
-      "PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
-      "CALSCALE:GREGORIAN\r\n"
-      "BEGIN:VEVENT\r\n"
-        "CREATED:20100203T013849Z\r\n"
-        "UID:4\r\n"
-        "DTEND;TZID=US/Pacific:20100207T173000\r\n" # TZID without VTIMEZONE
-        "TRANSP:OPAQUE\r\n"
-        "SUMMARY:New Event\r\n"
-        "DTSTART;TZID=US/Pacific:20100207T170000\r\n"
-        "DTSTAMP:20100203T013909Z\r\n"
-        "SEQUENCE:3\r\n"
-        "BEGIN:VALARM\r\n"
-          "X-WR-ALARMUID:1377CCC7-F85C-4610-8583-9513D4B364E1\r\n"
-          "TRIGGER:-PT20M\r\n"
-          "ATTACH;VALUE=URI:Basso\r\n"
-          "ACTION:AUDIO\r\n"
-        "END:VALARM\r\n"
-      "END:VEVENT\r\n"
-    "END:VCALENDAR\r\n"
-)
-
-
-
-event1modified_text = event4_text.replace(
-    "\r\nUID:uid4\r\n",
-    "\r\nUID:uid1\r\n"
-)
-
-
-
-def assertProvides(testCase, interface, provider):
-    """
-    Verify that C{provider} properly provides C{interface}
-
-    @type interface: L{zope.interface.Interface}
-    @type provider: C{provider}
-    """
-    try:
-        verifyObject(interface, provider)
-    except BrokenMethodImplementation, e:
-        testCase.fail(e)
-    except DoesNotImplement, e:
-        testCase.fail("%r does not provide %s.%s" %
-                      (provider, interface.__module__, interface.getName()))
-
-
-class CommonTests(object):
-    """
-    Tests for common functionality of interfaces defined in
-    L{txcaldav.icalendarstore}.
-    """
-
-    requirements = {
-        "home1": {
-            "calendar_1": {
-                "1.ics": cal1Root.child("1.ics").getContent(),
-                "2.ics": cal1Root.child("2.ics").getContent(),
-                "3.ics": cal1Root.child("3.ics").getContent()
-            },
-            "calendar_2": {},
-            "calendar_empty": {},
-            "not_a_calendar": None
-        },
-        "not_a_home": None
-    }
-
-    def storeUnderTest(self):
-        """
-        Subclasses must override this to return an L{ICommonDataStore} provider
-        which adheres to the structure detailed by L{CommonTests.requirements}.
-        This attribute is a dict of dict of dicts; the outermost layer
-        representing UIDs mapping to calendar homes, then calendar names mapping
-        to calendar collections, and finally calendar object names mapping to
-        calendar object text.
-        """
-        raise NotImplementedError()
-
-
-    lastTransaction = None
-    savedStore = None
-
-    def transactionUnderTest(self):
-        """
-        Create a transaction from C{storeUnderTest} and save it as
-        C[lastTransaction}.  Also makes sure to use the same store, saving the
-        value from C{storeUnderTest}.
-        """
-        if self.lastTransaction is not None:
-            return self.lastTransaction
-        if self.savedStore is None:
-            self.savedStore = self.storeUnderTest()
-        txn = self.lastTransaction = self.savedStore.newTransaction(self.id())
-        return txn
-
-
-    def commit(self):
-        """
-        Commit the last transaction created from C{transactionUnderTest}, and
-        clear it.
-        """
-        self.lastTransaction.commit()
-        self.lastTransaction = None
-
-
-    def abort(self):
-        """
-        Abort the last transaction created from C[transactionUnderTest}, and
-        clear it.
-        """
-        self.lastTransaction.abort()
-        self.lastTransaction = None
-
-
-    def setUp(self):
-        self.notifierFactory = StubNotifierFactory()
-
-
-    def tearDown(self):
-        if self.lastTransaction is not None:
-            self.commit()
-
-
-    def homeUnderTest(self):
-        """
-        Get the calendar home detailed by C{requirements['home1']}.
-        """
-        return self.transactionUnderTest().calendarHomeWithUID("home1")
-
-
-    def calendarUnderTest(self):
-        """
-        Get the calendar detailed by C{requirements['home1']['calendar_1']}.
-        """
-        return self.homeUnderTest().calendarWithName("calendar_1")
-
-
-    def calendarObjectUnderTest(self):
-        """
-        Get the calendar detailed by
-        C{requirements['home1']['calendar_1']['1.ics']}.
-        """
-        return self.calendarUnderTest().calendarObjectWithName("1.ics")
-
-
-    assertProvides = assertProvides
-
-    def test_calendarStoreProvides(self):
-        """
-        The calendar store provides L{IDataStore} and its required attributes.
-        """
-        calendarStore = self.storeUnderTest()
-        self.assertProvides(IDataStore, calendarStore)
-
-
-    def test_transactionProvides(self):
-        """
-        The transactions generated by the calendar store provide
-        L{ICommonStoreTransaction}, L{ICalendarTransaction}, and their
-        respectively required attributes.
-        """
-        txn = self.transactionUnderTest()
-        self.assertProvides(ICommonTransaction, txn)
-        self.assertProvides(ICalendarTransaction, txn)
-
-
-    def test_homeProvides(self):
-        """
-        The calendar homes generated by the calendar store provide
-        L{ICalendarHome} and its required attributes.
-        """
-        self.assertProvides(ICalendarHome, self.homeUnderTest())
-
-
-    def test_calendarProvides(self):
-        """
-        The calendars generated by the calendar store provide L{ICalendar} and
-        its required attributes.
-        """
-        self.assertProvides(ICalendar, self.calendarUnderTest())
-
-
-    def test_calendarObjectProvides(self):
-        """
-        The calendar objects generated by the calendar store provide
-        L{ICalendarObject} and its required attributes.
-        """
-        self.assertProvides(ICalendarObject, self.calendarObjectUnderTest())
-
-
-    def notificationUnderTest(self):
-        txn = self.transactionUnderTest()
-        notifications = txn.notificationsWithUID("home1")
-        inviteNotification = InviteNotification()
-        notifications.writeNotificationObject("abc", inviteNotification,
-            inviteNotification.toxml())
-        notificationObject = notifications.notificationObjectWithUID("abc")
-        return notificationObject
-
-
-    def test_notificationObjectProvides(self):
-        """
-        The objects retrieved from the notification home (the object returned
-        from L{notificationsWithUID}) provide L{INotificationObject}.
-        """
-        notificationObject = self.notificationUnderTest()
-        self.assertProvides(INotificationObject, notificationObject)
-
-
-    def test_replaceNotification(self):
-        """
-        L{INotificationCollection.writeNotificationObject} will silently
-        overwrite the notification object.
-        """
-        notifications = self.transactionUnderTest().notificationsWithUID(
-            "home1"
-        )
-        inviteNotification = InviteNotification()
-        notifications.writeNotificationObject("abc", inviteNotification,
-            inviteNotification.toxml())
-        inviteNotification2 = InviteNotification(InviteSummary("a summary"))
-        notifications.writeNotificationObject(
-            "abc", inviteNotification, inviteNotification2.toxml())
-        abc = notifications.notificationObjectWithUID("abc")
-        self.assertEquals(abc.xmldata(), inviteNotification2.toxml())
-
-
-    def test_notificationObjectModified(self):
-        """
-        The objects retrieved from the notification home have a C{modified}
-        method which returns the timestamp of their last modification.
-        """
-        notification = self.notificationUnderTest()
-        self.assertIsInstance(notification.modified(), int)
-
-
-    def test_notificationObjectParent(self):
-        """
-        L{INotificationObject.notificationCollection} returns the
-        L{INotificationCollection} that the object was retrieved from.
-        """
-        txn = self.transactionUnderTest()
-        collection = txn.notificationsWithUID("home1")
-        notification = self.notificationUnderTest()
-        self.assertIdentical(collection, notification.notificationCollection())
-
-
-    def test_notifierID(self):
-        home = self.homeUnderTest()
-        self.assertEquals(home.notifierID(), "home1")
-        calendar = home.calendarWithName("calendar_1")
-        self.assertEquals(calendar.notifierID(), "home1")
-        self.assertEquals(calendar.notifierID(label="collection"), "home1/calendar_1")
-
-
-    def test_calendarHomeWithUID_exists(self):
-        """
-        Finding an existing calendar home by UID results in an object that
-        provides L{ICalendarHome} and has a C{uid()} method that returns the
-        same value that was passed in.
-        """
-        calendarHome = (self.transactionUnderTest()
-                        .calendarHomeWithUID("home1"))
-        self.assertEquals(calendarHome.uid(), "home1")
-        self.assertProvides(ICalendarHome, calendarHome)
-
-
-    def test_calendarHomeWithUID_absent(self):
-        """
-        L{ICommonStoreTransaction.calendarHomeWithUID} should return C{None}
-        when asked for a non-existent calendar home.
-        """
-        txn = self.transactionUnderTest()
-        self.assertEquals(txn.calendarHomeWithUID("xyzzy"), None)
-
-
-    def test_calendarWithName_exists(self):
-        """
-        L{ICalendarHome.calendarWithName} returns an L{ICalendar} provider,
-        whose name matches the one passed in.
-        """
-        home = self.homeUnderTest()
-        for name in home1_calendarNames:
-            calendar = home.calendarWithName(name)
-            if calendar is None:
-                self.fail("calendar %r didn't exist" % (name,))
-            self.assertProvides(ICalendar, calendar)
-            self.assertEquals(calendar.name(), name)
-
-
-    def test_calendarRename(self):
-        """
-        L{ICalendar.rename} changes the name of the L{ICalendar}.
-        """
-        home = self.homeUnderTest()
-        calendar = home.calendarWithName("calendar_1")
-        calendar.rename("some_other_name")
-        def positiveAssertions():
-            self.assertEquals(calendar.name(), "some_other_name")
-            self.assertEquals(calendar, home.calendarWithName("some_other_name"))
-            self.assertEquals(None, home.calendarWithName("calendar_1"))
-        positiveAssertions()
-        self.commit()
-        home = self.homeUnderTest()
-        calendar = home.calendarWithName("some_other_name")
-        positiveAssertions()
-        # FIXME: revert
-        # FIXME: test for multiple renames
-        # FIXME: test for conflicting renames (a->b, c->a in the same txn)
-
-
-    def test_calendarWithName_absent(self):
-        """
-        L{ICalendarHome.calendarWithName} returns C{None} for calendars which
-        do not exist.
-        """
-        self.assertEquals(self.homeUnderTest().calendarWithName("xyzzy"),
-                          None)
-
-
-    def test_createCalendarWithName_absent(self):
-        """
-        L{ICalendarHome.createCalendarWithName} creates a new L{ICalendar} that
-        can be retrieved with L{ICalendarHome.calendarWithName}.
-        """
-        home = self.homeUnderTest()
-        name = "new"
-        self.assertIdentical(home.calendarWithName(name), None)
-        home.createCalendarWithName(name)
-        self.assertNotIdentical(home.calendarWithName(name), None)
-        def checkProperties():
-            calendarProperties = home.calendarWithName(name).properties()
-            self.assertEquals(
-                calendarProperties[
-                    PropertyName.fromString(davxml.ResourceType.sname())
-                ],
-                davxml.ResourceType.calendar #@UndefinedVariable
-            )
-        checkProperties()
-
-        self.commit()
-
-        # Make sure notification fired after commit
-        self.assertEquals(self.notifierFactory.history, [("update", "home1")])
-
-        # Make sure it's available in a new transaction; i.e. test the commit.
-        home = self.homeUnderTest()
-        self.assertNotIdentical(home.calendarWithName(name), None)
-
-        # Sanity check: are the properties actually persisted?  Check in
-        # subsequent transaction.
-        checkProperties()
-
-        # FIXME: no independent testing of the property store's persistence
-        # right now
-
-
-    def test_createCalendarWithName_exists(self):
-        """
-        L{ICalendarHome.createCalendarWithName} raises
-        L{CalendarAlreadyExistsError} when the name conflicts with an already-
-        existing 
-        """
-        for name in home1_calendarNames:
-            self.assertRaises(
-                HomeChildNameAlreadyExistsError,
-                self.homeUnderTest().createCalendarWithName, name
-            )
-
-
-    def test_removeCalendarWithName_exists(self):
-        """
-        L{ICalendarHome.removeCalendarWithName} removes a calendar that already
-        exists.
-        """
-        home = self.homeUnderTest()
-
-        # FIXME: test transactions
-        for name in home1_calendarNames:
-            self.assertNotIdentical(home.calendarWithName(name), None)
-            home.removeCalendarWithName(name)
-            self.assertEquals(home.calendarWithName(name), None)
-
-        self.commit()
-
-        # Make sure notification fired after commit
-        self.assertEquals(
-            self.notifierFactory.history,
-            [("update", "home1"), ("update", "home1"), ("update", "home1")]
-        )
-
-
-    def test_removeCalendarWithName_absent(self):
-        """
-        Attempt to remove an non-existing calendar should raise.
-        """
-        home = self.homeUnderTest()
-        self.assertRaises(NoSuchHomeChildError,
-                          home.removeCalendarWithName, "xyzzy")
-
-
-    def test_calendarObjects(self):
-        """
-        L{ICalendar.calendarObjects} will enumerate the calendar objects present
-        in the filesystem, in name order, but skip those with hidden names.
-        """
-        calendar1 = self.calendarUnderTest()
-        calendarObjects = list(calendar1.calendarObjects())
-
-        for calendarObject in calendarObjects:
-            self.assertProvides(ICalendarObject, calendarObject)
-            self.assertEquals(
-                calendar1.calendarObjectWithName(calendarObject.name()),
-                calendarObject
-            )
-
-        self.assertEquals(
-            set(list(o.name() for o in calendarObjects)),
-            set(calendar1_objectNames)
-        )
-
-
-    def test_calendarObjectsWithRemovedObject(self):
-        """
-        L{ICalendar.calendarObjects} skips those objects which have been
-        removed by L{Calendar.removeCalendarObjectWithName} in the same
-        transaction, even if it has not yet been committed.
-        """
-        calendar1 = self.calendarUnderTest()
-        calendar1.removeCalendarObjectWithName("2.ics")
-        calendarObjects = list(calendar1.calendarObjects())
-        self.assertEquals(set(o.name() for o in calendarObjects),
-                          set(calendar1_objectNames) - set(["2.ics"]))
-
-
-    def test_ownerCalendarHome(self):
-        """
-        L{ICalendar.ownerCalendarHome} should match the home UID.
-        """
-        self.assertEquals(
-            self.calendarUnderTest().ownerCalendarHome().uid(),
-            self.homeUnderTest().uid()
-        )
-
-
-    def test_calendarObjectWithName_exists(self):
-        """
-        L{ICalendar.calendarObjectWithName} returns an L{ICalendarObject}
-        provider for calendars which already exist.
-        """
-        calendar1 = self.calendarUnderTest()
-        for name in calendar1_objectNames:
-            calendarObject = calendar1.calendarObjectWithName(name)
-            self.assertProvides(ICalendarObject, calendarObject)
-            self.assertEquals(calendarObject.name(), name)
-            # FIXME: add more tests based on CommonTests.requirements
-
-
-    def test_calendarObjectWithName_absent(self):
-        """
-        L{ICalendar.calendarObjectWithName} returns C{None} for calendars which
-        don't exist.
-        """
-        calendar1 = self.calendarUnderTest()
-        self.assertEquals(calendar1.calendarObjectWithName("xyzzy"), None)
-
-
-    def test_removeCalendarObjectWithUID_exists(self):
-        """
-        Remove an existing calendar object.
-        """
-        calendar = self.calendarUnderTest()
-        for name in calendar1_objectNames:
-            uid = (u'uid' + name.rstrip(".ics"))
-            self.assertNotIdentical(calendar.calendarObjectWithUID(uid),
-                                    None)
-            calendar.removeCalendarObjectWithUID(uid)
-            self.assertEquals(
-                calendar.calendarObjectWithUID(uid),
-                None
-            )
-            self.assertEquals(
-                calendar.calendarObjectWithName(name),
-                None
-            )
-
-        # Make sure notifications are fired after commit
-        self.commit()
-        self.assertEquals(
-            self.notifierFactory.history,
-            [
-                ("update", "home1"),
-                ("update", "home1/calendar_1"),
-                ("update", "home1"),
-                ("update", "home1/calendar_1"),
-                ("update", "home1"),
-                ("update", "home1/calendar_1"),
-            ]
-        )
-
-    def test_removeCalendarObjectWithName_exists(self):
-        """
-        Remove an existing calendar object.
-        """
-        calendar = self.calendarUnderTest()
-        for name in calendar1_objectNames:
-            self.assertNotIdentical(
-                calendar.calendarObjectWithName(name), None
-            )
-            calendar.removeCalendarObjectWithName(name)
-            self.assertIdentical(
-                calendar.calendarObjectWithName(name), None
-            )
-
-
-    def test_removeCalendarObjectWithName_absent(self):
-        """
-        Attempt to remove an non-existing calendar object should raise.
-        """
-        calendar = self.calendarUnderTest()
-        self.assertRaises(
-            NoSuchObjectResourceError,
-            calendar.removeCalendarObjectWithName, "xyzzy"
-        )
-
-
-    def test_calendarName(self):
-        """
-        L{Calendar.name} reflects the name of the calendar.
-        """
-        self.assertEquals(self.calendarUnderTest().name(), "calendar_1")
-
-
-    def test_calendarObjectName(self):
-        """
-        L{ICalendarObject.name} reflects the name of the calendar object.
-        """
-        self.assertEquals(self.calendarObjectUnderTest().name(), "1.ics")
-
-
-    def test_component(self):
-        """
-        L{ICalendarObject.component} returns a L{VComponent} describing the
-        calendar data underlying that calendar object.
-        """
-        component = self.calendarObjectUnderTest().component()
-
-        self.failUnless(
-            isinstance(component, VComponent),
-            component
-        )
-
-        self.assertEquals(component.name(), "VCALENDAR")
-        self.assertEquals(component.mainType(), "VEVENT")
-        self.assertEquals(component.resourceUID(), "uid1")
-
-
-    def test_iCalendarText(self):
-        """
-        L{ICalendarObject.iCalendarText} returns a C{str} describing the same
-        data provided by L{ICalendarObject.component}.
-        """
-        text = self.calendarObjectUnderTest().iCalendarText()
-        self.assertIsInstance(text, str)
-        self.failUnless(text.startswith("BEGIN:VCALENDAR\r\n"))
-        self.assertIn("\r\nUID:uid1\r\n", text)
-        self.failUnless(text.endswith("\r\nEND:VCALENDAR\r\n"))
-
-
-    def test_calendarObjectUID(self):
-        """
-        L{ICalendarObject.uid} returns a C{str} describing the C{UID} property
-        of the calendar object's component.
-        """
-        self.assertEquals(self.calendarObjectUnderTest().uid(), "uid1")
-
-
-    def test_organizer(self):
-        """
-        L{ICalendarObject.organizer} returns a C{str} describing the calendar
-        user address of the C{ORGANIZER} property of the calendar object's
-        component.
-        """
-        self.assertEquals(
-            self.calendarObjectUnderTest().organizer(),
-            "mailto:wsanchez at apple.com"
-        )
-
-
-    def test_calendarObjectWithUID_absent(self):
-        """
-        L{ICalendar.calendarObjectWithUID} returns C{None} for calendars which
-        don't exist.
-        """
-        calendar1 = self.calendarUnderTest()
-        self.assertEquals(calendar1.calendarObjectWithUID("xyzzy"), None)
-
-
-    def test_calendars(self):
-        """
-        L{ICalendarHome.calendars} returns an iterable of L{ICalendar}
-        providers, which are consistent with the results from
-        L{ICalendar.calendarWithName}.
-        """
-        # Add a dot directory to make sure we don't find it
-        # self.home1._path.child(".foo").createDirectory()
-        home = self.homeUnderTest()
-        calendars = list(home.calendars())
-
-        for calendar in calendars:
-            self.assertProvides(ICalendar, calendar)
-            self.assertEquals(calendar,
-                              home.calendarWithName(calendar.name()))
-
-        self.assertEquals(
-            list(c.name() for c in calendars),
-            home1_calendarNames
-        )
-
-
-    def test_calendarsAfterAddCalendar(self):
-        """
-        L{ICalendarHome.calendars} includes calendars recently added with
-        L{ICalendarHome.createCalendarWithName}.
-        """
-        home = self.homeUnderTest()
-        before = set(x.name() for x in home.calendars())
-        home.createCalendarWithName("new-name")
-        after = set(x.name() for x in home.calendars())
-        self.assertEquals(before | set(['new-name']), after)
-
-
-    def test_createCalendarObjectWithName_absent(self):
-        """
-        L{ICalendar.createCalendarObjectWithName} creates a new
-        L{ICalendarObject}.
-        """
-        calendar1 = self.calendarUnderTest()
-        name = "4.ics"
-        self.assertIdentical(calendar1.calendarObjectWithName(name), None)
-        component = VComponent.fromString(event4_text)
-        calendar1.createCalendarObjectWithName(name, component)
-
-        calendarObject = calendar1.calendarObjectWithName(name)
-        self.assertEquals(calendarObject.component(), component)
-
-        self.commit()
-
-        # Make sure notifications fire after commit
-        self.assertEquals(
-            self.notifierFactory.history,
-            [
-                ("update", "home1"),
-                ("update", "home1/calendar_1"),
-            ]
-        )
-
-
-    def test_createCalendarObjectWithName_exists(self):
-        """
-        L{ICalendar.createCalendarObjectWithName} raises
-        L{CalendarObjectNameAlreadyExistsError} if a calendar object with the
-        given name already exists in that calendar.
-        """
-        cal = self.calendarUnderTest()
-        comp = VComponent.fromString(event4_text)
-        self.assertRaises(
-            ObjectResourceNameAlreadyExistsError,
-            cal.createCalendarObjectWithName,
-            "1.ics", comp
-        )
-
-
-    def test_createCalendarObjectWithName_invalid(self):
-        """
-        L{ICalendar.createCalendarObjectWithName} raises
-        L{InvalidCalendarComponentError} if presented with invalid iCalendar
-        text.
-        """
-        self.assertRaises(
-            InvalidObjectResourceError,
-            self.calendarUnderTest().createCalendarObjectWithName,
-            "new", VComponent.fromString(event4notCalDAV_text)
-        )
-
-
-    def test_setComponent_invalid(self):
-        """
-        L{ICalendarObject.setComponent} raises L{InvalidICalendarDataError} if
-        presented with invalid iCalendar text.
-        """
-        calendarObject = self.calendarObjectUnderTest()
-        self.assertRaises(
-            InvalidObjectResourceError,
-            calendarObject.setComponent,
-            VComponent.fromString(event4notCalDAV_text)
-        )
-
-
-    def test_setComponent_uidchanged(self):
-        """
-        L{ICalendarObject.setComponent} raises L{InvalidCalendarComponentError}
-        when given a L{VComponent} whose UID does not match its existing UID.
-        """
-        calendar1 = self.calendarUnderTest()
-        component = VComponent.fromString(event4_text)
-        calendarObject = calendar1.calendarObjectWithName("1.ics")
-        self.assertRaises(
-            InvalidObjectResourceError,
-            calendarObject.setComponent, component
-        )
-
-
-    def test_calendarHomeWithUID_create(self):
-        """
-        L{ICommonStoreTransaction.calendarHomeWithUID} with C{create=True}
-        will create a calendar home that doesn't exist yet.
-        """
-        txn = self.transactionUnderTest()
-        noHomeUID = "xyzzy"
-        calendarHome = txn.calendarHomeWithUID(
-            noHomeUID,
-            create=True
-        )
-        def readOtherTxn():
-            otherTxn = self.savedStore.newTransaction(self.id() + "other txn")
-            self.addCleanup(otherTxn.commit)
-            return otherTxn.calendarHomeWithUID(noHomeUID)
-        self.assertProvides(ICalendarHome, calendarHome)
-        # Default calendar should be automatically created.
-        self.assertProvides(ICalendar,
-                            calendarHome.calendarWithName("calendar"))
-        # A concurrent transaction shouldn't be able to read it yet:
-        self.assertIdentical(readOtherTxn(), None)
-        self.commit()
-        # But once it's committed, other transactions should see it.
-        self.assertProvides(ICalendarHome, readOtherTxn())
-
-
-    def test_setComponent(self):
-        """
-        L{CalendarObject.setComponent} changes the result of
-        L{CalendarObject.component} within the same transaction.
-        """
-        component = VComponent.fromString(event1modified_text)
-
-        calendar1 = self.calendarUnderTest()
-        calendarObject = calendar1.calendarObjectWithName("1.ics")
-        oldComponent = calendarObject.component()
-        self.assertNotEqual(component, oldComponent)
-        calendarObject.setComponent(component)
-        self.assertEquals(calendarObject.component(), component)
-
-        # Also check a new instance
-        calendarObject = calendar1.calendarObjectWithName("1.ics")
-        self.assertEquals(calendarObject.component(), component)
-
-        self.commit()
-
-        # Make sure notification fired after commit
-        self.assertEquals(
-            self.notifierFactory.history,
-            [
-                ("update", "home1"),
-                ("update", "home1/calendar_1"),
-            ]
-        )
-
-
-    def checkPropertiesMethod(self, thunk):
-        """
-        Verify that the given object has a properties method that returns an
-        L{IPropertyStore}.
-        """
-        properties = thunk.properties()
-        self.assertProvides(IPropertyStore, properties)
-
-
-    def test_homeProperties(self):
-        """
-        L{ICalendarHome.properties} returns a property store.
-        """
-        self.checkPropertiesMethod(self.homeUnderTest())
-
-
-    def test_calendarProperties(self):
-        """
-        L{ICalendar.properties} returns a property store.
-        """
-        self.checkPropertiesMethod(self.calendarUnderTest())
-
-
-    def test_calendarObjectProperties(self):
-        """
-        L{ICalendarObject.properties} returns a property store.
-        """
-        self.checkPropertiesMethod(self.calendarObjectUnderTest())
-
-
-    def test_newCalendarObjectProperties(self):
-        """
-        L{ICalendarObject.properties} returns an empty property store for a
-        calendar object which has been created but not committed.
-        """
-        calendar = self.calendarUnderTest()
-        calendar.createCalendarObjectWithName(
-            "4.ics", VComponent.fromString(event4_text)
-        )
-        newEvent = calendar.calendarObjectWithName("4.ics")
-        self.assertEquals(newEvent.properties().items(), [])
-
-
-    def test_setComponentPreservesProperties(self):
-        """
-        L{ICalendarObject.setComponent} preserves properties.
-
-        (Some implementations must go to extra trouble to provide this
-        behavior; for example, file storage must copy extended attributes from
-        the existing file to the temporary file replacing it.)
-        """
-        propertyName = PropertyName("http://example.com/ns", "example")
-        propertyContent = WebDAVUnknownElement("sample content")
-        propertyContent.name = propertyName.name
-        propertyContent.namespace = propertyName.namespace
-
-        self.calendarObjectUnderTest().properties()[
-            propertyName] = propertyContent
-        self.commit()
-        # Sanity check; are properties even readable in a separate transaction?
-        # Should probably be a separate test.
-        self.assertEquals(
-            self.calendarObjectUnderTest().properties()[propertyName],
-            propertyContent)
-        obj = self.calendarObjectUnderTest()
-        event1_text = obj.iCalendarText()
-        event1_text_withDifferentSubject = event1_text.replace(
-            "SUMMARY:CalDAV protocol updates",
-            "SUMMARY:Changed"
-        )
-        # Sanity check; make sure the test has the right idea of the subject.
-        self.assertNotEquals(event1_text, event1_text_withDifferentSubject)
-        newComponent = VComponent.fromString(event1_text_withDifferentSubject)
-        obj.setComponent(newComponent)
-
-        # Putting everything into a separate transaction to account for any
-        # caching that may take place.
-        self.commit()
-        self.assertEquals(
-            self.calendarObjectUnderTest().properties()[propertyName],
-            propertyContent
-        )
-
-
-    eventWithDropbox = "\r\n".join("""
-BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-PRODID:-//Example Inc.//Example Calendar//EN
-VERSION:2.0
-BEGIN:VTIMEZONE
-LAST-MODIFIED:20040110T032845Z
-TZID:US/Eastern
-BEGIN:DAYLIGHT
-DTSTART:20000404T020000
-RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:20001026T020000
-RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-END:VTIMEZONE
-BEGIN:VEVENT
-DTSTAMP:20051222T205953Z
-CREATED:20060101T150000Z
-DTSTART;TZID=US/Eastern:20060101T100000
-DURATION:PT1H
-SUMMARY:event 1
-UID:event1 at ninevah.local
-ORGANIZER:user01
-ATTENDEE;PARTSTAT=ACCEPTED:user01
-ATTACH;VALUE=URI:/calendars/users/home1/some-dropbox-id/some-dropbox-id/caldavd.plist
-X-APPLE-DROPBOX:/calendars/users/home1/dropbox/some-dropbox-id
-END:VEVENT
-END:VCALENDAR
-    """.strip().split("\n"))
-
-    def test_dropboxID(self):
-        """
-        L{ICalendarObject.dropboxID} should synthesize its dropbox from the X
-        -APPLE-DROPBOX property, if available.
-        """
-        cal = self.calendarUnderTest()
-        cal.createCalendarObjectWithName("drop.ics", VComponent.fromString(
-                self.eventWithDropbox
-            )
-        )
-        obj = cal.calendarObjectWithName("drop.ics")
-        self.assertEquals(obj.dropboxID(), "some-dropbox-id")
-
-
-    def test_indexByDropboxProperty(self):
-        """
-        L{ICalendarHome.calendarObjectWithDropboxID} will return a calendar
-        object in the calendar home with the given final segment in its C{X
-        -APPLE-DROPBOX} property URI.
-        """
-        objName = "with-dropbox.ics"
-        cal = self.calendarUnderTest()
-        cal.createCalendarObjectWithName(
-            objName, VComponent.fromString(
-                self.eventWithDropbox
-            )
-        )
-        self.commit()
-        home = self.homeUnderTest()
-        cal = self.calendarUnderTest()
-        fromName = cal.calendarObjectWithName(objName)
-        fromDropbox = home.calendarObjectWithDropboxID("some-dropbox-id")
-        self.assertEquals(fromName, fromDropbox)
-
-
-    @inlineCallbacks
-    def createAttachmentTest(self, refresh):
-        """
-        Common logic for attachment-creation tests.
-        """
-        obj = self.calendarObjectUnderTest()
-        t = obj.createAttachmentWithName("new.attachment", MimeType("text", "x-fixture"))
-        t.write("new attachment")
-        t.write(" text")
-        t.loseConnection()
-        obj = refresh(obj)
-        class CaptureProtocol(Protocol):
-            buf = ''
-            def dataReceived(self, data):
-                self.buf += data
-            def connectionLost(self, reason):
-                self.deferred.callback(self.buf)
-        capture = CaptureProtocol()
-        capture.deferred = Deferred()
-        attachment = obj.attachmentWithName("new.attachment")
-        self.assertProvides(IAttachment, attachment)
-        attachment.retrieve(capture)
-        data = yield capture.deferred
-        self.assertEquals(data, "new attachment text")
-        contentType = attachment.contentType()
-        self.assertIsInstance(contentType, MimeType)
-        self.assertEquals(contentType, MimeType("text", "x-fixture"))
-        self.assertEquals(attachment.md5(), '50a9f27aeed9247a0833f30a631f1858')
-        self.assertEquals(
-            [attachment.name() for attachment in obj.attachments()],
-            ['new.attachment']
-        )
-
-
-    def test_createAttachment(self):
-        """
-        L{ICalendarObject.createAttachmentWithName} will store an
-        L{IAttachment} object that can be retrieved by
-        L{ICalendarObject.attachmentWithName}.
-        """
-        return self.createAttachmentTest(lambda x: x)
-
-
-    def test_createAttachmentCommit(self):
-        """
-        L{ICalendarObject.createAttachmentWithName} will store an
-        L{IAttachment} object that can be retrieved by
-        L{ICalendarObject.attachmentWithName} in subsequent transactions.
-        """
-        def refresh(obj):
-            self.commit()
-            return self.calendarObjectUnderTest()
-        return self.createAttachmentTest(refresh)
-
-
-    def test_removeAttachmentWithName(self, refresh=lambda x:x):
-        """
-        L{ICalendarObject.removeAttachmentWithName} will remove the calendar
-        object with the given name.
-        """
-        def deleteIt(ignored):
-            obj = self.calendarObjectUnderTest()
-            obj.removeAttachmentWithName("new.attachment")
-            obj = refresh(obj)
-            self.assertIdentical(
-                None, obj.attachmentWithName("new.attachment")
-            )
-            self.assertEquals(list(obj.attachments()), [])
-        return self.test_createAttachmentCommit().addCallback(deleteIt)
-
-
-    def test_removeAttachmentWithNameCommit(self):
-        """
-        L{ICalendarObject.removeAttachmentWithName} will remove the calendar
-        object with the given name.  (After commit, it will still be gone.)
-        """
-        def refresh(obj):
-            self.commit()
-            return self.calendarObjectUnderTest()
-        return self.test_removeAttachmentWithName(refresh)
-
-
-    def test_noDropboxCalendar(self):
-        """
-        L{ICalendarObject.createAttachmentWithName} may create a directory
-        named 'dropbox', but this should not be seen as a calendar by
-        L{ICalendarHome.calendarWithName} or L{ICalendarHome.calendars}.
-        """
-        obj = self.calendarObjectUnderTest()
-        t = obj.createAttachmentWithName("new.attachment", MimeType("text", "plain"))
-        t.write("new attachment text")
-        t.loseConnection()
-        self.commit()
-        self.assertEquals(self.homeUnderTest().calendarWithName("dropbox"),
-                          None)
-        self.assertEquals(
-            set([n.name() for n in self.homeUnderTest().calendars()]),
-            set(home1_calendarNames))
-
-
-    def test_finishedOnCommit(self):
-        """ 
-        Calling L{ITransaction.abort} or L{ITransaction.commit} after
-        L{ITransaction.commit} has already been called raises an
-        L{AlreadyFinishedError}.
-        """
-        self.calendarObjectUnderTest()
-        txn = self.lastTransaction
-        self.commit()
-        self.assertRaises(AlreadyFinishedError, txn.commit)
-        self.assertRaises(AlreadyFinishedError, txn.abort)
-
-
-    def test_dontLeakCalendars(self):
-        """
-        Calendars in one user's calendar home should not show up in another
-        user's calendar home.
-        """
-        home2 = self.transactionUnderTest().calendarHomeWithUID(
-            "home2", create=True)
-        self.assertIdentical(home2.calendarWithName("calendar_1"), None)
-
-
-    def test_dontLeakObjects(self):
-        """
-        Calendar objects in one user's calendar should not show up in another
-        user's via uid or name queries.
-        """
-        home1 = self.homeUnderTest()
-        home2 = self.transactionUnderTest().calendarHomeWithUID(
-            "home2", create=True)
-        calendar1 = home1.calendarWithName("calendar_1")
-        calendar2 = home2.calendarWithName("calendar")
-        objects = list(home2.calendarWithName("calendar").calendarObjects())
-        self.assertEquals(objects, [])
-        for resourceName in self.requirements['home1']['calendar_1'].keys():
-            obj = calendar1.calendarObjectWithName(resourceName)
-            self.assertIdentical(
-                calendar2.calendarObjectWithName(resourceName), None)
-            self.assertIdentical(
-                calendar2.calendarObjectWithUID(obj.uid()), None)
-
-
-
-class StubNotifierFactory(object):
-
-    """ For testing push notifications without an XMPP server """
-
-    def __init__(self):
-        self.reset()
-
-    def newNotifier(self, label="default", id=None):
-        return Notifier(self, label=label, id=id)
-
-    def send(self, op, id):
-        self.history.append((op, id))
-
-    def reset(self):
-        self.history = []

Copied: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py (from rev 6185, CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/common.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,1202 @@
+# -*- test-case-name: txdav.caldav.datastore -*-
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Tests for common calendar store API functions.
+"""
+
+from zope.interface.verify import verifyObject
+from zope.interface.exceptions import (
+    BrokenMethodImplementation, DoesNotImplement)
+
+from twisted.internet.defer import Deferred, inlineCallbacks
+from twisted.internet.protocol import Protocol
+
+from txdav.idav import IPropertyStore, IDataStore, AlreadyFinishedError
+from txdav.base.propertystore.base import PropertyName
+
+from txdav.common.icommondatastore import HomeChildNameAlreadyExistsError, \
+    ICommonTransaction
+from txdav.common.icommondatastore import InvalidObjectResourceError
+from txdav.common.icommondatastore import NoSuchHomeChildError
+from txdav.common.icommondatastore import NoSuchObjectResourceError
+from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
+from txdav.common.inotifications import INotificationObject
+
+from txdav.caldav.icalendarstore import (
+    ICalendarObject, ICalendarHome,
+    ICalendar, IAttachment, ICalendarTransaction)
+
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.web2.dav import davxml
+from twext.web2.http_headers import MimeType
+from twext.web2.dav.element.base import WebDAVUnknownElement
+from twext.python.vcomponent import VComponent
+
+from twistedcaldav.notify import Notifier
+from twistedcaldav.customxml import InviteNotification, InviteSummary
+
+storePath = FilePath(__file__).parent().child("calendar_store")
+
+homeRoot = storePath.child("ho").child("me").child("home1")
+
+cal1Root = homeRoot.child("calendar_1")
+
+calendar1_objectNames = [
+    "1.ics",
+    "2.ics",
+    "3.ics",
+]
+
+
+home1_calendarNames = [
+    "calendar_1",
+    "calendar_2",
+    "calendar_empty",
+]
+
+
+event4_text = (
+    "BEGIN:VCALENDAR\r\n"
+      "VERSION:2.0\r\n"
+      "PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
+      "CALSCALE:GREGORIAN\r\n"
+      "BEGIN:VTIMEZONE\r\n"
+        "TZID:US/Pacific\r\n"
+        "BEGIN:DAYLIGHT\r\n"
+          "TZOFFSETFROM:-0800\r\n"
+          "RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\n"
+          "DTSTART:20070311T020000\r\n"
+          "TZNAME:PDT\r\n"
+          "TZOFFSETTO:-0700\r\n"
+        "END:DAYLIGHT\r\n"
+        "BEGIN:STANDARD\r\n"
+          "TZOFFSETFROM:-0700\r\n"
+          "RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\n"
+          "DTSTART:20071104T020000\r\n"
+          "TZNAME:PST\r\n"
+          "TZOFFSETTO:-0800\r\n"
+        "END:STANDARD\r\n"
+      "END:VTIMEZONE\r\n"
+      "BEGIN:VEVENT\r\n"
+        "CREATED:20100203T013849Z\r\n"
+        "UID:uid4\r\n"
+        "DTEND;TZID=US/Pacific:20100207T173000\r\n"
+        "TRANSP:OPAQUE\r\n"
+        "SUMMARY:New Event\r\n"
+        "DTSTART;TZID=US/Pacific:20100207T170000\r\n"
+        "DTSTAMP:20100203T013909Z\r\n"
+        "SEQUENCE:3\r\n"
+        "BEGIN:VALARM\r\n"
+          "X-WR-ALARMUID:1377CCC7-F85C-4610-8583-9513D4B364E1\r\n"
+          "TRIGGER:-PT20M\r\n"
+          "ATTACH;VALUE=URI:Basso\r\n"
+          "ACTION:AUDIO\r\n"
+        "END:VALARM\r\n"
+      "END:VEVENT\r\n"
+    "END:VCALENDAR\r\n"
+)
+
+
+
+event4notCalDAV_text = (
+    "BEGIN:VCALENDAR\r\n"
+      "VERSION:2.0\r\n"
+      "PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
+      "CALSCALE:GREGORIAN\r\n"
+      "BEGIN:VEVENT\r\n"
+        "CREATED:20100203T013849Z\r\n"
+        "UID:4\r\n"
+        "DTEND;TZID=US/Pacific:20100207T173000\r\n" # TZID without VTIMEZONE
+        "TRANSP:OPAQUE\r\n"
+        "SUMMARY:New Event\r\n"
+        "DTSTART;TZID=US/Pacific:20100207T170000\r\n"
+        "DTSTAMP:20100203T013909Z\r\n"
+        "SEQUENCE:3\r\n"
+        "BEGIN:VALARM\r\n"
+          "X-WR-ALARMUID:1377CCC7-F85C-4610-8583-9513D4B364E1\r\n"
+          "TRIGGER:-PT20M\r\n"
+          "ATTACH;VALUE=URI:Basso\r\n"
+          "ACTION:AUDIO\r\n"
+        "END:VALARM\r\n"
+      "END:VEVENT\r\n"
+    "END:VCALENDAR\r\n"
+)
+
+
+
+event1modified_text = event4_text.replace(
+    "\r\nUID:uid4\r\n",
+    "\r\nUID:uid1\r\n"
+)
+
+
+
+def assertProvides(testCase, interface, provider):
+    """
+    Verify that C{provider} properly provides C{interface}
+
+    @type interface: L{zope.interface.Interface}
+    @type provider: C{provider}
+    """
+    try:
+        verifyObject(interface, provider)
+    except BrokenMethodImplementation, e:
+        testCase.fail(e)
+    except DoesNotImplement, e:
+        testCase.fail("%r does not provide %s.%s" %
+                      (provider, interface.__module__, interface.getName()))
+
+
+class CommonTests(object):
+    """
+    Tests for common functionality of interfaces defined in
+    L{txdav.caldav.icalendarstore}.
+    """
+
+    requirements = {
+        "home1": {
+            "calendar_1": {
+                "1.ics": cal1Root.child("1.ics").getContent(),
+                "2.ics": cal1Root.child("2.ics").getContent(),
+                "3.ics": cal1Root.child("3.ics").getContent()
+            },
+            "calendar_2": {},
+            "calendar_empty": {},
+            "not_a_calendar": None
+        },
+        "not_a_home": None
+    }
+
+    def storeUnderTest(self):
+        """
+        Subclasses must override this to return an L{ICommonDataStore} provider
+        which adheres to the structure detailed by L{CommonTests.requirements}.
+        This attribute is a dict of dict of dicts; the outermost layer
+        representing UIDs mapping to calendar homes, then calendar names mapping
+        to calendar collections, and finally calendar object names mapping to
+        calendar object text.
+        """
+        raise NotImplementedError()
+
+
+    lastTransaction = None
+    savedStore = None
+
+    def transactionUnderTest(self):
+        """
+        Create a transaction from C{storeUnderTest} and save it as
+        C[lastTransaction}.  Also makes sure to use the same store, saving the
+        value from C{storeUnderTest}.
+        """
+        if self.lastTransaction is not None:
+            return self.lastTransaction
+        if self.savedStore is None:
+            self.savedStore = self.storeUnderTest()
+        txn = self.lastTransaction = self.savedStore.newTransaction(self.id())
+        return txn
+
+
+    def commit(self):
+        """
+        Commit the last transaction created from C{transactionUnderTest}, and
+        clear it.
+        """
+        self.lastTransaction.commit()
+        self.lastTransaction = None
+
+
+    def abort(self):
+        """
+        Abort the last transaction created from C[transactionUnderTest}, and
+        clear it.
+        """
+        self.lastTransaction.abort()
+        self.lastTransaction = None
+
+
+    def setUp(self):
+        self.notifierFactory = StubNotifierFactory()
+
+
+    def tearDown(self):
+        if self.lastTransaction is not None:
+            self.commit()
+
+
+    def homeUnderTest(self):
+        """
+        Get the calendar home detailed by C{requirements['home1']}.
+        """
+        return self.transactionUnderTest().calendarHomeWithUID("home1")
+
+
+    def calendarUnderTest(self):
+        """
+        Get the calendar detailed by C{requirements['home1']['calendar_1']}.
+        """
+        return self.homeUnderTest().calendarWithName("calendar_1")
+
+
+    def calendarObjectUnderTest(self):
+        """
+        Get the calendar detailed by
+        C{requirements['home1']['calendar_1']['1.ics']}.
+        """
+        return self.calendarUnderTest().calendarObjectWithName("1.ics")
+
+
+    assertProvides = assertProvides
+
+    def test_calendarStoreProvides(self):
+        """
+        The calendar store provides L{IDataStore} and its required attributes.
+        """
+        calendarStore = self.storeUnderTest()
+        self.assertProvides(IDataStore, calendarStore)
+
+
+    def test_transactionProvides(self):
+        """
+        The transactions generated by the calendar store provide
+        L{ICommonStoreTransaction}, L{ICalendarTransaction}, and their
+        respectively required attributes.
+        """
+        txn = self.transactionUnderTest()
+        self.assertProvides(ICommonTransaction, txn)
+        self.assertProvides(ICalendarTransaction, txn)
+
+
+    def test_homeProvides(self):
+        """
+        The calendar homes generated by the calendar store provide
+        L{ICalendarHome} and its required attributes.
+        """
+        self.assertProvides(ICalendarHome, self.homeUnderTest())
+
+
+    def test_calendarProvides(self):
+        """
+        The calendars generated by the calendar store provide L{ICalendar} and
+        its required attributes.
+        """
+        self.assertProvides(ICalendar, self.calendarUnderTest())
+
+
+    def test_calendarObjectProvides(self):
+        """
+        The calendar objects generated by the calendar store provide
+        L{ICalendarObject} and its required attributes.
+        """
+        self.assertProvides(ICalendarObject, self.calendarObjectUnderTest())
+
+
+    def notificationUnderTest(self):
+        txn = self.transactionUnderTest()
+        notifications = txn.notificationsWithUID("home1")
+        inviteNotification = InviteNotification()
+        notifications.writeNotificationObject("abc", inviteNotification,
+            inviteNotification.toxml())
+        notificationObject = notifications.notificationObjectWithUID("abc")
+        return notificationObject
+
+
+    def test_notificationObjectProvides(self):
+        """
+        The objects retrieved from the notification home (the object returned
+        from L{notificationsWithUID}) provide L{INotificationObject}.
+        """
+        notificationObject = self.notificationUnderTest()
+        self.assertProvides(INotificationObject, notificationObject)
+
+
+    def test_replaceNotification(self):
+        """
+        L{INotificationCollection.writeNotificationObject} will silently
+        overwrite the notification object.
+        """
+        notifications = self.transactionUnderTest().notificationsWithUID(
+            "home1"
+        )
+        inviteNotification = InviteNotification()
+        notifications.writeNotificationObject("abc", inviteNotification,
+            inviteNotification.toxml())
+        inviteNotification2 = InviteNotification(InviteSummary("a summary"))
+        notifications.writeNotificationObject(
+            "abc", inviteNotification, inviteNotification2.toxml())
+        abc = notifications.notificationObjectWithUID("abc")
+        self.assertEquals(abc.xmldata(), inviteNotification2.toxml())
+
+
+    def test_notificationObjectModified(self):
+        """
+        The objects retrieved from the notification home have a C{modified}
+        method which returns the timestamp of their last modification.
+        """
+        notification = self.notificationUnderTest()
+        self.assertIsInstance(notification.modified(), int)
+
+
+    def test_notificationObjectParent(self):
+        """
+        L{INotificationObject.notificationCollection} returns the
+        L{INotificationCollection} that the object was retrieved from.
+        """
+        txn = self.transactionUnderTest()
+        collection = txn.notificationsWithUID("home1")
+        notification = self.notificationUnderTest()
+        self.assertIdentical(collection, notification.notificationCollection())
+
+
+    def test_notifierID(self):
+        home = self.homeUnderTest()
+        self.assertEquals(home.notifierID(), "home1")
+        calendar = home.calendarWithName("calendar_1")
+        self.assertEquals(calendar.notifierID(), "home1")
+        self.assertEquals(calendar.notifierID(label="collection"), "home1/calendar_1")
+
+
+    def test_calendarHomeWithUID_exists(self):
+        """
+        Finding an existing calendar home by UID results in an object that
+        provides L{ICalendarHome} and has a C{uid()} method that returns the
+        same value that was passed in.
+        """
+        calendarHome = (self.transactionUnderTest()
+                        .calendarHomeWithUID("home1"))
+        self.assertEquals(calendarHome.uid(), "home1")
+        self.assertProvides(ICalendarHome, calendarHome)
+
+
+    def test_calendarHomeWithUID_absent(self):
+        """
+        L{ICommonStoreTransaction.calendarHomeWithUID} should return C{None}
+        when asked for a non-existent calendar home.
+        """
+        txn = self.transactionUnderTest()
+        self.assertEquals(txn.calendarHomeWithUID("xyzzy"), None)
+
+
+    def test_calendarWithName_exists(self):
+        """
+        L{ICalendarHome.calendarWithName} returns an L{ICalendar} provider,
+        whose name matches the one passed in.
+        """
+        home = self.homeUnderTest()
+        for name in home1_calendarNames:
+            calendar = home.calendarWithName(name)
+            if calendar is None:
+                self.fail("calendar %r didn't exist" % (name,))
+            self.assertProvides(ICalendar, calendar)
+            self.assertEquals(calendar.name(), name)
+
+
+    def test_calendarRename(self):
+        """
+        L{ICalendar.rename} changes the name of the L{ICalendar}.
+        """
+        home = self.homeUnderTest()
+        calendar = home.calendarWithName("calendar_1")
+        calendar.rename("some_other_name")
+        def positiveAssertions():
+            self.assertEquals(calendar.name(), "some_other_name")
+            self.assertEquals(calendar, home.calendarWithName("some_other_name"))
+            self.assertEquals(None, home.calendarWithName("calendar_1"))
+        positiveAssertions()
+        self.commit()
+        home = self.homeUnderTest()
+        calendar = home.calendarWithName("some_other_name")
+        positiveAssertions()
+        # FIXME: revert
+        # FIXME: test for multiple renames
+        # FIXME: test for conflicting renames (a->b, c->a in the same txn)
+
+
+    def test_calendarWithName_absent(self):
+        """
+        L{ICalendarHome.calendarWithName} returns C{None} for calendars which
+        do not exist.
+        """
+        self.assertEquals(self.homeUnderTest().calendarWithName("xyzzy"),
+                          None)
+
+
+    def test_createCalendarWithName_absent(self):
+        """
+        L{ICalendarHome.createCalendarWithName} creates a new L{ICalendar} that
+        can be retrieved with L{ICalendarHome.calendarWithName}.
+        """
+        home = self.homeUnderTest()
+        name = "new"
+        self.assertIdentical(home.calendarWithName(name), None)
+        home.createCalendarWithName(name)
+        self.assertNotIdentical(home.calendarWithName(name), None)
+        def checkProperties():
+            calendarProperties = home.calendarWithName(name).properties()
+            self.assertEquals(
+                calendarProperties[
+                    PropertyName.fromString(davxml.ResourceType.sname())
+                ],
+                davxml.ResourceType.calendar #@UndefinedVariable
+            )
+        checkProperties()
+
+        self.commit()
+
+        # Make sure notification fired after commit
+        self.assertEquals(self.notifierFactory.history, [("update", "home1")])
+
+        # Make sure it's available in a new transaction; i.e. test the commit.
+        home = self.homeUnderTest()
+        self.assertNotIdentical(home.calendarWithName(name), None)
+
+        # Sanity check: are the properties actually persisted?  Check in
+        # subsequent transaction.
+        checkProperties()
+
+        # FIXME: no independent testing of the property store's persistence
+        # right now
+
+
+    def test_createCalendarWithName_exists(self):
+        """
+        L{ICalendarHome.createCalendarWithName} raises
+        L{CalendarAlreadyExistsError} when the name conflicts with an already-
+        existing 
+        """
+        for name in home1_calendarNames:
+            self.assertRaises(
+                HomeChildNameAlreadyExistsError,
+                self.homeUnderTest().createCalendarWithName, name
+            )
+
+
+    def test_removeCalendarWithName_exists(self):
+        """
+        L{ICalendarHome.removeCalendarWithName} removes a calendar that already
+        exists.
+        """
+        home = self.homeUnderTest()
+
+        # FIXME: test transactions
+        for name in home1_calendarNames:
+            self.assertNotIdentical(home.calendarWithName(name), None)
+            home.removeCalendarWithName(name)
+            self.assertEquals(home.calendarWithName(name), None)
+
+        self.commit()
+
+        # Make sure notification fired after commit
+        self.assertEquals(
+            self.notifierFactory.history,
+            [("update", "home1"), ("update", "home1"), ("update", "home1")]
+        )
+
+
+    def test_removeCalendarWithName_absent(self):
+        """
+        Attempt to remove an non-existing calendar should raise.
+        """
+        home = self.homeUnderTest()
+        self.assertRaises(NoSuchHomeChildError,
+                          home.removeCalendarWithName, "xyzzy")
+
+
+    def test_calendarObjects(self):
+        """
+        L{ICalendar.calendarObjects} will enumerate the calendar objects present
+        in the filesystem, in name order, but skip those with hidden names.
+        """
+        calendar1 = self.calendarUnderTest()
+        calendarObjects = list(calendar1.calendarObjects())
+
+        for calendarObject in calendarObjects:
+            self.assertProvides(ICalendarObject, calendarObject)
+            self.assertEquals(
+                calendar1.calendarObjectWithName(calendarObject.name()),
+                calendarObject
+            )
+
+        self.assertEquals(
+            set(list(o.name() for o in calendarObjects)),
+            set(calendar1_objectNames)
+        )
+
+
+    def test_calendarObjectsWithRemovedObject(self):
+        """
+        L{ICalendar.calendarObjects} skips those objects which have been
+        removed by L{Calendar.removeCalendarObjectWithName} in the same
+        transaction, even if it has not yet been committed.
+        """
+        calendar1 = self.calendarUnderTest()
+        calendar1.removeCalendarObjectWithName("2.ics")
+        calendarObjects = list(calendar1.calendarObjects())
+        self.assertEquals(set(o.name() for o in calendarObjects),
+                          set(calendar1_objectNames) - set(["2.ics"]))
+
+
+    def test_ownerCalendarHome(self):
+        """
+        L{ICalendar.ownerCalendarHome} should match the home UID.
+        """
+        self.assertEquals(
+            self.calendarUnderTest().ownerCalendarHome().uid(),
+            self.homeUnderTest().uid()
+        )
+
+
+    def test_calendarObjectWithName_exists(self):
+        """
+        L{ICalendar.calendarObjectWithName} returns an L{ICalendarObject}
+        provider for calendars which already exist.
+        """
+        calendar1 = self.calendarUnderTest()
+        for name in calendar1_objectNames:
+            calendarObject = calendar1.calendarObjectWithName(name)
+            self.assertProvides(ICalendarObject, calendarObject)
+            self.assertEquals(calendarObject.name(), name)
+            # FIXME: add more tests based on CommonTests.requirements
+
+
+    def test_calendarObjectWithName_absent(self):
+        """
+        L{ICalendar.calendarObjectWithName} returns C{None} for calendars which
+        don't exist.
+        """
+        calendar1 = self.calendarUnderTest()
+        self.assertEquals(calendar1.calendarObjectWithName("xyzzy"), None)
+
+
+    def test_removeCalendarObjectWithUID_exists(self):
+        """
+        Remove an existing calendar object.
+        """
+        calendar = self.calendarUnderTest()
+        for name in calendar1_objectNames:
+            uid = (u'uid' + name.rstrip(".ics"))
+            self.assertNotIdentical(calendar.calendarObjectWithUID(uid),
+                                    None)
+            calendar.removeCalendarObjectWithUID(uid)
+            self.assertEquals(
+                calendar.calendarObjectWithUID(uid),
+                None
+            )
+            self.assertEquals(
+                calendar.calendarObjectWithName(name),
+                None
+            )
+
+        # Make sure notifications are fired after commit
+        self.commit()
+        self.assertEquals(
+            self.notifierFactory.history,
+            [
+                ("update", "home1"),
+                ("update", "home1/calendar_1"),
+                ("update", "home1"),
+                ("update", "home1/calendar_1"),
+                ("update", "home1"),
+                ("update", "home1/calendar_1"),
+            ]
+        )
+
+    def test_removeCalendarObjectWithName_exists(self):
+        """
+        Remove an existing calendar object.
+        """
+        calendar = self.calendarUnderTest()
+        for name in calendar1_objectNames:
+            self.assertNotIdentical(
+                calendar.calendarObjectWithName(name), None
+            )
+            calendar.removeCalendarObjectWithName(name)
+            self.assertIdentical(
+                calendar.calendarObjectWithName(name), None
+            )
+
+
+    def test_removeCalendarObjectWithName_absent(self):
+        """
+        Attempt to remove an non-existing calendar object should raise.
+        """
+        calendar = self.calendarUnderTest()
+        self.assertRaises(
+            NoSuchObjectResourceError,
+            calendar.removeCalendarObjectWithName, "xyzzy"
+        )
+
+
+    def test_calendarName(self):
+        """
+        L{Calendar.name} reflects the name of the calendar.
+        """
+        self.assertEquals(self.calendarUnderTest().name(), "calendar_1")
+
+
+    def test_calendarObjectName(self):
+        """
+        L{ICalendarObject.name} reflects the name of the calendar object.
+        """
+        self.assertEquals(self.calendarObjectUnderTest().name(), "1.ics")
+
+
+    def test_component(self):
+        """
+        L{ICalendarObject.component} returns a L{VComponent} describing the
+        calendar data underlying that calendar object.
+        """
+        component = self.calendarObjectUnderTest().component()
+
+        self.failUnless(
+            isinstance(component, VComponent),
+            component
+        )
+
+        self.assertEquals(component.name(), "VCALENDAR")
+        self.assertEquals(component.mainType(), "VEVENT")
+        self.assertEquals(component.resourceUID(), "uid1")
+
+
+    def test_iCalendarText(self):
+        """
+        L{ICalendarObject.iCalendarText} returns a C{str} describing the same
+        data provided by L{ICalendarObject.component}.
+        """
+        text = self.calendarObjectUnderTest().iCalendarText()
+        self.assertIsInstance(text, str)
+        self.failUnless(text.startswith("BEGIN:VCALENDAR\r\n"))
+        self.assertIn("\r\nUID:uid1\r\n", text)
+        self.failUnless(text.endswith("\r\nEND:VCALENDAR\r\n"))
+
+
+    def test_calendarObjectUID(self):
+        """
+        L{ICalendarObject.uid} returns a C{str} describing the C{UID} property
+        of the calendar object's component.
+        """
+        self.assertEquals(self.calendarObjectUnderTest().uid(), "uid1")
+
+
+    def test_organizer(self):
+        """
+        L{ICalendarObject.organizer} returns a C{str} describing the calendar
+        user address of the C{ORGANIZER} property of the calendar object's
+        component.
+        """
+        self.assertEquals(
+            self.calendarObjectUnderTest().organizer(),
+            "mailto:wsanchez at apple.com"
+        )
+
+
+    def test_calendarObjectWithUID_absent(self):
+        """
+        L{ICalendar.calendarObjectWithUID} returns C{None} for calendars which
+        don't exist.
+        """
+        calendar1 = self.calendarUnderTest()
+        self.assertEquals(calendar1.calendarObjectWithUID("xyzzy"), None)
+
+
+    def test_calendars(self):
+        """
+        L{ICalendarHome.calendars} returns an iterable of L{ICalendar}
+        providers, which are consistent with the results from
+        L{ICalendar.calendarWithName}.
+        """
+        # Add a dot directory to make sure we don't find it
+        # self.home1._path.child(".foo").createDirectory()
+        home = self.homeUnderTest()
+        calendars = list(home.calendars())
+
+        for calendar in calendars:
+            self.assertProvides(ICalendar, calendar)
+            self.assertEquals(calendar,
+                              home.calendarWithName(calendar.name()))
+
+        self.assertEquals(
+            set(c.name() for c in calendars),
+            set(home1_calendarNames)
+        )
+
+
+    def test_calendarsAfterAddCalendar(self):
+        """
+        L{ICalendarHome.calendars} includes calendars recently added with
+        L{ICalendarHome.createCalendarWithName}.
+        """
+        home = self.homeUnderTest()
+        before = set(x.name() for x in home.calendars())
+        home.createCalendarWithName("new-name")
+        after = set(x.name() for x in home.calendars())
+        self.assertEquals(before | set(['new-name']), after)
+
+
+    def test_createCalendarObjectWithName_absent(self):
+        """
+        L{ICalendar.createCalendarObjectWithName} creates a new
+        L{ICalendarObject}.
+        """
+        calendar1 = self.calendarUnderTest()
+        name = "4.ics"
+        self.assertIdentical(calendar1.calendarObjectWithName(name), None)
+        component = VComponent.fromString(event4_text)
+        calendar1.createCalendarObjectWithName(name, component)
+
+        calendarObject = calendar1.calendarObjectWithName(name)
+        self.assertEquals(calendarObject.component(), component)
+
+        self.commit()
+
+        # Make sure notifications fire after commit
+        self.assertEquals(
+            self.notifierFactory.history,
+            [
+                ("update", "home1"),
+                ("update", "home1/calendar_1"),
+            ]
+        )
+
+
+    def test_createCalendarObjectWithName_exists(self):
+        """
+        L{ICalendar.createCalendarObjectWithName} raises
+        L{CalendarObjectNameAlreadyExistsError} if a calendar object with the
+        given name already exists in that calendar.
+        """
+        cal = self.calendarUnderTest()
+        comp = VComponent.fromString(event4_text)
+        self.assertRaises(
+            ObjectResourceNameAlreadyExistsError,
+            cal.createCalendarObjectWithName,
+            "1.ics", comp
+        )
+
+
+    def test_createCalendarObjectWithName_invalid(self):
+        """
+        L{ICalendar.createCalendarObjectWithName} raises
+        L{InvalidCalendarComponentError} if presented with invalid iCalendar
+        text.
+        """
+        self.assertRaises(
+            InvalidObjectResourceError,
+            self.calendarUnderTest().createCalendarObjectWithName,
+            "new", VComponent.fromString(event4notCalDAV_text)
+        )
+
+
+    def test_setComponent_invalid(self):
+        """
+        L{ICalendarObject.setComponent} raises L{InvalidICalendarDataError} if
+        presented with invalid iCalendar text.
+        """
+        calendarObject = self.calendarObjectUnderTest()
+        self.assertRaises(
+            InvalidObjectResourceError,
+            calendarObject.setComponent,
+            VComponent.fromString(event4notCalDAV_text)
+        )
+
+
+    def test_setComponent_uidchanged(self):
+        """
+        L{ICalendarObject.setComponent} raises L{InvalidCalendarComponentError}
+        when given a L{VComponent} whose UID does not match its existing UID.
+        """
+        calendar1 = self.calendarUnderTest()
+        component = VComponent.fromString(event4_text)
+        calendarObject = calendar1.calendarObjectWithName("1.ics")
+        self.assertRaises(
+            InvalidObjectResourceError,
+            calendarObject.setComponent, component
+        )
+
+
+    def test_calendarHomeWithUID_create(self):
+        """
+        L{ICommonStoreTransaction.calendarHomeWithUID} with C{create=True}
+        will create a calendar home that doesn't exist yet.
+        """
+        txn = self.transactionUnderTest()
+        noHomeUID = "xyzzy"
+        calendarHome = txn.calendarHomeWithUID(
+            noHomeUID,
+            create=True
+        )
+        def readOtherTxn():
+            otherTxn = self.savedStore.newTransaction(self.id() + "other txn")
+            self.addCleanup(otherTxn.commit)
+            return otherTxn.calendarHomeWithUID(noHomeUID)
+        self.assertProvides(ICalendarHome, calendarHome)
+        # Default calendar should be automatically created.
+        self.assertProvides(ICalendar,
+                            calendarHome.calendarWithName("calendar"))
+        # A concurrent transaction shouldn't be able to read it yet:
+        self.assertIdentical(readOtherTxn(), None)
+        self.commit()
+        # But once it's committed, other transactions should see it.
+        self.assertProvides(ICalendarHome, readOtherTxn())
+
+
+    def test_setComponent(self):
+        """
+        L{CalendarObject.setComponent} changes the result of
+        L{CalendarObject.component} within the same transaction.
+        """
+        component = VComponent.fromString(event1modified_text)
+
+        calendar1 = self.calendarUnderTest()
+        calendarObject = calendar1.calendarObjectWithName("1.ics")
+        oldComponent = calendarObject.component()
+        self.assertNotEqual(component, oldComponent)
+        calendarObject.setComponent(component)
+        self.assertEquals(calendarObject.component(), component)
+
+        # Also check a new instance
+        calendarObject = calendar1.calendarObjectWithName("1.ics")
+        self.assertEquals(calendarObject.component(), component)
+
+        self.commit()
+
+        # Make sure notification fired after commit
+        self.assertEquals(
+            self.notifierFactory.history,
+            [
+                ("update", "home1"),
+                ("update", "home1/calendar_1"),
+            ]
+        )
+
+
+    def checkPropertiesMethod(self, thunk):
+        """
+        Verify that the given object has a properties method that returns an
+        L{IPropertyStore}.
+        """
+        properties = thunk.properties()
+        self.assertProvides(IPropertyStore, properties)
+
+
+    def test_homeProperties(self):
+        """
+        L{ICalendarHome.properties} returns a property store.
+        """
+        self.checkPropertiesMethod(self.homeUnderTest())
+
+
+    def test_calendarProperties(self):
+        """
+        L{ICalendar.properties} returns a property store.
+        """
+        self.checkPropertiesMethod(self.calendarUnderTest())
+
+
+    def test_calendarObjectProperties(self):
+        """
+        L{ICalendarObject.properties} returns a property store.
+        """
+        self.checkPropertiesMethod(self.calendarObjectUnderTest())
+
+
+    def test_newCalendarObjectProperties(self):
+        """
+        L{ICalendarObject.properties} returns an empty property store for a
+        calendar object which has been created but not committed.
+        """
+        calendar = self.calendarUnderTest()
+        calendar.createCalendarObjectWithName(
+            "4.ics", VComponent.fromString(event4_text)
+        )
+        newEvent = calendar.calendarObjectWithName("4.ics")
+        self.assertEquals(newEvent.properties().items(), [])
+
+
+    def test_setComponentPreservesProperties(self):
+        """
+        L{ICalendarObject.setComponent} preserves properties.
+
+        (Some implementations must go to extra trouble to provide this
+        behavior; for example, file storage must copy extended attributes from
+        the existing file to the temporary file replacing it.)
+        """
+        propertyName = PropertyName("http://example.com/ns", "example")
+        propertyContent = WebDAVUnknownElement("sample content")
+        propertyContent.name = propertyName.name
+        propertyContent.namespace = propertyName.namespace
+
+        self.calendarObjectUnderTest().properties()[
+            propertyName] = propertyContent
+        self.commit()
+        # Sanity check; are properties even readable in a separate transaction?
+        # Should probably be a separate test.
+        self.assertEquals(
+            self.calendarObjectUnderTest().properties()[propertyName],
+            propertyContent)
+        obj = self.calendarObjectUnderTest()
+        event1_text = obj.iCalendarText()
+        event1_text_withDifferentSubject = event1_text.replace(
+            "SUMMARY:CalDAV protocol updates",
+            "SUMMARY:Changed"
+        )
+        # Sanity check; make sure the test has the right idea of the subject.
+        self.assertNotEquals(event1_text, event1_text_withDifferentSubject)
+        newComponent = VComponent.fromString(event1_text_withDifferentSubject)
+        obj.setComponent(newComponent)
+
+        # Putting everything into a separate transaction to account for any
+        # caching that may take place.
+        self.commit()
+        self.assertEquals(
+            self.calendarObjectUnderTest().properties()[propertyName],
+            propertyContent
+        )
+
+
+    eventWithDropbox = "\r\n".join("""
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VTIMEZONE
+LAST-MODIFIED:20040110T032845Z
+TZID:US/Eastern
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART;TZID=US/Eastern:20060101T100000
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER:user01
+ATTENDEE;PARTSTAT=ACCEPTED:user01
+ATTACH;VALUE=URI:/calendars/users/home1/some-dropbox-id/some-dropbox-id/caldavd.plist
+X-APPLE-DROPBOX:/calendars/users/home1/dropbox/some-dropbox-id
+END:VEVENT
+END:VCALENDAR
+    """.strip().split("\n"))
+
+    def test_dropboxID(self):
+        """
+        L{ICalendarObject.dropboxID} should synthesize its dropbox from the X
+        -APPLE-DROPBOX property, if available.
+        """
+        cal = self.calendarUnderTest()
+        cal.createCalendarObjectWithName("drop.ics", VComponent.fromString(
+                self.eventWithDropbox
+            )
+        )
+        obj = cal.calendarObjectWithName("drop.ics")
+        self.assertEquals(obj.dropboxID(), "some-dropbox-id")
+
+
+    def test_indexByDropboxProperty(self):
+        """
+        L{ICalendarHome.calendarObjectWithDropboxID} will return a calendar
+        object in the calendar home with the given final segment in its C{X
+        -APPLE-DROPBOX} property URI.
+        """
+        objName = "with-dropbox.ics"
+        cal = self.calendarUnderTest()
+        cal.createCalendarObjectWithName(
+            objName, VComponent.fromString(
+                self.eventWithDropbox
+            )
+        )
+        self.commit()
+        home = self.homeUnderTest()
+        cal = self.calendarUnderTest()
+        fromName = cal.calendarObjectWithName(objName)
+        fromDropbox = home.calendarObjectWithDropboxID("some-dropbox-id")
+        self.assertEquals(fromName, fromDropbox)
+
+
+    @inlineCallbacks
+    def createAttachmentTest(self, refresh):
+        """
+        Common logic for attachment-creation tests.
+        """
+        obj = self.calendarObjectUnderTest()
+        t = obj.createAttachmentWithName("new.attachment", MimeType("text", "x-fixture"))
+        t.write("new attachment")
+        t.write(" text")
+        t.loseConnection()
+        obj = refresh(obj)
+        class CaptureProtocol(Protocol):
+            buf = ''
+            def dataReceived(self, data):
+                self.buf += data
+            def connectionLost(self, reason):
+                self.deferred.callback(self.buf)
+        capture = CaptureProtocol()
+        capture.deferred = Deferred()
+        attachment = obj.attachmentWithName("new.attachment")
+        self.assertProvides(IAttachment, attachment)
+        attachment.retrieve(capture)
+        data = yield capture.deferred
+        self.assertEquals(data, "new attachment text")
+        contentType = attachment.contentType()
+        self.assertIsInstance(contentType, MimeType)
+        self.assertEquals(contentType, MimeType("text", "x-fixture"))
+        self.assertEquals(attachment.md5(), '50a9f27aeed9247a0833f30a631f1858')
+        self.assertEquals(
+            [attachment.name() for attachment in obj.attachments()],
+            ['new.attachment']
+        )
+
+
+    def test_createAttachment(self):
+        """
+        L{ICalendarObject.createAttachmentWithName} will store an
+        L{IAttachment} object that can be retrieved by
+        L{ICalendarObject.attachmentWithName}.
+        """
+        return self.createAttachmentTest(lambda x: x)
+
+
+    def test_createAttachmentCommit(self):
+        """
+        L{ICalendarObject.createAttachmentWithName} will store an
+        L{IAttachment} object that can be retrieved by
+        L{ICalendarObject.attachmentWithName} in subsequent transactions.
+        """
+        def refresh(obj):
+            self.commit()
+            return self.calendarObjectUnderTest()
+        return self.createAttachmentTest(refresh)
+
+
+    def test_removeAttachmentWithName(self, refresh=lambda x:x):
+        """
+        L{ICalendarObject.removeAttachmentWithName} will remove the calendar
+        object with the given name.
+        """
+        def deleteIt(ignored):
+            obj = self.calendarObjectUnderTest()
+            obj.removeAttachmentWithName("new.attachment")
+            obj = refresh(obj)
+            self.assertIdentical(
+                None, obj.attachmentWithName("new.attachment")
+            )
+            self.assertEquals(list(obj.attachments()), [])
+        return self.test_createAttachmentCommit().addCallback(deleteIt)
+
+
+    def test_removeAttachmentWithNameCommit(self):
+        """
+        L{ICalendarObject.removeAttachmentWithName} will remove the calendar
+        object with the given name.  (After commit, it will still be gone.)
+        """
+        def refresh(obj):
+            self.commit()
+            return self.calendarObjectUnderTest()
+        return self.test_removeAttachmentWithName(refresh)
+
+
+    def test_noDropboxCalendar(self):
+        """
+        L{ICalendarObject.createAttachmentWithName} may create a directory
+        named 'dropbox', but this should not be seen as a calendar by
+        L{ICalendarHome.calendarWithName} or L{ICalendarHome.calendars}.
+        """
+        obj = self.calendarObjectUnderTest()
+        t = obj.createAttachmentWithName("new.attachment", MimeType("text", "plain"))
+        t.write("new attachment text")
+        t.loseConnection()
+        self.commit()
+        self.assertEquals(self.homeUnderTest().calendarWithName("dropbox"),
+                          None)
+        self.assertEquals(
+            set([n.name() for n in self.homeUnderTest().calendars()]),
+            set(home1_calendarNames))
+
+
+    def test_finishedOnCommit(self):
+        """ 
+        Calling L{ITransaction.abort} or L{ITransaction.commit} after
+        L{ITransaction.commit} has already been called raises an
+        L{AlreadyFinishedError}.
+        """
+        self.calendarObjectUnderTest()
+        txn = self.lastTransaction
+        self.commit()
+        self.assertRaises(AlreadyFinishedError, txn.commit)
+        self.assertRaises(AlreadyFinishedError, txn.abort)
+
+
+    def test_dontLeakCalendars(self):
+        """
+        Calendars in one user's calendar home should not show up in another
+        user's calendar home.
+        """
+        home2 = self.transactionUnderTest().calendarHomeWithUID(
+            "home2", create=True)
+        self.assertIdentical(home2.calendarWithName("calendar_1"), None)
+
+
+    def test_dontLeakObjects(self):
+        """
+        Calendar objects in one user's calendar should not show up in another
+        user's via uid or name queries.
+        """
+        home1 = self.homeUnderTest()
+        home2 = self.transactionUnderTest().calendarHomeWithUID(
+            "home2", create=True)
+        calendar1 = home1.calendarWithName("calendar_1")
+        calendar2 = home2.calendarWithName("calendar")
+        objects = list(home2.calendarWithName("calendar").calendarObjects())
+        self.assertEquals(objects, [])
+        for resourceName in self.requirements['home1']['calendar_1'].keys():
+            obj = calendar1.calendarObjectWithName(resourceName)
+            self.assertIdentical(
+                calendar2.calendarObjectWithName(resourceName), None)
+            self.assertIdentical(
+                calendar2.calendarObjectWithUID(obj.uid()), None)
+
+
+
+class StubNotifierFactory(object):
+
+    """ For testing push notifications without an XMPP server """
+
+    def __init__(self):
+        self.reset()
+
+    def newNotifier(self, label="default", id=None):
+        return Notifier(self, label=label, id=id)
+
+    def send(self, op, id):
+        self.history.append((op, id))
+
+    def reset(self):
+        self.history = []

Modified: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/test_file.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_file.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -33,10 +33,10 @@
 from txdav.common.icommondatastore import NoSuchHomeChildError
 from txdav.common.icommondatastore import NoSuchObjectResourceError
 
-from txcaldav.calendarstore.file import CalendarStore, CalendarHome
-from txcaldav.calendarstore.file import Calendar, CalendarObject
+from txdav.caldav.datastore.file import CalendarStore, CalendarHome
+from txdav.caldav.datastore.file import Calendar, CalendarObject
 
-from txcaldav.calendarstore.test.common import (
+from txdav.caldav.datastore.test.common import (
     CommonTests, event4_text, event1modified_text)
 
 storePath = FilePath(__file__).parent().child("calendar_store")

Deleted: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_postgres.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/test_postgres.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_postgres.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,243 +0,0 @@
-##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-Tests for txcaldav.calendarstore.postgres, mostly based on
-L{txcaldav.calendarstore.test.common}.
-"""
-
-import gc
-
-from txcaldav.calendarstore.test.common import CommonTests as CalendarCommonTests
-from txcarddav.addressbookstore.test.common import CommonTests as AddressBookCommonTests
-from txdav.common.icommondatastore import NoSuchHomeChildError
-
-from twisted.trial import unittest
-from txdav.datastore.subpostgres import (PostgresService,
-    DiagnosticConnectionWrapper)
-from txcaldav.calendarstore.postgres import PostgresStore, v1_schema
-from twisted.internet.defer import Deferred, inlineCallbacks, succeed
-from twisted.internet import reactor
-from twext.python.filepath import CachingFilePath
-from twext.python.vcomponent import VComponent
-from twistedcaldav.vcard import Component as VCard
-from twisted.internet.task import deferLater
-from twisted.python import log
-
-
-
-def allInstancesOf(cls):
-    for o in gc.get_referrers(cls):
-        if isinstance(o, cls):
-            yield o
-
-
-
-def dumpConnectionStatus():
-    print '+++ ALL CONNECTIONS +++'
-    for connection in allInstancesOf(DiagnosticConnectionWrapper):
-        print connection.label, connection.state
-    print '--- CONNECTIONS END ---'
-
-
-
-class StoreBuilder(object):
-    """
-    Test-fixture-builder which can construct a PostgresStore.
-    """
-    sharedService = None
-    currentTestID = None
-
-    SHARED_DB_PATH = "../_test_postgres_db"
-
-    def buildStore(self, testCase, notifierFactory):
-        """
-        Do the necessary work to build a store for a particular test case.
-
-        @return: a L{Deferred} which fires with an L{IDataStore}.
-        """
-        currentTestID = testCase.id()
-        dbRoot = CachingFilePath(self.SHARED_DB_PATH)
-        if self.sharedService is None:
-            ready = Deferred()
-            def getReady(connectionFactory):
-                attachmentRoot = dbRoot.child("attachments")
-                try:
-                    attachmentRoot.createDirectory()
-                except OSError:
-                    pass
-                try:
-                    self.store = PostgresStore(
-                        lambda label=None: connectionFactory(
-                            label or currentTestID
-                        ),
-                        notifierFactory,
-                        attachmentRoot
-                    )
-                except:
-                    ready.errback()
-                    raise
-                else:
-                    self.cleanDatabase(testCase)
-                    ready.callback(self.store)
-                return self.store
-            self.sharedService = PostgresService(
-                dbRoot, getReady, v1_schema, "caldav", resetSchema=True,
-                testMode=True
-            )
-            self.sharedService.startService()
-            def startStopping():
-                log.msg("Starting stopping.")
-                self.sharedService.unpauseMonitor()
-                return self.sharedService.stopService()
-            reactor.addSystemEventTrigger(#@UndefinedVariable
-                "before", "shutdown", startStopping)
-            result = ready
-        else:
-            self.store.notifierFactory = notifierFactory
-            self.cleanDatabase(testCase)
-            result = succeed(self.store)
-
-        def cleanUp():
-            # FIXME: clean up any leaked connections and report them with an
-            # immediate test failure.
-            def stopit():
-                self.sharedService.pauseMonitor()
-            return deferLater(reactor, 0.1, stopit)
-        testCase.addCleanup(cleanUp)
-        return result
-
-
-    def cleanDatabase(self, testCase):
-        cleanupConn = self.store.connectionFactory(
-            "%s schema-cleanup" % (testCase.id(),)
-        )
-        cursor = cleanupConn.cursor()
-        tables = ['INVITE',
-                  'RESOURCE_PROPERTY',
-                  'ATTACHMENT',
-                  'ADDRESSBOOK_OBJECT',
-                  'CALENDAR_OBJECT',
-                  'CALENDAR_BIND',
-                  'ADDRESSBOOK_BIND',
-                  'CALENDAR',
-                  'ADDRESSBOOK',
-                  'CALENDAR_HOME',
-                  'ADDRESSBOOK_HOME',
-                  'NOTIFICATION',
-                  'NOTIFICATION_HOME']
-        for table in tables:
-            try:
-                cursor.execute("delete from "+table)
-            except:
-                log.err()
-        cleanupConn.commit()
-        cleanupConn.close()
-
-
-
-theStoreBuilder = StoreBuilder()
-buildStore = theStoreBuilder.buildStore
-
-
-
-class CalendarSQLStorageTests(CalendarCommonTests, unittest.TestCase):
-    """
-    Calendar SQL storage tests.
-    """
-
-    @inlineCallbacks
-    def setUp(self):
-        super(CalendarSQLStorageTests, self).setUp()
-        self.calendarStore = yield buildStore(self, self.notifierFactory)
-        self.populate()
-
-
-    def populate(self):
-        populateTxn = self.calendarStore.newTransaction()
-        for homeUID in self.requirements:
-            calendars = self.requirements[homeUID]
-            if calendars is not None:
-                home = populateTxn.calendarHomeWithUID(homeUID, True)
-                # We don't want the default calendar to appear unless it's
-                # explicitly listed.
-                try:
-                    home.removeCalendarWithName("calendar")
-                except NoSuchHomeChildError:
-                    pass
-                for calendarName in calendars:
-                    calendarObjNames = calendars[calendarName]
-                    if calendarObjNames is not None:
-                        home.createCalendarWithName(calendarName)
-                        calendar = home.calendarWithName(calendarName)
-                        for objectName in calendarObjNames:
-                            objData = calendarObjNames[objectName]
-                            calendar.createCalendarObjectWithName(
-                                objectName, VComponent.fromString(objData)
-                            )
-        populateTxn.commit()
-        self.notifierFactory.reset()
-
-
-    def storeUnderTest(self):
-        """
-        Create and return a L{CalendarStore} for testing.
-        """
-        return self.calendarStore
-
-
-class AddressBookSQLStorageTests(AddressBookCommonTests, unittest.TestCase):
-    """
-    AddressBook SQL storage tests.
-    """
-
-    @inlineCallbacks
-    def setUp(self):
-        super(AddressBookSQLStorageTests, self).setUp()
-        self.addressbookStore = yield buildStore(self, self.notifierFactory)
-        self.populate()
-
-    def populate(self):
-        populateTxn = self.addressbookStore.newTransaction()
-        for homeUID in self.requirements:
-            addressbooks = self.requirements[homeUID]
-            if addressbooks is not None:
-                home = populateTxn.addressbookHomeWithUID(homeUID, True)
-                # We don't want the default addressbook to appear unless it's
-                # explicitly listed.
-                home.removeAddressBookWithName("addressbook")
-                for addressbookName in addressbooks:
-                    addressbookObjNames = addressbooks[addressbookName]
-                    if addressbookObjNames is not None:
-                        home.createAddressBookWithName(addressbookName)
-                        addressbook = home.addressbookWithName(addressbookName)
-                        for objectName in addressbookObjNames:
-                            objData = addressbookObjNames[objectName]
-                            addressbook.createAddressBookObjectWithName(
-                                objectName, VCard.fromString(objData)
-                            )
-
-        populateTxn.commit()
-        self.notifierFactory.reset()
-
-
-
-    def storeUnderTest(self):
-        """
-        Create and return a L{AddressBookStore} for testing.
-        """
-        return self.addressbookStore
-

Modified: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_scheduling.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/test_scheduling.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_scheduling.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -15,13 +15,13 @@
 ##
 
 """
-Tests for L{txcaldav.calendarstore.scheduling}.
+Tests for L{txdav.caldav.datastore.scheduling}.
 """
 
 from twisted.trial.unittest import TestCase
-from txcaldav.calendarstore.test.common import CommonTests
-from txcaldav.calendarstore.test.test_file import setUpCalendarStore
-from txcaldav.calendarstore.scheduling import ImplicitStore
+from txdav.caldav.datastore.test.common import CommonTests
+from txdav.caldav.datastore.test.test_file import setUpCalendarStore
+from txdav.caldav.datastore.scheduling import ImplicitStore
 
 simpleEvent = """BEGIN:VCALENDAR
 VERSION:2.0

Copied: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_sql.py (from rev 6184, CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/test_sql.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_sql.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_sql.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,78 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Tests for txdav.caldav.datastore.postgres, mostly based on
+L{txdav.caldav.datastore.test.common}.
+"""
+
+from txdav.caldav.datastore.test.common import CommonTests as CalendarCommonTests
+
+from txdav.common.datastore.test.util import SQLStoreBuilder
+from txdav.common.icommondatastore import NoSuchHomeChildError
+
+from twisted.trial import unittest
+from twisted.internet.defer import inlineCallbacks
+from twext.python.vcomponent import VComponent
+
+
+theStoreBuilder = SQLStoreBuilder()
+buildStore = theStoreBuilder.buildStore
+
+class CalendarSQLStorageTests(CalendarCommonTests, unittest.TestCase):
+    """
+    Calendar SQL storage tests.
+    """
+
+    @inlineCallbacks
+    def setUp(self):
+        super(CalendarSQLStorageTests, self).setUp()
+        self.calendarStore = yield buildStore(self, self.notifierFactory)
+        self.populate()
+
+
+    def populate(self):
+        populateTxn = self.calendarStore.newTransaction()
+        for homeUID in self.requirements:
+            calendars = self.requirements[homeUID]
+            if calendars is not None:
+                home = populateTxn.calendarHomeWithUID(homeUID, True)
+                # We don't want the default calendar or inbox to appear unless it's
+                # explicitly listed.
+                try:
+                    home.removeCalendarWithName("calendar")
+                    home.removeCalendarWithName("inbox")
+                except NoSuchHomeChildError:
+                    pass
+                for calendarName in calendars:
+                    calendarObjNames = calendars[calendarName]
+                    if calendarObjNames is not None:
+                        home.createCalendarWithName(calendarName)
+                        calendar = home.calendarWithName(calendarName)
+                        for objectName in calendarObjNames:
+                            objData = calendarObjNames[objectName]
+                            calendar.createCalendarObjectWithName(
+                                objectName, VComponent.fromString(objData)
+                            )
+        populateTxn.commit()
+        self.notifierFactory.reset()
+
+
+    def storeUnderTest(self):
+        """
+        Create and return a L{CalendarStore} for testing.
+        """
+        return self.calendarStore

Deleted: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/util.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,305 +0,0 @@
-##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-"""
-Utility logic common to multiple backend implementations.
-"""
-
-from twext.python.vcomponent import InvalidICalendarDataError
-from twext.python.vcomponent import VComponent
-from twistedcaldav.vcard import Component as VCard
-from twistedcaldav.vcard import InvalidVCardDataError
-
-from txdav.common.icommondatastore import InvalidObjectResourceError,\
-    NoSuchObjectResourceError
-
-
-def validateCalendarComponent(calendarObject, calendar, component):
-    """
-    Validate a calendar component for a particular calendar.
-
-    @param calendarObject: The calendar object whose component will be replaced.
-    @type calendarObject: L{ICalendarObject}
-
-    @param calendar: The calendar which the L{ICalendarObject} is present in.
-    @type calendar: L{ICalendar}
-
-    @param component: The VComponent to be validated.
-    @type component: L{VComponent}
-    """
-
-    if not isinstance(component, VComponent):
-        raise TypeError(type(component))
-
-    try:
-        if component.resourceUID() != calendarObject.uid():
-            raise InvalidObjectResourceError(
-                "UID may not change (%s != %s)" % (
-                    component.resourceUID(), calendarObject.uid()
-                 )
-            )
-    except NoSuchObjectResourceError:
-        pass
-
-    try:
-        # FIXME: This is a bad way to do this test, there should be a
-        # Calendar-level API for it.
-        if calendar.name() == 'inbox':
-            component.validateComponentsForCalDAV(True)
-        else:
-            component.validateForCalDAV()
-    except InvalidICalendarDataError, e:
-        raise InvalidObjectResourceError(e)
-
-
-def dropboxIDFromCalendarObject(calendarObject):
-    """
-    Helper to implement L{ICalendarObject.dropboxID}.
-
-    @param calendarObject: The calendar object to retrieve a dropbox ID for.
-    @type calendarObject: L{ICalendarObject}
-    """
-    dropboxProperty = calendarObject.component(
-        ).getFirstPropertyInAnyComponent("X-APPLE-DROPBOX")
-    if dropboxProperty is not None:
-        componentDropboxID = dropboxProperty.value().split("/")[-1]
-        return componentDropboxID
-    attachProperty = calendarObject.component().getFirstPropertyInAnyComponent("ATTACH")
-    if attachProperty is not None:
-        # Make sure the value type is URI
-        valueType = attachProperty.params().get("VALUE", ("TEXT",))
-        if valueType[0] == "URI": 
-            # FIXME: more aggressive checking to see if this URI is really the
-            # 'right' URI.  Maybe needs to happen in the front end.
-            attachPath = attachProperty.value().split("/")[-2]
-            return attachPath
-    
-    return calendarObject.uid() + ".dropbox"
-
-
-def validateAddressBookComponent(addressbookObject, vcard, component):
-    """
-    Validate an addressbook component for a particular addressbook.
-
-    @param addressbookObject: The addressbook object whose component will be replaced.
-    @type addressbookObject: L{IAddressBookObject}
-
-    @param addressbook: The addressbook which the L{IAddressBookObject} is present in.
-    @type addressbook: L{IAddressBook}
-
-    @param component: The VComponent to be validated.
-    @type component: L{VComponent}
-    """
-
-    if not isinstance(component, VCard):
-        raise TypeError(type(component))
-
-    try:
-        if component.resourceUID() != addressbookObject.uid():
-            raise InvalidObjectResourceError(
-                "UID may not change (%s != %s)" % (
-                    component.resourceUID(), addressbookObject.uid()
-                 )
-            )
-    except NoSuchObjectResourceError:
-        pass
-
-    try:
-        component.validForCardDAV()
-    except InvalidVCardDataError, e:
-        raise InvalidObjectResourceError(e)
-
-
-
-class CalendarSyncTokenHelper(object):
-    """
-    This is a mixin for use by data store implementations.
-    """
-
-    def syncToken(self):
-        revision = self._txn.execSQL(
-            "select REVISION from CALENDAR where RESOURCE_ID = %s",
-            [self._resourceID])[0][0]
-        return "%s#%s" % (self._resourceID, revision,)
-
-    def _updateSyncToken(self):
-        
-        self._txn.execSQL("""
-            update CALENDAR
-            set (REVISION)
-            = (nextval('CALENDAR_OBJECT_REVISION_SEQ'))
-            where RESOURCE_ID = %s
-            """,
-            [self._resourceID]
-        )
-
-    def _insertRevision(self, name):
-        self._changeRevision("insert", name)
-
-    def _updateRevision(self, name):
-        self._changeRevision("update", name)
-
-    def _deleteRevision(self, name):
-        self._changeRevision("delete", name)
-
-    def _changeRevision(self, action, name):
-        
-        nextrevision = self._txn.execSQL("""
-            select nextval('CALENDAR_OBJECT_REVISION_SEQ')
-            """
-        )
-
-        if action == "delete":
-            self._txn.execSQL("""
-                update CALENDAR_OBJECT_REVISIONS
-                set (REVISION, DELETED) = (%s, TRUE)
-                where CALENDAR_RESOURCE_ID = %s and RESOURCE_NAME = %s
-                """,
-                [nextrevision, self._resourceID, name]
-            )
-            self._txn.execSQL("""    
-                update CALENDAR
-                set (REVISION) = (%s)
-                where RESOURCE_ID = %s
-                """,
-                [nextrevision, self._resourceID]
-            )
-        elif action == "update":
-            self._txn.execSQL("""
-                update CALENDAR_OBJECT_REVISIONS
-                set (REVISION) = (%s)
-                where CALENDAR_RESOURCE_ID = %s and RESOURCE_NAME = %s
-                """,
-                [nextrevision, self._resourceID, name]
-            )
-            self._txn.execSQL("""    
-                update CALENDAR
-                set (REVISION) = (%s)
-                where RESOURCE_ID = %s
-                """,
-                [nextrevision, self._resourceID]
-            )
-        elif action == "insert":
-            self._txn.execSQL("""
-                delete from CALENDAR_OBJECT_REVISIONS
-                where CALENDAR_RESOURCE_ID = %s and RESOURCE_NAME = %s
-                """,
-                [self._resourceID, name,]
-            )
-            self._txn.execSQL("""
-                insert into CALENDAR_OBJECT_REVISIONS
-                (CALENDAR_RESOURCE_ID, RESOURCE_NAME, REVISION, DELETED)
-                values (%s, %s, %s, FALSE)
-                """,
-                [self._resourceID, name, nextrevision]
-            )
-            self._txn.execSQL("""    
-                update CALENDAR
-                set (REVISION) = (%s)
-                where RESOURCE_ID = %s
-                """,
-                [nextrevision, self._resourceID]
-            )
-
-class AddressbookSyncTokenHelper(object):
-    """
-    This is a mixin for use by data store implementations.
-    """
-
-    def syncToken(self):
-        revision = self._txn.execSQL(
-            "select REVISION from ADDRESSBOOK where RESOURCE_ID = %s",
-            [self._resourceID])[0][0]
-        return "%s#%s" % (self._resourceID, revision,)
-
-    def _updateSyncToken(self):
-        
-        self._txn.execSQL("""
-            update ADDRESSBOOK
-            set (REVISION)
-            = (nextval('ADDRESSBOOK_OBJECT_REVISION_SEQ'))
-            where RESOURCE_ID = %s
-            """,
-            [self._resourceID]
-        )
-
-    def _insertRevision(self, name):
-        self._changeRevision("insert", name)
-
-    def _updateRevision(self, name):
-        self._changeRevision("update", name)
-
-    def _deleteRevision(self, name):
-        self._changeRevision("delete", name)
-
-    def _changeRevision(self, action, name):
-        
-        nextrevision = self._txn.execSQL("""
-            select nextval('ADDRESSBOOK_OBJECT_REVISION_SEQ')
-            """
-        )
-
-        if action == "delete":
-            self._txn.execSQL("""
-                update ADDRESSBOOK_OBJECT_REVISIONS
-                set (REVISION, DELETED) = (%s, TRUE)
-                where ADDRESSBOOK_RESOURCE_ID = %s and RESOURCE_NAME = %s
-                """,
-                [nextrevision, self._resourceID, name]
-            )
-            self._txn.execSQL("""    
-                update ADDRESSBOOK
-                set (REVISION) = (%s)
-                where RESOURCE_ID = %s
-                """,
-                [nextrevision, self._resourceID]
-            )
-        elif action == "update":
-            self._txn.execSQL("""
-                update ADDRESSBOOK_OBJECT_REVISIONS
-                set (REVISION) = (%s)
-                where ADDRESSBOOK_RESOURCE_ID = %s and RESOURCE_NAME = %s
-                """,
-                [nextrevision, self._resourceID, name]
-            )
-            self._txn.execSQL("""    
-                update ADDRESSBOOK
-                set (REVISION) = (%s)
-                where RESOURCE_ID = %s
-                """,
-                [nextrevision, self._resourceID]
-            )
-        elif action == "insert":
-            self._txn.execSQL("""
-                delete from ADDRESSBOOK_OBJECT_REVISIONS
-                where ADDRESSBOOK_RESOURCE_ID = %s and RESOURCE_NAME = %s
-                """,
-                [self._resourceID, name,]
-            )
-            self._txn.execSQL("""
-                insert into ADDRESSBOOK_OBJECT_REVISIONS
-                (ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME, REVISION, DELETED)
-                values (%s, %s, %s, FALSE)
-                """,
-                [self._resourceID, name, nextrevision]
-            )
-            self._txn.execSQL("""    
-                update ADDRESSBOOK
-                set (REVISION) = (%s)
-                where RESOURCE_ID = %s
-                """,
-                [nextrevision, self._resourceID]
-            )
-        
\ No newline at end of file

Copied: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py (from rev 6184, CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/util.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,88 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+"""
+Utility logic common to multiple backend implementations.
+"""
+
+from twext.python.vcomponent import InvalidICalendarDataError
+from twext.python.vcomponent import VComponent
+
+from txdav.common.icommondatastore import InvalidObjectResourceError,\
+    NoSuchObjectResourceError
+
+
+def validateCalendarComponent(calendarObject, calendar, component, inserting):
+    """
+    Validate a calendar component for a particular calendar.
+
+    @param calendarObject: The calendar object whose component will be replaced.
+    @type calendarObject: L{ICalendarObject}
+
+    @param calendar: The calendar which the L{ICalendarObject} is present in.
+    @type calendar: L{ICalendar}
+
+    @param component: The VComponent to be validated.
+    @type component: L{VComponent}
+    """
+
+    if not isinstance(component, VComponent):
+        raise TypeError(type(component))
+
+    try:
+        if not inserting and component.resourceUID() != calendarObject.uid():
+            raise InvalidObjectResourceError(
+                "UID may not change (%s != %s)" % (
+                    component.resourceUID(), calendarObject.uid()
+                 )
+            )
+    except NoSuchObjectResourceError:
+        pass
+
+    try:
+        # FIXME: This is a bad way to do this test, there should be a
+        # Calendar-level API for it.
+        if calendar.name() == 'inbox':
+            component.validateComponentsForCalDAV(True)
+        else:
+            component.validateForCalDAV()
+    except InvalidICalendarDataError, e:
+        raise InvalidObjectResourceError(e)
+
+
+def dropboxIDFromCalendarObject(calendarObject):
+    """
+    Helper to implement L{ICalendarObject.dropboxID}.
+
+    @param calendarObject: The calendar object to retrieve a dropbox ID for.
+    @type calendarObject: L{ICalendarObject}
+    """
+    dropboxProperty = calendarObject.component(
+        ).getFirstPropertyInAnyComponent("X-APPLE-DROPBOX")
+    if dropboxProperty is not None:
+        componentDropboxID = dropboxProperty.value().split("/")[-1]
+        return componentDropboxID
+    attachProperty = calendarObject.component().getFirstPropertyInAnyComponent("ATTACH")
+    if attachProperty is not None:
+        # Make sure the value type is URI
+        valueType = attachProperty.params().get("VALUE", ("TEXT",))
+        if valueType[0] == "URI": 
+            # FIXME: more aggressive checking to see if this URI is really the
+            # 'right' URI.  Maybe needs to happen in the front end.
+            attachPath = attachProperty.value().split("/")[-2]
+            return attachPath
+    
+    return calendarObject.uid() + ".dropbox"
+        
\ No newline at end of file

Modified: CalendarServer/branches/generic-sqlstore/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/icalendarstore.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/icalendarstore.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore -*-
+# -*- test-case-name: txdav.caldav.datastore -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #

Deleted: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/file.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,322 +0,0 @@
-# -*- test-case-name: txcarddav.addressbookstore.test.test_file -*-
-##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-File addressbook store.
-"""
-
-__all__ = [
-    "AddressBookStore",
-    "AddressBookStoreTransaction",
-    "AddressBookHome",
-    "AddressBook",
-    "AddressBookObject",
-]
-
-from errno import ENOENT
-
-from twext.web2.dav.element.rfc2518 import ResourceType
-
-from twistedcaldav.sharing import InvitesDatabase
-from twistedcaldav.vcard import Component as VComponent, InvalidVCardDataError
-from twistedcaldav.vcardindex import AddressBookIndex as OldIndex
-
-from txcarddav.iaddressbookstore import IAddressBook, IAddressBookObject
-from txcarddav.iaddressbookstore import IAddressBookHome
-
-from txdav.common.datastore.file import CommonDataStore, CommonHome,\
-    CommonStoreTransaction, CommonHomeChild, CommonObjectResource,\
-    CommonStubResource
-from txdav.common.icommondatastore import InvalidObjectResourceError,\
-    NoSuchObjectResourceError, InternalDataStoreError
-from txdav.datastore.file import hidden, writeOperation
-from txdav.propertystore.base import PropertyName
-
-from twistedcaldav import customxml, carddavxml
-
-from zope.interface import implements
-
-AddressBookStore = CommonDataStore
-
-AddressBookStoreTransaction = CommonStoreTransaction
-
-class AddressBookHome(CommonHome):
-
-    implements(IAddressBookHome)
-
-    def __init__(self, uid, path, addressbookStore, transaction, notifier):
-        super(AddressBookHome, self).__init__(uid, path, addressbookStore, transaction, notifier)
-
-        self._childClass = AddressBook
-
-    addressbooks = CommonHome.children
-    listAddressbooks = CommonHome.listChildren
-    addressbookWithName = CommonHome.childWithName
-    createAddressBookWithName = CommonHome.createChildWithName
-    removeAddressBookWithName = CommonHome.removeChildWithName
-
-    @property
-    def _addressbookStore(self):
-        return self._dataStore
-
-    def created(self):
-        self.createAddressBookWithName("addressbook")
-
-class AddressBook(CommonHomeChild):
-    """
-    File-based implementation of L{IAddressBook}.
-    """
-    implements(IAddressBook)
-
-    def __init__(self, name, addressbookHome, notifier, realName=None):
-        """
-        Initialize an addressbook pointing at a path on disk.
-
-        @param name: the subdirectory of addressbookHome where this addressbook
-            resides.
-        @type name: C{str}
-
-        @param addressbookHome: the home containing this addressbook.
-        @type addressbookHome: L{AddressBookHome}
-
-        @param realName: If this addressbook was just created, the name which it
-        will eventually have on disk.
-        @type realName: C{str}
-        """
-        
-        super(AddressBook, self).__init__(name, addressbookHome, notifier,
-            realName=realName)
-
-        self._index = Index(self)
-        self._invites = Invites(self)
-        self._objectResourceClass = AddressBookObject
-
-    @property
-    def _addressbookHome(self):
-        return self._home
-
-    def resourceType(self):
-        return ResourceType.addressbook
-
-    ownerAddressBookHome = CommonHomeChild.ownerHome
-    addressbookObjects = CommonHomeChild.objectResources
-    listAddressbookObjects = CommonHomeChild.listObjectResources
-    addressbookObjectWithName = CommonHomeChild.objectResourceWithName
-    addressbookObjectWithUID = CommonHomeChild.objectResourceWithUID
-    createAddressBookObjectWithName = CommonHomeChild.createObjectResourceWithName
-    removeAddressBookObjectWithName = CommonHomeChild.removeObjectResourceWithName
-    removeAddressBookObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
-    addressbookObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
-
-
-    def initPropertyStore(self, props):
-        # Setup peruser special properties
-        props.setSpecialProperties(
-            (
-                PropertyName.fromElement(carddavxml.AddressBookDescription),
-            ),
-            (
-                PropertyName.fromElement(customxml.GETCTag),
-            ),
-        )
-
-    def _doValidate(self, component):
-        component.validForCardDAV()
-
-
-class AddressBookObject(CommonObjectResource):
-    """
-    """
-    implements(IAddressBookObject)
-
-    def __init__(self, name, addressbook):
-
-        super(AddressBookObject, self).__init__(name, addressbook)
-
-
-    @property
-    def _addressbook(self):
-        return self._parentCollection
-
-
-    def addressbook(self):
-        return self._addressbook
-
-
-    @writeOperation
-    def setComponent(self, component):
-        if not isinstance(component, VComponent):
-            raise TypeError(type(component))
-
-        try:
-            if component.resourceUID() != self.uid():
-                raise InvalidObjectResourceError(
-                    "UID may not change (%s != %s)" % (
-                        component.resourceUID(), self.uid()
-                     )
-                )
-        except NoSuchObjectResourceError:
-            pass
-
-        try:
-            self._addressbook._doValidate(component)
-        except InvalidVCardDataError, e:
-            raise InvalidObjectResourceError(e)
-
-        self._addressbook.retrieveOldIndex().addResource(
-            self.name(), component
-        )
-
-        self._component = component
-        # FIXME: needs to clear text cache
-
-        def do():
-            # Mark all properties as dirty, so they can be added back
-            # to the newly updated file.
-            self.properties().update(self.properties())
-
-            backup = None
-            if self._path.exists():
-                backup = hidden(self._path.temporarySibling())
-                self._path.moveTo(backup)
-            fh = self._path.open("w")
-            try:
-                # FIXME: concurrency problem; if this write is interrupted
-                # halfway through, the underlying file will be corrupt.
-                fh.write(str(component))
-            finally:
-                fh.close()
-
-            # Now re-write the original properties on the updated file
-            self.properties().flush()
-
-            def undo():
-                if backup:
-                    backup.moveTo(self._path)
-                else:
-                    self._path.remove()
-            return undo
-        self._transaction.addOperation(do, "set addressbook component %r" % (self.name(),))
-        if self._addressbook._notifier:
-            self._transaction.postCommit(self._addressbook._notifier.notify)
-
-
-
-    def component(self):
-        if self._component is not None:
-            return self._component
-        text = self.text()
-
-        try:
-            component = VComponent.fromString(text)
-        except InvalidVCardDataError, e:
-            raise InternalDataStoreError(
-                "File corruption detected (%s) in file: %s"
-                % (e, self._path.path)
-            )
-        return component
-
-
-    def text(self):
-        if self._component is not None:
-            return str(self._component)
-        try:
-            fh = self._path.open()
-        except IOError, e:
-            if e[0] == ENOENT:
-                raise NoSuchObjectResourceError(self)
-            else:
-                raise
-
-        try:
-            text = fh.read()
-        finally:
-            fh.close()
-
-        if not (
-            text.startswith("BEGIN:VCARD\r\n") or
-            text.endswith("\r\nEND:VCARD\r\n")
-        ):
-            raise InternalDataStoreError(
-                "File corruption detected (improper start) in file: %s"
-                % (self._path.path,)
-            )
-        return text
-
-    vCardText = text
-
-    def uid(self):
-        if not hasattr(self, "_uid"):
-            self._uid = self.component().resourceUID()
-        return self._uid
-
-
-class AddressBookStubResource(CommonStubResource):
-    """
-    Just enough resource to keep the addressbook's sql DB classes going.
-    """
-
-    def isAddressBookCollection(self):
-        return True
-
-    def getChild(self, name):
-        addressbookObject = self.resource.addressbookObjectWithName(name)
-        if addressbookObject:
-            class ChildResource(object):
-                def __init__(self, addressbookObject):
-                    self.addressbookObject = addressbookObject
-
-                def iAddressBook(self):
-                    return self.addressbookObject.component()
-
-            return ChildResource(addressbookObject)
-        else:
-            return None
-
-
-class Index(object):
-    #
-    # OK, here's where we get ugly.
-    # The index code needs to be rewritten also, but in the meantime...
-    #
-    def __init__(self, addressbook):
-        self.addressbook = addressbook
-        stubResource = AddressBookStubResource(addressbook)
-        self._oldIndex = OldIndex(stubResource)
-
-
-    def addressbookObjects(self):
-        addressbook = self.addressbook
-        for name, uid, componentType in self._oldIndex.bruteForceSearch():
-            addressbookObject = addressbook.addressbookObjectWithName(name)
-
-            # Precache what we found in the index
-            addressbookObject._uid = uid
-            addressbookObject._componentType = componentType
-
-            yield addressbookObject
-
-
-class Invites(object):
-    #
-    # OK, here's where we get ugly.
-    # The index code needs to be rewritten also, but in the meantime...
-    #
-    def __init__(self, addressbook):
-        self.addressbook = addressbook
-        stubResource = AddressBookStubResource(addressbook)
-        self._oldInvites = InvitesDatabase(stubResource)

Copied: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py (from rev 6185, CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/file.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,306 @@
+# -*- test-case-name: txdav.carddav.datastore.test.test_file -*-
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+File addressbook store.
+"""
+
+__all__ = [
+    "AddressBookStore",
+    "AddressBookStoreTransaction",
+    "AddressBookHome",
+    "AddressBook",
+    "AddressBookObject",
+]
+
+from errno import ENOENT
+
+from twext.web2.dav.element.rfc2518 import ResourceType
+
+from twistedcaldav.sharing import InvitesDatabase
+from twistedcaldav.vcard import Component as VComponent, InvalidVCardDataError
+from twistedcaldav.vcardindex import AddressBookIndex as OldIndex
+
+from txdav.carddav.datastore.util import validateAddressBookComponent
+from txdav.carddav.iaddressbookstore import IAddressBook, IAddressBookObject
+from txdav.carddav.iaddressbookstore import IAddressBookHome
+
+from txdav.common.datastore.file import CommonDataStore, CommonHome,\
+    CommonStoreTransaction, CommonHomeChild, CommonObjectResource,\
+    CommonStubResource
+from txdav.common.icommondatastore import NoSuchObjectResourceError, InternalDataStoreError
+from txdav.base.datastore.file import hidden, writeOperation
+from txdav.base.propertystore.base import PropertyName
+
+from twistedcaldav import customxml, carddavxml
+
+from zope.interface import implements
+
+AddressBookStore = CommonDataStore
+
+AddressBookStoreTransaction = CommonStoreTransaction
+
+class AddressBookHome(CommonHome):
+
+    implements(IAddressBookHome)
+
+    def __init__(self, uid, path, addressbookStore, transaction, notifier):
+        super(AddressBookHome, self).__init__(uid, path, addressbookStore, transaction, notifier)
+
+        self._childClass = AddressBook
+
+    addressbooks = CommonHome.children
+    listAddressbooks = CommonHome.listChildren
+    addressbookWithName = CommonHome.childWithName
+    createAddressBookWithName = CommonHome.createChildWithName
+    removeAddressBookWithName = CommonHome.removeChildWithName
+
+    @property
+    def _addressbookStore(self):
+        return self._dataStore
+
+    def createdHome(self):
+        self.createAddressBookWithName("addressbook")
+
+class AddressBook(CommonHomeChild):
+    """
+    File-based implementation of L{IAddressBook}.
+    """
+    implements(IAddressBook)
+
+    def __init__(self, name, addressbookHome, notifier, realName=None):
+        """
+        Initialize an addressbook pointing at a path on disk.
+
+        @param name: the subdirectory of addressbookHome where this addressbook
+            resides.
+        @type name: C{str}
+
+        @param addressbookHome: the home containing this addressbook.
+        @type addressbookHome: L{AddressBookHome}
+
+        @param realName: If this addressbook was just created, the name which it
+        will eventually have on disk.
+        @type realName: C{str}
+        """
+        
+        super(AddressBook, self).__init__(name, addressbookHome, notifier,
+            realName=realName)
+
+        self._index = Index(self)
+        self._invites = Invites(self)
+        self._objectResourceClass = AddressBookObject
+
+    @property
+    def _addressbookHome(self):
+        return self._home
+
+    def resourceType(self):
+        return ResourceType.addressbook #@UndefinedVariable
+
+    ownerAddressBookHome = CommonHomeChild.ownerHome
+    addressbookObjects = CommonHomeChild.objectResources
+    listAddressbookObjects = CommonHomeChild.listObjectResources
+    addressbookObjectWithName = CommonHomeChild.objectResourceWithName
+    addressbookObjectWithUID = CommonHomeChild.objectResourceWithUID
+    createAddressBookObjectWithName = CommonHomeChild.createObjectResourceWithName
+    removeAddressBookObjectWithName = CommonHomeChild.removeObjectResourceWithName
+    removeAddressBookObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
+    addressbookObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
+
+
+    def initPropertyStore(self, props):
+        # Setup peruser special properties
+        props.setSpecialProperties(
+            (
+                PropertyName.fromElement(carddavxml.AddressBookDescription),
+            ),
+            (
+                PropertyName.fromElement(customxml.GETCTag),
+            ),
+        )
+
+    def _doValidate(self, component):
+        component.validForCardDAV()
+
+
+class AddressBookObject(CommonObjectResource):
+    """
+    """
+    implements(IAddressBookObject)
+
+    def __init__(self, name, addressbook):
+
+        super(AddressBookObject, self).__init__(name, addressbook)
+
+
+    @property
+    def _addressbook(self):
+        return self._parentCollection
+
+
+    def addressbook(self):
+        return self._addressbook
+
+
+    @writeOperation
+    def setComponent(self, component, inserting=False):
+        validateAddressBookComponent(self, self._addressbook, component, inserting)
+
+        self._addressbook.retrieveOldIndex().addResource(
+            self.name(), component
+        )
+
+        self._component = component
+        # FIXME: needs to clear text cache
+
+        def do():
+            # Mark all properties as dirty, so they can be added back
+            # to the newly updated file.
+            self.properties().update(self.properties())
+
+            backup = None
+            if self._path.exists():
+                backup = hidden(self._path.temporarySibling())
+                self._path.moveTo(backup)
+            fh = self._path.open("w")
+            try:
+                # FIXME: concurrency problem; if this write is interrupted
+                # halfway through, the underlying file will be corrupt.
+                fh.write(str(component))
+            finally:
+                fh.close()
+
+            # Now re-write the original properties on the updated file
+            self.properties().flush()
+
+            def undo():
+                if backup:
+                    backup.moveTo(self._path)
+                else:
+                    self._path.remove()
+            return undo
+        self._transaction.addOperation(do, "set addressbook component %r" % (self.name(),))
+        if self._addressbook._notifier:
+            self._transaction.postCommit(self._addressbook._notifier.notify)
+
+
+
+    def component(self):
+        if self._component is not None:
+            return self._component
+        text = self.text()
+
+        try:
+            component = VComponent.fromString(text)
+        except InvalidVCardDataError, e:
+            raise InternalDataStoreError(
+                "File corruption detected (%s) in file: %s"
+                % (e, self._path.path)
+            )
+        return component
+
+
+    def text(self):
+        if self._component is not None:
+            return str(self._component)
+        try:
+            fh = self._path.open()
+        except IOError, e:
+            if e[0] == ENOENT:
+                raise NoSuchObjectResourceError(self)
+            else:
+                raise
+
+        try:
+            text = fh.read()
+        finally:
+            fh.close()
+
+        if not (
+            text.startswith("BEGIN:VCARD\r\n") or
+            text.endswith("\r\nEND:VCARD\r\n")
+        ):
+            raise InternalDataStoreError(
+                "File corruption detected (improper start) in file: %s"
+                % (self._path.path,)
+            )
+        return text
+
+    vCardText = text
+
+    def uid(self):
+        if not hasattr(self, "_uid"):
+            self._uid = self.component().resourceUID()
+        return self._uid
+
+
+class AddressBookStubResource(CommonStubResource):
+    """
+    Just enough resource to keep the addressbook's sql DB classes going.
+    """
+
+    def isAddressBookCollection(self):
+        return True
+
+    def getChild(self, name):
+        addressbookObject = self.resource.addressbookObjectWithName(name)
+        if addressbookObject:
+            class ChildResource(object):
+                def __init__(self, addressbookObject):
+                    self.addressbookObject = addressbookObject
+
+                def iAddressBook(self):
+                    return self.addressbookObject.component()
+
+            return ChildResource(addressbookObject)
+        else:
+            return None
+
+
+class Index(object):
+    #
+    # OK, here's where we get ugly.
+    # The index code needs to be rewritten also, but in the meantime...
+    #
+    def __init__(self, addressbook):
+        self.addressbook = addressbook
+        stubResource = AddressBookStubResource(addressbook)
+        self._oldIndex = OldIndex(stubResource)
+
+
+    def addressbookObjects(self):
+        addressbook = self.addressbook
+        for name, uid, componentType in self._oldIndex.bruteForceSearch():
+            addressbookObject = addressbook.addressbookObjectWithName(name)
+
+            # Precache what we found in the index
+            addressbookObject._uid = uid
+            addressbookObject._componentType = componentType
+
+            yield addressbookObject
+
+
+class Invites(object):
+    #
+    # OK, here's where we get ugly.
+    # The index code needs to be rewritten also, but in the meantime...
+    #
+    def __init__(self, addressbook):
+        self.addressbook = addressbook
+        stubResource = AddressBookStubResource(addressbook)
+        self._oldInvites = InvitesDatabase(stubResource)

Copied: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/sql.py (from rev 6185, CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/sql.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/sql.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/sql.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,241 @@
+# -*- test-case-name: txdav.carddav.datastore.test.test_sql -*-
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+__all__ = [
+    "AddressBookHome",
+    "AddressBook",
+    "AddressBookObject",
+]
+
+from twext.web2.dav.element.rfc2518 import ResourceType
+from twext.web2.http_headers import MimeType
+
+from twistedcaldav import carddavxml, customxml
+from twistedcaldav.vcard import Component as VCard
+
+from txdav.common.datastore.sql_legacy import \
+    PostgresLegacyABIndexEmulator, PostgresLegacyABInvitesEmulator,\
+    PostgresLegacyABSharesEmulator
+
+from txdav.carddav.datastore.util import validateAddressBookComponent
+from txdav.carddav.iaddressbookstore import IAddressBookHome, IAddressBook,\
+    IAddressBookObject
+
+from txdav.common.datastore.sql import CommonHome, CommonHomeChild,\
+    CommonObjectResource
+from txdav.common.datastore.sql_tables import ADDRESSBOOK_TABLE,\
+    ADDRESSBOOK_BIND_TABLE, ADDRESSBOOK_OBJECT_REVISIONS_TABLE,\
+    ADDRESSBOOK_OBJECT_TABLE
+from txdav.base.propertystore.base import PropertyName
+
+from zope.interface.declarations import implements
+
+class AddressBookHome(CommonHome):
+
+    implements(IAddressBookHome)
+
+    def __init__(self, transaction, ownerUID, resourceID, notifier):
+        super(AddressBookHome, self).__init__(transaction, ownerUID, resourceID, notifier)
+
+        self._shares = PostgresLegacyABSharesEmulator(self)
+        self._childClass = AddressBook
+        self._childTable = ADDRESSBOOK_TABLE
+        self._bindTable = ADDRESSBOOK_BIND_TABLE
+
+    addressbooks = CommonHome.children
+    listAddressbooks = CommonHome.listChildren
+    addressbookWithName = CommonHome.childWithName
+    createAddressBookWithName = CommonHome.createChildWithName
+    removeAddressBookWithName = CommonHome.removeChildWithName
+
+    def createdHome(self):
+        self.createAddressBookWithName("addressbook")
+
+class AddressBook(CommonHomeChild):
+    """
+    File-based implementation of L{IAddressBook}.
+    """
+    implements(IAddressBook)
+
+    def __init__(self, home, name, resourceID, notifier):
+        """
+        Initialize an addressbook pointing at a path on disk.
+
+        @param name: the subdirectory of addressbookHome where this addressbook
+            resides.
+        @type name: C{str}
+
+        @param addressbookHome: the home containing this addressbook.
+        @type addressbookHome: L{AddressBookHome}
+
+        @param realName: If this addressbook was just created, the name which it
+        will eventually have on disk.
+        @type realName: C{str}
+        """
+        
+        super(AddressBook, self).__init__(home, name, resourceID, notifier)
+
+        self._index = PostgresLegacyABIndexEmulator(self)
+        self._invites = PostgresLegacyABInvitesEmulator(self)
+        self._objectResourceClass = AddressBookObject
+        self._bindTable = ADDRESSBOOK_BIND_TABLE
+        self._homeChildTable = ADDRESSBOOK_TABLE
+        self._revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
+        self._objectTable = ADDRESSBOOK_OBJECT_TABLE
+
+    @property
+    def _addressbookHome(self):
+        return self._home
+
+    def resourceType(self):
+        return ResourceType.addressbook #@UndefinedVariable
+
+    ownerAddressBookHome = CommonHomeChild.ownerHome
+    addressbookObjects = CommonHomeChild.objectResources
+    listAddressbookObjects = CommonHomeChild.listObjectResources
+    addressbookObjectWithName = CommonHomeChild.objectResourceWithName
+    addressbookObjectWithUID = CommonHomeChild.objectResourceWithUID
+    createAddressBookObjectWithName = CommonHomeChild.createObjectResourceWithName
+    removeAddressBookObjectWithName = CommonHomeChild.removeObjectResourceWithName
+    removeAddressBookObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
+    addressbookObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
+
+
+    def initPropertyStore(self, props):
+        # Setup peruser special properties
+        props.setSpecialProperties(
+            (
+                PropertyName.fromElement(carddavxml.AddressBookDescription),
+            ),
+            (
+                PropertyName.fromElement(customxml.GETCTag),
+            ),
+        )
+
+    def _doValidate(self, component):
+        component.validForCardDAV()
+
+    def contentType(self):
+        """
+        The content type of Addresbook objects is text/vcard.
+        """
+        return MimeType.fromString("text/vcard; charset=utf-8")
+
+class AddressBookObject(CommonObjectResource):
+
+    implements(IAddressBookObject)
+
+    def __init__(self, name, addressbook, resid):
+
+        super(AddressBookObject, self).__init__(name, addressbook, resid)
+
+        self._objectTable = ADDRESSBOOK_OBJECT_TABLE
+
+    @property
+    def _addressbook(self):
+        return self._parentCollection
+
+    def addressbook(self):
+        return self._addressbook
+
+    def setComponent(self, component, inserting=False):
+        validateAddressBookComponent(self, self._addressbook, component, inserting)
+
+        self.updateDatabase(component, inserting=inserting)
+        if inserting:
+            self._addressbook._insertRevision(self._name)
+        else:
+            self._addressbook._updateRevision(self._name)
+
+        if self._addressbook._notifier:
+            self._addressbook._home._txn.postCommit(self._addressbook._notifier.notify)
+
+    def updateDatabase(self, component, expand_until=None, reCreate=False, inserting=False):
+        """
+        Update the database tables for the new data being written.
+
+        @param component: addressbook data to store
+        @type component: L{Component}
+        """
+
+        componentText = str(component)
+        self._objectText = componentText
+
+        # ADDRESSBOOK_OBJECT table update
+        if inserting:
+            self._resourceID = self._txn.execSQL(
+                """
+                insert into ADDRESSBOOK_OBJECT
+                (ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME, VCARD_TEXT, VCARD_UID)
+                 values
+                (%s, %s, %s, %s)
+                 returning RESOURCE_ID
+                """,
+                [
+                    self._addressbook._resourceID,
+                    self._name,
+                    componentText,
+                    component.resourceUID(),
+                ]
+            )[0][0]
+        else:
+            self._txn.execSQL(
+                """
+                update ADDRESSBOOK_OBJECT set
+                (VCARD_TEXT, VCARD_UID, MODIFIED)
+                 =
+                (%s, %s, timezone('UTC', CURRENT_TIMESTAMP))
+                 where RESOURCE_ID = %s
+                """,
+                [
+                    componentText,
+                    component.resourceUID(),
+                    self._resourceID
+                ]
+            )
+
+    def component(self):
+        return VCard.fromString(self.vCardText())
+
+    def text(self):
+        if self._objectText is None:
+            text = self._txn.execSQL(
+                "select VCARD_TEXT from ADDRESSBOOK_OBJECT where "
+                "RESOURCE_ID = %s", [self._resourceID]
+            )[0][0]
+            self._objectText = text
+            return text
+        else:
+            return self._objectText
+
+    vCardText = text
+
+    def uid(self):
+        return self.component().resourceUID()
+
+    def name(self):
+        return self._name
+
+    def componentType(self):
+        return self.component().mainType()
+
+    # IDataStoreResource
+    def contentType(self):
+        """
+        The content type of Addressbook objects is text/x-vcard.
+        """
+        return MimeType.fromString("text/vcard; charset=utf-8")

Modified: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/__init__.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/__init__.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/__init__.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcarddav.addressbookstore.test -*-
+# -*- test-case-name: txdav.carddav.datastore.test -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #

Deleted: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/common.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,897 +0,0 @@
-# -*- test-case-name: txcarddav.addressbookstore,txcaldav.calendarstore.test.test_postgres.AddressBookSQLStorageTests -*-
-##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-"""
-Tests for common addressbook store API functions.
-"""
-
-from zope.interface.verify import verifyObject
-from zope.interface.exceptions import (
-    BrokenMethodImplementation, DoesNotImplement
-)
-
-from txdav.idav import IPropertyStore, IDataStore
-from txdav.propertystore.base import PropertyName
-
-from txdav.common.icommondatastore import (
-    HomeChildNameAlreadyExistsError, ICommonTransaction
-)
-from txdav.common.icommondatastore import InvalidObjectResourceError
-from txdav.common.icommondatastore import NoSuchHomeChildError
-from txdav.common.icommondatastore import NoSuchObjectResourceError
-from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
-
-from txcarddav.iaddressbookstore import (
-    IAddressBookObject, IAddressBookHome,
-    IAddressBook, IAddressBookTransaction
-)
-from twistedcaldav.vcard import Component as VComponent
-from twistedcaldav.notify import Notifier
-
-from twext.python.filepath import CachingFilePath as FilePath
-from twext.web2.dav import davxml
-from twext.web2.dav.element.base import WebDAVUnknownElement
-
-
-storePath = FilePath(__file__).parent().child("addressbook_store")
-
-homeRoot = storePath.child("ho").child("me").child("home1")
-
-adbk1Root = homeRoot.child("addressbook_1")
-
-addressbook1_objectNames = [
-    "1.vcf",
-    "2.vcf",
-    "3.vcf",
-]
-
-
-home1_addressbookNames = [
-    "addressbook_1",
-    "addressbook_2",
-    "addressbook_empty",
-]
-
-
-vcard4_text = (
-    """BEGIN:VCARD
-VERSION:3.0
-N:Thompson;Default;;;
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at example.com
-TEL;type=WORK;type=pref:1-555-555-5555
-TEL;type=CELL:1-444-444-4444
-item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA
-item1.X-ABADR:us
-UID:uid4
-END:VCARD
-""".replace("\n", "\r\n")
-)
-
-
-
-vcard4notCardDAV_text = ( # Missing UID, N and FN
-"""BEGIN:VCARD
-VERSION:3.0
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at example.com
-TEL;type=WORK;type=pref:1-555-555-5555
-TEL;type=CELL:1-444-444-4444
-item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA
-item1.X-ABADR:us
-END:VCARD
-""".replace("\n", "\r\n")
-)
-
-
-
-vcard1modified_text = vcard4_text.replace(
-    "\r\nUID:uid4\r\n",
-    "\r\nUID:uid1\r\n"
-)
-
-
-def assertProvides(testCase, interface, provider):
-    """
-    Verify that C{provider} properly provides C{interface}
-
-    @type interface: L{zope.interface.Interface}
-    @type provider: C{provider}
-    """
-    try:
-        verifyObject(interface, provider)
-    except BrokenMethodImplementation, e:
-        testCase.fail(e)
-    except DoesNotImplement, e:
-        testCase.fail("%r does not provide %s.%s" %
-                      (provider, interface.__module__, interface.getName()))
-
-
-
-class CommonTests(object):
-    """
-    Tests for common functionality of interfaces defined in
-    L{txcarddav.iaddressbookstore}.
-    """
-
-    requirements = {
-        "home1": {
-            "addressbook_1": {
-                "1.vcf": adbk1Root.child("1.vcf").getContent(),
-                "2.vcf": adbk1Root.child("2.vcf").getContent(),
-                "3.vcf": adbk1Root.child("3.vcf").getContent()
-            },
-            "addressbook_2": {},
-            "addressbook_empty": {},
-            "not_a_addressbook": None
-        },
-        "not_a_home": None
-    }
-
-    def storeUnderTest(self):
-        """
-        Subclasses must override this to return an L{IAddressBookStore}
-        provider which adheres to the structure detailed by
-        L{CommonTests.requirements}. This attribute is a dict of dict of dicts;
-        the outermost layer representing UIDs mapping to addressbook homes,
-        then addressbook names mapping to addressbook collections, and finally
-        addressbook object names mapping to addressbook object text.
-        """
-        raise NotImplementedError()
-
-
-    lastTransaction = None
-    savedStore = None
-
-    def transactionUnderTest(self):
-        """
-        Create a transaction from C{storeUnderTest} and save it as
-        C[lastTransaction}.  Also makes sure to use the same store, saving the
-        value from C{storeUnderTest}.
-        """
-        if self.lastTransaction is not None:
-            return self.lastTransaction
-        if self.savedStore is None:
-            self.savedStore = self.storeUnderTest()
-        txn = self.lastTransaction = self.savedStore.newTransaction(self.id())
-        return txn
-
-
-    def commit(self):
-        """
-        Commit the last transaction created from C{transactionUnderTest}, and
-        clear it.
-        """
-        self.lastTransaction.commit()
-        self.lastTransaction = None
-
-
-    def abort(self):
-        """
-        Abort the last transaction created from C[transactionUnderTest}, and
-        clear it.
-        """
-        self.lastTransaction.abort()
-        self.lastTransaction = None
-
-    def setUp(self):
-        self.notifierFactory = StubNotifierFactory()
-
-    def tearDown(self):
-        if self.lastTransaction is not None:
-            self.commit()
-
-
-
-    def homeUnderTest(self):
-        """
-        Get the addressbook home detailed by C{requirements['home1']}.
-        """
-        return self.transactionUnderTest().addressbookHomeWithUID("home1")
-
-
-    def addressbookUnderTest(self):
-        """
-        Get the addressbook detailed by C{requirements['home1']['addressbook_1']}.
-        """
-        return self.homeUnderTest().addressbookWithName("addressbook_1")
-
-
-    def addressbookObjectUnderTest(self):
-        """
-        Get the addressbook detailed by
-        C{requirements['home1']['addressbook_1']['1.vcf']}.
-        """
-        return self.addressbookUnderTest().addressbookObjectWithName("1.vcf")
-
-
-    assertProvides = assertProvides
-
-
-    def test_addressbookStoreProvides(self):
-        """
-        The addressbook store provides L{IAddressBookStore} and its required
-        attributes.
-        """
-        addressbookStore = self.storeUnderTest()
-        self.assertProvides(IDataStore, addressbookStore)
-
-
-    def test_transactionProvides(self):
-        """
-        The transactions generated by the addressbook store provide
-        L{IAddressBookStoreTransaction} and its required attributes.
-        """
-        txn = self.transactionUnderTest()
-        self.assertProvides(ICommonTransaction, txn)
-        self.assertProvides(IAddressBookTransaction, txn)
-
-
-    def test_homeProvides(self):
-        """
-        The addressbook homes generated by the addressbook store provide
-        L{IAddressBookHome} and its required attributes.
-        """
-        self.assertProvides(IAddressBookHome, self.homeUnderTest())
-
-
-    def test_addressbookProvides(self):
-        """
-        The addressbooks generated by the addressbook store provide L{IAddressBook} and
-        its required attributes.
-        """
-        self.assertProvides(IAddressBook, self.addressbookUnderTest())
-
-
-    def test_addressbookObjectProvides(self):
-        """
-        The addressbook objects generated by the addressbook store provide
-        L{IAddressBookObject} and its required attributes.
-        """
-        self.assertProvides(IAddressBookObject, self.addressbookObjectUnderTest())
-
-    def test_notifierID(self):
-        home = self.homeUnderTest()
-        self.assertEquals(home.notifierID(), "home1")
-        addressbook = home.addressbookWithName("addressbook_1")
-        self.assertEquals(addressbook.notifierID(), "home1")
-        self.assertEquals(addressbook.notifierID(label="collection"), "home1/addressbook_1")
-
-
-    def test_addressbookHomeWithUID_exists(self):
-        """
-        Finding an existing addressbook home by UID results in an object that
-        provides L{IAddressBookHome} and has a C{uid()} method that returns the
-        same value that was passed in.
-        """
-        addressbookHome = (self.transactionUnderTest()
-                        .addressbookHomeWithUID("home1"))
-        self.assertEquals(addressbookHome.uid(), "home1")
-        self.assertProvides(IAddressBookHome, addressbookHome)
-
-
-    def test_addressbookHomeWithUID_absent(self):
-        """
-        L{IAddressBookStoreTransaction.addressbookHomeWithUID} should return C{None}
-        when asked for a non-existent addressbook home.
-        """
-        txn = self.transactionUnderTest()
-        self.assertEquals(txn.addressbookHomeWithUID("xyzzy"), None)
-
-
-    def test_addressbookWithName_exists(self):
-        """
-        L{IAddressBookHome.addressbookWithName} returns an L{IAddressBook} provider,
-        whose name matches the one passed in.
-        """
-        home = self.homeUnderTest()
-        for name in home1_addressbookNames:
-            addressbook = home.addressbookWithName(name)
-            if addressbook is None:
-                self.fail("addressbook %r didn't exist" % (name,))
-            self.assertProvides(IAddressBook, addressbook)
-            self.assertEquals(addressbook.name(), name)
-
-
-    def test_addressbookRename(self):
-        """
-        L{IAddressBook.rename} changes the name of the L{IAddressBook}.
-        """
-        home = self.homeUnderTest()
-        addressbook = home.addressbookWithName("addressbook_1")
-        addressbook.rename("some_other_name")
-        def positiveAssertions():
-            self.assertEquals(addressbook.name(), "some_other_name")
-            self.assertEquals(addressbook, home.addressbookWithName("some_other_name"))
-            self.assertEquals(None, home.addressbookWithName("addressbook_1"))
-        positiveAssertions()
-        self.commit()
-        home = self.homeUnderTest()
-        addressbook = home.addressbookWithName("some_other_name")
-        positiveAssertions()
-        # FIXME: revert
-        # FIXME: test for multiple renames
-        # FIXME: test for conflicting renames (a->b, c->a in the same txn)
-
-
-    def test_addressbookWithName_absent(self):
-        """
-        L{IAddressBookHome.addressbookWithName} returns C{None} for addressbooks which
-        do not exist.
-        """
-        self.assertEquals(self.homeUnderTest().addressbookWithName("xyzzy"),
-                          None)
-
-
-    def test_createAddressBookWithName_absent(self):
-        """
-        L{IAddressBookHome.createAddressBookWithName} creates a new L{IAddressBook} that
-        can be retrieved with L{IAddressBookHome.addressbookWithName}.
-        """
-        home = self.homeUnderTest()
-        name = "new"
-        self.assertIdentical(home.addressbookWithName(name), None)
-        home.createAddressBookWithName(name)
-        self.assertNotIdentical(home.addressbookWithName(name), None)
-        def checkProperties():
-            addressbookProperties = home.addressbookWithName(name).properties()
-            self.assertEquals(
-                addressbookProperties[
-                    PropertyName.fromString(davxml.ResourceType.sname())
-                ],
-                davxml.ResourceType.addressbook
-            ) #@UndefinedVariable
-        checkProperties()
-        self.commit()
-
-        # Make sure notification fired after commit
-        self.assertEquals(self.notifierFactory.history, [("update", "home1")])
-
-        # Make sure it's available in a new transaction; i.e. test the commit.
-        home = self.homeUnderTest()
-        self.assertNotIdentical(home.addressbookWithName(name), None)
-
-        # FIXME: These two lines aren't in the calendar common tests:
-        # home = self.addressbookStore.newTransaction().addressbookHomeWithUID(
-        #     "home1")
-
-        # Sanity check: are the properties actually persisted?
-        # FIXME: no independent testing of this right now
-        checkProperties()
-
-
-    def test_createAddressBookWithName_exists(self):
-        """
-        L{IAddressBookHome.createAddressBookWithName} raises
-        L{AddressBookAlreadyExistsError} when the name conflicts with an already-
-        existing address book.
-        """
-        for name in home1_addressbookNames:
-            self.assertRaises(
-                HomeChildNameAlreadyExistsError,
-                self.homeUnderTest().createAddressBookWithName, name
-            )
-
-
-    def test_removeAddressBookWithName_exists(self):
-        """
-        L{IAddressBookHome.removeAddressBookWithName} removes a addressbook that already
-        exists.
-        """
-        home = self.homeUnderTest()
-        # FIXME: test transactions
-        for name in home1_addressbookNames:
-            self.assertNotIdentical(home.addressbookWithName(name), None)
-            home.removeAddressBookWithName(name)
-            self.assertEquals(home.addressbookWithName(name), None)
-
-        self.commit()
-
-        # Make sure notification fired after commit
-        self.assertEquals(
-            self.notifierFactory.history,
-            [("update", "home1"), ("update", "home1"), ("update", "home1")]
-        )
-
-
-    def test_removeAddressBookWithName_absent(self):
-        """
-        Attempt to remove an non-existing addressbook should raise.
-        """
-        home = self.homeUnderTest()
-        self.assertRaises(NoSuchHomeChildError,
-                          home.removeAddressBookWithName, "xyzzy")
-
-
-    def test_addressbookObjects(self):
-        """
-        L{IAddressBook.addressbookObjects} will enumerate the addressbook objects present
-        in the filesystem, in name order, but skip those with hidden names.
-        """
-        addressbook1 = self.addressbookUnderTest()
-        addressbookObjects = list(addressbook1.addressbookObjects())
-
-        for addressbookObject in addressbookObjects:
-            self.assertProvides(IAddressBookObject, addressbookObject)
-            self.assertEquals(
-                addressbook1.addressbookObjectWithName(addressbookObject.name()),
-                addressbookObject
-            )
-
-        self.assertEquals(
-            set(o.name() for o in addressbookObjects),
-            set(addressbook1_objectNames)
-        )
-
-
-    def test_addressbookObjectsWithRemovedObject(self):
-        """
-        L{IAddressBook.addressbookObjects} skips those objects which have been
-        removed by L{AddressBook.removeAddressBookObjectWithName} in the same
-        transaction, even if it has not yet been committed.
-        """
-        addressbook1 = self.addressbookUnderTest()
-        addressbook1.removeAddressBookObjectWithName("2.vcf")
-        addressbookObjects = list(addressbook1.addressbookObjects())
-        self.assertEquals(set(o.name() for o in addressbookObjects),
-                          set(addressbook1_objectNames) - set(["2.vcf"]))
-
-
-    def test_ownerAddressBookHome(self):
-        """
-        L{IAddressBook.ownerAddressBookHome} should match the home UID.
-        """
-        self.assertEquals(
-            self.addressbookUnderTest().ownerAddressBookHome().uid(),
-            self.homeUnderTest().uid()
-        )
-
-
-    def test_addressbookObjectWithName_exists(self):
-        """
-        L{IAddressBook.addressbookObjectWithName} returns an L{IAddressBookObject}
-        provider for addressbooks which already exist.
-        """
-        addressbook1 = self.addressbookUnderTest()
-        for name in addressbook1_objectNames:
-            addressbookObject = addressbook1.addressbookObjectWithName(name)
-            self.assertProvides(IAddressBookObject, addressbookObject)
-            self.assertEquals(addressbookObject.name(), name)
-            # FIXME: add more tests based on CommonTests.requirements
-
-
-    def test_addressbookObjectWithName_absent(self):
-        """
-        L{IAddressBook.addressbookObjectWithName} returns C{None} for addressbooks which
-        don't exist.
-        """
-        addressbook1 = self.addressbookUnderTest()
-        self.assertEquals(addressbook1.addressbookObjectWithName("xyzzy"), None)
-
-
-    def test_removeAddressBookObjectWithUID_exists(self):
-        """
-        Remove an existing addressbook object.
-        """
-        addressbook = self.addressbookUnderTest()
-        for name in addressbook1_objectNames:
-            uid = (u'uid' + name.rstrip(".vcf"))
-            self.assertNotIdentical(addressbook.addressbookObjectWithUID(uid),
-                                    None)
-            addressbook.removeAddressBookObjectWithUID(uid)
-            self.assertEquals(
-                addressbook.addressbookObjectWithUID(uid),
-                None
-            )
-            self.assertEquals(
-                addressbook.addressbookObjectWithName(name),
-                None
-            )
-
-
-    def test_removeAddressBookObjectWithName_exists(self):
-        """
-        Remove an existing addressbook object.
-        """
-        addressbook = self.addressbookUnderTest()
-        for name in addressbook1_objectNames:
-            self.assertNotIdentical(
-                addressbook.addressbookObjectWithName(name), None
-            )
-            addressbook.removeAddressBookObjectWithName(name)
-            self.assertIdentical(
-                addressbook.addressbookObjectWithName(name), None
-            )
-
-        # Make sure notifications are fired after commit
-        self.commit()
-        self.assertEquals(
-            self.notifierFactory.history,
-            [
-                ("update", "home1"),
-                ("update", "home1/addressbook_1"),
-                ("update", "home1"),
-                ("update", "home1/addressbook_1"),
-                ("update", "home1"),
-                ("update", "home1/addressbook_1"),
-            ]
-        )
-
-
-    def test_removeAddressBookObjectWithName_absent(self):
-        """
-        Attempt to remove an non-existing addressbook object should raise.
-        """
-        addressbook = self.addressbookUnderTest()
-        self.assertRaises(
-            NoSuchObjectResourceError,
-            addressbook.removeAddressBookObjectWithName, "xyzzy"
-        )
-
-
-    def test_addressbookName(self):
-        """
-        L{AddressBook.name} reflects the name of the addressbook.
-        """
-        self.assertEquals(self.addressbookUnderTest().name(), "addressbook_1")
-
-
-    def test_addressbookObjectName(self):
-        """
-        L{IAddressBookObject.name} reflects the name of the addressbook object.
-        """
-        self.assertEquals(self.addressbookObjectUnderTest().name(), "1.vcf")
-
-
-    def test_component(self):
-        """
-        L{IAddressBookObject.component} returns a L{VComponent} describing the
-        addressbook data underlying that addressbook object.
-        """
-        component = self.addressbookObjectUnderTest().component()
-
-        self.failUnless(
-            isinstance(component, VComponent),
-            component
-        )
-
-        self.assertEquals(component.name(), "VCARD")
-        self.assertEquals(component.resourceUID(), "uid1")
-
-
-    def test_iAddressBookText(self):
-        """
-        L{IAddressBookObject.iAddressBookText} returns a C{str} describing the same
-        data provided by L{IAddressBookObject.component}.
-        """
-        text = self.addressbookObjectUnderTest().vCardText()
-        self.assertIsInstance(text, str)
-        self.failUnless(text.startswith("BEGIN:VCARD\r\n"))
-        self.assertIn("\r\nUID:uid1\r\n", text)
-        self.failUnless(text.endswith("\r\nEND:VCARD\r\n"))
-
-
-    def test_addressbookObjectUID(self):
-        """
-        L{IAddressBookObject.uid} returns a C{str} describing the C{UID} property
-        of the addressbook object's component.
-        """
-        self.assertEquals(self.addressbookObjectUnderTest().uid(), "uid1")
-
-
-    def test_addressbookObjectWithUID_absent(self):
-        """
-        L{IAddressBook.addressbookObjectWithUID} returns C{None} for addressbooks which
-        don't exist.
-        """
-        addressbook1 = self.addressbookUnderTest()
-        self.assertEquals(addressbook1.addressbookObjectWithUID("xyzzy"), None)
-
-
-    def test_addressbooks(self):
-        """
-        L{IAddressBookHome.addressbooks} returns an iterable of L{IAddressBook}
-        providers, which are consistent with the results from
-        L{IAddressBook.addressbookWithName}.
-        """
-        # Add a dot directory to make sure we don't find it
-        # self.home1._path.child(".foo").createDirectory()
-        home = self.homeUnderTest()
-        addressbooks = list(home.addressbooks())
-
-        for addressbook in addressbooks:
-            self.assertProvides(IAddressBook, addressbook)
-            self.assertEquals(addressbook,
-                              home.addressbookWithName(addressbook.name()))
-
-        self.assertEquals(
-            set(c.name() for c in addressbooks),
-            set(home1_addressbookNames)
-        )
-
-
-    def test_addressbooksAfterAddAddressBook(self):
-        """
-        L{IAddressBookHome.addressbooks} includes addressbooks recently added with
-        L{IAddressBookHome.createAddressBookWithName}.
-        """
-        home = self.homeUnderTest()
-        before = set(x.name() for x in home.addressbooks())
-        home.createAddressBookWithName("new-name")
-        after = set(x.name() for x in home.addressbooks())
-        self.assertEquals(before | set(['new-name']), after)
-
-
-    def test_createAddressBookObjectWithName_absent(self):
-        """
-        L{IAddressBook.createAddressBookObjectWithName} creates a new
-        L{IAddressBookObject}.
-        """
-        addressbook1 = self.addressbookUnderTest()
-        name = "4.vcf"
-        self.assertIdentical(addressbook1.addressbookObjectWithName(name), None)
-        component = VComponent.fromString(vcard4_text)
-        addressbook1.createAddressBookObjectWithName(name, component)
-
-        addressbookObject = addressbook1.addressbookObjectWithName(name)
-        self.assertEquals(addressbookObject.component(), component)
-
-        self.commit()
-
-        # Make sure notifications fire after commit
-        self.assertEquals(
-            self.notifierFactory.history,
-            [
-                ("update", "home1"),
-                ("update", "home1/addressbook_1"),
-            ]
-        )
-
-
-    def test_createAddressBookObjectWithName_exists(self):
-        """
-        L{IAddressBook.createAddressBookObjectWithName} raises
-        L{AddressBookObjectNameAlreadyExistsError} if a addressbook object with the
-        given name already exists in that addressbook.
-        """
-        self.assertRaises(
-            ObjectResourceNameAlreadyExistsError,
-            self.addressbookUnderTest().createAddressBookObjectWithName,
-            "1.vcf", VComponent.fromString(vcard4_text)
-        )
-
-
-    def test_createAddressBookObjectWithName_invalid(self):
-        """
-        L{IAddressBook.createAddressBookObjectWithName} raises
-        L{InvalidAddressBookComponentError} if presented with invalid iAddressBook
-        text.
-        """
-        self.assertRaises(
-            InvalidObjectResourceError,
-            self.addressbookUnderTest().createAddressBookObjectWithName,
-            "new", VComponent.fromString(vcard4notCardDAV_text)
-        )
-
-
-    def test_setComponent_invalid(self):
-        """
-        L{IAddressBookObject.setComponent} raises L{InvalidIAddressBookDataError} if
-        presented with invalid iAddressBook text.
-        """
-        addressbookObject = self.addressbookObjectUnderTest()
-        self.assertRaises(
-            InvalidObjectResourceError,
-            addressbookObject.setComponent,
-            VComponent.fromString(vcard4notCardDAV_text)
-        )
-
-
-    def test_setComponent_uidchanged(self):
-        """
-        L{IAddressBookObject.setComponent} raises L{InvalidAddressBookComponentError}
-        when given a L{VComponent} whose UID does not match its existing UID.
-        """
-        addressbook1 = self.addressbookUnderTest()
-        component = VComponent.fromString(vcard4_text)
-        addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
-        self.assertRaises(
-            InvalidObjectResourceError,
-            addressbookObject.setComponent, component
-        )
-
-
-    def test_addressbookHomeWithUID_create(self):
-        """
-        L{IAddressBookStoreTransaction.addressbookHomeWithUID} with C{create=True}
-        will create a addressbook home that doesn't exist yet.
-        """
-        txn = self.transactionUnderTest()
-        noHomeUID = "xyzzy"
-        addressbookHome = txn.addressbookHomeWithUID(
-            noHomeUID,
-            create=True
-        )
-        def readOtherTxn():
-            otherTxn = self.savedStore.newTransaction()
-            self.addCleanup(otherTxn.commit)
-            return otherTxn.addressbookHomeWithUID(noHomeUID)
-        self.assertProvides(IAddressBookHome, addressbookHome)
-        # A concurrent transaction shouldn't be able to read it yet:
-        self.assertIdentical(readOtherTxn(), None)
-        self.commit()
-        # But once it's committed, other transactions should see it.
-        self.assertProvides(IAddressBookHome, readOtherTxn())
-
-
-    def test_setComponent(self):
-        """
-        L{AddressBookObject.setComponent} changes the result of
-        L{AddressBookObject.component} within the same transaction.
-        """
-        component = VComponent.fromString(vcard1modified_text)
-
-        addressbook1 = self.addressbookUnderTest()
-        addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
-        oldComponent = addressbookObject.component()
-        self.assertNotEqual(component, oldComponent)
-        addressbookObject.setComponent(component)
-        self.assertEquals(addressbookObject.component(), component)
-
-        # Also check a new instance
-        addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
-        self.assertEquals(addressbookObject.component(), component)
-
-        self.commit()
-
-        # Make sure notification fired after commit
-        self.assertEquals(
-            self.notifierFactory.history,
-            [
-                ("update", "home1"),
-                ("update", "home1/addressbook_1"),
-            ]
-        )
-
-    def checkPropertiesMethod(self, thunk):
-        """
-        Verify that the given object has a properties method that returns an
-        L{IPropertyStore}.
-        """
-        properties = thunk.properties()
-        self.assertProvides(IPropertyStore, properties)
-
-
-    def test_homeProperties(self):
-        """
-        L{IAddressBookHome.properties} returns a property store.
-        """
-        self.checkPropertiesMethod(self.homeUnderTest())
-
-
-    def test_addressbookProperties(self):
-        """
-        L{IAddressBook.properties} returns a property store.
-        """
-        self.checkPropertiesMethod(self.addressbookUnderTest())
-
-
-    def test_addressbookObjectProperties(self):
-        """
-        L{IAddressBookObject.properties} returns a property store.
-        """
-        self.checkPropertiesMethod(self.addressbookObjectUnderTest())
-
-
-    def test_newAddressBookObjectProperties(self):
-        """
-        L{IAddressBookObject.properties} returns an empty property store for a
-        addressbook object which has been created but not committed.
-        """
-        addressbook = self.addressbookUnderTest()
-        addressbook.createAddressBookObjectWithName(
-            "4.vcf", VComponent.fromString(vcard4_text)
-        )
-        newEvent = addressbook.addressbookObjectWithName("4.vcf")
-        self.assertEquals(newEvent.properties().items(), [])
-
-
-    def test_setComponentPreservesProperties(self):
-        """
-        L{IAddressBookObject.setComponent} preserves properties.
-
-        (Some implementations must go to extra trouble to provide this
-        behavior; for example, file storage must copy extended attributes from
-        the existing file to the temporary file replacing it.)
-        """
-        propertyName = PropertyName("http://example.com/ns", "example")
-        propertyContent = WebDAVUnknownElement("sample content")
-        propertyContent.name = propertyName.name
-        propertyContent.namespace = propertyName.namespace
-
-        self.addressbookObjectUnderTest().properties()[
-            propertyName] = propertyContent
-        self.commit()
-        # Sanity check; are properties even readable in a separate transaction?
-        # Should probably be a separate test.
-        self.assertEquals(
-            self.addressbookObjectUnderTest().properties()[propertyName],
-            propertyContent)
-        obj = self.addressbookObjectUnderTest()
-        vcard1_text = obj.vCardText()
-        vcard1_text_withDifferentNote = vcard1_text.replace(
-            "NOTE:CardDAV protocol updates",
-            "NOTE:Changed"
-        )
-        # Sanity check; make sure the test has the right idea of the subject.
-        self.assertNotEquals(vcard1_text, vcard1_text_withDifferentNote)
-        newComponent = VComponent.fromString(vcard1_text_withDifferentNote)
-        obj.setComponent(newComponent)
-
-        # Putting everything into a separate transaction to account for any
-        # caching that may take place.
-        self.commit()
-        self.assertEquals(
-            self.addressbookObjectUnderTest().properties()[propertyName],
-            propertyContent
-        )
-
-
-    def test_dontLeakAddressbooks(self):
-        """
-        Addressbooks in one user's addressbook home should not show up in another
-        user's addressbook home.
-        """
-        home2 = self.transactionUnderTest().addressbookHomeWithUID(
-            "home2", create=True)
-        self.assertIdentical(home2.addressbookWithName("addressbook_1"), None)
-
-
-    def test_dontLeakObjects(self):
-        """
-        Addressbook objects in one user's addressbook should not show up in another
-        user's via uid or name queries.
-        """
-        home1 = self.homeUnderTest()
-        home2 = self.transactionUnderTest().addressbookHomeWithUID(
-            "home2", create=True)
-        addressbook1 = home1.addressbookWithName("addressbook_1")
-        addressbook2 = home2.addressbookWithName("addressbook")
-        objects = list(home2.addressbookWithName("addressbook").addressbookObjects())
-        self.assertEquals(objects, [])
-        for resourceName in self.requirements['home1']['addressbook_1'].keys():
-            obj = addressbook1.addressbookObjectWithName(resourceName)
-            self.assertIdentical(
-                addressbook2.addressbookObjectWithName(resourceName), None)
-            self.assertIdentical(
-                addressbook2.addressbookObjectWithUID(obj.uid()), None)
-
-
-
-class StubNotifierFactory(object):
-
-    """ For testing push notifications without an XMPP server """
-
-    def __init__(self):
-        self.reset()
-
-    def newNotifier(self, label="default", id=None):
-        return Notifier(self, label=label, id=id)
-
-    def send(self, op, id):
-        self.history.append((op, id))
-
-    def reset(self):
-        self.history = []

Copied: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py (from rev 6185, CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/common.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,897 @@
+# -*- test-case-name: txdav.carddav.datastore,txdav.caldav.datastore.test.test_postgres.AddressBookSQLStorageTests -*-
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+"""
+Tests for common addressbook store API functions.
+"""
+
+from zope.interface.verify import verifyObject
+from zope.interface.exceptions import (
+    BrokenMethodImplementation, DoesNotImplement
+)
+
+from txdav.idav import IPropertyStore, IDataStore
+from txdav.base.propertystore.base import PropertyName
+
+from txdav.common.icommondatastore import (
+    HomeChildNameAlreadyExistsError, ICommonTransaction
+)
+from txdav.common.icommondatastore import InvalidObjectResourceError
+from txdav.common.icommondatastore import NoSuchHomeChildError
+from txdav.common.icommondatastore import NoSuchObjectResourceError
+from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
+
+from txdav.carddav.iaddressbookstore import (
+    IAddressBookObject, IAddressBookHome,
+    IAddressBook, IAddressBookTransaction
+)
+from twistedcaldav.vcard import Component as VComponent
+from twistedcaldav.notify import Notifier
+
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import WebDAVUnknownElement
+
+
+storePath = FilePath(__file__).parent().child("addressbook_store")
+
+homeRoot = storePath.child("ho").child("me").child("home1")
+
+adbk1Root = homeRoot.child("addressbook_1")
+
+addressbook1_objectNames = [
+    "1.vcf",
+    "2.vcf",
+    "3.vcf",
+]
+
+
+home1_addressbookNames = [
+    "addressbook_1",
+    "addressbook_2",
+    "addressbook_empty",
+]
+
+
+vcard4_text = (
+    """BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default;;;
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at example.com
+TEL;type=WORK;type=pref:1-555-555-5555
+TEL;type=CELL:1-444-444-4444
+item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA
+item1.X-ABADR:us
+UID:uid4
+END:VCARD
+""".replace("\n", "\r\n")
+)
+
+
+
+vcard4notCardDAV_text = ( # Missing UID, N and FN
+"""BEGIN:VCARD
+VERSION:3.0
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at example.com
+TEL;type=WORK;type=pref:1-555-555-5555
+TEL;type=CELL:1-444-444-4444
+item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA
+item1.X-ABADR:us
+END:VCARD
+""".replace("\n", "\r\n")
+)
+
+
+
+vcard1modified_text = vcard4_text.replace(
+    "\r\nUID:uid4\r\n",
+    "\r\nUID:uid1\r\n"
+)
+
+
+def assertProvides(testCase, interface, provider):
+    """
+    Verify that C{provider} properly provides C{interface}
+
+    @type interface: L{zope.interface.Interface}
+    @type provider: C{provider}
+    """
+    try:
+        verifyObject(interface, provider)
+    except BrokenMethodImplementation, e:
+        testCase.fail(e)
+    except DoesNotImplement, e:
+        testCase.fail("%r does not provide %s.%s" %
+                      (provider, interface.__module__, interface.getName()))
+
+
+
+class CommonTests(object):
+    """
+    Tests for common functionality of interfaces defined in
+    L{txdav.carddav.iaddressbookstore}.
+    """
+
+    requirements = {
+        "home1": {
+            "addressbook_1": {
+                "1.vcf": adbk1Root.child("1.vcf").getContent(),
+                "2.vcf": adbk1Root.child("2.vcf").getContent(),
+                "3.vcf": adbk1Root.child("3.vcf").getContent()
+            },
+            "addressbook_2": {},
+            "addressbook_empty": {},
+            "not_a_addressbook": None
+        },
+        "not_a_home": None
+    }
+
+    def storeUnderTest(self):
+        """
+        Subclasses must override this to return an L{IAddressBookStore}
+        provider which adheres to the structure detailed by
+        L{CommonTests.requirements}. This attribute is a dict of dict of dicts;
+        the outermost layer representing UIDs mapping to addressbook homes,
+        then addressbook names mapping to addressbook collections, and finally
+        addressbook object names mapping to addressbook object text.
+        """
+        raise NotImplementedError()
+
+
+    lastTransaction = None
+    savedStore = None
+
+    def transactionUnderTest(self):
+        """
+        Create a transaction from C{storeUnderTest} and save it as
+        C[lastTransaction}.  Also makes sure to use the same store, saving the
+        value from C{storeUnderTest}.
+        """
+        if self.lastTransaction is not None:
+            return self.lastTransaction
+        if self.savedStore is None:
+            self.savedStore = self.storeUnderTest()
+        txn = self.lastTransaction = self.savedStore.newTransaction(self.id())
+        return txn
+
+
+    def commit(self):
+        """
+        Commit the last transaction created from C{transactionUnderTest}, and
+        clear it.
+        """
+        self.lastTransaction.commit()
+        self.lastTransaction = None
+
+
+    def abort(self):
+        """
+        Abort the last transaction created from C[transactionUnderTest}, and
+        clear it.
+        """
+        self.lastTransaction.abort()
+        self.lastTransaction = None
+
+    def setUp(self):
+        self.notifierFactory = StubNotifierFactory()
+
+    def tearDown(self):
+        if self.lastTransaction is not None:
+            self.commit()
+
+
+
+    def homeUnderTest(self):
+        """
+        Get the addressbook home detailed by C{requirements['home1']}.
+        """
+        return self.transactionUnderTest().addressbookHomeWithUID("home1")
+
+
+    def addressbookUnderTest(self):
+        """
+        Get the addressbook detailed by C{requirements['home1']['addressbook_1']}.
+        """
+        return self.homeUnderTest().addressbookWithName("addressbook_1")
+
+
+    def addressbookObjectUnderTest(self):
+        """
+        Get the addressbook detailed by
+        C{requirements['home1']['addressbook_1']['1.vcf']}.
+        """
+        return self.addressbookUnderTest().addressbookObjectWithName("1.vcf")
+
+
+    assertProvides = assertProvides
+
+
+    def test_addressbookStoreProvides(self):
+        """
+        The addressbook store provides L{IAddressBookStore} and its required
+        attributes.
+        """
+        addressbookStore = self.storeUnderTest()
+        self.assertProvides(IDataStore, addressbookStore)
+
+
+    def test_transactionProvides(self):
+        """
+        The transactions generated by the addressbook store provide
+        L{IAddressBookStoreTransaction} and its required attributes.
+        """
+        txn = self.transactionUnderTest()
+        self.assertProvides(ICommonTransaction, txn)
+        self.assertProvides(IAddressBookTransaction, txn)
+
+
+    def test_homeProvides(self):
+        """
+        The addressbook homes generated by the addressbook store provide
+        L{IAddressBookHome} and its required attributes.
+        """
+        self.assertProvides(IAddressBookHome, self.homeUnderTest())
+
+
+    def test_addressbookProvides(self):
+        """
+        The addressbooks generated by the addressbook store provide L{IAddressBook} and
+        its required attributes.
+        """
+        self.assertProvides(IAddressBook, self.addressbookUnderTest())
+
+
+    def test_addressbookObjectProvides(self):
+        """
+        The addressbook objects generated by the addressbook store provide
+        L{IAddressBookObject} and its required attributes.
+        """
+        self.assertProvides(IAddressBookObject, self.addressbookObjectUnderTest())
+
+    def test_notifierID(self):
+        home = self.homeUnderTest()
+        self.assertEquals(home.notifierID(), "home1")
+        addressbook = home.addressbookWithName("addressbook_1")
+        self.assertEquals(addressbook.notifierID(), "home1")
+        self.assertEquals(addressbook.notifierID(label="collection"), "home1/addressbook_1")
+
+
+    def test_addressbookHomeWithUID_exists(self):
+        """
+        Finding an existing addressbook home by UID results in an object that
+        provides L{IAddressBookHome} and has a C{uid()} method that returns the
+        same value that was passed in.
+        """
+        addressbookHome = (self.transactionUnderTest()
+                        .addressbookHomeWithUID("home1"))
+        self.assertEquals(addressbookHome.uid(), "home1")
+        self.assertProvides(IAddressBookHome, addressbookHome)
+
+
+    def test_addressbookHomeWithUID_absent(self):
+        """
+        L{IAddressBookStoreTransaction.addressbookHomeWithUID} should return C{None}
+        when asked for a non-existent addressbook home.
+        """
+        txn = self.transactionUnderTest()
+        self.assertEquals(txn.addressbookHomeWithUID("xyzzy"), None)
+
+
+    def test_addressbookWithName_exists(self):
+        """
+        L{IAddressBookHome.addressbookWithName} returns an L{IAddressBook} provider,
+        whose name matches the one passed in.
+        """
+        home = self.homeUnderTest()
+        for name in home1_addressbookNames:
+            addressbook = home.addressbookWithName(name)
+            if addressbook is None:
+                self.fail("addressbook %r didn't exist" % (name,))
+            self.assertProvides(IAddressBook, addressbook)
+            self.assertEquals(addressbook.name(), name)
+
+
+    def test_addressbookRename(self):
+        """
+        L{IAddressBook.rename} changes the name of the L{IAddressBook}.
+        """
+        home = self.homeUnderTest()
+        addressbook = home.addressbookWithName("addressbook_1")
+        addressbook.rename("some_other_name")
+        def positiveAssertions():
+            self.assertEquals(addressbook.name(), "some_other_name")
+            self.assertEquals(addressbook, home.addressbookWithName("some_other_name"))
+            self.assertEquals(None, home.addressbookWithName("addressbook_1"))
+        positiveAssertions()
+        self.commit()
+        home = self.homeUnderTest()
+        addressbook = home.addressbookWithName("some_other_name")
+        positiveAssertions()
+        # FIXME: revert
+        # FIXME: test for multiple renames
+        # FIXME: test for conflicting renames (a->b, c->a in the same txn)
+
+
+    def test_addressbookWithName_absent(self):
+        """
+        L{IAddressBookHome.addressbookWithName} returns C{None} for addressbooks which
+        do not exist.
+        """
+        self.assertEquals(self.homeUnderTest().addressbookWithName("xyzzy"),
+                          None)
+
+
+    def test_createAddressBookWithName_absent(self):
+        """
+        L{IAddressBookHome.createAddressBookWithName} creates a new L{IAddressBook} that
+        can be retrieved with L{IAddressBookHome.addressbookWithName}.
+        """
+        home = self.homeUnderTest()
+        name = "new"
+        self.assertIdentical(home.addressbookWithName(name), None)
+        home.createAddressBookWithName(name)
+        self.assertNotIdentical(home.addressbookWithName(name), None)
+        def checkProperties():
+            addressbookProperties = home.addressbookWithName(name).properties()
+            self.assertEquals(
+                addressbookProperties[
+                    PropertyName.fromString(davxml.ResourceType.sname())
+                ],
+                davxml.ResourceType.addressbook
+            ) #@UndefinedVariable
+        checkProperties()
+        self.commit()
+
+        # Make sure notification fired after commit
+        self.assertEquals(self.notifierFactory.history, [("update", "home1")])
+
+        # Make sure it's available in a new transaction; i.e. test the commit.
+        home = self.homeUnderTest()
+        self.assertNotIdentical(home.addressbookWithName(name), None)
+
+        # FIXME: These two lines aren't in the calendar common tests:
+        # home = self.addressbookStore.newTransaction().addressbookHomeWithUID(
+        #     "home1")
+
+        # Sanity check: are the properties actually persisted?
+        # FIXME: no independent testing of this right now
+        checkProperties()
+
+
+    def test_createAddressBookWithName_exists(self):
+        """
+        L{IAddressBookHome.createAddressBookWithName} raises
+        L{AddressBookAlreadyExistsError} when the name conflicts with an already-
+        existing address book.
+        """
+        for name in home1_addressbookNames:
+            self.assertRaises(
+                HomeChildNameAlreadyExistsError,
+                self.homeUnderTest().createAddressBookWithName, name
+            )
+
+
+    def test_removeAddressBookWithName_exists(self):
+        """
+        L{IAddressBookHome.removeAddressBookWithName} removes a addressbook that already
+        exists.
+        """
+        home = self.homeUnderTest()
+        # FIXME: test transactions
+        for name in home1_addressbookNames:
+            self.assertNotIdentical(home.addressbookWithName(name), None)
+            home.removeAddressBookWithName(name)
+            self.assertEquals(home.addressbookWithName(name), None)
+
+        self.commit()
+
+        # Make sure notification fired after commit
+        self.assertEquals(
+            self.notifierFactory.history,
+            [("update", "home1"), ("update", "home1"), ("update", "home1")]
+        )
+
+
+    def test_removeAddressBookWithName_absent(self):
+        """
+        Attempt to remove an non-existing addressbook should raise.
+        """
+        home = self.homeUnderTest()
+        self.assertRaises(NoSuchHomeChildError,
+                          home.removeAddressBookWithName, "xyzzy")
+
+
+    def test_addressbookObjects(self):
+        """
+        L{IAddressBook.addressbookObjects} will enumerate the addressbook objects present
+        in the filesystem, in name order, but skip those with hidden names.
+        """
+        addressbook1 = self.addressbookUnderTest()
+        addressbookObjects = list(addressbook1.addressbookObjects())
+
+        for addressbookObject in addressbookObjects:
+            self.assertProvides(IAddressBookObject, addressbookObject)
+            self.assertEquals(
+                addressbook1.addressbookObjectWithName(addressbookObject.name()),
+                addressbookObject
+            )
+
+        self.assertEquals(
+            set(o.name() for o in addressbookObjects),
+            set(addressbook1_objectNames)
+        )
+
+
+    def test_addressbookObjectsWithRemovedObject(self):
+        """
+        L{IAddressBook.addressbookObjects} skips those objects which have been
+        removed by L{AddressBook.removeAddressBookObjectWithName} in the same
+        transaction, even if it has not yet been committed.
+        """
+        addressbook1 = self.addressbookUnderTest()
+        addressbook1.removeAddressBookObjectWithName("2.vcf")
+        addressbookObjects = list(addressbook1.addressbookObjects())
+        self.assertEquals(set(o.name() for o in addressbookObjects),
+                          set(addressbook1_objectNames) - set(["2.vcf"]))
+
+
+    def test_ownerAddressBookHome(self):
+        """
+        L{IAddressBook.ownerAddressBookHome} should match the home UID.
+        """
+        self.assertEquals(
+            self.addressbookUnderTest().ownerAddressBookHome().uid(),
+            self.homeUnderTest().uid()
+        )
+
+
+    def test_addressbookObjectWithName_exists(self):
+        """
+        L{IAddressBook.addressbookObjectWithName} returns an L{IAddressBookObject}
+        provider for addressbooks which already exist.
+        """
+        addressbook1 = self.addressbookUnderTest()
+        for name in addressbook1_objectNames:
+            addressbookObject = addressbook1.addressbookObjectWithName(name)
+            self.assertProvides(IAddressBookObject, addressbookObject)
+            self.assertEquals(addressbookObject.name(), name)
+            # FIXME: add more tests based on CommonTests.requirements
+
+
+    def test_addressbookObjectWithName_absent(self):
+        """
+        L{IAddressBook.addressbookObjectWithName} returns C{None} for addressbooks which
+        don't exist.
+        """
+        addressbook1 = self.addressbookUnderTest()
+        self.assertEquals(addressbook1.addressbookObjectWithName("xyzzy"), None)
+
+
+    def test_removeAddressBookObjectWithUID_exists(self):
+        """
+        Remove an existing addressbook object.
+        """
+        addressbook = self.addressbookUnderTest()
+        for name in addressbook1_objectNames:
+            uid = (u'uid' + name.rstrip(".vcf"))
+            self.assertNotIdentical(addressbook.addressbookObjectWithUID(uid),
+                                    None)
+            addressbook.removeAddressBookObjectWithUID(uid)
+            self.assertEquals(
+                addressbook.addressbookObjectWithUID(uid),
+                None
+            )
+            self.assertEquals(
+                addressbook.addressbookObjectWithName(name),
+                None
+            )
+
+
+    def test_removeAddressBookObjectWithName_exists(self):
+        """
+        Remove an existing addressbook object.
+        """
+        addressbook = self.addressbookUnderTest()
+        for name in addressbook1_objectNames:
+            self.assertNotIdentical(
+                addressbook.addressbookObjectWithName(name), None
+            )
+            addressbook.removeAddressBookObjectWithName(name)
+            self.assertIdentical(
+                addressbook.addressbookObjectWithName(name), None
+            )
+
+        # Make sure notifications are fired after commit
+        self.commit()
+        self.assertEquals(
+            self.notifierFactory.history,
+            [
+                ("update", "home1"),
+                ("update", "home1/addressbook_1"),
+                ("update", "home1"),
+                ("update", "home1/addressbook_1"),
+                ("update", "home1"),
+                ("update", "home1/addressbook_1"),
+            ]
+        )
+
+
+    def test_removeAddressBookObjectWithName_absent(self):
+        """
+        Attempt to remove an non-existing addressbook object should raise.
+        """
+        addressbook = self.addressbookUnderTest()
+        self.assertRaises(
+            NoSuchObjectResourceError,
+            addressbook.removeAddressBookObjectWithName, "xyzzy"
+        )
+
+
+    def test_addressbookName(self):
+        """
+        L{AddressBook.name} reflects the name of the addressbook.
+        """
+        self.assertEquals(self.addressbookUnderTest().name(), "addressbook_1")
+
+
+    def test_addressbookObjectName(self):
+        """
+        L{IAddressBookObject.name} reflects the name of the addressbook object.
+        """
+        self.assertEquals(self.addressbookObjectUnderTest().name(), "1.vcf")
+
+
+    def test_component(self):
+        """
+        L{IAddressBookObject.component} returns a L{VComponent} describing the
+        addressbook data underlying that addressbook object.
+        """
+        component = self.addressbookObjectUnderTest().component()
+
+        self.failUnless(
+            isinstance(component, VComponent),
+            component
+        )
+
+        self.assertEquals(component.name(), "VCARD")
+        self.assertEquals(component.resourceUID(), "uid1")
+
+
+    def test_iAddressBookText(self):
+        """
+        L{IAddressBookObject.iAddressBookText} returns a C{str} describing the same
+        data provided by L{IAddressBookObject.component}.
+        """
+        text = self.addressbookObjectUnderTest().vCardText()
+        self.assertIsInstance(text, str)
+        self.failUnless(text.startswith("BEGIN:VCARD\r\n"))
+        self.assertIn("\r\nUID:uid1\r\n", text)
+        self.failUnless(text.endswith("\r\nEND:VCARD\r\n"))
+
+
+    def test_addressbookObjectUID(self):
+        """
+        L{IAddressBookObject.uid} returns a C{str} describing the C{UID} property
+        of the addressbook object's component.
+        """
+        self.assertEquals(self.addressbookObjectUnderTest().uid(), "uid1")
+
+
+    def test_addressbookObjectWithUID_absent(self):
+        """
+        L{IAddressBook.addressbookObjectWithUID} returns C{None} for addressbooks which
+        don't exist.
+        """
+        addressbook1 = self.addressbookUnderTest()
+        self.assertEquals(addressbook1.addressbookObjectWithUID("xyzzy"), None)
+
+
+    def test_addressbooks(self):
+        """
+        L{IAddressBookHome.addressbooks} returns an iterable of L{IAddressBook}
+        providers, which are consistent with the results from
+        L{IAddressBook.addressbookWithName}.
+        """
+        # Add a dot directory to make sure we don't find it
+        # self.home1._path.child(".foo").createDirectory()
+        home = self.homeUnderTest()
+        addressbooks = list(home.addressbooks())
+
+        for addressbook in addressbooks:
+            self.assertProvides(IAddressBook, addressbook)
+            self.assertEquals(addressbook,
+                              home.addressbookWithName(addressbook.name()))
+
+        self.assertEquals(
+            set(c.name() for c in addressbooks),
+            set(home1_addressbookNames)
+        )
+
+
+    def test_addressbooksAfterAddAddressBook(self):
+        """
+        L{IAddressBookHome.addressbooks} includes addressbooks recently added with
+        L{IAddressBookHome.createAddressBookWithName}.
+        """
+        home = self.homeUnderTest()
+        before = set(x.name() for x in home.addressbooks())
+        home.createAddressBookWithName("new-name")
+        after = set(x.name() for x in home.addressbooks())
+        self.assertEquals(before | set(['new-name']), after)
+
+
+    def test_createAddressBookObjectWithName_absent(self):
+        """
+        L{IAddressBook.createAddressBookObjectWithName} creates a new
+        L{IAddressBookObject}.
+        """
+        addressbook1 = self.addressbookUnderTest()
+        name = "4.vcf"
+        self.assertIdentical(addressbook1.addressbookObjectWithName(name), None)
+        component = VComponent.fromString(vcard4_text)
+        addressbook1.createAddressBookObjectWithName(name, component)
+
+        addressbookObject = addressbook1.addressbookObjectWithName(name)
+        self.assertEquals(addressbookObject.component(), component)
+
+        self.commit()
+
+        # Make sure notifications fire after commit
+        self.assertEquals(
+            self.notifierFactory.history,
+            [
+                ("update", "home1"),
+                ("update", "home1/addressbook_1"),
+            ]
+        )
+
+
+    def test_createAddressBookObjectWithName_exists(self):
+        """
+        L{IAddressBook.createAddressBookObjectWithName} raises
+        L{AddressBookObjectNameAlreadyExistsError} if a addressbook object with the
+        given name already exists in that addressbook.
+        """
+        self.assertRaises(
+            ObjectResourceNameAlreadyExistsError,
+            self.addressbookUnderTest().createAddressBookObjectWithName,
+            "1.vcf", VComponent.fromString(vcard4_text)
+        )
+
+
+    def test_createAddressBookObjectWithName_invalid(self):
+        """
+        L{IAddressBook.createAddressBookObjectWithName} raises
+        L{InvalidAddressBookComponentError} if presented with invalid iAddressBook
+        text.
+        """
+        self.assertRaises(
+            InvalidObjectResourceError,
+            self.addressbookUnderTest().createAddressBookObjectWithName,
+            "new", VComponent.fromString(vcard4notCardDAV_text)
+        )
+
+
+    def test_setComponent_invalid(self):
+        """
+        L{IAddressBookObject.setComponent} raises L{InvalidIAddressBookDataError} if
+        presented with invalid iAddressBook text.
+        """
+        addressbookObject = self.addressbookObjectUnderTest()
+        self.assertRaises(
+            InvalidObjectResourceError,
+            addressbookObject.setComponent,
+            VComponent.fromString(vcard4notCardDAV_text)
+        )
+
+
+    def test_setComponent_uidchanged(self):
+        """
+        L{IAddressBookObject.setComponent} raises L{InvalidAddressBookComponentError}
+        when given a L{VComponent} whose UID does not match its existing UID.
+        """
+        addressbook1 = self.addressbookUnderTest()
+        component = VComponent.fromString(vcard4_text)
+        addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
+        self.assertRaises(
+            InvalidObjectResourceError,
+            addressbookObject.setComponent, component
+        )
+
+
+    def test_addressbookHomeWithUID_create(self):
+        """
+        L{IAddressBookStoreTransaction.addressbookHomeWithUID} with C{create=True}
+        will create a addressbook home that doesn't exist yet.
+        """
+        txn = self.transactionUnderTest()
+        noHomeUID = "xyzzy"
+        addressbookHome = txn.addressbookHomeWithUID(
+            noHomeUID,
+            create=True
+        )
+        def readOtherTxn():
+            otherTxn = self.savedStore.newTransaction()
+            self.addCleanup(otherTxn.commit)
+            return otherTxn.addressbookHomeWithUID(noHomeUID)
+        self.assertProvides(IAddressBookHome, addressbookHome)
+        # A concurrent transaction shouldn't be able to read it yet:
+        self.assertIdentical(readOtherTxn(), None)
+        self.commit()
+        # But once it's committed, other transactions should see it.
+        self.assertProvides(IAddressBookHome, readOtherTxn())
+
+
+    def test_setComponent(self):
+        """
+        L{AddressBookObject.setComponent} changes the result of
+        L{AddressBookObject.component} within the same transaction.
+        """
+        component = VComponent.fromString(vcard1modified_text)
+
+        addressbook1 = self.addressbookUnderTest()
+        addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
+        oldComponent = addressbookObject.component()
+        self.assertNotEqual(component, oldComponent)
+        addressbookObject.setComponent(component)
+        self.assertEquals(addressbookObject.component(), component)
+
+        # Also check a new instance
+        addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
+        self.assertEquals(addressbookObject.component(), component)
+
+        self.commit()
+
+        # Make sure notification fired after commit
+        self.assertEquals(
+            self.notifierFactory.history,
+            [
+                ("update", "home1"),
+                ("update", "home1/addressbook_1"),
+            ]
+        )
+
+    def checkPropertiesMethod(self, thunk):
+        """
+        Verify that the given object has a properties method that returns an
+        L{IPropertyStore}.
+        """
+        properties = thunk.properties()
+        self.assertProvides(IPropertyStore, properties)
+
+
+    def test_homeProperties(self):
+        """
+        L{IAddressBookHome.properties} returns a property store.
+        """
+        self.checkPropertiesMethod(self.homeUnderTest())
+
+
+    def test_addressbookProperties(self):
+        """
+        L{IAddressBook.properties} returns a property store.
+        """
+        self.checkPropertiesMethod(self.addressbookUnderTest())
+
+
+    def test_addressbookObjectProperties(self):
+        """
+        L{IAddressBookObject.properties} returns a property store.
+        """
+        self.checkPropertiesMethod(self.addressbookObjectUnderTest())
+
+
+    def test_newAddressBookObjectProperties(self):
+        """
+        L{IAddressBookObject.properties} returns an empty property store for a
+        addressbook object which has been created but not committed.
+        """
+        addressbook = self.addressbookUnderTest()
+        addressbook.createAddressBookObjectWithName(
+            "4.vcf", VComponent.fromString(vcard4_text)
+        )
+        newEvent = addressbook.addressbookObjectWithName("4.vcf")
+        self.assertEquals(newEvent.properties().items(), [])
+
+
+    def test_setComponentPreservesProperties(self):
+        """
+        L{IAddressBookObject.setComponent} preserves properties.
+
+        (Some implementations must go to extra trouble to provide this
+        behavior; for example, file storage must copy extended attributes from
+        the existing file to the temporary file replacing it.)
+        """
+        propertyName = PropertyName("http://example.com/ns", "example")
+        propertyContent = WebDAVUnknownElement("sample content")
+        propertyContent.name = propertyName.name
+        propertyContent.namespace = propertyName.namespace
+
+        self.addressbookObjectUnderTest().properties()[
+            propertyName] = propertyContent
+        self.commit()
+        # Sanity check; are properties even readable in a separate transaction?
+        # Should probably be a separate test.
+        self.assertEquals(
+            self.addressbookObjectUnderTest().properties()[propertyName],
+            propertyContent)
+        obj = self.addressbookObjectUnderTest()
+        vcard1_text = obj.vCardText()
+        vcard1_text_withDifferentNote = vcard1_text.replace(
+            "NOTE:CardDAV protocol updates",
+            "NOTE:Changed"
+        )
+        # Sanity check; make sure the test has the right idea of the subject.
+        self.assertNotEquals(vcard1_text, vcard1_text_withDifferentNote)
+        newComponent = VComponent.fromString(vcard1_text_withDifferentNote)
+        obj.setComponent(newComponent)
+
+        # Putting everything into a separate transaction to account for any
+        # caching that may take place.
+        self.commit()
+        self.assertEquals(
+            self.addressbookObjectUnderTest().properties()[propertyName],
+            propertyContent
+        )
+
+
+    def test_dontLeakAddressbooks(self):
+        """
+        Addressbooks in one user's addressbook home should not show up in another
+        user's addressbook home.
+        """
+        home2 = self.transactionUnderTest().addressbookHomeWithUID(
+            "home2", create=True)
+        self.assertIdentical(home2.addressbookWithName("addressbook_1"), None)
+
+
+    def test_dontLeakObjects(self):
+        """
+        Addressbook objects in one user's addressbook should not show up in another
+        user's via uid or name queries.
+        """
+        home1 = self.homeUnderTest()
+        home2 = self.transactionUnderTest().addressbookHomeWithUID(
+            "home2", create=True)
+        addressbook1 = home1.addressbookWithName("addressbook_1")
+        addressbook2 = home2.addressbookWithName("addressbook")
+        objects = list(home2.addressbookWithName("addressbook").addressbookObjects())
+        self.assertEquals(objects, [])
+        for resourceName in self.requirements['home1']['addressbook_1'].keys():
+            obj = addressbook1.addressbookObjectWithName(resourceName)
+            self.assertIdentical(
+                addressbook2.addressbookObjectWithName(resourceName), None)
+            self.assertIdentical(
+                addressbook2.addressbookObjectWithUID(obj.uid()), None)
+
+
+
+class StubNotifierFactory(object):
+
+    """ For testing push notifications without an XMPP server """
+
+    def __init__(self):
+        self.reset()
+
+    def newNotifier(self, label="default", id=None):
+        return Notifier(self, label=label, id=id)
+
+    def send(self, op, id):
+        self.history.append((op, id))
+
+    def reset(self):
+        self.history = []

Modified: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/test_file.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_file.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -29,10 +29,10 @@
 from txdav.common.icommondatastore import NoSuchHomeChildError
 from txdav.common.icommondatastore import NoSuchObjectResourceError
 
-from txcarddav.addressbookstore.file import AddressBookStore, AddressBookHome
-from txcarddav.addressbookstore.file import AddressBook, AddressBookObject
+from txdav.carddav.datastore.file import AddressBookStore, AddressBookHome
+from txdav.carddav.datastore.file import AddressBook, AddressBookObject
 
-from txcarddav.addressbookstore.test.common import (
+from txdav.carddav.datastore.test.common import (
     CommonTests, vcard4_text, vcard1modified_text, StubNotifierFactory)
 
 storePath = FilePath(__file__).parent().child("addressbook_store")

Copied: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_sql.py (from rev 6184, CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/test_sql.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_sql.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_sql.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,75 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Tests for txdav.caldav.datastore.postgres, mostly based on
+L{txdav.caldav.datastore.test.common}.
+"""
+
+from txdav.carddav.datastore.test.common import CommonTests as AddressBookCommonTests
+
+from txdav.common.datastore.test.util import SQLStoreBuilder
+
+from twisted.trial import unittest
+from twisted.internet.defer import inlineCallbacks
+from twistedcaldav.vcard import Component as VCard
+
+
+theStoreBuilder = SQLStoreBuilder()
+buildStore = theStoreBuilder.buildStore
+
+class AddressBookSQLStorageTests(AddressBookCommonTests, unittest.TestCase):
+    """
+    AddressBook SQL storage tests.
+    """
+
+    @inlineCallbacks
+    def setUp(self):
+        super(AddressBookSQLStorageTests, self).setUp()
+        self.addressbookStore = yield buildStore(self, self.notifierFactory)
+        self.populate()
+
+    def populate(self):
+        populateTxn = self.addressbookStore.newTransaction()
+        for homeUID in self.requirements:
+            addressbooks = self.requirements[homeUID]
+            if addressbooks is not None:
+                home = populateTxn.addressbookHomeWithUID(homeUID, True)
+                # We don't want the default addressbook to appear unless it's
+                # explicitly listed.
+                home.removeAddressBookWithName("addressbook")
+                for addressbookName in addressbooks:
+                    addressbookObjNames = addressbooks[addressbookName]
+                    if addressbookObjNames is not None:
+                        home.createAddressBookWithName(addressbookName)
+                        addressbook = home.addressbookWithName(addressbookName)
+                        for objectName in addressbookObjNames:
+                            objData = addressbookObjNames[objectName]
+                            addressbook.createAddressBookObjectWithName(
+                                objectName, VCard.fromString(objData)
+                            )
+
+        populateTxn.commit()
+        self.notifierFactory.reset()
+
+
+
+    def storeUnderTest(self):
+        """
+        Create and return a L{AddressBookStore} for testing.
+        """
+        return self.addressbookStore
+

Copied: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/util.py (from rev 6184, CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/util.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/util.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/util.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,58 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Utility logic common to multiple backend implementations.
+"""
+
+from twistedcaldav.vcard import Component as VCard
+from twistedcaldav.vcard import InvalidVCardDataError
+
+from txdav.common.icommondatastore import InvalidObjectResourceError,\
+    NoSuchObjectResourceError
+
+def validateAddressBookComponent(addressbookObject, vcard, component, inserting):
+    """
+    Validate an addressbook component for a particular addressbook.
+
+    @param addressbookObject: The addressbook object whose component will be replaced.
+    @type addressbookObject: L{IAddressBookObject}
+
+    @param addressbook: The addressbook which the L{IAddressBookObject} is present in.
+    @type addressbook: L{IAddressBook}
+
+    @param component: The VComponent to be validated.
+    @type component: L{VComponent}
+    """
+
+    if not isinstance(component, VCard):
+        raise TypeError(type(component))
+
+    try:
+        if not inserting and component.resourceUID() != addressbookObject.uid():
+            raise InvalidObjectResourceError(
+                "UID may not change (%s != %s)" % (
+                    component.resourceUID(), addressbookObject.uid()
+                 )
+            )
+    except NoSuchObjectResourceError:
+        pass
+
+    try:
+        component.validForCardDAV()
+    except InvalidVCardDataError, e:
+        raise InvalidObjectResourceError(e)
+        
\ No newline at end of file

Modified: CalendarServer/branches/generic-sqlstore/txdav/carddav/iaddressbookstore.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/iaddressbookstore.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/iaddressbookstore.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcarddav.addressbookstore,txcaldav.calendarstore.test.test_postgres.AddressBookSQLStorageTests -*-
+# -*- test-case-name: txdav.carddav.datastore,txdav.caldav.datastore.test.test_postgres.AddressBookSQLStorageTests -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #

Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_file -*-
+# -*- test-case-name: txdav.caldav.datastore.test.test_file -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #
@@ -110,10 +110,10 @@
 
         @type dataStore: L{CommonDataStore}
         """
-        from txcaldav.icalendarstore import ICalendarTransaction
-        from txcarddav.iaddressbookstore import IAddressBookTransaction
-        from txcaldav.calendarstore.file import CalendarHome
-        from txcarddav.addressbookstore.file import AddressBookHome
+        from txdav.caldav.icalendarstore import ICalendarTransaction
+        from txdav.carddav.iaddressbookstore import IAddressBookTransaction
+        from txdav.caldav.datastore.file import CalendarHome
+        from txdav.carddav.datastore.file import AddressBookHome
 
         super(CommonStoreTransaction, self).__init__(dataStore, name)
         self._homes = {}

Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -36,9 +36,9 @@
 from twistedcaldav.customxml import NotificationType
 
 from txdav.common.datastore.sql_legacy import PostgresLegacyNotificationsEmulator
-from txcaldav.icalendarstore import ICalendarTransaction
+from txdav.caldav.icalendarstore import ICalendarTransaction
 
-from txcarddav.iaddressbookstore import IAddressBookTransaction
+from txdav.carddav.iaddressbookstore import IAddressBookTransaction
 
 from txdav.common.datastore.sql_tables import CALENDAR_HOME_TABLE,\
     ADDRESSBOOK_HOME_TABLE, NOTIFICATION_HOME_TABLE, _BIND_MODE_OWN,\
@@ -117,8 +117,8 @@
             extraInterfaces.append(IAddressBookTransaction)
         directlyProvides(self, *extraInterfaces)
 
-        from txcaldav.calendarstore.sql import CalendarHome
-        from txcarddav.addressbookstore.sql import AddressBookHome
+        from txdav.caldav.datastore.sql import CalendarHome
+        from txdav.carddav.datastore.sql import AddressBookHome
         CommonStoreTransaction._homeClass[ECALENDARTYPE] = CalendarHome
         CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = AddressBookHome
 

Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_legacy.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_legacy.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_legacy.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_postgres -*-
+# -*- test-case-name: txdav.caldav.datastore.test.test_postgres -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #

Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_tables.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_tables.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_tables.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_postgres -*-
+# -*- test-case-name: txdav.caldav.datastore.test.test_postgres -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #

Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/__init__.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/__init__.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/__init__.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcarddav.addressbookstore.test -*-
+# -*- test-case-name: txdav.carddav.datastore.test -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #

Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/util.py	2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/util.py	2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcarddav.addressbookstore.test -*-
+# -*- test-case-name: txdav.carddav.datastore.test -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100825/8115aa33/attachment-0001.html>


More information about the calendarserver-changes mailing list