[CalendarServer-changes] [11190] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Wed May 15 09:47:55 PDT 2013
Revision: 11190
http://trac.calendarserver.org//changeset/11190
Author: cdaboo at apple.com
Date: 2013-05-15 09:47:55 -0700 (Wed, 15 May 2013)
Log Message:
-----------
Fix depth:1 sync on home collections and also make property changes trigger a sync-token change on home child objects.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/customxml.py
CalendarServer/trunk/twistedcaldav/extensions.py
CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/stdconfig.py
CalendarServer/trunk/txdav/base/propertystore/sql.py
CalendarServer/trunk/txdav/caldav/datastore/sql.py
CalendarServer/trunk/txdav/caldav/datastore/test/common.py
CalendarServer/trunk/txdav/caldav/datastore/test/test_attachments.py
CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
CalendarServer/trunk/txdav/common/datastore/file.py
CalendarServer/trunk/txdav/common/datastore/sql.py
CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql
CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql
CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_18_to_19.sql
CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_18_to_19.sql
Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/twistedcaldav/customxml.py 2013-05-15 16:47:55 UTC (rev 11190)
@@ -72,7 +72,11 @@
"calendarserver-partstat-changes",
)
+calendarserver_home_sync_compliance = (
+ "calendarserver-home-sync",
+)
+
@registerElement
class TwistedCalendarSupportedComponents (WebDAVTextElement):
"""
Modified: CalendarServer/trunk/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/extensions.py 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/twistedcaldav/extensions.py 2013-05-15 16:47:55 UTC (rev 11190)
@@ -535,24 +535,6 @@
-def updateCacheTokenOnCallback(f):
- def wrapper(self, *args, **kwargs):
- if hasattr(self, "notifyChanged"):
- def updateToken(response):
- d = self.notifyChanged()
- d.addCallback(lambda _: response)
- return d
-
- d = maybeDeferred(f, self, *args, **kwargs)
- d.addCallback(updateToken)
- return d
- else:
- return f(self, *args, **kwargs)
-
- return wrapper
-
-
-
class DAVResource (DirectoryPrincipalPropertySearchMixIn,
SuperDAVResource, LoggingMixIn,
DirectoryRenderingMixIn, StaticRenderMixin):
@@ -563,20 +545,6 @@
that is currently in static.py but is actually applicable to any type of resource.
"""
- @updateCacheTokenOnCallback
- def http_PROPPATCH(self, request):
- return super(DAVResource, self).http_PROPPATCH(request)
-
-
- @updateCacheTokenOnCallback
- def http_DELETE(self, request):
- return super(DAVResource, self).http_DELETE(request)
-
-
- @updateCacheTokenOnCallback
- def http_ACL(self, request):
- return super(DAVResource, self).http_ACL(request)
-
http_REPORT = http_REPORT
def davComplianceClasses(self):
Modified: CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py 2013-05-15 16:47:55 UTC (rev 11190)
@@ -46,7 +46,7 @@
"""
Generate a sync-collection REPORT.
"""
-
+
# These resource support the report
if not config.EnableSyncReport or element.Report(element.SyncCollection(),) not in self.supportedReports():
log.err("sync-collection report is only allowed on calendar/inbox/addressbook/notification collection resources %s" % (self,))
@@ -55,24 +55,26 @@
element.SupportedReport(),
"Report not supported on this resource",
))
-
+
responses = []
# Process Depth and sync-level for backwards compatibility
# Use sync-level if present and ignore Depth, else use Depth
if sync_collection.sync_level:
depth = sync_collection.sync_level
+ if depth == "infinite":
+ depth = "infinity"
descriptor = "DAV:sync-level"
else:
depth = request.headers.getHeader("depth", None)
descriptor = "Depth header without DAV:sync-level"
-
+
if depth not in ("1", "infinity"):
log.err("sync-collection report with invalid depth header: %s" % (depth,))
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid %s value" % (descriptor,)))
-
- propertyreq = sync_collection.property.children if sync_collection.property else None
-
+
+ propertyreq = sync_collection.property.children if sync_collection.property else None
+
@inlineCallbacks
def _namedPropertiesForResource(request, props, resource, forbidden=False):
"""
@@ -87,7 +89,7 @@
responsecode.FORBIDDEN : [],
responsecode.NOT_FOUND : [],
}
-
+
for property in props:
if isinstance(property, element.WebDAVElement):
qname = property.qname()
@@ -106,13 +108,14 @@
f = Failure()
log.err("Error reading property %r for resource %s: %s" % (qname, request.uri, f.value))
status = statusForFailure(f, "getting property: %s" % (qname,))
- if status not in properties_by_status: properties_by_status[status] = []
+ if status not in properties_by_status:
+ properties_by_status[status] = []
properties_by_status[status].append(propertyName(qname))
else:
properties_by_status[responsecode.NOT_FOUND].append(propertyName(qname))
-
+
returnValue(properties_by_status)
-
+
# Do some optimization of access control calculation by determining any inherited ACLs outside of
# the child resource loop and supply those to the checkPrivileges on each child.
filteredaces = (yield self.inheritedACEsforChildren(request))
@@ -173,11 +176,11 @@
for name in removed:
href = element.HRef.fromString(joinURL(request.uri, name))
responses.append(element.StatusResponse(element.HRef.fromString(href), element.Status.fromResponseCode(responsecode.NOT_FOUND)))
-
+
for name in notallowed:
href = element.HRef.fromString(joinURL(request.uri, name))
responses.append(element.StatusResponse(element.HRef.fromString(href), element.Status.fromResponseCode(responsecode.NOT_ALLOWED)))
-
+
if not hasattr(request, "extendedLogItems"):
request.extendedLogItems = {}
request.extendedLogItems["responses"] = len(responses)
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2013-05-15 16:47:55 UTC (rev 11190)
@@ -202,22 +202,7 @@
calendarPrivilegeSet = _calendarPrivilegeSet()
-def updateCacheTokenOnCallback(f):
- def fun(self, *args, **kwargs):
- def _updateToken(response):
- return self.notifyChanged().addCallback(lambda _: response)
- d = maybeDeferred(f, self, *args, **kwargs)
-
- if hasattr(self, 'notifyChanged'):
- d.addCallback(_updateToken)
-
- return d
-
- return fun
-
-
-
class CalDAVResource (
CalDAVComplianceMixIn, SharedCollectionMixin,
DAVResourceWithChildrenMixin, DAVResource, LoggingMixIn
@@ -391,21 +376,6 @@
# End transitional new-store interface
- @updateCacheTokenOnCallback
- def http_PROPPATCH(self, request):
- return super(CalDAVResource, self).http_PROPPATCH(request)
-
-
- @updateCacheTokenOnCallback
- def http_DELETE(self, request):
- return super(CalDAVResource, self).http_DELETE(request)
-
-
- @updateCacheTokenOnCallback
- def http_ACL(self, request):
- return super(CalDAVResource, self).http_ACL(request)
-
-
##
# WebDAV
##
Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2013-05-15 16:47:55 UTC (rev 11190)
@@ -1542,6 +1542,9 @@
compliance += customxml.calendarserver_principal_property_search_compliance
compliance += customxml.calendarserver_principal_search_compliance
+ # Home Depth:1 sync report will include WebDAV property changes on home child resources
+ compliance += customxml.calendarserver_home_sync_compliance
+
configDict.CalDAVComplianceClasses = compliance
Modified: CalendarServer/trunk/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/sql.py 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/txdav/base/propertystore/sql.py 2013-05-15 16:47:55 UTC (rev 11190)
@@ -250,9 +250,12 @@
name=key_str, uid=uid)
self._cacher.delete(str(self._resourceID))
- # Call the registered notification callback
+ # Call the registered notification callback - we need to do this as a preCommit since it involves
+ # a bunch of deferred operations, but this propstore api is not deferred. preCommit will execute
+ # the deferreds properly, and it is fine to wait until everything else is done before sending the
+ # notifications.
if hasattr(self, "_notifyCallback") and self._notifyCallback is not None:
- self._notifyCallback()
+ self._txn.preCommit(self._notifyCallback)
def justLogIt(f):
f.trap(AllRetriesFailed)
@@ -278,6 +281,14 @@
name=key_str, uid=uid
)
self._cacher.delete(str(self._resourceID))
+
+ # Call the registered notification callback - we need to do this as a preCommit since it involves
+ # a bunch of deferred operations, but this propstore api is not deferred. preCommit will execute
+ # the deferreds properly, and it is fine to wait until everything else is done before sending the
+ # notifications.
+ if hasattr(self, "_notifyCallback") and self._notifyCallback is not None:
+ self._txn.preCommit(self._notifyCallback)
+
def justLogIt(f):
f.trap(AllRetriesFailed)
self.log_error("setting a property failed; probably nothing.")
Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py 2013-05-15 16:47:55 UTC (rev 11190)
@@ -636,11 +636,8 @@
pick another from the calendar home.
"""
- chm = self._homeMetaDataSchema
componentType = "VTODO" if tasks else "VEVENT"
test_name = "tasks" if tasks else "calendar"
- attribute_to_test = "_default_tasks" if tasks else "_default_events"
- column_to_set = chm.DEFAULT_TASKS if tasks else chm.DEFAULT_EVENTS
defaultCalendar = (yield self.calendarWithName(test_name))
if defaultCalendar is None or not defaultCalendar.owned():
@@ -669,13 +666,7 @@
# Failed to even create a default - bad news...
raise RuntimeError("No valid calendars to use as a default %s calendar." % (componentType,))
- setattr(self, attribute_to_test, defaultCalendar._resourceID)
- yield Update(
- {column_to_set: defaultCalendar._resourceID},
- Where=chm.RESOURCE_ID == self._resourceID,
- ).on(self._txn)
- yield self.invalidateQueryCache()
- yield self.notifyChanged()
+ yield self.setDefaultCalendar(defaultCalendar, tasks)
returnValue(defaultCalendar)
@@ -713,7 +704,12 @@
yield self.invalidateQueryCache()
yield self.notifyChanged()
+ # CalDAV stores the default calendar properties on the inbox so we also need to send a changed notification on that
+ inbox = (yield self.calendarWithName("inbox"))
+ if inbox is not None:
+ yield inbox.notifyChanged()
+
@inlineCallbacks
def defaultCalendar(self, componentType, create=True):
"""
@@ -773,14 +769,7 @@
yield default.setSupportedComponents(componentType.upper())
# Update the metadata
- chm = self._homeMetaDataSchema
- column_to_set = chm.DEFAULT_TASKS if componentType == "VTODO" else chm.DEFAULT_EVENTS
- setattr(self, attribute_to_test, default._resourceID)
- yield Update(
- {column_to_set: default._resourceID},
- Where=chm.RESOURCE_ID == self._resourceID,
- ).on(self._txn)
- yield self.invalidateQueryCache()
+ yield self.setDefaultCalendar(default, componentType == "VTODO")
returnValue(default)
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2013-05-15 16:47:55 UTC (rev 11190)
@@ -1733,15 +1733,16 @@
home = yield self.homeUnderTest()
changed, deleted = yield home.resourceNamesSinceToken(
- self.token2revision(st), "depth_is_ignored")
+ self.token2revision(st), "infinity")
- self.assertEquals(set(changed), set(["calendar_1/new.ics",
+ self.assertEquals(set(changed), set(["calendar_1/",
+ "calendar_1/new.ics",
"calendar_1/2.ics",
"other-calendar/"]))
self.assertEquals(set(deleted), set(["calendar_1/2.ics"]))
changed, deleted = yield home.resourceNamesSinceToken(
- self.token2revision(st2), "depth_is_ignored")
+ self.token2revision(st2), "infinity")
self.assertEquals(changed, [])
self.assertEquals(deleted, [])
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_attachments.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_attachments.py 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_attachments.py 2013-05-15 16:47:55 UTC (rev 11190)
@@ -14,7 +14,7 @@
# limitations under the License.
##
-from calendarserver.tap.util import getRootResource, directoryFromConfig
+from calendarserver.tap.util import directoryFromConfig
from pycalendar.datetime import PyCalendarDateTime
from pycalendar.value import PyCalendarValue
@@ -1404,10 +1404,7 @@
self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory, directoryFromConfig(config))
yield self.populate()
- self.rootResource = getRootResource(config, self._sqlCalendarStore)
- self.directory = self._sqlCalendarStore.directoryService()
-
@inlineCallbacks
def populate(self):
yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py 2013-05-15 16:47:55 UTC (rev 11190)
@@ -38,8 +38,9 @@
from twistedcaldav.query import calendarqueryfilter
from txdav.base.propertystore.base import PropertyName
+from txdav.caldav.datastore.sql import Calendar
from txdav.caldav.datastore.test.common import CommonTests as CalendarCommonTests, \
- test_event_text
+ test_event_text, OTHER_HOME_UID
from txdav.caldav.datastore.test.test_file import setUpCalendarStore
from txdav.caldav.datastore.test.util import buildCalendarStore
from txdav.caldav.datastore.util import _migrateCalendar, migrateHome
@@ -47,7 +48,7 @@
from txdav.common.datastore.sql import ECALENDARTYPE, CommonObjectResource
from txdav.common.datastore.sql_legacy import PostgresLegacyIndexEmulator
from txdav.common.datastore.sql_tables import schema, _BIND_MODE_DIRECT, \
- _BIND_STATUS_ACCEPTED
+ _BIND_STATUS_ACCEPTED, _BIND_MODE_WRITE, _BIND_STATUS_INVITED
from txdav.common.datastore.test.util import populateCalendarsFrom
from txdav.common.icommondatastore import NoSuchObjectResourceError
from txdav.xml.rfc2518 import GETContentLanguage, ResourceType
@@ -1046,7 +1047,7 @@
children = yield calendar1.listCalendarObjects()
self.assertEqual(len(children), 3)
new_sync_token1 = yield calendar1.syncToken()
- self.assertEqual(new_sync_token1, original_sync_token1)
+ self.assertNotEqual(new_sync_token1, original_sync_token1)
result = yield calendar1.getSupportedComponents()
self.assertEquals(result, "VEVENT")
@@ -1692,3 +1693,87 @@
self.assertEquals(alarm_result, None)
yield self.commit()
+
+
+ @inlineCallbacks
+ def test_shareWithRevision(self):
+ """
+ Verify that bindRevision on calendars and shared calendars has the correct value.
+ """
+ cal = yield self.calendarUnderTest()
+ self.assertEqual(cal._bindRevision, 0)
+ other = yield self.homeUnderTest(name=OTHER_HOME_UID)
+ newCalName = yield cal.shareWith(other, _BIND_MODE_WRITE)
+ yield self.commit()
+
+ normalCal = yield self.calendarUnderTest()
+ self.assertEqual(normalCal._bindRevision, 0)
+ otherHome = yield self.homeUnderTest(name=OTHER_HOME_UID)
+ otherCal = yield otherHome.childWithName(newCalName)
+ self.assertNotEqual(otherCal._bindRevision, 0)
+
+
+ @inlineCallbacks
+ def test_updateShareRevision(self):
+ """
+ Verify that bindRevision on calendars and shared calendars has the correct value.
+ """
+ cal = yield self.calendarUnderTest()
+ self.assertEqual(cal._bindRevision, 0)
+ other = yield self.homeUnderTest(name=OTHER_HOME_UID)
+ newCalName = yield cal.shareWith(other, _BIND_MODE_WRITE, status=_BIND_STATUS_INVITED)
+ yield self.commit()
+
+ normalCal = yield self.calendarUnderTest()
+ self.assertEqual(normalCal._bindRevision, 0)
+ otherHome = yield self.homeUnderTest(name=OTHER_HOME_UID)
+ otherCal = yield Calendar.invitedObjectWithName(otherHome, newCalName)
+ self.assertEqual(otherCal._bindRevision, 0)
+ yield self.commit()
+
+ normalCal = yield self.calendarUnderTest()
+ otherHome = yield self.homeUnderTest(name=OTHER_HOME_UID)
+ otherCal = yield Calendar.invitedObjectWithName(otherHome, newCalName)
+ yield normalCal.updateShare(otherCal, status=_BIND_STATUS_ACCEPTED)
+ yield self.commit()
+
+ normalCal = yield self.calendarUnderTest()
+ self.assertEqual(normalCal._bindRevision, 0)
+ otherHome = yield self.homeUnderTest(name=OTHER_HOME_UID)
+ otherCal = yield otherHome.childWithName(newCalName)
+ self.assertNotEqual(otherCal._bindRevision, 0)
+
+
+ @inlineCallbacks
+ def test_sharedRevisions(self):
+ """
+ Verify that resourceNamesSinceRevision returns all resources after initial bind and sync.
+ """
+ cal = yield self.calendarUnderTest()
+ self.assertEqual(cal._bindRevision, 0)
+ other = yield self.homeUnderTest(name=OTHER_HOME_UID)
+ newCalName = yield cal.shareWith(other, _BIND_MODE_WRITE)
+ yield self.commit()
+
+ normalCal = yield self.calendarUnderTest()
+ self.assertEqual(normalCal._bindRevision, 0)
+ otherHome = yield self.homeUnderTest(name=OTHER_HOME_UID)
+ otherCal = yield otherHome.childWithName(newCalName)
+ self.assertNotEqual(otherCal._bindRevision, 0)
+
+ changed, deleted = yield otherCal.resourceNamesSinceRevision(otherCal._bindRevision - 1)
+ self.assertNotEqual(len(changed), 0)
+ self.assertEqual(len(deleted), 0)
+
+ changed, deleted = yield otherCal.resourceNamesSinceRevision(otherCal._bindRevision)
+ self.assertEqual(len(changed), 0)
+ self.assertEqual(len(deleted), 0)
+
+ for depth in ("1", "infinity",):
+ changed, deleted = yield otherHome.resourceNamesSinceRevision(otherCal._bindRevision - 1, depth)
+ self.assertNotEqual(len(changed), 0)
+ self.assertEqual(len(deleted), 0)
+
+ changed, deleted = yield otherHome.resourceNamesSinceRevision(otherCal._bindRevision, depth)
+ self.assertEqual(len(changed), 0)
+ self.assertEqual(len(deleted), 0)
Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/txdav/common/datastore/file.py 2013-05-15 16:47:55 UTC (rev 11190)
@@ -286,7 +286,6 @@
self._notificationHomes = {}
self._notifierFactories = notifierFactories
self._notifiedAlready = set()
- self._bumpedAlready = set()
self._migrating = migrating
extraInterfaces = []
@@ -405,25 +404,7 @@
self._notifiedAlready.add(obj)
- def isBumpedAlready(self, obj):
- """
- Indicates whether or not bumpAddedForObject has already been
- called for the given object, in order to facilitate calling
- bumpModified only once per object.
- """
- return obj in self._bumpedAlready
-
- def bumpAddedForObject(self, obj):
- """
- Records the fact that a bumpModified( ) call has already been
- done, in order to facilitate calling bumpModified only once per
- object.
- """
- self._bumpedAlready.add(obj)
-
-
-
class StubResource(object):
"""
Just enough resource to keep the shared sql DB classes going.
Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py 2013-05-15 16:47:55 UTC (rev 11190)
@@ -452,7 +452,7 @@
self._notificationHomes = {}
self._notifierFactories = notifierFactories
self._notifiedAlready = set()
- self._bumpedAlready = set()
+ self._bumpedRevisionAlready = set()
self._label = label
self._migrating = migrating
self._primaryHomeType = None
@@ -886,22 +886,20 @@
self._notifiedAlready.add(obj.id())
- def isBumpedAlready(self, obj):
+ def isRevisionBumpedAlready(self, obj):
"""
- Indicates whether or not bumpAddedForObject has already been
- called for the given object, in order to facilitate calling
- bumpModified only once per object.
+ Indicates whether or not bumpRevisionForObject has already been
+ called for the given object, in order to facilitate changing the
+ revision only once per object.
"""
- return obj.id() in self._bumpedAlready
+ return obj.id() in self._bumpedRevisionAlready
- def bumpAddedForObject(self, obj):
+ def bumpRevisionForObject(self, obj):
"""
- Records the fact that a bumpModified( ) call has already been
- done, in order to facilitate calling bumpModified only once per
- object.
+ Records the fact that a revision token for the object has been bumped.
"""
- self._bumpedAlready.add(obj.id())
+ self._bumpedRevisionAlready.add(obj.id())
_savepointCounter = 0
@@ -1783,6 +1781,14 @@
def _syncTokenQuery(cls): #@NoSelf
"""
DAL Select statement to find the sync token.
+
+ This is the max(REVISION) from the union of:
+
+ 1) REVISION's for all object resources in all home child collections in the targeted home
+ 2) REVISION's for all child collections in the targeted home
+
+ Note the later is needed to track changes directly to the home child themselves (e.g.
+ property changes, deletion etc).
"""
rev = cls._revisionsSchema
bind = cls._bindSchema
@@ -1812,6 +1818,16 @@
)
+ def revisionFromToken(self, token):
+ if token is None:
+ return 0
+ elif isinstance(token, str):
+ _ignore_uuid, revision = token.split("_", 1)
+ return int(revision)
+ else:
+ return token
+
+
@inlineCallbacks
def syncToken(self):
"""
@@ -1829,23 +1845,58 @@
def _changesQuery(cls): #@NoSelf
bind = cls._bindSchema
rev = cls._revisionsSchema
- return Select([bind.RESOURCE_NAME, rev.COLLECTION_NAME,
- rev.RESOURCE_NAME, rev.DELETED],
- From=rev.join(
- bind,
- (bind.HOME_RESOURCE_ID ==
- Parameter("resourceID")).And(
- rev.RESOURCE_ID ==
- bind.RESOURCE_ID),
- 'left outer'),
- Where=(rev.REVISION > Parameter("token")).And(
- rev.HOME_RESOURCE_ID ==
- Parameter("resourceID")))
+ return Select(
+ [
+ bind.RESOURCE_NAME,
+ rev.COLLECTION_NAME,
+ rev.RESOURCE_NAME,
+ rev.DELETED,
+ ],
+ From=rev.join(
+ bind,
+ (bind.HOME_RESOURCE_ID == Parameter("resourceID")).And
+ (rev.RESOURCE_ID == bind.RESOURCE_ID),
+ 'left outer'
+ ),
+ Where=(rev.REVISION > Parameter("revision")).And
+ (rev.HOME_RESOURCE_ID == Parameter("resourceID"))
+ )
- @inlineCallbacks
def resourceNamesSinceToken(self, token, depth):
+ """
+ Return the changed and deleted resources since a particular sync-token. This simply extracts
+ the revision from from the token then calls L{resourceNamesSinceRevision}.
+ @param revision: the revision to determine changes since
+ @type revision: C{int}
+ """
+
+ return self.resourceNamesSinceRevision(self.revisionFromToken(token), depth)
+
+
+ @inlineCallbacks
+ def resourceNamesSinceRevision(self, revision, depth):
+ """
+ Determine the list of child resources that have changed since the specified sync revision.
+ We do the same SQL query for both depth "1" and "infinity", but filter the results for
+ "1" to only account for a collection change.
+
+ We need to handle shared collection a little differently from owned ones. When a shared collection
+ is bound into a home we record a revision for it using the sharee home id and sharee collection name.
+ That revision is the "starting point" for changes: so if sync occurs with a revision earlier than
+ that, we return the list of all resources in the shared collection since they are all "new" as far
+ as the client is concerned since the shared collection has just appeared. For a later revision, we
+ just report the changes since that one. When a shared collection is removed from a home, we again
+ record a revision for the sharee home and sharee collection name with the "deleted" flag set. That way
+ the shared collection can be reported as removed.
+
+ @param revision: the sync revision to compare to
+ @type revision: C{str}
+ @param depth: depth for determine what changed
+ @type depth: C{str}
+ """
+
results = [
(
path if path else (collection if collection else ""),
@@ -1855,33 +1906,48 @@
for path, collection, name, wasdeleted in
(yield self._changesQuery.on(self._txn,
resourceID=self._resourceID,
- token=token))
+ revision=revision))
]
- deleted = []
+ changed = set()
+ deleted = set()
deleted_collections = set()
changed_collections = set()
for path, name, wasdeleted in results:
if wasdeleted:
- if token:
- deleted.append("%s/%s" % (path, name,))
- if not name:
- deleted_collections.add(path)
+ if revision:
+ if name:
+ # Resource deleted - for depth "1" report collection as changed,
+ # otherwise report resource as deleted
+ if depth == "1":
+ changed.add("%s/" % (path,))
+ else:
+ deleted.add("%s/%s" % (path, name,))
+ else:
+ # Collection was deleted
+ deleted.add("%s/" % (path,))
+ deleted_collections.add(path)
- changed = []
for path, name, wasdeleted in results:
if path not in deleted_collections:
- changed.append("%s/%s" % (path, name,))
- if not name:
+ # Always report collection as changed
+ changed.add("%s/" % (path,))
+ if name:
+ # Resource changed - for depth "infinity" report resource as changed
+ if depth != "1":
+ changed.add("%s/%s" % (path, name,))
+ else:
+ # Collection was changed
changed_collections.add(path)
# Now deal with shared collections
+ # TODO: think about whether this can be done in one query rather than looping over each share
bind = self._bindSchema
rev = self._revisionsSchema
shares = yield self.children()
for share in shares:
if not share.owned():
- sharetoken = 0 if share.name() in changed_collections else token
+ sharerevision = 0 if revision < share._bindRevision else revision
shareID = (yield Select(
[bind.RESOURCE_ID], From=bind,
Where=(bind.RESOURCE_NAME == share.name()).And(
@@ -1897,21 +1963,24 @@
for name, wasdeleted in
(yield Select([rev.RESOURCE_NAME, rev.DELETED],
From=rev,
- Where=(rev.REVISION > sharetoken).And(
+ Where=(rev.REVISION > sharerevision).And(
rev.RESOURCE_ID == shareID)).on(self._txn))
if name
]
for path, name, wasdeleted in results:
if wasdeleted:
- if sharetoken:
- deleted.append("%s/%s" % (path, name,))
+ if sharerevision:
+ if depth == "1":
+ changed.add("%s/" % (path,))
+ else:
+ deleted.add("%s/%s" % (path, name,))
for path, name, wasdeleted in results:
- changed.append("%s/%s" % (path, name,))
+ changed.add("%s/%s" % (path, "" if depth == "1" else name,))
- changed.sort()
- deleted.sort()
+ changed = sorted(changed)
+ deleted = sorted(deleted)
returnValue((changed, deleted))
@@ -2118,18 +2187,22 @@
@inlineCallbacks
def notifyChanged(self):
"""
- Trigger a notification of a change
+ Send notifications, change sync token and bump last modified because the resource has changed. We ensure
+ we only do this once per object per transaction.
"""
+ if self._txn.isNotifiedAlready(self):
+ returnValue(None)
+ self._txn.notificationAddedForObject(self)
+
# Update modified if object still exists
if self._resourceID:
yield self.bumpModified()
- # Only send one set of change notifications per transaction
- if self._notifiers and not self._txn.isNotifiedAlready(self):
+ # Send notifications
+ if self._notifiers:
for notifier in self._notifiers.values():
self._txn.postCommit(notifier.notify)
- self._txn.notificationAddedForObject(self)
@classproperty
@@ -2161,10 +2234,6 @@
delay the transaction whilst waiting for deadlock detection to kick in.
"""
- if self._txn.isBumpedAlready(self):
- returnValue(None)
- self._txn.bumpAddedForObject(self)
-
# NB if modified is bumped we know that sync token will have changed too, so invalidate the cached value
self._syncTokenRevision = None
@@ -2215,8 +2284,13 @@
def revisionFromToken(self, token):
- _ignore_uuid, revision = token.split("_", 1)
- return int(revision)
+ if token is None:
+ return 0
+ elif isinstance(token, str):
+ _ignore_uuid, revision = token.split("_", 1)
+ return int(revision)
+ else:
+ return token
@inlineCallbacks
@@ -2243,19 +2317,32 @@
rev.RESOURCE_ID == Parameter("resourceID")))
- @inlineCallbacks
def resourceNamesSinceToken(self, token):
+ """
+ Return the changed and deleted resources since a particular sync-token. This simply extracts
+ the revision from from the token then calls L{resourceNamesSinceRevision}.
- if token is None:
- token = 0
- elif isinstance(token, str):
- token = self.revisionFromToken(token)
+ @param revision: the revision to determine changes since
+ @type revision: C{int}
+ """
+ return self.resourceNamesSinceRevision(self.revisionFromToken(token))
+
+
+ @inlineCallbacks
+ def resourceNamesSinceRevision(self, revision):
+ """
+ Return the changed and deleted resources since a particular revision.
+
+ @param revision: the revision to determine changes since
+ @type revision: C{int}
+ """
+
results = [
(name if name else "", deleted)
for name, deleted in
(yield self._objectNamesSinceRevisionQuery.on(
- self._txn, revision=token, resourceID=self._resourceID))
+ self._txn, revision=revision, resourceID=self._resourceID))
]
results.sort(key=lambda x: x[1])
@@ -2264,7 +2351,7 @@
for name, wasdeleted in results:
if name:
if wasdeleted:
- if token:
+ if revision:
deleted.append(name)
else:
changed.append(name)
@@ -2302,6 +2389,7 @@
self._addNewRevision.on(self._txn, homeID=self._home._resourceID,
resourceID=self._resourceID,
collectionName=self._name)))[0][0]
+ self._txn.bumpRevisionForObject(self)
@classproperty
@@ -2311,11 +2399,13 @@
resource name).
"""
rev = cls._revisionsSchema
- return Update({
- rev.REVISION: schema.REVISION_SEQ,
- rev.COLLECTION_NAME: Parameter("name")},
- Where=(rev.RESOURCE_ID == Parameter("resourceID")
- ).And(rev.RESOURCE_NAME == None),
+ return Update(
+ {
+ rev.REVISION: schema.REVISION_SEQ,
+ rev.COLLECTION_NAME: Parameter("name")
+ },
+ Where=(rev.RESOURCE_ID == Parameter("resourceID")).And
+ (rev.RESOURCE_NAME == None),
Return=rev.REVISION
)
@@ -2324,18 +2414,44 @@
def _renameSyncToken(self):
self._syncTokenRevision = (yield self._renameSyncTokenQuery.on(
self._txn, name=self._name, resourceID=self._resourceID))[0][0]
+ self._txn.bumpRevisionForObject(self)
@classproperty
+ def _bumpSyncTokenQuery(cls): #@NoSelf
+ """
+ DAL query to change collection sync token.
+ """
+ rev = cls._revisionsSchema
+ return Update(
+ {rev.REVISION: schema.REVISION_SEQ, },
+ Where=(rev.RESOURCE_ID == Parameter("resourceID")).And
+ (rev.RESOURCE_NAME == None),
+ Return=rev.REVISION
+ )
+
+
+ @inlineCallbacks
+ def _bumpSyncToken(self):
+
+ if not self._txn.isRevisionBumpedAlready(self):
+ self._txn.bumpRevisionForObject(self)
+ self._syncTokenRevision = (yield self._bumpSyncTokenQuery.on(
+ self._txn, resourceID=self._resourceID))[0][0]
+
+
+ @classproperty
def _deleteSyncTokenQuery(cls): #@NoSelf
"""
DAL query to update a sync revision to be a tombstone instead.
"""
rev = cls._revisionsSchema
- return Delete(From=rev, Where=(
- rev.HOME_RESOURCE_ID == Parameter("homeID")).And(
- rev.RESOURCE_ID == Parameter("resourceID")).And(
- rev.COLLECTION_NAME == None))
+ return Delete(
+ From=rev,
+ Where=(rev.HOME_RESOURCE_ID == Parameter("homeID")).And
+ (rev.RESOURCE_ID == Parameter("resourceID")).And
+ (rev.COLLECTION_NAME == None)
+ )
@classproperty
@@ -2527,7 +2643,7 @@
_objectTable = None
- def __init__(self, home, name, resourceID, mode, status, message=None, ownerHome=None, ownerName=None):
+ def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None):
self._home = home
self._name = name
@@ -2535,6 +2651,7 @@
self._bindMode = mode
self._bindStatus = status
self._bindMessage = message
+ self._bindRevision = revision
self._ownerHome = home if ownerHome is None else ownerHome
self._ownerName = name if ownerName is None else ownerName
self._created = None
@@ -2610,6 +2727,26 @@
@classmethod
+ def regularBindColumns(cls):
+ """
+ Return a list of column names for retrieval during creation. This allows
+ different child classes to have their own type specific data, but still make use of the
+ common base logic.
+ """
+
+ return (
+ cls._bindSchema.BIND_MODE,
+ cls._bindSchema.HOME_RESOURCE_ID,
+ cls._bindSchema.RESOURCE_ID,
+ cls._bindSchema.RESOURCE_NAME,
+ cls._bindSchema.BIND_STATUS,
+ cls._bindSchema.BIND_REVISION,
+ cls._bindSchema.MESSAGE
+ )
+
+ bindColumnCount = 7
+
+ @classmethod
def additionalBindColumns(cls):
"""
Return a list of column names for retrieval during creation. This allows
@@ -2674,12 +2811,7 @@
child = cls._homeChildSchema
childMetaData = cls._homeChildMetaDataSchema
- columns = [bind.BIND_MODE,
- bind.HOME_RESOURCE_ID,
- bind.RESOURCE_ID,
- bind.RESOURCE_NAME,
- bind.BIND_STATUS,
- bind.MESSAGE]
+ columns = list(cls.regularBindColumns())
columns.extend(cls.additionalBindColumns())
columns.extend(cls.metadataColumns())
return Select(columns,
@@ -2758,6 +2890,11 @@
resourceID=self._resourceID, homeID=shareeHome._resourceID
))[0][0]
+ if status == _BIND_STATUS_ACCEPTED:
+ shareeView = yield shareeHome.childWithName(sharedName)
+ yield shareeView._initSyncToken()
+ yield shareeView._initBindRevision()
+
# Must send notification to ensure cache invalidation occurs
yield self.notifyChanged()
yield shareeHome.notifyChanged()
@@ -2820,6 +2957,7 @@
shareeView._bindStatus = columnMap[bind.BIND_STATUS]
if shareeView._bindStatus == _BIND_STATUS_ACCEPTED:
yield shareeView._initSyncToken()
+ yield shareeView._initBindRevision()
elif shareeView._bindStatus == _BIND_STATUS_DECLINED:
shareeView._deletedSyncToken(sharedRemoval=True)
@@ -2888,6 +3026,23 @@
returnValue(resourceName)
+ @inlineCallbacks
+ def _initBindRevision(self):
+ bind = self._bindSchema
+ self._bindRevision = self._syncTokenRevision
+ yield Update(
+ {bind.BIND_REVISION : Parameter("revision"), },
+ Where=(bind.RESOURCE_ID == Parameter("resourceID")).And
+ (bind.HOME_RESOURCE_ID == Parameter("homeID")),
+ ).on(
+ self._txn,
+ revision=self._bindRevision,
+ resourceID=self._resourceID,
+ homeID=self.viewerHome()._resourceID,
+ )
+ yield self.invalidateQueryCache()
+
+
def shareMode(self):
"""
@see: L{ICalendar.shareMode}
@@ -2982,12 +3137,7 @@
@classmethod
def _bindFor(cls, condition): #@NoSelf
bind = cls._bindSchema
- columns = [bind.BIND_MODE,
- bind.HOME_RESOURCE_ID,
- bind.RESOURCE_ID,
- bind.RESOURCE_NAME,
- bind.BIND_STATUS,
- bind.MESSAGE]
+ columns = list(cls.regularBindColumns())
columns.extend(cls.additionalBindColumns())
return Select(
columns,
@@ -3028,8 +3178,8 @@
cls = self.__class__ # for ease of grepping...
result = []
for item in acceptedRows:
- bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = item[:6] #@UnusedVariable
- additionalBind = item[6:]
+ bindMode, homeID, resourceID, resourceName, bindStatus, bindRevision, bindMessage = item[:self.bindColumnCount] #@UnusedVariable
+ additionalBind = item[self.bindColumnCount:]
assert bindStatus == _BIND_STATUS_ACCEPTED
# TODO: this could all be issued in parallel; no need to serialize
@@ -3038,6 +3188,7 @@
home=(yield self._txn.homeWithResourceID(self._home._homeType, homeID)),
name=resourceName, resourceID=self._resourceID,
mode=bindMode, status=bindStatus,
+ revision=bindRevision,
message=bindMessage, ownerHome=self._home
)
yield new.initFromStore(additionalBind)
@@ -3075,14 +3226,15 @@
result = []
for item in rows:
- bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = item[:6] #@UnusedVariable
- additionalBind = item[6:]
+ bindMode, homeID, resourceID, resourceName, bindStatus, bindRevision, bindMessage = item[:self.bindColumnCount] #@UnusedVariable
+ additionalBind = item[self.bindColumnCount:]
# TODO: this could all be issued in parallel; no need to serialize
# the loop.
new = cls(
home=(yield self._txn.homeWithResourceID(self._home._homeType, homeID)),
name=resourceName, resourceID=self._resourceID,
mode=bindMode, status=bindStatus,
+ revision=bindRevision,
message=bindMessage, ownerHome=self._home
)
yield new.initFromStore(additionalBind)
@@ -3127,9 +3279,9 @@
# Create the actual objects merging in properties
for items in dataRows:
- bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = items[:6] #@UnusedVariable
- additionalBind = items[6:6 + len(cls.additionalBindColumns())]
- metadata = items[6 + len(cls.additionalBindColumns()):]
+ bindMode, homeID, resourceID, resourceName, bindStatus, bindRevision, bindMessage = items[:cls.bindColumnCount] #@UnusedVariable
+ additionalBind = items[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+ metadata = items[cls.bindColumnCount + len(cls.additionalBindColumns()):]
if bindStatus == _BIND_MODE_OWN:
ownerHome = home
@@ -3143,7 +3295,7 @@
home=home,
name=resourceName, resourceID=resourceID,
mode=bindMode, status=bindStatus,
- message=bindMessage,
+ revision=bindRevision, message=bindMessage,
ownerHome=ownerHome, ownerName=ownerName
)
for attr, value in zip(cls.additionalBindAttributes(), additionalBind):
@@ -3193,8 +3345,8 @@
returnValue(None)
item = rows[0]
- bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = item[:6] #@UnusedVariable
- additionalBind = item[6:]
+ bindMode, homeID, resourceID, resourceName, bindStatus, bindRevision, bindMessage = item[:cls.bindColumnCount] #@UnusedVariable
+ additionalBind = item[cls.bindColumnCount:]
#TODO: combine with _invitedBindForNameAndHomeID and sort results
ownerHomeID, ownerName = (yield cls._ownerHomeWithResourceID.on(home._txn, resourceID=resourceID))[0]
@@ -3204,7 +3356,7 @@
home=home,
name=resourceName, resourceID=resourceID,
mode=bindMode, status=bindStatus,
- message=bindMessage,
+ revision=bindRevision, message=bindMessage,
ownerHome=ownerHome, ownerName=ownerName,
)
yield child.initFromStore(additionalBind)
@@ -3249,7 +3401,7 @@
if rows:
item = rows[0]
- bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = item[:6] #@UnusedVariable
+ bindMode, homeID, resourceID, resourceName, bindStatus, bindRevision, bindMessage = item[:cls.bindColumnCount] #@UnusedVariable
# get ownerHomeID
if bindMode == _BIND_MODE_OWN:
ownerHomeID = homeID
@@ -3257,8 +3409,8 @@
else:
ownerHomeID, ownerName = (yield cls._ownerHomeWithResourceID.on(
home._txn, resourceID=resourceID))[0]
- rows[0].insert(6, ownerHomeID)
- rows[0].insert(7, ownerName)
+ rows[0].insert(cls.bindColumnCount, ownerHomeID)
+ rows[0].insert(cls.bindColumnCount + 1, ownerName)
if rows and queryCacher:
# Cache the result
@@ -3268,8 +3420,8 @@
returnValue(None)
item = rows[0] #@UnusedVariable
- bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage, ownerHomeID, ownerName = item[:8]
- additionalBind = item[8:]
+ bindMode, homeID, resourceID, resourceName, bindStatus, bindRevision, bindMessage, ownerHomeID, ownerName = item[:cls.bindColumnCount + 2]
+ additionalBind = item[cls.bindColumnCount + 2:]
if bindMode == _BIND_MODE_OWN:
ownerHome = home
@@ -3280,7 +3432,7 @@
home=home,
name=name, resourceID=resourceID,
mode=bindMode, status=bindStatus,
- message=bindMessage,
+ revision=bindRevision, message=bindMessage,
ownerHome=ownerHome, ownerName=ownerName,
)
yield child.initFromStore(additionalBind)
@@ -3317,8 +3469,8 @@
returnValue(None)
item = rows[0]
- bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = item[:6] #@UnusedVariable
- additionalBind = item[6:]
+ bindMode, homeID, resourceID, resourceName, bindStatus, bindRevision, bindMessage = item[:cls.bindColumnCount] #@UnusedVariable
+ additionalBind = item[cls.bindColumnCount:]
if bindMode == _BIND_MODE_OWN:
ownerHome = home
@@ -3330,7 +3482,7 @@
home=home,
name=resourceName, resourceID=resourceID,
mode=bindMode, status=bindStatus,
- message=bindMessage,
+ revision=bindRevision, message=bindMessage,
ownerHome=ownerHome, ownerName=ownerName,
)
yield child.initFromStore(additionalBind)
@@ -3940,6 +4092,22 @@
return False
+ def resourceNamesSinceRevision(self, revision):
+ """
+ Return the changed and deleted resources since a particular revision. This implementation takes
+ into account sharing by making use of the bindRevision attribute to determine if the requested
+ revision is earlier than the share acceptance. If so, then we need to return all resources in
+ the results since the collection is in effect "new".
+
+ @param revision: the revision to determine changes since
+ @type revision: C{int}
+ """
+
+ if revision < self._bindRevision:
+ revision = 0
+ return super(CommonHomeChild, self).resourceNamesSinceRevision(revision)
+
+
@inlineCallbacks
def _loadPropertyStore(self, props=None):
if props is None:
@@ -4010,18 +4178,25 @@
@inlineCallbacks
def notifyChanged(self):
"""
- Trigger a notification of a change
+ Send notifications, change sync token and bump last modified because the resource has changed. We ensure
+ we only do this once per object per transaction.
"""
+ if self._txn.isNotifiedAlready(self):
+ returnValue(None)
+ self._txn.notificationAddedForObject(self)
+
# Update modified if object still exists
if self._resourceID:
yield self.bumpModified()
- # Only send one set of change notifications per transaction
- if self._notifiers and not self._txn.isNotifiedAlready(self):
+ # We now also bump the collection level sync token on any change
+ yield self._bumpSyncToken()
+
+ # Send notifications
+ if self._notifiers:
for notifier in self._notifiers.values():
self._txn.postCommit(notifier.notify)
- self._txn.notificationAddedForObject(self)
@classproperty
@@ -4053,10 +4228,6 @@
delay the transaction whilst waiting for deadlock detection to kick in.
"""
- if self._txn.isBumpedAlready(self):
- returnValue(None)
- self._txn.bumpAddedForObject(self)
-
@inlineCallbacks
def _bumpModified(subtxn):
yield self._lockLastModifiedQuery.on(subtxn, resourceID=self._resourceID)
@@ -4822,14 +4993,18 @@
def notifyChanged(self):
"""
- Trigger a notification of a change
+ Send notifications, change sync token and bump last modified because the resource has changed. We ensure
+ we only do this once per object per transaction.
"""
- # Only send one set of change notifications per transaction
- if self._notifiers and not self._txn.isNotifiedAlready(self):
+ if self._txn.isNotifiedAlready(self):
+ returnValue(None)
+ self._txn.notificationAddedForObject(self)
+
+ # Send notifications
+ if self._notifiers:
for notifier in self._notifiers.values():
self._txn.postCommit(notifier.notify)
- self._txn.notificationAddedForObject(self)
@classproperty
Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql 2013-05-15 16:47:55 UTC (rev 11190)
@@ -68,6 +68,7 @@
"CALENDAR_RESOURCE_NAME" nvarchar2(255),
"BIND_MODE" integer not null,
"BIND_STATUS" integer not null,
+ "BIND_REVISION" integer default 0 not null,
"MESSAGE" nclob,
"TRANSP" integer default 0 not null,
"ALARM_VEVENT_TIMED" nclob default null,
@@ -228,6 +229,7 @@
"ADDRESSBOOK_RESOURCE_NAME" nvarchar2(255),
"BIND_MODE" integer not null,
"BIND_STATUS" integer not null,
+ "BIND_REVISION" integer default 0 not null,
"MESSAGE" nclob,
primary key("ADDRESSBOOK_HOME_RESOURCE_ID", "ADDRESSBOOK_RESOURCE_ID"),
unique("ADDRESSBOOK_HOME_RESOURCE_ID", "ADDRESSBOOK_RESOURCE_NAME")
Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql 2013-05-15 16:47:55 UTC (rev 11190)
@@ -130,6 +130,7 @@
CALENDAR_RESOURCE_NAME varchar(255) not null,
BIND_MODE integer not null, -- enum CALENDAR_BIND_MODE
BIND_STATUS integer not null, -- enum CALENDAR_BIND_STATUS
+ BIND_REVISION integer default 0 not null,
MESSAGE text,
TRANSP integer default 0 not null, -- enum CALENDAR_TRANSP
ALARM_VEVENT_TIMED text default null,
@@ -405,6 +406,7 @@
ADDRESSBOOK_RESOURCE_NAME varchar(255) not null,
BIND_MODE integer not null, -- enum CALENDAR_BIND_MODE
BIND_STATUS integer not null, -- enum CALENDAR_BIND_STATUS
+ BIND_REVISION integer default 0 not null,
MESSAGE text, -- FIXME: xml?
primary key (ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_ID), -- implicit index
Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_18_to_19.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_18_to_19.sql 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_18_to_19.sql 2013-05-15 16:47:55 UTC (rev 11190)
@@ -32,7 +32,8 @@
-- Calendar bind related updates
alter table CALENDAR_BIND
- add ("TRANSP" integer default 0 not null,
+ add ("BIND_REVISION" integer default 0 not null,
+ "TRANSP" integer default 0 not null,
"ALARM_VEVENT_TIMED" nclob default null,
"ALARM_VEVENT_ALLDAY" nclob default null,
"ALARM_VTODO_TIMED" nclob default null,
@@ -46,6 +47,13 @@
insert into CALENDAR_TRANSP (DESCRIPTION, ID) values ('opaque', 0);
insert into CALENDAR_TRANSP (DESCRIPTION, ID) values ('transparent', 1);
+
+-- Addressbook bind related updates
+
+alter table ADDRESSBOOK_BIND
+ add ("BIND_REVISION" integer default 0 not null);
+
+
-- Now update the version
-- No data upgrades
update CALENDARSERVER set VALUE = '19' where NAME = 'VERSION';
Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_18_to_19.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_18_to_19.sql 2013-05-15 16:44:50 UTC (rev 11189)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_18_to_19.sql 2013-05-15 16:47:55 UTC (rev 11190)
@@ -33,6 +33,7 @@
-- Calendar bind related updates
alter table CALENDAR_BIND
+ add column BIND_REVISION integer default 0 not null,
add column TRANSP integer default 0 not null,
add column ALARM_VEVENT_TIMED text default null,
add column ALARM_VEVENT_ALLDAY text default null,
@@ -47,6 +48,13 @@
insert into CALENDAR_TRANSP values (0, 'opaque' );
insert into CALENDAR_TRANSP values (1, 'transparent');
+
+-- Addressbook bind related updates
+
+alter table ADDRESSBOOK_BIND
+ add column BIND_REVISION integer default 0 not null;
+
+
-- Now update the version
-- No data upgrades
update CALENDARSERVER set VALUE = '19' where NAME = 'VERSION';
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130515/d856fc4b/attachment-0001.html>
More information about the calendarserver-changes
mailing list