[CalendarServer-changes] [7184] CalendarServer/trunk/txdav

source_changes at macosforge.org source_changes at macosforge.org
Fri Mar 11 11:38:45 PST 2011


Revision: 7184
          http://trac.macosforge.org/projects/calendarserver/changeset/7184
Author:   cdaboo at apple.com
Date:     2011-03-11 11:38:43 -0800 (Fri, 11 Mar 2011)
Log Message:
-----------
Make sure properties in SQL table are removed when the corresponding resource is removed.

Modified Paths:
--------------
    CalendarServer/trunk/txdav/base/propertystore/base.py
    CalendarServer/trunk/txdav/base/propertystore/none.py
    CalendarServer/trunk/txdav/base/propertystore/sql.py
    CalendarServer/trunk/txdav/base/propertystore/xattr.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
    CalendarServer/trunk/txdav/common/datastore/file.py
    CalendarServer/trunk/txdav/common/datastore/sql.py

Modified: CalendarServer/trunk/txdav/base/propertystore/base.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/base.py	2011-03-11 17:10:42 UTC (rev 7183)
+++ CalendarServer/trunk/txdav/base/propertystore/base.py	2011-03-11 19:38:43 UTC (rev 7184)
@@ -147,7 +147,10 @@
 
     def _keys_uid(self, uid):
         raise NotImplementedError()
-        
+    
+    def _removeResource(self):
+        raise NotImplementedError()
+
     def flush(self):
         raise NotImplementedError()
 

Modified: CalendarServer/trunk/txdav/base/propertystore/none.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/none.py	2011-03-11 17:10:42 UTC (rev 7183)
+++ CalendarServer/trunk/txdav/base/propertystore/none.py	2011-03-11 19:38:43 UTC (rev 7184)
@@ -50,6 +50,9 @@
     def keys(self):
         return ()
 
+    def _removeResource(self):
+        pass
+
     #
     # I/O
     #

Modified: CalendarServer/trunk/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/sql.py	2011-03-11 17:10:42 UTC (rev 7183)
+++ CalendarServer/trunk/txdav/base/propertystore/sql.py	2011-03-11 19:38:43 UTC (rev 7184)
@@ -204,4 +204,13 @@
             if cachedUID == uid:
                 yield PropertyName.fromString(cachedKey)
 
+    _deleteResourceQuery = Delete(
+        prop, Where=(prop.RESOURCE_ID == Parameter("resourceID"))
+    )
 
+    def _removeResource(self):
+
+        self._cached = {}
+        self._deleteResourceQuery.on(self._txn, resourceID=self._resourceID)
+        self._cacher.delete(str(self._resourceID))
+

Modified: CalendarServer/trunk/txdav/base/propertystore/xattr.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/xattr.py	2011-03-11 17:10:42 UTC (rev 7183)
+++ CalendarServer/trunk/txdav/base/propertystore/xattr.py	2011-03-11 19:38:43 UTC (rev 7184)
@@ -257,6 +257,11 @@
             if effectivekey[1] == uid and effectivekey not in seen:
                 yield effectivekey[0]
 
+    def _removeResource(self):
+        # xattrs are removed when the underlying file is deleted so just clear out cached changes
+        self.removed.clear()
+        self.modified.clear()
+
     #
     # I/O
     #

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py	2011-03-11 17:10:42 UTC (rev 7183)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py	2011-03-11 19:38:43 UTC (rev 7184)
@@ -13,34 +13,37 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-import datetime
-from twistedcaldav.dateops import datetimeMktime
 
 """
 Tests for txdav.caldav.datastore.postgres, mostly based on
 L{txdav.caldav.datastore.test.common}.
 """
 
-from twisted.trial import unittest
+from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.internet.task import deferLater
-from twisted.internet import reactor
 from twisted.python import hashlib
+from twisted.trial import unittest
 
+from twext.enterprise.dal.syntax import Select, Parameter
 from twext.python.vcomponent import VComponent
 from twext.web2.dav.element.rfc2518 import GETContentLanguage, ResourceType
 
-from txdav.caldav.datastore.test.common import CommonTests as CalendarCommonTests
+from txdav.base.propertystore.base import PropertyName
+from txdav.caldav.datastore.test.common import CommonTests as CalendarCommonTests,\
+    event4_text
+from txdav.caldav.datastore.test.test_file import setUpCalendarStore
+from txdav.caldav.datastore.util import _migrateCalendar, migrateHome
 from txdav.common.datastore.sql import ECALENDARTYPE
+from txdav.common.datastore.sql_tables import schema
 from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom
 
-from txdav.caldav.datastore.test.test_file import setUpCalendarStore
-from txdav.caldav.datastore.util import _migrateCalendar, migrateHome
-from txdav.base.propertystore.base import PropertyName
-
-from twistedcaldav import memcacher
+from twistedcaldav import memcacher, caldavxml
 from twistedcaldav.config import config
+from twistedcaldav.dateops import datetimeMktime
 
+import datetime
+
 class CalendarSQLStorageTests(CalendarCommonTests, unittest.TestCase):
     """
     Calendar SQL storage tests.
@@ -433,5 +436,140 @@
         self.assertNotEqual(notification_uid1_1, None)
         self.assertNotEqual(notification_uid1_2, None)
 
+    @inlineCallbacks
+    def test_removeCalendarPropertiesOnDelete(self):
+        """
+        L{ICalendarHome.removeCalendarWithName} removes a calendar that already
+        exists and makes sure properties are also removed.
+        """
 
-        
\ No newline at end of file
+        # Create calendar and add a property
+        home = yield self.homeUnderTest()
+        name = "remove-me"
+        calendar = yield home.createCalendarWithName(name)
+        resourceID = calendar._resourceID
+        calendarProperties = calendar.properties()
+        
+        prop = caldavxml.CalendarDescription.fromString("Calendar to be removed")
+        calendarProperties[PropertyName.fromElement(prop)] = prop
+        yield self.commit()
+
+        prop = schema.RESOURCE_PROPERTY
+        _allWithID = Select([prop.NAME, prop.VIEWER_UID, prop.VALUE],
+                        From=prop,
+                        Where=prop.RESOURCE_ID == Parameter("resourceID"))
+
+        # Check that two properties are present
+        home = yield self.homeUnderTest()
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 2)
+        yield self.commit()
+
+        # Remove calendar and check for no properties
+        home = yield self.homeUnderTest()
+        yield home.removeCalendarWithName(name)
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 0)
+        yield self.commit()
+
+        # Recheck it
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 0)
+        yield self.commit()
+
+    @inlineCallbacks
+    def test_removeCalendarObjectPropertiesOnDelete(self):
+        """
+        L{ICalendarHome.removeCalendarWithName} removes a calendar object that already
+        exists and makes sure properties are also removed (which is always the case as right
+        now calendar objects never have properties).
+        """
+
+        # Create calendar object
+        calendar1 = yield self.calendarUnderTest()
+        name = "4.ics"
+        component = VComponent.fromString(event4_text)
+        metadata = {
+            "accessMode": "PUBLIC",
+            "isScheduleObject": True,
+            "scheduleTag": "abc",
+            "scheduleEtags": (),
+            "hasPrivateComment": False,
+        }
+        calobject = yield calendar1.createCalendarObjectWithName(name, component, metadata=metadata)
+        resourceID = calobject._resourceID
+
+        prop = schema.RESOURCE_PROPERTY
+        _allWithID = Select([prop.NAME, prop.VIEWER_UID, prop.VALUE],
+                        From=prop,
+                        Where=prop.RESOURCE_ID == Parameter("resourceID"))
+
+        # No properties on existing calendar object
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 0)
+
+        yield self.commit()
+
+        # Remove calendar and check for no properties
+        calendar1 = yield self.calendarUnderTest()
+        yield calendar1.removeCalendarObjectWithName(name)
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 0)
+        yield self.commit()
+
+        # Recheck it
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 0)
+        yield self.commit()
+
+    @inlineCallbacks
+    def test_removeInboxObjectPropertiesOnDelete(self):
+        """
+        L{ICalendarHome.removeCalendarWithName} removes an inbox calendar object that already
+        exists and makes sure properties are also removed. Inbox calendar objects can have properties.
+        """
+
+        # Create calendar object and add a property
+        home = yield self.homeUnderTest()
+        inbox = yield home.createCalendarWithName("inbox")
+        
+        name = "4.ics"
+        component = VComponent.fromString(event4_text)
+        metadata = {
+            "accessMode": "PUBLIC",
+            "isScheduleObject": True,
+            "scheduleTag": "abc",
+            "scheduleEtags": (),
+            "hasPrivateComment": False,
+        }
+        calobject = yield inbox.createCalendarObjectWithName(name, component, metadata=metadata)
+        resourceID = calobject._resourceID
+        calobjectProperties = calobject.properties()
+
+        prop = caldavxml.CalendarDescription.fromString("Calendar object to be removed")
+        calobjectProperties[PropertyName.fromElement(prop)] = prop
+        yield self.commit()
+
+        prop = schema.RESOURCE_PROPERTY
+        _allWithID = Select([prop.NAME, prop.VIEWER_UID, prop.VALUE],
+                        From=prop,
+                        Where=prop.RESOURCE_ID == Parameter("resourceID"))
+
+        # One property exists calendar object
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 1)
+
+        yield self.commit()
+
+        # Remove calendar object and check for no properties
+        home = yield self.homeUnderTest()
+        inbox = yield home.calendarWithName("inbox")
+        yield inbox.removeCalendarObjectWithName(name)
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 0)
+        yield self.commit()
+
+        # Recheck it
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 0)
+        yield self.commit()

Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py	2011-03-11 17:10:42 UTC (rev 7183)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py	2011-03-11 19:38:43 UTC (rev 7184)
@@ -19,23 +19,27 @@
 L{txdav.carddav.datastore.test.common}.
 """
 
-from txdav.carddav.datastore.test.common import CommonTests as AddressBookCommonTests
-
-from txdav.common.datastore.sql import EADDRESSBOOKTYPE
-from txdav.common.datastore.test.util import buildStore
-from txdav.carddav.datastore.test.test_file import setUpAddressBookStore
+from twext.enterprise.dal.syntax import Select, Parameter
 from twext.web2.dav.element.rfc2518 import GETContentLanguage, ResourceType
-from txdav.base.propertystore.base import PropertyName
-from txdav.carddav.datastore.util import _migrateAddressbook, migrateHome
 
+from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.trial import unittest
-from twisted.internet.defer import inlineCallbacks, returnValue
 
-from twistedcaldav import memcacher
+from twistedcaldav import memcacher, carddavxml
 from twistedcaldav.config import config
 from twistedcaldav.vcard import Component as VCard
+from twistedcaldav.vcard import Component as VComponent
 
+from txdav.base.propertystore.base import PropertyName
+from txdav.carddav.datastore.test.common import CommonTests as AddressBookCommonTests,\
+    vcard4_text
+from txdav.carddav.datastore.test.test_file import setUpAddressBookStore
+from txdav.carddav.datastore.util import _migrateAddressbook, migrateHome
+from txdav.common.datastore.sql import EADDRESSBOOKTYPE
+from txdav.common.datastore.sql_tables import schema
+from txdav.common.datastore.test.util import buildStore
 
+
 class AddressBookSQLStorageTests(AddressBookCommonTests, unittest.TestCase):
     """
     AddressBook SQL storage tests.
@@ -260,3 +264,83 @@
 
         yield d1
         yield d2
+
+    @inlineCallbacks
+    def test_removeAddressBookPropertiesOnDelete(self):
+        """
+        L{IAddressBookHome.removeAddressBookWithName} removes an address book that already
+        exists and makes sure properties are also removed.
+        """
+
+        # Create address book and add a property
+        home = yield self.homeUnderTest()
+        name = "remove-me"
+        addressbook = yield home.createAddressBookWithName(name)
+        resourceID = addressbook._resourceID
+        addressbookProperties = addressbook.properties()
+        
+        prop = carddavxml.AddressBookDescription.fromString("Address Book to be removed")
+        addressbookProperties[PropertyName.fromElement(prop)] = prop
+        yield self.commit()
+
+        prop = schema.RESOURCE_PROPERTY
+        _allWithID = Select([prop.NAME, prop.VIEWER_UID, prop.VALUE],
+                        From=prop,
+                        Where=prop.RESOURCE_ID == Parameter("resourceID"))
+
+        # Check that two properties are present
+        home = yield self.homeUnderTest()
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 2)
+        yield self.commit()
+
+        # Remove address book and check for no properties
+        home = yield self.homeUnderTest()
+        yield home.removeAddressBookWithName(name)
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 0)
+        yield self.commit()
+
+        # Recheck it
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 0)
+        yield self.commit()
+
+    @inlineCallbacks
+    def test_removeAddressBookObjectPropertiesOnDelete(self):
+        """
+        L{IAddressBookHome.removeAddressBookWithName} removes an address book object that already
+        exists and makes sure properties are also removed (which is always the case as right
+        now address book objects never have properties).
+        """
+
+        # Create address book object
+        adbk1 = yield self.addressbookUnderTest()
+        name = "4.vcf"
+        component = VComponent.fromString(vcard4_text)
+        addressobject = yield adbk1.createAddressBookObjectWithName(name, component, metadata={})
+        resourceID = addressobject._resourceID
+
+        prop = schema.RESOURCE_PROPERTY
+        _allWithID = Select([prop.NAME, prop.VIEWER_UID, prop.VALUE],
+                        From=prop,
+                        Where=prop.RESOURCE_ID == Parameter("resourceID"))
+
+        # No properties on existing address book object
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 0)
+
+        yield self.commit()
+
+        # Remove address book object and check for no properties
+        adbk1 = yield self.addressbookUnderTest()
+        yield adbk1.removeAddressBookObjectWithName(name)
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 0)
+        yield self.commit()
+
+        # Recheck it
+        rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
+        self.assertEqual(len(tuple(rows)), 0)
+        yield self.commit()
+

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2011-03-11 17:10:42 UTC (rev 7183)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2011-03-11 19:38:43 UTC (rev 7184)
@@ -668,6 +668,7 @@
             def cleanup():
                 try:
                     trash.remove()
+                    self.properties()._removeResource()
                 except Exception, e:
                     self.log_error("Unable to delete trashed child at %s: %s" % (trash.fp, e))
 

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2011-03-11 17:10:42 UTC (rev 7183)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2011-03-11 19:38:43 UTC (rev 7184)
@@ -1655,6 +1655,8 @@
         yield self._deletedSyncToken()
         yield self._deleteQuery.on(self._txn, NoSuchHomeChildError,
                                    resourceID=self._resourceID)
+        self.properties()._removeResource()
+
         # Set to non-existent state
         self._resourceID = None
         self._created    = None
@@ -1858,54 +1860,34 @@
         returnValue(objectResource)
 
 
-    @classproperty
-    def _removeObjectResourceByNameQuery(cls):
-        """
-        DAL query to remove an object resource from this collection by name,
-        returning the UID of the resource it deleted.
-        """
-        obj = cls._objectSchema
-        return Delete(From=obj,
-                      Where=(obj.RESOURCE_NAME == Parameter("name")).And(
-                          obj.PARENT_RESOURCE_ID == Parameter("resourceID")),
-                     Return=obj.UID)
-
-
     @inlineCallbacks
     def removeObjectResourceWithName(self, name):
-        uid = (yield self._removeObjectResourceByNameQuery.on(
-            self._txn, NoSuchObjectResourceError,
-            name=name, resourceID=self._resourceID))[0][0]
-        self._objects.pop(name, None)
-        self._objects.pop(uid, None)
-        yield self._deleteRevision(name)
-        self.notifyChanged()
+        
+        child = (yield self.objectResourceWithName(name))
+        if child is None:
+            raise NoSuchObjectResourceError
+        yield self._removeObjectResource(child)
 
 
-    @classproperty
-    def _removeObjectResourceByUIDQuery(cls):
-        """
-        DAL query to remove an object resource from this collection by UID,
-        returning the name of the resource it deleted.
-        """
-        obj = cls._objectSchema
-        return Delete(From=obj,
-                      Where=(obj.UID == Parameter("uid")).And(
-                          obj.PARENT_RESOURCE_ID == Parameter("resourceID")),
-                     Return=obj.RESOURCE_NAME)
-
-
     @inlineCallbacks
     def removeObjectResourceWithUID(self, uid):
-        name = (yield self._removeObjectResourceByUIDQuery.on(
-            self._txn, NoSuchObjectResourceError,
-            uid=uid, resourceID=self._resourceID
-        ))[0][0]
-        self._objects.pop(name, None)
-        self._objects.pop(uid, None)
-        yield self._deleteRevision(name)
+        
+        child = (yield self.objectResourceWithUID(uid))
+        if child is None:
+            raise NoSuchObjectResourceError
+        yield self._removeObjectResource(child)
 
-        self.notifyChanged()
+    @inlineCallbacks
+    def _removeObjectResource(self, child):
+        name = child.name()
+        uid = child.uid()
+        try:
+            yield child.remove()
+        finally:        
+            self._objects.pop(name, None)
+            self._objects.pop(uid, None)
+            yield self._deleteRevision(name)
+            self.notifyChanged()
 
 
     def objectResourcesHaveProperties(self):
@@ -2242,7 +2224,30 @@
     def componentType(self):
         returnValue((yield self.component()).mainType())
 
+    @classproperty
+    def _deleteQuery(cls):
+        """
+        DAL statement to delete a L{CommonObjectResource} by its resource ID.
+        """
+        return Delete(cls._objectSchema, Where=cls._objectSchema.RESOURCE_ID == Parameter("resourceID"))
 
+
+    @inlineCallbacks
+    def remove(self):
+        yield self._deleteQuery.on(self._txn, NoSuchObjectResourceError,
+                                   resourceID=self._resourceID)
+        self.properties()._removeResource()
+
+        # Set to non-existent state
+        self._resourceID = None
+        self._name = None
+        self._uid = None
+        self._md5 = None
+        self._size = None
+        self._created = None
+        self._modified = None
+        self._objectText = None
+
     def uid(self):
         return self._uid
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110311/0871f679/attachment-0001.html>


More information about the calendarserver-changes mailing list