[CalendarServer-changes] [6152] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Aug 20 09:22:29 PDT 2010


Revision: 6152
          http://trac.macosforge.org/projects/calendarserver/changeset/6152
Author:   cdaboo at apple.com
Date:     2010-08-20 09:22:29 -0700 (Fri, 20 Aug 2010)
Log Message:
-----------
Fix webdav sync report with postgres. This includes a minor tweak to bring it into line with the
current spec.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/index.py
    CalendarServer/trunk/twistedcaldav/method/copymove_contact.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/sql.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/twistedcaldav/test/test_index.py
    CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py
    CalendarServer/trunk/twistedcaldav/vcardindex.py
    CalendarServer/trunk/txcaldav/calendarstore/file.py
    CalendarServer/trunk/txcaldav/calendarstore/postgres.py
    CalendarServer/trunk/txcaldav/calendarstore/postgres_schema_v1.sql
    CalendarServer/trunk/txcaldav/calendarstore/util.py
    CalendarServer/trunk/txcarddav/addressbookstore/file.py
    CalendarServer/trunk/txdav/common/datastore/file.py

Modified: CalendarServer/trunk/twistedcaldav/index.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/index.py	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/twistedcaldav/index.py	2010-08-20 16:22:29 UTC (rev 6152)
@@ -183,7 +183,7 @@
             if name is not None and self.resource.getChild(name_utf8) is None:
                 # Clean up
                 log.err("Stale resource record found for child %s with UID %s in %s" % (name, uid, self.resource))
-                self._delete_from_db(name, uid, None)
+                self._delete_from_db(name, uid, False)
                 self._db_commit()
             else:
                 resources.append(name_utf8)
@@ -215,7 +215,7 @@
 
         return uid
 
-    def addResource(self, name, calendar, revision, fast=False, reCreate=False):
+    def addResource(self, name, calendar, fast=False, reCreate=False):
         """
         Adding or updating an existing resource.
         To check for an update we attempt to get an existing UID
@@ -228,12 +228,12 @@
         """
         oldUID = self.resourceUIDForName(name)
         if oldUID is not None:
-            self._delete_from_db(name, oldUID, None)
-        self._add_to_db(name, calendar, revision, reCreate=reCreate)
+            self._delete_from_db(name, oldUID, False)
+        self._add_to_db(name, calendar, reCreate=reCreate)
         if not fast:
             self._db_commit()
 
-    def deleteResource(self, name, revision):
+    def deleteResource(self, name):
         """
         Remove this resource from the index.
         @param name: the name of the resource to add.
@@ -241,7 +241,7 @@
         """
         uid = self.resourceUIDForName(name)
         if uid is not None:
-            self._delete_from_db(name, uid, revision)
+            self._delete_from_db(name, uid)
             self._db_commit()
 
     def resourceExists(self, name):
@@ -279,16 +279,15 @@
 
     def whatchanged(self, revision):
 
-        results = [(name.encode("utf-8"), created, wasdeleted) for name, created, wasdeleted in self._db_execute("select NAME, CREATEDREVISION, WASDELETED from REVISIONS where REVISION > :1", revision)]
+        results = [(name.encode("utf-8"), deleted) for name, deleted in self._db_execute("select NAME, DELETED from REVISIONS where REVISION > :1", revision)]
         results.sort(key=lambda x:x[1])
         
         changed = []
         deleted = []
-        for name, created, wasdeleted in results:
+        for name, wasdeleted in results:
             if name:
                 if wasdeleted == 'Y':
-                    # Don't report items that were created/deleted since the requested revision
-                    if created <= revision:
+                    if revision:
                         deleted.append(name)
                 else:
                     changed.append(name)
@@ -297,6 +296,24 @@
         
         return changed, deleted,
 
+    def lastRevision(self):
+        return self._db_value_for_sql(
+            "select REVISION from REVISION_SEQUENCE"
+        )
+
+    def bumpRevision(self, fast=False):
+        self._db_execute(
+            """
+            update REVISION_SEQUENCE set REVISION = REVISION + 1
+            """,
+        )
+        self._db_commit()
+        return self._db_value_for_sql(
+            """
+            select REVISION from REVISION_SEQUENCE
+            """,
+        )
+
     def indexedSearch(self, filter, useruid="", fbtype=False):
         """
         Finds resources matching the given qualifiers.
@@ -400,7 +417,7 @@
         """
         return schema_version
 
-    def _add_to_db(self, name, calendar, revision, cursor=None, expand_until=None, reCreate=False):
+    def _add_to_db(self, name, calendar, cursor=None, expand_until=None, reCreate=False):
         """
         Records the given calendar resource in the index with the given name.
         Resource names and UIDs must both be unique; only one resource name may
@@ -413,7 +430,7 @@
         """
         raise NotImplementedError
 
-    def _delete_from_db(self, name, uid, revision):
+    def _delete_from_db(self, name, uid, dorevision=True):
         """
         Deletes the specified entry from all dbs.
         @param name: the name of the resource to delete.
@@ -535,11 +552,22 @@
         #
         q.execute(
             """
+            create table REVISION_SEQUENCE (
+                REVISION        integer
+            )
+            """
+        )
+        q.execute(
+            """
+            insert into REVISION_SEQUENCE (REVISION) values (0)
+            """
+        )
+        q.execute(
+            """
             create table REVISIONS (
                 NAME            text unique,
                 REVISION        integer,
-                CREATEDREVISION integer,
-                WASDELETED      text(1)
+                DELETED         text(1)
             )
             """
         )
@@ -613,10 +641,10 @@
         with a longer expansion.
         """
         calendar = self.resource.getChild(name).iCalendar()
-        self._add_to_db(name, calendar, None, expand_until=expand_until, reCreate=True)
+        self._add_to_db(name, calendar, expand_until=expand_until, reCreate=True)
         self._db_commit()
 
-    def _add_to_db(self, name, calendar, revision, cursor = None, expand_until=None, reCreate=False):
+    def _add_to_db(self, name, calendar, cursor = None, expand_until=None, reCreate=False):
         """
         Records the given calendar resource in the index with the given name.
         Resource names and UIDs must both be unique; only one resource name may
@@ -654,7 +682,7 @@
             log.err("Invalid instance %s when indexing %s in %s" % (e.rid, name, self.resource,))
             raise
 
-        self._delete_from_db(name, uid, None)
+        self._delete_from_db(name, uid, False)
 
         # Add RESOURCE item
         self._db_execute(
@@ -738,30 +766,26 @@
                     """, peruserid, instanceid, 'T' if transp else 'F'
                 )
             
-        if revision is not None:
-            created = self._db_value_for_sql("select CREATEDREVISION from REVISIONS where NAME = :1", name)
-            if created is None:
-                created = revision
-            self._db_execute(
-                """
-                insert or replace into REVISIONS (NAME, REVISION, CREATEDREVISION, WASDELETED)
-                values (:1, :2, :3, :4)
-                """, name, revision, revision, 'N',
-            )
+        self._db_execute(
+            """
+            insert or replace into REVISIONS (NAME, REVISION, DELETED)
+            values (:1, :2, :3)
+            """, name, self.bumpRevision(fast=True), 'N',
+        )
 
-    def _delete_from_db(self, name, uid, revision):
+    def _delete_from_db(self, name, uid, dorevision=True):
         """
         Deletes the specified entry from all dbs.
         @param name: the name of the resource to delete.
         @param uid: the uid of the resource to delete.
         """
         self._db_execute("delete from RESOURCE where NAME = :1", name)
-        if revision is not None:
+        if dorevision:
             self._db_execute(
                 """
-                update REVISIONS SET REVISION = :1, WASDELETED = :2
+                update REVISIONS SET REVISION = :1, DELETED = :2
                 where NAME = :3
-                """, revision, 'Y', name
+                """, self.bumpRevision(fast=True), 'Y', name
             )
 
 
@@ -1019,16 +1043,13 @@
                 log.err("Non-calendar resource: %s" % (name,))
             else:
                 #log.msg("Indexing resource: %s" % (name,))
-                self.addResource(name, calendar, 0, True, reCreate=True)
+                self.addResource(name, calendar, True, reCreate=True)
             finally:
                 stream.close()
 
         # Do commit outside of the loop for better performance
         if do_commit:
             self._db_commit()
-            
-        # Need new sync-token
-        self.resource.initSyncToken()
 
 class IndexSchedule (CalendarIndex):
     """
@@ -1126,13 +1147,10 @@
                 log.err("Non-calendar resource: %s" % (name,))
             else:
                 #log.msg("Indexing resource: %s" % (name,))
-                self.addResource(name, calendar, 0, True, reCreate=True)
+                self.addResource(name, calendar, True, reCreate=True)
             finally:
                 stream.close()
 
         # Do commit outside of the loop for better performance
         if do_commit:
             self._db_commit()
-            
-        # Need new sync-token
-        self.resource.initSyncToken()

Modified: CalendarServer/trunk/twistedcaldav/method/copymove_contact.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/copymove_contact.py	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/twistedcaldav/method/copymove_contact.py	2010-08-20 16:22:29 UTC (rev 6152)
@@ -119,10 +119,6 @@
     result, sourceadbk, sourceparent, destination_uri, destination, destinationadbk, destinationparent = (yield checkForAddressBookAction(self, request))
     if not result or not destinationadbk:
 
-        # assume it will work and dirty caches
-        if isAddressBookCollectionResource(self):
-            yield self.updateCTag()
-            
         # Do default WebDAV action
         returnValue(KEEP_GOING)
         

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2010-08-20 16:22:29 UTC (rev 6152)
@@ -354,6 +354,7 @@
             baseProperties += (
                 caldavxml.SupportedCalendarComponentSet.qname(),
                 caldavxml.SupportedCalendarData.qname(),
+                customxml.GETCTag.qname(),
             )
 
         if self.isCalendarCollection():
@@ -366,6 +367,7 @@
             baseProperties += (
                 davxml.ResourceID.qname(),
                 carddavxml.SupportedAddressData.qname(),
+                customxml.GETCTag.qname(),
                 customxml.PubSubXMPPPushKeyProperty.qname(),
             )
 
@@ -532,6 +534,11 @@
         elif qname == davxml.ResourceID.qname():
             returnValue(davxml.ResourceID(davxml.HRef.fromString(self.resourceID())))
 
+        elif qname == customxml.GETCTag.qname() and (
+            self.isPseudoCalendarCollection() or self.isAddressBookCollection()
+        ):
+            returnValue(customxml.GETCTag.fromString(self.getSyncToken()))
+
         elif qname == davxml.SyncToken.qname() and config.EnableSyncReport and (
             self.isPseudoCalendarCollection() or self.isAddressBookCollection()
         ):
@@ -1305,7 +1312,7 @@
 
     def whatchanged(self, client_token):
         
-        current_token = str(self.readDeadProperty(customxml.GETCTag))
+        current_token = self.getSyncToken()
         current_uuid, current_revision = current_token.split("#", 1)
         current_revision = int(current_revision)
 
@@ -1331,76 +1338,12 @@
 
         return changed, removed, current_token
 
-    @inlineCallbacks
-    def bumpSyncToken(self):
-        """
-        Increment the sync-token which is also the ctag.
-        
-        return: a deferred that returns the new revision number
-        """
-        assert self.isCollection()
-        
-        # Need to lock
-        lock = MemcacheLock("ResourceLock", self.resourceID(), timeout=60.0)
-        try:
-            try:
-                yield lock.acquire()
-            except MemcacheLockTimeoutError:
-                raise HTTPError(StatusResponse(responsecode.CONFLICT, "Resource: %s currently in use on the server." % (self.uri,)))
-
-            try:
-                token = str(self.readDeadProperty(customxml.GETCTag))
-                caluuid, revision = token.split("#", 1)
-                revision = int(revision) + 1
-                token = "%s#%d" % (caluuid, revision,)
-    
-            except (HTTPError, ValueError):
-                # Initialise it
-                caluuid = uuid4()
-                revision = 1
-                token = "%s#%d" % (caluuid, revision,)
-    
-            yield self.updateCTag(token)
-            returnValue(revision)
-        finally:
-            yield lock.clean()
-
-    def initSyncToken(self):
-        """
-        Create a new sync-token which is also the ctag.
-        """
-        # FIXME: new implementation is in txcaldav.file, this should be
-        # deleted.
-        assert self.isCollection()
-        # Initialise it
-        caluuid = uuid4()
-        revision = 1
-        token = "%s#%d" % (caluuid, revision,)
-        try:
-            self.writeDeadProperty(customxml.GETCTag(token))
-        except:
-            return fail(Failure())
-
     def getSyncToken(self):
         """
         Return current sync-token value.
         """
-        assert self.isCollection()
-        
-        return str(self.readDeadProperty(customxml.GETCTag))
+        raise NotImplementedError
 
-    def updateCTag(self, token=None):
-        assert self.isCollection()
-        
-        if not token:
-            token = str(datetime.datetime.now())
-        try:
-            self.writeDeadProperty(customxml.GETCTag(token))
-        except:
-            return fail(Failure())
-
-        return succeed(True)
-
     #
     # Stuff from CalDAVFile
     #

Modified: CalendarServer/trunk/twistedcaldav/sql.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sql.py	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/twistedcaldav/sql.py	2010-08-20 16:22:29 UTC (rev 6152)
@@ -190,7 +190,10 @@
             self._db_init_data_tables(q)
             self._db_recreate(False)
 
-        q.execute("commit")
+        try:
+            q.execute("commit")
+        except DatabaseError:
+            pass
 
         self._db_connection.isolation_level = old_isolation
 

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2010-08-20 16:22:29 UTC (rev 6152)
@@ -310,6 +310,22 @@
         )
 
 
+    def name(self):
+        return self._newStoreCalendar.name()
+
+    def etag(self):
+        return ETag(self._newStoreCalendar.md5())
+
+    def lastModified(self):
+        return self._newStoreCalendar.modified()
+
+    def creationDate(self):
+        return self._newStoreCalendar.created()
+
+    def getSyncToken(self):
+        return self._newStoreCalendar.syncToken()
+
+    
     def provisionFile(self):
         pass
 
@@ -678,6 +694,9 @@
     def creationDate(self):
         return self._newStoreCalendar.created()
 
+    def getSyncToken(self):
+        return self._newStoreCalendar.syncToken()
+
     def isCollection(self):
         return True
 
@@ -1312,6 +1331,9 @@
     def creationDate(self):
         return self._newStoreAddressBook.created()
 
+    def getSyncToken(self):
+        return self._newStoreAddressBook.syncToken()
+
     def isCollection(self):
         return True
 

Modified: CalendarServer/trunk/twistedcaldav/test/test_index.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_index.py	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/twistedcaldav/test/test_index.py	2010-08-20 16:22:29 UTC (rev 6152)
@@ -254,19 +254,17 @@
             ),
         )
 
-        revision = 0
         for description, name, calendar_txt, reCreate, ok in data:
-            revision += 1
             calendar = Component.fromString(calendar_txt)
             if ok:
                 f = open(os.path.join(self.indexDirPath.path, name), "w")
                 f.write(calendar_txt)
                 del f
 
-                self.db.addResource(name, calendar, revision, reCreate=reCreate)
+                self.db.addResource(name, calendar, reCreate=reCreate)
                 self.assertTrue(self.db.resourceExists(name), msg=description)
             else:
-                self.assertRaises(InvalidOverriddenInstanceError, self.db.addResource, name, calendar, revision)
+                self.assertRaises(InvalidOverriddenInstanceError, self.db.addResource, name, calendar)
                 self.assertFalse(self.db.resourceExists(name), msg=description)
 
         self.db._db_recreate()
@@ -428,16 +426,14 @@
             ),
         )
 
-        revision = 0
         for description, name, calendar_txt, trstart, trend, organizer, instances in data:
-            revision += 1
             calendar = Component.fromString(calendar_txt)
 
             f = open(os.path.join(self.indexDirPath.path, name), "w")
             f.write(calendar_txt)
             del f
 
-            self.db.addResource(name, calendar, revision)
+            self.db.addResource(name, calendar)
             self.assertTrue(self.db.resourceExists(name), msg=description)
 
             # Create fake filter element to match time-range
@@ -827,16 +823,14 @@
             ),
         )
 
-        revision = 0
         for description, name, calendar_txt, trstart, trend, organizer, peruserinstances in data:
-            revision += 1
             calendar = Component.fromString(calendar_txt)
 
             f = open(os.path.join(self.indexDirPath.path, name), "w")
             f.write(calendar_txt)
             del f
 
-            self.db.addResource(name, calendar, revision)
+            self.db.addResource(name, calendar)
             self.assertTrue(self.db.resourceExists(name), msg=description)
 
             # Create fake filter element to match time-range
@@ -863,8 +857,7 @@
     
                 self.assertEqual(set(instances), index_results, msg="%s, user:%s" % (description, useruid,))
 
-            revision += 1
-            self.db.deleteResource(name, revision)
+            self.db.deleteResource(name)
 
     def test_index_revisions(self):
         data1 = """BEGIN:VCALENDAR
@@ -910,17 +903,17 @@
 """
 
         calendar = Component.fromString(data1)
-        self.db.addResource("data1.ics", calendar, 1)
+        self.db.addResource("data1.ics", calendar)
         calendar = Component.fromString(data2)
-        self.db.addResource("data2.ics", calendar, 2)
+        self.db.addResource("data2.ics", calendar)
         calendar = Component.fromString(data3)
-        self.db.addResource("data3.ics", calendar, 3)
-        self.db.deleteResource("data3.ics", 4)
+        self.db.addResource("data3.ics", calendar)
+        self.db.deleteResource("data3.ics")
 
         tests = (
             (0, (["data1.ics", "data2.ics",], [],)),
-            (1, (["data2.ics",], [],)),
-            (2, ([], [],)),
+            (1, (["data2.ics",], ["data3.ics",],)),
+            (2, ([], ["data3.ics",],)),
             (3, ([], ["data3.ics",],)),
             (4, ([], [],)),
             (5, ([], [],)),

Modified: CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py	2010-08-20 16:22:29 UTC (rev 6152)
@@ -134,15 +134,13 @@
             ),
         )
 
-        revision = 0
         for description, name, vcard_txt in data:
-            revision += 1
             calendar = Component.fromString(vcard_txt)
             f = open(os.path.join(self.site.resource.fp.path, name), "w")
             f.write(vcard_txt)
             del f
 
-            self.db.addResource(name, calendar, revision)
+            self.db.addResource(name, calendar)
             self.assertTrue(self.db.resourceExists(name), msg=description)
 
         self.db._db_recreate()
@@ -179,17 +177,17 @@
 """
 
         vcard = Component.fromString(data1)
-        self.db.addResource("data1.vcf", vcard, 1)
+        self.db.addResource("data1.vcf", vcard)
         vcard = Component.fromString(data2)
-        self.db.addResource("data2.vcf", vcard, 2)
+        self.db.addResource("data2.vcf", vcard)
         vcard = Component.fromString(data3)
-        self.db.addResource("data3.vcf", vcard, 3)
-        self.db.deleteResource("data3.vcf", 4)
+        self.db.addResource("data3.vcf", vcard)
+        self.db.deleteResource("data3.vcf")
 
         tests = (
             (0, (["data1.vcf", "data2.vcf",], [],)),
-            (1, (["data2.vcf",], [],)),
-            (2, ([], [],)),
+            (1, (["data2.vcf",], ["data3.vcf",],)),
+            (2, ([], ["data3.vcf",],)),
             (3, ([], ["data3.vcf",],)),
             (4, ([], [],)),
             (5, ([], [],)),

Modified: CalendarServer/trunk/twistedcaldav/vcardindex.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/vcardindex.py	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/twistedcaldav/vcardindex.py	2010-08-20 16:22:29 UTC (rev 6152)
@@ -300,7 +300,7 @@
             if name is not None and self.resource.getChild(name_utf8) is None:
                 # Clean up
                 log.err("Stale resource record found for child %s with UID %s in %s" % (name, uid, self.resource))
-                self._delete_from_db(name, uid, None)
+                self._delete_from_db(name, uid, False)
                 self._db_commit()
             else:
                 resources.append(name_utf8)
@@ -332,7 +332,7 @@
 
         return uid
 
-    def addResource(self, name, vcard, revision, fast=False):
+    def addResource(self, name, vcard, fast=False):
         """
         Adding or updating an existing resource.
         To check for an update we attempt to get an existing UID
@@ -345,12 +345,12 @@
         """
         oldUID = self.resourceUIDForName(name)
         if oldUID is not None:
-            self._delete_from_db(name, oldUID, None)
-        self._add_to_db(name, vcard, revision)
+            self._delete_from_db(name, oldUID, False)
+        self._add_to_db(name, vcard)
         if not fast:
             self._db_commit()
 
-    def deleteResource(self, name, revision):
+    def deleteResource(self, name):
         """
         Remove this resource from the index.
         @param name: the name of the resource to add.
@@ -358,7 +358,7 @@
         """
         uid = self.resourceUIDForName(name)
         if uid is not None:
-            self._delete_from_db(name, uid, revision)
+            self._delete_from_db(name, uid)
             self._db_commit()
     
     def resourceExists(self, name):
@@ -387,16 +387,15 @@
     
     def whatchanged(self, revision):
 
-        results = [(name.encode("utf-8"), created, wasdeleted) for name, created, wasdeleted in self._db_execute("select NAME, CREATEDREVISION, WASDELETED from REVISIONS where REVISION > :1", revision)]
+        results = [(name.encode("utf-8"), deleted) for name, deleted in self._db_execute("select NAME, DELETED from REVISIONS where REVISION > :1", revision)]
         results.sort(key=lambda x:x[1])
         
         changed = []
         deleted = []
-        for name, created, wasdeleted in results:
+        for name, wasdeleted in results:
             if name:
                 if wasdeleted == 'Y':
-                    # Don't report items that were created/deleted since the requested revision
-                    if created <= revision:
+                    if revision:
                         deleted.append(name)
                 else:
                     changed.append(name)
@@ -405,6 +404,25 @@
         
         return changed, deleted,
 
+    def lastRevision(self):
+        return self._db_value_for_sql(
+            "select REVISION from REVISION_SEQUENCE"
+        )
+
+    def bumpRevision(self, fast=False):
+        self._db_execute(
+            """
+            update REVISION_SEQUENCE set REVISION = REVISION + 1
+            """,
+        )
+        if not fast:
+            self._db_commit()
+        return self._db_value_for_sql(
+            """
+            select REVISION from REVISION_SEQUENCE
+            """,
+        )
+
     def searchValid(self, filter):
         if isinstance(filter, carddavxml.Filter):
             qualifiers = addressbookquery.sqladdressbookquery(filter)
@@ -512,11 +530,22 @@
         #
         q.execute(
             """
+            create table REVISION_SEQUENCE (
+                REVISION        integer
+            )
+            """
+        )
+        q.execute(
+            """
+            insert into REVISION_SEQUENCE (REVISION) values (0)
+            """
+        )
+        q.execute(
+            """
             create table REVISIONS (
                 NAME            text unique,
                 REVISION        integer default 0,
-                CREATEDREVISION integer default 0,
-                WASDELETED      text(1) default "N"
+                DELETED         text(1) default "N"
             )
             """
         )
@@ -570,7 +599,7 @@
                     log.err("Non-addressbook resource: %s" % (name,))
                 else:
                     #log.msg("Indexing resource: %s" % (name,))
-                    self.addResource(name, vcard, 0, True)
+                    self.addResource(name, vcard, True)
             finally:
                 stream.close()
         
@@ -578,9 +607,6 @@
         if do_commit:
             self._db_commit()
 
-        # Need sync-token
-        self.resource.initSyncToken()
-
     def _db_can_upgrade(self, old_version):
         """
         Can we do an in-place upgrade
@@ -598,6 +624,18 @@
         if old_version < 2:
             q.execute(
                 """
+                create table REVISION_SEQUENCE (
+                    REVISION        integer
+                )
+                """
+            )
+            q.execute(
+                """
+                insert into REVISION_SEQUENCE (REVISION) values (0)
+                """
+            )
+            q.execute(
+                """
                 create table REVISIONS (
                     NAME            text unique,
                     REVISION        integer default 0,
@@ -620,7 +658,7 @@
             )
                 
 
-    def _add_to_db(self, name, vcard, revision, cursor = None):
+    def _add_to_db(self, name, vcard, cursor = None):
         """
         Records the given address book resource in the index with the given name.
         Resource names and UIDs must both be unique; only one resource name may
@@ -640,28 +678,24 @@
             """, name, uid,
         )
 
-        if revision is not None:
-            created = self._db_value_for_sql("select CREATEDREVISION from REVISIONS where NAME = :1", name)
-            if created is None:
-                created = revision
-            self._db_execute(
-                """
-                insert or replace into REVISIONS (NAME, REVISION, CREATEDREVISION, WASDELETED)
-                values (:1, :2, :3, :4)
-                """, name, revision, revision, 'N',
-            )
+        self._db_execute(
+            """
+            insert or replace into REVISIONS (NAME, REVISION, DELETED)
+            values (:1, :2, :3)
+            """, name, self.bumpRevision(fast=True), 'N',
+        )
     
-    def _delete_from_db(self, name, uid, revision):
+    def _delete_from_db(self, name, uid, dorevision=True):
         """
         Deletes the specified entry from all dbs.
         @param name: the name of the resource to delete.
         @param uid: the uid of the resource to delete.
         """
         self._db_execute("delete from RESOURCE where NAME = :1", name)
-        if revision is not None:
+        if dorevision:
             self._db_execute(
                 """
-                update REVISIONS SET REVISION = :1, WASDELETED = :2
+                update REVISIONS SET REVISION = :1, DELETED = :2
                 where NAME = :3
-                """, revision, 'Y', name
+                """, self.bumpRevision(fast=True), 'Y', name
             )

Modified: CalendarServer/trunk/txcaldav/calendarstore/file.py
===================================================================
--- CalendarServer/trunk/txcaldav/calendarstore/file.py	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/txcaldav/calendarstore/file.py	2010-08-20 16:22:29 UTC (rev 6152)
@@ -58,7 +58,7 @@
 from txdav.common.datastore.file import (
     CommonDataStore, CommonStoreTransaction, CommonHome, CommonHomeChild,
     CommonObjectResource
-)
+, CommonStubResource)
 
 from txdav.common.icommondatastore import (NoSuchObjectResourceError,
     InternalDataStoreError)
@@ -230,9 +230,8 @@
     def setComponent(self, component):
         validateCalendarComponent(self, self._calendar, component)
 
-        newRevision = self._calendar._updateSyncToken() # FIXME: test
         self._calendar.retrieveOldIndex().addResource(
-            self.name(), component, newRevision
+            self.name(), component
         )
 
         self._component = component
@@ -480,26 +479,17 @@
 
 
 
-class CalendarStubResource(object):
+class CalendarStubResource(CommonStubResource):
     """
     Just enough resource to keep the calendar's sql DB classes going.
     """
 
-    def __init__(self, calendar):
-        self.calendar = calendar
-
-
-    @property
-    def fp(self):
-        return self.calendar._path
-
-
     def isCalendarCollection(self):
         return True
 
 
     def getChild(self, name):
-        calendarObject = self.calendar.calendarObjectWithName(name)
+        calendarObject = self.resource.calendarObjectWithName(name)
         if calendarObject:
             class ChildResource(object):
                 def __init__(self, calendarObject):
@@ -513,17 +503,7 @@
             return None
 
 
-    def bumpSyncToken(self, reset=False):
-        # FIXME: needs direct tests
-        return self.calendar._updateSyncToken(reset)
 
-
-    def initSyncToken(self):
-        # FIXME: needs direct tests
-        self.bumpSyncToken(True)
-
-
-
 class Index(object):
     #
     # OK, here's where we get ugly.

Modified: CalendarServer/trunk/txcaldav/calendarstore/postgres.py
===================================================================
--- CalendarServer/trunk/txcaldav/calendarstore/postgres.py	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/txcaldav/calendarstore/postgres.py	2010-08-20 16:22:29 UTC (rev 6152)
@@ -55,7 +55,8 @@
     ObjectResourceNameAlreadyExistsError, HomeChildNameAlreadyExistsError,
     NoSuchHomeChildError, NoSuchObjectResourceError)
 from txcaldav.calendarstore.util import (validateCalendarComponent,
-    validateAddressBookComponent, dropboxIDFromCalendarObject, SyncTokenHelper)
+    validateAddressBookComponent, dropboxIDFromCalendarObject, CalendarSyncTokenHelper,
+    AddressbookSyncTokenHelper)
 from txdav.datastore.file import cached
 
 from txcaldav.icalendarstore import (ICalendarTransaction, ICalendarHome,
@@ -75,7 +76,8 @@
 from twistedcaldav.config import config
 from twistedcaldav.customxml import NotificationType
 from twistedcaldav.dateops import normalizeForIndex
-from twistedcaldav.index import IndexedSearchException, ReservationError
+from twistedcaldav.index import IndexedSearchException, ReservationError,\
+    SyncTokenValidException
 from twistedcaldav.instance import InvalidOverriddenInstanceError
 from twistedcaldav.memcachepool import CachePoolUserMixIn
 from twistedcaldav.notifications import NotificationRecord
@@ -324,9 +326,8 @@
         validateCalendarComponent(self, self._calendar, component)
 
         self.updateDatabase(component)
+        self._calendar._updateRevision(self._name)
 
-        self._calendar._updateSyncToken()
-
         if self._calendar._notifier:
             self._calendar._home._txn.postCommit(self._calendar._notifier.notify)
 
@@ -1267,6 +1268,33 @@
             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.
@@ -1358,7 +1386,7 @@
 
 
 
-class PostgresCalendar(SyncTokenHelper):
+class PostgresCalendar(CalendarSyncTokenHelper):
 
     implements(ICalendar)
 
@@ -1409,6 +1437,7 @@
         # update memos
         del self._home._calendars[oldName]
         self._home._calendars[name] = self
+        self._updateSyncToken()
 
 
     def ownerCalendarHome(self):
@@ -1472,9 +1501,8 @@
         validateCalendarComponent(calendarObject, self, component)
 
         calendarObject.updateDatabase(component, inserting=True)
+        self._insertRevision(name)
 
-        self._updateSyncToken()
-
         if self._notifier:
             self._home._txn.postCommit(self._notifier.notify)
 
@@ -1488,9 +1516,8 @@
         if self._txn._cursor.rowcount == 0:
             raise NoSuchObjectResourceError()
         self._objects.pop(name, None)
+        self._deleteRevision(name)
 
-        self._updateSyncToken()
-
         if self._notifier:
             self._txn.postCommit(self._notifier.notify)
 
@@ -1511,18 +1538,12 @@
         )
         self._objects.pop(name, None)
         self._objects.pop(uid, None)
-        self._updateSyncToken()
+        self._deleteRevision(name)
 
         if self._notifier:
             self._home._txn.postCommit(self._notifier.notify)
 
 
-    def syncToken(self):
-        return self._txn.execSQL(
-            "select SYNC_TOKEN from CALENDAR where RESOURCE_ID = %s",
-            [self._resourceID])[0][0]
-
-
     def calendarObjectsInTimeRange(self, start, end, timeZone):
         raise NotImplementedError()
 
@@ -1684,9 +1705,9 @@
         rows = self._txn.execSQL("select nextval('RESOURCE_ID_SEQ')")
         resourceID = rows[0][0]
         self._txn.execSQL(
-            "insert into CALENDAR (SYNC_TOKEN, RESOURCE_ID) values "
-            "(%s, %s)",
-            ['uninitialized', resourceID])
+            "insert into CALENDAR (RESOURCE_ID) values "
+            "(%s)",
+            [resourceID])
 
         self._txn.execSQL("""
             insert into CALENDAR_BIND (
@@ -1703,7 +1724,7 @@
         newCalendar = self.calendarWithName(name)
         newCalendar.properties()[
             PropertyName.fromElement(ResourceType)] = calendarType
-        newCalendar._updateSyncToken(True)
+        newCalendar._updateSyncToken()
 
         if self._notifier:
             self._txn.postCommit(self._notifier.notify)
@@ -2196,9 +2217,8 @@
         validateAddressBookComponent(self, self._addressbook, component)
 
         self.updateDatabase(component)
+        self._addressbook._updateRevision(self._name)
 
-        self._addressbook._updateSyncToken()
-
         if self._addressbook._notifier:
             self._addressbook._home._txn.postCommit(self._addressbook._notifier.notify)
 
@@ -2411,6 +2431,33 @@
         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)
@@ -2481,7 +2528,7 @@
 
 
 
-class PostgresAddressBook(SyncTokenHelper):
+class PostgresAddressBook(AddressbookSyncTokenHelper):
 
     implements(IAddressBook)
 
@@ -2532,6 +2579,7 @@
         # update memos
         del self._home._addressbooks[oldName]
         self._home._addressbooks[name] = self
+        self._updateSyncToken()
 
 
     def ownerAddressBookHome(self):
@@ -2595,9 +2643,8 @@
         validateAddressBookComponent(addressbookObject, self, component)
 
         addressbookObject.updateDatabase(component, inserting=True)
+        self._insertRevision(name)
 
-        self._updateSyncToken()
-
         if self._notifier:
             self._home._txn.postCommit(self._notifier.notify)
 
@@ -2611,9 +2658,8 @@
         if self._txn._cursor.rowcount == 0:
             raise NoSuchObjectResourceError()
         self._objects.pop(name, None)
+        self._deleteRevision(name)
 
-        self._updateSyncToken()
-
         if self._notifier:
             self._txn.postCommit(self._notifier.notify)
 
@@ -2634,18 +2680,12 @@
         )
         self._objects.pop(name, None)
         self._objects.pop(uid, None)
-        self._updateSyncToken()
+        self._deleteRevision(name)
 
         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()
 
@@ -2793,9 +2833,9 @@
         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])
+            "insert into ADDRESSBOOK (RESOURCE_ID) values "
+            "(%s)",
+            [resourceID])
 
         self._txn.execSQL("""
             insert into ADDRESSBOOK_BIND (
@@ -2812,7 +2852,7 @@
         newAddressbook = self.addressbookWithName(name)
         newAddressbook.properties()[
             PropertyName.fromElement(ResourceType)] = addressbookType
-        newAddressbook._updateSyncToken(True)
+        newAddressbook._updateSyncToken()
 
         if self._notifier:
             self._txn.postCommit(self._notifier.notify)

Modified: CalendarServer/trunk/txcaldav/calendarstore/postgres_schema_v1.sql
===================================================================
--- CalendarServer/trunk/txcaldav/calendarstore/postgres_schema_v1.sql	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/txcaldav/calendarstore/postgres_schema_v1.sql	2010-08-20 16:22:29 UTC (rev 6152)
@@ -20,8 +20,8 @@
 --------------
 
 create table CALENDAR (
-  RESOURCE_ID integer      primary key default nextval('RESOURCE_ID_SEQ'),
-  SYNC_TOKEN  varchar(255),
+  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)
 );
@@ -211,6 +211,27 @@
 );
 
 
+------------------------------
+-- 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 --
 ------------------
@@ -253,10 +274,10 @@
 -----------------
 
 create table ADDRESSBOOK (
-  RESOURCE_ID integer       primary key default nextval('RESOURCE_ID_SEQ'),
-  SYNC_TOKEN  varchar(255),
-  CREATED     timestamp     default timezone('UTC', CURRENT_TIMESTAMP),
-  MODIFIED    timestamp     default timezone('UTC', CURRENT_TIMESTAMP)
+  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)
 );
 
 
@@ -293,3 +314,25 @@
   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/trunk/txcaldav/calendarstore/util.py
===================================================================
--- CalendarServer/trunk/txcaldav/calendarstore/util.py	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/txcaldav/calendarstore/util.py	2010-08-20 16:22:29 UTC (rev 6152)
@@ -24,9 +24,6 @@
 
 from txdav.common.icommondatastore import InvalidObjectResourceError,\
     NoSuchObjectResourceError
-from twistedcaldav.customxml import GETCTag
-from uuid import uuid4
-from txdav.propertystore.base import PropertyName
 
 
 def validateCalendarComponent(calendarObject, calendar, component):
@@ -126,30 +123,183 @@
 
 
 
-class SyncTokenHelper(object):
+class CalendarSyncTokenHelper(object):
     """
-    Implement a basic _updateSyncToken in terms of an object with a property
-    store.  This is a mixin for use by data store implementations.
+    This is a mixin for use by data store implementations.
     """
 
-    def _updateSyncToken(self, reset=False):
-        # FIXME: add locking a-la CalDAVResource.bumpSyncToken
-        # FIXME: tests for desired concurrency properties
-        ctag = PropertyName.fromString(GETCTag.sname())
-        props = self.properties()
-        token = props.get(ctag)
-        if token is None or reset:
-            tokenuuid = uuid4()
-            revision = 1
-        else:
-            # FIXME: no direct tests for update
-            token = str(token)
-            tokenuuid, revision = token.split("#", 1)
-            revision = int(revision) + 1
-        token = "%s#%d" % (tokenuuid, revision)
-        props[ctag] = GETCTag(token)
-        # FIXME: no direct tests for commit
-        return revision
+    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

Modified: CalendarServer/trunk/txcarddav/addressbookstore/file.py
===================================================================
--- CalendarServer/trunk/txcarddav/addressbookstore/file.py	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/txcarddav/addressbookstore/file.py	2010-08-20 16:22:29 UTC (rev 6152)
@@ -177,9 +177,8 @@
         except InvalidVCardDataError, e:
             raise InvalidObjectResourceError(e)
 
-        newRevision = self._addressbook._updateSyncToken() # FIXME: test
         self._addressbook.retrieveOldIndex().addResource(
-            self.name(), component, newRevision
+            self.name(), component
         )
 
         self._component = component

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2010-08-20 16:16:22 UTC (rev 6151)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2010-08-20 16:22:29 UTC (rev 6152)
@@ -20,7 +20,8 @@
 """
 
 from twext.python.log import LoggingMixIn
-from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType
+from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType, HRef
+from twext.web2.dav.element.rfc5842 import ResourceID
 from twext.web2.http_headers import generateContentType, MimeType
 
 from twisted.python.util import FancyEqMixin
@@ -43,11 +44,11 @@
 from txdav.propertystore.base import PropertyName
 from txdav.propertystore.xattr import PropertyStore
 
-from txcaldav.calendarstore.util import SyncTokenHelper
-
 from errno import EEXIST, ENOENT
 from zope.interface import implements, directlyProvides
 
+import uuid
+
 ECALENDARTYPE = 0
 EADDRESSBOOKTYPE = 1
 TOPPATHS = (
@@ -295,11 +296,9 @@
         return self._uid
 
 
-    def _updateSyncToken(self, reset=False):
-        "Stub for updating sync token."
-        # FIXME: actually update something
+    def transaction(self):
+        return self._transaction
 
-
     def retrieveOldShares(self):
         """
         Retrieve the old Index object.
@@ -471,8 +470,7 @@
             return None
 
 
-class CommonHomeChild(FileMetaDataMixin, LoggingMixIn, FancyEqMixin,
-                      SyncTokenHelper):
+class CommonHomeChild(FileMetaDataMixin, LoggingMixIn, FancyEqMixin):
     """
     Common ancestor class of AddressBooks and Calendars.
     """
@@ -543,7 +541,6 @@
 
     @writeOperation
     def rename(self, name):
-        self._updateSyncToken()
         oldName = self.name()
         self._renamedName = name
         self._home._newChildren[name] = self
@@ -554,6 +551,8 @@
         self._transaction.addOperation(doIt, "rename home child %r -> %r" %
                                        (oldName, name))
 
+        self.retrieveOldIndex().bumpRevision()
+
         if self._notifier:
             self._transaction.postCommit(self._notifier.notify)
 
@@ -648,8 +647,7 @@
         if name.startswith("."):
             raise NoSuchObjectResourceError(name)
 
-        newRevision = self._updateSyncToken() # FIXME: Test
-        self.retrieveOldIndex().deleteResource(name, newRevision)
+        self.retrieveOldIndex().deleteResource(name)
 
         objectResourcePath = self._path.child(name)
         if objectResourcePath.isfile():
@@ -673,7 +671,13 @@
 
 
     def syncToken(self):
-        raise NotImplementedError()
+        
+        try:
+            urnuuid = str(self.properties()[PropertyName.fromElement(ResourceID)].children[0])
+        except KeyError:
+            urnuuid = uuid.uuid4().urn
+            self.properties()[PropertyName(*ResourceID.qname())] = ResourceID(HRef.fromString(urnuuid))
+        return "%s#%s" % (urnuuid[9:], self.retrieveOldIndex().lastRevision())
 
 
     def objectResourcesSinceToken(self, token):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100820/6e7e8a31/attachment-0001.html>


More information about the calendarserver-changes mailing list