[CalendarServer-changes] [6023] CalendarServer/branches/users/glyph/sql-store

source_changes at macosforge.org source_changes at macosforge.org
Mon Aug 9 17:49:55 PDT 2010


Revision: 6023
          http://trac.macosforge.org/projects/calendarserver/changeset/6023
Author:   sagen at apple.com
Date:     2010-08-09 17:49:53 -0700 (Mon, 09 Aug 2010)
Log Message:
-----------
Adds postgres addressbook implementation

Modified Paths:
--------------
    CalendarServer/branches/users/glyph/sql-store/calendarserver/tap/caldav.py
    CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres.py
    CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/test_postgres.py
    CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/util.py
    CalendarServer/branches/users/glyph/sql-store/txcarddav/addressbookstore/test/common.py

Modified: CalendarServer/branches/users/glyph/sql-store/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/calendarserver/tap/caldav.py	2010-08-09 22:16:48 UTC (rev 6022)
+++ CalendarServer/branches/users/glyph/sql-store/calendarserver/tap/caldav.py	2010-08-10 00:49:53 UTC (rev 6023)
@@ -88,7 +88,7 @@
 from calendarserver.tap.util import getRootResource, computeProcessCount
 from calendarserver.tools.util import checkDirectory
 
-from txcaldav.calendarstore.postgres import PostgresStore, v1_schema
+from txcaldav.calendarstore.postgres import v1_schema
 from txdav.datastore.subpostgres import PostgresService
 from twext.python.filepath import CachingFilePath
 

Modified: CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres.py	2010-08-09 22:16:48 UTC (rev 6022)
+++ CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres.py	2010-08-10 00:49:53 UTC (rev 6023)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_postgres.SQLStorageTests -*-
+# -*- test-case-name: txcaldav.calendarstore.test.test_postgres.SQLStorageTests -*- # FIXME: verify
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #
@@ -20,10 +20,13 @@
 """
 
 __all__ = [
-    "PostgresCalendarStore",
+    "PostgresStore",
     "PostgresCalendarHome",
     "PostgresCalendar",
     "PostgresCalendarObject",
+    "PostgresAddressBookHome",
+    "PostgresAddressBook",
+    "PostgresAddressBookObject",
 ]
 
 from twisted.python import hashlib
@@ -46,11 +49,13 @@
     ObjectResourceNameAlreadyExistsError, HomeChildNameAlreadyExistsError,
     NoSuchHomeChildError, NoSuchObjectResourceError)
 from txcaldav.calendarstore.util import (validateCalendarComponent,
-    dropboxIDFromCalendarObject)
+    validateAddressBookComponent, dropboxIDFromCalendarObject)
 
 
 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
 
@@ -1055,11 +1060,11 @@
 
 
 
-class PostgresCalendarTransaction(object):
+class PostgresTransaction(object):
     """
     Transaction implementation for postgres database.
     """
-    implements(ICalendarTransaction)
+    implements(ICalendarTransaction, IAddressBookTransaction)
 
     def __init__(self, store, connection, notifierFactory, label):
         # print 'STARTING', label
@@ -1118,6 +1123,32 @@
         return PostgresCalendarHome(self, uid, resid, notifier)
 
 
+    @memoized('uid', '_homes')
+    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)
+
+
     def notificationsWithUID(self, uid):
         """
         Implement notificationsWithUID.
@@ -1154,8 +1185,474 @@
         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 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 VComponent.fromString(self.vCardText())
+
+
+    def componentType(self):
+        return self.component().mainType()
+
+
+    def properties(self):
+        return PropertyStore(
+            self.uid(),
+            self.uid(),
+            self._txn,
+            self._resourceID
+        )
+
+
+    def setComponent(self, component):
+        validateAddressBookComponent(self, self._calendar, component)
+
+        vCardText = str(component)
+        self._txn.execSQL(
+            "update ADDRESSBOOK_OBJECT set VCARD_TEXT = %s "
+            "where RESOURCE_ID = %s", [vCardText, self._resourceID]
+        )
+        self._vCardText = vCardText
+        if self._calendar._notifier:
+            self._calendar._home._txn.postCommit(self._calendar._notifier.notify)
+
+
+
+    # IDataStoreResource
+    def contentType(self):
+        """
+        The content type of Addressbook objects is text/x-vcard.
+        """
+        return MimeType.fromString("text/x-vcard")
+
+
+    def md5(self):
+        return None
+
+
+    def size(self):
+        return 0
+
+
+    def created(self):
+        return None
+
+
+    def modified(self):
+        return None
+
+
+
+class PostgresAddressBook(object):
+
+    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 retrieveOldInvites(self):
+        return PostgresLegacyInvitesEmulator(self)
+
+    def retrieveOldIndex(self):
+        return PostgresLegacyIndexEmulator(self)
+
+
+    def notifierID(self, label="default"):
+        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
+
+
+    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)
+
+
+    def addressbookObjectWithUID(self, uid):
+        rows = self._txn.execSQL(
+            "select RESOURCE_NAME from ADDRESSBOOK_OBJECT where "
+            "VCARD_UID = %s",
+            [uid]
+        )
+        if not rows:
+            return None
+        name = rows[0][0]
+        return self.addressbookObjectWithName(name)
+
+
+    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)
+
+        componentText = str(component)
+        self._txn.execSQL(
+            """
+            insert into ADDRESSBOOK_OBJECT
+            (ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME, VCARD_TEXT,
+             VCARD_UID, VCARD_TYPE)
+             values
+            (%s, %s, %s, %s, %s)
+            """,
+            [self._resourceID, name, componentText, component.resourceUID(),
+            "VCARD"] # component.resourceType()]  FIXME: what value(s) here?
+        )
+        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)
+        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)
+        if self._notifier:
+            self._home._txn.postCommit(self._notifier.notify)
+
+
+    def syncToken(self):
+        return self._txn.execSQL(
+            "select SYNC_TOKEN from ADDRESSBOOK where RESOURCE_ID = %s",
+            [self._resourceID])[0][0]
+
+
+    def addressbookObjectsSinceToken(self, token):
+        raise NotImplementedError()
+
+
+    def properties(self):
+        ownerUID = self.ownerAddressBookHome().uid()
+        return PropertyStore(
+            ownerUID,
+            ownerUID,
+            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):
+        return None
+
+
+    def modified(self):
+        return None
+
+
+
+
+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 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 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 ADDRESS_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",
+            [name]
+        )
+        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 (SYNC_TOKEN, RESOURCE_ID) values "
+            "(%s, %s)",
+            ['uninitialized', 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
+        self.addressbookWithName(name).properties()[
+            PropertyName.fromElement(ResourceType)] = addressbookType
+        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)
+
+
+    def properties(self):
+        return PropertyStore(
+            self.uid(),
+            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"):
+        return None
+
+
+#
+
+
 class PostgresStore(Service, object):
 
     implements(IDataStore)
@@ -1167,7 +1664,7 @@
 
 
     def newTransaction(self, label="unlabeled"):
-        return PostgresCalendarTransaction(
+        return PostgresTransaction(
             self,
             self.connectionFactory(),
             self.notifierFactory,

Modified: CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/test_postgres.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/test_postgres.py	2010-08-09 22:16:48 UTC (rev 6022)
+++ CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/test_postgres.py	2010-08-10 00:49:53 UTC (rev 6023)
@@ -20,7 +20,8 @@
 """
 
 
-from txcaldav.calendarstore.test.common import CommonTests
+from txcaldav.calendarstore.test.common import CommonTests as CalendarCommonTests
+from txcarddav.addressbookstore.test.common import CommonTests as AddressBookCommonTests
 
 from twisted.trial import unittest
 from txdav.datastore.subpostgres import PostgresService, \
@@ -30,6 +31,7 @@
 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
 import gc
@@ -77,7 +79,7 @@
                 except OSError:
                     pass
                 try:
-                    self.calendarStore = PostgresStore(
+                    self.store = PostgresStore(
                         lambda label=None: connectionFactory(
                             label or currentTestID
                         ),
@@ -89,8 +91,8 @@
                     raise
                 else:
                     self.cleanDatabase(testCase)
-                    ready.callback(self.calendarStore)
-                return self.calendarStore
+                    ready.callback(self.store)
+                return self.store
             self.sharedService = PostgresService(
                 dbRoot,
                 getReady, v1_schema, "caldav"
@@ -104,9 +106,9 @@
                 "before", "shutdown", startStopping)
             result = ready
         else:
-            self.calendarStore.notifierFactory = notifierFactory
+            self.store.notifierFactory = notifierFactory
             self.cleanDatabase(testCase)
-            result = succeed(self.calendarStore)
+            result = succeed(self.store)
 
         def cleanUp():
             # FIXME: clean up any leaked connections and report them with an
@@ -119,7 +121,7 @@
 
 
     def cleanDatabase(self, testCase):
-        cleanupConn = self.calendarStore.connectionFactory(
+        cleanupConn = self.store.connectionFactory(
             "%s schema-cleanup" % (testCase.id(),)
         )
         cursor = cleanupConn.cursor()
@@ -147,20 +149,21 @@
 buildStore = theStoreBuilder.buildStore
 
 
-class SQLStorageTests(CommonTests, unittest.TestCase):
+
+class CalendarSQLStorageTests(CalendarCommonTests, unittest.TestCase):
     """
-    File storage tests.
+    Calendar SQL storage tests.
     """
 
     @inlineCallbacks
     def setUp(self):
-        super(SQLStorageTests, self).setUp()
-        self.calendarStore = yield buildStore(self, self.notifierFactory)
+        super(CalendarSQLStorageTests, self).setUp()
+        self.store = yield buildStore(self, self.notifierFactory)
         self.populate()
 
 
     def populate(self):
-        populateTxn = self.calendarStore.newTransaction()
+        populateTxn = self.store.newTransaction()
         for homeUID in self.requirements:
             calendars = self.requirements[homeUID]
             if calendars is not None:
@@ -186,5 +189,47 @@
         """
         Create and return a L{CalendarStore} for testing.
         """
-        return self.calendarStore
+        return self.store
 
+
+class AddressBookSQLStorageTests(AddressBookCommonTests, unittest.TestCase):
+    """
+    AddressBook SQL storage tests.
+    """
+
+    @inlineCallbacks
+    def setUp(self):
+        super(AddressBookSQLStorageTests, self).setUp()
+        self.store = yield buildStore(self, self.notifierFactory)
+        self.populate()
+
+    def populate(self):
+        populateTxn = self.store.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.history = []
+
+
+
+    def storeUnderTest(self):
+        """
+        Create and return a L{AddressBookStore} for testing.
+        """
+        return self.store
+

Modified: CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/util.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/util.py	2010-08-09 22:16:48 UTC (rev 6022)
+++ CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/util.py	2010-08-10 00:49:53 UTC (rev 6023)
@@ -22,6 +22,8 @@
 
 from twext.python.vcomponent import InvalidICalendarDataError
 from twext.python.vcomponent import VComponent
+from twistedcaldav.vcard import Component as VCard
+from twistedcaldav.vcard import InvalidVCardDataError
 
 def validateCalendarComponent(calendarObject, calendar, component):
     """
@@ -84,3 +86,37 @@
             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)
+

Modified: CalendarServer/branches/users/glyph/sql-store/txcarddav/addressbookstore/test/common.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txcarddav/addressbookstore/test/common.py	2010-08-09 22:16:48 UTC (rev 6022)
+++ CalendarServer/branches/users/glyph/sql-store/txcarddav/addressbookstore/test/common.py	2010-08-10 00:49:53 UTC (rev 6023)
@@ -169,6 +169,8 @@
         self.lastTransaction.abort()
         self.lastTransaction = None
 
+    def setUp(self):
+        self.notifierFactory = StubNotifierFactory()
 
     def homeUnderTest(self):
         """
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100809/a54cb5ce/attachment-0001.html>


More information about the calendarserver-changes mailing list