[CalendarServer-changes] [8407] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Tue Dec 6 12:36:21 PST 2011
Revision: 8407
http://trac.macosforge.org/projects/calendarserver/changeset/8407
Author: cdaboo at apple.com
Date: 2011-12-06 12:36:20 -0800 (Tue, 06 Dec 2011)
Log Message:
-----------
Make sure etag and getlastmodified work on collections. Required deferring etag() method, tweaking notifications to ensure modified
column in db is properly updated. Schema change applied to DB.
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/push/applepush.py
CalendarServer/trunk/calendarserver/webadmin/resource.py
CalendarServer/trunk/calendarserver/webcal/resource.py
CalendarServer/trunk/twext/web2/dav/resource.py
CalendarServer/trunk/twext/web2/dav/static.py
CalendarServer/trunk/twext/web2/static.py
CalendarServer/trunk/twext/web2/test/test_static.py
CalendarServer/trunk/twistedcaldav/directory/addressbook.py
CalendarServer/trunk/twistedcaldav/directory/calendar.py
CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
CalendarServer/trunk/twistedcaldav/directory/principal.py
CalendarServer/trunk/twistedcaldav/mail.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/schedule.py
CalendarServer/trunk/twistedcaldav/sharing.py
CalendarServer/trunk/twistedcaldav/simpleresource.py
CalendarServer/trunk/twistedcaldav/storebridge.py
CalendarServer/trunk/twistedcaldav/timezoneservice.py
CalendarServer/trunk/twistedcaldav/timezonestdservice.py
CalendarServer/trunk/txdav/caldav/datastore/sql.py
CalendarServer/trunk/txdav/caldav/datastore/test/common.py
CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
CalendarServer/trunk/txdav/carddav/datastore/sql.py
CalendarServer/trunk/txdav/carddav/datastore/test/common.py
CalendarServer/trunk/txdav/common/datastore/file.py
CalendarServer/trunk/txdav/common/datastore/sql.py
CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql
CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_6_to_7.sql
CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_6_to_7.sql
Modified: CalendarServer/trunk/calendarserver/push/applepush.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/applepush.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/calendarserver/push/applepush.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -26,7 +26,7 @@
from twext.web2.server import parsePOSTData
from twisted.application import service
from twisted.internet import reactor, protocol
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
from twisted.internet.protocol import ClientFactory, ReconnectingClientFactory
from twistedcaldav.extensions import DAVResource, DAVResourceWithoutChildrenMixin
from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn
@@ -498,7 +498,7 @@
return self._dead_properties
def etag(self):
- return None
+ return succeed(None)
def checkPreconditions(self, request):
return None
Modified: CalendarServer/trunk/calendarserver/webadmin/resource.py
===================================================================
--- CalendarServer/trunk/calendarserver/webadmin/resource.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/calendarserver/webadmin/resource.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -504,7 +504,7 @@
def etag(self):
# Can't be calculated here
- return None
+ return succeed(None)
def contentLength(self):
# Can't be calculated here
Modified: CalendarServer/trunk/calendarserver/webcal/resource.py
===================================================================
--- CalendarServer/trunk/calendarserver/webcal/resource.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/calendarserver/webcal/resource.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
+# Copyright (c) 2009-2011 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -38,7 +38,9 @@
from twistedcaldav.config import config
from twistedcaldav.extensions import DAVFile, ReadOnlyResourceMixIn
+from twisted.internet.defer import succeed
+
class WebCalendarResource (ReadOnlyResourceMixIn, DAVFile):
def defaultAccessControlList(self):
@@ -55,7 +57,7 @@
def etag(self):
# Can't be calculated here
- return None
+ return succeed(None)
def contentLength(self):
# Can't be calculated here
Modified: CalendarServer/trunk/twext/web2/dav/resource.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/resource.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twext/web2/dav/resource.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -1,6 +1,6 @@
# -*- test-case-name: twext.web2.dav.test.test_resource -*-
##
-# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+# Copyright (c) 2005-2011 Apple Computer, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -231,7 +231,7 @@
returnValue(davxml.ResourceType.empty) #@UndefinedVariable
if name == "getetag":
- etag = self.etag()
+ etag = (yield self.etag())
if etag is None:
returnValue(None)
returnValue(davxml.GETETag(etag.generate()))
Modified: CalendarServer/trunk/twext/web2/dav/static.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/static.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twext/web2/dav/static.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -1,6 +1,6 @@
# -*- test-case-name: twext.web2.dav.test.test_static -*-
##
-# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+# Copyright (c) 2005-2011 Apple Computer, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -86,9 +86,9 @@
##
def etag(self):
- if not self.fp.exists(): return None
+ if not self.fp.exists(): return succeed(None)
if self.hasDeadProperty(TwistedGETContentMD5):
- return http_headers.ETag(str(self.readDeadProperty(TwistedGETContentMD5)))
+ return succeed(http_headers.ETag(str(self.readDeadProperty(TwistedGETContentMD5))))
else:
return super(DAVFile, self).etag()
Modified: CalendarServer/trunk/twext/web2/static.py
===================================================================
--- CalendarServer/trunk/twext/web2/static.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twext/web2/static.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -1,7 +1,7 @@
# -*- test-case-name: twext.web2.test.test_static -*-
##
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
-# Copyright (c) 2010 Apple Computer, Inc. All rights reserved.
+# Copyright (c) 2010-2011 Apple Computer, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -39,7 +39,7 @@
# Twisted Imports
from twext.python.filepath import CachingFilePath as FilePath
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
from zope.interface import implements
class MetaDataMixin(object):
@@ -51,7 +51,7 @@
"""
@return: The current etag for the resource if available, None otherwise.
"""
- return None
+ return succeed(None)
def lastModified(self):
"""
@@ -102,10 +102,11 @@
def checkPreconditions(self, request):
# This code replaces the code in resource.RenderMixin
if request.method not in ("GET", "HEAD"):
+ etag = (yield self.etag())
http.checkPreconditions(
request,
entityExists = self.exists(),
- etag = self.etag(),
+ etag = etag,
lastModified = self.lastModified(),
)
@@ -134,8 +135,9 @@
# Content-* headers refer to the response content, not
# (necessarily) to the resource content, so they depend on the
# request method, and therefore can't be set here.
+ etag = (yield self.etag())
for (header, value) in (
- ("etag", self.etag()),
+ ("etag", etag),
("last-modified", self.lastModified()),
):
if value is not None:
@@ -155,8 +157,8 @@
def etag(self):
lastModified = self.lastModified()
- return http_headers.ETag("%X-%X" % (lastModified, hash(self.data)),
- weak=(time.time() - lastModified <= 1))
+ return succeed(http_headers.ETag("%X-%X" % (lastModified, hash(self.data)),
+ weak=(time.time() - lastModified <= 1)))
def lastModified(self):
return self.creationDate()
@@ -247,7 +249,7 @@
return self.fp.exists()
def etag(self):
- if not self.fp.exists(): return None
+ if not self.fp.exists(): return succeed(None)
st = self.fp.statinfo
@@ -258,10 +260,10 @@
#
weak = (time.time() - st.st_mtime <= 1)
- return http_headers.ETag(
+ return succeed(http_headers.ETag(
"%X-%X-%X" % (st.st_ino, st.st_size, st.st_mtime),
weak=weak
- )
+ ))
def lastModified(self):
if self.fp.exists():
Modified: CalendarServer/trunk/twext/web2/test/test_static.py
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_static.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twext/web2/test/test_static.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -1,4 +1,4 @@
-# Copyright (c) 2008 Twisted Matrix Laboratories.
+# Copyright (c) 2008-2011 Twisted Matrix Laboratories.
# See LICENSE for details.
"""
@@ -34,7 +34,10 @@
"""
Test that we can get an ETag
"""
- self.failUnless(self.data.etag())
+ def _defer(result):
+ self.failUnless(result)
+ d = self.data.etag().addCallback(_defer)
+ return d
def test_render(self):
Modified: CalendarServer/trunk/twistedcaldav/directory/addressbook.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/addressbook.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twistedcaldav/directory/addressbook.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
from twext.web2.http import HTTPError
from twext.web2.http_headers import ETag, MimeType
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
from twistedcaldav.config import config
from twistedcaldav.directory.idirectory import IDirectoryService
@@ -66,7 +66,7 @@
return config.ProvisioningResourceACL
def etag(self):
- return ETag(str(uuid4()))
+ return succeed(ETag(str(uuid4())))
def contentType(self):
return MimeType("httpd", "unix-directory")
Modified: CalendarServer/trunk/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendar.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twistedcaldav/directory/calendar.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -1,6 +1,6 @@
# -*- test-case-name: twistedcaldav.directory.test.test_calendar -*-
##
-# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -66,7 +66,7 @@
return config.ProvisioningResourceACL
def etag(self):
- return ETag(str(uuid4()))
+ return succeed(ETag(str(uuid4())))
def contentType(self):
return MimeType("httpd", "unix-directory")
Modified: CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -1,6 +1,6 @@
# -*- test-case-name: twistedcaldav.directory.test.test_proxyprincipalmembers -*-
##
-# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -225,7 +225,7 @@
return True
def etag(self):
- return None
+ return succeed(None)
def deadProperties(self):
if not hasattr(self, "_dead_properties"):
Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -163,7 +163,7 @@
return self._dead_properties
def etag(self):
- return None
+ return succeed(None)
def principalForShortName(self, recordType, name):
return self.principalForRecord(self.directory.recordWithShortName(recordType, name))
@@ -756,7 +756,7 @@
return self._dead_properties
def etag(self):
- return None
+ return succeed(None)
##
# HTTP
Modified: CalendarServer/trunk/twistedcaldav/mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/mail.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twistedcaldav/mail.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -1,6 +1,6 @@
# -*- test-case-name: twistedcaldav.test.test_mail -*-
##
-# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -442,7 +442,7 @@
return self._dead_properties
def etag(self):
- return None
+ return succeed(None)
def checkPreconditions(self, request):
return None
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -29,6 +29,7 @@
"isAddressBookCollectionResource",
]
+import hashlib
from urlparse import urlsplit
import urllib
import uuid
@@ -53,7 +54,7 @@
from twext.web2.dav.resource import TwistedACLInheritable
from twext.web2.dav.util import joinURL, parentForURL, normalizeURL
from twext.web2.http import HTTPError, RedirectResponse, StatusResponse, Response
-from twext.web2.http_headers import MimeType
+from twext.web2.http_headers import MimeType, ETag
from twext.web2.stream import MemoryStream
from twistedcaldav import caldavxml, customxml
@@ -1479,6 +1480,7 @@
# Stuff from CalDAVFile
#
+ @inlineCallbacks
def checkPreconditions(self, request):
"""
We override the base class to handle the special implicit scheduling weak ETag behavior
@@ -1494,7 +1496,8 @@
if request.method not in ("GET", "HEAD"):
# Always test against the current etag first just in case schedule-etags is out of sync
- etags = (self.etag(), ) + tuple([http_headers.ETag(etag) for etag in etags])
+ etag = (yield self.etag())
+ etags = (etag, ) + tuple([http_headers.ETag(etag) for etag in etags])
# Loop over each tag and succeed if any one matches, else re-raise last exception
exists = self.exists()
@@ -1519,13 +1522,12 @@
# Check per-method preconditions
method = getattr(self, "preconditions_" + request.method, None)
if method:
- response = maybeDeferred(method, request)
- response.addCallback(lambda _: request)
- return response
+ returnValue((yield method(request)))
else:
- return None
+ returnValue(None)
- return super(CalDAVResource, self).checkPreconditions(request)
+ result = (yield super(CalDAVResource, self).checkPreconditions(request))
+ returnValue(result)
@inlineCallbacks
def createCalendar(self, request):
@@ -2483,11 +2485,28 @@
def principalForRecord(self):
raise NotImplementedError("Subclass must implement principalForRecord()")
+ @inlineCallbacks
+ def etag(self):
+ """
+ Use the sync token as the etag
+ """
+ if self._newStoreHome:
+ token = (yield self.getInternalSyncToken())
+ returnValue(ETag(hashlib.md5(token).hexdigest()))
+ else:
+ returnValue(None)
+
+ def lastModified(self):
+ return self._newStoreHome.modified() if self._newStoreHome else None
+
+ def creationDate(self):
+ return self._newStoreHome.created() if self._newStoreHome else None
+
def notifierID(self, label="default"):
self._newStoreHome.notifierID(label)
def notifyChanged(self):
- self._newStoreHome.notifyChanged()
+ return self._newStoreHome.notifyChanged()
# Methods not supported
http_ACL = None
Modified: CalendarServer/trunk/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/schedule.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twistedcaldav/schedule.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -1,6 +1,6 @@
# -*- test-case-name: twistedcaldav.directory.test.test_calendar -*-
##
-# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -496,7 +496,7 @@
return self._dead_properties
def etag(self):
- return None
+ return succeed(None)
def checkPreconditions(self, request):
return None
Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twistedcaldav/sharing.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -1046,7 +1046,7 @@
yield sharedCollection.writeProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Transparent()), request)
# Notify client of changes
- self.notifyChanged()
+ yield self.notifyChanged()
# Return the URL of the shared collection
returnValue(XMLResponse(
@@ -1092,7 +1092,7 @@
yield self.sharesDB().removeRecordForShareUID(share.shareuid)
# Notify client of changes
- self.notifyChanged()
+ yield self.notifyChanged()
@inlineCallbacks
def declineShare(self, request, hostUrl, inviteUID):
Modified: CalendarServer/trunk/twistedcaldav/simpleresource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/simpleresource.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twistedcaldav/simpleresource.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -71,7 +71,7 @@
return self._dead_properties
def etag(self):
- return None
+ return succeed(None)
def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
return succeed(self.defaultACL)
Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -59,10 +59,12 @@
from txdav.base.propertystore.base import PropertyName
from txdav.caldav.icalendarstore import QuotaExceeded
from txdav.common.icommondatastore import NoSuchObjectResourceError
-from urlparse import urlsplit
-import time
from txdav.idav import PropertyChangeNotAllowedError
+import time
+import hashlib
+from urlparse import urlsplit
+
"""
Wrappers to translate between the APIs in L{txdav.caldav.icalendarstore} and
L{txdav.carddav.iaddressbookstore} and those in L{twistedcaldav}.
@@ -172,7 +174,7 @@
def etag(self):
- return ETag(self._newStoreObject.md5()) if self._newStoreObject is not None else None
+ return succeed(ETag(self._newStoreObject.md5()) if self._newStoreObject is not None else None)
def contentType(self):
@@ -319,8 +321,16 @@
return self._name
+ @inlineCallbacks
def etag(self):
- return ETag(self._newStoreObject.md5()) if self._newStoreObject else None
+ """
+ Use the sync token as the etag
+ """
+ if self._newStoreObject:
+ token = (yield self.getInternalSyncToken())
+ returnValue(ETag(hashlib.md5(token).hexdigest()))
+ else:
+ returnValue(None)
def lastModified(self):
@@ -586,13 +596,14 @@
if code is None:
+ etag = (yield newchild.etag())
if not return_changed or dataChanged is None:
xmlresponses.append(
davxml.PropertyStatusResponse(
davxml.HRef.fromString(newchildURL),
davxml.PropertyStatus(
davxml.PropertyContainer(
- davxml.GETETag.fromString(newchild.etag().generate()),
+ davxml.GETETag.fromString(etag.generate()),
customxml.UID.fromString(component.resourceUID()),
),
davxml.Status.fromResponseCode(responsecode.OK),
@@ -605,7 +616,7 @@
davxml.HRef.fromString(newchildURL),
davxml.PropertyStatus(
davxml.PropertyContainer(
- davxml.GETETag.fromString(newchild.etag().generate()),
+ davxml.GETETag.fromString(etag.generate()),
self.xmlDataElementType().fromTextData(dataChanged),
),
davxml.Status.fromResponseCode(responsecode.OK),
@@ -757,12 +768,13 @@
code = responsecode.BAD_REQUEST
if code is None:
+ etag = (yield newchild.etag())
xmlresponses.append(
davxml.PropertyStatusResponse(
davxml.HRef.fromString(newchildURL),
davxml.PropertyStatus(
davxml.PropertyContainer(
- davxml.GETETag.fromString(newchild.etag().generate()),
+ davxml.GETETag.fromString(etag.generate()),
customxml.UID.fromString(component.resourceUID()),
),
davxml.Status.fromResponseCode(responsecode.OK),
@@ -797,7 +809,8 @@
yield updateResource.authorize(request, (davxml.Write(),))
# Check if match
- if ifmatch and ifmatch != updateResource.etag().generate():
+ etag = (yield updateResource.etag())
+ if ifmatch and ifmatch != etag.generate():
raise HTTPError(responsecode.PRECONDITION_FAILED)
yield self.storeResourceData(request, updateResource, href, component, componentdata)
@@ -820,7 +833,7 @@
davxml.HRef.fromString(href),
davxml.PropertyStatus(
davxml.PropertyContainer(
- davxml.GETETag.fromString(updateResource.etag().generate()),
+ davxml.GETETag.fromString(etag.generate()),
),
davxml.Status.fromResponseCode(responsecode.OK),
)
@@ -850,7 +863,8 @@
raise HTTPError(responsecode.NOT_FOUND)
# Check if match
- if ifmatch and ifmatch != deleteResource.etag().generate():
+ etag = (yield deleteResource.etag())
+ if ifmatch and ifmatch != etag.generate():
raise HTTPError(responsecode.PRECONDITION_FAILED)
yield deleteResource.storeRemove(
@@ -892,7 +906,7 @@
self._newStoreObject.notifierID(label)
def notifyChanged(self):
- self._newStoreObject.notifyChanged()
+ return self._newStoreObject.notifyChanged()
class _CalendarCollectionBehaviorMixin():
"""
Modified: CalendarServer/trunk/twistedcaldav/timezoneservice.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/timezoneservice.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twistedcaldav/timezoneservice.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2008 Apple Inc. All rights reserved.
+# Copyright (c) 2008-2011 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -72,7 +72,7 @@
return self._dead_properties
def etag(self):
- return None
+ return succeed(None)
def checkPreconditions(self, request):
return None
Modified: CalendarServer/trunk/twistedcaldav/timezonestdservice.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/timezonestdservice.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/twistedcaldav/timezonestdservice.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -121,7 +121,7 @@
return self._dead_properties
def etag(self):
- return None
+ return succeed(None)
def checkPreconditions(self, request):
return None
Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -681,7 +681,7 @@
else:
yield self._calendar._updateRevision(self._name)
- self._calendar.notifyChanged()
+ yield self._calendar.notifyChanged()
@inlineCallbacks
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -921,12 +921,6 @@
[
("update", "CalDAV|home1"),
("update", "CalDAV|home1/calendar_1"),
- ("update", "CalDAV|home1"),
- ("update", "CalDAV|home1/calendar_1"),
- ("update", "CalDAV|home1"),
- ("update", "CalDAV|home1/calendar_1"),
- ("update", "CalDAV|home1"),
- ("update", "CalDAV|home1/calendar_1"),
]
)
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -351,7 +351,7 @@
def test_putConcurrency(self):
"""
Test that two concurrent attempts to PUT different calendar object
- resources to the same address book home does not cause a deadlock.
+ resources to the same calendar home does not cause a deadlock.
"""
calendarStore = self._sqlCalendarStore
Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -251,7 +251,7 @@
else:
yield self._addressbook._updateRevision(self._name)
- self._addressbook.notifyChanged()
+ yield self._addressbook.notifyChanged()
@inlineCallbacks
Modified: CalendarServer/trunk/txdav/carddav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/common.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/common.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -518,10 +518,6 @@
[
("update", "CardDAV|home1"),
("update", "CardDAV|home1/addressbook_1"),
- ("update", "CardDAV|home1"),
- ("update", "CardDAV|home1/addressbook_1"),
- ("update", "CardDAV|home1"),
- ("update", "CardDAV|home1/addressbook_1"),
]
)
Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/txdav/common/datastore/file.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -199,6 +199,7 @@
self._addressbookHomes = {}
self._notificationHomes = {}
self._notifierFactory = notifierFactory
+ self._notifiedAlready = set()
self._migrating = migrating
extraInterfaces = []
@@ -253,7 +254,13 @@
def apnSubscriptionsByKey(self, key):
return NotImplementedError
+ def isNotifiedAlready(self, obj):
+ return obj in self._notifiedAlready
+
+ def notificationAddedForObject(self, obj):
+ self._notifiedAlready.add(obj)
+
class StubResource(object):
"""
Just enough resource to keep the shared sql DB classes going.
@@ -469,15 +476,10 @@
self._transaction.addOperation(do, "create child %r" % (name,))
props = c.properties()
props[PropertyName(*ResourceType.qname())] = c.resourceType()
- self.createdChild(c)
self.notifyChanged()
return c
- def createdChild(self, child):
- pass
-
-
@writeOperation
def removeChildWithName(self, name):
if name.startswith(".") or name in self._removedChildren:
@@ -584,9 +586,12 @@
"""
Trigger a notification of a change
"""
- if self._notifiers:
+
+ # Only send one set of change notifications per transaction
+ if self._notifiers and not self._transaction.isNotifiedAlready(self):
for notifier in self._notifiers:
self._transaction.postCommit(notifier.notify)
+ self._transaction.notificationAddedForObject(self)
class CommonHomeChild(FileMetaDataMixin, LoggingMixIn, FancyEqMixin):
@@ -915,9 +920,12 @@
"""
Trigger a notification of a change
"""
- if self._notifiers:
+
+ # Only send one set of change notifications per transaction
+ if self._notifiers and not self._transaction.isNotifiedAlready(self):
for notifier in self._notifiers:
self._transaction.postCommit(notifier.notify)
+ self._transaction.notificationAddedForObject(self)
class CommonObjectResource(FileMetaDataMixin, LoggingMixIn, FancyEqMixin):
Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py 2011-12-06 20:36:20 UTC (rev 8407)
@@ -62,7 +62,7 @@
from twext.python.clsprop import classproperty
from twext.enterprise.ienterprise import AlreadyFinishedError
from twext.enterprise.dal.parseschema import significant
-from twext.enterprise.dal.syntax import Delete
+from twext.enterprise.dal.syntax import Delete, utcNowSQL
from twext.enterprise.dal.syntax import Insert
from twext.enterprise.dal.syntax import Len
from twext.enterprise.dal.syntax import Max
@@ -234,6 +234,7 @@
self._postCommitOperations = []
self._postAbortOperations = []
self._notifierFactory = notifierFactory
+ self._notifiedAlready = set()
self._label = label
self._migrating = migrating
self._primaryHomeType = None
@@ -429,6 +430,12 @@
self._postAbortOperations.append(operation)
+ def isNotifiedAlready(self, obj):
+ return obj in self._notifiedAlready
+
+ def notificationAddedForObject(self, obj):
+ self._notifiedAlready.add(obj)
+
_savepointCounter = 0
def _savepoint(self):
@@ -713,6 +720,8 @@
self._sharedChildren = {}
self._notifiers = notifiers
self._quotaUsedBytes = None
+ self._created = None
+ self._modified = None
# Needed for REVISION/BIND table join
self._revisionBindJoinTable = {}
@@ -739,6 +748,13 @@
From=home,
Where=home.RESOURCE_ID == Parameter("resourceID"))
+ @classproperty
+ def _metaDataQuery(cls): #@NoSelf
+ metadata = cls._homeMetaDataSchema
+ return Select([metadata.CREATED, metadata.MODIFIED],
+ From=metadata,
+ Where=metadata.RESOURCE_ID == Parameter("resourceID"))
+
@inlineCallbacks
def initFromStore(self, no_cache=False):
"""
@@ -755,6 +771,8 @@
if result:
self._resourceID = result[0][0]
+ self._created, self._modified = (yield self._metaDataQuery.on(
+ self._txn, resourceID=self._resourceID))[0]
yield self._loadPropertyStore()
returnValue(self)
else:
@@ -956,10 +974,6 @@
child = (yield self.childWithName(name))
returnValue(child)
- def createdChild(self, child):
- pass
-
-
@inlineCallbacks
def removeChildWithName(self, name):
child = yield self.childWithName(name)
@@ -1117,11 +1131,11 @@
def created(self):
- return None
+ return datetimeMktime(parseSQLTimestamp(self._created)) if self._created else None
def modified(self):
- return None
+ return datetimeMktime(parseSQLTimestamp(self._modified)) if self._modified else None
@classproperty
@@ -1218,7 +1232,7 @@
resourceID=self._resourceID)
self._quotaUsedBytes = 0
-
+
def addNotifier(self, notifier):
if self._notifiers is None:
self._notifiers = ()
@@ -1242,13 +1256,45 @@
else:
returnValue(None)
+ @classproperty
+ def _changeLastModifiedQuery(cls): #@NoSelf
+ meta = cls._homeMetaDataSchema
+ return Update({meta.MODIFIED: utcNowSQL},
+ Where=meta.RESOURCE_ID == Parameter("resourceID"),
+ Return=meta.MODIFIED)
+
+ @inlineCallbacks
+ def bumpModified(self):
+ """
+ Bump the MODIFIED value. A possible deadlock could happen here if two or more
+ simultaneous changes are happening. In that case it is OK for the MODIFIED change
+ to fail so long as at least one works. We will use SAVEPOINT logic to handle
+ ignoring the deadlock error.
+ """
+
+ def _bumpModified(subtxn):
+ return self._changeLastModifiedQuery.on(subtxn, resourceID=self._resourceID)
+
+ try:
+ self._modified = (yield self._txn.subtransaction(_bumpModified, retries=0))[0][0]
+ except AllRetriesFailed:
+ pass
+
+ @inlineCallbacks
def notifyChanged(self):
"""
Trigger a notification of a change
"""
- if self._notifiers:
+
+ # 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):
for notifier in self._notifiers:
self._txn.postCommit(notifier.notify)
+ self._txn.notificationAddedForObject(self)
@@ -1903,10 +1949,9 @@
PropertyName.fromElement(ResourceType)
] = child.resourceType()
yield child._initSyncToken()
- home.createdChild(child)
# Change notification for a create is on the home collection
- home.notifyChanged()
+ yield home.notifyChanged()
returnValue(child)
@@ -1997,8 +2042,11 @@
self._home._children[name] = self
yield self._renameSyncToken()
- self.notifyChanged()
+ yield self.notifyChanged()
+ # Make sure home collection modified is changed - not that we do not use _home.notifiedChanged() here
+ # since we are sending the notification on the existing child collection object
+ yield self._home.bumpModified()
@classproperty
@@ -2023,9 +2071,13 @@
self._modified = None
self._objects = {}
- self.notifyChanged()
+ yield self.notifyChanged()
+ # Make sure home collection modified is changed - not that we do not use _home.notifiedChanged() here
+ # since we are sending the notification on the previously existing child collection object
+ yield self._home.bumpModified()
+
def ownerHome(self):
return self._home
@@ -2247,7 +2299,7 @@
self._objects.pop(name, None)
self._objects.pop(uid, None)
yield self._deleteRevision(name)
- self.notifyChanged()
+ yield self.notifyChanged()
@classproperty
def _moveParentUpdateQuery(cls): #@NoSelf
@@ -2290,7 +2342,7 @@
self._objects.pop(uid, None)
self._objects.pop(child._resourceID, None)
yield self._deleteRevision(name)
- self.notifyChanged()
+ yield self.notifyChanged()
# Adjust the child to be a child of the new parent and update ancillary tables
yield self._moveParentUpdateQuery.on(
@@ -2303,7 +2355,7 @@
# Signal sync change on new collection
yield newparent._insertRevision(name)
- newparent.notifyChanged()
+ yield newparent.notifyChanged()
def objectResourcesHaveProperties(self):
return False
@@ -2376,13 +2428,45 @@
else:
returnValue(None)
+ @classproperty
+ def _changeLastModifiedQuery(cls): #@NoSelf
+ schema = cls._homeChildSchema
+ return Update({schema.MODIFIED: utcNowSQL},
+ Where=schema.RESOURCE_ID == Parameter("resourceID"),
+ Return=schema.MODIFIED)
+
+ @inlineCallbacks
+ def bumpModified(self):
+ """
+ Bump the MODIFIED value. A possible deadlock could happen here if two or more
+ simultaneous changes are happening. In that case it is OK for the MODIFIED change
+ to fail so long as at least one works. We will use SAVEPOINT logic to handle
+ ignoring the deadlock error.
+ """
+
+ def _bumpModified(subtxn):
+ return self._changeLastModifiedQuery.on(subtxn, resourceID=self._resourceID)
+
+ try:
+ self._modified = (yield self._txn.subtransaction(_bumpModified, retries=0))[0][0]
+ except AllRetriesFailed:
+ pass
+
+ @inlineCallbacks
def notifyChanged(self):
"""
Trigger a notification of a change
"""
- if self._notifiers:
+
+ # 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):
for notifier in self._notifiers:
self._txn.postCommit(notifier.notify)
+ self._txn.notificationAddedForObject(self)
@@ -2988,9 +3072,12 @@
"""
Trigger a notification of a change
"""
- if self._notifiers:
+
+ # Only send one set of change notifications per transaction
+ if self._notifiers and not self._txn.isNotifiedAlready(self):
for notifier in self._notifiers:
self._txn.postCommit(notifier.notify)
+ self._txn.notificationAddedForObject(self)
@classproperty
Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql 2011-12-06 20:36:20 UTC (rev 8407)
@@ -39,7 +39,9 @@
create table CALENDAR_HOME_METADATA (
RESOURCE_ID integer primary key references CALENDAR_HOME on delete cascade, -- implicit index
- QUOTA_USED_BYTES integer default 0 not null
+ QUOTA_USED_BYTES integer default 0 not null,
+ CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+ MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP)
);
--------------
@@ -324,7 +326,9 @@
create table ADDRESSBOOK_HOME_METADATA (
RESOURCE_ID integer primary key references ADDRESSBOOK_HOME on delete cascade, -- implicit index
- QUOTA_USED_BYTES integer default 0 not null
+ QUOTA_USED_BYTES integer default 0 not null,
+ CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+ MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP)
);
-----------------
Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_6_to_7.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_6_to_7.sql 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_6_to_7.sql 2011-12-06 20:36:20 UTC (rev 8407)
@@ -22,6 +22,12 @@
alter table CALENDAR_HOME
add ("DATAVERSION" integer default 1 null);
+-- Need to add timestamp columns
+alter table CALENDAR_HOME_METADATA
+ add ("CREATED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC');
+alter table CALENDAR_HOME_METADATA
+ add ("MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC');
+
-- Just need to modify one column
alter table CALENDAR_OBJECT
add ("SUPPORTED_COMPONENTS" nvarchar2(255) default null);
@@ -30,6 +36,12 @@
alter table ADDRESSBOOK_HOME
add ("DATAVERSION" integer default 1 null);
+-- Need to add timestamp columns
+alter table ADDRESSBOOK_HOME_METADATA
+ add ("CREATED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC');
+alter table ADDRESSBOOK_HOME_METADATA
+ add ("MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC');
+
-- Now update the version
update CALENDARSERVER set VALUE = '7' where NAME = 'VERSION';
Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_6_to_7.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_6_to_7.sql 2011-12-06 20:16:33 UTC (rev 8406)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_6_to_7.sql 2011-12-06 20:36:20 UTC (rev 8407)
@@ -22,14 +22,24 @@
alter table CALENDAR_HOME
add column DATAVERSION integer default 1 null;
+-- Need to add timestamp columns
+alter table CALENDAR_HOME_METADATA
+ add column CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+ add column MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP);
+
-- Just need to add one column
alter table CALENDAR
- add column SUPPORTED_COMPONENTS varchar(255) default null;
+ add column SUPPORTED_COMPONENTS varchar(255) default null;
-- Just need to add one column
alter table ADDRESSBOOK_HOME
add column DATAVERSION integer default 1 null;
+-- Need to add timestamp columns
+alter table ADDRESSBOOK_HOME_METADATA
+ add column CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+ add column MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP);
+
-- Now update the version
update CALENDARSERVER set VALUE = '7' where NAME = 'VERSION';
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20111206/25702e61/attachment-0001.html>
More information about the calendarserver-changes
mailing list