[CalendarServer-changes] [6446] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Tue Oct 19 08:39:57 PDT 2010
Revision: 6446
http://trac.macosforge.org/projects/calendarserver/changeset/6446
Author: glyph at apple.com
Date: 2010-10-19 08:39:55 -0700 (Tue, 19 Oct 2010)
Log Message:
-----------
merge more-deferreds-7, make SQL execution asynchronous
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/tools/purge.py
CalendarServer/trunk/support/build.sh
CalendarServer/trunk/twext/web2/dav/resource.py
CalendarServer/trunk/twext/web2/resource.py
CalendarServer/trunk/twext/web2/server.py
CalendarServer/trunk/twext/web2/static.py
CalendarServer/trunk/twext/web2/test/test_resource.py
CalendarServer/trunk/twistedcaldav/directory/addressbook.py
CalendarServer/trunk/twistedcaldav/directory/calendar.py
CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
CalendarServer/trunk/twistedcaldav/directory/principal.py
CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py
CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
CalendarServer/trunk/twistedcaldav/dropbox.py
CalendarServer/trunk/twistedcaldav/extensions.py
CalendarServer/trunk/twistedcaldav/fileops.py
CalendarServer/trunk/twistedcaldav/icaldav.py
CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
CalendarServer/trunk/twistedcaldav/method/put_common.py
CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py
CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
CalendarServer/trunk/twistedcaldav/method/report_common.py
CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py
CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
CalendarServer/trunk/twistedcaldav/notifications.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/schedule.py
CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
CalendarServer/trunk/twistedcaldav/scheduling/processing.py
CalendarServer/trunk/twistedcaldav/scheduling/utils.py
CalendarServer/trunk/twistedcaldav/sharing.py
CalendarServer/trunk/twistedcaldav/storebridge.py
CalendarServer/trunk/twistedcaldav/test/test_addressbookmultiget.py
CalendarServer/trunk/twistedcaldav/test/test_addressbookquery.py
CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py
CalendarServer/trunk/twistedcaldav/test/test_extensions.py
CalendarServer/trunk/twistedcaldav/test/test_sharing.py
CalendarServer/trunk/twistedcaldav/test/test_sql.py
CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
CalendarServer/trunk/twistedcaldav/test/util.py
CalendarServer/trunk/twistedcaldav/upgrade.py
CalendarServer/trunk/txdav/__init__.py
CalendarServer/trunk/txdav/base/datastore/sql.py
CalendarServer/trunk/txdav/base/datastore/subpostgres.py
CalendarServer/trunk/txdav/base/propertystore/sql.py
CalendarServer/trunk/txdav/base/propertystore/test/base.py
CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py
CalendarServer/trunk/txdav/caldav/datastore/file.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling.py
CalendarServer/trunk/txdav/caldav/datastore/sql.py
CalendarServer/trunk/txdav/caldav/datastore/test/common.py
CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py
CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
CalendarServer/trunk/txdav/caldav/datastore/test/test_util.py
CalendarServer/trunk/txdav/caldav/datastore/util.py
CalendarServer/trunk/txdav/caldav/icalendarstore.py
CalendarServer/trunk/txdav/carddav/datastore/file.py
CalendarServer/trunk/txdav/carddav/datastore/sql.py
CalendarServer/trunk/txdav/carddav/datastore/test/common.py
CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py
CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
CalendarServer/trunk/txdav/carddav/datastore/util.py
CalendarServer/trunk/txdav/carddav/iaddressbookstore.py
CalendarServer/trunk/txdav/common/datastore/file.py
CalendarServer/trunk/txdav/common/datastore/sql.py
CalendarServer/trunk/txdav/common/datastore/sql_legacy.py
CalendarServer/trunk/txdav/common/datastore/test/test_util.py
CalendarServer/trunk/txdav/common/datastore/test/util.py
CalendarServer/trunk/txdav/common/datastore/util.py
CalendarServer/trunk/txdav/common/icommondatastore.py
CalendarServer/trunk/txdav/common/inotifications.py
Added Paths:
-----------
CalendarServer/trunk/twistedcaldav/directory/common.py
CalendarServer/trunk/txdav/caldav/datastore/index_file.py
CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
CalendarServer/trunk/txdav/carddav/datastore/index_file.py
CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py
Removed Paths:
-------------
CalendarServer/trunk/twistedcaldav/index.py
CalendarServer/trunk/twistedcaldav/test/test_index.py
CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py
CalendarServer/trunk/twistedcaldav/vcardindex.py
Property Changed:
----------------
CalendarServer/trunk/
Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/more-deferreds-6:6330
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
+ /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
Modified: CalendarServer/trunk/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/purge.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/calendarserver/tools/purge.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -286,7 +286,7 @@
# Get the calendar home
principalCollection = directory.principalCollection
principal = principalCollection.principalForRecord(record)
- calendarHome = principal.calendarHome()
+ calendarHome = yield principal.calendarHome()
if verbose:
print "%s %-15s :" % (record.uid, record.shortNames[0]),
@@ -383,7 +383,7 @@
principalCollection = directory.principalCollection
principal = principalCollection.principalForRecord(record)
- calendarHome = principal.calendarHome()
+ calendarHome = yield principal.calendarHome()
# Anything in the past is left alone
now = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
Modified: CalendarServer/trunk/support/build.sh
===================================================================
--- CalendarServer/trunk/support/build.sh 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/support/build.sh 2010-10-19 15:39:55 UTC (rev 6446)
@@ -489,6 +489,10 @@
# Python dependencies
#
+ # First, let's make sure that we ourselves are on PYTHONPATH, in case some
+ # code (like, let's say, trial) decides to chdir somewhere.
+ export PYTHONPATH="${wd}:${PYTHONPATH:-}";
+
local zi="zope.interface-3.3.0";
py_dependency \
"Zope Interface" "zope.interface" "${zi}" \
Modified: CalendarServer/trunk/twext/web2/dav/resource.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/resource.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twext/web2/dav/resource.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -51,8 +51,9 @@
from twisted.cred.error import LoginFailed, UnauthorizedLogin
from twisted.python.failure import Failure
-from twisted.internet.defer import Deferred, maybeDeferred, succeed
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import (
+ Deferred, maybeDeferred, succeed, inlineCallbacks, returnValue
+)
from twisted.internet import reactor
from twext.python.log import Logger
@@ -204,10 +205,12 @@
self.deadProperties().contains(qname)
)
+
def readProperty(self, property, request):
"""
See L{IDAVResource.readProperty}.
"""
+ @inlineCallbacks
def defer():
if type(property) is tuple:
qname = property
@@ -222,22 +225,22 @@
if name == "resourcetype":
# Allow live property to be overridden by dead property
if self.deadProperties().contains(qname):
- return self.deadProperties().get(qname)
+ returnValue(self.deadProperties().get(qname))
if self.isCollection():
- return davxml.ResourceType.collection
- return davxml.ResourceType.empty
+ returnValue(davxml.ResourceType.collection) #@UndefinedVariable
+ returnValue(davxml.ResourceType.empty) #@UndefinedVariable
if name == "getetag":
- etag = self.etag()
+ etag = yield self.etag()
if etag is None:
- return None
- return davxml.GETETag(etag.generate())
+ returnValue(None)
+ returnValue(davxml.GETETag(etag.generate()))
if name == "getcontenttype":
mimeType = self.contentType()
if mimeType is None:
- return None
- return davxml.GETContentType(generateContentType(mimeType))
+ returnValue(None)
+ returnValue(davxml.GETContentType(generateContentType(mimeType)))
if name == "getcontentlength":
length = self.contentLength()
@@ -245,139 +248,125 @@
# TODO: really we should "render" the resource and
# determine its size from that but for now we just
# return an empty element.
- return davxml.GETContentLength("")
+ returnValue(davxml.GETContentLength(""))
else:
- return davxml.GETContentLength(str(length))
+ returnValue(davxml.GETContentLength(str(length)))
if name == "getlastmodified":
- lastModified = self.lastModified()
+ lastModified = yield self.lastModified()
if lastModified is None:
- return None
- return davxml.GETLastModified.fromDate(lastModified)
+ returnValue(None)
+ returnValue(davxml.GETLastModified.fromDate(lastModified))
if name == "creationdate":
- creationDate = self.creationDate()
+ creationDate = yield self.creationDate()
if creationDate is None:
- return None
- return davxml.CreationDate.fromDate(creationDate)
+ returnValue(None)
+ returnValue(davxml.CreationDate.fromDate(creationDate))
if name == "displayname":
displayName = self.displayName()
if displayName is None:
- return None
- return davxml.DisplayName(displayName)
+ returnValue(None)
+ returnValue(davxml.DisplayName(displayName))
if name == "supportedlock":
- return davxml.SupportedLock(
+ returnValue(davxml.SupportedLock(
davxml.LockEntry(
- davxml.LockScope.exclusive,
- davxml.LockType.write
+ davxml.LockScope.exclusive, #@UndefinedVariable
+ davxml.LockType.write #@UndefinedVariable
),
davxml.LockEntry(
- davxml.LockScope.shared,
- davxml.LockType.write
+ davxml.LockScope.shared, #@UndefinedVariable
+ davxml.LockType.write #@UndefinedVariable
),
- )
+ ))
if name == "supported-report-set":
- return davxml.SupportedReportSet(*[
+ returnValue(davxml.SupportedReportSet(*[
davxml.SupportedReport(report,)
for report in self.supportedReports()
- ])
+ ]))
if name == "supported-privilege-set":
- return self.supportedPrivileges(request)
+ returnValue((yield self.supportedPrivileges(request)))
if name == "acl-restrictions":
- return davxml.ACLRestrictions()
+ returnValue(davxml.ACLRestrictions())
if name == "inherited-acl-set":
- return davxml.InheritedACLSet(*self.inheritedACLSet())
+ returnValue(davxml.InheritedACLSet(*self.inheritedACLSet()))
if name == "principal-collection-set":
- return davxml.PrincipalCollectionSet(*[
+ returnValue(davxml.PrincipalCollectionSet(*[
davxml.HRef(
principalCollection.principalCollectionURL()
)
for principalCollection in self.principalCollections()
- ])
+ ]))
+ @inlineCallbacks
def ifAllowed(privileges, callback):
- def onError(failure):
- failure.trap(AccessDeniedError)
-
+ try:
+ yield self.checkPrivileges(request, privileges)
+ result = yield callback()
+ except AccessDeniedError:
raise HTTPError(StatusResponse(
responsecode.UNAUTHORIZED,
"Access denied while reading property %s."
% (sname,)
))
+ returnValue(result)
- d = self.checkPrivileges(request, privileges)
- d.addCallbacks(lambda _: callback(), onError)
- return d
-
if name == "current-user-privilege-set":
+ @inlineCallbacks
def callback():
- d = self.currentPrivileges(request)
- d.addCallback(
- lambda privs:
- davxml.CurrentUserPrivilegeSet(*privs)
- )
- return d
- return ifAllowed(
+ privs = yield self.currentPrivileges(request)
+ returnValue(davxml.CurrentUserPrivilegeSet(*privs))
+ returnValue((yield ifAllowed(
(davxml.ReadCurrentUserPrivilegeSet(),),
callback
- )
+ )))
if name == "acl":
+ @inlineCallbacks
def callback():
- def gotACL(acl):
- if acl is None:
- acl = davxml.ACL()
- return acl
- d = self.accessControlList(request)
- d.addCallback(gotACL)
- return d
- return ifAllowed((davxml.ReadACL(),), callback)
-
+ acl = yield self.accessControlList(request)
+ if acl is None:
+ acl = davxml.ACL()
+ returnValue(acl)
+ returnValue(
+ (yield ifAllowed((davxml.ReadACL(),), callback))
+ )
+
if name == "current-user-principal":
- return davxml.CurrentUserPrincipal(
+ returnValue(davxml.CurrentUserPrincipal(
self.currentPrincipal(request).children[0]
- )
+ ))
if name == "quota-available-bytes":
- def callback(qvalue):
- if qvalue is None:
- raise HTTPError(StatusResponse(
- responsecode.NOT_FOUND,
- "Property %s does not exist." % (sname,)
- ))
- else:
- return davxml.QuotaAvailableBytes(str(qvalue[0]))
- d = self.quota(request)
- d.addCallback(callback)
- return d
+ qvalue = yield self.quota(request)
+ if qvalue is None:
+ raise HTTPError(StatusResponse(
+ responsecode.NOT_FOUND,
+ "Property %s does not exist." % (sname,)
+ ))
+ else:
+ returnValue(davxml.QuotaAvailableBytes(str(qvalue[0])))
if name == "quota-used-bytes":
- def callback(qvalue):
- if qvalue is None:
- raise HTTPError(StatusResponse(
- responsecode.NOT_FOUND,
- "Property %s does not exist." % (sname,)
- ))
- else:
- return davxml.QuotaUsedBytes(str(qvalue[1]))
- d = self.quota(request)
- d.addCallback(callback)
- return d
+ qvalue = yield self.quota(request)
+ if qvalue is None:
+ raise HTTPError(StatusResponse(
+ responsecode.NOT_FOUND,
+ "Property %s does not exist." % (sname,)
+ ))
+ else:
+ returnValue(davxml.QuotaUsedBytes(str(qvalue[1])))
elif namespace == twisted_dav_namespace:
if name == "resource-class":
- class ResourceClass (davxml.WebDAVTextElement):
- namespace = twisted_dav_namespace
- name = "resource-class"
- hidden = False
- return ResourceClass(self.__class__.__name__)
+ returnValue(ResourceClass(self.__class__.__name__))
elif namespace == twisted_private_namespace:
raise HTTPError(StatusResponse(
@@ -385,10 +374,10 @@
"Properties in the %s namespace are private to the server."
% (sname,)
))
+ returnValue(self.deadProperties().get(qname))
- return self.deadProperties().get(qname)
+ return defer()
- return maybeDeferred(defer)
def writeProperty(self, property, request):
"""
@@ -629,7 +618,7 @@
completionDeferred = Deferred()
basepath = request.urlForResource(self)
- children = list(self.listChildren())
+ children = []
def checkPrivilegesError(failure):
failure.trap(AccessDeniedError)
@@ -679,7 +668,10 @@
d.addCallbacks(gotChild, checkPrivilegesError, (childpath,))
d.addErrback(completionDeferred.errback)
- getChild()
+ def gotChildren(listChildrenResult):
+ children[:] = list(listChildrenResult)
+ getChild()
+ maybeDeferred(self.listChildren).addCallback(gotChildren)
return completionDeferred
@@ -729,7 +721,7 @@
children = []
basepath = request.urlForResource(self)
- childnames = list(self.listChildren())
+ childnames = list((yield self.listChildren()))
for childname in childnames:
childpath = joinURL(basepath, urllib.quote(childname))
child = (yield request.locateChildResource(self, childname))
@@ -1090,6 +1082,7 @@
"""
self.writeDeadProperty(acl)
+
@inlineCallbacks
def mergeAccessControlList(self, new_acl, request):
"""
@@ -1253,9 +1246,10 @@
# FIXME: verify acl is self-consistent
# Step 11
- self.writeNewACEs(new_set)
+ yield self.writeNewACEs(new_set)
returnValue(None)
-
+
+
def writeNewACEs(self, new_aces):
"""
Write a new ACL to the resource's property store. This is a
@@ -1264,8 +1258,9 @@
command.
@param new_aces: C{list} of L{ACE} for ACL being set.
"""
- self.setAccessControlList(davxml.ACL(*new_aces))
+ return self.setAccessControlList(davxml.ACL(*new_aces))
+
def matchPrivilege(self, privilege, ace_privileges, supportedPrivileges):
for ace_privilege in ace_privileges:
if (
@@ -1276,6 +1271,7 @@
return False
+
@inlineCallbacks
def checkPrivileges(
self, request, privileges, recurse=False,
@@ -2674,3 +2670,9 @@
)
unauthenticatedPrincipal = davxml.Principal(davxml.Unauthenticated())
+
+
+class ResourceClass (davxml.WebDAVTextElement):
+ namespace = twisted_dav_namespace
+ name = "resource-class"
+ hidden = False
Modified: CalendarServer/trunk/twext/web2/resource.py
===================================================================
--- CalendarServer/trunk/twext/web2/resource.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twext/web2/resource.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -30,6 +30,8 @@
# System Imports
from zope.interface import implements
+from twisted.internet.defer import inlineCallbacks, returnValue
+
from twext.web2 import iweb, http, server, responsecode
class RenderMixin(object):
@@ -71,6 +73,8 @@
if method:
return method(request)
+
+ @inlineCallbacks
def renderHTTP(self, request):
"""
See L{iweb.IResource.renderHTTP}.
@@ -96,17 +100,15 @@
@return: an object adaptable to L{iweb.IResponse}.
"""
method = getattr(self, "http_" + request.method, None)
- if not method:
+ if method is None:
response = http.Response(responsecode.NOT_ALLOWED)
response.headers.setHeader("allow", self.allowedMethods())
- return response
+ returnValue(response)
- d = self.checkPreconditions(request)
- if d is None:
- return method(request)
- else:
- return d.addCallback(lambda _: method(request))
+ yield self.checkPreconditions(request)
+ returnValue((yield method(request)))
+
def http_OPTIONS(self, request):
"""
Respond to a OPTIONS request.
Modified: CalendarServer/trunk/twext/web2/server.py
===================================================================
--- CalendarServer/trunk/twext/web2/server.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twext/web2/server.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -346,6 +346,7 @@
d.addCallback(self._cbFinishRender)
d.addErrback(self._processingFailed)
d.callback(None)
+ return d
def preprocessRequest(self):
"""Do any request processing that doesn't follow the normal
@@ -470,10 +471,10 @@
@raise NoURLForResourceError: if C{resource} has no URL in this request
(because it was not obtained from the request).
"""
- resource = self._urlsByResource.get(resource, None)
- if resource is None:
+ url = self._urlsByResource.get(resource, None)
+ if url is None:
raise NoURLForResourceError(resource)
- return resource
+ return url
def locateResource(self, url):
"""
Modified: CalendarServer/trunk/twext/web2/static.py
===================================================================
--- CalendarServer/trunk/twext/web2/static.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twext/web2/static.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,3 +1,4 @@
+# -*- test-case-name: twext.web2.test.test_static -*-
##
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
# Copyright (c) 2010 Apple Computer, Inc. All rights reserved.
@@ -34,10 +35,11 @@
# Sibling Imports
from twext.web2 import http_headers, resource
from twext.web2 import http, iweb, stream, responsecode, server, dirlist
+from twext.web2.http import HTTPError
# Twisted Imports
from twext.python.filepath import CachingFilePath as FilePath
-from twisted.internet.defer import maybeDeferred
+from twisted.internet.defer import inlineCallbacks, returnValue
from zope.interface import implements
class MetaDataMixin(object):
@@ -94,21 +96,26 @@
return True
class StaticRenderMixin(resource.RenderMixin, MetaDataMixin):
+
+
+ @inlineCallbacks
def checkPreconditions(self, request):
# This code replaces the code in resource.RenderMixin
if request.method not in ("GET", "HEAD"):
http.checkPreconditions(
request,
entityExists = self.exists(),
- etag = self.etag(),
- lastModified = self.lastModified(),
+ etag = (yield self.etag()),
+ lastModified = (yield self.lastModified()),
)
# Check per-method preconditions
method = getattr(self, "preconditions_" + request.method, None)
if method:
- return method(request)
+ returnValue((yield method(request)))
+
+ @inlineCallbacks
def renderHTTP(self, request):
"""
See L{resource.RenderMixIn.renderHTTP}.
@@ -116,32 +123,27 @@
This implementation automatically sets some headers on the response
based on data available from L{MetaDataMixin} methods.
"""
- def setHeaders(response):
- response = iweb.IResponse(response)
+ try:
+ response = yield super(StaticRenderMixin, self).renderHTTP(request)
+ except HTTPError, he:
+ response = he.response
- # Don't provide additional resource information to error responses
- if response.code < 400:
- # 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.
- for (header, value) in (
- ("etag", self.etag()),
- ("last-modified", self.lastModified()),
- ):
- if value is not None:
- response.headers.setHeader(header, value)
+ response = iweb.IResponse(response)
+ # Don't provide additional resource information to error responses
+ if response.code < 400:
+ # 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.
+ for (header, value) in (
+ ("etag", (yield self.etag())),
+ ("last-modified", (yield self.lastModified())),
+ ):
+ if value is not None:
+ response.headers.setHeader(header, value)
+ returnValue(response)
- return response
- def onError(f):
- # If we get an HTTPError, run its response through setHeaders() as
- # well.
- f.trap(http.HTTPError)
- return setHeaders(f.value.response)
- d = maybeDeferred(super(StaticRenderMixin, self).renderHTTP, request)
- return d.addCallbacks(setHeaders, onError)
-
class Data(resource.Resource):
"""
This is a static, in-memory resource.
@@ -319,12 +321,6 @@
"""
self.ignoredExts.append(ext)
- def directoryListing(self):
- return dirlist.DirectoryLister(self.fp.path,
- self.listChildren(),
- self.contentTypes,
- self.contentEncodings,
- self.defaultType)
def putChild(self, name, child):
"""
@@ -580,7 +576,7 @@
"""
import mimetypes
# Grab Python's built-in mimetypes dictionary.
- contentTypes = mimetypes.types_map
+ contentTypes = mimetypes.types_map #@UndefinedVariable
# Update Python's semi-erroneous dictionary with a few of the
# usual suspects.
contentTypes.update(
Modified: CalendarServer/trunk/twext/web2/test/test_resource.py
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_resource.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twext/web2/test/test_resource.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -9,7 +9,7 @@
from zope.interface import implements
-from twisted.internet.defer import succeed, fail
+from twisted.internet.defer import succeed, fail, inlineCallbacks
from twisted.trial import unittest
from twext.web2 import responsecode
from twext.web2.iweb import IResource
@@ -78,6 +78,8 @@
self._my_allowed_methods
)
+
+ @inlineCallbacks
def test_checkPreconditions_raises(self):
"""
RenderMixin.checkPreconditions()
@@ -87,11 +89,17 @@
request = SimpleRequest(Site(resource), "BLEARGH", "/")
# Check that checkPreconditions raises as expected
- self.assertRaises(PreconditionError, resource.checkPreconditions, request)
+ self.assertRaises(
+ PreconditionError, resource.checkPreconditions, request
+ )
# Check that renderHTTP calls checkPreconditions
- self.assertRaises(PreconditionError, resource.renderHTTP, request)
+ yield self.failUnlessFailure(
+ resource.renderHTTP(request), PreconditionError
+ )
+
+ @inlineCallbacks
def test_checkPreconditions_none(self):
"""
RenderMixin.checkPreconditions()
@@ -101,8 +109,12 @@
request = SimpleRequest(Site(resource), "SWEETHOOKUPS", "/")
# Check that checkPreconditions without a raise doesn't barf
- self.assertEquals(resource.renderHTTP(request), responsecode.NO_CONTENT)
+ self.assertEquals(
+ (yield resource.renderHTTP(request)),
+ responsecode.NO_CONTENT
+ )
+
def test_checkPreconditions_deferred(self):
"""
RenderMixin.checkPreconditions()
Modified: CalendarServer/trunk/twistedcaldav/directory/addressbook.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/addressbook.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/addressbook.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -19,7 +19,6 @@
"""
__all__ = [
- "uidsResourceName",
"DirectoryAddressBookHomeProvisioningResource",
"DirectoryAddressBookHomeTypeProvisioningResource",
"DirectoryAddressBookHomeUIDProvisioningResource",
@@ -32,10 +31,14 @@
from twext.web2.http import HTTPError
from twext.web2.http_headers import ETag, MimeType
+from twisted.internet.defer import inlineCallbacks, returnValue
+
from twistedcaldav.config import config
from twistedcaldav.directory.idirectory import IDirectoryService
-from twistedcaldav.directory.resource import DirectoryReverseProxyResource
-from twistedcaldav.directory.util import transactionFromRequest
+
+from twistedcaldav.directory.common import CommonUIDProvisioningResource,\
+ uidsResourceName, CommonHomeTypeProvisioningResource
+
from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource,\
DAVResourceWithChildrenMixin
from twistedcaldav.resource import AddressBookHomeResource
@@ -44,8 +47,6 @@
log = Logger()
-# Use __underbars__ convention to avoid conflicts with directory resource types.
-uidsResourceName = "__uids__"
# FIXME: copied from resource.py to avoid circular dependency
class CalDAVComplianceMixIn(object):
@@ -71,7 +72,9 @@
return MimeType("httpd", "unix-directory")
-class DirectoryAddressBookHomeProvisioningResource (DirectoryAddressBookProvisioningResource):
+class DirectoryAddressBookHomeProvisioningResource (
+ DirectoryAddressBookProvisioningResource
+ ):
"""
Resource which provisions address book home collections as needed.
"""
@@ -134,7 +137,10 @@
return "addressbooks"
-class DirectoryAddressBookHomeTypeProvisioningResource (DirectoryAddressBookProvisioningResource):
+class DirectoryAddressBookHomeTypeProvisioningResource (
+ CommonHomeTypeProvisioningResource,
+ DirectoryAddressBookProvisioningResource
+ ):
"""
Resource which provisions address book home collections of a specific
record type as needed.
@@ -156,18 +162,7 @@
def url(self):
return joinURL(self._parent.url(), self.recordType)
- def locateChild(self, request, segments):
- name = segments[0]
- if name == "":
- return (self, segments[1:])
- record = self.directory.recordWithShortName(self.recordType, name)
- if record is None:
- return None, []
-
- return (self._parent.homeForDirectoryRecord(record, request),
- segments[1:])
-
def listChildren(self):
if config.EnablePrincipalListings:
@@ -206,99 +201,33 @@
return self._parent.principalForRecord(record)
-class DirectoryAddressBookHomeUIDProvisioningResource (DirectoryAddressBookProvisioningResource):
+class DirectoryAddressBookHomeUIDProvisioningResource (
+ CommonUIDProvisioningResource,
+ DirectoryAddressBookProvisioningResource
+ ):
- def __init__(self, parent):
- """
- @param parent: the parent of this resource
- """
- assert parent is not None
+ homeResourceTypeName = 'addressbooks'
- super(DirectoryAddressBookHomeUIDProvisioningResource, self).__init__()
+ enabledAttribute = 'enabledForAddressBooks'
- self.directory = parent.directory
- self.parent = parent
+ def homeResourceCreator(self, record, transaction):
+ return DirectoryAddressBookHomeResource.createHomeResource(
+ self, record, transaction)
- def url(self):
- return joinURL(self.parent.url(), uidsResourceName)
- def locateChild(self, request, segments):
-
- name = segments[0]
- if name == "":
- return (self, ())
-
- record = self.directory.recordWithUID(name)
- if record:
- return (self.homeResourceForRecord(record, request), segments[1:])
- else:
- return (None, ())
-
- def getChild(self, name, record=None):
- raise NotImplementedError("DirectoryAddressBookHomeUIDProvisioningResource.getChild no longer exists.")
-
- def listChildren(self):
- # Not a listable collection
- raise HTTPError(responsecode.FORBIDDEN)
-
- def homeResourceForRecord(self, record, request):
-
- transaction = transactionFromRequest(request, self.parent._newStore)
-
- name = record.uid
-
- if record is None:
- self.log_msg("No directory record with GUID %r" % (name,))
- return None
-
- if not record.enabledForAddressBooks:
- self.log_msg("Directory record %r is not enabled for address books" % (record,))
- return None
-
- assert len(name) > 4, "Directory record has an invalid GUID: %r" % (name,)
-
- if record.locallyHosted():
- child = DirectoryAddressBookHomeResource(self, record, transaction)
- else:
- child = DirectoryReverseProxyResource(self, record)
-
- return child
-
- ##
- # DAV
- ##
-
- def isCollection(self):
- return True
-
- def displayName(self):
- return uidsResourceName
-
- ##
- # ACL
- ##
-
- def principalCollections(self):
- return self.parent.principalCollections()
-
- def principalForRecord(self, record):
- return self.parent.principalForRecord(record)
-
-
class DirectoryAddressBookHomeResource (AddressBookHomeResource):
"""
Address book home collection resource.
"""
- def __init__(self, parent, record, transaction):
- """
- @param path: the path to the file which will back the resource.
- """
- assert parent is not None
- assert record is not None
- assert transaction is not None
+ @classmethod
+ @inlineCallbacks
+ def createHomeResource(cls, parent, record, transaction):
+ self = yield super(DirectoryAddressBookHomeResource, cls).createHomeResource(
+ parent, record.uid, transaction)
self.record = record
- super(DirectoryAddressBookHomeResource, self).__init__(parent, record.uid, transaction)
+ returnValue(self)
+
def principalForRecord(self):
return self.parent.principalForRecord(self.record)
Modified: CalendarServer/trunk/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendar.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/calendar.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -20,7 +20,6 @@
"""
__all__ = [
- "uidsResourceName",
"DirectoryCalendarHomeProvisioningResource",
"DirectoryCalendarHomeTypeProvisioningResource",
"DirectoryCalendarHomeUIDProvisioningResource",
@@ -33,12 +32,13 @@
from twext.web2.http import HTTPError
from twext.web2.http_headers import ETag, MimeType
-from twisted.internet.defer import succeed
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue
from twistedcaldav.config import config
from twistedcaldav.directory.idirectory import IDirectoryService
-from twistedcaldav.directory.resource import DirectoryReverseProxyResource
-from twistedcaldav.directory.util import transactionFromRequest
+from twistedcaldav.directory.common import uidsResourceName,\
+ CommonUIDProvisioningResource, CommonHomeTypeProvisioningResource
+
from twistedcaldav.directory.wiki import getWikiACL
from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource,\
DAVResourceWithChildrenMixin
@@ -48,9 +48,6 @@
log = Logger()
-# Use __underbars__ convention to avoid conflicts with directory resource types.
-uidsResourceName = "__uids__"
-
# FIXME: copied from resource.py to avoid circular dependency
class CalDAVComplianceMixIn(object):
def davComplianceClasses(self):
@@ -136,7 +133,10 @@
def displayName(self):
return "calendars"
-class DirectoryCalendarHomeTypeProvisioningResource (DirectoryCalendarProvisioningResource):
+class DirectoryCalendarHomeTypeProvisioningResource(
+ CommonHomeTypeProvisioningResource,
+ DirectoryCalendarProvisioningResource
+ ):
"""
Resource which provisions calendar home collections of a specific
record type as needed.
@@ -158,18 +158,7 @@
def url(self):
return joinURL(self._parent.url(), self.recordType)
- def locateChild(self, request, segments):
- name = segments[0]
- if name == "":
- return (self, segments[1:])
- record = self.directory.recordWithShortName(self.recordType, name)
- if record is None:
- return None, []
-
- return (self._parent.homeForDirectoryRecord(record, request),
- segments[1:])
-
def listChildren(self):
if config.EnablePrincipalListings:
@@ -207,99 +196,35 @@
def principalForRecord(self, record):
return self._parent.principalForRecord(record)
-class DirectoryCalendarHomeUIDProvisioningResource (DirectoryCalendarProvisioningResource):
+class DirectoryCalendarHomeUIDProvisioningResource (
+ CommonUIDProvisioningResource,
+ DirectoryCalendarProvisioningResource
+ ):
- def __init__(self, parent):
- """
- @param parent: the parent of this resource
- """
- assert parent is not None
+ homeResourceTypeName = 'calendars'
- super(DirectoryCalendarHomeUIDProvisioningResource, self).__init__()
+ enabledAttribute = 'enabledForCalendaring'
- self.directory = parent.directory
- self.parent = parent
+ def homeResourceCreator(self, record, transaction):
+ return DirectoryCalendarHomeResource.createHomeResource(
+ self, record, transaction)
- def url(self):
- return joinURL(self.parent.url(), uidsResourceName)
- def locateChild(self, request, segments):
- name = segments[0]
- if name == "":
- return (self, ())
-
- record = self.directory.recordWithUID(name)
- if record:
- return (self.homeResourceForRecord(record, request), segments[1:])
- else:
- return (None, ())
-
- def getChild(self, name, record=None):
- raise NotImplementedError("DirectoryCalendarProvisioningResource.getChild no longer exists.")
-
- def listChildren(self):
- # Not a listable collection
- raise HTTPError(responsecode.FORBIDDEN)
-
- def homeResourceForRecord(self, record, request):
-
- transaction = transactionFromRequest(request, self.parent._newStore)
- name = record.uid
-
- if record is None:
- log.debug("No directory record with GUID %r" % (name,))
- return None
-
- if not record.enabledForCalendaring:
- log.debug("Directory record %r is not enabled for calendaring" % (record,))
- return None
-
- assert len(name) > 4, "Directory record has an invalid GUID: %r" % (name,)
-
- if record.locallyHosted():
- child = DirectoryCalendarHomeResource(self, record, transaction)
- else:
- child = DirectoryReverseProxyResource(self, record)
-
- return child
-
- ##
- # DAV
- ##
-
- def isCollection(self):
- return True
-
- def displayName(self):
- return uidsResourceName
-
- ##
- # ACL
- ##
-
- def principalCollections(self):
- return self.parent.principalCollections()
-
- def principalForRecord(self, record):
- return self.parent.principalForRecord(record)
-
-
class DirectoryCalendarHomeResource (CalendarHomeResource):
"""
Calendar home collection resource.
"""
- def __init__(self, parent, record, transaction):
- """
- @param path: the path to the file which will back the resource.
- """
- assert parent is not None
- assert record is not None
- assert transaction is not None
+ @classmethod
+ @inlineCallbacks
+ def createHomeResource(cls, parent, record, transaction):
+ self = yield super(DirectoryCalendarHomeResource, cls).createHomeResource(
+ parent, record.uid, transaction)
self.record = record
- super(DirectoryCalendarHomeResource, self).__init__(parent, record.uid, transaction)
+ returnValue(self)
+
# Special ACLs for Wiki service
def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
def gotACL(wikiACL):
Modified: CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -131,9 +131,9 @@
def resourceType(self):
if self.proxyType == "calendar-proxy-read":
- return davxml.ResourceType.calendarproxyread
+ return davxml.ResourceType.calendarproxyread #@UndefinedVariable
elif self.proxyType == "calendar-proxy-write":
- return davxml.ResourceType.calendarproxywrite
+ return davxml.ResourceType.calendarproxywrite #@UndefinedVariable
else:
return super(CalendarUserProxyPrincipalResource, self).resourceType()
Copied: CalendarServer/trunk/twistedcaldav/directory/common.py (from rev 6445, CalendarServer/branches/users/glyph/more-deferreds-7/twistedcaldav/directory/common.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/common.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/common.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -0,0 +1,147 @@
+# -*- test-case-name: twistedcaldav.test.test_wrapping,twistedcaldav.directory.test.test_calendar -*-
+##
+# Copyright (c) 2010 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twext.web2.http import HTTPError
+from twext.web2 import responsecode
+from twext.web2.dav.util import joinURL
+from twistedcaldav.directory.util import transactionFromRequest
+from twistedcaldav.directory.resource import DirectoryReverseProxyResource
+
+__all__ = [
+ 'uidsResourceName',
+ 'CommonUIDProvisioningResource'
+]
+
+# Use __underbars__ convention to avoid conflicts with directory resource
+# types.
+
+uidsResourceName = "__uids__"
+
+class CommonUIDProvisioningResource(object):
+ """
+ Common ancestor for addressbook/calendar UID provisioning resources.
+
+ Must be mixed in to the hierarchy I{before} the appropriate resource type.
+
+ @ivar homeResourceTypeName: The name of the home resource type ('calendars'
+ or 'addressbooks').
+
+ @ivar enabledAttribute: The name of the attribute of the directory record
+ which determines whether this should be enabled or not.
+ """
+
+ def __init__(self, parent):
+ """
+ @param parent: the parent of this resource
+ """
+
+ super(CommonUIDProvisioningResource, self).__init__()
+
+ self.directory = parent.directory
+ self.parent = parent
+
+
+ @inlineCallbacks
+ def homeResourceForRecord(self, record, request):
+
+ transaction = transactionFromRequest(request, self.parent._newStore)
+ name = record.uid
+
+ if record is None:
+ self.log_msg("No directory record with GUID %r" % (name,))
+ returnValue(None)
+
+ if not getattr(record, self.enabledAttribute):
+ self.log_msg("Directory record %r is not enabled for %s" % (
+ record, self.homeResourceTypeName))
+ returnValue(None)
+
+ assert len(name) > 4, "Directory record has an invalid GUID: %r" % (
+ name,)
+
+ if record.locallyHosted():
+ child = yield self.homeResourceCreator(record, transaction)
+ else:
+ child = DirectoryReverseProxyResource(self, record)
+
+ returnValue(child)
+
+
+ @inlineCallbacks
+ def locateChild(self, request, segments):
+
+ name = segments[0]
+ if name == "":
+ returnValue((self, ()))
+
+ record = self.directory.recordWithUID(name)
+ if record:
+ child = yield self.homeResourceForRecord(record, request)
+ returnValue((child, segments[1:]))
+ else:
+ returnValue((None, ()))
+
+
+ def listChildren(self):
+ # Not a listable collection
+ raise HTTPError(responsecode.FORBIDDEN)
+
+ ##
+ # ACL
+ ##
+
+ def principalCollections(self):
+ return self.parent.principalCollections()
+
+ def principalForRecord(self, record):
+ return self.parent.principalForRecord(record)
+ ##
+ # DAV
+ ##
+
+ def isCollection(self):
+ return True
+
+
+ def getChild(self, name, record=None):
+ raise NotImplementedError(self.__class__.__name__ +
+ ".getChild no longer exists.")
+
+ def displayName(self):
+ return uidsResourceName
+
+ def url(self):
+ return joinURL(self.parent.url(), uidsResourceName)
+
+
+
+class CommonHomeTypeProvisioningResource(object):
+
+ @inlineCallbacks
+ def locateChild(self, request, segments):
+ name = segments[0]
+ if name == "":
+ returnValue((self, segments[1:]))
+
+ record = self.directory.recordWithShortName(self.recordType, name)
+ if record is None:
+ returnValue((None, []))
+
+ child = yield self._parent.homeForDirectoryRecord(record, request)
+ returnValue((child, segments[1:]))
+
Modified: CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -397,66 +397,7 @@
yield updateLock.release()
updateLock = None
- #FIXME: implement store based cache
-# tmpDirLock = self._tmpDirAddressBookLock
-# self.log_debug("blocking on lock of: \"%s\")" % self._tmpDirAddressBookLockPath)
-# yield tmpDirLock.acquire()
-#
-# try:
-# self.log_info("Filling directory address book")
-# startTime = time.time()
-# newAddressBook = CalDAVFile(makeTmpFilename())
-# yield newAddressBook.createAddressBookCollection()
-# for key, record in records.items():
-# try:
-# vcard = record.vCard()
-# # make up a destination
-#
-# fileName = unquote(record.uriName())
-# destination = CalDAVFile(join(newAddressBook.fp.path, fileName))
-# destination_uri = record.hRef()
-#
-# self.log_debug("Adding \"%s\", uri=\"%s\"" % (fileName, destination_uri, ))
-# self.log_debug("VCard text =\n%s" % (record.vCardText(), ))
-#
-# yield StoreAddressObjectResource( request = None,
-# sourceadbk = False,
-# destinationadbk = True,
-# destination = destination,
-# destination_uri = destination_uri,
-# destinationparent = newAddressBook,
-# vcard = vcard,
-# indexdestination = False,
-# ).run()
-# except:
-# self.log_info("Could not add record %s" % (record,))
-# del records[key]
-# newAddressBookCTag = customxml.GETCTag(str(hash(self.baseGUID + ":" + self.realmName + ":" + "".join(str(hash(records[key])) for key in records.keys()))))
-#
-# self.log_info("Indexing new directory address book")
-# newAddressBook.index().recreate()
-# elaspedTime = time.time()-startTime
-# self.log_info("Timing: Fill address book: %.1f ms (%d vcards, %.2f vcards/sec)" % (elaspedTime*1000, len(records), len(records)/elaspedTime))
-#
-# updateLock = self.updateLock()
-# self.log_debug("blocking on lock of: \"%s\")" % self._updateLockPath)
-# yield updateLock.acquire()
-#
-# self.log_debug("Swapping in new directory address book")
-#
-# # move old address book out of the way
-# if self.directoryBackedAddressBook.fp.exists():
-# os.rename(self.directoryBackedAddressBook.fp.path, makeTmpFilename())
-#
-# #move new one into place
-# os.rename(newAddressBook.fp.path, self.directoryBackedAddressBook.fp.path)
-# self.directoryBackedAddressBook.fp.restat()
-#
-# self.directoryBackedAddressBook.writeDeadProperty(newAddressBookCTag)
-# finally:
-# self.log_debug("unlocking: \"%s\")" % self._tmpDirAddressBookLockPath)
-# yield tmpDirLock.release()
-
+
if not keepLock:
self.log_debug("unlocking: \"%s\")" % self._updateLockPath)
yield updateLock.release()
Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -55,6 +55,7 @@
from twistedcaldav.directory import calendaruserproxy
from twistedcaldav.directory import augment
from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyPrincipalResource
+from twistedcaldav.directory.common import uidsResourceName
from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVPrincipalResource,\
DAVResourceWithChildrenMixin
@@ -67,9 +68,6 @@
log = Logger()
-# Use __underbars__ convention to avoid conflicts with directory resource types.
-uidsResourceName = "__uids__"
-
class PermissionsMixIn (ReadOnlyResourceMixIn):
def defaultAccessControlList(self):
return authReadACL
@@ -867,26 +865,27 @@
else:
return False
+ @inlineCallbacks
def scheduleInbox(self, request):
- home = self.calendarHome(request)
+ home = yield self.calendarHome(request)
if home is None:
- return succeed(None)
+ returnValue(None)
- inbox = home.getChild("inbox")
+ inbox = yield home.getChild("inbox")
if inbox is None:
- return succeed(None)
+ returnValue(None)
- return succeed(inbox)
+ returnValue(inbox)
+ @inlineCallbacks
def notificationCollection(self, request):
-
+
notification = None
if config.Sharing.Enabled:
- home = self.calendarHome(request)
+ home = yield self.calendarHome(request)
if home is not None:
- notification = home.getChild("notification")
-
- return succeed(notification)
+ notification = yield home.getChild("notification")
+ returnValue(notification)
def calendarHomeURLs(self):
homeURL = self._homeChildURL(None)
@@ -929,14 +928,16 @@
else:
return joinURL(url, name) if name else url
+
def calendarHome(self, request):
# FIXME: self.record.service.calendarHomesCollection smells like a hack
service = self.record.service
if hasattr(service, "calendarHomesCollection"):
return service.calendarHomesCollection.homeForDirectoryRecord(self.record, request)
else:
- return None
+ return succeed(None)
+
def _addressBookHomeChildURL(self, name):
if not hasattr(self, "addressBookHomeURL"):
if not hasattr(self.record.service, "addressBookHomesCollection"):
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -16,30 +16,51 @@
from twisted.internet.defer import inlineCallbacks
from twext.web2.dav import davxml
-from twext.web2.test.test_server import SimpleRequest
from twistedcaldav import caldavxml
from twistedcaldav.test.util import TestCase
+from twext.web2.test.test_server import SimpleRequest
+from twistedcaldav.directory.util import transactionFromRequest
class ProvisionedCalendars (TestCase):
"""
Directory service provisioned principals.
"""
+
+ @inlineCallbacks
def setUp(self):
- super(ProvisionedCalendars, self).setUp()
+ yield super(ProvisionedCalendars, self).setUp()
self.createStockDirectoryService()
self.setupCalendars()
+ def oneRequest(self, uri):
+ req = self._cleanupRequest = SimpleRequest(self.site, "GET", uri)
+ return req
+
+
+ def tearDown(self):
+ """
+ If the request started by this test has a transaction, commit it.
+ Otherwise, don't bother.
+ """
+ class JustForCleanup(object):
+ def newTransaction(self, *whatever):
+ return self
+ def commit(self):
+ return
+ return transactionFromRequest(self._cleanupRequest, JustForCleanup()).commit()
+
+
def test_NonExistentCalendarHome(self):
def _response(resource):
if resource is not None:
self.fail("Incorrect response to GET on non-existent calendar home.")
- request = SimpleRequest(self.site, "GET", "/calendars/users/12345/")
+ request = self.oneRequest("/calendars/users/12345/")
d = request.locateResource(request.uri)
d.addCallback(_response)
@@ -49,7 +70,7 @@
if resource is None:
self.fail("Incorrect response to GET on existent calendar home.")
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
+ request = self.oneRequest("/calendars/users/wsanchez/")
d = request.locateResource(request.uri)
d.addCallback(_response)
@@ -59,7 +80,7 @@
if resource is None:
self.fail("Incorrect response to GET on existent calendar.")
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/calendar/")
+ request = self.oneRequest("/calendars/users/wsanchez/calendar/")
d = request.locateResource(request.uri)
d.addCallback(_response)
@@ -69,14 +90,14 @@
if resource is None:
self.fail("Incorrect response to GET on existent inbox.")
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/inbox/")
+ request = self.oneRequest("/calendars/users/wsanchez/inbox/")
d = request.locateResource(request.uri)
d.addCallback(_response)
@inlineCallbacks
def test_CalendarTranspProperty(self):
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/calendar/")
+ request = self.oneRequest("/calendars/users/wsanchez/calendar/")
# Get calendar first
calendar = (yield request.locateResource("/calendars/users/wsanchez/calendar/"))
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -68,6 +68,8 @@
calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB(os.path.abspath(self.mktemp()))
+
+ @inlineCallbacks
def test_hierarchy(self):
"""
DirectoryPrincipalProvisioningResource.listChildren(),
@@ -92,7 +94,7 @@
principalCollections = provisioningResource.principalCollections()
self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
- recordTypes = set(provisioningResource.listChildren())
+ recordTypes = set((yield provisioningResource.listChildren()))
self.assertEquals(recordTypes, set(directory.recordTypes()))
for recordType in recordTypes:
@@ -106,7 +108,7 @@
principalCollections = typeResource.principalCollections()
self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
- shortNames = set(typeResource.listChildren())
+ shortNames = set((yield typeResource.listChildren()))
self.assertEquals(shortNames, set(r.shortNames[0] for r in directory.listRecords(recordType)))
for shortName in shortNames:
@@ -426,7 +428,7 @@
for args in _authReadOnlyPrivileges(self, provisioningResource, provisioningResource.principalCollectionURL()):
yield self._checkPrivileges(*args)
- for recordType in provisioningResource.listChildren():
+ for recordType in (yield provisioningResource.listChildren()):
#print " -> %s" % (recordType,)
typeResource = provisioningResource.getChild(recordType)
Modified: CalendarServer/trunk/twistedcaldav/dropbox.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/dropbox.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/dropbox.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -30,7 +30,7 @@
from twext.web2.dav.resource import DAVResource, TwistedACLInheritable
from twext.web2.dav.util import joinURL
-from twisted.internet.defer import succeed, inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue
from twistedcaldav.config import config
from twistedcaldav.customxml import calendarserver_namespace
@@ -42,7 +42,7 @@
Drop box collection resource.
"""
def resourceType(self):
- return davxml.ResourceType.dropboxhome
+ return davxml.ResourceType.dropboxhome #@UndefinedVariable
def isCollection(self):
return True
@@ -83,12 +83,15 @@
"""
Drop box resource.
"""
+
def resourceType(self):
- return davxml.ResourceType.dropbox
+ return davxml.ResourceType.dropbox #@UndefinedVariable
+
def isCollection(self):
return True
+
def writeNewACEs(self, newaces):
"""
Write a new ACL to the resource's property store. We override this for calendar collections
@@ -108,10 +111,11 @@
edited_aces.append(davxml.ACE(*children))
else:
edited_aces.append(ace)
-
+
# Do inherited with possibly modified set of aces
- super(DropBoxCollectionResource, self).writeNewACEs(edited_aces)
+ return super(DropBoxCollectionResource, self).writeNewACEs(edited_aces)
+
def http_PUT(self, request):
return ErrorResponse(
responsecode.FORBIDDEN,
Modified: CalendarServer/trunk/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/extensions.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/extensions.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -38,7 +38,7 @@
from twisted.cred.error import LoginFailed, UnauthorizedLogin
import twext.web2.server
-from twext.web2 import responsecode, iweb, http, server
+from twext.web2 import responsecode, server
from twext.web2.auth.wrapper import UnauthorizedResponse
from twext.web2.http import HTTPError, Response, RedirectResponse
from twext.web2.http import StatusResponse
@@ -521,7 +521,7 @@
]
even = Alternator()
- for name in sorted(self.listChildren()):
+ for name in sorted((yield self.listChildren())):
child = self.getChild(name)
url, name, size, lastModified, contentType = self.getChildDirectoryEntry(child, name, request)
@@ -734,6 +734,7 @@
self.putChildren = {}
super(DAVResourceWithChildrenMixin, self).__init__(principalCollections=principalCollections)
+
def putChild(self, name, child):
"""
Register a child with the given name with this resource.
@@ -742,9 +743,12 @@
"""
self.putChildren[name] = child
+
def getChild(self, name):
"""
- Look up a child resource.
+ Look up a child resource. First check C{self.putChildren}, then call
+ C{self.makeChild} if no pre-existing children were found.
+
@return: the child of this resource with the given name.
"""
if name == "":
@@ -755,23 +759,34 @@
result = self.makeChild(name)
return result
+
def makeChild(self, name):
- # Subclasses with real children need to override this and return the appropriate object
+ """
+ Called by L{DAVResourceWithChildrenMixin.getChild} to dynamically
+ create children that have not been pre-created with C{putChild}.
+ """
return None
+
def listChildren(self):
"""
@return: a sequence of the names of all known children of this resource.
"""
return self.putChildren.keys()
+
def locateChild(self, req, segments):
"""
- See L{IResource}C{.locateChild}.
+ See L{IResource.locateChild}.
"""
- # If getChild() finds a child resource, return it
- return (self.getChild(segments[0]), segments[1:])
+ thisSegment = segments[0]
+ moreSegments = segments[1:]
+ return maybeDeferred(self.getChild, thisSegment).addCallback(
+ lambda it: (it, moreSegments)
+ )
+
+
class DAVResourceWithoutChildrenMixin (object):
"""
Bits needed from twext.web2.static
@@ -789,6 +804,8 @@
def locateChild(self, request, segments):
return self, server.StopTraversal
+
+
class DAVPrincipalResource (DirectoryPrincipalPropertySearchMixIn,
SuperDAVPrincipalResource, LoggingMixIn,
DirectoryRenderingMixIn):
@@ -888,8 +905,8 @@
if self.deadProperties().contains((dav_namespace, "resourcetype")):
return self.deadProperties().get((dav_namespace, "resourcetype"))
if self.isCollection():
- return davxml.ResourceType.collection
- return davxml.ResourceType.empty
+ return davxml.ResourceType.collection #@UndefinedVariable
+ return davxml.ResourceType.empty #@UndefinedVariable
def render(self, request):
if not self.fp.exists():
Modified: CalendarServer/trunk/twistedcaldav/fileops.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/fileops.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/fileops.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,111 +0,0 @@
-##
-# Copyright (c) 2005-2008 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.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-Various file utilities.
-"""
-
-from twext.web2.dav.fileop import copy
-from twext.web2.dav.fileop import put
-from twext.web2.dav.xattrprops import xattrPropertyStore
-
-# This class simulates a DAVFile with enough information for use with xattrPropertyStore.
-class FakeXAttrResource(object):
-
- def __init__(self, fp):
- self.fp = fp
-
-def putWithXAttrs(stream, filepath):
- """
- Write a file to a possibly existing path and preserve any xattrs at that path.
-
- @param stream: the stream to write to the destination.
- @type stream: C{file}
- @param filepath: the destination file.
- @type filepath: L{FilePath}
- """
-
- # Preserve existings xattrs
- props = []
- if filepath.exists():
- xold = xattrPropertyStore(FakeXAttrResource(filepath))
- for item in xold.list():
- props.append((xold.get(item)))
- xold = None
-
- # First do the actual file copy
- def _gotResponse(response):
-
- # Restore original xattrs.
- if props:
- xnew = xattrPropertyStore(FakeXAttrResource(filepath))
- for prop in props:
- xnew.set(prop)
- xnew = None
-
- return response
-
- d = put(stream, filepath)
- d.addCallback(_gotResponse)
- return d
-
-def copyWithXAttrs(source_filepath, destination_filepath, destination_uri):
- """
- Copy a file from one path to another and also copy xattrs we care about.
-
- @param source_filepath: the file to copy from
- @type source_filepath: L{FilePath}
- @param destination_filepath: the file to copy to
- @type destination_filepath: L{FilePath}
- @param destination_uri: the URI of the destination resource
- @type destination_uri: C{str}
- """
-
- # First do the actual file copy
- def _gotResponse(response):
-
- # Now copy over xattrs.
- copyXAttrs(source_filepath, destination_filepath)
-
- return response
-
- d = copy(source_filepath, destination_filepath, destination_uri, "0")
- d.addCallback(_gotResponse)
- return d
-
-def copyToWithXAttrs(from_fp, to_fp):
- """
- Copy a file from one path to another and also copy xattrs we care about.
-
- @param from_fp: file being copied
- @type from_fp: L{FilePath}
- @param to_fp: file to copy to
- @type to_fp: L{FilePath}
- """
-
- # First do the actual file copy.
- from_fp.copyTo(to_fp)
-
- # Now copy over xattrs.
- copyXAttrs(from_fp, to_fp)
-
-def copyXAttrs(from_fp, to_fp):
- # Create xattr stores for each file and copy over all xattrs.
- xfrom = xattrPropertyStore(FakeXAttrResource(from_fp))
- xto = xattrPropertyStore(FakeXAttrResource(to_fp))
-
- for item in xfrom.list():
- xto.set(xfrom.get(item))
Modified: CalendarServer/trunk/twistedcaldav/icaldav.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/icaldav.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/icaldav.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -67,29 +67,25 @@
Create a calendar collection for this resource.
"""
- def iCalendar(name=None):
+ def iCalendar():
"""
Instantiate an iCalendar component object representing this resource or
its child with the given name.
The behavior of this method is not specified if it is called on a
resource that is not a calendar collection or a calendar resource within
a calendar collection.
- @param name: the name of the desired child of this resource, or None
- if this resource is desired. Must be None if this resource is
- not a calendar collection.
+
@return: a L{twistedcaldav.ical.Component} of type C{"VCALENDAR"}.
"""
- def iCalendarText(name=None):
+ def iCalendarText():
"""
Obtains the iCalendar text representing this resource or its child with
the given name.
The behavior of this method is not specified if it is called on a
resource that is not a calendar collection or a calendar resource within
a calendar collection.
- @param name: the name of the desired child of this resource, or None
- if this resource is desired. Must be None if this resource is
- not a calendar collection.
+
@return: a string containing iCalendar text with a top-level component
of type C{"VCALENDAR"}.
"""
Deleted: CalendarServer/trunk/twistedcaldav/index.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/index.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/index.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,1156 +0,0 @@
-# -*- test-case-name: twistedcaldav.test.test_index -*-
-##
-# Copyright (c) 2005-2010 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.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-CalDAV Index.
-
-This API is considered private to static.py and is therefore subject to
-change.
-"""
-
-__all__ = [
- "db_basename",
- "ReservationError",
- "MemcachedUIDReserver",
- "Index",
- "IndexSchedule",
- "IndexedSearchException",
-]
-
-import datetime
-import time
-import hashlib
-
-try:
- import sqlite3 as sqlite
-except ImportError:
- from pysqlite2 import dbapi2 as sqlite
-
-from vobject.icalendar import utc
-
-from twisted.internet.defer import maybeDeferred, succeed
-
-from twext.python.log import Logger, LoggingMixIn
-
-from twistedcaldav.ical import Component
-from twistedcaldav.query import calendarquery, calendarqueryfilter
-from twistedcaldav.sql import AbstractSQLDatabase
-from twistedcaldav.sql import db_prefix
-from twistedcaldav.instance import InvalidOverriddenInstanceError
-from twistedcaldav.config import config
-from twistedcaldav.memcachepool import CachePoolUserMixIn
-
-log = Logger()
-
-db_basename = db_prefix + "sqlite"
-schema_version = "10"
-collection_types = {"Calendar": "Regular Calendar Collection", "iTIP": "iTIP Calendar Collection"}
-
-icalfbtype_to_indexfbtype = {
- "FREE" : 'F',
- "BUSY" : 'B',
- "BUSY-UNAVAILABLE": 'U',
- "BUSY-TENTATIVE" : 'T',
-}
-indexfbtype_to_icalfbtype = dict([(v, k) for k,v in icalfbtype_to_indexfbtype.iteritems()])
-
-#
-# Duration into the future through which recurrences are expanded in the index
-# by default. This is a caching parameter which affects the size of the index;
-# it does not affect search results beyond this period, but it may affect
-# performance of such a search.
-#
-default_future_expansion_duration = datetime.timedelta(days=365*1)
-
-#
-# Maximum duration into the future through which recurrences are expanded in the
-# index. This is a caching parameter which affects the size of the index; it
-# does not affect search results beyond this period, but it may affect
-# performance of such a search.
-#
-# When a search is performed on a time span that goes beyond that which is
-# expanded in the index, we have to open each resource which may have data in
-# that time period. In order to avoid doing that multiple times, we want to
-# cache those results. However, we don't necessarily want to cache all
-# occurrences into some obscenely far-in-the-future date, so we cap the caching
-# period. Searches beyond this period will always be relatively expensive for
-# resources with occurrences beyond this period.
-#
-maximum_future_expansion_duration = datetime.timedelta(days=365*5)
-
-class ReservationError(LookupError):
- """
- Attempt to reserve a UID which is already reserved or to unreserve a UID
- which is not reserved.
- """
-
-class IndexedSearchException(ValueError):
- pass
-
-class SyncTokenValidException(ValueError):
- pass
-
-class AbstractCalendarIndex(AbstractSQLDatabase, LoggingMixIn):
- """
- Calendar collection index abstract base class that defines the apis for the index.
- This will be subclassed for the two types of index behaviour we need: one for
- regular calendar collections, one for schedule calendar collections.
- """
-
- def __init__(self, resource):
- """
- @param resource: the L{CalDAVResource} resource to
- index. C{resource} must be a calendar collection (ie.
- C{resource.isPseudoCalendarCollection()} returns C{True}.)
- """
- self.resource = resource
- db_filename = self.resource.fp.child(db_basename).path
- super(AbstractCalendarIndex, self).__init__(db_filename, False)
-
- def create(self):
- """
- Create the index and initialize it.
- """
- self._db()
-
- def reserveUID(self, uid):
- """
- Reserve a UID for this index's resource.
- @param uid: the UID to reserve
- @raise ReservationError: if C{uid} is already reserved
- """
- raise NotImplementedError
-
- def unreserveUID(self, uid):
- """
- Unreserve a UID for this index's resource.
- @param uid: the UID to reserve
- @raise ReservationError: if C{uid} is not reserved
- """
- raise NotImplementedError
-
- def isReservedUID(self, uid):
- """
- Check to see whether a UID is reserved.
- @param uid: the UID to check
- @return: True if C{uid} is reserved, False otherwise.
- """
- raise NotImplementedError
-
- def isAllowedUID(self, uid, *names):
- """
- Checks to see whether to allow an operation with adds the the specified
- UID is allowed to the index. Specifically, the operation may not
- violate the constraint that UIDs must be unique, and the UID must not
- be reserved.
- @param uid: the UID to check
- @param names: the names of resources being replaced or deleted by the
- operation; UIDs associated with these resources are not checked.
- @return: True if the UID is not in the index and is not reserved,
- False otherwise.
- """
- raise NotImplementedError
-
- def resourceNamesForUID(self, uid):
- """
- Looks up the names of the resources with the given UID.
- @param uid: the UID of the resources to look up.
- @return: a list of resource names
- """
- names = self._db_values_for_sql("select NAME from RESOURCE where UID = :1", uid)
-
- #
- # Check that each name exists as a child of self.resource. If not, the
- # resource record is stale.
- #
- resources = []
- for name in names:
- name_utf8 = name.encode("utf-8")
- 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, False)
- self._db_commit()
- else:
- resources.append(name_utf8)
-
- return resources
-
- def resourceNameForUID(self, uid):
- """
- Looks up the name of the resource with the given UID.
- @param uid: the UID of the resource to look up.
- @return: If the resource is found, its name; C{None} otherwise.
- """
- result = None
-
- for name in self.resourceNamesForUID(uid):
- assert result is None, "More than one resource with UID %s in calendar collection %r" % (uid, self)
- result = name
-
- return result
-
- def resourceUIDForName(self, name):
- """
- Looks up the UID of the resource with the given name.
- @param name: the name of the resource to look up.
- @return: If the resource is found, the UID of the resource; C{None}
- otherwise.
- """
- uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
-
- return uid
-
- 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
- for the resource name. If present, then the index entries for
- that UID are removed. After that the new index entries are added.
- @param name: the name of the resource to add.
- @param calendar: a L{Calendar} object representing the resource
- contents.
- @param fast: if C{True} do not do commit, otherwise do commit.
- """
- oldUID = self.resourceUIDForName(name)
- if oldUID is not None:
- 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):
- """
- Remove this resource from the index.
- @param name: the name of the resource to add.
- @param uid: the UID of the calendar component in the resource.
- """
- uid = self.resourceUIDForName(name)
- if uid is not None:
- self._delete_from_db(name, uid)
- self._db_commit()
-
- def resourceExists(self, name):
- """
- Determines whether the specified resource name exists in the index.
- @param name: the name of the resource to test
- @return: True if the resource exists, False if not
- """
- uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
- return uid is not None
-
- def resourcesExist(self, names):
- """
- Determines whether the specified resource name exists in the index.
- @param names: a C{list} containing the names of the resources to test
- @return: a C{list} of all names that exist
- """
- statement = "select NAME from RESOURCE where NAME in ("
- for ctr in (item[0] for item in enumerate(names)):
- if ctr != 0:
- statement += ", "
- statement += ":%s" % (ctr,)
- statement += ")"
- results = self._db_values_for_sql(statement, *names)
- return results
-
-
- def testAndUpdateIndex(self, minDate):
- # Find out if the index is expanded far enough
- names = self.notExpandedBeyond(minDate)
- # Actually expand recurrence max
- for name in names:
- self.log_info("Search falls outside range of index for %s %s" % (name, minDate))
- self.reExpandResource(name, minDate)
-
- def whatchanged(self, revision, depth):
-
- 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, wasdeleted in results:
- if name:
- if wasdeleted == 'Y':
- if revision:
- deleted.append(name)
- else:
- changed.append(name)
- else:
- raise SyncTokenValidException
-
- 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.
- @param filter: the L{Filter} for the calendar-query to execute.
- @return: an iterable of tuples for each resource matching the
- given C{qualifiers}. The tuples are C{(name, uid, type)}, where
- C{name} is the resource name, C{uid} is the resource UID, and
- C{type} is the resource iCalendar component type.
- """
-
- # Make sure we have a proper Filter element and get the partial SQL
- # statement to use.
- if isinstance(filter, calendarqueryfilter.Filter):
- if fbtype:
- # Lookup the useruid - try the empty (default) one if needed
- dbuseruid = self._db_value_for_sql(
- "select PERUSERID from PERUSER where USERUID == :1",
- useruid,
- )
- else:
- dbuseruid = ""
-
- qualifiers = calendarquery.sqlcalendarquery(filter, None, dbuseruid)
- if qualifiers is not None:
- # Determine how far we need to extend the current expansion of
- # events. If we have an open-ended time-range we will expand one
- # year past the start. That should catch bounded recurrences - unbounded
- # will have been indexed with an "infinite" value always included.
- maxDate, isStartDate = filter.getmaxtimerange()
- if maxDate:
- maxDate = maxDate.date()
- if isStartDate:
- maxDate += datetime.timedelta(days=365)
- self.testAndUpdateIndex(maxDate)
- else:
- # We cannot handler this filter in an indexed search
- raise IndexedSearchException()
-
- else:
- qualifiers = None
-
- # Perform the search
- if qualifiers is None:
- rowiter = self._db_execute("select NAME, UID, TYPE from RESOURCE")
- else:
- if fbtype:
- # Lookup the useruid - try the empty (default) one if needed
- dbuseruid = self._db_value_for_sql(
- "select PERUSERID from PERUSER where USERUID == :1",
- useruid,
- )
-
- # For a free-busy time-range query we return all instances
- rowiter = self._db_execute(
- "select DISTINCT RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE, RESOURCE.ORGANIZER, TIMESPAN.FLOAT, TIMESPAN.START, TIMESPAN.END, TIMESPAN.FBTYPE, TIMESPAN.TRANSPARENT, TRANSPARENCY.TRANSPARENT" +
- qualifiers[0],
- *qualifiers[1]
- )
- else:
- rowiter = self._db_execute("select DISTINCT RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE" + qualifiers[0], *qualifiers[1])
-
- # Check result for missing resources
-
- for row in rowiter:
- name = row[0]
- if self.resource.getChild(name.encode("utf-8")):
- if fbtype:
- row = list(row)
- if row[9]:
- row[8] = row[9]
- del row[9]
- yield row
- else:
- log.err("Calendar resource %s is missing from %s. Removing from index."
- % (name, self.resource))
- self.deleteResource(name)
-
- def bruteForceSearch(self):
- """
- List the whole index and tests for existence, updating the index
- @return: all resources in the index
- """
- # List all resources
- rowiter = self._db_execute("select NAME, UID, TYPE from RESOURCE")
-
- # Check result for missing resources:
-
- for row in rowiter:
- name = row[0]
- if self.resource.getChild(name.encode("utf-8")):
- yield row
- else:
- log.err("Calendar resource %s is missing from %s. Removing from index."
- % (name, self.resource))
- self.deleteResource(name)
-
-
- def _db_version(self):
- """
- @return: the schema version assigned to this index.
- """
- return schema_version
-
- 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
- be associated with any given UID and vice versa.
- NB This method does not commit the changes to the db - the caller
- MUST take care of that
- @param name: the name of the resource to add.
- @param calendar: a L{Calendar} object representing the resource
- contents.
- """
- raise NotImplementedError
-
- 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.
- """
- raise NotImplementedError
-
-class CalendarIndex (AbstractCalendarIndex):
- """
- Calendar index - abstract class for indexer that indexes calendar objects in a collection.
- """
-
- def __init__(self, resource):
- """
- @param resource: the L{CalDAVResource} resource to
- index.
- """
- super(CalendarIndex, self).__init__(resource)
-
- def _db_init_data_tables_base(self, q, uidunique):
- """
- Initialise the underlying database tables.
- @param q: a database cursor to use.
- """
- #
- # RESOURCE table is the primary index table
- # NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
- # UID: iCalendar UID (may or may not be unique)
- # TYPE: iCalendar component type
- # RECURRANCE_MAX: Highest date of recurrence expansion
- # ORGANIZER: cu-address of the Organizer of the event
- #
- q.execute(
- """
- create table RESOURCE (
- RESOURCEID integer primary key autoincrement,
- NAME text unique,
- UID text%s,
- TYPE text,
- RECURRANCE_MAX date,
- ORGANIZER text
- )
- """ % (" unique" if uidunique else "",)
- )
-
- #
- # TIMESPAN table tracks (expanded) time spans for resources
- # NAME: Related resource (RESOURCE foreign key)
- # FLOAT: 'Y' if start/end are floating, 'N' otherwise
- # START: Start date
- # END: End date
- # FBTYPE: FBTYPE value:
- # '?' - unknown
- # 'F' - free
- # 'B' - busy
- # 'U' - busy-unavailable
- # 'T' - busy-tentative
- # TRANSPARENT: Y if transparent, N if opaque (default non-per-user value)
- #
- q.execute(
- """
- create table TIMESPAN (
- INSTANCEID integer primary key autoincrement,
- RESOURCEID integer,
- FLOAT text(1),
- START date,
- END date,
- FBTYPE text(1),
- TRANSPARENT text(1)
- )
- """
- )
- q.execute(
- """
- create index STARTENDFLOAT on TIMESPAN (START, END, FLOAT)
- """
- )
-
- #
- # PERUSER table tracks per-user ids
- # PERUSERID: autoincrement primary key
- # UID: User ID used in calendar data
- #
- q.execute(
- """
- create table PERUSER (
- PERUSERID integer primary key autoincrement,
- USERUID text
- )
- """
- )
- q.execute(
- """
- create index PERUSER_UID on PERUSER (USERUID)
- """
- )
-
- #
- # TRANSPARENCY table tracks per-user per-instance transparency
- # PERUSERID: user id key
- # INSTANCEID: instance id key
- # TRANSPARENT: Y if transparent, N if opaque
- #
- q.execute(
- """
- create table TRANSPARENCY (
- PERUSERID integer,
- INSTANCEID integer,
- TRANSPARENT text(1)
- )
- """
- )
-
- #
- # REVISIONS table tracks changes
- # NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
- # REVISION: revision number
- # WASDELETED: Y if revision deleted, N if added or changed
- #
- 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,
- DELETED text(1)
- )
- """
- )
- q.execute(
- """
- create index REVISION on REVISIONS (REVISION)
- """
- )
-
- if uidunique:
- #
- # RESERVED table tracks reserved UIDs
- # UID: The UID being reserved
- # TIME: When the reservation was made
- #
- q.execute(
- """
- create table RESERVED (
- UID text unique,
- TIME date
- )
- """
- )
-
- # Cascading triggers to help on delete
- q.execute(
- """
- create trigger resourceDelete after delete on RESOURCE
- for each row
- begin
- delete from TIMESPAN where TIMESPAN.RESOURCEID = OLD.RESOURCEID;
- end
- """
- )
- q.execute(
- """
- create trigger timespanDelete after delete on TIMESPAN
- for each row
- begin
- delete from TRANSPARENCY where INSTANCEID = OLD.INSTANCEID;
- end
- """
- )
-
- def _db_can_upgrade(self, old_version):
- """
- Can we do an in-place upgrade
- """
-
- # v10 is a big change - no upgrade possible
- return False
-
- def _db_upgrade_data_tables(self, q, old_version):
- """
- Upgrade the data from an older version of the DB.
- """
-
- # v10 is a big change - no upgrade possible
- pass
-
- def notExpandedBeyond(self, minDate):
- """
- Gives all resources which have not been expanded beyond a given date
- in the index
- """
- return self._db_values_for_sql("select NAME from RESOURCE where RECURRANCE_MAX < :1", minDate)
-
- def reExpandResource(self, name, expand_until):
- """
- Given a resource name, remove it from the database and re-add it
- with a longer expansion.
- """
- calendar = self.resource.getChild(name).iCalendar()
- self._add_to_db(name, calendar, expand_until=expand_until, reCreate=True)
- self._db_commit()
-
- 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
- be associated with any given UID and vice versa.
- NB This method does not commit the changes to the db - the caller
- MUST take care of that
- @param name: the name of the resource to add.
- @param calendar: a L{Calendar} object representing the resource
- contents.
- """
- uid = calendar.resourceUID()
- organizer = calendar.getOrganizer()
- if not organizer:
- organizer = ""
-
- # Decide how far to expand based on the component
- master = calendar.masterComponent()
- if master is None or not calendar.isRecurring() and not calendar.isRecurringUnbounded():
- # When there is no master we have a set of overridden components - index them all.
- # When there is one instance - index it.
- # When bounded - index all.
- expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
- else:
- if expand_until:
- expand = expand_until
- else:
- expand = datetime.date.today() + default_future_expansion_duration
-
- if expand > (datetime.date.today() + maximum_future_expansion_duration):
- raise IndexedSearchException
-
- try:
- instances = calendar.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
- except InvalidOverriddenInstanceError, e:
- log.err("Invalid instance %s when indexing %s in %s" % (e.rid, name, self.resource,))
- raise
-
- self._delete_from_db(name, uid, False)
-
- # Add RESOURCE item
- self._db_execute(
- """
- insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
- values (:1, :2, :3, :4, :5)
- """, name, uid, calendar.resourceType(), instances.limit, organizer
- )
- resourceid = self.lastrowid
-
- # Get a set of all referenced per-user UIDs and map those to entries already
- # in the DB and add new ones as needed
- useruids = calendar.allPerUserUIDs()
- useruids.add("")
- useruidmap = {}
- for useruid in useruids:
- peruserid = self._db_value_for_sql(
- "select PERUSERID from PERUSER where USERUID = :1",
- useruid
- )
- if peruserid is None:
- self._db_execute(
- """
- insert into PERUSER (USERUID)
- values (:1)
- """, useruid
- )
- peruserid = self.lastrowid
- useruidmap[useruid] = peruserid
-
- for key in instances:
- instance = instances[key]
- start = instance.start.replace(tzinfo=utc)
- end = instance.end.replace(tzinfo=utc)
- float = 'Y' if instance.start.tzinfo is None else 'N'
- transp = 'T' if instance.component.propertyValue("TRANSP") == "TRANSPARENT" else 'F'
- self._db_execute(
- """
- insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE, TRANSPARENT)
- values (:1, :2, :3, :4, :5, :6)
- """,
- resourceid,
- float,
- start,
- end,
- icalfbtype_to_indexfbtype.get(instance.component.getFBType(), 'F'),
- transp
- )
- instanceid = self.lastrowid
- peruserdata = calendar.perUserTransparency(instance.rid)
- for useruid, transp in peruserdata:
- peruserid = useruidmap[useruid]
- self._db_execute(
- """
- insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
- values (:1, :2, :3)
- """, peruserid, instanceid, 'T' if transp else 'F'
- )
-
-
- # Special - for unbounded recurrence we insert a value for "infinity"
- # that will allow an open-ended time-range to always match it.
- if calendar.isRecurringUnbounded():
- start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
- end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
- float = 'N'
- self._db_execute(
- """
- insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE, TRANSPARENT)
- values (:1, :2, :3, :4, :5, :6)
- """, resourceid, float, start, end, '?', '?'
- )
- instanceid = self.lastrowid
- peruserdata = calendar.perUserTransparency(None)
- for useruid, transp in peruserdata:
- peruserid = useruidmap[useruid]
- self._db_execute(
- """
- insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
- values (:1, :2, :3)
- """, peruserid, instanceid, 'T' if transp else 'F'
- )
-
- 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, 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 dorevision:
- self._db_execute(
- """
- update REVISIONS SET REVISION = :1, DELETED = :2
- where NAME = :3
- """, self.bumpRevision(fast=True), 'Y', name
- )
-
-
-def wrapInDeferred(f):
- def _(*args, **kwargs):
- return maybeDeferred(f, *args, **kwargs)
-
- return _
-
-
-class MemcachedUIDReserver(CachePoolUserMixIn, LoggingMixIn):
- def __init__(self, index, cachePool=None):
- self.index = index
- self._cachePool = cachePool
-
- def _key(self, uid):
- return 'reservation:%s' % (
- hashlib.md5('%s:%s' % (uid,
- self.index.resource.fp.path)).hexdigest())
-
- def reserveUID(self, uid):
- uid = uid.encode('utf-8')
- self.log_debug("Reserving UID %r @ %r" % (
- uid,
- self.index.resource.fp.path))
-
- def _handleFalse(result):
- if result is False:
- raise ReservationError(
- "UID %s already reserved for calendar collection %s."
- % (uid, self.index.resource)
- )
-
- d = self.getCachePool().add(self._key(uid),
- 'reserved',
- expireTime=config.UIDReservationTimeOut)
- d.addCallback(_handleFalse)
- return d
-
-
- def unreserveUID(self, uid):
- uid = uid.encode('utf-8')
- self.log_debug("Unreserving UID %r @ %r" % (
- uid,
- self.index.resource.fp.path))
-
- def _handleFalse(result):
- if result is False:
- raise ReservationError(
- "UID %s is not reserved for calendar collection %s."
- % (uid, self.index.resource)
- )
-
- d =self.getCachePool().delete(self._key(uid))
- d.addCallback(_handleFalse)
- return d
-
-
- def isReservedUID(self, uid):
- uid = uid.encode('utf-8')
- self.log_debug("Is reserved UID %r @ %r" % (
- uid,
- self.index.resource.fp.path))
-
- def _checkValue((flags, value)):
- if value is None:
- return False
- else:
- return True
-
- d = self.getCachePool().get(self._key(uid))
- d.addCallback(_checkValue)
- return d
-
-
-
-class SQLUIDReserver(object):
- def __init__(self, index):
- self.index = index
-
- @wrapInDeferred
- def reserveUID(self, uid):
- """
- Reserve a UID for this index's resource.
- @param uid: the UID to reserve
- @raise ReservationError: if C{uid} is already reserved
- """
-
- try:
- self.index._db_execute("insert into RESERVED (UID, TIME) values (:1, :2)", uid, datetime.datetime.now())
- self.index._db_commit()
- except sqlite.IntegrityError:
- self.index._db_rollback()
- raise ReservationError(
- "UID %s already reserved for calendar collection %s."
- % (uid, self.index.resource)
- )
- except sqlite.Error, e:
- log.err("Unable to reserve UID: %s", (e,))
- self.index._db_rollback()
- raise
-
- def unreserveUID(self, uid):
- """
- Unreserve a UID for this index's resource.
- @param uid: the UID to reserve
- @raise ReservationError: if C{uid} is not reserved
- """
-
- def _cb(result):
- if result == False:
- raise ReservationError(
- "UID %s is not reserved for calendar collection %s."
- % (uid, self.index.resource)
- )
- else:
- try:
- self.index._db_execute(
- "delete from RESERVED where UID = :1", uid)
- self.index._db_commit()
- except sqlite.Error, e:
- log.err("Unable to unreserve UID: %s", (e,))
- self.index._db_rollback()
- raise
-
- d = self.isReservedUID(uid)
- d.addCallback(_cb)
- return d
-
-
- @wrapInDeferred
- def isReservedUID(self, uid):
- """
- Check to see whether a UID is reserved.
- @param uid: the UID to check
- @return: True if C{uid} is reserved, False otherwise.
- """
-
- rowiter = self.index._db_execute("select UID, TIME from RESERVED where UID = :1", uid)
- for uid, attime in rowiter:
- # Double check that the time is within a reasonable period of now
- # otherwise we probably have a stale reservation
- tm = time.strptime(attime[:19], "%Y-%m-%d %H:%M:%S")
- dt = datetime.datetime(year=tm.tm_year, month=tm.tm_mon, day=tm.tm_mday, hour=tm.tm_hour, minute=tm.tm_min, second = tm.tm_sec)
- if datetime.datetime.now() - dt > datetime.timedelta(seconds=config.UIDReservationTimeOut):
- try:
- self.index._db_execute("delete from RESERVED where UID = :1", uid)
- self.index._db_commit()
- except sqlite.Error, e:
- log.err("Unable to unreserve UID: %s", (e,))
- self.index._db_rollback()
- raise
- return False
- else:
- return True
-
- return False
-
-
-
-class Index (CalendarIndex):
- """
- Calendar collection index - regular collection that enforces CalDAV UID uniqueness requirement.
- """
-
- def __init__(self, resource):
- """
- @param resource: the L{CalDAVResource} resource to
- index. C{resource} must be a calendar collection (i.e.
- C{resource.isPseudoCalendarCollection()} returns C{True}.)
- """
- assert resource.isCalendarCollection(), "non-calendar collection resource %s has no index." % (resource,)
- super(Index, self).__init__(resource)
-
- if (
- hasattr(config, "Memcached") and
- config.Memcached.Pools.Default.ClientEnabled
- ):
- self.reserver = MemcachedUIDReserver(self)
- else:
- self.reserver = SQLUIDReserver(self)
-
- #
- # A dict of sets. The dict keys are calendar collection paths,
- # and the sets contains reserved UIDs for each path.
- #
-
- def reserveUID(self, uid):
- return self.reserver.reserveUID(uid)
-
-
- def unreserveUID(self, uid):
- return self.reserver.unreserveUID(uid)
-
-
- def isReservedUID(self, uid):
- return self.reserver.isReservedUID(uid)
-
-
- def isAllowedUID(self, uid, *names):
- """
- Checks to see whether to allow an operation which would add the
- specified UID to the index. Specifically, the operation may not
- violate the constraint that UIDs must be unique.
- @param uid: the UID to check
- @param names: the names of resources being replaced or deleted by the
- operation; UIDs associated with these resources are not checked.
- @return: True if the UID is not in the index and is not reserved,
- False otherwise.
- """
- rname = self.resourceNameForUID(uid)
- return (rname is None or rname in names)
-
- def _db_type(self):
- """
- @return: the collection type assigned to this index.
- """
- return collection_types["Calendar"]
-
- def _db_init_data_tables(self, q):
- """
- Initialise the underlying database tables.
- @param q: a database cursor to use.
- """
-
- # Create database where the RESOURCE table has unique UID column.
- self._db_init_data_tables_base(q, True)
-
- def _db_recreate(self, do_commit=True):
- """
- Re-create the database tables from existing calendar data.
- """
-
- #
- # Populate the DB with data from already existing resources.
- # This allows for index recovery if the DB file gets
- # deleted.
- #
- fp = self.resource.fp
- for name in fp.listdir():
- if name.startswith("."):
- continue
-
- try:
- stream = fp.child(name).open()
- except (IOError, OSError), e:
- log.err("Unable to open resource %s: %s" % (name, e))
- continue
-
- # FIXME: This is blocking I/O
- try:
- calendar = Component.fromStream(stream)
- calendar.validateForCalDAV()
- except ValueError:
- log.err("Non-calendar resource: %s" % (name,))
- else:
- #log.msg("Indexing resource: %s" % (name,))
- 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()
-
-class IndexSchedule (CalendarIndex):
- """
- Schedule collection index - does not require UID uniqueness.
- """
-
- def reserveUID(self, uid): #@UnusedVariable
- """
- Reserve a UID for this index's resource.
- @param uid: the UID to reserve
- @raise ReservationError: if C{uid} is already reserved
- """
-
- # iTIP does not require unique UIDs
- return succeed(None)
-
- def unreserveUID(self, uid): #@UnusedVariable
- """
- Unreserve a UID for this index's resource.
- @param uid: the UID to reserve
- @raise ReservationError: if C{uid} is not reserved
- """
-
- # iTIP does not require unique UIDs
- return succeed(None)
-
- def isReservedUID(self, uid): #@UnusedVariable
- """
- Check to see whether a UID is reserved.
- @param uid: the UID to check
- @return: True if C{uid} is reserved, False otherwise.
- """
-
- # iTIP does not require unique UIDs
- return succeed(False)
-
- def isAllowedUID(self, uid, *names): #@UnusedVariable
- """
- Checks to see whether to allow an operation with adds the the specified
- UID is allowed to the index. Specifically, the operation may not
- violate the constraint that UIDs must be unique, and the UID must not
- be reserved.
- @param uid: the UID to check
- @param names: the names of resources being replaced or deleted by the
- operation; UIDs associated with these resources are not checked.
- @return: True if the UID is not in the index and is not reserved,
- False otherwise.
- """
-
- # iTIP does not require unique UIDs
- return True
-
- def _db_type(self):
- """
- @return: the collection type assigned to this index.
- """
- return collection_types["iTIP"]
-
- def _db_init_data_tables(self, q):
- """
- Initialise the underlying database tables.
- @param q: a database cursor to use.
- """
-
- # Create database where the RESOURCE table has a UID column that is not unique.
- self._db_init_data_tables_base(q, False)
-
- def _db_recreate(self, do_commit=True):
- """
- Re-create the database tables from existing calendar data.
- """
-
- #
- # Populate the DB with data from already existing resources.
- # This allows for index recovery if the DB file gets
- # deleted.
- #
- fp = self.resource.fp
- for name in fp.listdir():
- if name.startswith("."):
- continue
-
- try:
- stream = fp.child(name).open()
- except (IOError, OSError), e:
- log.err("Unable to open resource %s: %s" % (name, e))
- continue
-
- # FIXME: This is blocking I/O
- try:
- calendar = Component.fromStream(stream)
- calendar.validCalendarForCalDAV()
- calendar.validateComponentsForCalDAV(True)
- except ValueError:
- log.err("Non-calendar resource: %s" % (name,))
- else:
- #log.msg("Indexing resource: %s" % (name,))
- 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()
Modified: CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -25,7 +25,9 @@
from twisted.internet import reactor
from twisted.python.failure import Failure
-from twisted.internet.defer import Deferred, inlineCallbacks, succeed
+from txdav.common.icommondatastore import ReservationError
+
+from twisted.internet.defer import Deferred, inlineCallbacks
from twisted.internet.defer import returnValue
from twext.web2 import responsecode
from twext.web2.dav import davxml
@@ -42,7 +44,6 @@
from twistedcaldav.config import config
from twistedcaldav.carddavxml import NoUIDConflict, carddav_namespace
from twistedcaldav.vcard import Component
-from twistedcaldav.vcardindex import ReservationError
from twext.python.log import Logger
log = Logger()
@@ -149,6 +150,8 @@
self.access = None
+
+ @inlineCallbacks
def fullValidation(self):
"""
Do full validation of source and destination vcard data.
@@ -191,7 +194,7 @@
else:
# Get UID from original resource
self.source_index = self.sourceparent.index()
- self.uid = self.source_index.resourceUIDForName(self.source.name())
+ self.uid = yield self.source_index.resourceUIDForName(self.source.name())
if self.uid is None:
log.err("Source vcard does not have a UID: %s" % self.source.name())
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (carddav_namespace, "valid-addressbook-object-resource")))
@@ -207,7 +210,7 @@
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (carddav_namespace, "max-resource-size")))
# Check access
- return succeed(None)
+ returnValue(None)
def validResourceName(self):
"""
@@ -271,6 +274,8 @@
return result, message
+
+ @inlineCallbacks
def noUIDConflict(self, uid):
"""
Check that the UID of the new vcard object conforms to the requirements of
@@ -292,8 +297,8 @@
# UID must be unique
index = self.destinationparent.index()
- if not index.isAllowedUID(uid, oldname, self.destination.name()):
- rname = index.resourceNameForUID(uid)
+ if not (yield index.isAllowedUID(uid, oldname, self.destination.name())):
+ rname = yield index.resourceNameForUID(uid)
# This can happen if two simultaneous PUTs occur with the same UID.
# i.e. one PUT has reserved the UID but has not yet written the resource,
# the other PUT tries to reserve and fails but no index entry exists yet.
@@ -305,14 +310,15 @@
else:
# Cannot overwrite a resource with different UID
if self.destination.exists():
- olduid = index.resourceUIDForName(self.destination.name())
+ olduid = yield index.resourceUIDForName(self.destination.name())
if olduid != uid:
rname = self.destination.name()
result = False
message = "Cannot overwrite vcard resource %s with different UID %s" % (rname, olduid)
- return result, message, rname
+ returnValue((result, message, rname))
+
@inlineCallbacks
def checkQuota(self):
"""
@@ -350,7 +356,7 @@
# Retrieve information from the source, in case we have to delete
# it.
sourceProperties = dict(source.newStoreProperties().iteritems())
- sourceText = source.vCardText()
+ sourceText = yield source.vCardText()
# Delete the original source if needed (for example, if this is a
# same-calendar MOVE of a calendar object, implemented as an
@@ -436,7 +442,7 @@
# UID conflict check - note we do this after reserving the UID to avoid a race condition where two requests
# try to write the same vcard data to two different resource URIs.
- result, message, rname = self.noUIDConflict(self.uid)
+ result, message, rname = yield self.noUIDConflict(self.uid)
if not result:
log.err(message)
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN,
Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -46,6 +46,8 @@
from twext.python.log import Logger
from twext.web2.dav.http import ErrorResponse
+from txdav.common.icommondatastore import ReservationError
+
from twistedcaldav.config import config
from twistedcaldav.caldavxml import ScheduleTag, NoUIDConflict
from twistedcaldav.caldavxml import NumberOfRecurrencesWithinLimits
@@ -57,7 +59,6 @@
from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
from twistedcaldav.ical import Component, Property
-from twistedcaldav.index import ReservationError
from twistedcaldav.instance import TooManyInstancesError,\
InvalidOverriddenInstanceError
from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
@@ -280,7 +281,7 @@
else:
# Get UID from original resource
self.source_index = self.sourceparent.index()
- self.uid = self.source_index.resourceUIDForName(self.source.name())
+ self.uid = yield self.source_index.resourceUIDForName(self.source.name())
if self.uid is None:
log.err("Source calendar does not have a UID: %s" % self.source)
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-object-resource")))
@@ -573,6 +574,7 @@
returnValue(new_has_private_comments)
+ @inlineCallbacks
def noUIDConflict(self, uid):
"""
Check that the UID of the new calendar object conforms to the requirements of
@@ -586,7 +588,7 @@
result = True
message = ""
rname = ""
-
+
# Adjust for a move into same calendar collection
oldname = None
if self.sourceparent and (self.sourceparent == self.destinationparent) and self.deletesource:
@@ -594,8 +596,8 @@
# UID must be unique
index = self.destinationparent.index()
- if not index.isAllowedUID(uid, oldname, self.destination.name()):
- rname = index.resourceNameForUID(uid)
+ if not (yield index.isAllowedUID(uid, oldname, self.destination.name())):
+ rname = yield index.resourceNameForUID(uid)
# This can happen if two simultaneous PUTs occur with the same UID.
# i.e. one PUT has reserved the UID but has not yet written the resource,
# the other PUT tries to reserve and fails but no index entry exists yet.
@@ -606,16 +608,15 @@
else:
# Cannot overwrite a resource with different UID
if self.destination.exists():
- olduid = index.resourceUIDForName(self.destination.name())
+ olduid = yield index.resourceUIDForName(self.destination.name())
if olduid != uid:
rname = self.destination.name()
result = False
message = "Cannot overwrite calendar resource %s with different UID %s" % (rname, olduid)
- return result, message, rname
+ returnValue((result, message, rname))
-
@inlineCallbacks
def doImplicitScheduling(self):
@@ -696,14 +697,17 @@
returnValue((is_scheduling_resource, data_changed, did_implicit_action,))
+
@inlineCallbacks
def mergePerUserData(self):
-
if self.calendar:
accessUID = (yield self.destination.resourceOwnerPrincipal(self.request))
accessUID = accessUID.principalUID() if accessUID else ""
- oldCal = self.destination.iCalendar() if self.destination.exists() and self.destinationcal else None
-
+ if self.destination.exists() and self.destinationcal:
+ oldCal = yield self.destination.iCalendar()
+ else:
+ oldCal = None
+
# Duplicate before we do the merge because someone else may "own" the calendar object
# and we should not change it. This is not ideal as we may duplicate it unnecessarily
# but we currently have no api to let the caller tell us whether it cares about the
@@ -715,7 +719,8 @@
log.err(msg)
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), description=msg))
self.calendardata = None
-
+
+
@inlineCallbacks
def doStore(self, implicit):
@@ -730,7 +735,7 @@
sourceProperties = dict(source.newStoreProperties().iteritems())
if not implicit:
# Only needed in implicit case; see below.
- sourceText = source.iCalendarText()
+ sourceText = yield source.iCalendarText()
# Delete the original source if needed (for example, if this is a
# same-calendar MOVE of a calendar object, implemented as an
@@ -758,6 +763,7 @@
returnValue(IResponse(response))
+
@inlineCallbacks
def doStorePut(self):
@@ -830,7 +836,7 @@
# UID conflict check - note we do this after reserving the UID to avoid a race condition where two requests
# try to write the same calendar data to two different resource URIs.
if not self.isiTIP:
- result, message, rname = self.noUIDConflict(self.uid)
+ result, message, rname = yield self.noUIDConflict(self.uid)
if not result:
log.err(message)
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN,
@@ -931,7 +937,7 @@
etags = self.destination.readDeadProperty(TwistedScheduleMatchETags).children
else:
etags = ()
- etags += (davxml.GETETag.fromString(self.destination.etag().tag),)
+ etags += (davxml.GETETag.fromString((yield self.destination.etag()).tag),)
self.destination.writeDeadProperty(TwistedScheduleMatchETags(*etags))
else:
self.destination.removeDeadProperty(TwistedScheduleMatchETags)
Modified: CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,3 +1,4 @@
+# -*- test-case-name: twistedcaldav.test.test_addressbookquery -*-
##
# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
@@ -146,12 +147,12 @@
for vCardRecord in records:
# match against original filter
- if filter.match(vCardRecord.vCard()):
+ if filter.match((yield vCardRecord.vCard())):
# Check size of results is within limit
checkMaxResults()
- yield report_common.responseForHref(request, responses, vCardRecord.hRef(), vCardRecord, propertiesForResource, query, vcard=vCardRecord.vCard())
+ yield report_common.responseForHref(request, responses, vCardRecord.hRef(), vCardRecord, propertiesForResource, query, vcard=(yield vCardRecord.vCard()))
@@ -213,7 +214,7 @@
index_query_ok = addrresource.index().searchValid(filter)
# Get list of children that match the search and have read access
- names = [name for name, ignore_uid in addrresource.index().search(filter)] #@UnusedVariable
+ names = [name for name, ignore_uid in (yield addrresource.index().search(filter))] #@UnusedVariable
if not names:
return
@@ -228,13 +229,12 @@
(davxml.Read(),),
inherited_aces=filteredaces
)
-
for child, child_uri in ok_resources:
child_uri_name = child_uri[child_uri.rfind("/") + 1:]
child_path_name = urllib.unquote(child_uri_name)
if generate_address_data or not index_query_ok:
- vcard = addrresource.vCard(child_path_name)
+ vcard = yield addrresource.vCard(child_path_name)
assert vcard is not None, "vCard %s is missing from address book collection %r" % (child_uri_name, self)
else:
vcard = None
@@ -262,7 +262,7 @@
handled = True
if not handled:
- vcard = addrresource.vCard()
+ vcard = yield addrresource.vCard()
yield queryAddressBookObjectResource(addrresource, uri, None, vcard)
if limited[0]:
Modified: CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -38,7 +38,7 @@
NumberOfRecurrencesWithinLimits
from twistedcaldav.config import config
from twistedcaldav.customxml import TwistedCalendarAccessProperty
-from twistedcaldav.index import IndexedSearchException
+from txdav.common.icommondatastore import IndexedSearchException
from twistedcaldav.instance import TooManyInstancesError
from twistedcaldav.method import report_common
from twistedcaldav.query import calendarqueryfilter
@@ -175,12 +175,11 @@
try:
# Get list of children that match the search and have read
# access
- names = [name for name, ignore_uid, ignore_type
- in calresource.index().indexedSearch(filter)]
+ records = yield calresource.index().indexedSearch(filter)
except IndexedSearchException:
- names = [name for name, ignore_uid, ignore_type
- in calresource.index().bruteForceSearch()]
+ records = yield calresource.index().bruteForceSearch()
index_query_ok = False
+ names = [name for name, ignore_uid, ignore_type in records]
if not names:
returnValue(True)
Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_common.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -64,9 +64,11 @@
from twistedcaldav.dateops import clipPeriod, normalizePeriodList, timeRangesOverlap
from twistedcaldav.ical import Component, Property, iCalendarProductID
from twistedcaldav.instance import InstanceList
-from twistedcaldav.index import IndexedSearchException
+
from twistedcaldav.query import calendarqueryfilter
+from txdav.common.icommondatastore import IndexedSearchException
+
log = Logger()
COLLECTION_TYPE_REGULAR = "collection"
@@ -381,9 +383,12 @@
fbtype_mapper = {"BUSY": 0, "BUSY-TENTATIVE": 1, "BUSY-UNAVAILABLE": 2}
fbtype_index_mapper = {'B': 0, 'T': 1, 'U': 2}
+
+
@inlineCallbacks
def generateFreeBusyInfo(request, calresource, fbinfo, timerange, matchtotal,
- excludeuid=None, organizer=None, organizerPrincipal=None, same_calendar_user=False,
+ excludeuid=None, organizer=None,
+ organizerPrincipal=None, same_calendar_user=False,
servertoserver=False):
"""
Run a free busy report on the specified calendar collection
@@ -393,15 +398,18 @@
@param fbinfo: the array of busy periods to update.
@param timerange: the L{TimeRange} for the query.
@param matchtotal: the running total for the number of matches.
- @param excludeuid: a C{str} containing a UID value to exclude any components with that
- UID from contributing to free-busy.
- @param organizer: a C{str} containing the value of the ORGANIZER property in the VFREEBUSY request.
- This is used in conjunction with the UID value to process exclusions.
- @param same_calendar_user: a C{bool} indicating whether the calendar user requesting the free-busy information
- is the same as the calendar user being targeted.
- @param servertoserver: a C{bool} indicating whether we are doing a local or remote lookup request.
+ @param excludeuid: a C{str} containing a UID value to exclude any
+ components with that UID from contributing to free-busy.
+ @param organizer: a C{str} containing the value of the ORGANIZER property
+ in the VFREEBUSY request. This is used in conjunction with the UID
+ value to process exclusions.
+ @param same_calendar_user: a C{bool} indicating whether the calendar user
+ requesting the free-busy information is the same as the calendar user
+ being targeted.
+ @param servertoserver: a C{bool} indicating whether we are doing a local or
+ remote lookup request.
"""
-
+
# First check the privilege on this collection
# TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
if not servertoserver:
@@ -441,16 +449,22 @@
tz = None
tzinfo = filter.settimezone(tz)
- # 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.
+ # 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 calresource.inheritedACEsforChildren(request))
+ userPrincipal = (yield calresource.resourceOwnerPrincipal(request))
+ if userPrincipal:
+ useruid = userPrincipal.principalUID()
+ else:
+ useruid = ""
try:
- useruid = (yield calresource.resourceOwnerPrincipal(request))
- useruid = useruid.principalUID() if useruid else ""
- resources = calresource.index().indexedSearch(filter, useruid=useruid, fbtype=True)
+ resources = yield calresource.index().indexedSearch(
+ filter, useruid=useruid, fbtype=True
+ )
except IndexedSearchException:
- resources = calresource.index().bruteForceSearch()
+ resources = yield calresource.index().bruteForceSearch()
# We care about separate instances for VEVENTs only
aggregated_resources = {}
Modified: CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -179,7 +179,9 @@
returnValue(None)
# Verify that valid requested resources are calendar objects
- exists_names = tuple(self.index().resourcesExist(valid_names))
+ exists_names = tuple(
+ (yield self.index().resourcesExist(valid_names))
+ )
checked_names = []
for name in valid_names:
if name not in exists_names:
@@ -309,11 +311,11 @@
parent = (yield child.locateParent(request, resource_uri))
if collection_type == COLLECTION_TYPE_CALENDAR:
- if not parent.isCalendarCollection() or not parent.index().resourceExists(name):
+ if not parent.isCalendarCollection() or not (yield parent.index().resourceExists(name)):
responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
continue
elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
- if not parent.isAddressBookCollection() or not parent.index().resourceExists(name):
+ if not parent.isAddressBookCollection() or not (yield parent.index().resourceExists(name)):
responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
continue
@@ -343,11 +345,11 @@
parent = (yield self.locateParent(request, resource_uri))
if collection_type == COLLECTION_TYPE_CALENDAR:
- if not parent.isPseudoCalendarCollection() or not parent.index().resourceExists(name):
+ if not parent.isPseudoCalendarCollection() or not (yield parent.index().resourceExists(name)):
responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
continue
elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
- if not parent.isAddressBookCollection() or not parent.index().resourceExists(name):
+ if not parent.isAddressBookCollection() or not (yield parent.index().resourceExists(name)):
responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
continue
child = self
Modified: CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -106,7 +106,7 @@
# the child resource loop and supply those to the checkPrivileges on each child.
filteredaces = (yield self.inheritedACEsforChildren(request))
- changed, removed, notallowed, newtoken = self.whatchanged(sync_collection.sync_token, depth)
+ changed, removed, notallowed, newtoken = yield self.whatchanged(sync_collection.sync_token, depth)
# Now determine which valid resources are readable and which are not
ok_resources = []
Modified: CalendarServer/trunk/twistedcaldav/notifications.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/notifications.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/notifications.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -27,7 +27,8 @@
from twext.web2 import responsecode
from twext.web2.dav import davxml
-from twisted.internet.defer import succeed, inlineCallbacks, returnValue
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue,\
+ maybeDeferred
from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn, CalDAVResource
from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
@@ -89,39 +90,45 @@
# Update database
self.notificationsDB().addOrUpdateRecord(NotificationRecord(uid, rname, xmltype.name))
+
def getNotifictionMessages(self, request, componentType=None, returnLatestVersion=True):
return succeed([])
+
def getNotifictionMessageByUID(self, request, uid):
- return succeed(self.notificationsDB().recordForUID(uid))
+ return maybeDeferred(self.notificationsDB().recordForUID, uid)
+
@inlineCallbacks
def deleteNotifictionMessageByUID(self, request, uid):
# See if it exists and delete the resource
- record = self.notificationsDB().recordForUID(uid)
+ record = yield self.notificationsDB().recordForUID(uid)
if record:
yield self.deleteNotification(request, record)
+
@inlineCallbacks
def deleteNotifictionMessageByName(self, request, rname):
# See if it exists and delete the resource
- record = self.notificationsDB().recordForName(rname)
+ record = yield self.notificationsDB().recordForName(rname)
if record:
yield self.deleteNotification(request, record)
returnValue(None)
+
@inlineCallbacks
def deleteNotification(self, request, record):
yield self._deleteNotification(request, record.name)
- self.notificationsDB().removeRecordForUID(record.uid)
-
+ yield self.notificationsDB().removeRecordForUID(record.uid)
+
+
def removedNotifictionMessage(self, request, rname):
- self.notificationsDB().removeRecordForName(rname)
- return succeed(None)
-
+ return maybeDeferred(self.notificationsDB().removeRecordForName, rname)
+
+
class NotificationRecord(object):
def __init__(self, uid, name, xmltype):
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,4 +1,4 @@
-# -*- test-case-name: twistedcaldav.test.test_resource -*-
+# -*- test-case-name: twistedcaldav.test.test_resource,twistedcaldav.test.test_wrapping -*-
##
# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
@@ -72,20 +72,18 @@
from twistedcaldav.extensions import DAVResource, DAVPrincipalResource,\
PropertyNotFoundError, DAVResourceWithChildrenMixin
from twistedcaldav.ical import Component
-from twistedcaldav.ical import Component as iComponent
-from twistedcaldav.ical import Property as iProperty
+
from twistedcaldav.ical import allowedComponents
from twistedcaldav.icaldav import ICalDAVResource, ICalendarPrincipalResource
-from twistedcaldav.index import SyncTokenValidException, Index
from twistedcaldav.linkresource import LinkResource
from twistedcaldav.notify import (getPubSubConfiguration, getPubSubPath,
getPubSubXMPPURI, getPubSubHeartbeatURI, getPubSubAPSConfiguration,
getNodeCacher, NodeCreationException)
from twistedcaldav.sharing import SharedCollectionMixin, SharedHomeMixin
from twistedcaldav.vcard import Component as vComponent
-from twistedcaldav.vcardindex import AddressBookIndex
-from txdav.common.icommondatastore import InternalDataStoreError
+from txdav.common.icommondatastore import InternalDataStoreError, \
+ SyncTokenValidException
##
# Sharing Conts
@@ -181,7 +179,10 @@
calendarPrivilegeSet = _calendarPrivilegeSet()
-class CalDAVResource (CalDAVComplianceMixIn, SharedCollectionMixin, DAVResourceWithChildrenMixin, DAVResource, LoggingMixIn):
+class CalDAVResource (
+ CalDAVComplianceMixIn, SharedCollectionMixin,
+ DAVResourceWithChildrenMixin, DAVResource, LoggingMixIn
+ ):
"""
CalDAV resource.
@@ -274,6 +275,7 @@
self._transactionError = True
+ @inlineCallbacks
def renderHTTP(self, request, transaction=None):
"""
Override C{renderHTTP} to commit the transaction when the resource is
@@ -284,17 +286,15 @@
@param transaction: optional transaction to use instead of associated transaction
@type transaction: L{txdav.caldav.idav.ITransaction}
"""
- d = maybeDeferred(super(CalDAVResource, self).renderHTTP, request)
- def succeeded(result, transaction=None):
- if transaction is None:
- transaction = self._associatedTransaction
- if transaction is not None:
- if self._transactionError:
- transaction.abort()
- else:
- transaction.commit()
- return result
- return d.addCallback(succeeded, transaction=transaction)
+ result = yield super(CalDAVResource, self).renderHTTP(request)
+ if transaction is None:
+ transaction = self._associatedTransaction
+ if transaction is not None:
+ if self._transactionError:
+ yield transaction.abort()
+ else:
+ yield transaction.commit()
+ returnValue(result)
# Begin transitional new-store resource interface:
@@ -538,12 +538,12 @@
elif qname == customxml.GETCTag.qname() and (
self.isPseudoCalendarCollection() or self.isAddressBookCollection()
):
- returnValue(customxml.GETCTag.fromString(self.getSyncToken()))
+ returnValue(customxml.GETCTag.fromString((yield self.getSyncToken())))
elif qname == davxml.SyncToken.qname() and config.EnableSyncReport and (
davxml.Report(SyncCollection(),) in self.supportedReports()
):
- returnValue(davxml.SyncToken.fromString(self.getSyncToken()))
+ returnValue(davxml.SyncToken.fromString((yield self.getSyncToken())))
elif qname == davxml.AddMember.qname() and config.EnableAddMember and (
self.isCalendarCollection() or self.isAddressBookCollection()
@@ -840,6 +840,7 @@
else:
returnValue(None)
+
@inlineCallbacks
def resourceOwnerPrincipal(self, request):
"""
@@ -851,13 +852,16 @@
if isVirt:
returnValue(self._shareePrincipal)
else:
- parent = (yield self.locateParent(request, request.urlForResource(self)))
+ parent = (yield self.locateParent(
+ request, request.urlForResource(self)
+ ))
if parent and isinstance(parent, CalDAVResource):
result = (yield parent.resourceOwnerPrincipal(request))
returnValue(result)
else:
returnValue(None)
+
def isOwner(self, request, adminprincipals=False, readprincipals=False):
"""
Determine whether the DAV:owner of this resource matches the currently authorized principal
@@ -1012,8 +1016,11 @@
if depth != "0" and self.isCollection():
basepath = request.urlForResource(self)
- children = list(self.listChildren())
- getChild()
+ children = []
+ def gotChildren(childNames):
+ children[:] = list(childNames)
+ getChild()
+ maybeDeferred(self.listChildren).addCallback(gotChildren)
else:
completionDeferred.callback(None)
@@ -1025,7 +1032,7 @@
if depth != "0" and self.isCollection():
basepath = request.urlForResource(self)
- for childname in self.listChildren():
+ for childname in (yield self.listChildren()):
childpath = joinURL(basepath, childname)
child = (yield request.locateResource(childpath))
if privileges:
@@ -1111,35 +1118,19 @@
returnValue(False)
- def iCalendar(self, name=None):
- """
- See L{ICalDAVResource.iCalendar}.
- This implementation returns the an object created from the data returned
- by L{iCalendarText} when given the same arguments.
-
- Note that L{iCalendarText} by default calls this method, which creates
- an infinite loop. A subclass must override one of both of these
- methods.
- """
-
- try:
- calendar_data = self.iCalendarText(name)
- except InternalDataStoreError:
- return None
-
- if calendar_data is None: return None
-
- try:
- return iComponent.fromString(calendar_data)
- except ValueError:
- return None
-
@inlineCallbacks
def iCalendarForUser(self, request, name=None):
-
- caldata = self.iCalendar(name)
-
+ if name is not None:
+ # FIXME: this is really the caller's job; why am I looking up sub-
+ # resources?
+ returnValue(
+ (yield (yield request.locateChildResource(self, name)
+ ).iCalendarForUser(request))
+ )
+
+ caldata = yield self.iCalendar()
+
accessUID = (yield self.resourceOwnerPrincipal(request))
if accessUID is None:
accessUID = ""
@@ -1148,6 +1139,7 @@
returnValue(PerUserDataFilter(accessUID).filter(caldata))
+
def iCalendarAddressDoNormalization(self, ical):
"""
Normalize calendar user addresses in the supplied iCalendar object into their
@@ -1176,6 +1168,8 @@
return principal
return None
+
+ @inlineCallbacks
def vCard(self, name=None):
"""
See L{ICalDAVResource.vCard}.
@@ -1188,17 +1182,19 @@
methods.
"""
try:
- vcard_data = self.vCardText(name)
+ vcard_data = yield self.vCardText(name)
except InternalDataStoreError:
- return None
+ returnValue(None)
- if vcard_data is None: return None
+ if vcard_data is None:
+ returnValue(None)
try:
- return vComponent.fromString(vcard_data)
+ returnValue(vComponent.fromString(vcard_data))
except ValueError:
- return None
+ returnValue(None)
+
def supportedReports(self):
result = super(CalDAVResource, self).supportedReports()
result.append(davxml.Report(caldavxml.CalendarQuery(),))
@@ -1321,9 +1317,10 @@
# Collection sync stuff
+
+ @inlineCallbacks
def whatchanged(self, client_token, depth):
-
- current_token = self.getSyncToken()
+ current_token = yield self.getSyncToken()
current_uuid, current_revision = current_token.split("#", 1)
current_revision = int(current_revision)
@@ -1343,11 +1340,11 @@
revision = 0
try:
- changed, removed, notallowed = self._indexWhatChanged(revision, depth)
+ changed, removed, notallowed = yield self._indexWhatChanged(revision, depth)
except SyncTokenValidException:
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (dav_namespace, "valid-sync-token")))
- return changed, removed, notallowed, current_token
+ returnValue((changed, removed, notallowed, current_token))
def _indexWhatChanged(self, revision, depth):
# Now handled directly by newstore
@@ -1460,124 +1457,14 @@
return fail(NotImplementedError())
- def createSpecialCollection(self, resourceType=None):
- #
- # Create the collection once we know it is safe to do so
- #
- def onCollection(status):
- if status != responsecode.CREATED:
- raise HTTPError(status)
+ def iCalendarRolledup(self):
+ """
+ Only implemented by calendar collections; see storebridge.
+ """
+
- self.writeDeadProperty(resourceType)
- return status
- def onError(f):
- try:
- rmdir(self.fp)
- except Exception, e:
- log.err("Unable to clean up after failed MKCOL (special resource type: %s): %s" % (e, resourceType,))
- return f
-
- d = mkcollection(self.fp)
- if resourceType is not None:
- d.addCallback(onCollection)
- d.addErrback(onError)
- return d
-
@inlineCallbacks
- def iCalendarRolledup(self, request):
- if self.isPseudoCalendarCollection():
-
-
-# FIXME: move cache implementation!
- # Determine the cache key
-# isvirt = self.isVirtualShare()
-# if isvirt:
-# principal = (yield self.resourceOwnerPrincipal(request))
-# if principal:
-# cacheKey = principal.principalUID()
-# else:
-# cacheKey = "unknown"
-# else:
-# isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
-# cacheKey = "owner" if isowner else "notowner"
-
- # Now check for a cached .ics
-# rolled = self.fp.child(".subscriptions")
-# if not rolled.exists():
-# try:
-# rolled.makedirs()
-# except IOError, e:
-# self.log_error("Unable to create internet calendar subscription cache directory: %s because of: %s" % (rolled.path, e,))
-# raise HTTPError(ErrorResponse(responsecode.INTERNAL_SERVER_ERROR))
-# cached = rolled.child(cacheKey)
-# if cached.exists():
-# try:
-# cachedData = cached.open().read()
-# except IOError, e:
-# self.log_error("Unable to open or read internet calendar subscription cache file: %s because of: %s" % (cached.path, e,))
-# else:
-# # Check the cache token
-# token, data = cachedData.split("\r\n", 1)
-# if token == self.getSyncToken():
-# returnValue(data)
-
- # Generate a monolithic calendar
- calendar = iComponent("VCALENDAR")
- calendar.addProperty(iProperty("VERSION", "2.0"))
-
- # Do some optimisation 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))
-
- tzids = set()
- isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
- accessPrincipal = (yield self.resourceOwnerPrincipal(request))
-
- for name, uid, type in self.index().bruteForceSearch(): #@UnusedVariable
- try:
- child = yield request.locateChildResource(self, name)
- except TypeError:
- child = None
-
- if child is not None:
- # Check privileges of child - skip if access denied
- try:
- yield child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces)
- except AccessDeniedError:
- continue
-
- # Get the access filtered view of the data
- caldata = child.iCalendarTextFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
- try:
- subcalendar = iComponent.fromString(caldata)
- except ValueError:
- continue
- assert subcalendar.name() == "VCALENDAR"
-
- for component in subcalendar.subcomponents():
-
- # Only insert VTIMEZONEs once
- if component.name() == "VTIMEZONE":
- tzid = component.propertyValue("TZID")
- if tzid in tzids:
- continue
- tzids.add(tzid)
-
- calendar.addComponent(component)
-
- # Cache the data
- data = str(calendar)
- data = self.getSyncToken() + "\r\n" + data
-# try:
-# cached.open(mode='w').write(data)
-# except IOError, e:
-# self.log_error("Unable to open or write internet calendar subscription cache file: %s because of: %s" % (cached.path, e,))
-
- returnValue(calendar)
-
- raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST))
-
def iCalendarTextFiltered(self, isowner, accessUID=None):
try:
access = self.readDeadProperty(TwistedCalendarAccessProperty)
@@ -1585,28 +1472,23 @@
access = None
# Now "filter" the resource calendar data
- caldata = PrivateEventFilter(access, isowner).filter(self.iCalendarText())
+ caldata = PrivateEventFilter(access, isowner).filter(
+ (yield self.iCalendarText())
+ )
if accessUID:
caldata = PerUserDataFilter(accessUID).filter(caldata)
- return str(caldata)
+ returnValue(str(caldata))
- def iCalendarText(self, name=None):
- if self.isPseudoCalendarCollection():
- if name is None:
- return str(self.iCalendar())
- calendar_resource = self.getChild(name)
- return calendar_resource.iCalendarText()
+ def iCalendarText(self):
+ # storebridge handles this method
+ raise NotImplementedError()
- elif self.isCollection():
- return None
- else:
- if name is not None:
- raise AssertionError("name must be None for non-collection calendar resource")
+ def iCalendar(self):
+ # storebridge handles this method
+ raise NotImplementedError()
- # FIXME: StoreBridge handles this case
- raise NotImplementedError
def createAddressBook(self, request):
"""
@@ -1668,26 +1550,22 @@
# TODO: just catenate all the vCards together
yield fail(HTTPError((ErrorResponse(responsecode.BAD_REQUEST))))
+
+ @inlineCallbacks
def vCardText(self, name=None):
if self.isAddressBookCollection():
if name is None:
- return str(self.vCard())
-
- vcard_resource = self.getChild(name)
- return vcard_resource.vCardText()
-
+ returnValue(str((yield self.vCard())))
+ vcard_resource = yield self.getChild(name)
+ returnValue((yield vcard_resource.vCardText()))
elif self.isCollection():
- return None
-
+ returnValue(None)
else:
if name is not None:
raise AssertionError("name must be None for non-collection vcard resource")
-
# FIXME: StoreBridge handles this case
raise NotImplementedError
- def vCardXML(self, name=None):
- return carddavxml.AddressData.fromAddressData(self.vCardText(name))
def supportedPrivileges(self, request):
# read-free-busy support on calendar collection and calendar object resources
@@ -1706,18 +1584,6 @@
return super(CalDAVResource, self).supportedPrivileges(request)
- def index(self):
- """
- Obtains the index for a calendar collection resource.
- @return: the index object for this resource.
- @raise AssertionError: if this resource is not a calendar collection
- resource.
- """
- if self.isAddressBookCollection():
- return AddressBookIndex(self)
- else:
- return Index(self)
-
##
# Quota
##
@@ -1830,9 +1696,6 @@
def isCalendarCollection(self):
return False
- def isPseudoCalendarCollection(self):
- return False
-
def isAddressBookCollection(self):
return False
@@ -2098,35 +1961,51 @@
class CommonHomeResource(SharedHomeMixin, CalDAVResource):
"""
- Calendar home collection resource.
+ Logic common to Calendar and Addressbook home resources.
"""
- def __init__(self, parent, name, transaction):
- """
- """
-
+ def __init__(self, parent, name, transaction, home):
self.parent = parent
self.name = name
self.associateWithTransaction(transaction)
self._provisionedChildren = {}
self._provisionedLinks = {}
self._setupProvisions()
-
- self._newStoreHome, created = self.makeNewStore()
+ self._newStoreHome = home
CalDAVResource.__init__(self)
from twistedcaldav.storebridge import _NewStorePropertiesWrapper
self._dead_properties = _NewStorePropertiesWrapper(
self._newStoreHome.properties()
)
+
+
+ @classmethod
+ @inlineCallbacks
+ def createHomeResource(cls, parent, name, transaction):
+ home, created = yield cls.homeFromTransaction(
+ transaction, name)
+ resource = cls(parent, name, transaction, home)
if created:
- self.postCreateHome()
+ yield resource.postCreateHome()
+ returnValue(resource)
+
+ @classmethod
+ def homeFromTransaction(cls, transaction, uid):
+ """
+ Create or retrieve an appropriate back-end-home object from a
+ transaction and a home UID.
+
+ @return: a L{Deferred} which fires a 2-tuple of C{(created, home)}
+ where C{created} is a boolean indicating whether this call created
+ the home in the back-end, and C{home} is the home object itself.
+ """
+ raise NotImplementedError("Subclasses must implement.")
+
+
def _setupProvisions(self):
pass
- def makeNewStore(self):
- raise NotImplementedError
-
def postCreateHome(self):
pass
@@ -2178,8 +2057,8 @@
"""
Always get quota root value from config.
- @return: a C{int} containing the maximum allowed bytes if this collection
- is quota-controlled, or C{None} if not quota controlled.
+ @return: a C{int} containing the maximum allowed bytes if this
+ collection is quota-controlled, or C{None} if not quota controlled.
"""
return config.UserQuota if config.UserQuota != 0 else None
@@ -2192,8 +2071,8 @@
def _mergeSyncTokens(self, hometoken, notificationtoken):
"""
- Merge two sync tokens, choosing the higher revision number of the two, but keeping
- the home resource-id intact.
+ Merge two sync tokens, choosing the higher revision number of the two,
+ but keeping the home resource-id intact.
"""
homekey, homerev = hometoken.split("#", 1)
notrev = notificationtoken.split("#", 1)[1]
@@ -2204,38 +2083,40 @@
def canShare(self):
raise NotImplementedError
+
+ @inlineCallbacks
def makeChild(self, name):
-
# Try built-in children first
if name in self._provisionedChildren:
cls = self._provisionedChildren[name]
from twistedcaldav.notifications import NotificationCollectionResource
if cls is NotificationCollectionResource:
- return self.createNotificationsCollection()
- child = self._provisionedChildren[name](self)
+ returnValue((yield self.createNotificationsCollection()))
+ child = yield self._provisionedChildren[name](self)
self.propagateTransaction(child)
self.putChild(name, child)
- return child
-
+ returnValue(child)
+
# Try built-in links next
if name in self._provisionedLinks:
child = LinkResource(self, self._provisionedLinks[name])
self.putChild(name, child)
- return child
-
+ returnValue(child)
+
# Try shares next
if self.canShare():
- child = self.provisionShare(name)
+ child = yield self.provisionShare(name)
if child:
- return child
+ returnValue(child)
# Do normal child types
- return self.makeRegularChild(name)
+ returnValue((yield self.makeRegularChild(name)))
+
+ @inlineCallbacks
def createNotificationsCollection(self):
-
txn = self._associatedTransaction
- notifications = txn.notificationsWithUID(self._newStoreHome.uid())
+ notifications = yield txn.notificationsWithUID(self._newStoreHome.uid())
from twistedcaldav.storebridge import StoreNotificationCollectionResource
similar = StoreNotificationCollectionResource(
@@ -2244,21 +2125,25 @@
principalCollections = self.principalCollections(),
)
self.propagateTransaction(similar)
- return similar
+ returnValue(similar)
+
def makeRegularChild(self, name):
raise NotImplementedError
+
+ @inlineCallbacks
def listChildren(self):
"""
@return: a sequence of the names of all known children of this resource.
"""
children = set(self._provisionedChildren.keys())
children.update(self._provisionedLinks.keys())
- children.update(self.allShareNames())
- children.update(self._newStoreHome.listChildren())
- return children
+ children.update((yield self.allShareNames()))
+ children.update((yield self._newStoreHome.listChildren()))
+ returnValue(children)
+
@inlineCallbacks
def readProperty(self, property, request):
if type(property) is tuple:
@@ -2426,14 +2311,26 @@
class CalendarHomeResource(CommonHomeResource):
"""
- Calendar home collection resource.
+ Calendar home collection classmethod.
"""
+ @classmethod
+ @inlineCallbacks
+ def homeFromTransaction(cls, transaction, uid):
+ storeHome = yield transaction.calendarHomeWithUID(uid)
+ if storeHome is not None:
+ created = False
+ else:
+ storeHome = yield transaction.calendarHomeWithUID(uid, create=True)
+ created = True
+ returnValue((storeHome, created))
+
+
def _setupProvisions(self):
# Cache children which must be of a specific type
from twistedcaldav.storebridge import StoreScheduleInboxResource
- self._provisionedChildren["inbox"] = StoreScheduleInboxResource
+ self._provisionedChildren["inbox"] = StoreScheduleInboxResource.maybeCreateInbox
from twistedcaldav.schedule import ScheduleOutboxResource
self._provisionedChildren["outbox"] = ScheduleOutboxResource
@@ -2450,32 +2347,24 @@
from twistedcaldav.notifications import NotificationCollectionResource
self._provisionedChildren["notification"] = NotificationCollectionResource
- def makeNewStore(self):
- storeHome = self._associatedTransaction.calendarHomeWithUID(self.name)
- if storeHome is not None:
- created = False
- else:
- storeHome = self._associatedTransaction.calendarHomeWithUID(
- self.name, create=True
- )
- created = True
- return storeHome, created
-
+ @inlineCallbacks
def postCreateHome(self):
# This is a bit of a hack. Really we ought to be always generating
# this URL live from a back-end method that tells us what the
# default calendar is.
- inbox = self.getChild("inbox")
+ inbox = yield self.getChild("inbox")
childURL = joinURL(self.url(), "calendar")
inbox.processFreeBusyCalendar(childURL, True)
+
def canShare(self):
return config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.exists()
- def makeRegularChild(self, name):
- newCalendar = self._newStoreHome.calendarWithName(name)
+ @inlineCallbacks
+ def makeRegularChild(self, name):
+ newCalendar = yield self._newStoreHome.calendarWithName(name)
if newCalendar is None:
# Local imports.due to circular dependency between modules.
from twistedcaldav.storebridge import (
@@ -2492,8 +2381,9 @@
principalCollections=self.principalCollections()
)
self.propagateTransaction(similar)
- return similar
+ returnValue(similar)
+
def defaultAccessControlList(self):
myPrincipal = self.principalForRecord()
@@ -2546,21 +2436,27 @@
return davxml.ACL(*aces)
+
+ @inlineCallbacks
def getSyncToken(self):
# The newstore implementation supports this directly
- caltoken = self._newStoreHome.syncToken()
+ caltoken = yield self._newStoreHome.syncToken()
if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
- notificationtoken = self.getChild("notification").getSyncToken()
-
+ notificationtoken = yield (yield self.getChild("notification")).getSyncToken()
+
# Merge tokens
caltoken = self._mergeSyncTokens(caltoken, notificationtoken)
-
- return caltoken
+ returnValue(caltoken)
+
+
+ @inlineCallbacks
def _indexWhatChanged(self, revision, depth):
# The newstore implementation supports this directly
- changed, deleted = self._newStoreHome.resourceNamesSinceToken(revision, depth)
+ changed, deleted = yield self._newStoreHome.resourceNamesSinceToken(
+ revision, depth
+ )
notallowed = []
# Need to insert some addition items on first sync
@@ -2569,29 +2465,43 @@
if config.FreeBusyURL.Enabled:
changed.append("freebusy")
-
+
if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
changed.append("notification/")
-
+
# Dropbox is never synchronized
if config.EnableDropBox:
notallowed.append("dropbox/")
-
+
# Add in notification changes
if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
- noti_changed, noti_deleted, noti_notallowed = self.getChild("notification")._indexWhatChanged(revision, depth)
+ noti_changed, noti_deleted, noti_notallowed = yield (yield self.getChild("notification"))._indexWhatChanged(revision, depth)
changed.extend([joinURL("notification", name) for name in noti_changed])
deleted.extend([joinURL("notification", name) for name in noti_deleted])
notallowed.extend([joinURL("notification", name) for name in noti_notallowed])
- return changed, deleted, notallowed
+ returnValue((changed, deleted, notallowed))
+
+
class AddressBookHomeResource (CommonHomeResource):
"""
Address book home collection resource.
"""
-
+
+ @classmethod
+ @inlineCallbacks
+ def homeFromTransaction(cls, transaction, uid):
+ storeHome = yield transaction.addressbookHomeWithUID(uid)
+ if storeHome is not None:
+ created = False
+ else:
+ storeHome = yield transaction.addressbookHomeWithUID(uid, create=True)
+ created = True
+ returnValue((storeHome, created))
+
+
def _setupProvisions(self):
# Cache children which must be of a specific type
@@ -2608,6 +2518,7 @@
def canShare(self):
return config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.exists()
+ @inlineCallbacks
def makeRegularChild(self, name):
# Check for public/global path
@@ -2624,7 +2535,7 @@
mainCls = GlobalAddressBookCollectionResource
protoCls = ProtoGlobalAddressBookCollectionResource
- newAddressBook = self._newStoreHome.addressbookWithName(name)
+ newAddressBook = yield self._newStoreHome.addressbookWithName(name)
if newAddressBook is None:
# Local imports.due to circular dependency between modules.
similar = protoCls(
@@ -2638,51 +2549,58 @@
principalCollections=self.principalCollections()
)
self.propagateTransaction(similar)
- return similar
+ returnValue(similar)
+ @inlineCallbacks
def getSyncToken(self):
# The newstore implementation supports this directly
- adbktoken = self._newStoreHome.syncToken()
+ adbktoken = yield self._newStoreHome.syncToken()
if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
- notifcationtoken = self.getChild("notification").getSyncToken()
+ notifcationtoken = yield (yield self.getChild("notification")).getSyncToken()
# Merge tokens
adbkkey, adbkrev = adbktoken.split("#", 1)
notrev = notifcationtoken.split("#", 1)[1]
if int(notrev) > int(adbkrev):
adbktoken = "%s#%s" % (adbkkey, notrev,)
-
- return adbktoken
+ returnValue(adbktoken)
+
+
+ @inlineCallbacks
def _indexWhatChanged(self, revision, depth):
# The newstore implementation supports this directly
- changed, deleted = self._newStoreHome.resourceNamesSinceToken(revision, depth)
+ changed, deleted = yield self._newStoreHome.resourceNamesSinceToken(
+ revision, depth
+ )
notallowed = []
# Need to insert some addition items on first sync
if revision == 0:
if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
changed.append("notification/")
-
+
# Add in notification changes
if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
- noti_changed, noti_deleted, noti_notallowed = self.getChild("notification")._indexWhatChanged(revision, depth)
+ noti_changed, noti_deleted, noti_notallowed = yield (yield self.getChild("notification"))._indexWhatChanged(revision, depth)
changed.extend([joinURL("notification", name) for name in noti_changed])
deleted.extend([joinURL("notification", name) for name in noti_deleted])
notallowed.extend([joinURL("notification", name) for name in noti_notallowed])
- return changed, deleted, notallowed
+ returnValue((changed, deleted, notallowed))
+
+
class GlobalAddressBookResource (ReadOnlyResourceMixIn, CalDAVResource):
"""
Global address book. All we care about is making sure permissions are setup.
"""
def resourceType(self):
- return davxml.ResourceType.sharedaddressbook
+ return davxml.ResourceType.sharedaddressbook #@UndefinedVariable
def defaultAccessControlList(self):
@@ -2749,8 +2667,6 @@
# Utilities
##
-def isCalendarHomeCollectionResource(resource):
- return isinstance(resource, CalendarHomeResource)
def isCalendarCollectionResource(resource):
try:
@@ -2760,6 +2676,7 @@
else:
return resource.isCalendarCollection()
+
def isPseudoCalendarCollectionResource(resource):
try:
resource = ICalDAVResource(resource)
@@ -2768,8 +2685,6 @@
else:
return resource.isPseudoCalendarCollection()
-def isAddressBookHomeCollectionResource(resource):
- return isinstance(resource, AddressBookHomeResource)
def isAddressBookCollectionResource(resource):
try:
Modified: CalendarServer/trunk/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/schedule.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/schedule.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -154,7 +154,7 @@
if not self.hasDeadProperty(property):
top = self.parent.url()
values = []
- for cal in self.parent._newStoreHome.calendars():
+ for cal in (yield self.parent._newStoreHome.calendars()):
prop = cal.properties().get(PropertyName.fromString(ScheduleCalendarTransp.sname()))
if prop == ScheduleCalendarTransp(Opaque()):
values.append(HRef(joinURL(top, cal.name())))
@@ -261,7 +261,7 @@
defaultCalendarURL = joinURL(calendarHomeURL, "calendar")
defaultCalendar = (yield request.locateResource(defaultCalendarURL))
if defaultCalendar is None or not defaultCalendar.exists():
- getter = iter(self.parent._newStoreHome.calendars())
+ getter = iter((yield self.parent._newStoreHome.calendars()))
# FIXME: the back-end should re-provision a default calendar here.
# Really, the dead property shouldn't be necessary, and this should
# be entirely computed by a back-end method like 'defaultCalendar()'
Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -376,7 +376,7 @@
# Get owner's calendar-home
calendar_owner_principal = (yield self.resource.resourceOwnerPrincipal(self.request))
- calendar_home = calendar_owner_principal.calendarHome(self.request)
+ calendar_home = yield calendar_owner_principal.calendarHome(self.request)
check_parent_uri = parentForURL(check_uri)[:-1] if check_uri else None
@@ -388,7 +388,7 @@
@inlineCallbacks
def queryCalendarCollection(collection, collection_uri):
- rname = collection.index().resourceNameForUID(self.uid)
+ rname = yield collection.index().resourceNameForUID(self.uid)
if rname:
child = (yield self.request.locateResource(joinURL(collection_uri, rname)))
if child == check_resource:
Modified: CalendarServer/trunk/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/processing.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/scheduling/processing.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -20,6 +20,7 @@
from vobject.icalendar import dateTimeToString, utc
+from twisted.python.log import err as log_traceback
from twext.python.log import Logger
from twisted.internet import reactor
@@ -94,6 +95,7 @@
# We attempt to recover from this. That involves trying to re-write the attendee data
# to match that of the organizer assuming we have the organizer's full data available, then
# we try the processing operation again.
+ log_traceback()
log.error("ImplicitProcessing - originator '%s' to recipient '%s' with UID: '%s' - exception raised will try to fix: %s" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid, e))
result = (yield self.doImplicitAttendeeEventFix(e))
if result:
@@ -101,6 +103,7 @@
try:
result = (yield self.doImplicitAttendee())
except Exception, e:
+ log_traceback()
log.error("ImplicitProcessing - originator '%s' to recipient '%s' with UID: '%s' - exception raised after fix: %s" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid, e))
raise ImplicitProcessorException("5.1;Service unavailable")
else:
@@ -422,7 +425,7 @@
@param calendar: calendar data to examine
@type calendar: L{Component}
-
+
@return: L{Component} for the new calendar data to write
"""
@@ -444,7 +447,7 @@
# inNewTransaction wipes out the remembered resource<-> URL mappings in the
# request object but we need to be able to map the actual reply resource to its
# URL when doing auto-processing, so we have to sneak that mapping back in here.
- txn = resource.inNewTransaction(self.request)
+ txn = yield resource.inNewTransaction(self.request)
self.request._rememberResource(resource, resource._url)
try:
@@ -454,9 +457,9 @@
scheduler = ImplicitScheduler()
yield scheduler.sendAttendeeReply(self.request, resource, calendar, self.recipient)
except:
- txn.abort()
+ yield txn.abort()
else:
- txn.commit()
+ yield txn.commit()
finally:
yield lock.clean()
@@ -653,6 +656,7 @@
returnValue(newchild)
+
@inlineCallbacks
def deleteCalendarResource(self, collURL, collection, name):
"""
@@ -665,7 +669,7 @@
@param name: the resource name to write into, or {None} to write a new resource.
@type name: C{str}
"""
- delchild = collection.getChild(name)
+ delchild = yield collection.getChild(name)
childURL = joinURL(collURL, name)
self.request._rememberResource(delchild, childURL)
yield delchild.storeRemove(self.request, False, childURL)
Modified: CalendarServer/trunk/twistedcaldav/scheduling/utils.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/utils.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/scheduling/utils.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -14,7 +14,7 @@
# limitations under the License.
##
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twisted.internet.defer import inlineCallbacks, returnValue
from twistedcaldav.method import report_common
from twext.web2.dav.util import joinURL
@@ -23,7 +23,7 @@
"""
Get a copy of the event for a principal.
"""
-
+
result = {
"resource": None,
"resource_name": None,
@@ -33,33 +33,38 @@
if principal and principal.locallyHosted():
# Get principal's calendar-home
- calendar_home = principal.calendarHome(request)
-
- # FIXME: because of the URL->resource request mapping thing, we have to force the request
- # to recognize this resource
+ calendar_home = yield principal.calendarHome(request)
+
+ # FIXME: because of the URL->resource request mapping thing, we have to
+ # force the request to recognize this resource.
request._rememberResource(calendar_home, calendar_home.url())
- # Run a UID query against the UID
+ # Run a UID query against the UID.
+ @inlineCallbacks
def queryCalendarCollection(collection, uri):
if not allow_shared:
if collection.isVirtualShare():
- return succeed(True)
+ returnValue(True)
- rname = collection.index().resourceNameForUID(uid)
+ rname = yield collection.index().resourceNameForUID(uid)
if rname:
- resource = collection.getChild(rname)
+ resource = yield collection.getChild(rname)
request._rememberResource(resource, joinURL(uri, rname))
result["resource"] = resource
result["resource_name"] = rname
result["calendar_collection"] = collection
result["calendar_collection_uri"] = uri
- return succeed(False)
+ returnValue(False)
else:
- return succeed(True)
-
- # NB We are by-passing privilege checking here. That should be OK as the data found is not
- # exposed to the user.
- yield report_common.applyToCalendarCollections(calendar_home, request, calendar_home.url(), "infinity", queryCalendarCollection, None)
+ returnValue(True)
+ # NB We are by-passing privilege checking here. That should be OK as
+ # the data found is not exposed to the user.
+ yield report_common.applyToCalendarCollections(
+ calendar_home, request, calendar_home.url(),
+ "infinity", queryCalendarCollection, None
+ )
+
returnValue((result["resource"], result["resource_name"], result["calendar_collection"], result["calendar_collection_uri"],))
+
Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/sharing.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -54,22 +54,22 @@
class SharedCollectionMixin(object):
+ @inlineCallbacks
def inviteProperty(self, request):
"""
Calculate the customxml.Invite property (for readProperty) from the
invites database.
"""
+ isShared = yield self.isShared(request)
+ if config.Sharing.Enabled and isShared:
+ yield self.validateInvites()
+ records = yield self.invitesDB().allRecords()
+ returnValue(customxml.Invite(
+ *[record.makePropertyElement() for record in records]
+ ))
+ else:
+ returnValue(None)
- def sharedOK(isShared):
- if config.Sharing.Enabled and isShared:
- self.validateInvites()
- return customxml.Invite(
- *[record.makePropertyElement() for
- record in self.invitesDB().allRecords()]
- )
- else:
- return None
- return self.isShared(request).addCallback(sharedOK)
def upgradeToShare(self):
""" Upgrade this collection to a shared state """
@@ -92,7 +92,7 @@
self.writeDeadProperty(rtype)
# Remove all invitees
- for record in self.invitesDB().allRecords():
+ for record in (yield self.invitesDB().allRecords()):
yield self.uninviteRecordFromShare(record, request)
# Remove invites database
@@ -101,15 +101,15 @@
returnValue(True)
+
+ @inlineCallbacks
def removeUserFromInvite(self, userid, request):
""" Remove a user from this shared calendar """
- self.invitesDB().removeRecordForUserID(userid)
+ returnValue((yield self.invitesDB().removeRecordForUserID(userid)))
- return succeed(True)
@inlineCallbacks
def changeUserInviteState(self, request, inviteUID, principalURL, state, summary=None):
-
shared = (yield self.isShared(request))
if not shared:
raise HTTPError(ErrorResponse(
@@ -118,7 +118,7 @@
"invalid share",
))
- record = self.invitesDB().recordForInviteUID(inviteUID)
+ record = yield self.invitesDB().recordForInviteUID(inviteUID)
if record is None or record.principalURL != principalURL:
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
@@ -131,8 +131,9 @@
record.state = state
if summary is not None:
record.summary = summary
- self.invitesDB().addOrUpdateRecord(record)
+ yield self.invitesDB().addOrUpdateRecord(record)
+
@inlineCallbacks
def directShare(self, request):
"""
@@ -174,9 +175,9 @@
# Get the home collection
if self.isCalendarCollection():
- home = principal.calendarHome(request)
+ home = yield principal.calendarHome(request)
elif self.isAddressBookCollection():
- home = principal.addressBookHome(request)
+ home = yield principal.addressBookHome(request)
else:
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
@@ -196,38 +197,45 @@
# Accept it
response = (yield home.acceptDirectShare(request, request.path, self.resourceID(), self.displayName()))
-
+
# Return the URL of the shared calendar
returnValue(response)
+
+ @inlineCallbacks
def isShared(self, request):
""" Return True if this is an owner shared calendar collection """
- return succeed(self.isSpecialCollection(customxml.SharedOwner))
+ returnValue((yield self.isSpecialCollection(customxml.SharedOwner)))
+
def setVirtualShare(self, shareePrincipal, share):
self._isVirtualShare = True
self._shareePrincipal = shareePrincipal
self._share = share
-
+
if hasattr(self, "_newStoreCalendar"):
self._newStoreCalendar.setSharingUID(self._shareePrincipal.principalUID())
elif hasattr(self, "_newStoreAddressBook"):
self._newStoreAddressBook.setSharingUID(self._shareePrincipal.principalUID())
+
def isVirtualShare(self):
""" Return True if this is a shared calendar collection """
return hasattr(self, "_isVirtualShare")
+
+ @inlineCallbacks
def removeVirtualShare(self, request):
""" Return True if this is a shared calendar collection """
# Remove from sharee's calendar/address book home
if self.isCalendarCollection():
- shareeHome = self._shareePrincipal.calendarHome(request)
+ shareeHome = yield self._shareePrincipal.calendarHome(request)
elif self.isAddressBookCollection():
- shareeHome = self._shareePrincipal.addressBookHome(request)
- return shareeHome.removeShare(request, self._share)
+ shareeHome = yield self._shareePrincipal.addressBookHome(request)
+ returnValue((yield shareeHome.removeShare(request, self._share)))
+
def resourceType(self):
superObject = super(SharedCollectionMixin, self)
try:
@@ -246,7 +254,8 @@
)
)
return rtype
-
+
+
def sharedResourceType(self):
"""
Return the DAV:resourcetype stripped of any shared elements.
@@ -259,6 +268,7 @@
else:
return ""
+
@inlineCallbacks
def shareeAccessControlList(self, request, *args, **kwargs):
@@ -273,7 +283,9 @@
# Invite shares use access mode from the invite
# Get the invite for this sharee
- invite = self.invitesDB().recordForInviteUID(self._share.shareuid)
+ invite = yield self.invitesDB().recordForInviteUID(
+ self._share.shareuid
+ )
if invite is None:
returnValue(davxml.ACL())
@@ -289,7 +301,7 @@
userprivs.append(davxml.Privilege(davxml.Write()))
proxyprivs = list(userprivs)
proxyprivs.remove(davxml.Privilege(davxml.ReadACL()))
-
+
aces = (
# Inheritable specific access for the resource's associated principal.
davxml.ACE(
@@ -299,7 +311,7 @@
TwistedACLInheritable(),
),
)
-
+
if self.isCalendarCollection():
aces += (
# Inheritable CALDAV:read-free-busy access for authenticated users.
@@ -309,13 +321,13 @@
TwistedACLInheritable(),
),
)
-
+
# Give read access to config.ReadPrincipals
aces += config.ReadACEs
-
+
# Give all access to config.AdminPrincipals
aces += config.AdminACEs
-
+
if config.EnableProxyPrincipals:
aces += (
# DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
@@ -336,7 +348,7 @@
TwistedACLInheritable(),
),
)
-
+
returnValue(davxml.ACL(*aces))
def validUserIDForShare(self, userid):
@@ -389,17 +401,20 @@
else:
return None, None, None
+
+ @inlineCallbacks
def validateInvites(self):
"""
Make sure each userid in an invite is valid - if not re-write status.
"""
- records = self.invitesDB().allRecords()
+ records = yield self.invitesDB().allRecords()
for record in records:
if self.validUserIDForShare(record.userid) is None and record.state != "INVALID":
record.state = "INVALID"
- self.invitesDB().addOrUpdateRecord(record)
-
+ yield self.invitesDB().addOrUpdateRecord(record)
+
+
def inviteUserToShare(self, userid, cn, ace, summary, request):
""" Send out in invite first, and then add this user to the share list
@param userid:
@@ -455,6 +470,7 @@
dl = [self.inviteSingleUserUpdateToShare(user, cn, aceOLD, aceNEW, summary, request) for user, cn in zip(userid, cn)]
return DeferredList(dl).addCallback(_defer)
+
@inlineCallbacks
def inviteSingleUserToShare(self, userid, cn, ace, summary, request):
@@ -466,7 +482,7 @@
returnValue(False)
# Look for existing invite and update its fields or create new one
- record = self.invitesDB().recordForPrincipalURL(principalURL)
+ record = yield self.invitesDB().recordForPrincipalURL(principalURL)
if record:
record.name = cn
record.access = inviteAccessMapFromXML[type(ace)]
@@ -478,16 +494,22 @@
yield self.sendInvite(record, request)
# Add to database
- self.invitesDB().addOrUpdateRecord(record)
+ yield self.invitesDB().addOrUpdateRecord(record)
returnValue(True)
+
+ @inlineCallbacks
def uninviteSingleUserFromShare(self, userid, aces, request):
-
# Cancel invites - we'll just use whatever userid we are given
- record = self.invitesDB().recordForUserID(userid)
- return self.uninviteRecordFromShare(record, request) if record else succeed(True)
-
+ record = yield self.invitesDB().recordForUserID(userid)
+ if record:
+ result = (yield self.uninviteRecordFromShare(record, request))
+ else:
+ result = True
+ returnValue(result)
+
+
@inlineCallbacks
def uninviteRecordFromShare(self, record, request):
@@ -495,9 +517,9 @@
sharee = self.principalForCalendarUserAddress(record.userid)
if sharee:
if self.isCalendarCollection():
- shareeHome = sharee.calendarHome(request)
+ shareeHome = yield sharee.calendarHome(request)
elif self.isAddressBookCollection():
- shareeHome = sharee.addressBookHome(request)
+ shareeHome = yield sharee.addressBookHome(request)
yield shareeHome.removeShareByUID(request, record.inviteuid)
# If current user state is accepted then we send an invite with the new state, otherwise
@@ -509,7 +531,7 @@
yield self.sendInvite(record, request)
# Remove from database
- self.invitesDB().removeRecordForUserID(record.userid)
+ yield self.invitesDB().removeRecordForUserID(record.userid)
returnValue(True)
@@ -530,7 +552,7 @@
sharee = self.principalForCalendarUserAddress(record.userid)
if sharee is None:
raise ValueError("sharee is None but userid was valid before")
- notifications = self._associatedTransaction.notificationsWithUID(
+ notifications = yield self._associatedTransaction.notificationsWithUID(
sharee.principalUID()
)
@@ -684,7 +706,7 @@
(okusers if result else badusers).add(userid)
# Do a final validation of the entire set of invites
- self.validateInvites()
+ yield self.validateInvites()
# Create the multistatus response - only needed if some are bad
if badusers:
@@ -938,27 +960,35 @@
A mix-in for calendar/addressbook homes that defines the operations for
manipulating a sharee's set of shared calendars.
"""
-
+
+ @inlineCallbacks
def provisionShare(self, name):
-
# Try to find a matching share
child = None
- shares = self.allShares()
+ shares = yield self.allShares()
if name in shares:
from twistedcaldav.sharedcollection import SharedCollectionResource
child = SharedCollectionResource(self, shares[name])
self.putChild(name, child)
- return child
+ returnValue(child)
+
+ @inlineCallbacks
def allShares(self):
if not hasattr(self, "_allShares"):
- self._allShares = dict([(share.localname, share) for share in self.sharesDB().allRecords()])
- return self._allShares
+ allShareRecords = yield self.sharesDB().allRecords()
+ self._allShares = dict([(share.localname, share) for share in
+ allShareRecords])
+ returnValue(self._allShares)
+
+ @inlineCallbacks
def allShareNames(self):
- return tuple(self.allShares().keys())
+ allShares = yield self.allShares()
+ returnValue(tuple(allShares.keys()))
+
@inlineCallbacks
def acceptInviteShare(self, request, hostUrl, inviteUID, displayname=None):
@@ -977,12 +1007,12 @@
def _acceptShare(self, request, sharetype, hostUrl, shareUID, displayname=None):
# Add or update in DB
- oldShare = self.sharesDB().recordForShareUID(shareUID)
+ oldShare = yield self.sharesDB().recordForShareUID(shareUID)
if oldShare:
share = oldShare
else:
share = SharedCollectionRecord(shareUID, sharetype, hostUrl, str(uuid4()), displayname)
- self.sharesDB().addOrUpdateRecord(share)
+ yield self.sharesDB().addOrUpdateRecord(share)
# Set per-user displayname to whatever was given
sharedCollection = (yield request.locateResource(hostUrl))
@@ -1019,7 +1049,7 @@
def removeShareByUID(self, request, shareUID):
""" Remove a shared collection but do not send a decline back """
- share = self.sharesDB().recordForShareUID(shareUID)
+ share = yield self.sharesDB().recordForShareUID(shareUID)
if share:
yield self.removeDirectShare(request, share)
@@ -1039,7 +1069,7 @@
inbox = (yield request.locateResource(inboxURL))
inbox.processFreeBusyCalendar(shareURL, False)
- self.sharesDB().removeRecordForShareUID(share.shareuid)
+ yield self.sharesDB().removeRecordForShareUID(share.shareuid)
# Notify client of changes
self.notifyChanged()
@@ -1209,12 +1239,8 @@
records = self._db_execute("select * from SHARES order by LOCALNAME")
return [self._makeRecord(row) for row in (records if records is not None else ())]
-
- def recordForLocalName(self, localname):
-
- row = self._db_execute("select * from SHARES where LOCALNAME = :1", localname)
- return self._makeRecord(row[0]) if row else None
-
+
+
def recordForShareUID(self, shareUID):
row = self._db_execute("select * from SHARES where SHAREUID = :1", shareUID)
Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -24,7 +24,8 @@
from urlparse import urlsplit
-from twisted.internet.defer import succeed, inlineCallbacks, returnValue
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue,\
+ maybeDeferred
from twisted.internet.protocol import Protocol
from twisted.python.log import err as logDefaultException
from twisted.python.util import FancyEqMixin
@@ -35,7 +36,7 @@
from twext.web2.dav import davxml
from twext.web2.dav.element.base import dav_namespace
from twext.web2.dav.http import ErrorResponse, ResponseQueue
-from twext.web2.dav.resource import TwistedACLInheritable
+from twext.web2.dav.resource import TwistedACLInheritable, AccessDeniedError
from twext.web2.dav.util import parentForURL, allDataFromStream, joinURL, \
davXMLFromStream
from twext.web2.http import HTTPError, StatusResponse, Response
@@ -160,45 +161,54 @@
def name(self):
return self._newStoreObject.name() if self._newStoreObject is not None else None
+
+ @inlineCallbacks
def etag(self):
# FIXME: far too slow to be used for real, but I needed something to
# placate the etag computation in the case where the file doesn't exist
# yet (an uncommitted transaction creating this calendar file)
if self._newStoreObject is None:
- return None
+ returnValue(None)
# FIXME: direct tests
try:
- md5 = self._newStoreObject.md5()
+ md5 = yield self._newStoreObject.md5()
if md5:
- return ETag(md5)
+ returnValue(ETag(md5))
else:
- return ETag(
- hashlib.new("md5", self.text()).hexdigest(),
+ returnValue(ETag(
+ hashlib.new("md5", (yield self.text())).hexdigest(),
weak=False
- )
+ ))
except NoSuchObjectResourceError:
# FIXME: a workaround for the fact that DELETE still rudely vanishes
# the calendar object out from underneath the store, and doesn't
# call storeRemove.
- return None
+ returnValue(None)
+
def contentType(self):
return self._newStoreObject.contentType() if self._newStoreObject is not None else None
+
def contentLength(self):
return self._newStoreObject.size() if self._newStoreObject is not None else None
+
def lastModified(self):
return self._newStoreObject.modified() if self._newStoreObject is not None else None
+
def creationDate(self):
return self._newStoreObject.created() if self._newStoreObject is not None else None
+
def newStoreProperties(self):
return self._newStoreObject.properties() if self._newStoreObject is not None else None
+
+
class _CalendarChildHelper(object):
"""
Methods for things which are like calendars.
@@ -236,15 +246,21 @@
self._invitesDB = self._newStoreCalendar.retrieveOldInvites()
return self._invitesDB
+
def exists(self):
# FIXME: tests
return True
+ @inlineCallbacks
def _indexWhatChanged(self, revision, depth):
# The newstore implementation supports this directly
- return self._newStoreCalendar.resourceNamesSinceToken(revision) + ([],)
+ returnValue(
+ (yield self._newStoreCalendar.resourceNamesSinceToken(revision))
+ + ([],)
+ )
+
@classmethod
def transform(cls, self, calendar, home):
"""
@@ -254,13 +270,15 @@
self._initializeWithCalendar(calendar, home)
+ @inlineCallbacks
def makeChild(self, name):
"""
- Create a L{CalendarObjectResource} or L{ProtoCalendarObjectResource} based on a
- path object.
+ Create a L{CalendarObjectResource} or L{ProtoCalendarObjectResource}
+ based on a calendar object name.
"""
- newStoreObject = self._newStoreCalendar.calendarObjectWithName(name)
+ cal = self._newStoreCalendar
+ newStoreObject = yield cal.calendarObjectWithName(name)
if newStoreObject is not None:
similar = CalendarObjectResource(
@@ -268,27 +286,23 @@
principalCollections=self._principalCollections
)
else:
- # FIXME: creation in http_PUT should talk to a specific resource
- # type; this is the domain of StoreCalendarObjectResource.
- # similar = ProtoCalendarObjectFile(self._newStoreCalendar, path)
similar = ProtoCalendarObjectResource(
- self._newStoreCalendar,
- name,
+ cal, name,
principalCollections=self._principalCollections
)
- # FIXME: tests should be failing without this line.
- # Specifically, http_PUT won't be committing its transaction properly.
self.propagateTransaction(similar)
- return similar
+ returnValue(similar)
+
+ @inlineCallbacks
def listChildren(self):
"""
@return: a sequence of the names of all known children of this resource.
"""
children = set(self.putChildren.keys())
- children.update(self._newStoreCalendar.listCalendarObjects())
- return sorted(children)
+ children.update((yield self._newStoreCalendar.listCalendarObjects()))
+ returnValue(sorted(children))
def quotaSize(self, request):
@@ -302,37 +316,49 @@
def __init__(self, *a, **kw):
super(StoreScheduleInboxResource, self).__init__(*a, **kw)
self.parent.propagateTransaction(self)
+
+
+ @classmethod
+ @inlineCallbacks
+ def maybeCreateInbox(cls, *a, **kw):
+ self = cls(*a, **kw)
home = self.parent._newStoreHome
- storage = home.calendarWithName("inbox")
+ storage = yield home.calendarWithName("inbox")
if storage is None:
# raise RuntimeError("backend should be handling this for us")
# FIXME: spurious error, sanity check, should not be needed;
# unfortunately, user09's calendar home does not have an inbox, so
# this is a temporary workaround.
- home.createCalendarWithName("inbox")
- storage = home.calendarWithName("inbox")
+ yield home.createCalendarWithName("inbox")
+ storage = yield home.calendarWithName("inbox")
self._initializeWithCalendar(
storage,
self.parent._newStoreHome
)
+ returnValue(self)
def name(self):
return self._newStoreCalendar.name()
+
+ @inlineCallbacks
def etag(self):
- return ETag(self._newStoreCalendar.md5())
+ returnValue(ETag((yield 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
@@ -397,36 +423,42 @@
return True
+ @inlineCallbacks
def getChild(self, name):
- calendarObject = self._newStoreHome.calendarObjectWithDropboxID(name)
+ calendarObject = yield self._newStoreHome.calendarObjectWithDropboxID(name)
if calendarObject is None:
- return NoDropboxHere()
+ returnValue(NoDropboxHere())
objectDropbox = CalendarObjectDropbox(
calendarObject, principalCollections=self.principalCollections()
)
self.propagateTransaction(objectDropbox)
- return objectDropbox
+ returnValue(objectDropbox)
def resourceType(self,):
return davxml.ResourceType.dropboxhome #@UndefinedVariable
+ @inlineCallbacks
def listChildren(self):
l = []
- for everyCalendar in self._newStoreHome.calendars():
- for everyObject in everyCalendar.calendarObjects():
- l.append(everyObject.dropboxID())
- return l
+ for everyCalendar in (yield self._newStoreHome.calendars()):
+ for everyObject in (yield everyCalendar.calendarObjects()):
+ l.append((yield everyObject.dropboxID()))
+ returnValue(l)
+
+
class NoDropboxHere(_GetChildHelper):
def isCollection(self):
return False
+
def exists(self):
return False
+
def http_GET(self, request):
return NOT_FOUND
@@ -459,8 +491,9 @@
return davxml.ResourceType.dropbox #@UndefinedVariable
+ @inlineCallbacks
def getChild(self, name):
- attachment = self._newStoreCalendarObject.attachmentWithName(name)
+ attachment = yield self._newStoreCalendarObject.attachmentWithName(name)
if attachment is None:
result = ProtoCalendarAttachment(
self._newStoreCalendarObject,
@@ -471,7 +504,7 @@
self._newStoreCalendarObject,
attachment, principalCollections=self.principalCollections())
self.propagateTransaction(result)
- return result
+ returnValue(result)
@inlineCallbacks
@@ -481,7 +514,7 @@
that refer to permissions not referenced by attendees in the iCalendar
data.
"""
- attendees = self._newStoreCalendarObject.component().getAttendees()
+ attendees = (yield self._newStoreCalendarObject.component()).getAttendees()
attendees = [attendee.split("urn:uuid:")[-1] for attendee in attendees]
document = yield davXMLFromStream(request.stream)
for ace in document.root_element.children:
@@ -516,69 +549,73 @@
return NO_CONTENT
+ @inlineCallbacks
def listChildren(self):
l = []
- for attachment in self._newStoreCalendarObject.attachments():
+ for attachment in (self._newStoreCalendarObject.attachments()):
l.append(attachment.name())
- return l
+ returnValue(l)
+ @inlineCallbacks
def accessControlList(self, *a, **kw):
"""
All principals identified as ATTENDEEs on the event for this dropbox
may read all its children. Also include proxies of ATTENDEEs. Ignore
unknown attendees.
"""
- d = super(CalendarObjectDropbox, self).accessControlList(*a, **kw)
- def moreACLs(originalACL):
- othersCanWrite = (
- self._newStoreCalendarObject.attendeesCanManageAttachments()
+ originalACL = yield super(
+ CalendarObjectDropbox, self).accessControlList(*a, **kw)
+ othersCanWrite = (
+ yield self._newStoreCalendarObject.attendeesCanManageAttachments()
+ )
+ originalACEs = list(originalACL.children)
+ cuas = (yield self._newStoreCalendarObject.component()).getAttendees()
+ newACEs = []
+ for calendarUserAddress in cuas:
+ principal = self.principalForCalendarUserAddress(
+ calendarUserAddress
)
- originalACEs = list(originalACL.children)
- cuas = self._newStoreCalendarObject.component().getAttendees()
- newACEs = []
- for calendarUserAddress in cuas:
- principal = self.principalForCalendarUserAddress(
- calendarUserAddress
- )
- if principal is None:
- continue
- principalURL = principal.principalURL()
- writePrivileges = [
- davxml.Privilege(davxml.Read()),
- davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
- davxml.Privilege(davxml.Write()),
- ]
- readPrivileges = [
- davxml.Privilege(davxml.Read()),
- davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
- ]
- privileges = writePrivileges if othersCanWrite else readPrivileges
- newACEs.append(davxml.ACE(
- davxml.Principal(davxml.HRef(principalURL)),
- davxml.Grant(*privileges),
- davxml.Protected(),
- TwistedACLInheritable(),
- ))
- newACEs.append(davxml.ACE(
- davxml.Principal(davxml.HRef(joinURL(principalURL, "calendar-proxy-write/"))),
- davxml.Grant(*privileges),
- davxml.Protected(),
- TwistedACLInheritable(),
- ))
- newACEs.append(davxml.ACE(
- davxml.Principal(davxml.HRef(joinURL(principalURL, "calendar-proxy-read/"))),
- davxml.Grant(*readPrivileges),
- davxml.Protected(),
- TwistedACLInheritable(),
- ))
+ if principal is None:
+ continue
- return davxml.ACL(*tuple(newACEs + originalACEs))
- d.addCallback(moreACLs)
- return d
+ principalURL = principal.principalURL()
+ writePrivileges = [
+ davxml.Privilege(davxml.Read()),
+ davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+ davxml.Privilege(davxml.Write()),
+ ]
+ readPrivileges = [
+ davxml.Privilege(davxml.Read()),
+ davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+ ]
+ if othersCanWrite:
+ privileges = writePrivileges
+ else:
+ privileges = readPrivileges
+ newACEs.append(davxml.ACE(
+ davxml.Principal(davxml.HRef(principalURL)),
+ davxml.Grant(*privileges),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ))
+ newACEs.append(davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(principalURL, "calendar-proxy-write/"))),
+ davxml.Grant(*privileges),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ))
+ newACEs.append(davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(principalURL, "calendar-proxy-read/"))),
+ davxml.Grant(*readPrivileges),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ))
+ returnValue(davxml.ACL(*tuple(newACEs + originalACEs)))
+
class ProtoCalendarAttachment(_NewStoreFileMetaDataHelper, _GetChildHelper):
def __init__(self, calendarObject, attachmentName, **kw):
@@ -587,6 +624,7 @@
self.attachmentName = attachmentName
self._newStoreObject = None
+
def isCollection(self):
return False
@@ -601,28 +639,29 @@
@requiresPermissions(fromParent=[davxml.Bind()])
+ @inlineCallbacks
def http_PUT(self, request):
# FIXME: direct test
# FIXME: transformation?
-
content_type = request.headers.getHeader("content-type")
if content_type is None:
content_type = MimeType("application", "octet-stream")
-
- t = self.calendarObject.createAttachmentWithName(
+ t = yield self.calendarObject.createAttachmentWithName(
self.attachmentName,
content_type,
)
- def done(ignored):
- self._newStoreObject = self.calendarObject.attachmentWithName(self.attachmentName)
- t.loseConnection()
- return CREATED
- return readStream(request.stream, t.write).addCallback(done)
+ yield readStream(request.stream, t.write)
+ self._newStoreObject = yield self.calendarObject.attachmentWithName(
+ self.attachmentName
+ )
+ t.loseConnection()
+ returnValue(CREATED)
http_MKCOL = None
http_MKCALENDAR = None
+
class CalendarAttachment(_NewStoreFileMetaDataHelper, _GetChildHelper):
def __init__(self, calendarObject, attachment, **kw):
@@ -631,10 +670,11 @@
self._newStoreAttachment = self._newStoreObject = attachment
+ @inlineCallbacks
def etag(self):
# FIXME: test
- md5 = self._newStoreAttachment.md5()
- return ETag(md5)
+ md5 = yield self._newStoreAttachment.md5()
+ returnValue(ETag(md5))
def contentType(self):
@@ -674,15 +714,18 @@
self._newStoreAttachment.retrieve(StreamProtocol())
return Response(OK, {"content-type":self.contentType()}, stream)
+
@requiresPermissions(fromParent=[davxml.Unbind()])
+ @inlineCallbacks
def http_DELETE(self, request):
- self._newStoreCalendarObject.removeAttachmentWithName(
+ yield self._newStoreCalendarObject.removeAttachmentWithName(
self._newStoreAttachment.name()
)
del self._newStoreCalendarObject
self.__class__ = ProtoCalendarAttachment
- return NO_CONTENT
+ returnValue(NO_CONTENT)
+
http_MKCOL = None
http_MKCALENDAR = None
@@ -714,18 +757,24 @@
def name(self):
return self._newStoreCalendar.name()
+
+ @inlineCallbacks
def etag(self):
- return ETag(self._newStoreCalendar.md5())
+ returnValue(ETag((yield 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 isCollection(self):
return True
@@ -737,6 +786,62 @@
return True
+ @inlineCallbacks
+ def iCalendarRolledup(self, request):
+ # FIXME: uncached: implement cache in the storage layer
+
+ # Generate a monolithic calendar
+ calendar = vcomponent.VComponent("VCALENDAR")
+ calendar.addProperty(vcomponent.VProperty("VERSION", "2.0"))
+
+ # Do some optimisation 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))
+
+ tzids = set()
+ isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+ accessPrincipal = (yield self.resourceOwnerPrincipal(request))
+
+ for name, uid, type in (yield self.index().bruteForceSearch()): #@UnusedVariable
+ try:
+ child = yield request.locateChildResource(self, name)
+ except TypeError:
+ child = None
+
+ if child is not None:
+ # Check privileges of child - skip if access denied
+ try:
+ yield child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces)
+ except AccessDeniedError:
+ continue
+
+ # Get the access filtered view of the data
+ caldata = yield child.iCalendarTextFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
+ try:
+ subcalendar = vcomponent.VComponent.fromString(caldata)
+ except ValueError:
+ continue
+ assert subcalendar.name() == "VCALENDAR"
+
+ for component in subcalendar.subcomponents():
+
+ # Only insert VTIMEZONEs once
+ if component.name() == "VTIMEZONE":
+ tzid = component.propertyValue("TZID")
+ if tzid in tzids:
+ continue
+ tzids.add(tzid)
+
+ calendar.addComponent(component)
+
+ # Cache the data
+ data = str(calendar)
+ data = (yield self.getSyncToken()) + "\r\n" + data
+
+ returnValue(calendar)
+
+
@requiresPermissions(fromParent=[davxml.Unbind()])
@inlineCallbacks
def http_DELETE(self, request):
@@ -807,7 +912,7 @@
errors = ResponseQueue(where, "DELETE", NO_CONTENT)
- for childname in self.listChildren():
+ for childname in (yield self.listChildren()):
childurl = joinURL(where, childname)
@@ -830,7 +935,7 @@
yield self.downgradeFromShare(request)
# Actually delete it.
- self._newStoreParentHome.removeCalendarWithName(
+ yield self._newStoreParentHome.removeCalendarWithName(
self._newStoreCalendar.name()
)
self.__class__ = ProtoCalendarCollectionResource
@@ -876,7 +981,7 @@
# FIXME: should really use something other than 'fp' attribute.
basename = destination.name()
calendar = self._newStoreCalendar
- calendar.rename(basename)
+ yield calendar.rename(basename)
CalendarCollectionResource.transform(destination, calendar,
self._newStoreParentHome)
del self._newStoreCalendar
@@ -937,20 +1042,19 @@
return self.createCalendarCollection()
+ @inlineCallbacks
def createCalendarCollection(self):
"""
Override C{createCalendarCollection} to actually do the work.
"""
- d = succeed(CREATED)
-
- self._newStoreParentHome.createCalendarWithName(self._name)
- newStoreCalendar = self._newStoreParentHome.calendarWithName(
+ yield self._newStoreParentHome.createCalendarWithName(self._name)
+ newStoreCalendar = yield self._newStoreParentHome.calendarWithName(
self._name
)
CalendarCollectionResource.transform(
self, newStoreCalendar, self._newStoreParentHome
)
- return d
+ returnValue(CREATED)
def exists(self):
@@ -992,17 +1096,17 @@
self._initializeWithObject(calendarObject)
+ @inlineCallbacks
def inNewTransaction(self, request):
"""
Implicit auto-replies need to span multiple transactions. Clean out
the given request's resource-lookup mapping, transaction, and re-look-
- up my calendar object in a new transaction.
+ up this L{CalendarObjectResource}'s calendar object in a new
+ transaction.
- @return: the new transaction so it can be committed.
+ @return: a Deferred which fires with the new transaction, so it can be
+ committed.
"""
- # FIXME: private names from 'file' implementation; maybe there should
- # be a public way to do this? or maybe we should just have a real
- # queue.
objectName = self._newStoreObject.name()
calendar = self._newStoreObject.calendar()
calendarName = calendar.name()
@@ -1010,14 +1114,14 @@
homeUID = ownerHome.uid()
txn = ownerHome.transaction().store().newTransaction(
"new transaction for " + self._newStoreObject.name())
- newObject = (txn.calendarHomeWithUID(homeUID)
- .calendarWithName(calendarName)
- .calendarObjectWithName(objectName))
+ newObject = ((yield (yield (yield txn.calendarHomeWithUID(homeUID))
+ .calendarWithName(calendarName))
+ .calendarObjectWithName(objectName)))
request._newStoreTransaction = txn
request._resourcesByURL.clear()
request._urlsByResource.clear()
self._initializeWithObject(newObject)
- return txn
+ returnValue(txn)
def isCollection(self):
@@ -1029,16 +1133,20 @@
return True
+ @inlineCallbacks
def quotaSize(self, request):
# FIXME: tests
- return succeed(len(self._newStoreObject.iCalendarText()))
+ returnValue(len((yield self.iCalendarText())))
- def iCalendarText(self, ignored=None):
- assert ignored is None, "This is a calendar object, not a calendar"
+ def iCalendarText(self):
return self._newStoreObject.iCalendarText()
+ def iCalendar(self):
+ return self._newStoreObject.component()
+
+
def text(self):
return self.iCalendarText()
@@ -1057,7 +1165,7 @@
component = vcomponent.VComponent.fromString(
(yield allDataFromStream(stream))
)
- self._newStoreObject.setComponent(component)
+ yield self._newStoreObject.setComponent(component)
returnValue(NO_CONTENT)
@@ -1152,7 +1260,7 @@
# FIXME: public attribute please. Should ICalendar maybe just have
# a delete() method?
- storeCalendar.removeCalendarObjectWithName(
+ yield storeCalendar.removeCalendarObjectWithName(
self._newStoreObject.name()
)
@@ -1212,10 +1320,15 @@
component = vcomponent.VComponent.fromString(
(yield allDataFromStream(stream))
)
- self._newStoreParentCalendar.createCalendarObjectWithName(
+ yield self._newStoreParentCalendar.createCalendarObjectWithName(
self.name(), component
)
- CalendarObjectResource.transform(self, self._newStoreParentCalendar.calendarObjectWithName(self.name()))
+ CalendarObjectResource.transform(
+ self,
+ (yield self._newStoreParentCalendar.calendarObjectWithName(
+ self.name()
+ ))
+ )
returnValue(CREATED)
@@ -1277,15 +1390,21 @@
self._invitesDB = self._newStoreAddressBook.retrieveOldInvites()
return self._invitesDB
+
def exists(self):
# FIXME: tests
return True
+ @inlineCallbacks
def _indexWhatChanged(self, revision, depth):
# The newstore implementation supports this directly
- return self._newStoreAddressBook.resourceNamesSinceToken(revision) + ([],)
+ returnValue(
+ (yield self._newStoreAddressBook.resourceNamesSinceToken(revision))
+ + ([],)
+ )
+
@classmethod
def transform(cls, self, addressbook, home):
"""
@@ -1295,12 +1414,13 @@
self._initializeWithAddressBook(addressbook, home)
+ @inlineCallbacks
def makeChild(self, name):
"""
Create a L{AddressBookObjectResource} or L{ProtoAddressBookObjectResource} based on a
path object.
"""
- newStoreObject = self._newStoreAddressBook.addressbookObjectWithName(name)
+ newStoreObject = yield self._newStoreAddressBook.addressbookObjectWithName(name)
if newStoreObject is not None:
similar = AddressBookObjectResource(
@@ -1320,15 +1440,19 @@
# FIXME: tests should be failing without this line.
# Specifically, http_PUT won't be committing its transaction properly.
self.propagateTransaction(similar)
- return similar
+ returnValue(similar)
+
+ @inlineCallbacks
def listChildren(self):
"""
@return: a sequence of the names of all known children of this resource.
"""
children = set(self.putChildren.keys())
- children.update(self._newStoreAddressBook.listAddressbookObjects())
- return sorted(children)
+ children.update(
+ (yield self._newStoreAddressBook.listAddressbookObjects())
+ )
+ returnValue(sorted(children))
@@ -1355,18 +1479,24 @@
def name(self):
return self._newStoreAddressBook.name()
+
+ @inlineCallbacks
def etag(self):
- return ETag(self._newStoreAddressBook.md5())
+ returnValue(ETag((yield self._newStoreAddressBook.md5())))
+
def lastModified(self):
return self._newStoreAddressBook.modified()
+
def creationDate(self):
return self._newStoreAddressBook.created()
+
def getSyncToken(self):
return self._newStoreAddressBook.syncToken()
+
def isCollection(self):
return True
@@ -1433,7 +1563,7 @@
errors = ResponseQueue(where, "DELETE", NO_CONTENT)
- for childname in self.listChildren():
+ for childname in (yield self.listChildren()):
childurl = joinURL(where, childname)
@@ -1456,7 +1586,7 @@
yield self.downgradeFromShare(request)
# Actually delete it.
- self._newStoreParentHome.removeAddressBookWithName(
+ yield self._newStoreParentHome.removeAddressBookWithName(
self._newStoreAddressBook.name()
)
self.__class__ = ProtoAddressBookCollectionResource
@@ -1497,7 +1627,7 @@
# FIXME: should really use something other than 'fp' attribute.
basename = destination.name()
addressbook = self._newStoreAddressBook
- addressbook.rename(basename)
+ yield addressbook.rename(basename)
AddressBookCollectionResource.transform(destination, addressbook,
self._newStoreParentHome)
del self._newStoreAddressBook
@@ -1547,20 +1677,19 @@
return self.createAddressBookCollection()
+ @inlineCallbacks
def createAddressBookCollection(self):
"""
Override C{createAddressBookCollection} to actually do the work.
"""
- d = succeed(CREATED)
-
- self._newStoreParentHome.createAddressBookWithName(self._name)
- newStoreAddressBook = self._newStoreParentHome.addressbookWithName(
+ yield self._newStoreParentHome.createAddressBookWithName(self._name)
+ newStoreAddressBook = yield self._newStoreParentHome.addressbookWithName(
self._name
)
AddressBookCollectionResource.transform(
self, newStoreAddressBook, self._newStoreParentHome
)
- return d
+ returnValue(CREATED)
def exists(self):
@@ -1623,9 +1752,10 @@
return True
+ @inlineCallbacks
def quotaSize(self, request):
# FIXME: tests
- return succeed(len(self._newStoreObject.vCardText()))
+ returnValue(len((yield self._newStoreObject.vCardText())))
def vCardText(self, ignored=None):
@@ -1637,13 +1767,15 @@
return self.vCardText()
+ @inlineCallbacks
def render(self, request):
- output = self.vCardText()
+ output = yield self.vCardText()
response = Response(200, {}, output)
response.headers.setHeader("content-type", self.contentType())
- return response
+ returnValue(response)
+
@requiresPermissions(fromParent=[davxml.Unbind()])
def http_DELETE(self, request):
"""
@@ -1658,7 +1790,7 @@
component = VCard.fromString(
(yield allDataFromStream(stream))
)
- self._newStoreObject.setComponent(component)
+ yield self._newStoreObject.setComponent(component)
returnValue(NO_CONTENT)
@@ -1681,7 +1813,9 @@
# Do delete
# FIXME: public attribute please
- storeAddressBook.removeAddressBookObjectWithName(self._newStoreObject.name())
+ yield storeAddressBook.removeAddressBookObjectWithName(
+ self._newStoreObject.name()
+ )
# FIXME: clean this up with a 'transform' method
self._newStoreParentAddressBook = storeAddressBook
@@ -1721,16 +1855,22 @@
self._newStoreParentAddressBook = parentAddressBook
self._name = name
+
@inlineCallbacks
def storeStream(self, stream):
# FIXME: direct tests
component = VCard.fromString(
(yield allDataFromStream(stream))
)
- self._newStoreParentAddressBook.createAddressBookObjectWithName(
+ yield self._newStoreParentAddressBook.createAddressBookObjectWithName(
self.name(), component
)
- AddressBookObjectResource.transform(self, self._newStoreParentAddressBook.addressbookObjectWithName(self.name()))
+ AddressBookObjectResource.transform(
+ self,
+ (yield self._newStoreParentAddressBook.addressbookObjectWithName(
+ self.name())
+ )
+ )
returnValue(CREATED)
@@ -1806,12 +1946,15 @@
self._initializeWithNotifications(notifications, home)
+ @inlineCallbacks
def makeChild(self, name):
"""
- Create a L{NotificationObjectFile} or L{ProtoNotificationObjectFile} based on a
- path object.
+ Create a L{NotificationObjectFile} or L{ProtoNotificationObjectFile}
+ based on the name of a notification.
"""
- newStoreObject = self._newStoreNotifications.notificationObjectWithName(name)
+ newStoreObject = (
+ yield self._newStoreNotifications.notificationObjectWithName(name)
+ )
if newStoreObject is not None:
similar = StoreNotificationObjectFile(newStoreObject, self)
@@ -1824,18 +1967,19 @@
# FIXME: tests should be failing without this line.
# Specifically, http_PUT won't be committing its transaction properly.
self.propagateTransaction(similar)
- return similar
+ returnValue(similar)
+
+ @inlineCallbacks
def listChildren(self):
"""
@return: a sequence of the names of all known children of this resource.
"""
children = set(self.putChildren.keys())
children.update(self._newStoreNotifications.listNotificationObjects())
- return children
+ returnValue(children)
-
def quotaSize(self, request):
# FIXME: tests, workingness
return succeed(0)
@@ -1860,30 +2004,44 @@
def name(self):
return "notification"
+ @inlineCallbacks
def listChildren(self):
l = []
- for notification in self._newStoreNotifications.notificationObjects():
+ for notification in (yield self._newStoreNotifications.notificationObjects()):
l.append(notification.name())
- return l
+ returnValue(l)
def isCollection(self):
return True
+
def getSyncToken(self):
return self._newStoreNotifications.syncToken()
+
+ @inlineCallbacks
def _indexWhatChanged(self, revision, depth):
- return self._newStoreNotifications.resourceNamesSinceToken(revision) + ([],)
+ # The newstore implementation supports this directly
+ returnValue(
+ (yield self._newStoreNotifications.resourceNamesSinceToken(revision))
+ + ([],)
+ )
+
def addNotification(self, request, uid, xmltype, xmldata):
+ return maybeDeferred(
+ self._newStoreNotifications.writeNotificationObject,
+ uid, xmltype, xmldata
+ )
- self._newStoreNotifications.writeNotificationObject(uid, xmltype, xmldata)
- return succeed(None)
def deleteNotification(self, request, record):
- self._newStoreNotifications.removeNotificationObjectWithName(record.name)
- return succeed(None)
+ return maybeDeferred(
+ self._newStoreNotifications.removeNotificationObjectWithName,
+ record.name
+ )
+
class StoreProtoNotificationCollectionResource(NotificationCollectionResource):
"""
A resource representing a notification collection which hasn't yet been created.
@@ -1982,6 +2140,7 @@
return True
+ @inlineCallbacks
def etag(self):
# FIXME: far too slow to be used for real, but I needed something to
# placate the etag computation in the case where the file doesn't exist
@@ -1989,20 +2148,21 @@
# FIXME: direct tests
try:
- md5 = self._newStoreObject.md5()
+ md5 = yield self._newStoreObject.md5()
if md5:
- return ETag(md5)
+ returnValue(ETag(md5))
else:
- return ETag(
- hashlib.new("md5", self.text()).hexdigest(),
+ returnValue(ETag(
+ hashlib.new("md5", (yield self.text())).hexdigest(),
weak=False
- )
+ ))
except NoSuchObjectResourceError:
# FIXME: a workaround for the fact that DELETE still rudely vanishes
# the calendar object out from underneath the store, and doesn't
# call storeRemove.
- return None
+ returnValue(None)
+
def contentType(self):
return self._newStoreObject.contentType()
@@ -2015,13 +2175,15 @@
def creationDate(self):
return self._newStoreObject.created()
+
def newStoreProperties(self):
return self._newStoreObject.properties()
+ @inlineCallbacks
def quotaSize(self, request):
# FIXME: tests
- return succeed(len(self._newStoreObject.xmldata()))
+ returnValue(len((yield self._newStoreObject.xmldata())))
def text(self, ignored=None):
@@ -2030,9 +2192,14 @@
@requiresPermissions(davxml.Read())
+ @inlineCallbacks
def http_GET(self, request):
- return Response(OK, {"content-type":self.contentType()}, MemoryStream(self.text()))
+ returnValue(
+ Response(OK, {"content-type":self.contentType()},
+ MemoryStream((yield self.text())))
+ )
+
@requiresPermissions(fromParent=[davxml.Unbind()])
def http_DELETE(self, request):
"""
@@ -2060,7 +2227,9 @@
# Do delete
# FIXME: public attribute please
- storeNotifications.removeNotificationObjectWithName(self._newStoreObject.name())
+ yield storeNotifications.removeNotificationObjectWithName(
+ self._newStoreObject.name()
+ )
# FIXME: clean this up with a 'transform' method
self._newStoreParentNotifications = storeNotifications
Modified: CalendarServer/trunk/twistedcaldav/test/test_addressbookmultiget.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_addressbookmultiget.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_addressbookmultiget.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -27,7 +27,11 @@
from twistedcaldav import carddavxml
from twistedcaldav import vcard
-from twistedcaldav.index import db_basename
+
+# FIXME: remove this, we should not be importing this module, we should be
+# testing the public API. See comments below about cheating.
+from txdav.carddav.datastore.index_file import db_basename
+
from twistedcaldav.config import config
from twistedcaldav.test.util import AddressBookHomeTestCase
Modified: CalendarServer/trunk/twistedcaldav/test/test_addressbookquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_addressbookquery.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_addressbookquery.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -25,8 +25,11 @@
from twext.web2.dav.util import davXMLFromStream
from twext.web2.test.test_server import SimpleRequest
+# FIXME: remove this, we should not be importing this module, we should be
+# testing the public API. See comments below about cheating.
+from txdav.carddav.datastore.index_file import db_basename
+
from twistedcaldav import carddavxml, vcard
-from twistedcaldav.index import db_basename
from twistedcaldav.config import config
from twistedcaldav.test.util import AddressBookHomeTestCase
Modified: CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -34,8 +34,7 @@
from twistedcaldav.config import config
from twistedcaldav.test.util import HomeTestCase
from twisted.internet.defer import inlineCallbacks, returnValue
-from txdav.common.datastore.test.util import buildStore
-from txdav.caldav.datastore.test.common import StubNotifierFactory
+from txdav.common.datastore.test.util import buildStore, StubNotifierFactory
@inlineCallbacks
Modified: CalendarServer/trunk/twistedcaldav/test/test_extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_extensions.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_extensions.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -21,11 +21,11 @@
from twext.web2.http_headers import MimeType
from twext.web2.static import MetaDataMixin
-from twisted.internet.defer import inlineCallbacks, succeed
+from twisted.internet.defer import inlineCallbacks, Deferred, succeed
from twisted.trial.unittest import TestCase
from twisted.web.microdom import parseString
-from twistedcaldav.extensions import DAVFile
+from twistedcaldav.extensions import DAVFile, DAVResourceWithChildrenMixin
from xml.etree.cElementTree import XML
@@ -184,3 +184,30 @@
yield self.doDirectoryTest([nonASCIIFilename], addUnicodeChild,
[nonASCIIFilename.encode("utf-8")])
+
+
+class ChildTraversalTests(TestCase):
+ def test_makeChildDeferred(self):
+ """
+ If L{DAVResourceWithChildrenMixin.makeChild} returns a L{Deferred},
+ L{DAVResourceWithChildrenMixin.locateChild} will return a L{Deferred}.
+ """
+ class FakeChild(object):
+ def __init__(self, name):
+ self.name = name
+ class SmellsLikeDAVResource(object):
+ def __init__(self, **kw):
+ pass
+ class ResourceWithCheese(DAVResourceWithChildrenMixin,
+ SmellsLikeDAVResource):
+ def makeChild(self, name):
+ return succeed(FakeChild(name))
+ d = ResourceWithCheese().locateChild(None, ['cheese', 'burger'])
+ self.assertIsInstance(d, Deferred)
+ x = []
+ d.addCallback(x.append)
+ self.assertEquals(len(x), 1)
+ [result] = x
+ self.assertEquals(len(result), 2)
+ self.assertEquals(result[0].name, 'cheese')
+ self.assertEquals(result[1], ['burger'])
Deleted: CalendarServer/trunk/twistedcaldav/test/test_index.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_index.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_index.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,936 +0,0 @@
-##
-# Copyright (c) 2010 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.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.internet import reactor
-from twisted.internet.task import deferLater
-
-from twistedcaldav import caldavxml
-from twistedcaldav.caldavxml import TimeRange
-from twistedcaldav.ical import Component
-from twistedcaldav.index import Index
-from twistedcaldav.index import ReservationError, MemcachedUIDReserver
-from twistedcaldav.instance import InvalidOverriddenInstanceError
-from twistedcaldav.query import calendarqueryfilter
-from twistedcaldav.test.util import InMemoryMemcacheProtocol
-import twistedcaldav.test.util
-
-import datetime
-import os
-
-
-class MinimalResourceReplacement(object):
- """
- Provide the minimal set of attributes and methods from CalDAVFile required
- by L{Index}.
- """
-
- def __init__(self, filePath):
- self.fp = filePath
-
-
- def isCalendarCollection(self):
- return True
-
-
- def getChild(self, name):
- # FIXME: this should really return something with a child method
- return self.fp.child(name)
-
-
- def initSyncToken(self):
- pass
-
-
-
-class SQLIndexTests (twistedcaldav.test.util.TestCase):
- """
- Test abstract SQL DB class
- """
-
- def setUp(self):
- super(SQLIndexTests, self).setUp()
- self.site.resource.isCalendarCollection = lambda: True
- self.indexDirPath = self.site.resource.fp
- # FIXME: since this resource lies about isCalendarCollection, it doesn't
- # have all the associated backend machinery to actually get children.
- self.db = Index(MinimalResourceReplacement(self.indexDirPath))
-
-
- def test_reserve_uid_ok(self):
- uid = "test-test-test"
- d = self.db.isReservedUID(uid)
- d.addCallback(self.assertFalse)
- d.addCallback(lambda _: self.db.reserveUID(uid))
- d.addCallback(lambda _: self.db.isReservedUID(uid))
- d.addCallback(self.assertTrue)
- d.addCallback(lambda _: self.db.unreserveUID(uid))
- d.addCallback(lambda _: self.db.isReservedUID(uid))
- d.addCallback(self.assertFalse)
-
- return d
-
-
- def test_reserve_uid_twice(self):
- uid = "test-test-test"
- d = self.db.reserveUID(uid)
- d.addCallback(lambda _: self.db.isReservedUID(uid))
- d.addCallback(self.assertTrue)
- d.addCallback(lambda _:
- self.assertFailure(self.db.reserveUID(uid),
- ReservationError))
- return d
-
-
- def test_unreserve_unreserved(self):
- uid = "test-test-test"
- return self.assertFailure(self.db.unreserveUID(uid),
- ReservationError)
-
-
- def test_reserve_uid_timeout(self):
- # WARNING: This test is fundamentally flawed and will fail
- # intermittently because it uses the real clock.
- uid = "test-test-test"
- from twistedcaldav.config import config
- old_timeout = config.UIDReservationTimeOut
- config.UIDReservationTimeOut = 1
-
- def _finally():
- config.UIDReservationTimeOut = old_timeout
-
- d = self.db.isReservedUID(uid)
- d.addCallback(self.assertFalse)
- d.addCallback(lambda _: self.db.reserveUID(uid))
- d.addCallback(lambda _: self.db.isReservedUID(uid))
- d.addCallback(self.assertTrue)
- d.addCallback(lambda _: deferLater(reactor, 2, lambda: None))
- d.addCallback(lambda _: self.db.isReservedUID(uid))
- d.addCallback(self.assertFalse)
- self.addCleanup(_finally)
-
- return d
-
-
- def test_index(self):
- data = (
- (
- "#1.1 Simple component",
- "1.1",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
- False,
- True,
- ),
- (
- "#2.1 Recurring component",
- "2.1",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=WEEKLY;COUNT=2
-END:VEVENT
-END:VCALENDAR
-""",
- False,
- True,
- ),
- (
- "#2.2 Recurring component with override",
- "2.2",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.2
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=WEEKLY;COUNT=2
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-2.2
-RECURRENCE-ID:20080608T120000Z
-DTSTART:20080608T120000Z
-DTEND:20080608T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
- False,
- True,
- ),
- (
- "#2.3 Recurring component with broken override - new",
- "2.3",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.3
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=WEEKLY;COUNT=2
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-2.3
-RECURRENCE-ID:20080609T120000Z
-DTSTART:20080608T120000Z
-DTEND:20080608T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
- False,
- False,
- ),
- (
- "#2.4 Recurring component with broken override - existing",
- "2.4",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.4
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=WEEKLY;COUNT=2
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-2.4
-RECURRENCE-ID:20080609T120000Z
-DTSTART:20080608T120000Z
-DTEND:20080608T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
- True,
- True,
- ),
- )
-
- for description, name, calendar_txt, reCreate, ok in data:
- 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, reCreate=reCreate)
- self.assertTrue(self.db.resourceExists(name), msg=description)
- else:
- self.assertRaises(InvalidOverriddenInstanceError, self.db.addResource, name, calendar)
- self.assertFalse(self.db.resourceExists(name), msg=description)
-
- self.db._db_recreate()
- for description, name, calendar_txt, reCreate, ok in data:
- if ok:
- self.assertTrue(self.db.resourceExists(name), msg=description)
- else:
- self.assertFalse(self.db.resourceExists(name), msg=description)
-
- self.db.testAndUpdateIndex(datetime.date(2020, 1, 1))
- for description, name, calendar_txt, reCreate, ok in data:
- if ok:
- self.assertTrue(self.db.resourceExists(name), msg=description)
- else:
- self.assertFalse(self.db.resourceExists(name), msg=description)
-
- def test_index_timespan(self):
- data = (
- (
- "#1.1 Simple component - busy",
- "1.1",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
- "20080601T000000Z", "20080602T000000Z",
- "mailto:user1 at example.com",
- (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
- ),
- (
- "#1.2 Simple component - transparent",
- "1.2",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.2
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-TRANSP:TRANSPARENT
-END:VEVENT
-END:VCALENDAR
-""",
- "20080602T000000Z", "20080603T000000Z",
- "mailto:user1 at example.com",
- (('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),),
- ),
- (
- "#1.3 Simple component - canceled",
- "1.3",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.3
-DTSTART:20080603T120000Z
-DTEND:20080603T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-STATUS:CANCELLED
-END:VEVENT
-END:VCALENDAR
-""",
- "20080603T000000Z", "20080604T000000Z",
- "mailto:user1 at example.com",
- (('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'F', 'F'),),
- ),
- (
- "#1.4 Simple component - tentative",
- "1.4",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.4
-DTSTART:20080604T120000Z
-DTEND:20080604T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-STATUS:TENTATIVE
-END:VEVENT
-END:VCALENDAR
-""",
- "20080604T000000Z", "20080605T000000Z",
- "mailto:user1 at example.com",
- (('N', "2008-06-04 12:00:00+00:00", "2008-06-04 13:00:00+00:00", 'T', 'F'),),
- ),
- (
- "#2.1 Recurring component - busy",
- "2.1",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.1
-DTSTART:20080605T120000Z
-DTEND:20080605T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=DAILY;COUNT=2
-END:VEVENT
-END:VCALENDAR
-""",
- "20080605T000000Z", "20080607T000000Z",
- "mailto:user1 at example.com",
- (
- ('N', "2008-06-05 12:00:00+00:00", "2008-06-05 13:00:00+00:00", 'B', 'F'),
- ('N', "2008-06-06 12:00:00+00:00", "2008-06-06 13:00:00+00:00", 'B', 'F'),
- ),
- ),
- (
- "#2.2 Recurring component - busy",
- "2.2",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.2
-DTSTART:20080607T120000Z
-DTEND:20080607T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=DAILY;COUNT=2
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-2.2
-RECURRENCE-ID:20080608T120000Z
-DTSTART:20080608T140000Z
-DTEND:20080608T150000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-TRANSP:TRANSPARENT
-END:VEVENT
-END:VCALENDAR
-""",
- "20080607T000000Z", "20080609T000000Z",
- "mailto:user1 at example.com",
- (
- ('N', "2008-06-07 12:00:00+00:00", "2008-06-07 13:00:00+00:00", 'B', 'F'),
- ('N', "2008-06-08 14:00:00+00:00", "2008-06-08 15:00:00+00:00", 'B', 'T'),
- ),
- ),
- )
-
- for description, name, calendar_txt, trstart, trend, organizer, instances in data:
- 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)
- self.assertTrue(self.db.resourceExists(name), msg=description)
-
- # Create fake filter element to match time-range
- filter = caldavxml.Filter(
- caldavxml.ComponentFilter(
- caldavxml.ComponentFilter(
- TimeRange(
- start=trstart,
- end=trend,
- ),
- name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
- ),
- name="VCALENDAR",
- )
- )
- filter = calendarqueryfilter.Filter(filter)
-
- resources = self.db.indexedSearch(filter, fbtype=True)
- index_results = set()
- for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
- self.assertEqual(test_organizer, organizer, msg=description)
- index_results.add((float, start, end, fbtype, transp,))
-
- self.assertEqual(set(instances), index_results, msg=description)
-
- def test_index_timespan_per_user(self):
- data = (
- (
- "#1.1 Single per-user non-recurring component",
- "1.1",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.1
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""",
- "20080601T000000Z", "20080602T000000Z",
- "mailto:user1 at example.com",
- (
- (
- "user01",
- (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),),
- ),
- (
- "user02",
- (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
- ),
- ),
- ),
- (
- "#1.2 Two per-user non-recurring component",
- "1.2",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.2
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.2
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.2
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""",
- "20080601T000000Z", "20080602T000000Z",
- "mailto:user1 at example.com",
- (
- (
- "user01",
- (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),),
- ),
- (
- "user02",
- (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
- ),
- (
- "user03",
- (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
- ),
- ),
- ),
- (
- "#2.1 Single per-user simple recurring component",
- "2.1",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.1
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""",
- "20080601T000000Z", "20080603T000000Z",
- "mailto:user1 at example.com",
- (
- (
- "user01",
- (
- ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
- ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),
- ),
- ),
- (
- "user02",
- (
- ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
- ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
- ),
- ),
- ),
- ),
- (
- "#2.2 Two per-user simple recurring component",
- "2.2",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.2
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.2
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.2
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""",
- "20080601T000000Z", "20080603T000000Z",
- "mailto:user1 at example.com",
- (
- (
- "user01",
- (
- ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
- ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),
- ),
- ),
- (
- "user02",
- (
- ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
- ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
- ),
- ),
- (
- "user03",
- (
- ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
- ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
- ),
- ),
- ),
- ),
- (
- "#3.1 Single per-user complex recurring component",
- "3.1",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-1.1
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.1
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""",
- "20080601T000000Z", "20080604T000000Z",
- "mailto:user1 at example.com",
- (
- (
- "user01",
- (
- ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
- ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
- ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
- ),
- ),
- (
- "user02",
- (
- ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
- ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
- ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'F'),
- ),
- ),
- ),
- ),
- (
- "#3.2 Two per-user complex recurring component",
- "3.2",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.2
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-1.2
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.2
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.2
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080603T120000Z
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""",
- "20080601T000000Z", "20080604T000000Z",
- "mailto:user1 at example.com",
- (
- (
- "user01",
- (
- ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
- ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
- ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
- ),
- ),
- (
- "user02",
- (
- ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
- ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
- ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
- ),
- ),
- (
- "user03",
- (
- ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
- ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
- ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'F'),
- ),
- ),
- ),
- ),
- )
-
- for description, name, calendar_txt, trstart, trend, organizer, peruserinstances in data:
- 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)
- self.assertTrue(self.db.resourceExists(name), msg=description)
-
- # Create fake filter element to match time-range
- filter = caldavxml.Filter(
- caldavxml.ComponentFilter(
- caldavxml.ComponentFilter(
- TimeRange(
- start=trstart,
- end=trend,
- ),
- name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
- ),
- name="VCALENDAR",
- )
- )
- filter = calendarqueryfilter.Filter(filter)
-
- for useruid, instances in peruserinstances:
- resources = self.db.indexedSearch(filter, useruid=useruid, fbtype=True)
- index_results = set()
- for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
- self.assertEqual(test_organizer, organizer, msg=description)
- index_results.add((str(float), str(start), str(end), str(fbtype), str(transp),))
-
- self.assertEqual(set(instances), index_results, msg="%s, user:%s" % (description, useruid,))
-
- self.db.deleteResource(name)
-
- def test_index_revisions(self):
- data1 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-"""
- data2 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=WEEKLY;COUNT=2
-END:VEVENT
-END:VCALENDAR
-"""
- data3 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.3
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=WEEKLY;COUNT=2
-END:VEVENT
-END:VCALENDAR
-"""
-
- calendar = Component.fromString(data1)
- self.db.addResource("data1.ics", calendar)
- calendar = Component.fromString(data2)
- self.db.addResource("data2.ics", calendar)
- calendar = Component.fromString(data3)
- self.db.addResource("data3.ics", calendar)
- self.db.deleteResource("data3.ics")
-
- tests = (
- (0, (["data1.ics", "data2.ics",], [],)),
- (1, (["data2.ics",], ["data3.ics",],)),
- (2, ([], ["data3.ics",],)),
- (3, ([], ["data3.ics",],)),
- (4, ([], [],)),
- (5, ([], [],)),
- )
-
- for revision, results in tests:
- for depth in ("1", "infinity"):
- self.assertEquals(self.db.whatchanged(revision, depth), results, "Mismatched results for whatchanged with revision %d" % (revision,))
-
-class MemcacheTests(SQLIndexTests):
- def setUp(self):
- super(MemcacheTests, self).setUp()
- self.memcache = InMemoryMemcacheProtocol()
- self.db.reserver = MemcachedUIDReserver(self.db, self.memcache)
-
-
- def tearDown(self):
- for _ignore_k, v in self.memcache._timeouts.iteritems():
- if v.active():
- v.cancel()
Modified: CalendarServer/trunk/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sharing.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_sharing.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -25,8 +25,7 @@
from twistedcaldav.config import config
from twistedcaldav.test.util import HomeTestCase, norequest
from twistedcaldav.resource import CalDAVResource
-from txdav.common.datastore.test.util import buildStore
-from txdav.caldav.datastore.test.common import StubNotifierFactory
+from txdav.common.datastore.test.util import buildStore, StubNotifierFactory
sharedOwnerType = davxml.ResourceType.sharedownercalendar #@UndefinedVariable
regularCalendarType = davxml.ResourceType.calendar #@UndefinedVariable
Modified: CalendarServer/trunk/twistedcaldav/test/test_sql.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sql.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_sql.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -17,7 +17,7 @@
from twistedcaldav.sql import AbstractSQLDatabase
import twistedcaldav.test.util
-from twistedcaldav.sql import db_prefix
+
from threading import Thread
import time
import os
Deleted: CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,210 +0,0 @@
-##
-# Copyright (c) 2010 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.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.internet import reactor
-from twisted.internet.task import deferLater
-
-from twistedcaldav.test.util import InMemoryMemcacheProtocol
-from twistedcaldav.vcard import Component
-from twistedcaldav.vcardindex import AddressBookIndex, MemcachedUIDReserver, ReservationError
-import twistedcaldav.test.util
-
-import os
-
-class MinimalResourceReplacement(object):
- """
- Provide the minimal set of attributes and methods from CalDAVFile required
- by L{Index}.
- """
-
- def __init__(self, filePath):
- self.fp = filePath
-
-
- def isAddressBookCollection(self):
- return True
-
-
- def getChild(self, name):
- # FIXME: this should really return something with a child method
- return self.fp.child(name)
-
-
- def initSyncToken(self):
- pass
-
-
-
-class SQLIndexTests (twistedcaldav.test.util.TestCase):
- """
- Test abstract SQL DB class
- """
-
- def setUp(self):
- super(SQLIndexTests, self).setUp()
- self.site.resource.isAddressBookCollection = lambda: True
- self.indexDirPath = self.site.resource.fp
- # FIXME: since this resource lies about isCalendarCollection, it doesn't
- # have all the associated backend machinery to actually get children.
- self.db = AddressBookIndex(MinimalResourceReplacement(self.indexDirPath))
-
-
- def test_reserve_uid_ok(self):
- uid = "test-test-test"
- d = self.db.isReservedUID(uid)
- d.addCallback(self.assertFalse)
- d.addCallback(lambda _: self.db.reserveUID(uid))
- d.addCallback(lambda _: self.db.isReservedUID(uid))
- d.addCallback(self.assertTrue)
- d.addCallback(lambda _: self.db.unreserveUID(uid))
- d.addCallback(lambda _: self.db.isReservedUID(uid))
- d.addCallback(self.assertFalse)
-
- return d
-
-
- def test_reserve_uid_twice(self):
- uid = "test-test-test"
- d = self.db.reserveUID(uid)
- d.addCallback(lambda _: self.db.isReservedUID(uid))
- d.addCallback(self.assertTrue)
- d.addCallback(lambda _:
- self.assertFailure(self.db.reserveUID(uid),
- ReservationError))
- return d
-
-
- def test_unreserve_unreserved(self):
- uid = "test-test-test"
- return self.assertFailure(self.db.unreserveUID(uid),
- ReservationError)
-
-
- def test_reserve_uid_timeout(self):
- # WARNING: This test is fundamentally flawed and will fail
- # intermittently because it uses the real clock.
- uid = "test-test-test"
- from twistedcaldav.config import config
- old_timeout = config.UIDReservationTimeOut
- config.UIDReservationTimeOut = 1
-
- def _finally():
- config.UIDReservationTimeOut = old_timeout
-
- d = self.db.isReservedUID(uid)
- d.addCallback(self.assertFalse)
- d.addCallback(lambda _: self.db.reserveUID(uid))
- d.addCallback(lambda _: self.db.isReservedUID(uid))
- d.addCallback(self.assertTrue)
- d.addCallback(lambda _: deferLater(reactor, 2, lambda: None))
- d.addCallback(lambda _: self.db.isReservedUID(uid))
- d.addCallback(self.assertFalse)
- self.addCleanup(_finally)
-
- return d
-
-
- def test_index(self):
- data = (
- (
- "#1.1 Simple component",
- "1.1",
- """BEGIN:VCARD
-VERSION:3.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-UID:12345-67890-1.1
-FN:Cyrus Daboo
-N:Daboo;Cyrus
-EMAIL;TYPE=INTERNET,PREF:cyrus at example.com
-END:VCARD
-""",
- ),
- )
-
- for description, name, vcard_txt in data:
- 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)
- self.assertTrue(self.db.resourceExists(name), msg=description)
-
- self.db._db_recreate()
- for description, name, vcard_txt in data:
- self.assertTrue(self.db.resourceExists(name), msg=description)
-
- def test_index_revisions(self):
- data1 = """BEGIN:VCARD
-VERSION:3.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-UID:12345-67890-1-1.1
-FN:Cyrus Daboo
-N:Daboo;Cyrus
-EMAIL;TYPE=INTERNET,PREF:cyrus at example.com
-END:VCARD
-"""
- data2 = """BEGIN:VCARD
-VERSION:3.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-UID:12345-67890-2-1.1
-FN:Wilfredo Sanchez
-N:Sanchez;Wilfredo
-EMAIL;TYPE=INTERNET,PREF:wsanchez at example.com
-END:VCARD
-"""
- data3 = """BEGIN:VCARD
-VERSION:3.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-UID:12345-67890-3-1.1
-FN:Bruce Gaya
-N:Gaya;Bruce
-EMAIL;TYPE=INTERNET,PREF:bruce at example.com
-END:VCARD
-"""
-
- vcard = Component.fromString(data1)
- self.db.addResource("data1.vcf", vcard)
- vcard = Component.fromString(data2)
- self.db.addResource("data2.vcf", vcard)
- vcard = Component.fromString(data3)
- self.db.addResource("data3.vcf", vcard)
- self.db.deleteResource("data3.vcf")
-
- tests = (
- (0, (["data1.vcf", "data2.vcf",], [],)),
- (1, (["data2.vcf",], ["data3.vcf",],)),
- (2, ([], ["data3.vcf",],)),
- (3, ([], ["data3.vcf",],)),
- (4, ([], [],)),
- (5, ([], [],)),
- )
-
- for revision, results in tests:
- for depth in ("1", "infinity"):
- self.assertEquals(self.db.whatchanged(revision, depth), results, "Mismatched results for whatchanged with revision %d" % (revision,))
-
-class MemcacheTests(SQLIndexTests):
- def setUp(self):
- super(MemcacheTests, self).setUp()
- self.memcache = InMemoryMemcacheProtocol()
- self.db.reserver = MemcachedUIDReserver(self.db, self.memcache)
-
-
- def tearDown(self):
- for _ignore_k, v in self.memcache._timeouts.iteritems():
- if v.active():
- v.cancel()
Modified: CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_wrapping.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_wrapping.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -41,15 +41,18 @@
from txdav.carddav.datastore.test.test_file import vcard4_text
-from txdav.common.datastore.test.util import buildStore
-from txdav.caldav.datastore.test.common import StubNotifierFactory, \
- assertProvides
+from txdav.common.datastore.test.util import buildStore, assertProvides,\
+ StubNotifierFactory
+
+
from txdav.caldav.icalendarstore import ICalendarHome
from txdav.carddav.iaddressbookstore import IAddressBookHome
class FakeChanRequest(object):
+ code = 'request-not-finished'
+
def writeHeaders(self, code, headers):
self.code = code
self.headers = headers
@@ -89,6 +92,7 @@
self.setupCalendars()
+ @inlineCallbacks
def populateOneObject(self, objectName, objectText):
"""
Populate one calendar object in the test user's calendar.
@@ -107,12 +111,13 @@
except:
pass
txn = self.calendarCollection._newStore.newTransaction()
- home = txn.calendarHomeWithUID(uid, True)
- cal = home.calendarWithName("calendar")
+ home = yield txn.calendarHomeWithUID(uid, True)
+ cal = yield home.calendarWithName("calendar")
cal.createCalendarObjectWithName(objectName, VComponent.fromString(objectText))
- txn.commit()
+ yield txn.commit()
+ @inlineCallbacks
def populateOneAddressBookObject(self, objectName, objectText):
"""
Populate one addressbook object in the test user's addressbook.
@@ -131,13 +136,13 @@
except:
pass
txn = self.addressbookCollection._newStore.newTransaction()
- home = txn.addressbookHomeWithUID(uid, True)
- adbk = home.addressbookWithName("addressbook")
+ home = yield txn.addressbookHomeWithUID(uid, True)
+ adbk = yield home.addressbookWithName("addressbook")
if adbk is None:
- home.createAddressBookWithName("addressbook")
- adbk = home.addressbookWithName("addressbook")
+ yield home.createAddressBookWithName("addressbook")
+ adbk = yield home.addressbookWithName("addressbook")
adbk.createAddressBookObjectWithName(objectName, VCComponent.fromString(objectText))
- txn.commit()
+ yield txn.commit()
requestUnderTest = None
@@ -169,7 +174,7 @@
an associated transaction. Commit that transaction to bring the
filesystem into a consistent state.
"""
- self.requestUnderTest._newStoreTransaction.commit()
+ return self.requestUnderTest._newStoreTransaction.commit()
def requestForPath(self, path):
@@ -241,7 +246,7 @@
L{CalendarHome} via C{newTransaction().calendarHomeWithUID}.
"""
calDavFile = yield self.getResource("calendars/users/wsanchez/")
- self.commit()
+ yield self.commit()
assertProvides(self, ICalendarHome, calDavFile._newStoreHome)
@@ -256,7 +261,7 @@
dropBoxResource = yield self.getResource(
"calendars/users/wsanchez/dropbox"
)
- self.commit()
+ yield self.commit()
self.assertIsInstance(dropBoxResource, DropboxCollection)
dropboxHomeType = davxml.ResourceType.dropboxhome #@UndefinedVariable
self.assertEquals(dropBoxResource.resourceType(),
@@ -275,7 +280,7 @@
regularCalendarType = davxml.ResourceType.calendar #@UndefinedVariable
self.assertEquals(calDavFile.resourceType(),
regularCalendarType)
- self.commit()
+ yield self.commit()
@inlineCallbacks
@@ -290,7 +295,7 @@
calDavFile = yield self.getResource("calendars/users/wsanchez/frobozz")
self.assertIsInstance(calDavFile, ProtoCalendarCollectionResource)
calDavFile.createCalendarCollection()
- self.commit()
+ yield self.commit()
@inlineCallbacks
@@ -308,7 +313,7 @@
self.assertIdentical(
getattr(calDavFile, "_newStoreCalendar", None), None
)
- self.commit()
+ yield self.commit()
@inlineCallbacks
@@ -336,17 +341,21 @@
@inlineCallbacks
def test_lookupCalendarObject(self):
"""
- When a L{CalDAVResource} representing an existing calendar object is looked
- up on a L{CalDAVResource} representing a calendar collection, a parallel
- L{CalendarObject} will be created (with a matching FilePath).
+ When a L{CalDAVResource} representing an existing calendar object is
+ looked up on a L{CalDAVResource} representing a calendar collection, a
+ parallel L{CalendarObject} will be created. Its principal collections
+ and transaction should match.
"""
- self.populateOneObject("1.ics", event4_text)
+ yield self.populateOneObject("1.ics", event4_text)
+ calendarHome = yield self.getResource("calendars/users/wsanchez")
calDavFileCalendar = yield self.getResource(
"calendars/users/wsanchez/calendar/1.ics"
)
- self.commit()
+ yield self.commit()
self.assertEquals(calDavFileCalendar._principalCollections,
frozenset([self.principalsResource]))
+ self.assertEquals(calDavFileCalendar._associatedTransaction,
+ calendarHome._associatedTransaction)
@inlineCallbacks
@@ -359,7 +368,7 @@
calDavFileCalendar = yield self.getResource(
"calendars/users/wsanchez/calendar/xyzzy.ics"
)
- self.commit()
+ yield self.commit()
self.assertEquals(calDavFileCalendar._principalCollections,
frozenset([self.principalsResource]))
@@ -380,7 +389,7 @@
via C{newTransaction().addressbookHomeWithUID}.
"""
calDavFile = yield self.getResource("addressbooks/users/wsanchez/")
- self.commit()
+ yield self.commit()
assertProvides(self, IAddressBookHome, calDavFile._newStoreHome)
@@ -392,7 +401,7 @@
create a corresponding L{AddressBook} via C{AddressBookHome.addressbookWithName}.
"""
calDavFile = yield self.getResource("addressbooks/users/wsanchez/addressbook")
- self.commit()
+ yield self.commit()
self.assertEquals(calDavFile._principalCollections,
frozenset([self.principalsResource]))
@@ -409,7 +418,7 @@
calDavFile = yield self.getResource("addressbooks/users/wsanchez/frobozz")
self.assertIsInstance(calDavFile, ProtoAddressBookCollectionResource)
calDavFile.createAddressBookCollection()
- self.commit()
+ yield self.commit()
self.assertEquals(calDavFile._principalCollections,
frozenset([self.principalsResource]))
@@ -421,11 +430,11 @@
up on a L{CalDAVResource} representing a addressbook collection, a parallel
L{AddressBookObject} will be created (with a matching FilePath).
"""
- self.populateOneAddressBookObject("1.vcf", vcard4_text)
+ yield self.populateOneAddressBookObject("1.vcf", vcard4_text)
calDavFileAddressBook = yield self.getResource(
"addressbooks/users/wsanchez/addressbook/1.vcf"
)
- self.commit()
+ yield self.commit()
self.assertEquals(calDavFileAddressBook._principalCollections,
frozenset([self.principalsResource]))
@@ -440,7 +449,7 @@
calDavFileAddressBook = yield self.getResource(
"addressbooks/users/wsanchez/addressbook/xyzzy.ics"
)
- self.commit()
+ yield self.commit()
self.assertEquals(calDavFileAddressBook._principalCollections,
frozenset([self.principalsResource]))
Modified: CalendarServer/trunk/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/util.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/util.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -352,7 +352,7 @@
"""
if not self.committed:
self.committed = True
- self.site.resource._associatedTransaction.commit()
+ return self.site.resource._associatedTransaction.commit()
@inlineCallbacks
@@ -361,10 +361,10 @@
Refresh the user resource positioned at the root of this site, to give
it a new transaction.
"""
- self.noRenderCommit()
+ yield self.noRenderCommit()
if request is None:
request = norequest()
- users = self.homeProvisioner.getChild("users")
+ users = yield self.homeProvisioner.getChild("users")
user, ignored = (yield users.locateChild(request, ["wsanchez"]))
@@ -387,7 +387,7 @@
Override C{send} in order to refresh the 'user' resource each time, to
get a new transaction to associate with the calendar home.
"""
- self.noRenderCommit()
+ yield self.noRenderCommit()
yield self._refreshRoot(request)
result = (yield super(HomeTestCase, self).send(request))
self.committed = True
@@ -424,12 +424,13 @@
self.directoryService, "/addressbooks/",
_newStore
)
-
+
+ @inlineCallbacks
def _defer(user):
# Commit the transaction
- self.site.resource._associatedTransaction.commit()
+ yield self.site.resource._associatedTransaction.commit()
self.docroot = user._newStoreHome._path.path
-
+
return self._refreshRoot().addCallback(_defer)
@inlineCallbacks
Modified: CalendarServer/trunk/twistedcaldav/upgrade.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/upgrade.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/upgrade.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -35,7 +35,7 @@
from twistedcaldav.ical import Component
from twistedcaldav import caldavxml
-from twisted.internet.defer import inlineCallbacks, succeed, returnValue
+from twisted.internet.defer import inlineCallbacks, succeed
from calendarserver.tools.util import getDirectory
from calendarserver.tools.resources import migrateResources
Deleted: CalendarServer/trunk/twistedcaldav/vcardindex.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/vcardindex.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/vcardindex.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,701 +0,0 @@
-##
-# Copyright (c) 2005-2009 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.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-CardDAV Index.
-
-This API is considered private to static.py and is therefore subject to
-change.
-"""
-
-__all__ = [
- "AddressBookIndex",
-]
-
-import datetime
-import os
-import time
-import hashlib
-
-try:
- import sqlite3 as sqlite
-except ImportError:
- from pysqlite2 import dbapi2 as sqlite
-
-from twisted.internet.defer import maybeDeferred
-
-from twistedcaldav import carddavxml
-from twistedcaldav.index import SyncTokenValidException
-from twistedcaldav.query import addressbookquery
-from twistedcaldav.sql import AbstractSQLDatabase
-from twistedcaldav.sql import db_prefix
-from twistedcaldav.vcard import Component
-
-from twext.python.log import Logger, LoggingMixIn
-from twistedcaldav.config import config
-from twistedcaldav.memcachepool import CachePoolUserMixIn
-
-log = Logger()
-
-db_basename = db_prefix + "sqlite"
-schema_version = "2"
-
-class ReservationError(LookupError):
- """
- Attempt to reserve a UID which is already reserved or to unreverse a UID
- which is not reserved.
- """
-
-def wrapInDeferred(f):
- def _(*args, **kwargs):
- return maybeDeferred(f, *args, **kwargs)
-
- return _
-
-
-class MemcachedUIDReserver(CachePoolUserMixIn, LoggingMixIn):
- def __init__(self, index, cachePool=None):
- self.index = index
- self._cachePool = cachePool
-
- def _key(self, uid):
- return 'reservation:%s' % (
- hashlib.md5('%s:%s' % (uid,
- self.index.resource.fp.path)).hexdigest())
-
- def reserveUID(self, uid):
- uid = uid.encode('utf-8')
- self.log_debug("Reserving UID %r @ %r" % (
- uid,
- self.index.resource.fp.path))
-
- def _handleFalse(result):
- if result is False:
- raise ReservationError(
- "UID %s already reserved for address book collection %s."
- % (uid, self.index.resource)
- )
-
- d = self.getCachePool().add(self._key(uid),
- 'reserved',
- expireTime=config.UIDReservationTimeOut)
- d.addCallback(_handleFalse)
- return d
-
-
- def unreserveUID(self, uid):
- uid = uid.encode('utf-8')
- self.log_debug("Unreserving UID %r @ %r" % (
- uid,
- self.index.resource.fp.path))
-
- def _handleFalse(result):
- if result is False:
- raise ReservationError(
- "UID %s is not reserved for address book collection %s."
- % (uid, self.index.resource)
- )
-
- d =self.getCachePool().delete(self._key(uid))
- d.addCallback(_handleFalse)
- return d
-
-
- def isReservedUID(self, uid):
- uid = uid.encode('utf-8')
- self.log_debug("Is reserved UID %r @ %r" % (
- uid,
- self.index.resource.fp.path))
-
- def _checkValue((flags, value)):
- if value is None:
- return False
- else:
- return True
-
- d = self.getCachePool().get(self._key(uid))
- d.addCallback(_checkValue)
- return d
-
-
-
-class SQLUIDReserver(object):
- def __init__(self, index):
- self.index = index
-
- @wrapInDeferred
- def reserveUID(self, uid):
- """
- Reserve a UID for this index's resource.
- @param uid: the UID to reserve
- @raise ReservationError: if C{uid} is already reserved
- """
-
- try:
- self.index._db_execute("insert into RESERVED (UID, TIME) values (:1, :2)", uid, datetime.datetime.now())
- self.index._db_commit()
- except sqlite.IntegrityError:
- self.index._db_rollback()
- raise ReservationError(
- "UID %s already reserved for address book collection %s."
- % (uid, self.index.resource)
- )
- except sqlite.Error, e:
- log.err("Unable to reserve UID: %s", (e,))
- self.index._db_rollback()
- raise
-
- def unreserveUID(self, uid):
- """
- Unreserve a UID for this index's resource.
- @param uid: the UID to reserve
- @raise ReservationError: if C{uid} is not reserved
- """
-
- def _cb(result):
- if result == False:
- raise ReservationError(
- "UID %s is not reserved for address book collection %s."
- % (uid, self.index.resource)
- )
- else:
- try:
- self.index._db_execute(
- "delete from RESERVED where UID = :1", uid)
- self.index._db_commit()
- except sqlite.Error, e:
- log.err("Unable to unreserve UID: %s", (e,))
- self.index._db_rollback()
- raise
-
- d = self.isReservedUID(uid)
- d.addCallback(_cb)
- return d
-
-
- @wrapInDeferred
- def isReservedUID(self, uid):
- """
- Check to see whether a UID is reserved.
- @param uid: the UID to check
- @return: True if C{uid} is reserved, False otherwise.
- """
-
- rowiter = self.index._db_execute("select UID, TIME from RESERVED where UID = :1", uid)
- for uid, attime in rowiter:
- # Double check that the time is within a reasonable period of now
- # otherwise we probably have a stale reservation
- tm = time.strptime(attime[:19], "%Y-%m-%d %H:%M:%S")
- dt = datetime.datetime(year=tm.tm_year, month=tm.tm_mon, day=tm.tm_mday, hour=tm.tm_hour, minute=tm.tm_min, second = tm.tm_sec)
- if datetime.datetime.now() - dt > datetime.timedelta(seconds=config.UIDReservationTimeOut):
- try:
- self.index._db_execute("delete from RESERVED where UID = :1", uid)
- self.index._db_commit()
- except sqlite.Error, e:
- log.err("Unable to unreserve UID: %s", (e,))
- self.index._db_rollback()
- raise
- return False
- else:
- return True
-
- return False
-
-class AddressBookIndex(AbstractSQLDatabase):
- """
- AddressBook collection index abstract base class that defines the apis for the index.
- """
-
- def __init__(self, resource):
- """
- @param resource: the L{CalDAVResource} resource to
- index. C{resource} must be an addressbook collection (ie.
- C{resource.isAddressBookCollection()} returns C{True}.)
- """
- assert resource.isAddressBookCollection(), "non-addressbook collection resource %s has no index." % (resource,)
- self.resource = resource
- db_filename = os.path.join(self.resource.fp.path, db_basename)
- super(AddressBookIndex, self).__init__(db_filename, False)
-
- if (
- hasattr(config, "Memcached") and
- config.Memcached.Pools.Default.ClientEnabled
- ):
- self.reserver = MemcachedUIDReserver(self)
- else:
- self.reserver = SQLUIDReserver(self)
-
- def create(self):
- """
- Create the index and initialize it.
- """
- self._db()
-
- def recreate(self):
- """
- Delete the database and re-create it
- """
- try:
- os.remove(self.dbpath)
- except OSError:
- pass
- self.create()
-
- #
- # A dict of sets. The dict keys are address book collection paths,
- # and the sets contains reserved UIDs for each path.
- #
-
- def reserveUID(self, uid):
- return self.reserver.reserveUID(uid)
-
- def unreserveUID(self, uid):
- return self.reserver.unreserveUID(uid)
-
- def isReservedUID(self, uid):
- return self.reserver.isReservedUID(uid)
-
- def isAllowedUID(self, uid, *names):
- """
- Checks to see whether to allow an operation which would add the
- specified UID to the index. Specifically, the operation may not
- violate the constraint that UIDs must be unique.
- @param uid: the UID to check
- @param names: the names of resources being replaced or deleted by the
- operation; UIDs associated with these resources are not checked.
- @return: True if the UID is not in the index and is not reserved,
- False otherwise.
- """
- rname = self.resourceNameForUID(uid)
- return (rname is None or rname in names)
-
- def resourceNamesForUID(self, uid):
- """
- Looks up the names of the resources with the given UID.
- @param uid: the UID of the resources to look up.
- @return: a list of resource names
- """
- names = self._db_values_for_sql("select NAME from RESOURCE where UID = :1", uid)
-
- #
- # Check that each name exists as a child of self.resource. If not, the
- # resource record is stale.
- #
- resources = []
- for name in names:
- name_utf8 = name.encode("utf-8")
- 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, False)
- self._db_commit()
- else:
- resources.append(name_utf8)
-
- return resources
-
- def resourceNameForUID(self, uid):
- """
- Looks up the name of the resource with the given UID.
- @param uid: the UID of the resource to look up.
- @return: If the resource is found, its name; C{None} otherwise.
- """
- result = None
-
- for name in self.resourceNamesForUID(uid):
- assert result is None, "More than one resource with UID %s in address book collection %r" % (uid, self)
- result = name
-
- return result
-
- def resourceUIDForName(self, name):
- """
- Looks up the UID of the resource with the given name.
- @param name: the name of the resource to look up.
- @return: If the resource is found, the UID of the resource; C{None}
- otherwise.
- """
- uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
-
- return uid
-
- 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
- for the resource name. If present, then the index entries for
- that UID are removed. After that the new index entries are added.
- @param name: the name of the resource to add.
- @param vCard: a L{Component} object representing the resource
- contents.
- @param fast: if C{True} do not do commit, otherwise do commit.
- """
- oldUID = self.resourceUIDForName(name)
- if oldUID is not None:
- self._delete_from_db(name, oldUID, False)
- self._add_to_db(name, vcard)
- if not fast:
- self._db_commit()
-
- def deleteResource(self, name):
- """
- Remove this resource from the index.
- @param name: the name of the resource to add.
- @param uid: the UID of the vcard component in the resource.
- """
- uid = self.resourceUIDForName(name)
- if uid is not None:
- self._delete_from_db(name, uid)
- self._db_commit()
-
- def resourceExists(self, name):
- """
- Determines whether the specified resource name exists in the index.
- @param name: the name of the resource to test
- @return: True if the resource exists, False if not
- """
- uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
- return uid is not None
-
- def resourcesExist(self, names):
- """
- Determines whether the specified resource name exists in the index.
- @param names: a C{list} containing the names of the resources to test
- @return: a C{list} of all names that exist
- """
- statement = "select NAME from RESOURCE where NAME in ("
- for ctr, ignore_name in enumerate(names):
- if ctr != 0:
- statement += ", "
- statement += ":%s" % (ctr,)
- statement += ")"
- results = self._db_values_for_sql(statement, *names)
- return results
-
- def whatchanged(self, revision, depth):
-
- 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, wasdeleted in results:
- if name:
- if wasdeleted == 'Y':
- if revision:
- deleted.append(name)
- else:
- changed.append(name)
- else:
- raise SyncTokenValidException
-
- 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)
- else:
- qualifiers = None
-
- return qualifiers is not None
-
- def search(self, filter):
- """
- Finds resources matching the given qualifiers.
- @param filter: the L{Filter} for the addressbook-query to execute.
- @return: an interable iterable of tuples for each resource matching the
- given C{qualifiers}. The tuples are C{(name, uid, type)}, where
- C{name} is the resource name, C{uid} is the resource UID, and
- C{type} is the resource iCalendar component type.x
- """
- # FIXME: Don't forget to use maximum_future_expansion_duration when we
- # start caching...
-
- # Make sure we have a proper Filter element and get the partial SQL statement to use.
- if isinstance(filter, carddavxml.Filter):
- qualifiers = addressbookquery.sqladdressbookquery(filter)
- else:
- qualifiers = None
- if qualifiers is not None:
- rowiter = self._db_execute("select DISTINCT RESOURCE.NAME, RESOURCE.UID" + qualifiers[0], *qualifiers[1])
- else:
- rowiter = self._db_execute("select NAME, UID from RESOURCE")
-
- for row in rowiter:
- name = row[0]
- if self.resource.getChild(name.encode("utf-8")):
- yield row
- else:
- log.err("vCard resource %s is missing from %s. Removing from index."
- % (name, self.resource))
- self.deleteResource(name, None)
-
- def bruteForceSearch(self):
- """
- List the whole index and tests for existence, updating the index
- @return: all resources in the index
- """
- # List all resources
- rowiter = self._db_execute("select NAME, UID from RESOURCE")
-
- # Check result for missing resources:
-
- for row in rowiter:
- name = row[0]
- if self.resource.getChild(name.encode("utf-8")):
- yield row
- else:
- log.err("AddressBook resource %s is missing from %s. Removing from index."
- % (name, self.resource))
- self.deleteResource(name)
-
-
- def _db_version(self):
- """
- @return: the schema version assigned to this index.
- """
- return schema_version
-
- def _db_type(self):
- """
- @return: the collection type assigned to this index.
- """
- return "AddressBook"
-
- def _db_init_data_tables(self, q):
- """
- Initialise the underlying database tables.
- @param q: a database cursor to use.
- """
-
- # Create database where the RESOURCE table has unique UID column.
- self._db_init_data_tables_base(q, True)
-
- def _db_init_data_tables_base(self, q, uidunique):
- """
- Initialise the underlying database tables.
- @param q: a database cursor to use.
- """
- #
- # RESOURCE table is the primary index table
- # NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
- # UID: iCalendar UID (may or may not be unique)
- #
- q.execute(
- """
- create table RESOURCE (
- NAME text unique,
- UID text unique
- )
- """
- )
-
- #
- # REVISIONS table tracks changes
- # NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
- # REVISION: revision number
- # WASDELETED: Y if revision deleted, N if added or changed
- #
- 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,
- DELETED text(1) default "N"
- )
- """
- )
- q.execute(
- """
- create index REVISION on REVISIONS (REVISION)
- """
- )
-
- #
- # RESERVED table tracks reserved UIDs
- # UID: The UID being reserved
- # TIME: When the reservation was made
- #
- q.execute(
- """
- create table RESERVED (
- UID text unique,
- TIME date
- )
- """
- )
-
- def _db_recreate(self, do_commit=True):
- """
- Re-create the database tables from existing address book data.
- """
-
- #
- # Populate the DB with data from already existing resources.
- # This allows for index recovery if the DB file gets
- # deleted.
- #
- fp = self.resource.fp
- for name in fp.listdir():
- if name.startswith("."):
- continue
-
- try:
- stream = fp.child(name).open()
- except (IOError, OSError), e:
- log.err("Unable to open resource %s: %s" % (name, e))
- continue
-
- try:
- # FIXME: This is blocking I/O
- try:
- vcard = Component.fromStream(stream)
- vcard.validForCardDAV()
- except ValueError:
- log.err("Non-addressbook resource: %s" % (name,))
- else:
- #log.msg("Indexing resource: %s" % (name,))
- self.addResource(name, vcard, True)
- finally:
- stream.close()
-
- # Do commit outside of the loop for better performance
- if do_commit:
- self._db_commit()
-
- def _db_can_upgrade(self, old_version):
- """
- Can we do an in-place upgrade
- """
-
- # v2 is a minor change
- return True
-
- def _db_upgrade_data_tables(self, q, old_version):
- """
- Upgrade the data from an older version of the DB.
- """
-
- # When going to version 2+ all we need to do is add revision table and index
- 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,
- CREATEDREVISION integer default 0,
- WASDELETED text(1) default "N"
- )
- """
- )
- q.execute(
- """
- create index REVISION on REVISIONS (REVISION)
- """
- )
-
- self._db_execute(
- """
- insert into REVISIONS (NAME)
- select NAME from RESOURCE
- """
- )
-
-
- 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
- be associated with any given UID and vice versa.
- NB This method does not commit the changes to the db - the caller
- MUST take care of that
- @param name: the name of the resource to add.
- @param vcard: a L{AddressBook} object representing the resource
- contents.
- """
- uid = vcard.resourceUID()
-
- self._db_execute(
- """
- insert into RESOURCE (NAME, UID)
- values (:1, :2)
- """, name, uid,
- )
-
- 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, 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 dorevision:
- self._db_execute(
- """
- update REVISIONS SET REVISION = :1, DELETED = :2
- where NAME = :3
- """, self.bumpRevision(fast=True), 'Y', name
- )
Modified: CalendarServer/trunk/txdav/__init__.py
===================================================================
--- CalendarServer/trunk/txdav/__init__.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/__init__.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,3 +1,4 @@
+# -*- test-case-name: txdav -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
Modified: CalendarServer/trunk/txdav/base/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/datastore/sql.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/base/datastore/sql.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -18,6 +18,7 @@
Logic common to SQL implementations.
"""
+from twisted.internet.defer import Deferred
from inspect import getargspec
def _getarg(argname, argspec, args, kw):
@@ -68,16 +69,37 @@
@type memoAttribute: C{str}
"""
def decorate(thunk):
- spec = getargspec(thunk)
+ # cheater move to try to get the right argspec from inlineCallbacks.
+ # This could probably be more robust, but the 'cell_contents' thing
+ # probably can't (that's the only real reference to the underlying
+ # function).
+ if thunk.func_code.co_name == 'unwindGenerator':
+ specTarget = thunk.func_closure[0].cell_contents
+ else:
+ specTarget = thunk
+ spec = getargspec(specTarget)
def outer(*a, **kw):
self = a[0]
memo = getattr(self, memoAttribute)
key = _getarg(keyArgument, spec, a, kw)
if key in memo:
- return memo[key]
- result = thunk(*a, **kw)
- if result is not None:
- memo[key] = result
+ result = memo[key]
+ else:
+ result = thunk(*a, **kw)
+ if result is not None:
+ memo[key] = result
+ if isinstance(result, Deferred):
+ # clone the Deferred so that the old one keeps its result.
+ # FIXME: cancellation?
+ returnResult = Deferred()
+ def relayAndPreserve(innerResult):
+ if innerResult is None and key in memo and memo[key] is result:
+ # The result was None, call it again.
+ del memo[key]
+ returnResult.callback(innerResult)
+ return innerResult
+ result.addBoth(relayAndPreserve)
+ return returnResult
return result
return outer
return decorate
Modified: CalendarServer/trunk/txdav/base/datastore/subpostgres.py
===================================================================
--- CalendarServer/trunk/txdav/base/datastore/subpostgres.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/base/datastore/subpostgres.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -14,12 +14,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
+import sys
"""
Run and manage PostgreSQL as a subprocess.
"""
import os
import pwd
+import thread
+
+
from hashlib import md5
from twisted.python.procutils import which
@@ -67,6 +71,10 @@
def execute(self, sql, args=()):
self.connectionWrapper.state = 'executing %r' % (sql,)
+# sys.stdout.write(
+# "Really executing SQL %r in thread %r\n" %
+# ((sql % tuple(args)), thread.get_ident())
+# )
self.realCursor.execute(sql, args)
@@ -75,7 +83,12 @@
def fetchall(self):
- return self.realCursor.fetchall()
+ results = self.realCursor.fetchall()
+# sys.stdout.write(
+# "Really fetching results %r thread %r\n" %
+# (results, thread.get_ident())
+# )
+ return results
Modified: CalendarServer/trunk/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/sql.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/base/propertystore/sql.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
+from twisted.internet.defer import inlineCallbacks, returnValue
"""
PostgreSQL data store.
@@ -30,31 +31,35 @@
class PropertyStore(AbstractPropertyStore):
- def __init__(self, defaultuser, txn, resourceID):
+ def __init__(self, *a, **kw):
+ raise NotImplementedError(
+ "do not construct directly, call PropertyStore.load()"
+ )
+
+
+ @classmethod
+ @inlineCallbacks
+ def load(cls, defaultuser, txn, resourceID):
+ self = cls.__new__(cls)
super(PropertyStore, self).__init__(defaultuser)
self._txn = txn
self._resourceID = resourceID
+ self._cached = {}
+ rows = yield self._txn.execSQL(
+ """
+ select NAME, VIEWER_UID, VALUE from RESOURCE_PROPERTY
+ where RESOURCE_ID = %s
+ """,
+ [self._resourceID]
+ )
+ for name, uid, value in rows:
+ self._cached[(name, uid)] = value
+ returnValue(self)
- @property
- def _cached(self):
-
- if not hasattr(self, "_cached_properties"):
- self._cached_properties = {}
- rows = self._txn.execSQL(
- """
- select NAME, VIEWER_UID, VALUE from RESOURCE_PROPERTY
- where RESOURCE_ID = %s
- """,
- [self._resourceID]
- )
- for name, uid, value in rows:
- self._cached_properties[(name, uid)] = value
-
- return self._cached_properties
def _getitem_uid(self, key, uid):
validKey(key)
-
+
try:
value = self._cached[(key.toString(), uid)]
except KeyError:
Modified: CalendarServer/trunk/txdav/base/propertystore/test/base.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/test/base.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/base/propertystore/test/base.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -27,6 +27,7 @@
from zope.interface.verify import verifyObject, BrokenMethodImplementation
+from twisted.internet.defer import inlineCallbacks
from twisted.trial import unittest
from twext.web2.dav import davxml
@@ -38,10 +39,6 @@
class PropertyStoreTest(unittest.TestCase):
# Subclass must define self.propertyStore in setUp().
- def _preTest(self):
- self.addCleanup(self._postTest)
- def _postTest(self):
- pass
def _changed(self, store):
store.flush()
def _abort(self, store):
@@ -49,21 +46,20 @@
def test_interface(self):
try:
- self._preTest()
verifyObject(IPropertyStore, self.propertyStore)
except BrokenMethodImplementation, e:
self.fail(e)
+
+ @inlineCallbacks
def test_set_get_contains(self):
-
- self._preTest()
name = propertyName("test")
value = propertyValue("Hello, World!")
# Test with commit after change
self.propertyStore[name] = value
- self._changed(self.propertyStore)
+ yield self._changed(self.propertyStore)
self.assertEquals(self.propertyStore.get(name, None), value)
self.failUnless(name in self.propertyStore)
@@ -73,19 +69,19 @@
self.assertEquals(self.propertyStore.get(name, None), value)
self.failUnless(name in self.propertyStore)
+
+ @inlineCallbacks
def test_delete_get_contains(self):
- self._preTest()
-
# Test with commit after change
name = propertyName("test")
value = propertyValue("Hello, World!")
self.propertyStore[name] = value
- self._changed(self.propertyStore)
+ yield self._changed(self.propertyStore)
del self.propertyStore[name]
- self._changed(self.propertyStore)
+ yield self._changed(self.propertyStore)
self.assertEquals(self.propertyStore.get(name, None), None)
self.failIf(name in self.propertyStore)
@@ -95,53 +91,53 @@
value = propertyValue("Hello, Universe!")
self.propertyStore[name] = value
- self._changed(self.propertyStore)
+ yield self._changed(self.propertyStore)
del self.propertyStore[name]
self.assertEquals(self.propertyStore.get(name, None), None)
self.failIf(name in self.propertyStore)
+
+ @inlineCallbacks
def test_peruser(self):
- self._preTest()
-
name = propertyName("test")
value1 = propertyValue("Hello, World1!")
value2 = propertyValue("Hello, World2!")
self.propertyStore1[name] = value1
- self._changed(self.propertyStore1)
+ yield self._changed(self.propertyStore1)
self.assertEquals(self.propertyStore1.get(name, None), value1)
self.assertEquals(self.propertyStore2.get(name, None), None)
self.failUnless(name in self.propertyStore1)
self.failIf(name in self.propertyStore2)
-
+
self.propertyStore2[name] = value2
- self._changed(self.propertyStore2)
+ yield self._changed(self.propertyStore2)
self.assertEquals(self.propertyStore1.get(name, None), value1)
self.assertEquals(self.propertyStore2.get(name, None), value2)
self.failUnless(name in self.propertyStore1)
self.failUnless(name in self.propertyStore2)
-
+
del self.propertyStore2[name]
- self._changed(self.propertyStore2)
+ yield self._changed(self.propertyStore2)
self.assertEquals(self.propertyStore1.get(name, None), value1)
self.assertEquals(self.propertyStore2.get(name, None), None)
self.failUnless(name in self.propertyStore1)
self.failIf(name in self.propertyStore2)
-
+
del self.propertyStore1[name]
- self._changed(self.propertyStore1)
+ yield self._changed(self.propertyStore1)
self.assertEquals(self.propertyStore1.get(name, None), None)
self.assertEquals(self.propertyStore2.get(name, None), None)
self.failIf(name in self.propertyStore1)
self.failIf(name in self.propertyStore2)
-
- def test_peruser_shadow(self):
- self._preTest()
+ @inlineCallbacks
+ def test_peruserShadow(self):
+
name = propertyName("shadow")
self.propertyStore1.setSpecialProperties((name,), ())
@@ -151,38 +147,37 @@
value2 = propertyValue("Hello, World2!")
self.propertyStore1[name] = value1
- self._changed(self.propertyStore1)
+ yield self._changed(self.propertyStore1)
self.assertEquals(self.propertyStore1.get(name, None), value1)
self.assertEquals(self.propertyStore2.get(name, None), value1)
self.failUnless(name in self.propertyStore1)
self.failUnless(name in self.propertyStore2)
-
+
self.propertyStore2[name] = value2
- self._changed(self.propertyStore2)
+ yield self._changed(self.propertyStore2)
self.assertEquals(self.propertyStore1.get(name, None), value1)
self.assertEquals(self.propertyStore2.get(name, None), value2)
self.failUnless(name in self.propertyStore1)
self.failUnless(name in self.propertyStore2)
-
+
del self.propertyStore2[name]
- self._changed(self.propertyStore2)
+ yield self._changed(self.propertyStore2)
self.assertEquals(self.propertyStore1.get(name, None), value1)
self.assertEquals(self.propertyStore2.get(name, None), value1)
self.failUnless(name in self.propertyStore1)
self.failUnless(name in self.propertyStore2)
-
+
del self.propertyStore1[name]
- self._changed(self.propertyStore1)
+ yield self._changed(self.propertyStore1)
self.assertEquals(self.propertyStore1.get(name, None), None)
self.assertEquals(self.propertyStore2.get(name, None), None)
self.failIf(name in self.propertyStore1)
self.failIf(name in self.propertyStore2)
+ @inlineCallbacks
def test_peruser_global(self):
- self._preTest()
-
name = propertyName("global")
self.propertyStore1.setSpecialProperties((), (name,))
@@ -192,34 +187,32 @@
value2 = propertyValue("Hello, World2!")
self.propertyStore1[name] = value1
- self._changed(self.propertyStore1)
+ yield self._changed(self.propertyStore1)
self.assertEquals(self.propertyStore1.get(name, None), value1)
self.assertEquals(self.propertyStore2.get(name, None), value1)
self.failUnless(name in self.propertyStore1)
self.failUnless(name in self.propertyStore2)
-
+
self.propertyStore2[name] = value2
- self._changed(self.propertyStore2)
+ yield self._changed(self.propertyStore2)
self.assertEquals(self.propertyStore1.get(name, None), value2)
self.assertEquals(self.propertyStore2.get(name, None), value2)
self.failUnless(name in self.propertyStore1)
self.failUnless(name in self.propertyStore2)
-
+
del self.propertyStore2[name]
- self._changed(self.propertyStore2)
+ yield self._changed(self.propertyStore2)
self.assertEquals(self.propertyStore1.get(name, None), None)
self.assertEquals(self.propertyStore2.get(name, None), None)
self.failIf(name in self.propertyStore1)
self.failIf(name in self.propertyStore2)
-
+
def test_iteration(self):
- self._preTest()
-
value = propertyValue("Hello, World!")
- names = set(propertyName(str(i)) for i in (1,2,3,4))
+ names = set(propertyName(str(i)) for i in (1, 2, 3, 4))
for name in names:
self.propertyStore[name] = value
@@ -227,18 +220,17 @@
self.assertEquals(set(self.propertyStore.keys()), names)
self.assertEquals(len(self.propertyStore), len(names))
+
def test_delete_none(self):
- self._preTest()
def doDelete():
del self.propertyStore[propertyName("xyzzy")]
self.assertRaises(KeyError, doDelete)
+
def test_keyInPropertyName(self):
- self._preTest()
-
def doGet():
self.propertyStore["xyzzy"]
@@ -256,10 +248,10 @@
self.assertRaises(TypeError, doDelete)
self.assertRaises(TypeError, doContains)
+
+ @inlineCallbacks
def test_flush(self):
- self._preTest()
-
name = propertyName("test")
value = propertyValue("Hello, World!")
@@ -268,8 +260,8 @@
#
self.propertyStore[name] = value
- self._changed(self.propertyStore)
- self._abort(self.propertyStore)
+ yield self._changed(self.propertyStore)
+ yield self._abort(self.propertyStore)
self.assertEquals(self.propertyStore.get(name, None), value)
self.assertEquals(len(self.propertyStore), 1)
@@ -279,29 +271,29 @@
#
del self.propertyStore[name]
- self._changed(self.propertyStore)
- self._abort(self.propertyStore)
+ yield self._changed(self.propertyStore)
+ yield self._abort(self.propertyStore)
self.assertEquals(self.propertyStore.get(name, None), None)
self.assertEquals(len(self.propertyStore), 0)
- def test_abort(self):
- self._preTest()
-
+ @inlineCallbacks
+ def test_abort(self):
name = propertyName("test")
value = propertyValue("Hello, World!")
self.propertyStore[name] = value
- self._abort(self.propertyStore)
+ yield self._abort(self.propertyStore)
self.assertEquals(self.propertyStore.get(name, None), None)
self.assertEquals(len(self.propertyStore), 0)
+
+ @inlineCallbacks
def test_peruser_keys(self):
- self._preTest()
name = propertyName("shadow")
self.propertyStore1.setSpecialProperties((name,), ())
@@ -310,7 +302,7 @@
value1 = propertyValue("Hello, World1!")
self.propertyStore1[name] = value1
- self._changed(self.propertyStore1)
+ yield self._changed(self.propertyStore1)
self.failUnless(name in self.propertyStore2.keys())
Modified: CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -19,12 +19,10 @@
L{txdav.caldav.datastore.test.common}.
"""
-from twisted.internet.defer import inlineCallbacks
+from twisted.internet.defer import inlineCallbacks, returnValue
-from txdav.caldav.datastore.test.common import StubNotifierFactory
+from txdav.common.datastore.test.util import buildStore, StubNotifierFactory
-from txdav.common.datastore.test.util import buildStore
-
from txdav.base.propertystore.base import PropertyName
from txdav.base.propertystore.test import base
@@ -38,66 +36,74 @@
class PropertyStoreTest(base.PropertyStoreTest):
- def _preTest(self):
+
+ @inlineCallbacks
+ def setUp(self):
+ self.notifierFactory = StubNotifierFactory()
+ self.store = yield buildStore(self, self.notifierFactory)
self._txn = self.store.newTransaction()
- self.propertyStore = self.propertyStore1 = PropertyStore(
+ self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
"user01", self._txn, 1
)
- self.propertyStore2 = PropertyStore("user01", self._txn, 1)
+ self.propertyStore2 = yield PropertyStore.load("user01", self._txn, 1)
self.propertyStore2._setPerUserUID("user02")
-
- self.addCleanup(self._postTest)
- def _postTest(self):
+
+ @inlineCallbacks
+ def tearDown(self):
if hasattr(self, "_txn"):
- self._txn.commit()
+ result = yield self._txn.commit()
delattr(self, "_txn")
+ else:
+ result = None
self.propertyStore = self.propertyStore1 = self.propertyStore2 = None
+ returnValue(result)
+
+ @inlineCallbacks
def _changed(self, store):
if hasattr(self, "_txn"):
- self._txn.commit()
+ yield self._txn.commit()
delattr(self, "_txn")
self._txn = self.store.newTransaction()
store = self.propertyStore1
- self.propertyStore = self.propertyStore1 = PropertyStore(
+ self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
"user01", self._txn, 1
)
self.propertyStore1._shadowableKeys = store._shadowableKeys
self.propertyStore1._globalKeys = store._globalKeys
store = self.propertyStore2
- self.propertyStore2 = PropertyStore("user01", self._txn, 1)
+ self.propertyStore2 = yield PropertyStore.load("user01", self._txn, 1)
self.propertyStore2._setPerUserUID("user02")
self.propertyStore2._shadowableKeys = store._shadowableKeys
self.propertyStore2._globalKeys = store._globalKeys
+
+ @inlineCallbacks
def _abort(self, store):
if hasattr(self, "_txn"):
- self._txn.abort()
+ yield self._txn.abort()
delattr(self, "_txn")
self._txn = self.store.newTransaction()
store = self.propertyStore1
- self.propertyStore = self.propertyStore1 = PropertyStore(
+ self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
"user01", self._txn, 1
)
self.propertyStore1._shadowableKeys = store._shadowableKeys
self.propertyStore1._globalKeys = store._globalKeys
store = self.propertyStore2
- self.propertyStore2 = PropertyStore("user01", self._txn, 1)
+ self.propertyStore2 = yield PropertyStore.load("user01", self._txn, 1)
self.propertyStore2._setPerUserUID("user02")
self.propertyStore2._shadowableKeys = store._shadowableKeys
self.propertyStore2._globalKeys = store._globalKeys
- @inlineCallbacks
- def setUp(self):
- self.notifierFactory = StubNotifierFactory()
- self.store = yield buildStore(self, self.notifierFactory)
+
if PropertyStore is None:
PropertyStoreTest.skip = importErrorMessage
Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
+from twisted.internet.defer import inlineCallbacks, returnValue
"""
File calendar store.
@@ -44,13 +45,14 @@
from twistedcaldav import caldavxml, customxml
from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
-from twistedcaldav.index import Index as OldIndex, IndexSchedule as OldInboxIndex
from twistedcaldav.sharing import InvitesDatabase
from txdav.caldav.icalendarstore import IAttachment
from txdav.caldav.icalendarstore import ICalendar, ICalendarObject
from txdav.caldav.icalendarstore import ICalendarHome
+from txdav.caldav.datastore.index_file import Index as OldIndex,\
+ IndexSchedule as OldInboxIndex
from txdav.caldav.datastore.util import (
validateCalendarComponent, dropboxIDFromCalendarObject
)
@@ -100,6 +102,7 @@
for child in self.listCalendars():
yield self.calendarWithName(child)
+
def listCalendars(self):
"""
Return a generator of the child resource names.
@@ -110,14 +113,16 @@
yield name
+
+ @inlineCallbacks
def calendarObjectWithDropboxID(self, dropboxID):
"""
Implement lookup with brute-force scanning.
"""
for calendar in self.calendars():
for calendarObject in calendar.calendarObjects():
- if dropboxID == calendarObject.dropboxID():
- return calendarObject
+ if dropboxID == (yield calendarObject.dropboxID()):
+ returnValue(calendarObject)
@property
@@ -332,26 +337,30 @@
return self.component().getOrganizer()
+ @inlineCallbacks
def createAttachmentWithName(self, name, contentType):
"""
Implement L{ICalendarObject.removeAttachmentWithName}.
"""
# Make a (FIXME: temp, remember rollbacks) file in dropbox-land
- attachment = Attachment(self, name)
+ dropboxPath = yield self._dropboxPath()
+ attachment = Attachment(self, name, dropboxPath)
self._attachments[name] = attachment
- return attachment.store(contentType)
+ returnValue(attachment.store(contentType))
+ @inlineCallbacks
def removeAttachmentWithName(self, name):
"""
Implement L{ICalendarObject.removeAttachmentWithName}.
"""
# FIXME: rollback, tests for rollback
- self._dropboxPath().child(name).remove()
+ (yield self._dropboxPath()).child(name).remove()
if name in self._attachments:
del self._attachments[name]
+ @inlineCallbacks
def attachmentWithName(self, name):
# Attachments can be local or remote, but right now we only care about
# local. So we're going to base this on the listing of files in the
@@ -359,37 +368,46 @@
# 'attach' properties.
if name in self._attachments:
- return self._attachments[name]
+ returnValue(self._attachments[name])
# FIXME: cache consistently (put it in self._attachments)
- if self._dropboxPath().child(name).exists():
- return Attachment(self, name)
+ dbp = yield self._dropboxPath()
+ if dbp.child(name).exists():
+ returnValue(Attachment(self, name, dbp))
else:
# FIXME: test for non-existent attachment.
- return None
+ returnValue(None)
+ @inlineCallbacks
def attendeesCanManageAttachments(self):
- return self.component().hasPropertyInAnyComponent("X-APPLE-DROPBOX")
+ returnValue((yield self.component()).hasPropertyInAnyComponent("X-APPLE-DROPBOX"))
def dropboxID(self):
+ # NB: Deferred
return dropboxIDFromCalendarObject(self)
+ @inlineCallbacks
def _dropboxPath(self):
dropboxPath = self._parentCollection._home._path.child(
"dropbox"
- ).child(self.dropboxID())
+ ).child((yield self.dropboxID()))
if not dropboxPath.isdir():
dropboxPath.makedirs()
- return dropboxPath
+ returnValue(dropboxPath)
+ @inlineCallbacks
def attachments(self):
# See comment on attachmentWithName.
- return [Attachment(self, name)
- for name in self._dropboxPath().listdir()]
+ dropboxPath = (yield self._dropboxPath())
+ returnValue(
+ [Attachment(self, name, dropboxPath)
+ for name in dropboxPath.listdir()]
+ )
+
def initPropertyStore(self, props):
# Setup peruser special properties
props.setSpecialProperties(
@@ -408,6 +426,7 @@
)
+
contentTypeKey = PropertyName.fromElement(GETContentType)
md5key = PropertyName.fromElement(TwistedGETContentMD5)
@@ -457,9 +476,10 @@
implements(IAttachment)
- def __init__(self, calendarObject, name):
+ def __init__(self, calendarObject, name, dropboxPath):
self._calendarObject = calendarObject
self._name = name
+ self._dropboxPath = dropboxPath
def name(self):
@@ -474,6 +494,7 @@
def store(self, contentType):
return AttachmentStorageTransport(self, contentType)
+
def retrieve(self, protocol):
# FIXME: makeConnection
# FIXME: actually stream
@@ -482,10 +503,10 @@
# FIXME: ConnectionDone, not NotImplementedError
protocol.connectionLost(Failure(NotImplementedError()))
+
@property
def _path(self):
- dropboxPath = self._calendarObject._dropboxPath()
- return dropboxPath.child(self.name())
+ return self._dropboxPath.child(self.name())
Copied: CalendarServer/trunk/txdav/caldav/datastore/index_file.py (from rev 6445, CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py)
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/index_file.py (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/index_file.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -0,0 +1,1148 @@
+# -*- test-case-name: twistedcaldav.test.test_index -*-
+##
+# Copyright (c) 2005-2010 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+CalDAV Index.
+
+This API is considered private to static.py and is therefore subject to
+change.
+"""
+
+__all__ = [
+ "db_basename",
+ "ReservationError",
+ "MemcachedUIDReserver",
+ "Index",
+ "IndexSchedule",
+ "IndexedSearchException",
+]
+
+import datetime
+import time
+import hashlib
+
+try:
+ import sqlite3 as sqlite
+except ImportError:
+ from pysqlite2 import dbapi2 as sqlite
+
+from vobject.icalendar import utc
+
+from twisted.internet.defer import maybeDeferred, succeed, inlineCallbacks
+
+from twext.python.log import Logger, LoggingMixIn
+
+from txdav.common.icommondatastore import SyncTokenValidException,\
+ ReservationError, IndexedSearchException
+
+from twistedcaldav.ical import Component
+from twistedcaldav.query import calendarquery, calendarqueryfilter
+from twistedcaldav.sql import AbstractSQLDatabase
+from twistedcaldav.sql import db_prefix
+from twistedcaldav.instance import InvalidOverriddenInstanceError
+from twistedcaldav.config import config
+from twistedcaldav.memcachepool import CachePoolUserMixIn
+
+log = Logger()
+
+db_basename = db_prefix + "sqlite"
+schema_version = "10"
+collection_types = {"Calendar": "Regular Calendar Collection", "iTIP": "iTIP Calendar Collection"}
+
+icalfbtype_to_indexfbtype = {
+ "FREE" : 'F',
+ "BUSY" : 'B',
+ "BUSY-UNAVAILABLE": 'U',
+ "BUSY-TENTATIVE" : 'T',
+}
+indexfbtype_to_icalfbtype = dict([(v, k) for k,v in icalfbtype_to_indexfbtype.iteritems()])
+
+#
+# Duration into the future through which recurrences are expanded in the index
+# by default. This is a caching parameter which affects the size of the index;
+# it does not affect search results beyond this period, but it may affect
+# performance of such a search.
+#
+default_future_expansion_duration = datetime.timedelta(days=365*1)
+
+#
+# Maximum duration into the future through which recurrences are expanded in the
+# index. This is a caching parameter which affects the size of the index; it
+# does not affect search results beyond this period, but it may affect
+# performance of such a search.
+#
+# When a search is performed on a time span that goes beyond that which is
+# expanded in the index, we have to open each resource which may have data in
+# that time period. In order to avoid doing that multiple times, we want to
+# cache those results. However, we don't necessarily want to cache all
+# occurrences into some obscenely far-in-the-future date, so we cap the caching
+# period. Searches beyond this period will always be relatively expensive for
+# resources with occurrences beyond this period.
+#
+maximum_future_expansion_duration = datetime.timedelta(days=365*5)
+
+
+class AbstractCalendarIndex(AbstractSQLDatabase, LoggingMixIn):
+ """
+ Calendar collection index abstract base class that defines the apis for the index.
+ This will be subclassed for the two types of index behaviour we need: one for
+ regular calendar collections, one for schedule calendar collections.
+ """
+
+ def __init__(self, resource):
+ """
+ @param resource: the L{CalDAVResource} resource to
+ index. C{resource} must be a calendar collection (ie.
+ C{resource.isPseudoCalendarCollection()} returns C{True}.)
+ """
+ self.resource = resource
+ db_filename = self.resource.fp.child(db_basename).path
+ super(AbstractCalendarIndex, self).__init__(db_filename, False)
+
+ def create(self):
+ """
+ Create the index and initialize it.
+ """
+ self._db()
+
+ def reserveUID(self, uid):
+ """
+ Reserve a UID for this index's resource.
+ @param uid: the UID to reserve
+ @raise ReservationError: if C{uid} is already reserved
+ """
+ raise NotImplementedError
+
+ def unreserveUID(self, uid):
+ """
+ Unreserve a UID for this index's resource.
+ @param uid: the UID to reserve
+ @raise ReservationError: if C{uid} is not reserved
+ """
+ raise NotImplementedError
+
+ def isReservedUID(self, uid):
+ """
+ Check to see whether a UID is reserved.
+ @param uid: the UID to check
+ @return: True if C{uid} is reserved, False otherwise.
+ """
+ raise NotImplementedError
+
+ def isAllowedUID(self, uid, *names):
+ """
+ Checks to see whether to allow an operation with adds the the specified
+ UID is allowed to the index. Specifically, the operation may not
+ violate the constraint that UIDs must be unique, and the UID must not
+ be reserved.
+ @param uid: the UID to check
+ @param names: the names of resources being replaced or deleted by the
+ operation; UIDs associated with these resources are not checked.
+ @return: True if the UID is not in the index and is not reserved,
+ False otherwise.
+ """
+ raise NotImplementedError
+
+ def resourceNamesForUID(self, uid):
+ """
+ Looks up the names of the resources with the given UID.
+ @param uid: the UID of the resources to look up.
+ @return: a list of resource names
+ """
+ names = self._db_values_for_sql("select NAME from RESOURCE where UID = :1", uid)
+
+ #
+ # Check that each name exists as a child of self.resource. If not, the
+ # resource record is stale.
+ #
+ resources = []
+ for name in names:
+ name_utf8 = name.encode("utf-8")
+ 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, False)
+ self._db_commit()
+ else:
+ resources.append(name_utf8)
+
+ return resources
+
+ def resourceNameForUID(self, uid):
+ """
+ Looks up the name of the resource with the given UID.
+ @param uid: the UID of the resource to look up.
+ @return: If the resource is found, its name; C{None} otherwise.
+ """
+ result = None
+
+ for name in self.resourceNamesForUID(uid):
+ assert result is None, "More than one resource with UID %s in calendar collection %r" % (uid, self)
+ result = name
+
+ return result
+
+ def resourceUIDForName(self, name):
+ """
+ Looks up the UID of the resource with the given name.
+ @param name: the name of the resource to look up.
+ @return: If the resource is found, the UID of the resource; C{None}
+ otherwise.
+ """
+ uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
+
+ return uid
+
+ 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
+ for the resource name. If present, then the index entries for
+ that UID are removed. After that the new index entries are added.
+ @param name: the name of the resource to add.
+ @param calendar: a L{Calendar} object representing the resource
+ contents.
+ @param fast: if C{True} do not do commit, otherwise do commit.
+ """
+ oldUID = self.resourceUIDForName(name)
+ if oldUID is not None:
+ 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):
+ """
+ Remove this resource from the index.
+ @param name: the name of the resource to add.
+ @param uid: the UID of the calendar component in the resource.
+ """
+ uid = self.resourceUIDForName(name)
+ if uid is not None:
+ self._delete_from_db(name, uid)
+ self._db_commit()
+
+ def resourceExists(self, name):
+ """
+ Determines whether the specified resource name exists in the index.
+ @param name: the name of the resource to test
+ @return: True if the resource exists, False if not
+ """
+ uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
+ return uid is not None
+
+ def resourcesExist(self, names):
+ """
+ Determines whether the specified resource name exists in the index.
+ @param names: a C{list} containing the names of the resources to test
+ @return: a C{list} of all names that exist
+ """
+ statement = "select NAME from RESOURCE where NAME in ("
+ for ctr in (item[0] for item in enumerate(names)):
+ if ctr != 0:
+ statement += ", "
+ statement += ":%s" % (ctr,)
+ statement += ")"
+ results = self._db_values_for_sql(statement, *names)
+ return results
+
+
+ def testAndUpdateIndex(self, minDate):
+ # Find out if the index is expanded far enough
+ names = self.notExpandedBeyond(minDate)
+ # Actually expand recurrence max
+ for name in names:
+ self.log_info("Search falls outside range of index for %s %s" % (name, minDate))
+ self.reExpandResource(name, minDate)
+
+ def whatchanged(self, revision, depth):
+
+ 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, wasdeleted in results:
+ if name:
+ if wasdeleted == 'Y':
+ if revision:
+ deleted.append(name)
+ else:
+ changed.append(name)
+ else:
+ raise SyncTokenValidException
+
+ 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.
+ @param filter: the L{Filter} for the calendar-query to execute.
+ @return: an iterable of tuples for each resource matching the
+ given C{qualifiers}. The tuples are C{(name, uid, type)}, where
+ C{name} is the resource name, C{uid} is the resource UID, and
+ C{type} is the resource iCalendar component type.
+ """
+
+ # Make sure we have a proper Filter element and get the partial SQL
+ # statement to use.
+ if isinstance(filter, calendarqueryfilter.Filter):
+ if fbtype:
+ # Lookup the useruid - try the empty (default) one if needed
+ dbuseruid = self._db_value_for_sql(
+ "select PERUSERID from PERUSER where USERUID == :1",
+ useruid,
+ )
+ else:
+ dbuseruid = ""
+
+ qualifiers = calendarquery.sqlcalendarquery(filter, None, dbuseruid)
+ if qualifiers is not None:
+ # Determine how far we need to extend the current expansion of
+ # events. If we have an open-ended time-range we will expand one
+ # year past the start. That should catch bounded recurrences - unbounded
+ # will have been indexed with an "infinite" value always included.
+ maxDate, isStartDate = filter.getmaxtimerange()
+ if maxDate:
+ maxDate = maxDate.date()
+ if isStartDate:
+ maxDate += datetime.timedelta(days=365)
+ self.testAndUpdateIndex(maxDate)
+ else:
+ # We cannot handler this filter in an indexed search
+ raise IndexedSearchException()
+
+ else:
+ qualifiers = None
+
+ # Perform the search
+ if qualifiers is None:
+ rowiter = self._db_execute("select NAME, UID, TYPE from RESOURCE")
+ else:
+ if fbtype:
+ # Lookup the useruid - try the empty (default) one if needed
+ dbuseruid = self._db_value_for_sql(
+ "select PERUSERID from PERUSER where USERUID == :1",
+ useruid,
+ )
+
+ # For a free-busy time-range query we return all instances
+ rowiter = self._db_execute(
+ "select DISTINCT RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE, RESOURCE.ORGANIZER, TIMESPAN.FLOAT, TIMESPAN.START, TIMESPAN.END, TIMESPAN.FBTYPE, TIMESPAN.TRANSPARENT, TRANSPARENCY.TRANSPARENT" +
+ qualifiers[0],
+ *qualifiers[1]
+ )
+ else:
+ rowiter = self._db_execute("select DISTINCT RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE" + qualifiers[0], *qualifiers[1])
+
+ # Check result for missing resources
+
+ for row in rowiter:
+ name = row[0]
+ if self.resource.getChild(name.encode("utf-8")):
+ if fbtype:
+ row = list(row)
+ if row[9]:
+ row[8] = row[9]
+ del row[9]
+ yield row
+ else:
+ log.err("Calendar resource %s is missing from %s. Removing from index."
+ % (name, self.resource))
+ self.deleteResource(name)
+
+ def bruteForceSearch(self):
+ """
+ List the whole index and tests for existence, updating the index
+ @return: all resources in the index
+ """
+ # List all resources
+ rowiter = self._db_execute("select NAME, UID, TYPE from RESOURCE")
+
+ # Check result for missing resources:
+
+ for row in rowiter:
+ name = row[0]
+ if self.resource.getChild(name.encode("utf-8")):
+ yield row
+ else:
+ log.err("Calendar resource %s is missing from %s. Removing from index."
+ % (name, self.resource))
+ self.deleteResource(name)
+
+
+ def _db_version(self):
+ """
+ @return: the schema version assigned to this index.
+ """
+ return schema_version
+
+ 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
+ be associated with any given UID and vice versa.
+ NB This method does not commit the changes to the db - the caller
+ MUST take care of that
+ @param name: the name of the resource to add.
+ @param calendar: a L{Calendar} object representing the resource
+ contents.
+ """
+ raise NotImplementedError
+
+ 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.
+ """
+ raise NotImplementedError
+
+class CalendarIndex (AbstractCalendarIndex):
+ """
+ Calendar index - abstract class for indexer that indexes calendar objects in a collection.
+ """
+
+ def __init__(self, resource):
+ """
+ @param resource: the L{CalDAVResource} resource to
+ index.
+ """
+ super(CalendarIndex, self).__init__(resource)
+
+ def _db_init_data_tables_base(self, q, uidunique):
+ """
+ Initialise the underlying database tables.
+ @param q: a database cursor to use.
+ """
+ #
+ # RESOURCE table is the primary index table
+ # NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
+ # UID: iCalendar UID (may or may not be unique)
+ # TYPE: iCalendar component type
+ # RECURRANCE_MAX: Highest date of recurrence expansion
+ # ORGANIZER: cu-address of the Organizer of the event
+ #
+ q.execute(
+ """
+ create table RESOURCE (
+ RESOURCEID integer primary key autoincrement,
+ NAME text unique,
+ UID text%s,
+ TYPE text,
+ RECURRANCE_MAX date,
+ ORGANIZER text
+ )
+ """ % (" unique" if uidunique else "",)
+ )
+
+ #
+ # TIMESPAN table tracks (expanded) time spans for resources
+ # NAME: Related resource (RESOURCE foreign key)
+ # FLOAT: 'Y' if start/end are floating, 'N' otherwise
+ # START: Start date
+ # END: End date
+ # FBTYPE: FBTYPE value:
+ # '?' - unknown
+ # 'F' - free
+ # 'B' - busy
+ # 'U' - busy-unavailable
+ # 'T' - busy-tentative
+ # TRANSPARENT: Y if transparent, N if opaque (default non-per-user value)
+ #
+ q.execute(
+ """
+ create table TIMESPAN (
+ INSTANCEID integer primary key autoincrement,
+ RESOURCEID integer,
+ FLOAT text(1),
+ START date,
+ END date,
+ FBTYPE text(1),
+ TRANSPARENT text(1)
+ )
+ """
+ )
+ q.execute(
+ """
+ create index STARTENDFLOAT on TIMESPAN (START, END, FLOAT)
+ """
+ )
+
+ #
+ # PERUSER table tracks per-user ids
+ # PERUSERID: autoincrement primary key
+ # UID: User ID used in calendar data
+ #
+ q.execute(
+ """
+ create table PERUSER (
+ PERUSERID integer primary key autoincrement,
+ USERUID text
+ )
+ """
+ )
+ q.execute(
+ """
+ create index PERUSER_UID on PERUSER (USERUID)
+ """
+ )
+
+ #
+ # TRANSPARENCY table tracks per-user per-instance transparency
+ # PERUSERID: user id key
+ # INSTANCEID: instance id key
+ # TRANSPARENT: Y if transparent, N if opaque
+ #
+ q.execute(
+ """
+ create table TRANSPARENCY (
+ PERUSERID integer,
+ INSTANCEID integer,
+ TRANSPARENT text(1)
+ )
+ """
+ )
+
+ #
+ # REVISIONS table tracks changes
+ # NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
+ # REVISION: revision number
+ # WASDELETED: Y if revision deleted, N if added or changed
+ #
+ 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,
+ DELETED text(1)
+ )
+ """
+ )
+ q.execute(
+ """
+ create index REVISION on REVISIONS (REVISION)
+ """
+ )
+
+ if uidunique:
+ #
+ # RESERVED table tracks reserved UIDs
+ # UID: The UID being reserved
+ # TIME: When the reservation was made
+ #
+ q.execute(
+ """
+ create table RESERVED (
+ UID text unique,
+ TIME date
+ )
+ """
+ )
+
+ # Cascading triggers to help on delete
+ q.execute(
+ """
+ create trigger resourceDelete after delete on RESOURCE
+ for each row
+ begin
+ delete from TIMESPAN where TIMESPAN.RESOURCEID = OLD.RESOURCEID;
+ end
+ """
+ )
+ q.execute(
+ """
+ create trigger timespanDelete after delete on TIMESPAN
+ for each row
+ begin
+ delete from TRANSPARENCY where INSTANCEID = OLD.INSTANCEID;
+ end
+ """
+ )
+
+ def _db_can_upgrade(self, old_version):
+ """
+ Can we do an in-place upgrade
+ """
+
+ # v10 is a big change - no upgrade possible
+ return False
+
+ def _db_upgrade_data_tables(self, q, old_version):
+ """
+ Upgrade the data from an older version of the DB.
+ """
+
+ # v10 is a big change - no upgrade possible
+ pass
+
+ def notExpandedBeyond(self, minDate):
+ """
+ Gives all resources which have not been expanded beyond a given date
+ in the index
+ """
+ return self._db_values_for_sql("select NAME from RESOURCE where RECURRANCE_MAX < :1", minDate)
+
+ def reExpandResource(self, name, expand_until):
+ """
+ Given a resource name, remove it from the database and re-add it
+ with a longer expansion.
+ """
+ calendar = self.resource.getChild(name).iCalendar()
+ self._add_to_db(name, calendar, expand_until=expand_until, reCreate=True)
+ self._db_commit()
+
+ 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
+ be associated with any given UID and vice versa.
+ NB This method does not commit the changes to the db - the caller
+ MUST take care of that
+ @param name: the name of the resource to add.
+ @param calendar: a L{Calendar} object representing the resource
+ contents.
+ """
+ uid = calendar.resourceUID()
+ organizer = calendar.getOrganizer()
+ if not organizer:
+ organizer = ""
+
+ # Decide how far to expand based on the component
+ master = calendar.masterComponent()
+ if master is None or not calendar.isRecurring() and not calendar.isRecurringUnbounded():
+ # When there is no master we have a set of overridden components - index them all.
+ # When there is one instance - index it.
+ # When bounded - index all.
+ expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
+ else:
+ if expand_until:
+ expand = expand_until
+ else:
+ expand = datetime.date.today() + default_future_expansion_duration
+
+ if expand > (datetime.date.today() + maximum_future_expansion_duration):
+ raise IndexedSearchException
+
+ try:
+ instances = calendar.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
+ except InvalidOverriddenInstanceError, e:
+ log.err("Invalid instance %s when indexing %s in %s" % (e.rid, name, self.resource,))
+ raise
+
+ self._delete_from_db(name, uid, False)
+
+ # Add RESOURCE item
+ self._db_execute(
+ """
+ insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
+ values (:1, :2, :3, :4, :5)
+ """, name, uid, calendar.resourceType(), instances.limit, organizer
+ )
+ resourceid = self.lastrowid
+
+ # Get a set of all referenced per-user UIDs and map those to entries already
+ # in the DB and add new ones as needed
+ useruids = calendar.allPerUserUIDs()
+ useruids.add("")
+ useruidmap = {}
+ for useruid in useruids:
+ peruserid = self._db_value_for_sql(
+ "select PERUSERID from PERUSER where USERUID = :1",
+ useruid
+ )
+ if peruserid is None:
+ self._db_execute(
+ """
+ insert into PERUSER (USERUID)
+ values (:1)
+ """, useruid
+ )
+ peruserid = self.lastrowid
+ useruidmap[useruid] = peruserid
+
+ for key in instances:
+ instance = instances[key]
+ start = instance.start.replace(tzinfo=utc)
+ end = instance.end.replace(tzinfo=utc)
+ float = 'Y' if instance.start.tzinfo is None else 'N'
+ transp = 'T' if instance.component.propertyValue("TRANSP") == "TRANSPARENT" else 'F'
+ self._db_execute(
+ """
+ insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE, TRANSPARENT)
+ values (:1, :2, :3, :4, :5, :6)
+ """,
+ resourceid,
+ float,
+ start,
+ end,
+ icalfbtype_to_indexfbtype.get(instance.component.getFBType(), 'F'),
+ transp
+ )
+ instanceid = self.lastrowid
+ peruserdata = calendar.perUserTransparency(instance.rid)
+ for useruid, transp in peruserdata:
+ peruserid = useruidmap[useruid]
+ self._db_execute(
+ """
+ insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
+ values (:1, :2, :3)
+ """, peruserid, instanceid, 'T' if transp else 'F'
+ )
+
+
+ # Special - for unbounded recurrence we insert a value for "infinity"
+ # that will allow an open-ended time-range to always match it.
+ if calendar.isRecurringUnbounded():
+ start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
+ end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
+ float = 'N'
+ self._db_execute(
+ """
+ insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE, TRANSPARENT)
+ values (:1, :2, :3, :4, :5, :6)
+ """, resourceid, float, start, end, '?', '?'
+ )
+ instanceid = self.lastrowid
+ peruserdata = calendar.perUserTransparency(None)
+ for useruid, transp in peruserdata:
+ peruserid = useruidmap[useruid]
+ self._db_execute(
+ """
+ insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
+ values (:1, :2, :3)
+ """, peruserid, instanceid, 'T' if transp else 'F'
+ )
+
+ 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, 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 dorevision:
+ self._db_execute(
+ """
+ update REVISIONS SET REVISION = :1, DELETED = :2
+ where NAME = :3
+ """, self.bumpRevision(fast=True), 'Y', name
+ )
+
+
+def wrapInDeferred(f):
+ def _(*args, **kwargs):
+ return maybeDeferred(f, *args, **kwargs)
+
+ return _
+
+
+class MemcachedUIDReserver(CachePoolUserMixIn, LoggingMixIn):
+ def __init__(self, index, cachePool=None):
+ self.index = index
+ self._cachePool = cachePool
+
+ def _key(self, uid):
+ return 'reservation:%s' % (
+ hashlib.md5('%s:%s' % (uid,
+ self.index.resource.fp.path)).hexdigest())
+
+ def reserveUID(self, uid):
+ uid = uid.encode('utf-8')
+ self.log_debug("Reserving UID %r @ %r" % (
+ uid,
+ self.index.resource.fp.path))
+
+ def _handleFalse(result):
+ if result is False:
+ raise ReservationError(
+ "UID %s already reserved for calendar collection %s."
+ % (uid, self.index.resource)
+ )
+
+ d = self.getCachePool().add(self._key(uid),
+ 'reserved',
+ expireTime=config.UIDReservationTimeOut)
+ d.addCallback(_handleFalse)
+ return d
+
+
+ def unreserveUID(self, uid):
+ uid = uid.encode('utf-8')
+ self.log_debug("Unreserving UID %r @ %r" % (
+ uid,
+ self.index.resource.fp.path))
+
+ def _handleFalse(result):
+ if result is False:
+ raise ReservationError(
+ "UID %s is not reserved for calendar collection %s."
+ % (uid, self.index.resource)
+ )
+
+ d =self.getCachePool().delete(self._key(uid))
+ d.addCallback(_handleFalse)
+ return d
+
+
+ def isReservedUID(self, uid):
+ uid = uid.encode('utf-8')
+ self.log_debug("Is reserved UID %r @ %r" % (
+ uid,
+ self.index.resource.fp.path))
+
+ def _checkValue((flags, value)):
+ if value is None:
+ return False
+ else:
+ return True
+
+ d = self.getCachePool().get(self._key(uid))
+ d.addCallback(_checkValue)
+ return d
+
+
+
+class SQLUIDReserver(object):
+ def __init__(self, index):
+ self.index = index
+
+ @wrapInDeferred
+ def reserveUID(self, uid):
+ """
+ Reserve a UID for this index's resource.
+ @param uid: the UID to reserve
+ @raise ReservationError: if C{uid} is already reserved
+ """
+
+ try:
+ self.index._db_execute("insert into RESERVED (UID, TIME) values (:1, :2)", uid, datetime.datetime.now())
+ self.index._db_commit()
+ except sqlite.IntegrityError:
+ self.index._db_rollback()
+ raise ReservationError(
+ "UID %s already reserved for calendar collection %s."
+ % (uid, self.index.resource)
+ )
+ except sqlite.Error, e:
+ log.err("Unable to reserve UID: %s", (e,))
+ self.index._db_rollback()
+ raise
+
+ def unreserveUID(self, uid):
+ """
+ Unreserve a UID for this index's resource.
+ @param uid: the UID to reserve
+ @raise ReservationError: if C{uid} is not reserved
+ """
+
+ def _cb(result):
+ if result == False:
+ raise ReservationError(
+ "UID %s is not reserved for calendar collection %s."
+ % (uid, self.index.resource)
+ )
+ else:
+ try:
+ self.index._db_execute(
+ "delete from RESERVED where UID = :1", uid)
+ self.index._db_commit()
+ except sqlite.Error, e:
+ log.err("Unable to unreserve UID: %s", (e,))
+ self.index._db_rollback()
+ raise
+
+ d = self.isReservedUID(uid)
+ d.addCallback(_cb)
+ return d
+
+
+ @wrapInDeferred
+ def isReservedUID(self, uid):
+ """
+ Check to see whether a UID is reserved.
+ @param uid: the UID to check
+ @return: True if C{uid} is reserved, False otherwise.
+ """
+
+ rowiter = self.index._db_execute("select UID, TIME from RESERVED where UID = :1", uid)
+ for uid, attime in rowiter:
+ # Double check that the time is within a reasonable period of now
+ # otherwise we probably have a stale reservation
+ tm = time.strptime(attime[:19], "%Y-%m-%d %H:%M:%S")
+ dt = datetime.datetime(year=tm.tm_year, month=tm.tm_mon, day=tm.tm_mday, hour=tm.tm_hour, minute=tm.tm_min, second = tm.tm_sec)
+ if datetime.datetime.now() - dt > datetime.timedelta(seconds=config.UIDReservationTimeOut):
+ try:
+ self.index._db_execute("delete from RESERVED where UID = :1", uid)
+ self.index._db_commit()
+ except sqlite.Error, e:
+ log.err("Unable to unreserve UID: %s", (e,))
+ self.index._db_rollback()
+ raise
+ return False
+ else:
+ return True
+
+ return False
+
+
+
+class Index (CalendarIndex):
+ """
+ Calendar collection index - regular collection that enforces CalDAV UID uniqueness requirement.
+ """
+
+ def __init__(self, resource):
+ """
+ @param resource: the L{CalDAVResource} resource to
+ index. C{resource} must be a calendar collection (i.e.
+ C{resource.isPseudoCalendarCollection()} returns C{True}.)
+ """
+ assert resource.isCalendarCollection(), "non-calendar collection resource %s has no index." % (resource,)
+ super(Index, self).__init__(resource)
+
+ if (
+ hasattr(config, "Memcached") and
+ config.Memcached.Pools.Default.ClientEnabled
+ ):
+ self.reserver = MemcachedUIDReserver(self)
+ else:
+ self.reserver = SQLUIDReserver(self)
+
+ #
+ # A dict of sets. The dict keys are calendar collection paths,
+ # and the sets contains reserved UIDs for each path.
+ #
+
+ def reserveUID(self, uid):
+ return self.reserver.reserveUID(uid)
+
+
+ def unreserveUID(self, uid):
+ return self.reserver.unreserveUID(uid)
+
+
+ def isReservedUID(self, uid):
+ return self.reserver.isReservedUID(uid)
+
+
+ def isAllowedUID(self, uid, *names):
+ """
+ Checks to see whether to allow an operation which would add the
+ specified UID to the index. Specifically, the operation may not
+ violate the constraint that UIDs must be unique.
+ @param uid: the UID to check
+ @param names: the names of resources being replaced or deleted by the
+ operation; UIDs associated with these resources are not checked.
+ @return: True if the UID is not in the index and is not reserved,
+ False otherwise.
+ """
+ rname = self.resourceNameForUID(uid)
+ return (rname is None or rname in names)
+
+ def _db_type(self):
+ """
+ @return: the collection type assigned to this index.
+ """
+ return collection_types["Calendar"]
+
+ def _db_init_data_tables(self, q):
+ """
+ Initialise the underlying database tables.
+ @param q: a database cursor to use.
+ """
+
+ # Create database where the RESOURCE table has unique UID column.
+ self._db_init_data_tables_base(q, True)
+
+ def _db_recreate(self, do_commit=True):
+ """
+ Re-create the database tables from existing calendar data.
+ """
+
+ #
+ # Populate the DB with data from already existing resources.
+ # This allows for index recovery if the DB file gets
+ # deleted.
+ #
+ fp = self.resource.fp
+ for name in fp.listdir():
+ if name.startswith("."):
+ continue
+
+ try:
+ stream = fp.child(name).open()
+ except (IOError, OSError), e:
+ log.err("Unable to open resource %s: %s" % (name, e))
+ continue
+
+ # FIXME: This is blocking I/O
+ try:
+ calendar = Component.fromStream(stream)
+ calendar.validateForCalDAV()
+ except ValueError:
+ log.err("Non-calendar resource: %s" % (name,))
+ else:
+ #log.msg("Indexing resource: %s" % (name,))
+ 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()
+
+class IndexSchedule (CalendarIndex):
+ """
+ Schedule collection index - does not require UID uniqueness.
+ """
+
+ def reserveUID(self, uid): #@UnusedVariable
+ """
+ Reserve a UID for this index's resource.
+ @param uid: the UID to reserve
+ @raise ReservationError: if C{uid} is already reserved
+ """
+
+ # iTIP does not require unique UIDs
+ return succeed(None)
+
+ def unreserveUID(self, uid): #@UnusedVariable
+ """
+ Unreserve a UID for this index's resource.
+ @param uid: the UID to reserve
+ @raise ReservationError: if C{uid} is not reserved
+ """
+
+ # iTIP does not require unique UIDs
+ return succeed(None)
+
+ def isReservedUID(self, uid): #@UnusedVariable
+ """
+ Check to see whether a UID is reserved.
+ @param uid: the UID to check
+ @return: True if C{uid} is reserved, False otherwise.
+ """
+
+ # iTIP does not require unique UIDs
+ return succeed(False)
+
+ def isAllowedUID(self, uid, *names): #@UnusedVariable
+ """
+ Checks to see whether to allow an operation with adds the the specified
+ UID is allowed to the index. Specifically, the operation may not
+ violate the constraint that UIDs must be unique, and the UID must not
+ be reserved.
+ @param uid: the UID to check
+ @param names: the names of resources being replaced or deleted by the
+ operation; UIDs associated with these resources are not checked.
+ @return: True if the UID is not in the index and is not reserved,
+ False otherwise.
+ """
+
+ # iTIP does not require unique UIDs
+ return True
+
+ def _db_type(self):
+ """
+ @return: the collection type assigned to this index.
+ """
+ return collection_types["iTIP"]
+
+ def _db_init_data_tables(self, q):
+ """
+ Initialise the underlying database tables.
+ @param q: a database cursor to use.
+ """
+
+ # Create database where the RESOURCE table has a UID column that is not unique.
+ self._db_init_data_tables_base(q, False)
+
+ def _db_recreate(self, do_commit=True):
+ """
+ Re-create the database tables from existing calendar data.
+ """
+
+ #
+ # Populate the DB with data from already existing resources.
+ # This allows for index recovery if the DB file gets
+ # deleted.
+ #
+ fp = self.resource.fp
+ for name in fp.listdir():
+ if name.startswith("."):
+ continue
+
+ try:
+ stream = fp.child(name).open()
+ except (IOError, OSError), e:
+ log.err("Unable to open resource %s: %s" % (name, e))
+ continue
+
+ # FIXME: This is blocking I/O
+ try:
+ calendar = Component.fromStream(stream)
+ calendar.validCalendarForCalDAV()
+ calendar.validateComponentsForCalDAV(True)
+ except ValueError:
+ log.err("Non-calendar resource: %s" % (name,))
+ else:
+ #log.msg("Indexing resource: %s" % (name,))
+ 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()
Property changes on: CalendarServer/trunk/txdav/caldav/datastore/index_file.py
___________________________________________________________________
Added: svn:mergeinfo
+ /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -20,6 +20,7 @@
from twisted.python.util import FancyEqMixin
from twisted.python.components import proxyForInterface
+from twisted.internet.defer import inlineCallbacks, returnValue
@@ -39,16 +40,17 @@
self._transaction = transaction
+ @inlineCallbacks
def calendarHomeWithUID(self, uid, create=False):
# FIXME: 'create' flag
- newHome = super(ImplicitTransaction, self
+ newHome = yield super(ImplicitTransaction, self
).calendarHomeWithUID(uid, create)
# return ImplicitCalendarHome(newHome, self)
if newHome is None:
- return None
+ returnValue(None)
else:
# FIXME: relay transaction
- return ImplicitCalendarHome(newHome, None)
+ returnValue(ImplicitCalendarHome(newHome, None))
@@ -71,23 +73,30 @@
# # FIXME: wrap?
# return self._calendarHome.properties()
+ @inlineCallbacks
def calendars(self):
- for calendar in super(ImplicitCalendarHome, self).calendars():
- yield ImplicitCalendar(self, calendar)
+ superCalendars = (yield super(ImplicitCalendarHome, self).calendars())
+ wrapped = []
+ for calendar in superCalendars:
+ wrapped.append(ImplicitCalendar(self, calendar))
+ returnValue(wrapped)
+
def createCalendarWithName(self, name):
self._calendarHome.createCalendarWithName(name)
+
def removeCalendarWithName(self, name):
self._calendarHome.removeCalendarWithName(name)
+ @inlineCallbacks
def calendarWithName(self, name):
- calendar = self._calendarHome.calendarWithName(name)
+ calendar = yield self._calendarHome.calendarWithName(name)
if calendar is not None:
- return ImplicitCalendar(self, calendar)
+ returnValue(ImplicitCalendar(self, calendar))
else:
- return None
+ returnValue(None)
Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -15,6 +15,10 @@
# limitations under the License.
##
+"""
+SQL backend for CalDAV storage.
+"""
+
__all__ = [
"CalendarHome",
"Calendar",
@@ -25,6 +29,7 @@
from twext.web2.dav.element.rfc2518 import ResourceType
from twext.web2.http_headers import MimeType, generateContentType
+from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.internet.error import ConnectionLost
from twisted.internet.interfaces import ITransport
from twisted.python import hashlib
@@ -33,7 +38,7 @@
from twistedcaldav import caldavxml, customxml
from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
from twistedcaldav.dateops import normalizeForIndex, datetimeMktime
-from twistedcaldav.index import IndexedSearchException
+from txdav.common.icommondatastore import IndexedSearchException
from twistedcaldav.instance import InvalidOverriddenInstanceError
from txdav.caldav.datastore.util import validateCalendarComponent,\
@@ -45,7 +50,7 @@
CommonObjectResource
from txdav.common.datastore.sql_legacy import \
PostgresLegacyIndexEmulator, SQLLegacyCalendarInvites,\
- SQLLegacyCalendarShares
+ SQLLegacyCalendarShares, PostgresLegacyInboxIndexEmulator
from txdav.common.datastore.sql_tables import CALENDAR_TABLE,\
CALENDAR_BIND_TABLE, CALENDAR_OBJECT_REVISIONS_TABLE, CALENDAR_OBJECT_TABLE,\
_ATTACHMENTS_MODE_WRITE
@@ -77,24 +82,30 @@
calendars = CommonHome.children
listCalendars = CommonHome.listChildren
+
+ @inlineCallbacks
def calendarObjectWithDropboxID(self, dropboxID):
"""
Implement lookup with brute-force scanning.
"""
- for calendar in self.calendars():
- for calendarObject in calendar.calendarObjects():
- if dropboxID == calendarObject.dropboxID():
- return calendarObject
+ for calendar in (yield self.calendars()):
+ for calendarObject in (yield calendar.calendarObjects()):
+ dbid = yield calendarObject.dropboxID()
+ if dropboxID == dbid:
+ returnValue(calendarObject)
+ @inlineCallbacks
def createdHome(self):
- self.createCalendarWithName("calendar")
- defaultCal = self.calendarWithName("calendar")
+ yield self.createCalendarWithName("calendar")
+ defaultCal = yield self.calendarWithName("calendar")
props = defaultCal.properties()
props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
Opaque())
- self.createCalendarWithName("inbox")
+ yield self.createCalendarWithName("inbox")
+
+
class Calendar(CommonHomeChild):
"""
File-based implementation of L{ICalendar}.
@@ -103,22 +114,20 @@
def __init__(self, home, name, resourceID, notifier):
"""
- Initialize a calendar pointing at a path on disk.
+ Initialize a calendar pointing at a record in a database.
- @param name: the subdirectory of calendarHome where this calendar
- resides.
+ @param name: the name of the calendar resource.
@type name: C{str}
- @param calendarHome: the home containing this calendar.
- @type calendarHome: L{CalendarHome}
-
- @param realName: If this calendar was just created, the name which it
- will eventually have on disk.
- @type realName: C{str}
+ @param home: the home containing this calendar.
+ @type home: L{CalendarHome}
"""
super(Calendar, self).__init__(home, name, resourceID, notifier)
- self._index = PostgresLegacyIndexEmulator(self)
+ if name == 'inbox':
+ self._index = PostgresLegacyInboxIndexEmulator(self)
+ else:
+ self._index = PostgresLegacyIndexEmulator(self)
self._invites = SQLLegacyCalendarInvites(self)
self._objectResourceClass = CalendarObject
self._bindTable = CALENDAR_BIND_TABLE
@@ -213,32 +222,36 @@
def _pathToName(path):
return path.rsplit(".", 1)[0]
+
+
class CalendarObject(CommonObjectResource):
implements(ICalendarObject)
- def __init__(self, name, calendar, resid):
- super(CalendarObject, self).__init__(name, calendar, resid)
+ _objectTable = CALENDAR_OBJECT_TABLE
- self._objectTable = CALENDAR_OBJECT_TABLE
-
@property
def _calendar(self):
return self._parentCollection
+
def calendar(self):
return self._calendar
+
+ @inlineCallbacks
def setComponent(self, component, inserting=False):
validateCalendarComponent(self, self._calendar, component, inserting)
- self.updateDatabase(component, inserting=inserting)
+ yield self.updateDatabase(component, inserting=inserting)
if inserting:
- self._calendar._insertRevision(self._name)
+ yield self._calendar._insertRevision(self._name)
else:
- self._calendar._updateRevision(self._name)
+ yield self._calendar._updateRevision(self._name)
self._calendar.notifyChanged()
+
+ @inlineCallbacks
def updateDatabase(self, component, expand_until=None, reCreate=False, inserting=False):
"""
Update the database tables for the new data being written.
@@ -282,7 +295,7 @@
# CALENDAR_OBJECT table update
if inserting:
- self._resourceID = self._txn.execSQL(
+ self._resourceID = (yield self._txn.execSQL(
"""
insert into CALENDAR_OBJECT
(CALENDAR_RESOURCE_ID, RESOURCE_NAME, ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX)
@@ -302,9 +315,9 @@
organizer,
normalizeForIndex(instances.limit) if instances.limit else None,
]
- )[0][0]
+ ))[0][0]
else:
- self._txn.execSQL(
+ yield self._txn.execSQL(
"""
update CALENDAR_OBJECT set
(ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, MODIFIED)
@@ -327,7 +340,7 @@
)
# Need to wipe the existing time-range for this and rebuild
- self._txn.execSQL(
+ yield self._txn.execSQL(
"""
delete from TIME_RANGE where CALENDAR_OBJECT_RESOURCE_ID = %s
""",
@@ -335,6 +348,7 @@
self._resourceID,
],
)
+ self._uid = component.resourceUID()
# CALENDAR_OBJECT table update
@@ -344,7 +358,7 @@
end = instance.end.replace(tzinfo=utc)
float = instance.start.tzinfo is None
transp = instance.component.propertyValue("TRANSP") == "TRANSPARENT"
- instanceid = self._txn.execSQL(
+ instanceid = (yield self._txn.execSQL(
"""
insert into TIME_RANGE
(CALENDAR_RESOURCE_ID, CALENDAR_OBJECT_RESOURCE_ID, FLOATING, START_DATE, END_DATE, FBTYPE, TRANSPARENT)
@@ -362,10 +376,10 @@
icalfbtype_to_indexfbtype.get(instance.component.getFBType(), icalfbtype_to_indexfbtype["FREE"]),
transp,
],
- )[0][0]
+ ))[0][0]
peruserdata = component.perUserTransparency(instance.rid)
for useruid, transp in peruserdata:
- self._txn.execSQL(
+ yield self._txn.execSQL(
"""
insert into TRANSPARENCY
(TIME_RANGE_INSTANCE_ID, USER_ID, TRANSPARENT)
@@ -385,7 +399,7 @@
start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
float = False
- instanceid = self._txn.execSQL(
+ instanceid = (yield self._txn.execSQL(
"""
insert into TIME_RANGE
(CALENDAR_RESOURCE_ID, CALENDAR_OBJECT_RESOURCE_ID, FLOATING, START_DATE, END_DATE, FBTYPE, TRANSPARENT)
@@ -403,10 +417,10 @@
icalfbtype_to_indexfbtype["UNKNOWN"],
True,
],
- )[0][0]
+ ))[0][0]
peruserdata = component.perUserTransparency(None)
for useruid, transp in peruserdata:
- self._txn.execSQL(
+ yield self._txn.execSQL(
"""
insert into TRANSPARENCY
(TIME_RANGE_INSTANCE_ID, USER_ID, TRANSPARENT)
@@ -420,34 +434,21 @@
],
)
+
+ @inlineCallbacks
def component(self):
- return VComponent.fromString(self.iCalendarText())
+ returnValue(VComponent.fromString((yield self.iCalendarText())))
- def text(self):
- if self._objectText is None:
- text = self._txn.execSQL(
- "select ICALENDAR_TEXT from CALENDAR_OBJECT where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- self._objectText = text
- return text
- else:
- return self._objectText
- iCalendarText = text
+ iCalendarText = CommonObjectResource.text
- def uid(self):
- return self.component().resourceUID()
- def name(self):
- return self._name
+ @inlineCallbacks
+ def organizer(self):
+ returnValue((yield self.component()).getOrganizer())
- def componentType(self):
- return self.component().mainType()
- def organizer(self):
- return self.component().getOrganizer()
-
+ @inlineCallbacks
def createAttachmentWithName(self, name, contentType):
try:
@@ -456,56 +457,70 @@
pass
attachment = Attachment(self, name)
- self._txn.execSQL("""
+ yield self._txn.execSQL(
+ """
insert into ATTACHMENT (CALENDAR_OBJECT_RESOURCE_ID, CONTENT_TYPE,
- SIZE, MD5, PATH)
- values (%s, %s, %s, %s, %s)
+ SIZE, MD5, PATH) values (%s, %s, %s, %s, %s)
""",
[
- self._resourceID,
- generateContentType(contentType),
- 0,
- "",
+ self._resourceID, generateContentType(contentType), 0, "",
name,
]
)
- return attachment.store(contentType)
+ returnValue(attachment.store(contentType))
+
+ @inlineCallbacks
def removeAttachmentWithName(self, name):
attachment = Attachment(self, name)
self._txn.postCommit(attachment._path.remove)
- self._txn.execSQL("""
- delete from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s AND
- PATH = %s
- """, [self._resourceID, name])
+ yield self._txn.execSQL(
+ """
+ delete from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s AND
+ PATH = %s
+ """, [self._resourceID, name]
+ )
+
+ @inlineCallbacks
def attachmentWithName(self, name):
attachment = Attachment(self, name)
- if attachment._populate():
- return attachment
+ if (yield attachment._populate()):
+ returnValue(attachment)
else:
- return None
+ returnValue(None)
+
+ @inlineCallbacks
def attendeesCanManageAttachments(self):
- return self.component().hasPropertyInAnyComponent("X-APPLE-DROPBOX")
+ returnValue((yield self.component()).hasPropertyInAnyComponent(
+ "X-APPLE-DROPBOX"
+ ))
- def dropboxID(self):
- return dropboxIDFromCalendarObject(self)
+ dropboxID = dropboxIDFromCalendarObject
+
+
def _attachmentPathRoot(self):
attachmentRoot = self._txn._store.attachmentsPath
# Use directory hashing scheme based on owner user id
homeName = self._calendar.ownerHome().name()
return attachmentRoot.child(homeName[0:2]).child(homeName[2:4]).child(homeName).child(self.uid())
-
+
+
+ @inlineCallbacks
def attachments(self):
- rows = self._txn.execSQL("""
- select PATH from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s
- """, [self._resourceID])
+ rows = yield self._txn.execSQL(
+ """
+ select PATH from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s
+ """, [self._resourceID])
+ result = []
for row in rows:
- yield self.attachmentWithName(row[0])
+ result.append((yield self.attachmentWithName(row[0])))
+ returnValue(result)
+
def initPropertyStore(self, props):
# Setup peruser special properties
props.setSpecialProperties(
@@ -551,15 +566,21 @@
self.hash.update(data)
+ @inlineCallbacks
def loseConnection(self):
self.attachment._path.setContent(self.buf)
contentTypeString = generateContentType(self.contentType)
- self._txn.execSQL(
- "update ATTACHMENT set CONTENT_TYPE = %s, SIZE = %s, MD5 = %s, MODIFIED = timezone('UTC', CURRENT_TIMESTAMP) "
- "WHERE PATH = %s",
- [contentTypeString, len(self.buf), self.hash.hexdigest(), self.attachment.name()]
+ yield self._txn.execSQL(
+ """
+ update ATTACHMENT set CONTENT_TYPE = %s, SIZE = %s, MD5 = %s,
+ MODIFIED = timezone('UTC', CURRENT_TIMESTAMP) WHERE PATH = %s
+ """,
+ [contentTypeString, len(self.buf),
+ self.hash.hexdigest(), self.attachment.name()]
)
+
+
class Attachment(object):
implements(IAttachment)
@@ -574,24 +595,27 @@
return self._calendarObject._txn
+ @inlineCallbacks
def _populate(self):
"""
Execute necessary SQL queries to retrieve attributes.
@return: C{True} if this attachment exists, C{False} otherwise.
"""
- rows = self._txn.execSQL(
+ rows = yield self._txn.execSQL(
"""
select CONTENT_TYPE, SIZE, MD5, CREATED, MODIFIED from ATTACHMENT where PATH = %s
- """, [self._name])
+ """,
+ [self._name]
+ )
if not rows:
- return False
+ returnValue(False)
self._contentType = MimeType.fromString(rows[0][0])
self._size = rows[0][1]
self._md5 = rows[0][2]
self._created = datetimeMktime(datetime.datetime.strptime(rows[0][3], "%Y-%m-%d %H:%M:%S.%f"))
self._modified = datetimeMktime(datetime.datetime.strptime(rows[0][4], "%Y-%m-%d %H:%M:%S.%f"))
- return True
+ returnValue(True)
def name(self):
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -19,11 +19,8 @@
Tests for common calendar store API functions.
"""
-from zope.interface.verify import verifyObject
-from zope.interface.exceptions import (
- BrokenMethodImplementation, DoesNotImplement)
-
-from twisted.internet.defer import Deferred, inlineCallbacks
+from twisted.internet.defer import Deferred, inlineCallbacks, returnValue,\
+ maybeDeferred
from twisted.internet.protocol import Protocol
from txdav.idav import IPropertyStore, IDataStore, AlreadyFinishedError
@@ -36,6 +33,7 @@
from txdav.common.icommondatastore import NoSuchObjectResourceError
from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
from txdav.common.inotifications import INotificationObject
+from txdav.common.datastore.test.util import CommonCommonTests
from txdav.caldav.icalendarstore import (
ICalendarObject, ICalendarHome,
@@ -47,7 +45,6 @@
from twext.web2.dav.element.base import WebDAVUnknownElement
from twext.python.vcomponent import VComponent
-from twistedcaldav.notify import Notifier
from twistedcaldav.customxml import InviteNotification, InviteSummary
storePath = FilePath(__file__).parent().child("calendar_store")
@@ -146,24 +143,8 @@
-def assertProvides(testCase, interface, provider):
+class CommonTests(CommonCommonTests):
"""
- Verify that C{provider} properly provides C{interface}
-
- @type interface: L{zope.interface.Interface}
- @type provider: C{provider}
- """
- try:
- verifyObject(interface, provider)
- except BrokenMethodImplementation, e:
- testCase.fail(e)
- except DoesNotImplement, e:
- testCase.fail("%r does not provide %s.%s" %
- (provider, interface.__module__, interface.getName()))
-
-
-class CommonTests(object):
- """
Tests for common functionality of interfaces defined in
L{txdav.caldav.icalendarstore}.
"""
@@ -194,74 +175,37 @@
raise NotImplementedError()
- lastTransaction = None
- savedStore = None
-
- def transactionUnderTest(self):
- """
- Create a transaction from C{storeUnderTest} and save it as
- C[lastTransaction}. Also makes sure to use the same store, saving the
- value from C{storeUnderTest}.
- """
- if self.lastTransaction is not None:
- return self.lastTransaction
- if self.savedStore is None:
- self.savedStore = self.storeUnderTest()
- txn = self.lastTransaction = self.savedStore.newTransaction(self.id())
- return txn
-
-
- def commit(self):
- """
- Commit the last transaction created from C{transactionUnderTest}, and
- clear it.
- """
- self.lastTransaction.commit()
- self.lastTransaction = None
-
-
- def abort(self):
- """
- Abort the last transaction created from C[transactionUnderTest}, and
- clear it.
- """
- self.lastTransaction.abort()
- self.lastTransaction = None
-
-
- def setUp(self):
- self.notifierFactory = StubNotifierFactory()
-
-
- def tearDown(self):
- if self.lastTransaction is not None:
- self.commit()
-
-
+ @inlineCallbacks
def homeUnderTest(self):
"""
Get the calendar home detailed by C{requirements['home1']}.
"""
- return self.transactionUnderTest().calendarHomeWithUID("home1")
+ returnValue(
+ (yield self.transactionUnderTest().calendarHomeWithUID("home1"))
+ )
+ @inlineCallbacks
def calendarUnderTest(self):
"""
Get the calendar detailed by C{requirements['home1']['calendar_1']}.
"""
- return self.homeUnderTest().calendarWithName("calendar_1")
+ returnValue((yield
+ (yield self.homeUnderTest()).calendarWithName("calendar_1"))
+ )
+ @inlineCallbacks
def calendarObjectUnderTest(self):
"""
Get the calendar detailed by
C{requirements['home1']['calendar_1']['1.ics']}.
"""
- return self.calendarUnderTest().calendarObjectWithName("1.ics")
+ returnValue(
+ (yield (yield self.calendarUnderTest())
+ .calendarObjectWithName("1.ics")))
- assertProvides = assertProvides
-
def test_calendarStoreProvides(self):
"""
The calendar store provides L{IDataStore} and its required attributes.
@@ -281,225 +225,251 @@
self.assertProvides(ICalendarTransaction, txn)
+ @inlineCallbacks
def test_homeProvides(self):
"""
The calendar homes generated by the calendar store provide
L{ICalendarHome} and its required attributes.
"""
- self.assertProvides(ICalendarHome, self.homeUnderTest())
+ self.assertProvides(ICalendarHome, (yield self.homeUnderTest()))
+ @inlineCallbacks
def test_calendarProvides(self):
"""
The calendars generated by the calendar store provide L{ICalendar} and
its required attributes.
"""
- self.assertProvides(ICalendar, self.calendarUnderTest())
+ self.assertProvides(ICalendar, (yield self.calendarUnderTest()))
+ @inlineCallbacks
def test_calendarObjectProvides(self):
"""
The calendar objects generated by the calendar store provide
L{ICalendarObject} and its required attributes.
"""
- self.assertProvides(ICalendarObject, self.calendarObjectUnderTest())
+ self.assertProvides(
+ ICalendarObject, (yield self.calendarObjectUnderTest())
+ )
+ @inlineCallbacks
def notificationUnderTest(self):
txn = self.transactionUnderTest()
- notifications = txn.notificationsWithUID("home1")
+ notifications = yield txn.notificationsWithUID("home1")
inviteNotification = InviteNotification()
- notifications.writeNotificationObject("abc", inviteNotification,
+ yield notifications.writeNotificationObject("abc", inviteNotification,
inviteNotification.toxml())
- notificationObject = notifications.notificationObjectWithUID("abc")
- return notificationObject
+ notificationObject = yield notifications.notificationObjectWithUID("abc")
+ returnValue(notificationObject)
+ @inlineCallbacks
def test_notificationObjectProvides(self):
"""
The objects retrieved from the notification home (the object returned
from L{notificationsWithUID}) provide L{INotificationObject}.
"""
- notificationObject = self.notificationUnderTest()
+ notificationObject = yield self.notificationUnderTest()
self.assertProvides(INotificationObject, notificationObject)
+ @inlineCallbacks
def test_replaceNotification(self):
"""
L{INotificationCollection.writeNotificationObject} will silently
overwrite the notification object.
"""
- notifications = self.transactionUnderTest().notificationsWithUID(
+ notifications = yield self.transactionUnderTest().notificationsWithUID(
"home1"
)
inviteNotification = InviteNotification()
- notifications.writeNotificationObject("abc", inviteNotification,
+ yield notifications.writeNotificationObject("abc", inviteNotification,
inviteNotification.toxml())
inviteNotification2 = InviteNotification(InviteSummary("a summary"))
- notifications.writeNotificationObject(
+ yield notifications.writeNotificationObject(
"abc", inviteNotification, inviteNotification2.toxml())
- abc = notifications.notificationObjectWithUID("abc")
- self.assertEquals(abc.xmldata(), inviteNotification2.toxml())
+ abc = yield notifications.notificationObjectWithUID("abc")
+ self.assertEquals((yield abc.xmldata()), inviteNotification2.toxml())
+ @inlineCallbacks
def test_notificationObjectModified(self):
"""
The objects retrieved from the notification home have a C{modified}
method which returns the timestamp of their last modification.
"""
- notification = self.notificationUnderTest()
- self.assertIsInstance(notification.modified(), int)
+ notification = yield self.notificationUnderTest()
+ self.assertIsInstance((yield notification.modified()), int)
+ @inlineCallbacks
def test_notificationObjectParent(self):
"""
L{INotificationObject.notificationCollection} returns the
L{INotificationCollection} that the object was retrieved from.
"""
txn = self.transactionUnderTest()
- collection = txn.notificationsWithUID("home1")
- notification = self.notificationUnderTest()
+ collection = yield txn.notificationsWithUID("home1")
+ notification = yield self.notificationUnderTest()
self.assertIdentical(collection, notification.notificationCollection())
+ @inlineCallbacks
def test_notifierID(self):
- home = self.homeUnderTest()
+ home = yield self.homeUnderTest()
self.assertEquals(home.notifierID(), "CalDAV|home1")
- calendar = home.calendarWithName("calendar_1")
+ calendar = yield home.calendarWithName("calendar_1")
self.assertEquals(calendar.notifierID(), "CalDAV|home1")
self.assertEquals(calendar.notifierID(label="collection"), "CalDAV|home1/calendar_1")
+ @inlineCallbacks
def test_calendarHomeWithUID_exists(self):
"""
Finding an existing calendar home by UID results in an object that
provides L{ICalendarHome} and has a C{uid()} method that returns the
same value that was passed in.
"""
- calendarHome = (self.transactionUnderTest()
+ calendarHome = (yield self.transactionUnderTest()
.calendarHomeWithUID("home1"))
self.assertEquals(calendarHome.uid(), "home1")
self.assertProvides(ICalendarHome, calendarHome)
+ @inlineCallbacks
def test_calendarHomeWithUID_absent(self):
"""
L{ICommonStoreTransaction.calendarHomeWithUID} should return C{None}
when asked for a non-existent calendar home.
"""
txn = self.transactionUnderTest()
- self.assertEquals(txn.calendarHomeWithUID("xyzzy"), None)
+ self.assertEquals((yield txn.calendarHomeWithUID("xyzzy")), None)
+ @inlineCallbacks
def test_calendarWithName_exists(self):
"""
L{ICalendarHome.calendarWithName} returns an L{ICalendar} provider,
whose name matches the one passed in.
"""
- home = self.homeUnderTest()
+ home = yield self.homeUnderTest()
for name in home1_calendarNames:
- calendar = home.calendarWithName(name)
+ calendar = yield home.calendarWithName(name)
if calendar is None:
self.fail("calendar %r didn't exist" % (name,))
self.assertProvides(ICalendar, calendar)
self.assertEquals(calendar.name(), name)
+ @inlineCallbacks
def test_calendarRename(self):
"""
L{ICalendar.rename} changes the name of the L{ICalendar}.
"""
- home = self.homeUnderTest()
- calendar = home.calendarWithName("calendar_1")
- calendar.rename("some_other_name")
+ home = yield self.homeUnderTest()
+ calendar = yield home.calendarWithName("calendar_1")
+ yield calendar.rename("some_other_name")
+ @inlineCallbacks
def positiveAssertions():
self.assertEquals(calendar.name(), "some_other_name")
- self.assertEquals(calendar, home.calendarWithName("some_other_name"))
- self.assertEquals(None, home.calendarWithName("calendar_1"))
- positiveAssertions()
- self.commit()
- home = self.homeUnderTest()
- calendar = home.calendarWithName("some_other_name")
- positiveAssertions()
+ self.assertEquals(
+ calendar, (yield home.calendarWithName("some_other_name")))
+ self.assertEquals(
+ None, (yield home.calendarWithName("calendar_1")))
+ yield positiveAssertions()
+ yield self.commit()
+ home = yield self.homeUnderTest()
+ calendar = yield home.calendarWithName("some_other_name")
+ yield positiveAssertions()
# FIXME: revert
# FIXME: test for multiple renames
# FIXME: test for conflicting renames (a->b, c->a in the same txn)
+ @inlineCallbacks
def test_calendarWithName_absent(self):
"""
L{ICalendarHome.calendarWithName} returns C{None} for calendars which
do not exist.
"""
- self.assertEquals(self.homeUnderTest().calendarWithName("xyzzy"),
- None)
+ home = yield self.homeUnderTest()
+ calendar = yield home.calendarWithName("xyzzy")
+ self.assertEquals(calendar, None)
+ @inlineCallbacks
def test_createCalendarWithName_absent(self):
"""
L{ICalendarHome.createCalendarWithName} creates a new L{ICalendar} that
can be retrieved with L{ICalendarHome.calendarWithName}.
"""
- home = self.homeUnderTest()
+ home = yield self.homeUnderTest()
name = "new"
- self.assertIdentical(home.calendarWithName(name), None)
- home.createCalendarWithName(name)
- self.assertNotIdentical(home.calendarWithName(name), None)
+ self.assertIdentical((yield home.calendarWithName(name)), None)
+ yield home.createCalendarWithName(name)
+ self.assertNotIdentical((yield home.calendarWithName(name)), None)
+ @inlineCallbacks
def checkProperties():
- calendarProperties = home.calendarWithName(name).properties()
+ calendarProperties = (
+ yield home.calendarWithName(name)).properties()
self.assertEquals(
calendarProperties[
PropertyName.fromString(davxml.ResourceType.sname())
],
davxml.ResourceType.calendar #@UndefinedVariable
)
- checkProperties()
+ yield checkProperties()
- self.commit()
+ yield self.commit()
# Make sure notification fired after commit
self.assertEquals(self.notifierFactory.history,
[("update", "CalDAV|home1")])
# Make sure it's available in a new transaction; i.e. test the commit.
- home = self.homeUnderTest()
- self.assertNotIdentical(home.calendarWithName(name), None)
+ home = yield self.homeUnderTest()
+ self.assertNotIdentical((yield home.calendarWithName(name)), None)
# Sanity check: are the properties actually persisted? Check in
# subsequent transaction.
- checkProperties()
+ yield checkProperties()
# FIXME: no independent testing of the property store's persistence
# right now
+ @inlineCallbacks
def test_createCalendarWithName_exists(self):
"""
L{ICalendarHome.createCalendarWithName} raises
L{CalendarAlreadyExistsError} when the name conflicts with an already-
existing
"""
+ home = yield self.homeUnderTest()
for name in home1_calendarNames:
- self.assertRaises(
- HomeChildNameAlreadyExistsError,
- self.homeUnderTest().createCalendarWithName, name
+ yield self.failUnlessFailure(
+ maybeDeferred(home.createCalendarWithName, name),
+ HomeChildNameAlreadyExistsError
)
+ @inlineCallbacks
def test_removeCalendarWithName_exists(self):
"""
L{ICalendarHome.removeCalendarWithName} removes a calendar that already
exists.
"""
- home = self.homeUnderTest()
+ home = yield self.homeUnderTest()
# FIXME: test transactions
for name in home1_calendarNames:
- self.assertNotIdentical(home.calendarWithName(name), None)
- home.removeCalendarWithName(name)
- self.assertEquals(home.calendarWithName(name), None)
+ self.assertNotIdentical((yield home.calendarWithName(name)), None)
+ yield home.removeCalendarWithName(name)
+ self.assertEquals((yield home.calendarWithName(name)), None)
- self.commit()
+ yield self.commit()
# Make sure notification fired after commit
self.assertEquals(
@@ -515,27 +485,31 @@
)
+ @inlineCallbacks
def test_removeCalendarWithName_absent(self):
"""
Attempt to remove an non-existing calendar should raise.
"""
- home = self.homeUnderTest()
- self.assertRaises(NoSuchHomeChildError,
- home.removeCalendarWithName, "xyzzy")
+ home = yield self.homeUnderTest()
+ yield self.failUnlessFailure(
+ maybeDeferred(home.removeCalendarWithName, "xyzzy"),
+ NoSuchHomeChildError
+ )
+ @inlineCallbacks
def test_calendarObjects(self):
"""
L{ICalendar.calendarObjects} will enumerate the calendar objects present
in the filesystem, in name order, but skip those with hidden names.
"""
- calendar1 = self.calendarUnderTest()
- calendarObjects = list(calendar1.calendarObjects())
+ calendar1 = yield self.calendarUnderTest()
+ calendarObjects = list((yield calendar1.calendarObjects()))
for calendarObject in calendarObjects:
self.assertProvides(ICalendarObject, calendarObject)
self.assertEquals(
- calendar1.calendarObjectWithName(calendarObject.name()),
+ (yield calendar1.calendarObjectWithName(calendarObject.name())),
calendarObject
)
@@ -545,72 +519,77 @@
)
+ @inlineCallbacks
def test_calendarObjectsWithRemovedObject(self):
"""
L{ICalendar.calendarObjects} skips those objects which have been
removed by L{Calendar.removeCalendarObjectWithName} in the same
transaction, even if it has not yet been committed.
"""
- calendar1 = self.calendarUnderTest()
+ calendar1 = yield self.calendarUnderTest()
calendar1.removeCalendarObjectWithName("2.ics")
- calendarObjects = list(calendar1.calendarObjects())
+ calendarObjects = list((yield calendar1.calendarObjects()))
self.assertEquals(set(o.name() for o in calendarObjects),
set(calendar1_objectNames) - set(["2.ics"]))
+ @inlineCallbacks
def test_ownerCalendarHome(self):
"""
L{ICalendar.ownerCalendarHome} should match the home UID.
"""
self.assertEquals(
- self.calendarUnderTest().ownerCalendarHome().uid(),
- self.homeUnderTest().uid()
+ (yield self.calendarUnderTest()).ownerCalendarHome().uid(),
+ (yield self.homeUnderTest()).uid()
)
+ @inlineCallbacks
def test_calendarObjectWithName_exists(self):
"""
L{ICalendar.calendarObjectWithName} returns an L{ICalendarObject}
provider for calendars which already exist.
"""
- calendar1 = self.calendarUnderTest()
+ calendar1 = yield self.calendarUnderTest()
for name in calendar1_objectNames:
- calendarObject = calendar1.calendarObjectWithName(name)
+ calendarObject = yield calendar1.calendarObjectWithName(name)
self.assertProvides(ICalendarObject, calendarObject)
self.assertEquals(calendarObject.name(), name)
# FIXME: add more tests based on CommonTests.requirements
+ @inlineCallbacks
def test_calendarObjectWithName_absent(self):
"""
L{ICalendar.calendarObjectWithName} returns C{None} for calendars which
don't exist.
"""
- calendar1 = self.calendarUnderTest()
- self.assertEquals(calendar1.calendarObjectWithName("xyzzy"), None)
+ calendar1 = yield self.calendarUnderTest()
+ self.assertEquals((yield calendar1.calendarObjectWithName("xyzzy")), None)
+ @inlineCallbacks
def test_removeCalendarObjectWithUID_exists(self):
"""
Remove an existing calendar object.
"""
- calendar = self.calendarUnderTest()
+ calendar = yield self.calendarUnderTest()
for name in calendar1_objectNames:
uid = (u'uid' + name.rstrip(".ics"))
- self.assertNotIdentical(calendar.calendarObjectWithUID(uid),
+ self.assertNotIdentical((yield calendar.calendarObjectWithUID(uid)),
None)
- calendar.removeCalendarObjectWithUID(uid)
+ yield calendar.removeCalendarObjectWithUID(uid)
self.assertEquals(
- calendar.calendarObjectWithUID(uid),
+ (yield calendar.calendarObjectWithUID(uid)),
None
)
self.assertEquals(
- calendar.calendarObjectWithName(name),
+ (yield calendar.calendarObjectWithName(name)),
None
)
# Make sure notifications are fired after commit
- self.commit()
+ yield self.commit()
self.assertEquals(
self.notifierFactory.history,
[
@@ -623,52 +602,60 @@
]
)
+ @inlineCallbacks
def test_removeCalendarObjectWithName_exists(self):
"""
Remove an existing calendar object.
"""
- calendar = self.calendarUnderTest()
+ calendar = yield self.calendarUnderTest()
for name in calendar1_objectNames:
self.assertNotIdentical(
- calendar.calendarObjectWithName(name), None
+ (yield calendar.calendarObjectWithName(name)), None
)
- calendar.removeCalendarObjectWithName(name)
+ yield calendar.removeCalendarObjectWithName(name)
self.assertIdentical(
- calendar.calendarObjectWithName(name), None
+ (yield calendar.calendarObjectWithName(name)), None
)
+ @inlineCallbacks
def test_removeCalendarObjectWithName_absent(self):
"""
Attempt to remove an non-existing calendar object should raise.
"""
- calendar = self.calendarUnderTest()
- self.assertRaises(
- NoSuchObjectResourceError,
- calendar.removeCalendarObjectWithName, "xyzzy"
+ calendar = yield self.calendarUnderTest()
+ yield self.failUnlessFailure(
+ maybeDeferred(calendar.removeCalendarObjectWithName, "xyzzy"),
+ NoSuchObjectResourceError
)
+ @inlineCallbacks
def test_calendarName(self):
"""
L{Calendar.name} reflects the name of the calendar.
"""
- self.assertEquals(self.calendarUnderTest().name(), "calendar_1")
+ self.assertEquals((yield self.calendarUnderTest()).name(), "calendar_1")
+ @inlineCallbacks
def test_calendarObjectName(self):
"""
L{ICalendarObject.name} reflects the name of the calendar object.
"""
- self.assertEquals(self.calendarObjectUnderTest().name(), "1.ics")
+ self.assertEquals(
+ (yield self.calendarObjectUnderTest()).name(),
+ "1.ics"
+ )
+ @inlineCallbacks
def test_component(self):
"""
L{ICalendarObject.component} returns a L{VComponent} describing the
calendar data underlying that calendar object.
"""
- component = self.calendarObjectUnderTest().component()
+ component = yield (yield self.calendarObjectUnderTest()).component()
self.failUnless(
isinstance(component, VComponent),
@@ -680,24 +667,28 @@
self.assertEquals(component.resourceUID(), "uid1")
+ @inlineCallbacks
def test_iCalendarText(self):
"""
L{ICalendarObject.iCalendarText} returns a C{str} describing the same
data provided by L{ICalendarObject.component}.
"""
- text = self.calendarObjectUnderTest().iCalendarText()
+ text = yield (yield self.calendarObjectUnderTest()).iCalendarText()
self.assertIsInstance(text, str)
self.failUnless(text.startswith("BEGIN:VCALENDAR\r\n"))
self.assertIn("\r\nUID:uid1\r\n", text)
self.failUnless(text.endswith("\r\nEND:VCALENDAR\r\n"))
+ @inlineCallbacks
def test_calendarObjectUID(self):
"""
L{ICalendarObject.uid} returns a C{str} describing the C{UID} property
of the calendar object's component.
"""
- self.assertEquals(self.calendarObjectUnderTest().uid(), "uid1")
+ self.assertEquals(
+ (yield self.calendarObjectUnderTest()).uid(), "uid1"
+ )
def test_organizer(self):
@@ -707,20 +698,23 @@
component.
"""
self.assertEquals(
- self.calendarObjectUnderTest().organizer(),
+ (yield self.calendarObjectUnderTest()).organizer(),
"mailto:wsanchez at apple.com"
)
+ @inlineCallbacks
def test_calendarObjectWithUID_absent(self):
"""
L{ICalendar.calendarObjectWithUID} returns C{None} for calendars which
don't exist.
"""
- calendar1 = self.calendarUnderTest()
- self.assertEquals(calendar1.calendarObjectWithUID("xyzzy"), None)
+ calendar1 = yield self.calendarUnderTest()
+ self.assertEquals((yield calendar1.calendarObjectWithUID("xyzzy")),
+ None)
+ @inlineCallbacks
def test_calendars(self):
"""
L{ICalendarHome.calendars} returns an iterable of L{ICalendar}
@@ -729,13 +723,13 @@
"""
# Add a dot directory to make sure we don't find it
# self.home1._path.child(".foo").createDirectory()
- home = self.homeUnderTest()
- calendars = list(home.calendars())
+ home = yield self.homeUnderTest()
+ calendars = list((yield home.calendars()))
for calendar in calendars:
self.assertProvides(ICalendar, calendar)
self.assertEquals(calendar,
- home.calendarWithName(calendar.name()))
+ (yield home.calendarWithName(calendar.name())))
self.assertEquals(
set(c.name() for c in calendars),
@@ -743,33 +737,39 @@
)
+ @inlineCallbacks
def test_calendarsAfterAddCalendar(self):
"""
L{ICalendarHome.calendars} includes calendars recently added with
L{ICalendarHome.createCalendarWithName}.
"""
- home = self.homeUnderTest()
- before = set(x.name() for x in home.calendars())
- home.createCalendarWithName("new-name")
- after = set(x.name() for x in home.calendars())
+ home = yield self.homeUnderTest()
+ allCalendars = yield home.calendars()
+ before = set(x.name() for x in allCalendars)
+ yield home.createCalendarWithName("new-name")
+ allCalendars = yield home.calendars()
+ after = set(x.name() for x in allCalendars)
self.assertEquals(before | set(['new-name']), after)
+ @inlineCallbacks
def test_createCalendarObjectWithName_absent(self):
"""
L{ICalendar.createCalendarObjectWithName} creates a new
L{ICalendarObject}.
"""
- calendar1 = self.calendarUnderTest()
+ calendar1 = yield self.calendarUnderTest()
name = "4.ics"
- self.assertIdentical(calendar1.calendarObjectWithName(name), None)
+ self.assertIdentical(
+ (yield calendar1.calendarObjectWithName(name)), None
+ )
component = VComponent.fromString(event4_text)
- calendar1.createCalendarObjectWithName(name, component)
+ yield calendar1.createCalendarObjectWithName(name, component)
- calendarObject = calendar1.calendarObjectWithName(name)
- self.assertEquals(calendarObject.component(), component)
+ calendarObject = yield calendar1.calendarObjectWithName(name)
+ self.assertEquals((yield calendarObject.component()), component)
- self.commit()
+ yield self.commit()
# Make sure notifications fire after commit
self.assertEquals(
@@ -781,61 +781,64 @@
)
+ @inlineCallbacks
def test_createCalendarObjectWithName_exists(self):
"""
L{ICalendar.createCalendarObjectWithName} raises
L{CalendarObjectNameAlreadyExistsError} if a calendar object with the
given name already exists in that calendar.
"""
- cal = self.calendarUnderTest()
+ cal = yield self.calendarUnderTest()
comp = VComponent.fromString(event4_text)
- self.assertRaises(
+ yield self.failUnlessFailure(
+ maybeDeferred(cal.createCalendarObjectWithName, "1.ics", comp),
ObjectResourceNameAlreadyExistsError,
- cal.createCalendarObjectWithName,
- "1.ics", comp
)
+ @inlineCallbacks
def test_createCalendarObjectWithName_invalid(self):
"""
L{ICalendar.createCalendarObjectWithName} raises
L{InvalidCalendarComponentError} if presented with invalid iCalendar
text.
"""
- self.assertRaises(
+ yield self.failUnlessFailure(
+ maybeDeferred((yield self.calendarUnderTest()).createCalendarObjectWithName,
+ "new", VComponent.fromString(event4notCalDAV_text)),
InvalidObjectResourceError,
- self.calendarUnderTest().createCalendarObjectWithName,
- "new", VComponent.fromString(event4notCalDAV_text)
)
-
+ @inlineCallbacks
def test_setComponent_invalid(self):
"""
L{ICalendarObject.setComponent} raises L{InvalidICalendarDataError} if
presented with invalid iCalendar text.
"""
- calendarObject = self.calendarObjectUnderTest()
- self.assertRaises(
+ calendarObject = yield self.calendarObjectUnderTest()
+ yield self.failUnlessFailure(
+ maybeDeferred(calendarObject.setComponent,
+ VComponent.fromString(event4notCalDAV_text)),
InvalidObjectResourceError,
- calendarObject.setComponent,
- VComponent.fromString(event4notCalDAV_text)
)
+ @inlineCallbacks
def test_setComponent_uidchanged(self):
"""
L{ICalendarObject.setComponent} raises L{InvalidCalendarComponentError}
when given a L{VComponent} whose UID does not match its existing UID.
"""
- calendar1 = self.calendarUnderTest()
+ calendar1 = yield self.calendarUnderTest()
component = VComponent.fromString(event4_text)
- calendarObject = calendar1.calendarObjectWithName("1.ics")
- self.assertRaises(
+ calendarObject = yield calendar1.calendarObjectWithName("1.ics")
+ yield self.failUnlessFailure(
+ maybeDeferred(calendarObject.setComponent, component),
InvalidObjectResourceError,
- calendarObject.setComponent, component
)
+ @inlineCallbacks
def test_calendarHomeWithUID_create(self):
"""
L{ICommonStoreTransaction.calendarHomeWithUID} with C{create=True}
@@ -843,25 +846,27 @@
"""
txn = self.transactionUnderTest()
noHomeUID = "xyzzy"
- calendarHome = txn.calendarHomeWithUID(
+ calendarHome = yield txn.calendarHomeWithUID(
noHomeUID,
create=True
)
+ @inlineCallbacks
def readOtherTxn():
otherTxn = self.savedStore.newTransaction(self.id() + "other txn")
self.addCleanup(otherTxn.commit)
- return otherTxn.calendarHomeWithUID(noHomeUID)
+ returnValue((yield otherTxn.calendarHomeWithUID(noHomeUID)))
self.assertProvides(ICalendarHome, calendarHome)
# Default calendar should be automatically created.
self.assertProvides(ICalendar,
- calendarHome.calendarWithName("calendar"))
+ (yield calendarHome.calendarWithName("calendar")))
# A concurrent transaction shouldn't be able to read it yet:
- self.assertIdentical(readOtherTxn(), None)
- self.commit()
+ self.assertIdentical((yield readOtherTxn()), None)
+ yield self.commit()
# But once it's committed, other transactions should see it.
- self.assertProvides(ICalendarHome, readOtherTxn())
+ self.assertProvides(ICalendarHome, (yield readOtherTxn()))
+ @inlineCallbacks
def test_setComponent(self):
"""
L{CalendarObject.setComponent} changes the result of
@@ -869,18 +874,18 @@
"""
component = VComponent.fromString(event1modified_text)
- calendar1 = self.calendarUnderTest()
- calendarObject = calendar1.calendarObjectWithName("1.ics")
- oldComponent = calendarObject.component()
+ calendar1 = yield self.calendarUnderTest()
+ calendarObject = yield calendar1.calendarObjectWithName("1.ics")
+ oldComponent = yield calendarObject.component()
self.assertNotEqual(component, oldComponent)
- calendarObject.setComponent(component)
- self.assertEquals(calendarObject.component(), component)
+ yield calendarObject.setComponent(component)
+ self.assertEquals((yield calendarObject.component()), component)
# Also check a new instance
- calendarObject = calendar1.calendarObjectWithName("1.ics")
- self.assertEquals(calendarObject.component(), component)
+ calendarObject = yield calendar1.calendarObjectWithName("1.ics")
+ self.assertEquals((yield calendarObject.component()), component)
- self.commit()
+ yield self.commit()
# Make sure notification fired after commit
self.assertEquals(
@@ -901,40 +906,45 @@
self.assertProvides(IPropertyStore, properties)
+ @inlineCallbacks
def test_homeProperties(self):
"""
L{ICalendarHome.properties} returns a property store.
"""
- self.checkPropertiesMethod(self.homeUnderTest())
+ self.checkPropertiesMethod((yield self.homeUnderTest()))
+ @inlineCallbacks
def test_calendarProperties(self):
"""
L{ICalendar.properties} returns a property store.
"""
- self.checkPropertiesMethod(self.calendarUnderTest())
+ self.checkPropertiesMethod((yield self.calendarUnderTest()))
+ @inlineCallbacks
def test_calendarObjectProperties(self):
"""
L{ICalendarObject.properties} returns a property store.
"""
- self.checkPropertiesMethod(self.calendarObjectUnderTest())
+ self.checkPropertiesMethod((yield self.calendarObjectUnderTest()))
+ @inlineCallbacks
def test_newCalendarObjectProperties(self):
"""
L{ICalendarObject.properties} returns an empty property store for a
calendar object which has been created but not committed.
"""
- calendar = self.calendarUnderTest()
- calendar.createCalendarObjectWithName(
+ calendar = yield self.calendarUnderTest()
+ yield calendar.createCalendarObjectWithName(
"4.ics", VComponent.fromString(event4_text)
)
- newEvent = calendar.calendarObjectWithName("4.ics")
+ newEvent = yield calendar.calendarObjectWithName("4.ics")
self.assertEquals(newEvent.properties().items(), [])
+ @inlineCallbacks
def test_setComponentPreservesProperties(self):
"""
L{ICalendarObject.setComponent} preserves properties.
@@ -948,16 +958,16 @@
propertyContent.name = propertyName.name
propertyContent.namespace = propertyName.namespace
- self.calendarObjectUnderTest().properties()[
+ (yield self.calendarObjectUnderTest()).properties()[
propertyName] = propertyContent
- self.commit()
+ yield self.commit()
# Sanity check; are properties even readable in a separate transaction?
# Should probably be a separate test.
self.assertEquals(
- self.calendarObjectUnderTest().properties()[propertyName],
+ (yield self.calendarObjectUnderTest()).properties()[propertyName],
propertyContent)
- obj = self.calendarObjectUnderTest()
- event1_text = obj.iCalendarText()
+ obj = yield self.calendarObjectUnderTest()
+ event1_text = yield obj.iCalendarText()
event1_text_withDifferentSubject = event1_text.replace(
"SUMMARY:CalDAV protocol updates",
"SUMMARY:Changed"
@@ -969,9 +979,9 @@
# Putting everything into a separate transaction to account for any
# caching that may take place.
- self.commit()
+ yield self.commit()
self.assertEquals(
- self.calendarObjectUnderTest().properties()[propertyName],
+ (yield self.calendarObjectUnderTest()).properties()[propertyName],
propertyContent
)
@@ -1014,20 +1024,23 @@
END:VCALENDAR
""".strip().split("\n"))
+
+ @inlineCallbacks
def test_dropboxID(self):
"""
L{ICalendarObject.dropboxID} should synthesize its dropbox from the X
-APPLE-DROPBOX property, if available.
"""
- cal = self.calendarUnderTest()
- cal.createCalendarObjectWithName("drop.ics", VComponent.fromString(
+ cal = yield self.calendarUnderTest()
+ yield cal.createCalendarObjectWithName("drop.ics", VComponent.fromString(
self.eventWithDropbox
)
)
- obj = cal.calendarObjectWithName("drop.ics")
- self.assertEquals(obj.dropboxID(), "some-dropbox-id")
+ obj = yield cal.calendarObjectWithName("drop.ics")
+ self.assertEquals((yield obj.dropboxID()), "some-dropbox-id")
+ @inlineCallbacks
def test_indexByDropboxProperty(self):
"""
L{ICalendarHome.calendarObjectWithDropboxID} will return a calendar
@@ -1035,17 +1048,17 @@
-APPLE-DROPBOX} property URI.
"""
objName = "with-dropbox.ics"
- cal = self.calendarUnderTest()
+ cal = yield self.calendarUnderTest()
cal.createCalendarObjectWithName(
objName, VComponent.fromString(
self.eventWithDropbox
)
)
- self.commit()
- home = self.homeUnderTest()
- cal = self.calendarUnderTest()
- fromName = cal.calendarObjectWithName(objName)
- fromDropbox = home.calendarObjectWithDropboxID("some-dropbox-id")
+ yield self.commit()
+ home = yield self.homeUnderTest()
+ cal = yield self.calendarUnderTest()
+ fromName = yield cal.calendarObjectWithName(objName)
+ fromDropbox = yield home.calendarObjectWithDropboxID("some-dropbox-id")
self.assertEquals(fromName, fromDropbox)
@@ -1054,12 +1067,14 @@
"""
Common logic for attachment-creation tests.
"""
- obj = self.calendarObjectUnderTest()
- t = obj.createAttachmentWithName("new.attachment", MimeType("text", "x-fixture"))
+ obj = yield self.calendarObjectUnderTest()
+ t = yield obj.createAttachmentWithName(
+ "new.attachment", MimeType("text", "x-fixture")
+ )
t.write("new attachment")
t.write(" text")
t.loseConnection()
- obj = refresh(obj)
+ obj = yield refresh(obj)
class CaptureProtocol(Protocol):
buf = ''
def dataReceived(self, data):
@@ -1068,7 +1083,7 @@
self.deferred.callback(self.buf)
capture = CaptureProtocol()
capture.deferred = Deferred()
- attachment = obj.attachmentWithName("new.attachment")
+ attachment = yield obj.attachmentWithName("new.attachment")
self.assertProvides(IAttachment, attachment)
attachment.retrieve(capture)
data = yield capture.deferred
@@ -1078,7 +1093,7 @@
self.assertEquals(contentType, MimeType("text", "x-fixture"))
self.assertEquals(attachment.md5(), '50a9f27aeed9247a0833f30a631f1858')
self.assertEquals(
- [attachment.name() for attachment in obj.attachments()],
+ [attachment.name() for attachment in (yield obj.attachments())],
['new.attachment']
)
@@ -1098,9 +1113,11 @@
L{IAttachment} object that can be retrieved by
L{ICalendarObject.attachmentWithName} in subsequent transactions.
"""
+ @inlineCallbacks
def refresh(obj):
- self.commit()
- return self.calendarObjectUnderTest()
+ yield self.commit()
+ result = yield self.calendarObjectUnderTest()
+ returnValue(result)
return self.createAttachmentTest(refresh)
@@ -1109,14 +1126,15 @@
L{ICalendarObject.removeAttachmentWithName} will remove the calendar
object with the given name.
"""
+ @inlineCallbacks
def deleteIt(ignored):
- obj = self.calendarObjectUnderTest()
+ obj = yield self.calendarObjectUnderTest()
obj.removeAttachmentWithName("new.attachment")
- obj = refresh(obj)
+ obj = yield refresh(obj)
self.assertIdentical(
- None, obj.attachmentWithName("new.attachment")
+ None, (yield obj.attachmentWithName("new.attachment"))
)
- self.assertEquals(list(obj.attachments()), [])
+ self.assertEquals(list((yield obj.attachments())), [])
return self.test_createAttachmentCommit().addCallback(deleteIt)
@@ -1125,73 +1143,92 @@
L{ICalendarObject.removeAttachmentWithName} will remove the calendar
object with the given name. (After commit, it will still be gone.)
"""
+ @inlineCallbacks
def refresh(obj):
- self.commit()
- return self.calendarObjectUnderTest()
+ yield self.commit()
+ result = yield self.calendarObjectUnderTest()
+ returnValue(result)
return self.test_removeAttachmentWithName(refresh)
+ @inlineCallbacks
def test_noDropboxCalendar(self):
"""
L{ICalendarObject.createAttachmentWithName} may create a directory
named 'dropbox', but this should not be seen as a calendar by
L{ICalendarHome.calendarWithName} or L{ICalendarHome.calendars}.
"""
- obj = self.calendarObjectUnderTest()
- t = obj.createAttachmentWithName("new.attachment", MimeType("text", "plain"))
+ obj = yield self.calendarObjectUnderTest()
+ t = yield obj.createAttachmentWithName(
+ "new.attachment", MimeType("text", "plain")
+ )
t.write("new attachment text")
t.loseConnection()
- self.commit()
- self.assertEquals(self.homeUnderTest().calendarWithName("dropbox"),
- None)
+ yield self.commit()
+ home = (yield self.homeUnderTest())
+ calendars = (yield home.calendars())
+ self.assertEquals((yield home.calendarWithName("dropbox")), None)
self.assertEquals(
- set([n.name() for n in self.homeUnderTest().calendars()]),
+ set([n.name() for n in calendars]),
set(home1_calendarNames))
+ @inlineCallbacks
def test_finishedOnCommit(self):
"""
Calling L{ITransaction.abort} or L{ITransaction.commit} after
L{ITransaction.commit} has already been called raises an
L{AlreadyFinishedError}.
"""
- self.calendarObjectUnderTest()
+ yield self.calendarObjectUnderTest()
txn = self.lastTransaction
- self.commit()
- self.assertRaises(AlreadyFinishedError, txn.commit)
- self.assertRaises(AlreadyFinishedError, txn.abort)
+ yield self.commit()
+
+ yield self.failUnlessFailure(
+ maybeDeferred(txn.commit),
+ AlreadyFinishedError
+ )
+ yield self.failUnlessFailure(
+ maybeDeferred(txn.abort),
+ AlreadyFinishedError
+ )
+ @inlineCallbacks
def test_dontLeakCalendars(self):
"""
Calendars in one user's calendar home should not show up in another
user's calendar home.
"""
- home2 = self.transactionUnderTest().calendarHomeWithUID(
+ home2 = yield self.transactionUnderTest().calendarHomeWithUID(
"home2", create=True)
- self.assertIdentical(home2.calendarWithName("calendar_1"), None)
+ self.assertIdentical(
+ (yield home2.calendarWithName("calendar_1")), None)
+ @inlineCallbacks
def test_dontLeakObjects(self):
"""
Calendar objects in one user's calendar should not show up in another
user's via uid or name queries.
"""
- home1 = self.homeUnderTest()
- home2 = self.transactionUnderTest().calendarHomeWithUID(
+ home1 = yield self.homeUnderTest()
+ home2 = yield self.transactionUnderTest().calendarHomeWithUID(
"home2", create=True)
- calendar1 = home1.calendarWithName("calendar_1")
- calendar2 = home2.calendarWithName("calendar")
- objects = list(home2.calendarWithName("calendar").calendarObjects())
+ calendar1 = yield home1.calendarWithName("calendar_1")
+ calendar2 = yield home2.calendarWithName("calendar")
+ objects = list(
+ (yield (yield home2.calendarWithName("calendar")).calendarObjects()))
self.assertEquals(objects, [])
for resourceName in self.requirements['home1']['calendar_1'].keys():
- obj = calendar1.calendarObjectWithName(resourceName)
+ obj = yield calendar1.calendarObjectWithName(resourceName)
self.assertIdentical(
- calendar2.calendarObjectWithName(resourceName), None)
+ (yield calendar2.calendarObjectWithName(resourceName)), None)
self.assertIdentical(
- calendar2.calendarObjectWithUID(obj.uid()), None)
+ (yield calendar2.calendarObjectWithUID(obj.uid())), None)
+ @inlineCallbacks
def test_eachCalendarHome(self):
"""
L{ICalendarTransaction.eachCalendarHome} returns an iterator that
@@ -1201,11 +1238,13 @@
additionalUIDs = set('alpha-uid home2 home3 beta-uid'.split())
txn = self.transactionUnderTest()
for name in additionalUIDs:
- txn.calendarHomeWithUID(name, create=True)
- self.commit()
+ # maybe it's not actually necessary to yield (i.e. wait) for each
+ # one? commit() should wait for all of them.
+ yield txn.calendarHomeWithUID(name, create=True)
+ yield self.commit()
foundUIDs = set([])
lastTxn = None
- for txn, home in self.storeUnderTest().eachCalendarHome():
+ for txn, home in (yield self.storeUnderTest().eachCalendarHome()):
self.addCleanup(txn.commit)
foundUIDs.add(home.uid())
self.assertNotIdentical(lastTxn, txn)
@@ -1219,18 +1258,3 @@
-class StubNotifierFactory(object):
-
- """ For testing push notifications without an XMPP server """
-
- def __init__(self):
- self.reset()
-
- def newNotifier(self, label="default", id=None, prefix=None):
- return Notifier(self, label=label, id=id, prefix=prefix)
-
- def send(self, op, id):
- self.history.append((op, id))
-
- def reset(self):
- self.history = []
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -22,9 +22,11 @@
# deleted and replaced with either implementation-specific methods on
# FileStorageTests, or implementation-agnostic methods on CommonTests.
-from twext.python.filepath import CachingFilePath as FilePath
from twisted.trial import unittest
+from twisted.internet.defer import inlineCallbacks
+from twext.python.filepath import CachingFilePath as FilePath
+
from twext.python.vcomponent import VComponent
from txdav.common.icommondatastore import HomeChildNameNotAllowedError
@@ -63,21 +65,22 @@
storePath.copyTo(calendarPath)
test.calendarStore = CalendarStore(storeRootPath, test.notifierFactory)
- test.txn = test.calendarStore.newTransaction()
+ test.txn = test.calendarStore.newTransaction(test.id() + "(old)")
assert test.calendarStore is not None, "No calendar store?"
+ at inlineCallbacks
def setUpHome1(test):
setUpCalendarStore(test)
- test.home1 = test.txn.calendarHomeWithUID("home1")
+ test.home1 = yield test.txn.calendarHomeWithUID("home1")
assert test.home1 is not None, "No calendar home?"
-
+ at inlineCallbacks
def setUpCalendar1(test):
- setUpHome1(test)
- test.calendar1 = test.home1.calendarWithName("calendar_1")
+ yield setUpHome1(test)
+ test.calendar1 = yield test.home1.calendarWithName("calendar_1")
assert test.calendar1 is not None, "No calendar?"
@@ -109,7 +112,7 @@
notifierFactory = None
def setUp(self):
- setUpHome1(self)
+ return setUpHome1(self)
def test_init(self):
@@ -167,7 +170,7 @@
notifierFactory = None
def setUp(self):
- setUpCalendar1(self)
+ return setUpCalendar1(self)
def test_init(self):
@@ -185,29 +188,31 @@
)
+ @inlineCallbacks
def test_useIndexImmediately(self):
"""
L{Calendar._index} is usable in the same transaction it is created, with
a temporary filename.
"""
self.home1.createCalendarWithName("calendar2")
- calendar = self.home1.calendarWithName("calendar2")
+ calendar = yield self.home1.calendarWithName("calendar2")
index = calendar._index
- self.assertEquals(set(index.calendarObjects()),
- set(calendar.calendarObjects()))
- self.txn.commit()
+ yield self.assertEquals(set((yield index.calendarObjects())),
+ set((yield calendar.calendarObjects())))
+ yield self.txn.commit()
self.txn = self.calendarStore.newTransaction()
- self.home1 = self.txn.calendarHomeWithUID("home1")
- calendar = self.home1.calendarWithName("calendar2")
+ self.home1 = yield self.txn.calendarHomeWithUID("home1")
+ calendar = yield self.home1.calendarWithName("calendar2")
# FIXME: we should be curating our own index here, but in order to fix
# that the code in the old implicit scheduler needs to change. This
# test would be more effective if there were actually some objects in
# this list.
index = calendar._index
- self.assertEquals(set(index.calendarObjects()),
- set(calendar.calendarObjects()))
+ self.assertEquals(set((yield index.calendarObjects())),
+ set((yield calendar.calendarObjects())))
+ @inlineCallbacks
def test_calendarObjectWithName_dot(self):
"""
Filenames starting with "." are reserved by this
@@ -216,22 +221,25 @@
"""
name = ".foo.ics"
self.home1._path.child(name).touch()
- self.assertEquals(self.calendar1.calendarObjectWithName(name), None)
+ self.assertEquals(
+ (yield self.calendar1.calendarObjectWithName(name)),
+ None)
@featureUnimplemented
+ @inlineCallbacks
def test_calendarObjectWithUID_exists(self):
"""
Find existing calendar object by name.
"""
- calendarObject = self.calendar1.calendarObjectWithUID("1")
+ calendarObject = yield self.calendar1.calendarObjectWithUID("1")
self.failUnless(
isinstance(calendarObject, CalendarObject),
calendarObject
)
self.assertEquals(
calendarObject.component(),
- self.calendar1.calendarObjectWithName("1.ics").component()
+ (yield self.calendar1.calendarObjectWithName("1.ics")).component()
)
@@ -249,13 +257,14 @@
@featureUnimplemented
+ @inlineCallbacks
def test_createCalendarObjectWithName_uidconflict(self):
"""
Attempt to create a calendar object with a conflicting UID
should raise.
"""
name = "foo.ics"
- assert self.calendar1.calendarObjectWithName(name) is None
+ assert (yield self.calendar1.calendarObjectWithName(name)) is None
component = VComponent.fromString(event1modified_text)
self.assertRaises(
ObjectResourceUIDAlreadyExistsError,
@@ -264,6 +273,7 @@
)
+ @inlineCallbacks
def test_removeCalendarObject_delayedEffect(self):
"""
Removing a calendar object should not immediately remove the underlying
@@ -271,7 +281,7 @@
"""
self.calendar1.removeCalendarObjectWithName("2.ics")
self.failUnless(self.calendar1._path.child("2.ics").exists())
- self.txn.commit()
+ yield self.txn.commit()
self.failIf(self.calendar1._path.child("2.ics").exists())
@@ -289,16 +299,22 @@
)
+ counter = 0
+ @inlineCallbacks
def _refresh(self):
"""
Re-read the (committed) home1 and calendar1 objects in a new
transaction.
"""
- self.txn = self.calendarStore.newTransaction()
- self.home1 = self.txn.calendarHomeWithUID("home1")
- self.calendar1 = self.home1.calendarWithName("calendar_1")
+ self.counter += 1
+ self.txn = self.calendarStore.newTransaction(
+ self.id() + " (old #" + str(self.counter) + ")"
+ )
+ self.home1 = yield self.txn.calendarHomeWithUID("home1")
+ self.calendar1 = yield self.home1.calendarWithName("calendar_1")
+ @inlineCallbacks
def test_undoCreateCalendarObject(self):
"""
If a calendar object is created as part of a transaction, it will be
@@ -306,19 +322,22 @@
"""
# Make sure that the calendar home is actually committed; rolling back
# calendar home creation will remove the whole directory.
- self.txn.commit()
- self._refresh()
+ yield self.txn.commit()
+ yield self._refresh()
self.calendar1.createCalendarObjectWithName(
"sample.ics",
VComponent.fromString(event4_text)
)
- self._refresh()
+ yield self.txn.abort()
+ yield self._refresh()
self.assertIdentical(
- self.calendar1.calendarObjectWithName("sample.ics"),
+ (yield self.calendar1.calendarObjectWithName("sample.ics")),
None
)
+ yield self.txn.commit()
+ @inlineCallbacks
def doThenUndo(self):
"""
Commit the current transaction, but add an operation that will cause it
@@ -330,43 +349,46 @@
raise RuntimeError("oops")
self.txn.addOperation(fail, "dummy failing operation")
self.assertRaises(RuntimeError, self.txn.commit)
- self._refresh()
+ yield self._refresh()
+ @inlineCallbacks
def test_undoModifyCalendarObject(self):
"""
If an existing calendar object is modified as part of a transaction, it
should be restored to its previous status if the transaction aborts.
"""
- originalComponent = self.calendar1.calendarObjectWithName(
+ originalComponent = yield self.calendar1.calendarObjectWithName(
"1.ics").component()
- self.calendar1.calendarObjectWithName("1.ics").setComponent(
+ (yield self.calendar1.calendarObjectWithName("1.ics")).setComponent(
VComponent.fromString(event1modified_text)
)
# Sanity check.
self.assertEquals(
- self.calendar1.calendarObjectWithName("1.ics").component(),
+ (yield self.calendar1.calendarObjectWithName("1.ics")).component(),
VComponent.fromString(event1modified_text)
)
- self.doThenUndo()
+ yield self.doThenUndo()
self.assertEquals(
- self.calendar1.calendarObjectWithName("1.ics").component(),
+ (yield self.calendar1.calendarObjectWithName("1.ics")).component(),
originalComponent
)
+ @inlineCallbacks
def test_modifyCalendarObjectCaches(self):
"""
Modifying a calendar object should cache the modified component in
memory, to avoid unnecessary parsing round-trips.
"""
+ self.addCleanup(self.txn.commit)
modifiedComponent = VComponent.fromString(event1modified_text)
- self.calendar1.calendarObjectWithName("1.ics").setComponent(
+ (yield self.calendar1.calendarObjectWithName("1.ics")).setComponent(
modifiedComponent
)
self.assertIdentical(
modifiedComponent,
- self.calendar1.calendarObjectWithName("1.ics").component()
+ (yield self.calendar1.calendarObjectWithName("1.ics")).component()
)
@@ -410,9 +432,10 @@
class CalendarObjectTest(unittest.TestCase):
notifierFactory = None
+ @inlineCallbacks
def setUp(self):
- setUpCalendar1(self)
- self.object1 = self.calendar1.calendarObjectWithName("1.ics")
+ yield setUpCalendar1(self)
+ self.object1 = yield self.calendar1.calendarObjectWithName("1.ics")
def test_init(self):
@@ -464,10 +487,11 @@
self.storeRootPath)
+ @inlineCallbacks
def test_calendarObjectsWithDotFile(self):
"""
Adding a dotfile to the calendar home should not increase
"""
- self.homeUnderTest()._path.child(".foo").createDirectory()
- self.test_calendarObjects()
+ (yield self.homeUnderTest())._path.child(".foo").createDirectory()
+ yield self.test_calendarObjects()
Copied: CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py (from rev 6445, CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py)
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -0,0 +1,943 @@
+##
+# Copyright (c) 2010 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet import reactor
+from twisted.internet.task import deferLater
+
+from txdav.caldav.datastore.index_file import Index, MemcachedUIDReserver
+from txdav.common.icommondatastore import ReservationError
+
+from twistedcaldav import caldavxml
+from twistedcaldav.caldavxml import TimeRange
+from twistedcaldav.ical import Component
+from twistedcaldav.instance import InvalidOverriddenInstanceError
+from twistedcaldav.query import calendarqueryfilter
+from twistedcaldav.test.util import InMemoryMemcacheProtocol
+import twistedcaldav.test.util
+
+import datetime
+import os
+from twisted.internet.defer import inlineCallbacks
+
+
+class MinimalResourceReplacement(object):
+ """
+ Provide the minimal set of attributes and methods from CalDAVFile required
+ by L{Index}.
+ """
+
+ def __init__(self, filePath):
+ self.fp = filePath
+
+
+ def isCalendarCollection(self):
+ return True
+
+
+ def getChild(self, name):
+ # FIXME: this should really return something with a child method
+ return self.fp.child(name)
+
+
+ def initSyncToken(self):
+ pass
+
+
+
+class SQLIndexTests (twistedcaldav.test.util.TestCase):
+ """
+ Test abstract SQL DB class
+ """
+
+ def setUp(self):
+ super(SQLIndexTests, self).setUp()
+ self.site.resource.isCalendarCollection = lambda: True
+ self.indexDirPath = self.site.resource.fp
+ # FIXME: since this resource lies about isCalendarCollection, it doesn't
+ # have all the associated backend machinery to actually get children.
+ self.db = Index(MinimalResourceReplacement(self.indexDirPath))
+
+
+ def test_reserve_uid_ok(self):
+ uid = "test-test-test"
+ d = self.db.isReservedUID(uid)
+ d.addCallback(self.assertFalse)
+ d.addCallback(lambda _: self.db.reserveUID(uid))
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertTrue)
+ d.addCallback(lambda _: self.db.unreserveUID(uid))
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertFalse)
+
+ return d
+
+
+ def test_reserve_uid_twice(self):
+ uid = "test-test-test"
+ d = self.db.reserveUID(uid)
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertTrue)
+ d.addCallback(lambda _:
+ self.assertFailure(self.db.reserveUID(uid),
+ ReservationError))
+ return d
+
+
+ def test_unreserve_unreserved(self):
+ uid = "test-test-test"
+ return self.assertFailure(self.db.unreserveUID(uid),
+ ReservationError)
+
+
+ def test_reserve_uid_timeout(self):
+ # WARNING: This test is fundamentally flawed and will fail
+ # intermittently because it uses the real clock.
+ uid = "test-test-test"
+ from twistedcaldav.config import config
+ old_timeout = config.UIDReservationTimeOut
+ config.UIDReservationTimeOut = 1
+
+ def _finally():
+ config.UIDReservationTimeOut = old_timeout
+
+ d = self.db.isReservedUID(uid)
+ d.addCallback(self.assertFalse)
+ d.addCallback(lambda _: self.db.reserveUID(uid))
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertTrue)
+ d.addCallback(lambda _: deferLater(reactor, 2, lambda: None))
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertFalse)
+ self.addCleanup(_finally)
+
+ return d
+
+
+ def test_index(self):
+ data = (
+ (
+ "#1.1 Simple component",
+ "1.1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ True,
+ ),
+ (
+ "#2.1 Recurring component",
+ "2.1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ True,
+ ),
+ (
+ "#2.2 Recurring component with override",
+ "2.2",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-2.2
+RECURRENCE-ID:20080608T120000Z
+DTSTART:20080608T120000Z
+DTEND:20080608T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ True,
+ ),
+ (
+ "#2.3 Recurring component with broken override - new",
+ "2.3",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.3
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-2.3
+RECURRENCE-ID:20080609T120000Z
+DTSTART:20080608T120000Z
+DTEND:20080608T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ False,
+ ),
+ (
+ "#2.4 Recurring component with broken override - existing",
+ "2.4",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.4
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-2.4
+RECURRENCE-ID:20080609T120000Z
+DTSTART:20080608T120000Z
+DTEND:20080608T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ True,
+ ),
+ )
+
+ for description, name, calendar_txt, reCreate, ok in data:
+ 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, reCreate=reCreate)
+ self.assertTrue(self.db.resourceExists(name), msg=description)
+ else:
+ self.assertRaises(InvalidOverriddenInstanceError, self.db.addResource, name, calendar)
+ self.assertFalse(self.db.resourceExists(name), msg=description)
+
+ self.db._db_recreate()
+ for description, name, calendar_txt, reCreate, ok in data:
+ if ok:
+ self.assertTrue(self.db.resourceExists(name), msg=description)
+ else:
+ self.assertFalse(self.db.resourceExists(name), msg=description)
+
+ self.db.testAndUpdateIndex(datetime.date(2020, 1, 1))
+ for description, name, calendar_txt, reCreate, ok in data:
+ if ok:
+ self.assertTrue(self.db.resourceExists(name), msg=description)
+ else:
+ self.assertFalse(self.db.resourceExists(name), msg=description)
+
+
+ @inlineCallbacks
+ def test_index_timespan(self):
+ data = (
+ (
+ "#1.1 Simple component - busy",
+ "1.1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ "20080601T000000Z", "20080602T000000Z",
+ "mailto:user1 at example.com",
+ (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+ ),
+ (
+ "#1.2 Simple component - transparent",
+ "1.2",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+ "20080602T000000Z", "20080603T000000Z",
+ "mailto:user1 at example.com",
+ (('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),),
+ ),
+ (
+ "#1.3 Simple component - canceled",
+ "1.3",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.3
+DTSTART:20080603T120000Z
+DTEND:20080603T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+STATUS:CANCELLED
+END:VEVENT
+END:VCALENDAR
+""",
+ "20080603T000000Z", "20080604T000000Z",
+ "mailto:user1 at example.com",
+ (('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'F', 'F'),),
+ ),
+ (
+ "#1.4 Simple component - tentative",
+ "1.4",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.4
+DTSTART:20080604T120000Z
+DTEND:20080604T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+STATUS:TENTATIVE
+END:VEVENT
+END:VCALENDAR
+""",
+ "20080604T000000Z", "20080605T000000Z",
+ "mailto:user1 at example.com",
+ (('N', "2008-06-04 12:00:00+00:00", "2008-06-04 13:00:00+00:00", 'T', 'F'),),
+ ),
+ (
+ "#2.1 Recurring component - busy",
+ "2.1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.1
+DTSTART:20080605T120000Z
+DTEND:20080605T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=2
+END:VEVENT
+END:VCALENDAR
+""",
+ "20080605T000000Z", "20080607T000000Z",
+ "mailto:user1 at example.com",
+ (
+ ('N', "2008-06-05 12:00:00+00:00", "2008-06-05 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-06 12:00:00+00:00", "2008-06-06 13:00:00+00:00", 'B', 'F'),
+ ),
+ ),
+ (
+ "#2.2 Recurring component - busy",
+ "2.2",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.2
+DTSTART:20080607T120000Z
+DTEND:20080607T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-2.2
+RECURRENCE-ID:20080608T120000Z
+DTSTART:20080608T140000Z
+DTEND:20080608T150000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+ "20080607T000000Z", "20080609T000000Z",
+ "mailto:user1 at example.com",
+ (
+ ('N', "2008-06-07 12:00:00+00:00", "2008-06-07 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-08 14:00:00+00:00", "2008-06-08 15:00:00+00:00", 'B', 'T'),
+ ),
+ ),
+ )
+
+ for description, name, calendar_txt, trstart, trend, organizer, instances in data:
+ 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)
+ self.assertTrue(self.db.resourceExists(name), msg=description)
+
+ # Create fake filter element to match time-range
+ filter = caldavxml.Filter(
+ caldavxml.ComponentFilter(
+ caldavxml.ComponentFilter(
+ TimeRange(
+ start=trstart,
+ end=trend,
+ ),
+ name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
+ ),
+ name="VCALENDAR",
+ )
+ )
+ filter = calendarqueryfilter.Filter(filter)
+
+ resources = yield self.db.indexedSearch(filter, fbtype=True)
+ index_results = set()
+ for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
+ self.assertEqual(test_organizer, organizer, msg=description)
+ index_results.add((float, start, end, fbtype, transp,))
+
+ self.assertEqual(set(instances), index_results, msg=description)
+
+
+ @inlineCallbacks
+ def test_index_timespan_per_user(self):
+ data = (
+ (
+ "#1.1 Single per-user non-recurring component",
+ "1.1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+ "20080601T000000Z", "20080602T000000Z",
+ "mailto:user1 at example.com",
+ (
+ (
+ "user01",
+ (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),),
+ ),
+ (
+ "user02",
+ (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+ ),
+ ),
+ ),
+ (
+ "#1.2 Two per-user non-recurring component",
+ "1.2",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+ "20080601T000000Z", "20080602T000000Z",
+ "mailto:user1 at example.com",
+ (
+ (
+ "user01",
+ (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),),
+ ),
+ (
+ "user02",
+ (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+ ),
+ (
+ "user03",
+ (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+ ),
+ ),
+ ),
+ (
+ "#2.1 Single per-user simple recurring component",
+ "2.1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+ "20080601T000000Z", "20080603T000000Z",
+ "mailto:user1 at example.com",
+ (
+ (
+ "user01",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+ ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),
+ ),
+ ),
+ (
+ "user02",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
+ ),
+ ),
+ ),
+ ),
+ (
+ "#2.2 Two per-user simple recurring component",
+ "2.2",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+ "20080601T000000Z", "20080603T000000Z",
+ "mailto:user1 at example.com",
+ (
+ (
+ "user01",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+ ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),
+ ),
+ ),
+ (
+ "user02",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
+ ),
+ ),
+ (
+ "user03",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
+ ),
+ ),
+ ),
+ ),
+ (
+ "#3.1 Single per-user complex recurring component",
+ "3.1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1.1
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+ "20080601T000000Z", "20080604T000000Z",
+ "mailto:user1 at example.com",
+ (
+ (
+ "user01",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+ ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
+ ),
+ ),
+ (
+ "user02",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'F'),
+ ),
+ ),
+ ),
+ ),
+ (
+ "#3.2 Two per-user complex recurring component",
+ "3.2",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1.2
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080603T120000Z
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+ "20080601T000000Z", "20080604T000000Z",
+ "mailto:user1 at example.com",
+ (
+ (
+ "user01",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+ ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
+ ),
+ ),
+ (
+ "user02",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
+ ),
+ ),
+ (
+ "user03",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'F'),
+ ),
+ ),
+ ),
+ ),
+ )
+
+ for description, name, calendar_txt, trstart, trend, organizer, peruserinstances in data:
+ 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)
+ self.assertTrue(self.db.resourceExists(name), msg=description)
+
+ # Create fake filter element to match time-range
+ filter = caldavxml.Filter(
+ caldavxml.ComponentFilter(
+ caldavxml.ComponentFilter(
+ TimeRange(
+ start=trstart,
+ end=trend,
+ ),
+ name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
+ ),
+ name="VCALENDAR",
+ )
+ )
+ filter = calendarqueryfilter.Filter(filter)
+
+ for useruid, instances in peruserinstances:
+ resources = yield self.db.indexedSearch(filter, useruid=useruid, fbtype=True)
+ index_results = set()
+ for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
+ self.assertEqual(test_organizer, organizer, msg=description)
+ index_results.add((str(float), str(start), str(end), str(fbtype), str(transp),))
+
+ self.assertEqual(set(instances), index_results, msg="%s, user:%s" % (description, useruid,))
+
+ self.db.deleteResource(name)
+
+
+ def test_index_revisions(self):
+ data1 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+ data2 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+END:VCALENDAR
+"""
+ data3 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.3
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+END:VCALENDAR
+"""
+
+ calendar = Component.fromString(data1)
+ self.db.addResource("data1.ics", calendar)
+ calendar = Component.fromString(data2)
+ self.db.addResource("data2.ics", calendar)
+ calendar = Component.fromString(data3)
+ self.db.addResource("data3.ics", calendar)
+ self.db.deleteResource("data3.ics")
+
+ tests = (
+ (0, (["data1.ics", "data2.ics",], [],)),
+ (1, (["data2.ics",], ["data3.ics",],)),
+ (2, ([], ["data3.ics",],)),
+ (3, ([], ["data3.ics",],)),
+ (4, ([], [],)),
+ (5, ([], [],)),
+ )
+
+ for revision, results in tests:
+ for depth in ("1", "infinity"):
+ self.assertEquals(self.db.whatchanged(revision, depth), results, "Mismatched results for whatchanged with revision %d" % (revision,))
+
+class MemcacheTests(SQLIndexTests):
+ def setUp(self):
+ super(MemcacheTests, self).setUp()
+ self.memcache = InMemoryMemcacheProtocol()
+ self.db.reserver = MemcachedUIDReserver(self.db, self.memcache)
+
+
+ def tearDown(self):
+ for _ignore_k, v in self.memcache._timeouts.iteritems():
+ if v.active():
+ v.cancel()
Property changes on: CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
___________________________________________________________________
Added: svn:mergeinfo
+ /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -19,19 +19,18 @@
L{txdav.caldav.datastore.test.common}.
"""
-import time
+from twisted.trial import unittest
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.task import deferLater
+from twisted.internet import reactor
+from twext.python.vcomponent import VComponent
+from twext.web2.dav.element.rfc2518 import GETContentLanguage, ResourceType
+
from txdav.caldav.datastore.test.common import CommonTests as CalendarCommonTests
-
from txdav.common.datastore.sql import ECALENDARTYPE
from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom
-from twisted.trial import unittest
-from twisted.internet.defer import inlineCallbacks
-from twisted.internet.threads import deferToThread
-from twext.python.vcomponent import VComponent
-from twext.web2.dav.element.rfc2518 import GETContentLanguage, ResourceType
-
from txdav.caldav.datastore.test.test_file import setUpCalendarStore
from txdav.caldav.datastore.util import _migrateCalendar, migrateHome
from txdav.base.propertystore.base import PropertyName
@@ -47,11 +46,12 @@
def setUp(self):
super(CalendarSQLStorageTests, self).setUp()
self._sqlCalendarStore = yield buildStore(self, self.notifierFactory)
- self.populate()
+ yield self.populate()
+ @inlineCallbacks
def populate(self):
- populateCalendarsFrom(self.requirements, self.storeUnderTest())
+ yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
self.notifierFactory.reset()
@@ -62,19 +62,24 @@
return self._sqlCalendarStore
+ @inlineCallbacks
def assertCalendarsSimilar(self, a, b, bCalendarFilter=None):
"""
Assert that two calendars have a similar structure (contain the same
events).
"""
- def namesAndComponents(x, filter=lambda x:x.component()):
- return dict([(fromObj.name(), filter(fromObj))
- for fromObj in x.calendarObjects()])
+ @inlineCallbacks
+ def namesAndComponents(x, filter=lambda x: x.component()):
+ result = {}
+ for fromObj in (yield x.calendarObjects()):
+ result[fromObj.name()] = yield filter(fromObj)
+ returnValue(result)
if bCalendarFilter is not None:
extra = [bCalendarFilter]
else:
extra = []
- self.assertEquals(namesAndComponents(a), namesAndComponents(b, *extra))
+ self.assertEquals((yield namesAndComponents(a)),
+ (yield namesAndComponents(b, *extra)))
def assertPropertiesSimilar(self, a, b, disregard=[]):
@@ -103,6 +108,7 @@
self.addCleanup(txn.commit)
return txn
+
@inlineCallbacks
def test_attachmentPath(self):
"""
@@ -111,32 +117,40 @@
L{ICalendarObject.attachmentWithName}.
"""
yield self.createAttachmentTest(lambda x: x)
- attachmentRoot = self.calendarObjectUnderTest()._txn._store.attachmentsPath
+ attachmentRoot = (
+ yield self.calendarObjectUnderTest()
+ )._txn._store.attachmentsPath
attachmentPath = attachmentRoot.child("ho").child("me").child("home1")
- attachmentPath = attachmentPath.child(self.calendarObjectUnderTest().uid()).child("new.attachment")
+ attachmentPath = attachmentPath.child(
+ (yield self.calendarObjectUnderTest()).uid()).child(
+ "new.attachment")
self.assertTrue(attachmentPath.isfile())
+
+ @inlineCallbacks
def test_migrateCalendarFromFile(self):
"""
C{_migrateCalendar()} can migrate a file-backed calendar to a database-
backed calendar.
"""
- fromCalendar = self.fileTransaction().calendarHomeWithUID(
- "home1").calendarWithName("calendar_1")
- toHome = self.transactionUnderTest().calendarHomeWithUID(
+ fromCalendar = yield (yield self.fileTransaction().calendarHomeWithUID(
+ "home1")).calendarWithName("calendar_1")
+ toHome = yield self.transactionUnderTest().calendarHomeWithUID(
"new-home", create=True)
- toCalendar = toHome.calendarWithName("calendar")
- _migrateCalendar(fromCalendar, toCalendar, lambda x: x.component())
- self.assertCalendarsSimilar(fromCalendar, toCalendar)
+ toCalendar = yield toHome.calendarWithName("calendar")
+ yield _migrateCalendar(fromCalendar, toCalendar,
+ lambda x: x.component())
+ yield self.assertCalendarsSimilar(fromCalendar, toCalendar)
+ @inlineCallbacks
def test_migrateHomeFromFile(self):
"""
L{migrateHome} will migrate an L{ICalendarHome} provider from one
backend to another; in this specific case, from the file-based backend
to the SQL-based backend.
"""
- fromHome = self.fileTransaction().calendarHomeWithUID("home1")
+ fromHome = yield self.fileTransaction().calendarHomeWithUID("home1")
builtinProperties = [PropertyName.fromElement(ResourceType)]
@@ -145,19 +159,21 @@
key = PropertyName.fromElement(GETContentLanguage)
fromHome.properties()[key] = GETContentLanguage("C")
- fromHome.calendarWithName("calendar_1").properties()[key] = (
+ (yield fromHome.calendarWithName("calendar_1")).properties()[key] = (
GETContentLanguage("pig-latin")
)
- toHome = self.transactionUnderTest().calendarHomeWithUID(
+ toHome = yield self.transactionUnderTest().calendarHomeWithUID(
"new-home", create=True
)
- migrateHome(fromHome, toHome, lambda x: x.component())
- self.assertEquals(set([c.name() for c in toHome.calendars()]),
+ yield migrateHome(fromHome, toHome, lambda x: x.component())
+ toCalendars = yield toHome.calendars()
+ self.assertEquals(set([c.name() for c in toCalendars]),
set([k for k in self.requirements['home1'].keys()
if self.requirements['home1'][k] is not None]))
- for c in fromHome.calendars():
+ fromCalendars = yield fromHome.calendars()
+ for c in fromCalendars:
self.assertPropertiesSimilar(
- c, toHome.calendarWithName(c.name()),
+ c, (yield toHome.calendarWithName(c.name())),
builtinProperties
)
self.assertPropertiesSimilar(fromHome, toHome, builtinProperties)
@@ -175,55 +191,58 @@
"stubbed out, as migration only needs to go from file->sql currently")
-
-
@inlineCallbacks
def test_homeProvisioningConcurrency(self):
"""
- Test that two concurrent attempts to provision a calendar home do not cause a race-condition
- whereby the second commit results in a second INSERT that violates a unique constraint. Also verify
- that, whilst the two provisioning attempts are happening and doing various lock operations, that we
- do not block other reads of the table.
+ Test that two concurrent attempts to provision a calendar home do not
+ cause a race-condition whereby the second commit results in a second
+ C{INSERT} that violates a unique constraint. Also verify that, while
+ the two provisioning attempts are happening and doing various lock
+ operations, that we do not block other reads of the table.
"""
- calendarStore1 = yield buildStore(self, self.notifierFactory)
- calendarStore2 = yield buildStore(self, self.notifierFactory)
- calendarStore3 = yield buildStore(self, self.notifierFactory)
+ calendarStore = yield buildStore(self, self.notifierFactory)
- txn1 = calendarStore1.newTransaction()
- txn2 = calendarStore2.newTransaction()
- txn3 = calendarStore3.newTransaction()
-
- # Provision one home now - we will use this to later verify we can do reads of
- # existing data in the table
- home_uid2 = txn3.homeWithUID(ECALENDARTYPE, "uid2", create=True)
+ txn1 = calendarStore.newTransaction()
+ txn2 = calendarStore.newTransaction()
+ txn3 = calendarStore.newTransaction()
+
+ # Provision one home now - we will use this to later verify we can do
+ # reads of existing data in the table
+ home_uid2 = yield txn3.homeWithUID(ECALENDARTYPE, "uid2", create=True)
self.assertNotEqual(home_uid2, None)
txn3.commit()
- home_uid1_1 = txn1.homeWithUID(ECALENDARTYPE, "uid1", create=True)
-
+ home_uid1_1 = yield txn1.homeWithUID(
+ ECALENDARTYPE, "uid1", create=True
+ )
+
+ @inlineCallbacks
def _defer_home_uid1_2():
- home_uid1_2 = txn2.homeWithUID(ECALENDARTYPE, "uid1", create=True)
- txn2.commit()
- return home_uid1_2
- d1 = deferToThread(_defer_home_uid1_2)
-
+ home_uid1_2 = yield txn2.homeWithUID(
+ ECALENDARTYPE, "uid1", create=True
+ )
+ yield txn2.commit()
+ returnValue(home_uid1_2)
+ d1 = _defer_home_uid1_2()
+
+ @inlineCallbacks
def _pause_home_uid1_1():
- time.sleep(1)
- txn1.commit()
- d2 = deferToThread(_pause_home_uid1_1)
-
+ yield deferLater(reactor, 1.0, lambda : None)
+ yield txn1.commit()
+ d2 = _pause_home_uid1_1()
+
# Verify that we can still get to the existing home - i.e. the lock
# on the table allows concurrent reads
- txn4 = calendarStore3.newTransaction()
- home_uid2 = txn4.homeWithUID(ECALENDARTYPE, "uid2", create=True)
+ txn4 = calendarStore.newTransaction()
+ home_uid2 = yield txn4.homeWithUID(ECALENDARTYPE, "uid2", create=True)
self.assertNotEqual(home_uid2, None)
txn4.commit()
-
+
# Now do the concurrent provision attempt
yield d2
home_uid1_2 = yield d1
-
+
self.assertNotEqual(home_uid1_1, None)
self.assertNotEqual(home_uid1_2, None)
@@ -240,21 +259,22 @@
# Provision the home now
txn = calendarStore1.newTransaction()
- home = txn.homeWithUID(ECALENDARTYPE, "uid1", create=True)
+ home = yield txn.homeWithUID(ECALENDARTYPE, "uid1", create=True)
self.assertNotEqual(home, None)
txn.commit()
txn1 = calendarStore1.newTransaction()
txn2 = calendarStore2.newTransaction()
- home1 = txn1.homeWithUID(ECALENDARTYPE, "uid1", create=True)
- home2 = txn2.homeWithUID(ECALENDARTYPE, "uid1", create=True)
-
- adbk1 = home1.calendarWithName("calendar")
- adbk2 = home2.calendarWithName("calendar")
-
+ home1 = yield txn1.homeWithUID(ECALENDARTYPE, "uid1", create=True)
+ home2 = yield txn2.homeWithUID(ECALENDARTYPE, "uid1", create=True)
+
+ adbk1 = yield home1.calendarWithName("calendar")
+ adbk2 = yield home2.calendarWithName("calendar")
+
+ @inlineCallbacks
def _defer1():
- adbk1.createObjectResourceWithName("1.ics", VComponent.fromString(
+ yield adbk1.createObjectResourceWithName("1.ics", VComponent.fromString(
"BEGIN:VCALENDAR\r\n"
"VERSION:2.0\r\n"
"PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
@@ -294,11 +314,12 @@
"END:VEVENT\r\n"
"END:VCALENDAR\r\n"
))
- txn1.commit()
- d1 = deferToThread(_defer1)
-
+ yield txn1.commit()
+ d1 = _defer1()
+
+ @inlineCallbacks
def _defer2():
- adbk2.createObjectResourceWithName("2.ics", VComponent.fromString(
+ yield adbk2.createObjectResourceWithName("2.ics", VComponent.fromString(
"BEGIN:VCALENDAR\r\n"
"VERSION:2.0\r\n"
"PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
@@ -338,8 +359,8 @@
"END:VEVENT\r\n"
"END:VCALENDAR\r\n"
))
- txn2.commit()
- d2 = deferToThread(_defer2)
+ yield txn2.commit()
+ d2 = _defer2()
yield d1
yield d2
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_util.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_util.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_util.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -18,6 +18,7 @@
Tests for txdav.caldav.datastore.util.
"""
+from twisted.internet.defer import inlineCallbacks
from twistedcaldav.ical import Component
from twistedcaldav.test.util import TestCase
from txdav.caldav.datastore.util import dropboxIDFromCalendarObject
@@ -31,19 +32,20 @@
"""
Fake object resource to work with tests.
"""
-
+
def __init__(self, data):
-
+
self.ical = Component.fromString(data)
-
+
def component(self):
return self.ical
-
+
def uid(self):
return self.ical.resourceUID()
+
+ @inlineCallbacks
def test_noAttachOrXdash(self):
-
resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
@@ -54,9 +56,14 @@
END:VEVENT
END:VCALENDAR
""")
-
- self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890.dropbox")
+ self.assertEquals(
+ (yield dropboxIDFromCalendarObject(resource)),
+ "12345-67890.dropbox"
+ )
+
+
+ @inlineCallbacks
def test_okXdash(self):
resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -70,11 +77,15 @@
END:VEVENT
END:VCALENDAR
""")
-
- self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890X.dropbox")
+ self.assertEquals(
+ (yield dropboxIDFromCalendarObject(resource)),
+ "12345-67890X.dropbox"
+ )
+
+
+ @inlineCallbacks
def test_badXdash(self):
-
resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
@@ -86,9 +97,11 @@
END:VEVENT
END:VCALENDAR
""")
-
- self.assertEquals(dropboxIDFromCalendarObject(resource), "")
+ self.assertEquals( (yield dropboxIDFromCalendarObject(resource)), "")
+
+
+ @inlineCallbacks
def test_okAttach(self):
resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -102,9 +115,14 @@
END:VEVENT
END:VCALENDAR
""")
-
- self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890Y.dropbox")
+ self.assertEquals(
+ (yield dropboxIDFromCalendarObject(resource)),
+ "12345-67890Y.dropbox"
+ )
+
+
+ @inlineCallbacks
def test_badAttach(self):
resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -118,9 +136,14 @@
END:VEVENT
END:VCALENDAR
""")
-
- self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890.dropbox")
+ self.assertEquals(
+ (yield dropboxIDFromCalendarObject(resource)),
+ "12345-67890.dropbox"
+ )
+
+
+ @inlineCallbacks
def test_inlineAttach(self):
resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -134,9 +157,14 @@
END:VEVENT
END:VCALENDAR
""")
-
- self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890.dropbox")
+ self.assertEquals(
+ (yield dropboxIDFromCalendarObject(resource)),
+ "12345-67890.dropbox"
+ )
+
+
+ @inlineCallbacks
def test_multipleAttach(self):
resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -152,9 +180,14 @@
END:VEVENT
END:VCALENDAR
""")
-
- self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890Z.dropbox")
+ self.assertEquals(
+ (yield dropboxIDFromCalendarObject(resource)),
+ "12345-67890Z.dropbox"
+ )
+
+
+ @inlineCallbacks
def test_okAttachRecurring(self):
resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -176,10 +209,14 @@
END:VEVENT
END:VCALENDAR
""")
-
- self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890Y.dropbox")
+ self.assertEquals(
+ (yield dropboxIDFromCalendarObject(resource)),
+ "12345-67890Y.dropbox"
+ )
+
+ @inlineCallbacks
def test_okAttachAlarm(self):
resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -198,6 +235,10 @@
END:VEVENT
END:VCALENDAR
""")
-
- self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890.dropbox")
+ self.assertEquals(
+ (yield dropboxIDFromCalendarObject(resource)),
+ "12345-67890.dropbox"
+ )
+
+
Modified: CalendarServer/trunk/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/util.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/util.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,3 +1,4 @@
+# -*- test-case-name: txdav.caldav.datastore.test.test_util -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
@@ -18,7 +19,7 @@
Utility logic common to multiple backend implementations.
"""
-from twisted.internet.defer import inlineCallbacks, Deferred
+from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
from twisted.internet.protocol import Protocol
from twext.python.vcomponent import InvalidICalendarDataError
@@ -69,6 +70,7 @@
raise InvalidObjectResourceError(e)
+ at inlineCallbacks
def dropboxIDFromCalendarObject(calendarObject):
"""
Helper to implement L{ICalendarObject.dropboxID}.
@@ -78,16 +80,17 @@
"""
# Try "X-APPLE-DROPBOX" first
- dropboxProperty = calendarObject.component(
- ).getFirstPropertyInAnyComponent("X-APPLE-DROPBOX")
+ dropboxProperty = (yield calendarObject.component(
+ )).getFirstPropertyInAnyComponent("X-APPLE-DROPBOX")
if dropboxProperty is not None:
componentDropboxID = dropboxProperty.value().split("/")[-1]
- return componentDropboxID
+ returnValue(componentDropboxID)
# Now look at each ATTACH property and see if it might be a dropbox item
# and if so extract the id from that
- attachments = calendarObject.component().getAllPropertiesInAnyComponent(
+ attachments = (yield calendarObject.component()
+ ).getAllPropertiesInAnyComponent(
"ATTACH",
depth=1,
)
@@ -99,11 +102,11 @@
segments = attachment.value().split("/")
try:
if segments[-3] == "dropbox":
- return segments[-2]
+ returnValue(segments[-2])
except IndexError:
pass
- return calendarObject.uid() + ".dropbox"
+ returnValue(calendarObject.uid() + ".dropbox")
@inlineCallbacks
@@ -119,24 +122,24 @@
@return: a L{Deferred} which fires when the calendar has migrated.
"""
outCalendar.properties().update(inCalendar.properties())
- for calendarObject in inCalendar.calendarObjects():
+ for calendarObject in (yield inCalendar.calendarObjects()):
try:
- outCalendar.createCalendarObjectWithName(
+ yield outCalendar.createCalendarObjectWithName(
calendarObject.name(),
- calendarObject.component()) # XXX WRONG SHOULD CALL getComponent
-
+ (yield calendarObject.component())) # XXX WRONG SHOULD CALL getComponent
+
# Only the owner's properties are migrated, since previous releases of
# calendar server didn't have per-user properties.
- outObject = outCalendar.calendarObjectWithName(
+ outObject = yield outCalendar.calendarObjectWithName(
calendarObject.name())
outObject.properties().update(calendarObject.properties())
# Migrate attachments.
- for attachment in calendarObject.attachments():
+ for attachment in (yield calendarObject.attachments()):
name = attachment.name()
ctype = attachment.contentType()
- transport = outObject.createAttachmentWithName(name, ctype)
+ transport = yield outObject.createAttachmentWithName(name, ctype)
proto =_AttachmentMigrationProto(transport)
attachment.retrieve(proto)
yield proto.done
@@ -184,15 +187,16 @@
(from a calendar in C{inHome}) and returns a L{VComponent} (to store in
a calendar in outHome).
"""
- outHome.removeCalendarWithName("calendar")
- outHome.removeCalendarWithName("inbox")
+ yield outHome.removeCalendarWithName("calendar")
+ yield outHome.removeCalendarWithName("inbox")
outHome.properties().update(inHome.properties())
- for calendar in inHome.calendars():
+ inCalendars = yield inHome.calendars()
+ for calendar in inCalendars:
name = calendar.name()
if name == "outbox":
continue
- outHome.createCalendarWithName(name)
- outCalendar = outHome.calendarWithName(name)
+ yield outHome.createCalendarWithName(name)
+ outCalendar = yield outHome.calendarWithName(name)
try:
yield _migrateCalendar(calendar, outCalendar, getComponent)
except InternalDataStoreError:
Modified: CalendarServer/trunk/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/icalendarstore.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/icalendarstore.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -57,8 +57,8 @@
If C{create} is C{True}, create the calendar home if it doesn't
already exist.
- @return: an L{ICalendarHome} or C{None} if no such calendar
- home exists.
+ @return: a L{Deferred} which fires with L{ICalendarHome} or C{None} if
+ no such calendar home exists.
"""
@@ -202,8 +202,9 @@
in this calendar.
@param uid: a string.
- @return: an L{ICalendarObject} or C{None} if no such calendar
- object exists.
+
+ @return: a L{Deferred} firing an L{ICalendarObject} or C{None} if no
+ such calendar object exists.
"""
def createCalendarObjectWithName(name, component):
Modified: CalendarServer/trunk/txdav/carddav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/file.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/datastore/file.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -33,7 +33,7 @@
from twistedcaldav.sharing import InvitesDatabase
from twistedcaldav.vcard import Component as VComponent, InvalidVCardDataError
-from twistedcaldav.vcardindex import AddressBookIndex as OldIndex
+from txdav.carddav.datastore.index_file import AddressBookIndex as OldIndex
from txdav.carddav.datastore.util import validateAddressBookComponent
from txdav.carddav.iaddressbookstore import IAddressBook, IAddressBookObject
Copied: CalendarServer/trunk/txdav/carddav/datastore/index_file.py (from rev 6445, CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py)
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/index_file.py (rev 0)
+++ CalendarServer/trunk/txdav/carddav/datastore/index_file.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -0,0 +1,696 @@
+##
+# Copyright (c) 2005-2009 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+CardDAV Index.
+
+This API is considered private to static.py and is therefore subject to
+change.
+"""
+
+__all__ = [
+ "AddressBookIndex",
+]
+
+import datetime
+import os
+import time
+import hashlib
+
+try:
+ import sqlite3 as sqlite
+except ImportError:
+ from pysqlite2 import dbapi2 as sqlite
+
+from twisted.internet.defer import maybeDeferred
+
+from twistedcaldav import carddavxml
+from txdav.common.icommondatastore import SyncTokenValidException,\
+ ReservationError
+from twistedcaldav.query import addressbookquery
+from twistedcaldav.sql import AbstractSQLDatabase
+from twistedcaldav.sql import db_prefix
+from twistedcaldav.vcard import Component
+
+from twext.python.log import Logger, LoggingMixIn
+from twistedcaldav.config import config
+from twistedcaldav.memcachepool import CachePoolUserMixIn
+
+log = Logger()
+
+db_basename = db_prefix + "sqlite"
+schema_version = "2"
+
+def wrapInDeferred(f):
+ def _(*args, **kwargs):
+ return maybeDeferred(f, *args, **kwargs)
+
+ return _
+
+
+class MemcachedUIDReserver(CachePoolUserMixIn, LoggingMixIn):
+ def __init__(self, index, cachePool=None):
+ self.index = index
+ self._cachePool = cachePool
+
+ def _key(self, uid):
+ return 'reservation:%s' % (
+ hashlib.md5('%s:%s' % (uid,
+ self.index.resource.fp.path)).hexdigest())
+
+ def reserveUID(self, uid):
+ uid = uid.encode('utf-8')
+ self.log_debug("Reserving UID %r @ %r" % (
+ uid,
+ self.index.resource.fp.path))
+
+ def _handleFalse(result):
+ if result is False:
+ raise ReservationError(
+ "UID %s already reserved for address book collection %s."
+ % (uid, self.index.resource)
+ )
+
+ d = self.getCachePool().add(self._key(uid),
+ 'reserved',
+ expireTime=config.UIDReservationTimeOut)
+ d.addCallback(_handleFalse)
+ return d
+
+
+ def unreserveUID(self, uid):
+ uid = uid.encode('utf-8')
+ self.log_debug("Unreserving UID %r @ %r" % (
+ uid,
+ self.index.resource.fp.path))
+
+ def _handleFalse(result):
+ if result is False:
+ raise ReservationError(
+ "UID %s is not reserved for address book collection %s."
+ % (uid, self.index.resource)
+ )
+
+ d =self.getCachePool().delete(self._key(uid))
+ d.addCallback(_handleFalse)
+ return d
+
+
+ def isReservedUID(self, uid):
+ uid = uid.encode('utf-8')
+ self.log_debug("Is reserved UID %r @ %r" % (
+ uid,
+ self.index.resource.fp.path))
+
+ def _checkValue((flags, value)):
+ if value is None:
+ return False
+ else:
+ return True
+
+ d = self.getCachePool().get(self._key(uid))
+ d.addCallback(_checkValue)
+ return d
+
+
+
+class SQLUIDReserver(object):
+ def __init__(self, index):
+ self.index = index
+
+ @wrapInDeferred
+ def reserveUID(self, uid):
+ """
+ Reserve a UID for this index's resource.
+ @param uid: the UID to reserve
+ @raise ReservationError: if C{uid} is already reserved
+ """
+
+ try:
+ self.index._db_execute("insert into RESERVED (UID, TIME) values (:1, :2)", uid, datetime.datetime.now())
+ self.index._db_commit()
+ except sqlite.IntegrityError:
+ self.index._db_rollback()
+ raise ReservationError(
+ "UID %s already reserved for address book collection %s."
+ % (uid, self.index.resource)
+ )
+ except sqlite.Error, e:
+ log.err("Unable to reserve UID: %s", (e,))
+ self.index._db_rollback()
+ raise
+
+ def unreserveUID(self, uid):
+ """
+ Unreserve a UID for this index's resource.
+ @param uid: the UID to reserve
+ @raise ReservationError: if C{uid} is not reserved
+ """
+
+ def _cb(result):
+ if result == False:
+ raise ReservationError(
+ "UID %s is not reserved for address book collection %s."
+ % (uid, self.index.resource)
+ )
+ else:
+ try:
+ self.index._db_execute(
+ "delete from RESERVED where UID = :1", uid)
+ self.index._db_commit()
+ except sqlite.Error, e:
+ log.err("Unable to unreserve UID: %s", (e,))
+ self.index._db_rollback()
+ raise
+
+ d = self.isReservedUID(uid)
+ d.addCallback(_cb)
+ return d
+
+
+ @wrapInDeferred
+ def isReservedUID(self, uid):
+ """
+ Check to see whether a UID is reserved.
+ @param uid: the UID to check
+ @return: True if C{uid} is reserved, False otherwise.
+ """
+
+ rowiter = self.index._db_execute("select UID, TIME from RESERVED where UID = :1", uid)
+ for uid, attime in rowiter:
+ # Double check that the time is within a reasonable period of now
+ # otherwise we probably have a stale reservation
+ tm = time.strptime(attime[:19], "%Y-%m-%d %H:%M:%S")
+ dt = datetime.datetime(year=tm.tm_year, month=tm.tm_mon, day=tm.tm_mday, hour=tm.tm_hour, minute=tm.tm_min, second = tm.tm_sec)
+ if datetime.datetime.now() - dt > datetime.timedelta(seconds=config.UIDReservationTimeOut):
+ try:
+ self.index._db_execute("delete from RESERVED where UID = :1", uid)
+ self.index._db_commit()
+ except sqlite.Error, e:
+ log.err("Unable to unreserve UID: %s", (e,))
+ self.index._db_rollback()
+ raise
+ return False
+ else:
+ return True
+
+ return False
+
+class AddressBookIndex(AbstractSQLDatabase):
+ """
+ AddressBook collection index abstract base class that defines the apis for the index.
+ """
+
+ def __init__(self, resource):
+ """
+ @param resource: the L{CalDAVResource} resource to
+ index. C{resource} must be an addressbook collection (ie.
+ C{resource.isAddressBookCollection()} returns C{True}.)
+ """
+ assert resource.isAddressBookCollection(), "non-addressbook collection resource %s has no index." % (resource,)
+ self.resource = resource
+ db_filename = os.path.join(self.resource.fp.path, db_basename)
+ super(AddressBookIndex, self).__init__(db_filename, False)
+
+ if (
+ hasattr(config, "Memcached") and
+ config.Memcached.Pools.Default.ClientEnabled
+ ):
+ self.reserver = MemcachedUIDReserver(self)
+ else:
+ self.reserver = SQLUIDReserver(self)
+
+ def create(self):
+ """
+ Create the index and initialize it.
+ """
+ self._db()
+
+ def recreate(self):
+ """
+ Delete the database and re-create it
+ """
+ try:
+ os.remove(self.dbpath)
+ except OSError:
+ pass
+ self.create()
+
+ #
+ # A dict of sets. The dict keys are address book collection paths,
+ # and the sets contains reserved UIDs for each path.
+ #
+
+ def reserveUID(self, uid):
+ return self.reserver.reserveUID(uid)
+
+ def unreserveUID(self, uid):
+ return self.reserver.unreserveUID(uid)
+
+ def isReservedUID(self, uid):
+ return self.reserver.isReservedUID(uid)
+
+ def isAllowedUID(self, uid, *names):
+ """
+ Checks to see whether to allow an operation which would add the
+ specified UID to the index. Specifically, the operation may not
+ violate the constraint that UIDs must be unique.
+ @param uid: the UID to check
+ @param names: the names of resources being replaced or deleted by the
+ operation; UIDs associated with these resources are not checked.
+ @return: True if the UID is not in the index and is not reserved,
+ False otherwise.
+ """
+ rname = self.resourceNameForUID(uid)
+ return (rname is None or rname in names)
+
+ def resourceNamesForUID(self, uid):
+ """
+ Looks up the names of the resources with the given UID.
+ @param uid: the UID of the resources to look up.
+ @return: a list of resource names
+ """
+ names = self._db_values_for_sql("select NAME from RESOURCE where UID = :1", uid)
+
+ #
+ # Check that each name exists as a child of self.resource. If not, the
+ # resource record is stale.
+ #
+ resources = []
+ for name in names:
+ name_utf8 = name.encode("utf-8")
+ 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, False)
+ self._db_commit()
+ else:
+ resources.append(name_utf8)
+
+ return resources
+
+ def resourceNameForUID(self, uid):
+ """
+ Looks up the name of the resource with the given UID.
+ @param uid: the UID of the resource to look up.
+ @return: If the resource is found, its name; C{None} otherwise.
+ """
+ result = None
+
+ for name in self.resourceNamesForUID(uid):
+ assert result is None, "More than one resource with UID %s in address book collection %r" % (uid, self)
+ result = name
+
+ return result
+
+ def resourceUIDForName(self, name):
+ """
+ Looks up the UID of the resource with the given name.
+ @param name: the name of the resource to look up.
+ @return: If the resource is found, the UID of the resource; C{None}
+ otherwise.
+ """
+ uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
+
+ return uid
+
+ 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
+ for the resource name. If present, then the index entries for
+ that UID are removed. After that the new index entries are added.
+ @param name: the name of the resource to add.
+ @param vCard: a L{Component} object representing the resource
+ contents.
+ @param fast: if C{True} do not do commit, otherwise do commit.
+ """
+ oldUID = self.resourceUIDForName(name)
+ if oldUID is not None:
+ self._delete_from_db(name, oldUID, False)
+ self._add_to_db(name, vcard)
+ if not fast:
+ self._db_commit()
+
+ def deleteResource(self, name):
+ """
+ Remove this resource from the index.
+ @param name: the name of the resource to add.
+ @param uid: the UID of the vcard component in the resource.
+ """
+ uid = self.resourceUIDForName(name)
+ if uid is not None:
+ self._delete_from_db(name, uid)
+ self._db_commit()
+
+ def resourceExists(self, name):
+ """
+ Determines whether the specified resource name exists in the index.
+ @param name: the name of the resource to test
+ @return: True if the resource exists, False if not
+ """
+ uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
+ return uid is not None
+
+ def resourcesExist(self, names):
+ """
+ Determines whether the specified resource name exists in the index.
+ @param names: a C{list} containing the names of the resources to test
+ @return: a C{list} of all names that exist
+ """
+ statement = "select NAME from RESOURCE where NAME in ("
+ for ctr, ignore_name in enumerate(names):
+ if ctr != 0:
+ statement += ", "
+ statement += ":%s" % (ctr,)
+ statement += ")"
+ results = self._db_values_for_sql(statement, *names)
+ return results
+
+ def whatchanged(self, revision, depth):
+
+ 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, wasdeleted in results:
+ if name:
+ if wasdeleted == 'Y':
+ if revision:
+ deleted.append(name)
+ else:
+ changed.append(name)
+ else:
+ raise SyncTokenValidException
+
+ 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)
+ else:
+ qualifiers = None
+
+ return qualifiers is not None
+
+ def search(self, filter):
+ """
+ Finds resources matching the given qualifiers.
+ @param filter: the L{Filter} for the addressbook-query to execute.
+ @return: an interable iterable of tuples for each resource matching the
+ given C{qualifiers}. The tuples are C{(name, uid, type)}, where
+ C{name} is the resource name, C{uid} is the resource UID, and
+ C{type} is the resource iCalendar component type.x
+ """
+ # FIXME: Don't forget to use maximum_future_expansion_duration when we
+ # start caching...
+
+ # Make sure we have a proper Filter element and get the partial SQL statement to use.
+ if isinstance(filter, carddavxml.Filter):
+ qualifiers = addressbookquery.sqladdressbookquery(filter)
+ else:
+ qualifiers = None
+ if qualifiers is not None:
+ rowiter = self._db_execute("select DISTINCT RESOURCE.NAME, RESOURCE.UID" + qualifiers[0], *qualifiers[1])
+ else:
+ rowiter = self._db_execute("select NAME, UID from RESOURCE")
+
+ for row in rowiter:
+ name = row[0]
+ if self.resource.getChild(name.encode("utf-8")):
+ yield row
+ else:
+ log.err("vCard resource %s is missing from %s. Removing from index."
+ % (name, self.resource))
+ self.deleteResource(name, None)
+
+ def bruteForceSearch(self):
+ """
+ List the whole index and tests for existence, updating the index
+ @return: all resources in the index
+ """
+ # List all resources
+ rowiter = self._db_execute("select NAME, UID from RESOURCE")
+
+ # Check result for missing resources:
+
+ for row in rowiter:
+ name = row[0]
+ if self.resource.getChild(name.encode("utf-8")):
+ yield row
+ else:
+ log.err("AddressBook resource %s is missing from %s. Removing from index."
+ % (name, self.resource))
+ self.deleteResource(name)
+
+
+ def _db_version(self):
+ """
+ @return: the schema version assigned to this index.
+ """
+ return schema_version
+
+ def _db_type(self):
+ """
+ @return: the collection type assigned to this index.
+ """
+ return "AddressBook"
+
+ def _db_init_data_tables(self, q):
+ """
+ Initialise the underlying database tables.
+ @param q: a database cursor to use.
+ """
+
+ # Create database where the RESOURCE table has unique UID column.
+ self._db_init_data_tables_base(q, True)
+
+ def _db_init_data_tables_base(self, q, uidunique):
+ """
+ Initialise the underlying database tables.
+ @param q: a database cursor to use.
+ """
+ #
+ # RESOURCE table is the primary index table
+ # NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
+ # UID: iCalendar UID (may or may not be unique)
+ #
+ q.execute(
+ """
+ create table RESOURCE (
+ NAME text unique,
+ UID text unique
+ )
+ """
+ )
+
+ #
+ # REVISIONS table tracks changes
+ # NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
+ # REVISION: revision number
+ # WASDELETED: Y if revision deleted, N if added or changed
+ #
+ 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,
+ DELETED text(1) default "N"
+ )
+ """
+ )
+ q.execute(
+ """
+ create index REVISION on REVISIONS (REVISION)
+ """
+ )
+
+ #
+ # RESERVED table tracks reserved UIDs
+ # UID: The UID being reserved
+ # TIME: When the reservation was made
+ #
+ q.execute(
+ """
+ create table RESERVED (
+ UID text unique,
+ TIME date
+ )
+ """
+ )
+
+ def _db_recreate(self, do_commit=True):
+ """
+ Re-create the database tables from existing address book data.
+ """
+
+ #
+ # Populate the DB with data from already existing resources.
+ # This allows for index recovery if the DB file gets
+ # deleted.
+ #
+ fp = self.resource.fp
+ for name in fp.listdir():
+ if name.startswith("."):
+ continue
+
+ try:
+ stream = fp.child(name).open()
+ except (IOError, OSError), e:
+ log.err("Unable to open resource %s: %s" % (name, e))
+ continue
+
+ try:
+ # FIXME: This is blocking I/O
+ try:
+ vcard = Component.fromStream(stream)
+ vcard.validForCardDAV()
+ except ValueError:
+ log.err("Non-addressbook resource: %s" % (name,))
+ else:
+ #log.msg("Indexing resource: %s" % (name,))
+ self.addResource(name, vcard, True)
+ finally:
+ stream.close()
+
+ # Do commit outside of the loop for better performance
+ if do_commit:
+ self._db_commit()
+
+ def _db_can_upgrade(self, old_version):
+ """
+ Can we do an in-place upgrade
+ """
+
+ # v2 is a minor change
+ return True
+
+ def _db_upgrade_data_tables(self, q, old_version):
+ """
+ Upgrade the data from an older version of the DB.
+ """
+
+ # When going to version 2+ all we need to do is add revision table and index
+ 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,
+ CREATEDREVISION integer default 0,
+ WASDELETED text(1) default "N"
+ )
+ """
+ )
+ q.execute(
+ """
+ create index REVISION on REVISIONS (REVISION)
+ """
+ )
+
+ self._db_execute(
+ """
+ insert into REVISIONS (NAME)
+ select NAME from RESOURCE
+ """
+ )
+
+
+ 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
+ be associated with any given UID and vice versa.
+ NB This method does not commit the changes to the db - the caller
+ MUST take care of that
+ @param name: the name of the resource to add.
+ @param vcard: a L{AddressBook} object representing the resource
+ contents.
+ """
+ uid = vcard.resourceUID()
+
+ self._db_execute(
+ """
+ insert into RESOURCE (NAME, UID)
+ values (:1, :2)
+ """, name, uid,
+ )
+
+ 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, 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 dorevision:
+ self._db_execute(
+ """
+ update REVISIONS SET REVISION = :1, DELETED = :2
+ where NAME = :3
+ """, self.bumpRevision(fast=True), 'Y', name
+ )
Property changes on: CalendarServer/trunk/txdav/carddav/datastore/index_file.py
___________________________________________________________________
Added: svn:mergeinfo
+ /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394
Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -15,12 +15,20 @@
# limitations under the License.
##
+"""
+SQL backend for CardDAV storage.
+"""
+
__all__ = [
"AddressBookHome",
"AddressBook",
"AddressBookObject",
]
+from zope.interface.declarations import implements
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+
from twext.web2.dav.element.rfc2518 import ResourceType
from twext.web2.http_headers import MimeType
@@ -42,8 +50,8 @@
ADDRESSBOOK_OBJECT_TABLE
from txdav.base.propertystore.base import PropertyName
-from zope.interface.declarations import implements
+
class AddressBookHome(CommonHome):
implements(IAddressBookHome)
@@ -58,15 +66,19 @@
super(AddressBookHome, self).__init__(transaction, ownerUID, resourceID, notifier)
self._shares = SQLLegacyAddressBookShares(self)
+
addressbooks = CommonHome.children
listAddressbooks = CommonHome.listChildren
addressbookWithName = CommonHome.childWithName
createAddressBookWithName = CommonHome.createChildWithName
removeAddressBookWithName = CommonHome.removeChildWithName
+
def createdHome(self):
- self.createAddressBookWithName("addressbook")
+ return self.createAddressBookWithName("addressbook")
+
+
class AddressBook(CommonHomeChild):
"""
File-based implementation of L{IAddressBook}.
@@ -99,13 +111,16 @@
self._revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
self._objectTable = ADDRESSBOOK_OBJECT_TABLE
+
@property
def _addressbookHome(self):
return self._home
+
def resourceType(self):
return ResourceType.addressbook #@UndefinedVariable
+
ownerAddressBookHome = CommonHomeChild.ownerHome
addressbookObjects = CommonHomeChild.objectResources
listAddressbookObjects = CommonHomeChild.listObjectResources
@@ -128,43 +143,52 @@
),
)
+
def _doValidate(self, component):
component.validForCardDAV()
+
def contentType(self):
"""
The content type of Addresbook objects is text/vcard.
"""
return MimeType.fromString("text/vcard; charset=utf-8")
+
+
class AddressBookObject(CommonObjectResource):
implements(IAddressBookObject)
- def __init__(self, name, addressbook, resid):
+ def __init__(self, name, addressbook, resid, uid):
- super(AddressBookObject, self).__init__(name, addressbook, resid)
-
+ super(AddressBookObject, self).__init__(name, addressbook, resid, uid)
self._objectTable = ADDRESSBOOK_OBJECT_TABLE
+
@property
def _addressbook(self):
return self._parentCollection
+
def addressbook(self):
return self._addressbook
+
+ @inlineCallbacks
def setComponent(self, component, inserting=False):
validateAddressBookComponent(self, self._addressbook, component, inserting)
- self.updateDatabase(component, inserting=inserting)
+ yield self.updateDatabase(component, inserting=inserting)
if inserting:
- self._addressbook._insertRevision(self._name)
+ yield self._addressbook._insertRevision(self._name)
else:
- self._addressbook._updateRevision(self._name)
+ yield self._addressbook._updateRevision(self._name)
self._addressbook.notifyChanged()
+
+ @inlineCallbacks
def updateDatabase(self, component, expand_until=None, reCreate=False, inserting=False):
"""
Update the database tables for the new data being written.
@@ -178,7 +202,7 @@
# ADDRESSBOOK_OBJECT table update
if inserting:
- self._resourceID = self._txn.execSQL(
+ self._resourceID = (yield self._txn.execSQL(
"""
insert into ADDRESSBOOK_OBJECT
(ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME, VCARD_TEXT, VCARD_UID)
@@ -192,9 +216,9 @@
componentText,
component.resourceUID(),
]
- )[0][0]
+ ))[0][0]
else:
- self._txn.execSQL(
+ yield self._txn.execSQL(
"""
update ADDRESSBOOK_OBJECT set
(VCARD_TEXT, VCARD_UID, MODIFIED)
@@ -209,31 +233,15 @@
]
)
+
+ @inlineCallbacks
def component(self):
- return VCard.fromString(self.vCardText())
+ returnValue(VCard.fromString((yield self.vCardText())))
- def text(self):
- if self._objectText is None:
- text = self._txn.execSQL(
- "select VCARD_TEXT from ADDRESSBOOK_OBJECT where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- self._objectText = text
- return text
- else:
- return self._objectText
- vCardText = text
+ vCardText = CommonObjectResource.text
- def uid(self):
- return self.component().resourceUID()
- def name(self):
- return self._name
-
- def componentType(self):
- return self.component().mainType()
-
# IDataStoreResource
def contentType(self):
"""
Modified: CalendarServer/trunk/txdav/carddav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/common.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/common.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -17,12 +17,8 @@
"""
Tests for common addressbook store API functions.
"""
+from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
-from zope.interface.verify import verifyObject
-from zope.interface.exceptions import (
- BrokenMethodImplementation, DoesNotImplement
-)
-
from txdav.idav import IPropertyStore, IDataStore
from txdav.base.propertystore.base import PropertyName
@@ -38,8 +34,10 @@
IAddressBookObject, IAddressBookHome,
IAddressBook, IAddressBookTransaction
)
+
+from txdav.common.datastore.test.util import CommonCommonTests
+
from twistedcaldav.vcard import Component as VComponent
-from twistedcaldav.notify import Notifier
from twext.python.filepath import CachingFilePath as FilePath
from twext.web2.dav import davxml
@@ -103,25 +101,8 @@
)
-def assertProvides(testCase, interface, provider):
+class CommonTests(CommonCommonTests):
"""
- Verify that C{provider} properly provides C{interface}
-
- @type interface: L{zope.interface.Interface}
- @type provider: C{provider}
- """
- try:
- verifyObject(interface, provider)
- except BrokenMethodImplementation, e:
- testCase.fail(e)
- except DoesNotImplement, e:
- testCase.fail("%r does not provide %s.%s" %
- (provider, interface.__module__, interface.getName()))
-
-
-
-class CommonTests(object):
- """
Tests for common functionality of interfaces defined in
L{txdav.carddav.iaddressbookstore}.
"""
@@ -152,49 +133,6 @@
raise NotImplementedError()
- lastTransaction = None
- savedStore = None
-
- def transactionUnderTest(self):
- """
- Create a transaction from C{storeUnderTest} and save it as
- C[lastTransaction}. Also makes sure to use the same store, saving the
- value from C{storeUnderTest}.
- """
- if self.lastTransaction is not None:
- return self.lastTransaction
- if self.savedStore is None:
- self.savedStore = self.storeUnderTest()
- txn = self.lastTransaction = self.savedStore.newTransaction(self.id())
- return txn
-
-
- def commit(self):
- """
- Commit the last transaction created from C{transactionUnderTest}, and
- clear it.
- """
- self.lastTransaction.commit()
- self.lastTransaction = None
-
-
- def abort(self):
- """
- Abort the last transaction created from C[transactionUnderTest}, and
- clear it.
- """
- self.lastTransaction.abort()
- self.lastTransaction = None
-
- def setUp(self):
- self.notifierFactory = StubNotifierFactory()
-
- def tearDown(self):
- if self.lastTransaction is not None:
- self.commit()
-
-
-
def homeUnderTest(self):
"""
Get the addressbook home detailed by C{requirements['home1']}.
@@ -202,24 +140,25 @@
return self.transactionUnderTest().addressbookHomeWithUID("home1")
+ @inlineCallbacks
def addressbookUnderTest(self):
"""
Get the addressbook detailed by C{requirements['home1']['addressbook_1']}.
"""
- return self.homeUnderTest().addressbookWithName("addressbook_1")
+ returnValue((yield (yield self.homeUnderTest())
+ .addressbookWithName("addressbook_1")))
+ @inlineCallbacks
def addressbookObjectUnderTest(self):
"""
Get the addressbook detailed by
C{requirements['home1']['addressbook_1']['1.vcf']}.
"""
- return self.addressbookUnderTest().addressbookObjectWithName("1.vcf")
+ returnValue((yield (yield self.addressbookUnderTest())
+ .addressbookObjectWithName("1.vcf")))
- assertProvides = assertProvides
-
-
def test_addressbookStoreProvides(self):
"""
The addressbook store provides L{IAddressBookStore} and its required
@@ -239,114 +178,128 @@
self.assertProvides(IAddressBookTransaction, txn)
+ @inlineCallbacks
def test_homeProvides(self):
"""
The addressbook homes generated by the addressbook store provide
L{IAddressBookHome} and its required attributes.
"""
- self.assertProvides(IAddressBookHome, self.homeUnderTest())
+ self.assertProvides(IAddressBookHome, (yield self.homeUnderTest()))
+ @inlineCallbacks
def test_addressbookProvides(self):
"""
The addressbooks generated by the addressbook store provide L{IAddressBook} and
its required attributes.
"""
- self.assertProvides(IAddressBook, self.addressbookUnderTest())
+ self.assertProvides(IAddressBook, (yield self.addressbookUnderTest()))
+ @inlineCallbacks
def test_addressbookObjectProvides(self):
"""
The addressbook objects generated by the addressbook store provide
L{IAddressBookObject} and its required attributes.
"""
- self.assertProvides(IAddressBookObject, self.addressbookObjectUnderTest())
+ self.assertProvides(IAddressBookObject,
+ (yield self.addressbookObjectUnderTest()))
+
+ @inlineCallbacks
def test_notifierID(self):
- home = self.homeUnderTest()
+ home = yield self.homeUnderTest()
self.assertEquals(home.notifierID(), "CardDAV|home1")
- addressbook = home.addressbookWithName("addressbook_1")
+ addressbook = yield home.addressbookWithName("addressbook_1")
self.assertEquals(addressbook.notifierID(), "CardDAV|home1")
self.assertEquals(addressbook.notifierID(label="collection"), "CardDAV|home1/addressbook_1")
+ @inlineCallbacks
def test_addressbookHomeWithUID_exists(self):
"""
Finding an existing addressbook home by UID results in an object that
provides L{IAddressBookHome} and has a C{uid()} method that returns the
same value that was passed in.
"""
- addressbookHome = (self.transactionUnderTest()
- .addressbookHomeWithUID("home1"))
+ addressbookHome = (yield self.transactionUnderTest()
+ .addressbookHomeWithUID("home1"))
self.assertEquals(addressbookHome.uid(), "home1")
self.assertProvides(IAddressBookHome, addressbookHome)
+ @inlineCallbacks
def test_addressbookHomeWithUID_absent(self):
"""
L{IAddressBookStoreTransaction.addressbookHomeWithUID} should return C{None}
when asked for a non-existent addressbook home.
"""
txn = self.transactionUnderTest()
- self.assertEquals(txn.addressbookHomeWithUID("xyzzy"), None)
+ self.assertEquals((yield txn.addressbookHomeWithUID("xyzzy")), None)
+ @inlineCallbacks
def test_addressbookWithName_exists(self):
"""
L{IAddressBookHome.addressbookWithName} returns an L{IAddressBook} provider,
whose name matches the one passed in.
"""
- home = self.homeUnderTest()
+ home = yield self.homeUnderTest()
for name in home1_addressbookNames:
- addressbook = home.addressbookWithName(name)
+ addressbook = yield home.addressbookWithName(name)
if addressbook is None:
self.fail("addressbook %r didn't exist" % (name,))
self.assertProvides(IAddressBook, addressbook)
self.assertEquals(addressbook.name(), name)
+ @inlineCallbacks
def test_addressbookRename(self):
"""
L{IAddressBook.rename} changes the name of the L{IAddressBook}.
"""
- home = self.homeUnderTest()
- addressbook = home.addressbookWithName("addressbook_1")
- addressbook.rename("some_other_name")
+ home = yield self.homeUnderTest()
+ addressbook = yield home.addressbookWithName("addressbook_1")
+ yield addressbook.rename("some_other_name")
+ @inlineCallbacks
def positiveAssertions():
self.assertEquals(addressbook.name(), "some_other_name")
- self.assertEquals(addressbook, home.addressbookWithName("some_other_name"))
- self.assertEquals(None, home.addressbookWithName("addressbook_1"))
+ self.assertEquals(addressbook, (yield home.addressbookWithName("some_other_name")))
+ self.assertEquals(None, (yield home.addressbookWithName("addressbook_1")))
+ yield positiveAssertions()
+ yield self.commit()
+ home = yield self.homeUnderTest()
+ addressbook = yield home.addressbookWithName("some_other_name")
positiveAssertions()
- self.commit()
- home = self.homeUnderTest()
- addressbook = home.addressbookWithName("some_other_name")
- positiveAssertions()
# FIXME: revert
# FIXME: test for multiple renames
# FIXME: test for conflicting renames (a->b, c->a in the same txn)
+ @inlineCallbacks
def test_addressbookWithName_absent(self):
"""
L{IAddressBookHome.addressbookWithName} returns C{None} for addressbooks which
do not exist.
"""
- self.assertEquals(self.homeUnderTest().addressbookWithName("xyzzy"),
- None)
+ self.assertEquals(
+ (yield (yield self.homeUnderTest()).addressbookWithName("xyzzy")),
+ None)
+ @inlineCallbacks
def test_createAddressBookWithName_absent(self):
"""
L{IAddressBookHome.createAddressBookWithName} creates a new L{IAddressBook} that
can be retrieved with L{IAddressBookHome.addressbookWithName}.
"""
- home = self.homeUnderTest()
+ home = yield self.homeUnderTest()
name = "new"
- self.assertIdentical(home.addressbookWithName(name), None)
- home.createAddressBookWithName(name)
- self.assertNotIdentical(home.addressbookWithName(name), None)
+ self.assertIdentical((yield home.addressbookWithName(name)), None)
+ yield home.createAddressBookWithName(name)
+ self.assertNotIdentical((yield home.addressbookWithName(name)), None)
def checkProperties():
- addressbookProperties = home.addressbookWithName(name).properties()
+ addressbookProperties = (yield home.addressbookWithName(name)).properties()
addressbookType = davxml.ResourceType.addressbook #@UndefinedVariable
self.assertEquals(
addressbookProperties[
@@ -355,15 +308,15 @@
addressbookType
)
checkProperties()
- self.commit()
+ yield self.commit()
# Make sure notification fired after commit
self.assertEquals(self.notifierFactory.history,
[("update", "CardDAV|home1")])
# Make sure it's available in a new transaction; i.e. test the commit.
- home = self.homeUnderTest()
- self.assertNotIdentical(home.addressbookWithName(name), None)
+ home = yield self.homeUnderTest()
+ self.assertNotIdentical((yield home.addressbookWithName(name)), None)
# FIXME: These two lines aren't in the calendar common tests:
# home = self.addressbookStore.newTransaction().addressbookHomeWithUID(
@@ -374,6 +327,7 @@
checkProperties()
+ @inlineCallbacks
def test_createAddressBookWithName_exists(self):
"""
L{IAddressBookHome.createAddressBookWithName} raises
@@ -381,25 +335,27 @@
existing address book.
"""
for name in home1_addressbookNames:
- self.assertRaises(
+ yield self.failUnlessFailure(
+ maybeDeferred(
+ (yield self.homeUnderTest()).createAddressBookWithName, name),
HomeChildNameAlreadyExistsError,
- self.homeUnderTest().createAddressBookWithName, name
)
+ @inlineCallbacks
def test_removeAddressBookWithName_exists(self):
"""
L{IAddressBookHome.removeAddressBookWithName} removes a addressbook that already
exists.
"""
- home = self.homeUnderTest()
+ home = yield self.homeUnderTest()
# FIXME: test transactions
for name in home1_addressbookNames:
- self.assertNotIdentical(home.addressbookWithName(name), None)
- home.removeAddressBookWithName(name)
- self.assertEquals(home.addressbookWithName(name), None)
+ self.assertNotIdentical((yield home.addressbookWithName(name)), None)
+ yield home.removeAddressBookWithName(name)
+ self.assertEquals((yield home.addressbookWithName(name)), None)
- self.commit()
+ yield self.commit()
# Make sure notification fired after commit
self.assertEquals(
@@ -415,27 +371,31 @@
)
+ @inlineCallbacks
def test_removeAddressBookWithName_absent(self):
"""
Attempt to remove an non-existing addressbook should raise.
"""
- home = self.homeUnderTest()
- self.assertRaises(NoSuchHomeChildError,
- home.removeAddressBookWithName, "xyzzy")
+ home = yield self.homeUnderTest()
+ yield self.failUnlessFailure(
+ maybeDeferred(home.removeAddressBookWithName, "xyzzy"),
+ NoSuchHomeChildError
+ )
+ @inlineCallbacks
def test_addressbookObjects(self):
"""
L{IAddressBook.addressbookObjects} will enumerate the addressbook objects present
in the filesystem, in name order, but skip those with hidden names.
"""
- addressbook1 = self.addressbookUnderTest()
- addressbookObjects = list(addressbook1.addressbookObjects())
+ addressbook1 = yield self.addressbookUnderTest()
+ addressbookObjects = list((yield addressbook1.addressbookObjects()))
for addressbookObject in addressbookObjects:
self.assertProvides(IAddressBookObject, addressbookObject)
self.assertEquals(
- addressbook1.addressbookObjectWithName(addressbookObject.name()),
+ (yield addressbook1.addressbookObjectWithName(addressbookObject.name())),
addressbookObject
)
@@ -445,87 +405,95 @@
)
+ @inlineCallbacks
def test_addressbookObjectsWithRemovedObject(self):
"""
L{IAddressBook.addressbookObjects} skips those objects which have been
removed by L{AddressBook.removeAddressBookObjectWithName} in the same
transaction, even if it has not yet been committed.
"""
- addressbook1 = self.addressbookUnderTest()
+ addressbook1 = yield self.addressbookUnderTest()
addressbook1.removeAddressBookObjectWithName("2.vcf")
- addressbookObjects = list(addressbook1.addressbookObjects())
+ addressbookObjects = list((yield addressbook1.addressbookObjects()))
self.assertEquals(set(o.name() for o in addressbookObjects),
set(addressbook1_objectNames) - set(["2.vcf"]))
+ @inlineCallbacks
def test_ownerAddressBookHome(self):
"""
L{IAddressBook.ownerAddressBookHome} should match the home UID.
"""
self.assertEquals(
- self.addressbookUnderTest().ownerAddressBookHome().uid(),
- self.homeUnderTest().uid()
+ (yield self.addressbookUnderTest()).ownerAddressBookHome().uid(),
+ (yield self.homeUnderTest()).uid()
)
+ @inlineCallbacks
def test_addressbookObjectWithName_exists(self):
"""
L{IAddressBook.addressbookObjectWithName} returns an L{IAddressBookObject}
provider for addressbooks which already exist.
"""
- addressbook1 = self.addressbookUnderTest()
+ addressbook1 = yield self.addressbookUnderTest()
for name in addressbook1_objectNames:
- addressbookObject = addressbook1.addressbookObjectWithName(name)
+ addressbookObject = yield addressbook1.addressbookObjectWithName(name)
self.assertProvides(IAddressBookObject, addressbookObject)
self.assertEquals(addressbookObject.name(), name)
# FIXME: add more tests based on CommonTests.requirements
+ @inlineCallbacks
def test_addressbookObjectWithName_absent(self):
"""
L{IAddressBook.addressbookObjectWithName} returns C{None} for addressbooks which
don't exist.
"""
- addressbook1 = self.addressbookUnderTest()
- self.assertEquals(addressbook1.addressbookObjectWithName("xyzzy"), None)
+ addressbook1 = yield self.addressbookUnderTest()
+ self.assertEquals((yield addressbook1.addressbookObjectWithName("xyzzy")), None)
+ @inlineCallbacks
def test_removeAddressBookObjectWithUID_exists(self):
"""
Remove an existing addressbook object.
"""
- addressbook = self.addressbookUnderTest()
+ addressbook = yield self.addressbookUnderTest()
for name in addressbook1_objectNames:
uid = (u'uid' + name.rstrip(".vcf"))
- self.assertNotIdentical(addressbook.addressbookObjectWithUID(uid),
- None)
- addressbook.removeAddressBookObjectWithUID(uid)
+ self.assertNotIdentical(
+ (yield addressbook.addressbookObjectWithUID(uid)),
+ None
+ )
+ yield addressbook.removeAddressBookObjectWithUID(uid)
self.assertEquals(
- addressbook.addressbookObjectWithUID(uid),
+ (yield addressbook.addressbookObjectWithUID(uid)),
None
)
self.assertEquals(
- addressbook.addressbookObjectWithName(name),
+ (yield addressbook.addressbookObjectWithName(name)),
None
)
+ @inlineCallbacks
def test_removeAddressBookObjectWithName_exists(self):
"""
Remove an existing addressbook object.
"""
- addressbook = self.addressbookUnderTest()
+ addressbook = yield self.addressbookUnderTest()
for name in addressbook1_objectNames:
self.assertNotIdentical(
- addressbook.addressbookObjectWithName(name), None
+ (yield addressbook.addressbookObjectWithName(name)), None
)
- addressbook.removeAddressBookObjectWithName(name)
+ yield addressbook.removeAddressBookObjectWithName(name)
self.assertIdentical(
- addressbook.addressbookObjectWithName(name), None
+ (yield addressbook.addressbookObjectWithName(name)), None
)
# Make sure notifications are fired after commit
- self.commit()
+ yield self.commit()
self.assertEquals(
self.notifierFactory.history,
[
@@ -539,37 +507,43 @@
)
+ @inlineCallbacks
def test_removeAddressBookObjectWithName_absent(self):
"""
Attempt to remove an non-existing addressbook object should raise.
"""
- addressbook = self.addressbookUnderTest()
- self.assertRaises(
- NoSuchObjectResourceError,
- addressbook.removeAddressBookObjectWithName, "xyzzy"
+ addressbook = yield self.addressbookUnderTest()
+ yield self.failUnlessFailure(
+ maybeDeferred(addressbook.removeAddressBookObjectWithName, "xyzzy"),
+ NoSuchObjectResourceError
)
+ @inlineCallbacks
def test_addressbookName(self):
"""
L{AddressBook.name} reflects the name of the addressbook.
"""
- self.assertEquals(self.addressbookUnderTest().name(), "addressbook_1")
+ self.assertEquals((yield self.addressbookUnderTest()).name(), "addressbook_1")
+ @inlineCallbacks
def test_addressbookObjectName(self):
"""
L{IAddressBookObject.name} reflects the name of the addressbook object.
"""
- self.assertEquals(self.addressbookObjectUnderTest().name(), "1.vcf")
+ self.assertEquals(
+ (yield self.addressbookObjectUnderTest()).name(),
+ "1.vcf")
+ @inlineCallbacks
def test_component(self):
"""
L{IAddressBookObject.component} returns a L{VComponent} describing the
addressbook data underlying that addressbook object.
"""
- component = self.addressbookObjectUnderTest().component()
+ component = yield (yield self.addressbookObjectUnderTest()).component()
self.failUnless(
isinstance(component, VComponent),
@@ -580,35 +554,42 @@
self.assertEquals(component.resourceUID(), "uid1")
+ @inlineCallbacks
def test_iAddressBookText(self):
"""
L{IAddressBookObject.iAddressBookText} returns a C{str} describing the same
data provided by L{IAddressBookObject.component}.
"""
- text = self.addressbookObjectUnderTest().vCardText()
+ text = yield (yield self.addressbookObjectUnderTest()).vCardText()
self.assertIsInstance(text, str)
self.failUnless(text.startswith("BEGIN:VCARD\r\n"))
self.assertIn("\r\nUID:uid1\r\n", text)
self.failUnless(text.endswith("\r\nEND:VCARD\r\n"))
+ @inlineCallbacks
def test_addressbookObjectUID(self):
"""
L{IAddressBookObject.uid} returns a C{str} describing the C{UID} property
of the addressbook object's component.
"""
- self.assertEquals(self.addressbookObjectUnderTest().uid(), "uid1")
+ self.assertEquals((yield self.addressbookObjectUnderTest()).uid(), "uid1")
+ @inlineCallbacks
def test_addressbookObjectWithUID_absent(self):
"""
L{IAddressBook.addressbookObjectWithUID} returns C{None} for addressbooks which
don't exist.
"""
- addressbook1 = self.addressbookUnderTest()
- self.assertEquals(addressbook1.addressbookObjectWithUID("xyzzy"), None)
+ addressbook1 = yield self.addressbookUnderTest()
+ self.assertEquals(
+ (yield addressbook1.addressbookObjectWithUID("xyzzy")),
+ None
+ )
+ @inlineCallbacks
def test_addressbooks(self):
"""
L{IAddressBookHome.addressbooks} returns an iterable of L{IAddressBook}
@@ -617,13 +598,15 @@
"""
# Add a dot directory to make sure we don't find it
# self.home1._path.child(".foo").createDirectory()
- home = self.homeUnderTest()
- addressbooks = list(home.addressbooks())
+ home = yield self.homeUnderTest()
+ addressbooks = list((yield home.addressbooks()))
for addressbook in addressbooks:
self.assertProvides(IAddressBook, addressbook)
- self.assertEquals(addressbook,
- home.addressbookWithName(addressbook.name()))
+ self.assertEquals(
+ addressbook,
+ (yield home.addressbookWithName(addressbook.name()))
+ )
self.assertEquals(
set(c.name() for c in addressbooks),
@@ -631,33 +614,37 @@
)
+ @inlineCallbacks
def test_addressbooksAfterAddAddressBook(self):
"""
L{IAddressBookHome.addressbooks} includes addressbooks recently added with
L{IAddressBookHome.createAddressBookWithName}.
"""
- home = self.homeUnderTest()
- before = set(x.name() for x in home.addressbooks())
- home.createAddressBookWithName("new-name")
- after = set(x.name() for x in home.addressbooks())
+ home = yield self.homeUnderTest()
+ allAddressbooks = yield home.addressbooks()
+ before = set(x.name() for x in allAddressbooks)
+ yield home.createAddressBookWithName("new-name")
+ allAddressbooks = yield home.addressbooks()
+ after = set(x.name() for x in allAddressbooks)
self.assertEquals(before | set(['new-name']), after)
+ @inlineCallbacks
def test_createAddressBookObjectWithName_absent(self):
"""
L{IAddressBook.createAddressBookObjectWithName} creates a new
L{IAddressBookObject}.
"""
- addressbook1 = self.addressbookUnderTest()
+ addressbook1 = yield self.addressbookUnderTest()
name = "4.vcf"
- self.assertIdentical(addressbook1.addressbookObjectWithName(name), None)
+ self.assertIdentical((yield addressbook1.addressbookObjectWithName(name)), None)
component = VComponent.fromString(vcard4_text)
- addressbook1.createAddressBookObjectWithName(name, component)
+ yield addressbook1.createAddressBookObjectWithName(name, component)
- addressbookObject = addressbook1.addressbookObjectWithName(name)
- self.assertEquals(addressbookObject.component(), component)
+ addressbookObject = yield addressbook1.addressbookObjectWithName(name)
+ self.assertEquals((yield addressbookObject.component()), component)
- self.commit()
+ yield self.commit()
# Make sure notifications fire after commit
self.assertEquals(
@@ -669,82 +656,92 @@
)
+ @inlineCallbacks
def test_createAddressBookObjectWithName_exists(self):
"""
L{IAddressBook.createAddressBookObjectWithName} raises
L{AddressBookObjectNameAlreadyExistsError} if a addressbook object with the
given name already exists in that addressbook.
"""
- self.assertRaises(
- ObjectResourceNameAlreadyExistsError,
- self.addressbookUnderTest().createAddressBookObjectWithName,
- "1.vcf", VComponent.fromString(vcard4_text)
+ self.failUnlessFailure(
+ maybeDeferred((yield self.addressbookUnderTest())
+ .createAddressBookObjectWithName,
+ "1.vcf", VComponent.fromString(vcard4_text)),
+ ObjectResourceNameAlreadyExistsError
)
+ @inlineCallbacks
def test_createAddressBookObjectWithName_invalid(self):
"""
L{IAddressBook.createAddressBookObjectWithName} raises
L{InvalidAddressBookComponentError} if presented with invalid iAddressBook
text.
"""
- self.assertRaises(
- InvalidObjectResourceError,
- self.addressbookUnderTest().createAddressBookObjectWithName,
- "new", VComponent.fromString(vcard4notCardDAV_text)
+ yield self.failUnlessFailure(
+ maybeDeferred((yield self.addressbookUnderTest())
+ .createAddressBookObjectWithName,
+ "new", VComponent.fromString(vcard4notCardDAV_text)),
+ InvalidObjectResourceError
)
+ @inlineCallbacks
def test_setComponent_invalid(self):
"""
L{IAddressBookObject.setComponent} raises L{InvalidIAddressBookDataError} if
presented with invalid iAddressBook text.
"""
- addressbookObject = self.addressbookObjectUnderTest()
- self.assertRaises(
- InvalidObjectResourceError,
- addressbookObject.setComponent,
- VComponent.fromString(vcard4notCardDAV_text)
+ addressbookObject = (yield self.addressbookObjectUnderTest())
+ yield self.failUnlessFailure(
+ maybeDeferred(addressbookObject.setComponent,
+ VComponent.fromString(vcard4notCardDAV_text)),
+ InvalidObjectResourceError
)
+ @inlineCallbacks
def test_setComponent_uidchanged(self):
"""
- L{IAddressBookObject.setComponent} raises L{InvalidAddressBookComponentError}
- when given a L{VComponent} whose UID does not match its existing UID.
+ L{IAddressBookObject.setComponent} raises
+ L{InvalidAddressBookComponentError} when given a L{VComponent} whose
+ UID does not match its existing UID.
"""
- addressbook1 = self.addressbookUnderTest()
+ addressbook1 = yield self.addressbookUnderTest()
component = VComponent.fromString(vcard4_text)
- addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
- self.assertRaises(
- InvalidObjectResourceError,
- addressbookObject.setComponent, component
+ addressbookObject = yield addressbook1.addressbookObjectWithName("1.vcf")
+ yield self.failUnlessFailure(
+ maybeDeferred(addressbookObject.setComponent, component),
+ InvalidObjectResourceError
)
+ @inlineCallbacks
def test_addressbookHomeWithUID_create(self):
"""
- L{IAddressBookStoreTransaction.addressbookHomeWithUID} with C{create=True}
- will create a addressbook home that doesn't exist yet.
+ L{IAddressBookStoreTransaction.addressbookHomeWithUID} with
+ C{create=True} will create a addressbook home that doesn't exist yet.
"""
txn = self.transactionUnderTest()
noHomeUID = "xyzzy"
- addressbookHome = txn.addressbookHomeWithUID(
+ addressbookHome = yield txn.addressbookHomeWithUID(
noHomeUID,
create=True
)
+ @inlineCallbacks
def readOtherTxn():
otherTxn = self.savedStore.newTransaction()
self.addCleanup(otherTxn.commit)
- return otherTxn.addressbookHomeWithUID(noHomeUID)
+ returnValue((yield otherTxn.addressbookHomeWithUID(noHomeUID)))
self.assertProvides(IAddressBookHome, addressbookHome)
- # A concurrent transaction shouldn't be able to read it yet:
- self.assertIdentical(readOtherTxn(), None)
- self.commit()
+ # A concurrent tnransaction shouldn't be able to read it yet:
+ self.assertIdentical((yield readOtherTxn()), None)
+ yield self.commit()
# But once it's committed, other transactions should see it.
- self.assertProvides(IAddressBookHome, readOtherTxn())
+ self.assertProvides(IAddressBookHome, (yield readOtherTxn()))
+ @inlineCallbacks
def test_setComponent(self):
"""
L{AddressBookObject.setComponent} changes the result of
@@ -752,18 +749,18 @@
"""
component = VComponent.fromString(vcard1modified_text)
- addressbook1 = self.addressbookUnderTest()
- addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
- oldComponent = addressbookObject.component()
+ addressbook1 = yield self.addressbookUnderTest()
+ addressbookObject = yield addressbook1.addressbookObjectWithName("1.vcf")
+ oldComponent = yield addressbookObject.component()
self.assertNotEqual(component, oldComponent)
- addressbookObject.setComponent(component)
- self.assertEquals(addressbookObject.component(), component)
+ yield addressbookObject.setComponent(component)
+ self.assertEquals((yield addressbookObject.component()), component)
# Also check a new instance
- addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
- self.assertEquals(addressbookObject.component(), component)
+ addressbookObject = yield addressbook1.addressbookObjectWithName("1.vcf")
+ self.assertEquals((yield addressbookObject.component()), component)
- self.commit()
+ yield self.commit()
# Make sure notification fired after commit
self.assertEquals(
@@ -774,6 +771,7 @@
]
)
+
def checkPropertiesMethod(self, thunk):
"""
Verify that the given object has a properties method that returns an
@@ -783,40 +781,45 @@
self.assertProvides(IPropertyStore, properties)
+ @inlineCallbacks
def test_homeProperties(self):
"""
L{IAddressBookHome.properties} returns a property store.
"""
- self.checkPropertiesMethod(self.homeUnderTest())
+ self.checkPropertiesMethod((yield self.homeUnderTest()))
+ @inlineCallbacks
def test_addressbookProperties(self):
"""
L{IAddressBook.properties} returns a property store.
"""
- self.checkPropertiesMethod(self.addressbookUnderTest())
+ self.checkPropertiesMethod((yield self.addressbookUnderTest()))
+ @inlineCallbacks
def test_addressbookObjectProperties(self):
"""
L{IAddressBookObject.properties} returns a property store.
"""
- self.checkPropertiesMethod(self.addressbookObjectUnderTest())
+ self.checkPropertiesMethod((yield self.addressbookObjectUnderTest()))
+ @inlineCallbacks
def test_newAddressBookObjectProperties(self):
"""
L{IAddressBookObject.properties} returns an empty property store for a
addressbook object which has been created but not committed.
"""
- addressbook = self.addressbookUnderTest()
- addressbook.createAddressBookObjectWithName(
+ addressbook = yield self.addressbookUnderTest()
+ yield addressbook.createAddressBookObjectWithName(
"4.vcf", VComponent.fromString(vcard4_text)
)
- newEvent = addressbook.addressbookObjectWithName("4.vcf")
+ newEvent = yield addressbook.addressbookObjectWithName("4.vcf")
self.assertEquals(newEvent.properties().items(), [])
+ @inlineCallbacks
def test_setComponentPreservesProperties(self):
"""
L{IAddressBookObject.setComponent} preserves properties.
@@ -830,16 +833,18 @@
propertyContent.name = propertyName.name
propertyContent.namespace = propertyName.namespace
- self.addressbookObjectUnderTest().properties()[
+ (yield self.addressbookObjectUnderTest()).properties()[
propertyName] = propertyContent
- self.commit()
+ yield self.commit()
# Sanity check; are properties even readable in a separate transaction?
# Should probably be a separate test.
self.assertEquals(
- self.addressbookObjectUnderTest().properties()[propertyName],
+ (yield self.addressbookObjectUnderTest()).properties()[
+ propertyName
+ ],
propertyContent)
- obj = self.addressbookObjectUnderTest()
- vcard1_text = obj.vCardText()
+ obj = yield self.addressbookObjectUnderTest()
+ vcard1_text = yield obj.vCardText()
vcard1_text_withDifferentNote = vcard1_text.replace(
"NOTE:CardDAV protocol updates",
"NOTE:Changed"
@@ -851,43 +856,47 @@
# Putting everything into a separate transaction to account for any
# caching that may take place.
- self.commit()
+ yield self.commit()
self.assertEquals(
- self.addressbookObjectUnderTest().properties()[propertyName],
+ (yield self.addressbookObjectUnderTest()).properties()[propertyName],
propertyContent
)
+ @inlineCallbacks
def test_dontLeakAddressbooks(self):
"""
Addressbooks in one user's addressbook home should not show up in another
user's addressbook home.
"""
- home2 = self.transactionUnderTest().addressbookHomeWithUID(
- "home2", create=True)
- self.assertIdentical(home2.addressbookWithName("addressbook_1"), None)
+ home2 = yield self.transactionUnderTest().addressbookHomeWithUID(
+ "home2", create=True
+ )
+ self.assertIdentical((yield home2.addressbookWithName("addressbook_1")), None)
+ @inlineCallbacks
def test_dontLeakObjects(self):
"""
Addressbook objects in one user's addressbook should not show up in another
user's via uid or name queries.
"""
- home1 = self.homeUnderTest()
- home2 = self.transactionUnderTest().addressbookHomeWithUID(
+ home1 = yield self.homeUnderTest()
+ home2 = yield self.transactionUnderTest().addressbookHomeWithUID(
"home2", create=True)
- addressbook1 = home1.addressbookWithName("addressbook_1")
- addressbook2 = home2.addressbookWithName("addressbook")
- objects = list(home2.addressbookWithName("addressbook").addressbookObjects())
+ addressbook1 = yield home1.addressbookWithName("addressbook_1")
+ addressbook2 = yield home2.addressbookWithName("addressbook")
+ objects = list((yield (yield home2.addressbookWithName("addressbook")).addressbookObjects()))
self.assertEquals(objects, [])
for resourceName in self.requirements['home1']['addressbook_1'].keys():
- obj = addressbook1.addressbookObjectWithName(resourceName)
+ obj = yield addressbook1.addressbookObjectWithName(resourceName)
self.assertIdentical(
- addressbook2.addressbookObjectWithName(resourceName), None)
+ (yield addressbook2.addressbookObjectWithName(resourceName)), None)
self.assertIdentical(
- addressbook2.addressbookObjectWithUID(obj.uid()), None)
+ (yield addressbook2.addressbookObjectWithUID(obj.uid())), None)
+ @inlineCallbacks
def test_eachAddressbookHome(self):
"""
L{IAddressbookTransaction.eachAddressbookHome} returns an iterator that
@@ -897,11 +906,11 @@
additionalUIDs = set('alpha-uid home2 home3 beta-uid'.split())
txn = self.transactionUnderTest()
for name in additionalUIDs:
- txn.addressbookHomeWithUID(name, create=True)
- self.commit()
+ yield txn.addressbookHomeWithUID(name, create=True)
+ yield self.commit()
foundUIDs = set([])
lastTxn = None
- for txn, home in self.storeUnderTest().eachAddressbookHome():
+ for txn, home in (yield self.storeUnderTest().eachAddressbookHome()):
self.addCleanup(txn.commit)
foundUIDs.add(home.uid())
self.assertNotIdentical(lastTxn, txn)
@@ -915,18 +924,3 @@
-class StubNotifierFactory(object):
-
- """ For testing push notifications without an XMPP server """
-
- def __init__(self):
- self.reset()
-
- def newNotifier(self, label="default", id=None, prefix=None):
- return Notifier(self, label=label, id=id, prefix=prefix)
-
- def send(self, op, id):
- self.history.append((op, id))
-
- def reset(self):
- self.history = []
Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -18,9 +18,11 @@
File addressbook store tests.
"""
-from twext.python.filepath import CachingFilePath as FilePath
+from twisted.internet.defer import inlineCallbacks
from twisted.trial import unittest
+from twext.python.filepath import CachingFilePath as FilePath
+
from twistedcaldav.vcard import Component as VComponent
from txdav.common.icommondatastore import HomeChildNameNotAllowedError
@@ -33,8 +35,10 @@
from txdav.carddav.datastore.file import AddressBook, AddressBookObject
from txdav.carddav.datastore.test.common import (
- CommonTests, vcard4_text, vcard1modified_text, StubNotifierFactory)
+ CommonTests, vcard4_text, vcard1modified_text)
+from txdav.common.datastore.test.util import StubNotifierFactory
+
storePath = FilePath(__file__).parent().child("addressbook_store")
def _todo(f, why):
@@ -59,21 +63,23 @@
test.notifierFactory = StubNotifierFactory()
test.addressbookStore = AddressBookStore(storeRootPath, test.notifierFactory)
- test.txn = test.addressbookStore.newTransaction()
+ test.txn = test.addressbookStore.newTransaction(test.id() + " (old)")
assert test.addressbookStore is not None, "No addressbook store?"
+ at inlineCallbacks
def setUpHome1(test):
setUpAddressBookStore(test)
- test.home1 = test.txn.addressbookHomeWithUID("home1")
+ test.home1 = yield test.txn.addressbookHomeWithUID("home1")
assert test.home1 is not None, "No addressbook home?"
+ at inlineCallbacks
def setUpAddressBook1(test):
- setUpHome1(test)
- test.addressbook1 = test.home1.addressbookWithName("addressbook_1")
+ yield setUpHome1(test)
+ test.addressbook1 = yield test.home1.addressbookWithName("addressbook_1")
assert test.addressbook1 is not None, "No addressbook?"
@@ -87,13 +93,15 @@
setUpAddressBookStore(self)
+ @inlineCallbacks
def test_addressbookHomeWithUID_dot(self):
"""
Filenames starting with "." are reserved by this
implementation, so no UIDs may start with ".".
"""
self.assertEquals(
- self.addressbookStore.newTransaction().addressbookHomeWithUID("xyzzy"),
+ (yield self.addressbookStore.newTransaction(self.id()
+ ).addressbookHomeWithUID(".xyzzy")),
None
)
@@ -102,7 +110,7 @@
class AddressBookHomeTest(unittest.TestCase):
def setUp(self):
- setUpHome1(self)
+ return setUpHome1(self)
def test_init(self):
@@ -176,6 +184,7 @@
)
+ @inlineCallbacks
def test_useIndexImmediately(self):
"""
L{AddressBook._index} is usable in the same transaction it is created, with
@@ -184,21 +193,22 @@
self.home1.createAddressBookWithName("addressbook2")
addressbook = self.home1.addressbookWithName("addressbook2")
index = addressbook._index
- self.assertEquals(set(index.addressbookObjects()),
- set(addressbook.addressbookObjects()))
- self.txn.commit()
- self.txn = self.addressbookStore.newTransaction()
- self.home1 = self.txn.addressbookHomeWithUID("home1")
- addressbook = self.home1.addressbookWithName("addressbook2")
+ self.assertEquals(set((yield index.addressbookObjects())),
+ set((yield addressbook.addressbookObjects())))
+ yield self.txn.commit()
+ self.txn = self.addressbookStore.newTransaction(self.id())
+ self.home1 = yield self.txn.addressbookHomeWithUID("home1")
+ addressbook = yield self.home1.addressbookWithName("addressbook2")
# FIXME: we should be curating our own index here, but in order to fix
# that the code in the old implicit scheduler needs to change. This
# test would be more effective if there were actually some objects in
# this list.
index = addressbook._index
- self.assertEquals(set(index.addressbookObjects()),
- set(addressbook.addressbookObjects()))
+ self.assertEquals(set((yield index.addressbookObjects())),
+ set((yield addressbook.addressbookObjects())))
+ @inlineCallbacks
def test_addressbookObjectWithName_dot(self):
"""
Filenames starting with "." are reserved by this
@@ -207,7 +217,10 @@
"""
name = ".foo.vcf"
self.home1._path.child(name).touch()
- self.assertEquals(self.addressbook1.addressbookObjectWithName(name), None)
+ self.assertEquals(
+ (yield self.addressbook1.addressbookObjectWithName(name)),
+ None
+ )
@featureUnimplemented
@@ -215,14 +228,15 @@
"""
Find existing addressbook object by name.
"""
- addressbookObject = self.addressbook1.addressbookObjectWithUID("1")
+ addressbookObject = yield self.addressbook1.addressbookObjectWithUID("1")
self.failUnless(
isinstance(addressbookObject, AddressBookObject),
addressbookObject
)
self.assertEquals(
addressbookObject.component(),
- self.addressbook1.addressbookObjectWithName("1.vcf").component()
+ (yield self.addressbook1.addressbookObjectWithName("1.vcf")
+ ).component()
)
@@ -246,7 +260,10 @@
should raise.
"""
name = "foo.vcf"
- assert self.addressbook1.addressbookObjectWithName(name) is None
+ self.assertIdentical(
+ (yield self.addressbook1.addressbookObjectWithName(name)),
+ None
+ )
component = VComponent.fromString(vcard1modified_text)
self.assertRaises(
ObjectResourceUIDAlreadyExistsError,
@@ -280,16 +297,18 @@
)
+ @inlineCallbacks
def _refresh(self):
"""
Re-read the (committed) home1 and addressbook1 objects in a new
transaction.
"""
- self.txn = self.addressbookStore.newTransaction()
- self.home1 = self.txn.addressbookHomeWithUID("home1")
- self.addressbook1 = self.home1.addressbookWithName("addressbook_1")
+ self.txn = self.addressbookStore.newTransaction(self.id())
+ self.home1 = yield self.txn.addressbookHomeWithUID("home1")
+ self.addressbook1 = yield self.home1.addressbookWithName("addressbook_1")
+ @inlineCallbacks
def test_undoCreateAddressBookObject(self):
"""
If a addressbook object is created as part of a transaction, it will be
@@ -297,19 +316,21 @@
"""
# Make sure that the addressbook home is actually committed; rolling back
# addressbook home creation will remove the whole directory.
- self.txn.commit()
- self._refresh()
+ yield self.txn.commit()
+ yield self._refresh()
self.addressbook1.createAddressBookObjectWithName(
"sample.vcf",
VComponent.fromString(vcard4_text)
)
- self._refresh()
+ yield self.txn.abort()
+ yield self._refresh()
self.assertIdentical(
- self.addressbook1.addressbookObjectWithName("sample.vcf"),
+ (yield self.addressbook1.addressbookObjectWithName("sample.vcf")),
None
)
+ @inlineCallbacks
def doThenUndo(self):
"""
Commit the current transaction, but add an operation that will cause it
@@ -321,27 +342,28 @@
raise RuntimeError("oops")
self.txn.addOperation(fail, "dummy failing operation")
self.assertRaises(RuntimeError, self.txn.commit)
- self._refresh()
+ yield self._refresh()
+ @inlineCallbacks
def test_undoModifyAddressBookObject(self):
"""
If an existing addressbook object is modified as part of a transaction, it
should be restored to its previous status if the transaction aborts.
"""
- originalComponent = self.addressbook1.addressbookObjectWithName(
+ originalComponent = yield self.addressbook1.addressbookObjectWithName(
"1.vcf").component()
- self.addressbook1.addressbookObjectWithName("1.vcf").setComponent(
+ (yield self.addressbook1.addressbookObjectWithName("1.vcf")).setComponent(
VComponent.fromString(vcard1modified_text)
)
# Sanity check.
self.assertEquals(
- self.addressbook1.addressbookObjectWithName("1.vcf").component(),
+ (yield self.addressbook1.addressbookObjectWithName("1.vcf")).component(),
VComponent.fromString(vcard1modified_text)
)
- self.doThenUndo()
+ yield self.doThenUndo()
self.assertEquals(
- self.addressbook1.addressbookObjectWithName("1.vcf").component(),
+ (yield self.addressbook1.addressbookObjectWithName("1.vcf")).component(),
originalComponent
)
@@ -352,13 +374,14 @@
memory, to avoid unnecessary parsing round-trips.
"""
modifiedComponent = VComponent.fromString(vcard1modified_text)
- self.addressbook1.addressbookObjectWithName("1.vcf").setComponent(
+ (yield self.addressbook1.addressbookObjectWithName("1.vcf")).setComponent(
modifiedComponent
)
self.assertIdentical(
modifiedComponent,
- self.addressbook1.addressbookObjectWithName("1.vcf").component()
+ (yield self.addressbook1.addressbookObjectWithName("1.vcf")).component()
)
+ self.txn.commit()
@featureUnimplemented
@@ -399,9 +422,11 @@
class AddressBookObjectTest(unittest.TestCase):
+
+ @inlineCallbacks
def setUp(self):
- setUpAddressBook1(self)
- self.object1 = self.addressbook1.addressbookObjectWithName("1.vcf")
+ yield setUpAddressBook1(self)
+ self.object1 = yield self.addressbook1.addressbookObjectWithName("1.vcf")
def test_init(self):
@@ -420,7 +445,7 @@
)
-class FileStorageTests(unittest.TestCase, CommonTests):
+class FileStorageTests(CommonTests, unittest.TestCase):
"""
File storage tests.
"""
@@ -445,10 +470,12 @@
self.storeRootPath)
+ @inlineCallbacks
def test_addressbookObjectsWithDotFile(self):
"""
- Adding a dotfile to the addressbook home should not increase
+ Adding a dotfile to the addressbook home should not create a new
+ addressbook object.
"""
self.homeUnderTest()._path.child(".foo").createDirectory()
- self.test_addressbookObjects()
+ yield self.test_addressbookObjects()
Copied: CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py (from rev 6445, CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py)
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py (rev 0)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -0,0 +1,213 @@
+##
+# Copyright (c) 2010 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet import reactor
+from twisted.internet.task import deferLater
+
+from txdav.common.icommondatastore import ReservationError
+from txdav.carddav.datastore.index_file import AddressBookIndex,\
+ MemcachedUIDReserver
+
+from twistedcaldav.test.util import InMemoryMemcacheProtocol
+from twistedcaldav.vcard import Component
+import twistedcaldav.test.util
+
+import os
+
+class MinimalResourceReplacement(object):
+ """
+ Provide the minimal set of attributes and methods from CalDAVFile required
+ by L{Index}.
+ """
+
+ def __init__(self, filePath):
+ self.fp = filePath
+
+
+ def isAddressBookCollection(self):
+ return True
+
+
+ def getChild(self, name):
+ # FIXME: this should really return something with a child method
+ return self.fp.child(name)
+
+
+ def initSyncToken(self):
+ pass
+
+
+
+class SQLIndexTests (twistedcaldav.test.util.TestCase):
+ """
+ Test abstract SQL DB class
+ """
+
+ def setUp(self):
+ super(SQLIndexTests, self).setUp()
+ self.site.resource.isAddressBookCollection = lambda: True
+ self.indexDirPath = self.site.resource.fp
+ # FIXME: since this resource lies about isCalendarCollection, it doesn't
+ # have all the associated backend machinery to actually get children.
+ self.db = AddressBookIndex(MinimalResourceReplacement(self.indexDirPath))
+
+
+ def test_reserve_uid_ok(self):
+ uid = "test-test-test"
+ d = self.db.isReservedUID(uid)
+ d.addCallback(self.assertFalse)
+ d.addCallback(lambda _: self.db.reserveUID(uid))
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertTrue)
+ d.addCallback(lambda _: self.db.unreserveUID(uid))
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertFalse)
+
+ return d
+
+
+ def test_reserve_uid_twice(self):
+ uid = "test-test-test"
+ d = self.db.reserveUID(uid)
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertTrue)
+ d.addCallback(lambda _:
+ self.assertFailure(self.db.reserveUID(uid),
+ ReservationError))
+ return d
+
+
+ def test_unreserve_unreserved(self):
+ uid = "test-test-test"
+ return self.assertFailure(self.db.unreserveUID(uid),
+ ReservationError)
+
+
+ def test_reserve_uid_timeout(self):
+ # WARNING: This test is fundamentally flawed and will fail
+ # intermittently because it uses the real clock.
+ uid = "test-test-test"
+ from twistedcaldav.config import config
+ old_timeout = config.UIDReservationTimeOut
+ config.UIDReservationTimeOut = 1
+
+ def _finally():
+ config.UIDReservationTimeOut = old_timeout
+
+ d = self.db.isReservedUID(uid)
+ d.addCallback(self.assertFalse)
+ d.addCallback(lambda _: self.db.reserveUID(uid))
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertTrue)
+ d.addCallback(lambda _: deferLater(reactor, 2, lambda: None))
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertFalse)
+ self.addCleanup(_finally)
+
+ return d
+
+
+ def test_index(self):
+ data = (
+ (
+ "#1.1 Simple component",
+ "1.1",
+ """BEGIN:VCARD
+VERSION:3.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+UID:12345-67890-1.1
+FN:Cyrus Daboo
+N:Daboo;Cyrus
+EMAIL;TYPE=INTERNET,PREF:cyrus at example.com
+END:VCARD
+""",
+ ),
+ )
+
+ for description, name, vcard_txt in data:
+ 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)
+ self.assertTrue(self.db.resourceExists(name), msg=description)
+
+ self.db._db_recreate()
+ for description, name, vcard_txt in data:
+ self.assertTrue(self.db.resourceExists(name), msg=description)
+
+ def test_index_revisions(self):
+ data1 = """BEGIN:VCARD
+VERSION:3.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+UID:12345-67890-1-1.1
+FN:Cyrus Daboo
+N:Daboo;Cyrus
+EMAIL;TYPE=INTERNET,PREF:cyrus at example.com
+END:VCARD
+"""
+ data2 = """BEGIN:VCARD
+VERSION:3.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+UID:12345-67890-2-1.1
+FN:Wilfredo Sanchez
+N:Sanchez;Wilfredo
+EMAIL;TYPE=INTERNET,PREF:wsanchez at example.com
+END:VCARD
+"""
+ data3 = """BEGIN:VCARD
+VERSION:3.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+UID:12345-67890-3-1.1
+FN:Bruce Gaya
+N:Gaya;Bruce
+EMAIL;TYPE=INTERNET,PREF:bruce at example.com
+END:VCARD
+"""
+
+ vcard = Component.fromString(data1)
+ self.db.addResource("data1.vcf", vcard)
+ vcard = Component.fromString(data2)
+ self.db.addResource("data2.vcf", vcard)
+ vcard = Component.fromString(data3)
+ self.db.addResource("data3.vcf", vcard)
+ self.db.deleteResource("data3.vcf")
+
+ tests = (
+ (0, (["data1.vcf", "data2.vcf",], [],)),
+ (1, (["data2.vcf",], ["data3.vcf",],)),
+ (2, ([], ["data3.vcf",],)),
+ (3, ([], ["data3.vcf",],)),
+ (4, ([], [],)),
+ (5, ([], [],)),
+ )
+
+ for revision, results in tests:
+ for depth in ("1", "infinity"):
+ self.assertEquals(self.db.whatchanged(revision, depth), results, "Mismatched results for whatchanged with revision %d" % (revision,))
+
+class MemcacheTests(SQLIndexTests):
+ def setUp(self):
+ super(MemcacheTests, self).setUp()
+ self.memcache = InMemoryMemcacheProtocol()
+ self.db.reserver = MemcachedUIDReserver(self.db, self.memcache)
+
+
+ def tearDown(self):
+ for _ignore_k, v in self.memcache._timeouts.iteritems():
+ if v.active():
+ v.cancel()
Property changes on: CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py
___________________________________________________________________
Added: svn:mergeinfo
+ /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394
Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -15,12 +15,10 @@
##
"""
-Tests for txdav.caldav.datastore.postgres, mostly based on
-L{txdav.caldav.datastore.test.common}.
+Tests for L{txdav.carddav.datastore.sql}, mostly based on
+L{txdav.carddav.datastore.test.common}.
"""
-import time
-
from txdav.carddav.datastore.test.common import CommonTests as AddressBookCommonTests
from txdav.common.datastore.sql import EADDRESSBOOKTYPE
@@ -31,8 +29,8 @@
from txdav.carddav.datastore.util import _migrateAddressbook, migrateHome
from twisted.trial import unittest
-from twisted.internet.defer import inlineCallbacks
-from twisted.internet.threads import deferToThread
+from twisted.internet.defer import inlineCallbacks, returnValue
+
from twistedcaldav.vcard import Component as VCard
@@ -45,29 +43,30 @@
def setUp(self):
super(AddressBookSQLStorageTests, self).setUp()
self._sqlStore = yield buildStore(self, self.notifierFactory)
- self.populate()
+ yield self.populate()
+ @inlineCallbacks
def populate(self):
populateTxn = self.storeUnderTest().newTransaction()
for homeUID in self.requirements:
addressbooks = self.requirements[homeUID]
if addressbooks is not None:
- home = populateTxn.addressbookHomeWithUID(homeUID, True)
+ home = yield populateTxn.addressbookHomeWithUID(homeUID, True)
# We don't want the default addressbook to appear unless it's
# explicitly listed.
- home.removeAddressBookWithName("addressbook")
+ yield home.removeAddressBookWithName("addressbook")
for addressbookName in addressbooks:
addressbookObjNames = addressbooks[addressbookName]
if addressbookObjNames is not None:
- home.createAddressBookWithName(addressbookName)
- addressbook = home.addressbookWithName(addressbookName)
+ yield home.createAddressBookWithName(addressbookName)
+ addressbook = yield home.addressbookWithName(addressbookName)
for objectName in addressbookObjNames:
objData = addressbookObjNames[objectName]
- addressbook.createAddressBookObjectWithName(
+ yield addressbook.createAddressBookObjectWithName(
objectName, VCard.fromString(objData)
)
- populateTxn.commit()
+ yield populateTxn.commit()
self.notifierFactory.reset()
@@ -79,19 +78,23 @@
return self._sqlStore
+ @inlineCallbacks
def assertAddressbooksSimilar(self, a, b, bAddressbookFilter=None):
"""
Assert that two addressbooks have a similar structure (contain the same
events).
"""
+ @inlineCallbacks
def namesAndComponents(x, filter=lambda x:x.component()):
- return dict([(fromObj.name(), filter(fromObj))
- for fromObj in x.addressbookObjects()])
+ fromObjs = yield x.addressbookObjects()
+ returnValue(dict([(fromObj.name(), (yield filter(fromObj)))
+ for fromObj in fromObjs]))
if bAddressbookFilter is not None:
extra = [bAddressbookFilter]
else:
extra = []
- self.assertEquals(namesAndComponents(a), namesAndComponents(b, *extra))
+ self.assertEquals((yield namesAndComponents(a)),
+ (yield namesAndComponents(b, *extra)))
def assertPropertiesSimilar(self, a, b, disregard=[]):
@@ -121,28 +124,30 @@
return txn
+ @inlineCallbacks
def test_migrateAddressbookFromFile(self):
"""
C{_migrateAddressbook()} can migrate a file-backed addressbook to a
database- backed addressbook.
"""
- fromAddressbook = self.fileTransaction().addressbookHomeWithUID(
+ fromAddressbook = yield self.fileTransaction().addressbookHomeWithUID(
"home1").addressbookWithName("addressbook_1")
- toHome = self.transactionUnderTest().addressbookHomeWithUID(
+ toHome = yield self.transactionUnderTest().addressbookHomeWithUID(
"new-home", create=True)
- toAddressbook = toHome.addressbookWithName("addressbook")
- _migrateAddressbook(fromAddressbook, toAddressbook,
- lambda x: x.component())
- self.assertAddressbooksSimilar(fromAddressbook, toAddressbook)
+ toAddressbook = yield toHome.addressbookWithName("addressbook")
+ yield _migrateAddressbook(fromAddressbook, toAddressbook,
+ lambda x: x.component())
+ yield self.assertAddressbooksSimilar(fromAddressbook, toAddressbook)
+ @inlineCallbacks
def test_migrateHomeFromFile(self):
"""
L{migrateHome} will migrate an L{IAddressbookHome} provider from one
backend to another; in this specific case, from the file-based backend
to the SQL-based backend.
"""
- fromHome = self.fileTransaction().addressbookHomeWithUID("home1")
+ fromHome = yield self.fileTransaction().addressbookHomeWithUID("home1")
builtinProperties = [PropertyName.fromElement(ResourceType)]
@@ -151,19 +156,22 @@
key = PropertyName.fromElement(GETContentLanguage)
fromHome.properties()[key] = GETContentLanguage("C")
- fromHome.addressbookWithName("addressbook_1").properties()[key] = (
+ (yield fromHome.addressbookWithName("addressbook_1")).properties()[
+ key] = (
GETContentLanguage("pig-latin")
)
- toHome = self.transactionUnderTest().addressbookHomeWithUID(
+ toHome = yield self.transactionUnderTest().addressbookHomeWithUID(
"new-home", create=True
)
- migrateHome(fromHome, toHome, lambda x: x.component())
- self.assertEquals(set([c.name() for c in toHome.addressbooks()]),
+ yield migrateHome(fromHome, toHome, lambda x: x.component())
+ toAddressbooks = yield toHome.addressbooks()
+ self.assertEquals(set([c.name() for c in toAddressbooks]),
set([k for k in self.requirements['home1'].keys()
if self.requirements['home1'][k] is not None]))
- for c in fromHome.addressbooks():
+ fromAddressbooks = yield fromHome.addressbooks()
+ for c in fromAddressbooks:
self.assertPropertiesSimilar(
- c, toHome.addressbookWithName(c.name()),
+ c, (yield toHome.addressbookWithName(c.name())),
builtinProperties
)
self.assertPropertiesSimilar(fromHome, toHome, builtinProperties)
@@ -182,58 +190,6 @@
@inlineCallbacks
- def test_homeProvisioningConcurrency(self):
- """
- Test that two concurrent attempts to provision an addressbook home do
- not cause a race-condition whereby the second commit results in a
- second INSERT that violates a unique constraint. Also verify that,
- whilst the two provisioning attempts are happening and doing various
- lock operations, that we do not block other reads of the table.
- """
-
- addressbookStore1 = yield buildStore(self, self.notifierFactory)
- addressbookStore2 = yield buildStore(self, self.notifierFactory)
- addressbookStore3 = yield buildStore(self, self.notifierFactory)
-
- txn1 = addressbookStore1.newTransaction()
- txn2 = addressbookStore2.newTransaction()
- txn3 = addressbookStore3.newTransaction()
-
- # Provision one home now - we will use this to later verify we can do reads of
- # existing data in the table
- home_uid2 = txn3.homeWithUID(EADDRESSBOOKTYPE, "uid2", create=True)
- self.assertNotEqual(home_uid2, None)
- txn3.commit()
-
- home_uid1_1 = txn1.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
-
- def _defer_home_uid1_2():
- home_uid1_2 = txn2.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
- txn2.commit()
- return home_uid1_2
- d1 = deferToThread(_defer_home_uid1_2)
-
- def _pause_home_uid1_1():
- time.sleep(1)
- txn1.commit()
- d2 = deferToThread(_pause_home_uid1_1)
-
- # Verify that we can still get to the existing home - i.e. the lock
- # on the table allows concurrent reads
- txn4 = addressbookStore3.newTransaction()
- home_uid2 = txn4.homeWithUID(EADDRESSBOOKTYPE, "uid2", create=True)
- self.assertNotEqual(home_uid2, None)
- txn4.commit()
-
- # Now do the concurrent provision attempt
- yield d2
- home_uid1_2 = yield d1
-
- self.assertNotEqual(home_uid1_1, None)
- self.assertNotEqual(home_uid1_2, None)
-
-
- @inlineCallbacks
def test_putConcurrency(self):
"""
Test that two concurrent attempts to PUT different address book object resources to the
@@ -245,21 +201,22 @@
# Provision the home now
txn = addressbookStore1.newTransaction()
- home = txn.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
+ home = yield txn.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
self.assertNotEqual(home, None)
- txn.commit()
+ yield txn.commit()
txn1 = addressbookStore1.newTransaction()
txn2 = addressbookStore2.newTransaction()
- home1 = txn1.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
- home2 = txn2.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
-
- adbk1 = home1.addressbookWithName("addressbook")
- adbk2 = home2.addressbookWithName("addressbook")
-
+ home1 = yield txn1.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
+ home2 = yield txn2.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
+
+ adbk1 = yield home1.addressbookWithName("addressbook")
+ adbk2 = yield home2.addressbookWithName("addressbook")
+
+ @inlineCallbacks
def _defer1():
- adbk1.createObjectResourceWithName("1.vcf", VCard.fromString(
+ yield adbk1.createObjectResourceWithName("1.vcf", VCard.fromString(
"""BEGIN:VCARD
VERSION:3.0
N:Thompson;Default1;;;
@@ -273,11 +230,12 @@
END:VCARD
""".replace("\n", "\r\n")
))
- txn1.commit()
- d1 = deferToThread(_defer1)
-
+ yield txn1.commit() # FIXME: CONCURRENT
+ d1 = _defer1()
+
+ @inlineCallbacks
def _defer2():
- adbk2.createObjectResourceWithName("2.vcf", VCard.fromString(
+ yield adbk2.createObjectResourceWithName("2.vcf", VCard.fromString(
"""BEGIN:VCARD
VERSION:3.0
N:Thompson;Default2;;;
@@ -291,8 +249,8 @@
END:VCARD
""".replace("\n", "\r\n")
))
- txn2.commit()
- d2 = deferToThread(_defer2)
+ yield txn2.commit() # FIXME: CONCURRENT
+ d2 = _defer2()
yield d1
yield d2
Modified: CalendarServer/trunk/txdav/carddav/datastore/util.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/util.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/datastore/util.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -18,6 +18,7 @@
"""
Utility logic common to multiple backend implementations.
"""
+from twisted.internet.defer import inlineCallbacks
from twistedcaldav.vcard import Component as VCard
from twistedcaldav.vcard import InvalidVCardDataError
@@ -28,6 +29,7 @@
from twext.python.log import Logger
log = Logger()
+
def validateAddressBookComponent(addressbookObject, vcard, component, inserting):
"""
Validate an addressbook component for a particular addressbook.
@@ -61,6 +63,8 @@
raise InvalidObjectResourceError(e)
+
+ at inlineCallbacks
def _migrateAddressbook(inAddressbook, outAddressbook, getComponent):
"""
Copy all addressbook objects and properties in the given input addressbook
@@ -72,17 +76,18 @@
@param getComponent: a 1-argument callable; see L{migrateHome}.
"""
outAddressbook.properties().update(inAddressbook.properties())
- for addressbookObject in inAddressbook.addressbookObjects():
+ inObjects = yield inAddressbook.addressbookObjects()
+ for addressbookObject in inObjects:
try:
- outAddressbook.createAddressBookObjectWithName(
+ yield outAddressbook.createAddressBookObjectWithName(
addressbookObject.name(),
- addressbookObject.component()) # XXX WRONG SHOULD CALL getComponent
+ (yield addressbookObject.component())) # XXX WRONG SHOULD CALL getComponent
# Only the owner's properties are migrated, since previous releases of
# addressbook server didn't have per-user properties.
- outAddressbook.addressbookObjectWithName(
- addressbookObject.name()).properties().update(
+ (yield outAddressbook.addressbookObjectWithName(
+ addressbookObject.name())).properties().update(
addressbookObject.properties())
except InternalDataStoreError:
@@ -93,16 +98,20 @@
))
+
+ at inlineCallbacks
def migrateHome(inHome, outHome, getComponent=lambda x:x.component()):
- outHome.removeAddressBookWithName("addressbook")
+ yield outHome.removeAddressBookWithName("addressbook")
outHome.properties().update(inHome.properties())
- for addressbook in inHome.addressbooks():
+ inAddressbooks = yield inHome.addressbooks()
+ for addressbook in inAddressbooks:
name = addressbook.name()
- outHome.createAddressBookWithName(name)
- outAddressbook = outHome.addressbookWithName(name)
+ yield outHome.createAddressBookWithName(name)
+ outAddressbook = yield outHome.addressbookWithName(name)
try:
- _migrateAddressbook(addressbook, outAddressbook, getComponent)
+ yield _migrateAddressbook(addressbook, outAddressbook, getComponent)
except InternalDataStoreError:
log.error(" Failed to migrate address book: %s/%s" % (inHome.name(), name,))
+
Modified: CalendarServer/trunk/txdav/carddav/iaddressbookstore.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/iaddressbookstore.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/iaddressbookstore.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -44,8 +44,8 @@
If C{create} is C{True}, create the addressbook home if it doesn't
already exist.
- @return: an L{IAddressBookHome} or C{None} if no such addressbook
- home exists.
+ @return: a L{Deferred} which fires with an L{IAddressBookHome} or
+ C{None} if no such addressbook home exists.
"""
@@ -147,8 +147,9 @@
in this addressbook.
@param name: a string.
- @return: an L{IAddressBookObject} or C{None} if no such addressbook
- object exists.
+
+ @return: a L{Deferred} that fires with an L{IAddressBookObject} or
+ C{None} if no such addressbook object exists.
"""
def addressbookObjectWithUID(uid):
Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/datastore/file.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -49,7 +49,6 @@
from errno import EEXIST, ENOENT
from zope.interface import implements, directlyProvides
-import os
import uuid
ECALENDARTYPE = 0
@@ -891,7 +890,6 @@
listNotificationObjects = CommonHomeChild.listObjectResources
notificationObjectWithName = CommonHomeChild.objectResourceWithName
removeNotificationObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
- notificationObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
def notificationObjectWithUID(self, uid):
name = uid + ".xml"
Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -25,6 +25,10 @@
"CommonHome",
]
+import sys
+
+from Queue import Queue
+
from twext.python.log import Logger, LoggingMixIn
from twext.web2.dav.element.rfc2518 import ResourceType
from twext.web2.http_headers import MimeType
@@ -34,6 +38,10 @@
from twisted.python.modules import getModule
from twisted.python.util import FancyEqMixin
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, returnValue, Deferred
+from twisted.python.failure import Failure
+
from twistedcaldav.customxml import NotificationType
from twistedcaldav.dateops import datetimeMktime
@@ -52,7 +60,7 @@
from txdav.common.inotifications import INotificationCollection, \
INotificationObject
from txdav.base.datastore.sql import memoized
-from txdav.base.datastore.util import cached
+
from txdav.idav import AlreadyFinishedError
from txdav.base.propertystore.base import PropertyName
from txdav.base.propertystore.sql import PropertyStore
@@ -108,7 +116,7 @@
def newTransaction(self, label="unlabeled", migrating=False):
return CommonStoreTransaction(
self,
- self.connectionFactory(),
+ self.connectionFactory,
self.enableCalendars,
self.enableAddressBooks,
self.notifierFactory,
@@ -116,18 +124,104 @@
migrating,
)
+
+_DONE = object()
+
+_STATE_STOPPED = 'STOPPED'
+_STATE_RUNNING = 'RUNNING'
+_STATE_STOPPING = 'STOPPING'
+
+class ThreadHolder(object):
+ """
+ A queue which will hold a reactor threadpool thread open until all of the
+ work in that queue is done.
+ """
+
+ def __init__(self, reactor):
+ self._reactor = reactor
+ self._state = _STATE_STOPPED
+ self._stopper = None
+ self._q = None
+
+
+ def _run(self):
+ """
+ Worker function which runs in a non-reactor thread.
+ """
+ while True:
+ work = self._q.get()
+ if work is _DONE:
+ def finishStopping():
+ self._state = _STATE_STOPPED
+ self._q = None
+ s = self._stopper
+ self._stopper = None
+ s.callback(None)
+ self._reactor.callFromThread(finishStopping)
+ return
+ self._oneWorkUnit(*work)
+
+
+ def _oneWorkUnit(self, deferred, instruction):
+ try:
+ result = instruction()
+ except:
+ etype, evalue, etb = sys.exc_info()
+ def relayFailure():
+ f = Failure(evalue, etype, etb)
+ deferred.errback(f)
+ self._reactor.callFromThread(relayFailure)
+ else:
+ self._reactor.callFromThread(deferred.callback, result)
+
+
+ def submit(self, work):
+ """
+ Submit some work to be run.
+
+ @param work: a 0-argument callable, which will be run in a thread.
+
+ @return: L{Deferred} that fires with the result of L{work}
+ """
+ d = Deferred()
+ self._q.put((d, work))
+ return d
+
+
+ def start(self):
+ """
+ Start this thing, if it's stopped.
+ """
+ if self._state != _STATE_STOPPED:
+ raise RuntimeError("Not stopped.")
+ self._state = _STATE_RUNNING
+ self._q = Queue(0)
+ self._reactor.callInThread(self._run)
+
+
+ def stop(self):
+ """
+ Stop this thing and release its thread, if it's running.
+ """
+ if self._state != _STATE_RUNNING:
+ raise RuntimeError("Not running.")
+ s = self._stopper = Deferred()
+ self._state = _STATE_STOPPING
+ self._q.put(_DONE)
+ return s
+
+
+
class CommonStoreTransaction(object):
"""
Transaction implementation for SQL database.
"""
-
_homeClass = {}
- def __init__(self, store, connection, enableCalendars, enableAddressBooks, notifierFactory, label, migrating=False):
-
+ def __init__(self, store, connectionFactory,
+ enableCalendars, enableAddressBooks,
+ notifierFactory, label, migrating=False):
self._store = store
- self._connection = connection
- self._cursor = connection.cursor()
self._completed = False
self._calendarHomes = {}
self._addressbookHomes = {}
@@ -148,7 +242,20 @@
from txdav.carddav.datastore.sql import AddressBookHome
CommonStoreTransaction._homeClass[ECALENDARTYPE] = CalendarHome
CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = AddressBookHome
+ self._holder = ThreadHolder(reactor)
+ self._holder.start()
+ def initCursor():
+ # support threadlevel=1; we can't necessarily cursor() in a
+ # different thread than we do transactions in.
+ # FIXME: may need to be pooling ThreadHolders along with
+ # connections, if threadlevel=1 requires connect() be called in the
+ # same thread as cursor() et. al.
+ self._connection = connectionFactory()
+ self._cursor = self._connection.cursor()
+ self._holder.submit(initCursor)
+
+
def store(self):
return self._store
@@ -157,8 +264,7 @@
return 'PG-TXN<%s>' % (self._label,)
- def execSQL(self, sql, args=[], raiseOnZeroRowCount=None):
- # print 'EXECUTE %s: %s' % (self._label, sql)
+ def _reallyExecSQL(self, sql, args=[], raiseOnZeroRowCount=None):
self._cursor.execute(sql, args)
if raiseOnZeroRowCount is not None and self._cursor.rowcount == 0:
raise raiseOnZeroRowCount()
@@ -168,75 +274,99 @@
return None
+ noisy = False
+
+ def execSQL(self, *args, **kw):
+ result = self._holder.submit(
+ lambda : self._reallyExecSQL(*args, **kw)
+ )
+ if self.noisy:
+ def reportResult(results):
+ sys.stdout.write("\n".join([
+ "",
+ "SQL: %r %r" % (args, kw),
+ "Results: %r" % (results,),
+ "",
+ ]))
+ return results
+ result.addBoth(reportResult)
+ return result
+
+
def __del__(self):
if not self._completed:
- self._connection.rollback()
- self._connection.close()
+ print 'CommonStoreTransaction.__del__: OK'
+ self.abort()
@memoized('uid', '_calendarHomes')
def calendarHomeWithUID(self, uid, create=False):
return self.homeWithUID(ECALENDARTYPE, uid, create=create)
+
@memoized('uid', '_addressbookHomes')
def addressbookHomeWithUID(self, uid, create=False):
return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
+
+ @inlineCallbacks
def homeWithUID(self, storeType, uid, create=False):
-
if storeType == ECALENDARTYPE:
homeTable = CALENDAR_HOME_TABLE
elif storeType == EADDRESSBOOKTYPE:
homeTable = ADDRESSBOOK_HOME_TABLE
+ else:
+ raise RuntimeError("Unknown home type.")
- data = self.execSQL(
- "select %(column_RESOURCE_ID)s from %(name)s where %(column_OWNER_UID)s = %%s" % homeTable,
+ data = yield self.execSQL(
+ "select %(column_RESOURCE_ID)s from %(name)s"
+ " where %(column_OWNER_UID)s = %%s" % homeTable,
[uid]
)
if not data:
if not create:
- return None
-
+ returnValue(None)
# Need to lock to prevent race condition
# FIXME: this is an entire table lock - ideally we want a row lock
- # but the row does not exist yet. However, the "exclusive" mode does
- # allow concurrent reads so the only thing we block is other attempts
- # to provision a home, which is not too bad
- self.execSQL(
+ # but the row does not exist yet. However, the "exclusive" mode
+ # does allow concurrent reads so the only thing we block is other
+ # attempts to provision a home, which is not too bad
+ yield self.execSQL(
"lock %(name)s in exclusive mode" % homeTable,
)
-
# Now test again
- data = self.execSQL(
- "select %(column_RESOURCE_ID)s from %(name)s where %(column_OWNER_UID)s = %%s" % homeTable,
+ data = yield self.execSQL(
+ "select %(column_RESOURCE_ID)s from %(name)s"
+ " where %(column_OWNER_UID)s = %%s" % homeTable,
[uid]
)
-
if not data:
- self.execSQL(
+ yield self.execSQL(
"insert into %(name)s (%(column_OWNER_UID)s) values (%%s)" % homeTable,
[uid]
)
- home = self.homeWithUID(storeType, uid)
- home.createdHome()
- return home
+ home = yield self.homeWithUID(storeType, uid)
+ yield home.createdHome()
+ returnValue(home)
resid = data[0][0]
-
if self._notifierFactory:
- notifier = self._notifierFactory.newNotifier(id=uid,
- prefix=NotifierPrefixes[storeType])
+ notifier = self._notifierFactory.newNotifier(
+ id=uid, prefix=NotifierPrefixes[storeType]
+ )
else:
notifier = None
+ homeObject = self._homeClass[storeType](self, uid, resid, notifier)
+ yield homeObject._loadPropertyStore()
+ returnValue(homeObject)
- return self._homeClass[storeType](self, uid, resid, notifier)
-
@memoized('uid', '_notificationHomes')
+ @inlineCallbacks
def notificationsWithUID(self, uid):
"""
Implement notificationsWithUID.
"""
- rows = self.execSQL(
+ rows = yield self.execSQL(
"""
select %(column_RESOURCE_ID)s from %(name)s where
%(column_OWNER_UID)s = %%s
@@ -246,34 +376,43 @@
resourceID = rows[0][0]
created = False
else:
- resourceID = str(self.execSQL(
+ resourceID = str((yield self.execSQL(
"insert into %(name)s (%(column_OWNER_UID)s) values (%%s) returning %(column_RESOURCE_ID)s" % NOTIFICATION_HOME_TABLE,
[uid]
- )[0][0])
+ ))[0][0])
created = True
collection = NotificationCollection(self, uid, resourceID)
+ yield collection._loadPropertyStore()
if created:
collection._initSyncToken()
- return collection
+ returnValue(collection)
+
def abort(self):
if not self._completed:
- # print 'ABORTING', self._label
+ def reallyAbort():
+ self._connection.rollback()
+ self._connection.close()
self._completed = True
- self._connection.rollback()
- self._connection.close()
+ result = self._holder.submit(reallyAbort)
+ self._holder.stop()
+ return result
else:
raise AlreadyFinishedError()
def commit(self):
if not self._completed:
- # print 'COMPLETING', self._label
self._completed = True
- self._connection.commit()
- self._connection.close()
- for operation in self._postCommitOperations:
- operation()
+ def postCommit(ignored):
+ for operation in self._postCommitOperations:
+ operation()
+ def reallyCommit():
+ self._connection.commit()
+ self._connection.close()
+ result = self._holder.submit(reallyCommit).addCallback(postCommit)
+ self._holder.stop()
+ return result
else:
raise AlreadyFinishedError()
@@ -283,8 +422,9 @@
Run things after 'commit.'
"""
self._postCommitOperations.append(operation)
- # FIXME: implement.
+
+
class CommonHome(LoggingMixIn):
_childClass = None
@@ -336,13 +476,16 @@
return self.uid()
+ @inlineCallbacks
def children(self):
"""
Retrieve children contained in this home.
"""
- names = self.listChildren()
+ x = []
+ names = yield self.listChildren()
for name in names:
- yield self.childWithName(name)
+ x.append((yield self.childWithName(name)))
+ returnValue(x)
def listChildren(self):
@@ -353,6 +496,7 @@
"""
return self._listChildren(owned=True)
+
def listSharedChildren(self):
"""
Retrieve the names of the children in this home.
@@ -361,6 +505,8 @@
"""
return self._listChildren(owned=False)
+
+ @inlineCallbacks
def _listChildren(self, owned):
"""
Retrieve the names of the children in this home.
@@ -370,7 +516,7 @@
# FIXME: not specified on the interface or exercised by the tests, but
# required by clients of the implementation!
if owned:
- rows = self._txn.execSQL("""
+ rows = yield self._txn.execSQL("""
select %(column_RESOURCE_NAME)s from %(name)s
where
%(column_HOME_RESOURCE_ID)s = %%s and
@@ -379,7 +525,7 @@
[self._resourceID, _BIND_MODE_OWN]
)
else:
- rows = self._txn.execSQL("""
+ rows = yield self._txn.execSQL("""
select %(column_RESOURCE_NAME)s from %(name)s
where
%(column_HOME_RESOURCE_ID)s = %%s and
@@ -390,7 +536,7 @@
)
names = [row[0] for row in rows]
- return names
+ returnValue(names)
@memoized('name', '_children')
@@ -400,11 +546,11 @@
home.
@param name: a string.
- @return: an L{ICalendar} or C{None} if no such child
- exists.
+ @return: an L{ICalendar} or C{None} if no such child exists.
"""
return self._childWithName(name, owned=True)
+
@memoized('name', '_sharedChildren')
def sharedChildWithName(self, name):
"""
@@ -423,6 +569,8 @@
"""
return self._childWithName(name, owned=False)
+
+ @inlineCallbacks
def _childWithName(self, name, owned):
"""
Retrieve the child with the given C{name} contained in this
@@ -434,7 +582,7 @@
"""
if owned:
- data = self._txn.execSQL("""
+ data = yield self._txn.execSQL("""
select %(column_RESOURCE_ID)s from %(name)s
where
%(column_RESOURCE_NAME)s = %%s and
@@ -448,7 +596,7 @@
]
)
else:
- data = self._txn.execSQL("""
+ data = yield self._txn.execSQL("""
select %(column_RESOURCE_ID)s from %(name)s
where
%(column_RESOURCE_NAME)s = %%s and
@@ -463,36 +611,40 @@
)
if not data:
- return None
+ returnValue(None)
resourceID = data[0][0]
if self._notifier:
childID = "%s/%s" % (self.uid(), name)
notifier = self._notifier.clone(label="collection", id=childID)
else:
notifier = None
- return self._childClass(self, name, resourceID, notifier)
+ child = self._childClass(self, name, resourceID, notifier)
+ yield child._loadPropertyStore()
+ returnValue(child)
+
+ @inlineCallbacks
def createChildWithName(self, name):
if name.startswith("."):
raise HomeChildNameNotAllowedError(name)
- rows = self._txn.execSQL(
+ rows = yield self._txn.execSQL(
"select %(column_RESOURCE_NAME)s from %(name)s where "
"%(column_RESOURCE_NAME)s = %%s AND "
"%(column_HOME_RESOURCE_ID)s = %%s" % self._bindTable,
[name, self._resourceID]
)
if rows:
- raise HomeChildNameAlreadyExistsError()
+ raise HomeChildNameAlreadyExistsError(name)
- rows = self._txn.execSQL("select nextval('RESOURCE_ID_SEQ')")
+ rows = yield self._txn.execSQL("select nextval('RESOURCE_ID_SEQ')")
resourceID = rows[0][0]
- self._txn.execSQL(
+ yield self._txn.execSQL(
"insert into %(name)s (%(column_RESOURCE_ID)s) values "
"(%%s)" % self._childTable,
[resourceID])
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
insert into %(name)s (
%(column_HOME_RESOURCE_ID)s,
%(column_RESOURCE_ID)s, %(column_RESOURCE_NAME)s, %(column_BIND_MODE)s,
@@ -503,7 +655,7 @@
_BIND_STATUS_ACCEPTED]
)
- newChild = self.childWithName(name)
+ newChild = yield self.childWithName(name)
newChild.properties()[
PropertyName.fromElement(ResourceType)
] = newChild.resourceType()
@@ -517,14 +669,14 @@
pass
+ @inlineCallbacks
def removeChildWithName(self, name):
-
- child = self.childWithName(name)
+ child = yield self.childWithName(name)
if not child:
raise NoSuchHomeChildError()
- child._deletedSyncToken()
+ yield child._deletedSyncToken()
- self._txn.execSQL(
+ yield self._txn.execSQL(
"delete from %(name)s where %(column_RESOURCE_ID)s = %%s" % self._childTable,
[child._resourceID]
)
@@ -535,8 +687,9 @@
child.notifyChanged()
+ @inlineCallbacks
def syncToken(self):
- revision = self._txn.execSQL(
+ revision = (yield self._txn.execSQL(
"""
select max(%(REV:column_REVISION)s) from %(REV:name)s
where %(REV:column_RESOURCE_ID)s in (
@@ -548,9 +701,11 @@
)
""" % self._revisionBindJoinTable,
[self._resourceID, self._resourceID,]
- )[0][0]
- return "%s#%s" % (self._resourceID, revision)
+ ))[0][0]
+ returnValue("%s#%s" % (self._resourceID, revision))
+
+ @inlineCallbacks
def resourceNamesSinceToken(self, token, depth):
results = [
@@ -560,7 +715,7 @@
wasdeleted
)
for path, collection, name, wasdeleted in
- self._txn.execSQL("""
+ (yield self._txn.execSQL("""
select %(BIND:column_RESOURCE_NAME)s, %(REV:column_COLLECTION_NAME)s, %(REV:column_RESOURCE_NAME)s, %(REV:column_DELETED)s
from %(REV:name)s
left outer join %(BIND:name)s on (
@@ -572,7 +727,7 @@
%(REV:name)s.%(REV:column_HOME_RESOURCE_ID)s = %%s
""" % self._revisionBindJoinTable,
[self._resourceID, token, self._resourceID],
- )
+ ))
]
deleted = []
@@ -593,10 +748,10 @@
changed_collections.add(path)
# Now deal with shared collections
- shares = self.listSharedChildren()
+ shares = yield self.listSharedChildren()
for sharename in shares:
sharetoken = 0 if sharename in changed_collections else token
- shareID = self._txn.execSQL("""
+ shareID = (yield self._txn.execSQL("""
select %(column_RESOURCE_ID)s from %(name)s
where
%(column_RESOURCE_NAME)s = %%s and
@@ -608,7 +763,7 @@
self._resourceID,
_BIND_MODE_OWN
]
- )[0][0]
+ ))[0][0]
results = [
(
sharename,
@@ -616,13 +771,13 @@
wasdeleted
)
for name, wasdeleted in
- self._txn.execSQL("""
+ (yield self._txn.execSQL("""
select %(column_RESOURCE_NAME)s, %(column_DELETED)s
from %(name)s
where %(column_REVISION)s > %%s and %(column_RESOURCE_ID)s = %%s
""" % self._revisionsTable,
[sharetoken, shareID],
- ) if name
+ )) if name
]
for path, name, wasdeleted in results:
@@ -636,16 +791,22 @@
changed.sort()
deleted.sort()
- return changed, deleted,
+ returnValue((changed, deleted))
- @cached
- def properties(self):
- return PropertyStore(
+
+ @inlineCallbacks
+ def _loadPropertyStore(self):
+ props = yield PropertyStore.load(
self.uid(),
self._txn,
self._resourceID
)
+ self._propertyStore = props
+
+ def properties(self):
+ return self._propertyStore
+
# IDataStoreResource
def contentType(self):
@@ -725,16 +886,19 @@
def retrieveOldInvites(self):
return self._invites
+
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
+
def name(self):
return self._name
+ @inlineCallbacks
def rename(self, name):
oldName = self._name
- self._txn.execSQL(
+ yield self._txn.execSQL(
"update %(name)s set %(column_RESOURCE_NAME)s = %%s "
"where %(column_RESOURCE_ID)s = %%s AND "
"%(column_HOME_RESOURCE_ID)s = %%s" % self._bindTable,
@@ -744,7 +908,7 @@
# update memos
del self._home._children[oldName]
self._home._children[name] = self
- self._renameSyncToken()
+ yield self._renameSyncToken()
self.notifyChanged()
@@ -757,67 +921,92 @@
self.properties()._setPerUserUID(uid)
+ @inlineCallbacks
def objectResources(self):
- for name in self.listObjectResources():
- yield self.objectResourceWithName(name)
+ x = []
+ r = x.append
+ for name in (yield self.listObjectResources()):
+ r((yield self.objectResourceWithName(name)))
+ returnValue(x)
+ @inlineCallbacks
def listObjectResources(self):
- rows = self._txn.execSQL(
+ rows = yield self._txn.execSQL(
"select %(column_RESOURCE_NAME)s from %(name)s "
"where %(column_PARENT_RESOURCE_ID)s = %%s" % self._objectTable,
[self._resourceID])
- return sorted([row[0] for row in rows])
+ returnValue(sorted([row[0] for row in rows]))
@memoized('name', '_objects')
+ @inlineCallbacks
def objectResourceWithName(self, name):
- rows = self._txn.execSQL(
- "select %(column_RESOURCE_ID)s from %(name)s "
+ rows = yield self._txn.execSQL(
+ "select %(column_RESOURCE_ID)s, %(column_UID)s from %(name)s "
"where %(column_RESOURCE_NAME)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s" % self._objectTable,
[name, self._resourceID]
)
if not rows:
- return None
- resid = rows[0][0]
- return self._objectResourceClass(name, self, resid)
+ returnValue(None)
+ [resid, uid] = rows[0]
+ returnValue((yield self._makeObjectResource(name, resid, uid)))
+ @inlineCallbacks
+ def _makeObjectResource(self, name, resid, uid):
+ """
+ Create an instance of C{self._objectResourceClass}.
+ """
+ objectResource = yield self._objectResourceClass(
+ name, self, resid, uid
+ )
+ yield objectResource._loadPropertyStore()
+ returnValue(objectResource)
+
+
@memoized('uid', '_objects')
+ @inlineCallbacks
def objectResourceWithUID(self, uid):
- rows = self._txn.execSQL(
- "select %(column_RESOURCE_ID)s, %(column_RESOURCE_NAME)s from %(name)s "
- "where %(column_UID)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s" % self._objectTable,
+ rows = yield self._txn.execSQL(
+ "select %(column_RESOURCE_ID)s, %(column_RESOURCE_NAME)s "
+ "from %(name)s where %(column_UID)s = %%s "
+ "and %(column_PARENT_RESOURCE_ID)s = %%s" % self._objectTable,
[uid, self._resourceID]
)
if not rows:
- return None
+ returnValue(None)
resid = rows[0][0]
name = rows[0][1]
- return self._objectResourceClass(name, self, resid)
+ returnValue((yield self._makeObjectResource(name, resid, uid)))
+ @inlineCallbacks
def createObjectResourceWithName(self, name, component):
if name.startswith("."):
raise ObjectResourceNameNotAllowedError(name)
- rows = self._txn.execSQL(
+ rows = yield self._txn.execSQL(
"select %(column_RESOURCE_ID)s from %(name)s "
- "where %(column_RESOURCE_NAME)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s" % self._objectTable,
+ "where %(column_RESOURCE_NAME)s = %%s "
+ "and %(column_PARENT_RESOURCE_ID)s = %%s" % self._objectTable,
[name, self._resourceID]
)
if rows:
raise ObjectResourceNameAlreadyExistsError()
- objectResource = self._objectResourceClass(name, self, None)
- objectResource.setComponent(component, inserting=True)
+ objectResource = (
+ yield self._makeObjectResource(name, None, component.resourceUID())
+ )
+ yield objectResource.setComponent(component, inserting=True)
# Note: setComponent triggers a notification, so we don't need to
# call notify( ) here like we do for object removal.
+ @inlineCallbacks
def removeObjectResourceWithName(self, name):
- rows = self._txn.execSQL(
+ rows = yield self._txn.execSQL(
"delete from %(name)s "
"where %(column_RESOURCE_NAME)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s "
"returning %(column_UID)s" % self._objectTable,
@@ -827,13 +1016,14 @@
uid = rows[0][0]
self._objects.pop(name, None)
self._objects.pop(uid, None)
- self._deleteRevision(name)
+ yield self._deleteRevision(name)
self.notifyChanged()
+ @inlineCallbacks
def removeObjectResourceWithUID(self, uid):
- rows = self._txn.execSQL(
+ rows = yield self._txn.execSQL(
"delete from %(name)s "
"where %(column_UID)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s "
"returning %(column_RESOURCE_NAME)s" % self._objectTable,
@@ -843,43 +1033,46 @@
name = rows[0][0]
self._objects.pop(name, None)
self._objects.pop(uid, None)
- self._deleteRevision(name)
+ yield self._deleteRevision(name)
self.notifyChanged()
+ @inlineCallbacks
def syncToken(self):
- revision = self._txn.execSQL(
+ revision = (yield self._txn.execSQL(
"""
select max(%(column_REVISION)s) from %(name)s
where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is not null
""" % self._revisionsTable,
[self._resourceID,]
- )[0][0]
+ ))[0][0]
if revision is None:
- revision = self._txn.execSQL(
+ revision = (yield self._txn.execSQL(
"""
select %(column_REVISION)s from %(name)s
where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is null
""" % self._revisionsTable,
[self._resourceID,]
- )[0][0]
-
- return "%s#%s" % (self._resourceID, revision,)
+ ))[0][0]
+ returnValue(("%s#%s" % (self._resourceID, revision,)))
+
def objectResourcesSinceToken(self, token):
raise NotImplementedError()
+
+ @inlineCallbacks
def resourceNamesSinceToken(self, token):
results = [
(name if name else "", deleted)
for name, deleted in
- self._txn.execSQL("""
+ (yield self._txn.execSQL("""
select %(column_RESOURCE_NAME)s, %(column_DELETED)s from %(name)s
where %(column_REVISION)s > %%s and %(column_RESOURCE_ID)s = %%s
""" % self._revisionsTable,
[token, self._resourceID],
- )
+ ))
]
results.sort(key=lambda x:x[1])
@@ -893,12 +1086,14 @@
else:
changed.append(name)
- return changed, deleted,
+ returnValue((changed, deleted))
+
+ @inlineCallbacks
def _initSyncToken(self):
-
+
# Remove any deleted revision entry that uses the same name
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
delete from %(name)s
where %(column_HOME_RESOURCE_ID)s = %%s and %(column_COLLECTION_NAME)s = %%s
""" % self._revisionsTable,
@@ -906,7 +1101,7 @@
)
# Insert new entry
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
insert into %(name)s
(%(column_HOME_RESOURCE_ID)s, %(column_RESOURCE_ID)s, %(column_COLLECTION_NAME)s, %(column_RESOURCE_NAME)s, %(column_REVISION)s, %(column_DELETED)s)
values (%%s, %%s, %%s, null, nextval('%(sequence)s'), FALSE)
@@ -914,9 +1109,11 @@
[self._home._resourceID, self._resourceID, self._name]
)
+
+ @inlineCallbacks
def _updateSyncToken(self):
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
update %(name)s
set (%(column_REVISION)s) = (nextval('%(sequence)s'))
where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is null
@@ -924,9 +1121,11 @@
[self._resourceID,]
)
+
+ @inlineCallbacks
def _renameSyncToken(self):
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
update %(name)s
set (%(column_REVISION)s, %(column_COLLECTION_NAME)s) = (nextval('%(sequence)s'), %%s)
where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is null
@@ -934,19 +1133,21 @@
[self._name, self._resourceID,]
)
+
+ @inlineCallbacks
def _deletedSyncToken(self):
# Remove all child entries
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
delete from %(name)s
where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_ID)s = %%s and %(column_COLLECTION_NAME)s is null
""" % self._revisionsTable,
[self._home._resourceID, self._resourceID,]
)
-
+
# Then adjust collection entry to deleted state (do this for all entries with this collection's
# resource-id so that we deal with direct shares which are not normally removed thorugh an unshare
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
update %(name)s
set (%(column_RESOURCE_ID)s, %(column_REVISION)s, %(column_DELETED)s)
= (null, nextval('%(sequence)s'), TRUE)
@@ -955,24 +1156,27 @@
[self._resourceID,]
)
+
def _insertRevision(self, name):
- self._changeRevision("insert", name)
+ return self._changeRevision("insert", name)
def _updateRevision(self, name):
- self._changeRevision("update", name)
+ return self._changeRevision("update", name)
def _deleteRevision(self, name):
- self._changeRevision("delete", name)
+ return self._changeRevision("delete", name)
+
+ @inlineCallbacks
def _changeRevision(self, action, name):
- nextrevision = self._txn.execSQL("""
+ nextrevision = yield self._txn.execSQL("""
select nextval('%(sequence)s')
""" % self._revisionsTable
)
if action == "delete":
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
update %(name)s
set (%(column_REVISION)s, %(column_DELETED)s) = (%%s, TRUE)
where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
@@ -980,7 +1184,7 @@
[nextrevision, self._resourceID, name]
)
elif action == "update":
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
update %(name)s
set (%(column_REVISION)s) = (%%s)
where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
@@ -992,15 +1196,14 @@
# was deleted. In that case an entry in the REVISIONS table still exists so we have to
# detect that and do db INSERT or UPDATE as appropriate
- self._txn.execSQL("""
+ found = bool( (yield self._txn.execSQL("""
select %(column_RESOURCE_ID)s from %(name)s
where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
""" % self._revisionsTable,
[self._resourceID, name, ]
- )
- found = self._txn._cursor.rowcount != 0
+ )) )
if found:
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
update %(name)s
set (%(column_REVISION)s, %(column_DELETED)s) = (%%s, FALSE)
where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
@@ -1008,7 +1211,7 @@
[nextrevision, self._resourceID, name]
)
else:
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
insert into %(name)s
(%(column_HOME_RESOURCE_ID)s, %(column_RESOURCE_ID)s, %(column_RESOURCE_NAME)s, %(column_REVISION)s, %(column_DELETED)s)
values (%%s, %%s, %%s, %%s, FALSE)
@@ -1016,16 +1219,22 @@
[self._home._resourceID, self._resourceID, name, nextrevision]
)
- @cached
- def properties(self):
- props = PropertyStore(
+
+ @inlineCallbacks
+ def _loadPropertyStore(self):
+ props = yield PropertyStore.load(
self.ownerHome().uid(),
self._txn,
self._resourceID
)
self.initPropertyStore(props)
- return props
+ self._properties = props
+
+ def properties(self):
+ return self._properties
+
+
def initPropertyStore(self, props):
"""
A hook for subclasses to override in order to set up their property
@@ -1033,11 +1242,12 @@
@param props: the L{PropertyStore} from C{properties()}.
"""
- pass
+
def _doValidate(self, component):
raise NotImplementedError
+
# IDataStoreResource
def contentType(self):
raise NotImplementedError()
@@ -1051,38 +1261,44 @@
return 0
+ @inlineCallbacks
def created(self):
- created = self._txn.execSQL(
+ created = (yield self._txn.execSQL(
"select %(column_CREATED)s from %(name)s "
"where %(column_RESOURCE_ID)s = %%s" % self._homeChildTable,
[self._resourceID]
- )[0][0]
+ ))[0][0]
utc = datetime.datetime.strptime(created, "%Y-%m-%d %H:%M:%S.%f")
- return datetimeMktime(utc)
+ returnValue(datetimeMktime(utc))
+
+ @inlineCallbacks
def modified(self):
- modified = self._txn.execSQL(
+ modified = (yield self._txn.execSQL(
"select %(column_MODIFIED)s from %(name)s "
"where %(column_RESOURCE_ID)s = %%s" % self._homeChildTable,
[self._resourceID]
- )[0][0]
+ ))[0][0]
utc = datetime.datetime.strptime(modified, "%Y-%m-%d %H:%M:%S.%f")
- return datetimeMktime(utc)
+ returnValue(datetimeMktime(utc))
+
def notifierID(self, label="default"):
if self._notifier:
return self._notifier.getID(label)
else:
return None
+
def notifyChanged(self):
"""
Trigger a notification of a change
"""
if self._notifier:
self._txn.postCommit(self._notifier.notify)
-
+
+
class CommonObjectResource(LoggingMixIn, FancyEqMixin):
"""
@ivar _path: The path of the file on disk
@@ -1094,19 +1310,38 @@
_objectTable = None
- def __init__(self, name, parent, resid):
+ def __init__(self, name, parent, resid, uid):
self._name = name
self._parentCollection = parent
self._resourceID = resid
self._objectText = None
+ self._uid = uid
+
+ @inlineCallbacks
+ def _loadPropertyStore(self):
+ props = yield PropertyStore.load(
+ self.uid(),
+ self._txn,
+ self._resourceID
+ )
+ self.initPropertyStore(props)
+ self._propertyStore = props
+
+
+ def properties(self):
+ return self._propertyStore
+
+
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
+
@property
def _txn(self):
return self._parentCollection._txn
+
def setComponent(self, component, inserting=False):
raise NotImplementedError
@@ -1115,23 +1350,19 @@
raise NotImplementedError
- def text(self):
- raise NotImplementedError
+ @inlineCallbacks
+ def componentType(self):
+ returnValue((yield self.component()).mainType())
def uid(self):
- raise NotImplementedError
+ return self._uid
- @cached
- def properties(self):
- props = PropertyStore(
- self.uid(),
- self._txn,
- self._resourceID
- )
- self.initPropertyStore(props)
- return props
+ def name(self):
+ return self._name
+
+
def initPropertyStore(self, props):
"""
A hook for subclasses to override in order to set up their property
@@ -1139,42 +1370,64 @@
@param props: the L{PropertyStore} from C{properties()}.
"""
- pass
+
# IDataStoreResource
def contentType(self):
raise NotImplementedError()
+
def md5(self):
return None
+
+ @inlineCallbacks
def size(self):
- size = self._txn.execSQL(
+ size = (yield self._txn.execSQL(
"select character_length(%(column_TEXT)s) from %(name)s "
"where %(column_RESOURCE_ID)s = %%s" % self._objectTable,
[self._resourceID]
- )[0][0]
- return size
+ ))[0][0]
+ returnValue(size)
+ @inlineCallbacks
def created(self):
- created = self._txn.execSQL(
+ created = (yield self._txn.execSQL(
"select %(column_CREATED)s from %(name)s "
"where %(column_RESOURCE_ID)s = %%s" % self._objectTable,
[self._resourceID]
- )[0][0]
+ ))[0][0]
utc = datetime.datetime.strptime(created, "%Y-%m-%d %H:%M:%S.%f")
- return datetimeMktime(utc)
+ returnValue(datetimeMktime(utc))
+
+ @inlineCallbacks
def modified(self):
- modified = self._txn.execSQL(
+ modified = (yield self._txn.execSQL(
"select %(column_MODIFIED)s from %(name)s "
"where %(column_RESOURCE_ID)s = %%s" % self._objectTable,
[self._resourceID]
- )[0][0]
+ ))[0][0]
utc = datetime.datetime.strptime(modified, "%Y-%m-%d %H:%M:%S.%f")
- return datetimeMktime(utc)
+ returnValue(datetimeMktime(utc))
+
+ @inlineCallbacks
+ def text(self):
+ if self._objectText is None:
+ text = (yield self._txn.execSQL(
+ "select %(column_TEXT)s from %(name)s where "
+ "%(column_RESOURCE_ID)s = %%s" % self._objectTable,
+ [self._resourceID]
+ ))[0][0]
+ self._objectText = text
+ returnValue(text)
+ else:
+ returnValue(self._objectText)
+
+
+
class NotificationCollection(LoggingMixIn, FancyEqMixin):
implements(INotificationCollection)
@@ -1192,6 +1445,15 @@
self._notifications = {}
+ @inlineCallbacks
+ def _loadPropertyStore(self):
+ self._propertyStore = yield PropertyStore.load(
+ self._uid,
+ self._txn,
+ self._resourceID
+ )
+
+
def resourceType(self):
return ResourceType.notification #@UndefinedVariable
@@ -1207,17 +1469,24 @@
def uid(self):
return self._uid
+
+ @inlineCallbacks
def notificationObjects(self):
- for name in self.listNotificationObjects():
- yield self.notificationObjectWithName(name)
+ L = []
+ for name in (yield self.listNotificationObjects()):
+ L.append((yield self.notificationObjectWithName(name)))
+ returnValue(L)
+
+ @inlineCallbacks
def listNotificationObjects(self):
- rows = self._txn.execSQL(
+ rows = yield self._txn.execSQL(
"select (NOTIFICATION_UID) from NOTIFICATION "
"where NOTIFICATION_HOME_RESOURCE_ID = %s",
[self._resourceID])
- return sorted(["%s.xml" % row[0] for row in rows])
+ returnValue(sorted(["%s.xml" % row[0] for row in rows]))
+
def _nameToUID(self, name):
"""
Based on the file-backed implementation, the 'name' is just uid +
@@ -1229,48 +1498,55 @@
def notificationObjectWithName(self, name):
return self.notificationObjectWithUID(self._nameToUID(name))
+
@memoized('uid', '_notifications')
+ @inlineCallbacks
def notificationObjectWithUID(self, uid):
- rows = self._txn.execSQL(
+ rows = (yield self._txn.execSQL(
"select RESOURCE_ID from NOTIFICATION "
"where NOTIFICATION_UID = %s and NOTIFICATION_HOME_RESOURCE_ID = %s",
- [uid, self._resourceID])
+ [uid, self._resourceID]))
if rows:
resourceID = rows[0][0]
- return NotificationObject(self, resourceID)
+ no = NotificationObject(self, uid, resourceID)
+ yield no._loadPropertyStore()
+ returnValue(no)
else:
- return None
+ returnValue(None)
+ @inlineCallbacks
def writeNotificationObject(self, uid, xmltype, xmldata):
inserting = False
- notificationObject = self.notificationObjectWithUID(uid)
+ notificationObject = yield self.notificationObjectWithUID(uid)
if notificationObject is None:
- notificationObject = NotificationObject(self, None)
+ notificationObject = NotificationObject(self, uid, None)
inserting = True
- notificationObject.setData(uid, xmltype, xmldata, inserting=inserting)
+ yield notificationObject.setData(uid, xmltype, xmldata, inserting=inserting)
if inserting:
- self._insertRevision("%s.xml" % (uid,))
+ yield self._insertRevision("%s.xml" % (uid,))
else:
- self._updateRevision("%s.xml" % (uid,))
+ yield self._updateRevision("%s.xml" % (uid,))
+
def removeNotificationObjectWithName(self, name):
- self.removeNotificationObjectWithUID(self._nameToUID(name))
+ return self.removeNotificationObjectWithUID(self._nameToUID(name))
+ @inlineCallbacks
def removeNotificationObjectWithUID(self, uid):
- self._txn.execSQL(
+ yield self._txn.execSQL(
"delete from NOTIFICATION "
"where NOTIFICATION_UID = %s and NOTIFICATION_HOME_RESOURCE_ID = %s",
[uid, self._resourceID]
)
self._notifications.pop(uid, None)
- self._deleteRevision("%s.xml" % (uid,))
+ yield self._deleteRevision("%s.xml" % (uid,))
def _initSyncToken(self):
- self._txn.execSQL("""
+ return self._txn.execSQL("""
insert into %(name)s
(%(column_HOME_RESOURCE_ID)s, %(column_RESOURCE_NAME)s, %(column_REVISION)s, %(column_DELETED)s)
values (%%s, null, nextval('%(sequence)s'), FALSE)
@@ -1278,49 +1554,46 @@
[self._resourceID,]
)
+
+ @inlineCallbacks
def syncToken(self):
- revision = self._txn.execSQL(
+ revision = (yield self._txn.execSQL(
"""
select max(%(column_REVISION)s) from %(name)s
where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is not null
""" % self._revisionsTable,
[self._resourceID,]
- )[0][0]
+ ))[0][0]
+
if revision is None:
- revision = self._txn.execSQL(
+ revision = (yield self._txn.execSQL(
"""
select %(column_REVISION)s from %(name)s
where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is null
""" % self._revisionsTable,
[self._resourceID,]
- )[0][0]
+ ))[0][0]
+ returnValue("%s#%s" % (self._resourceID, revision,))
- return "%s#%s" % (self._resourceID, revision,)
def objectResourcesSinceToken(self, token):
raise NotImplementedError()
- def notificationObjectsSinceToken(self, token):
- changed = []
- removed = []
- token = self.syncToken()
- return (changed, removed, token)
-
-
+ @inlineCallbacks
def resourceNamesSinceToken(self, token):
results = [
(name if name else "", deleted)
for name, deleted in
- self._txn.execSQL("""
+ (yield self._txn.execSQL("""
select %(column_RESOURCE_NAME)s, %(column_DELETED)s from %(name)s
where %(column_REVISION)s > %%s and %(column_HOME_RESOURCE_ID)s = %%s
""" % self._revisionsTable,
[token, self._resourceID],
- )
+ ))
]
results.sort(key=lambda x:x[1])
-
+
changed = []
deleted = []
for name, wasdeleted in results:
@@ -1330,12 +1603,12 @@
deleted.append(name)
else:
changed.append(name)
-
- return changed, deleted,
+ returnValue((changed, deleted))
+
+
def _updateSyncToken(self):
-
- self._txn.execSQL("""
+ return self._txn.execSQL("""
update %(name)s
set (%(column_REVISION)s) = (nextval('%(sequence)s'))
where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is null
@@ -1343,24 +1616,29 @@
[self._resourceID,]
)
+
def _insertRevision(self, name):
- self._changeRevision("insert", name)
+ return self._changeRevision("insert", name)
+
def _updateRevision(self, name):
- self._changeRevision("update", name)
+ return self._changeRevision("update", name)
+
def _deleteRevision(self, name):
- self._changeRevision("delete", name)
+ return self._changeRevision("delete", name)
+
+ @inlineCallbacks
def _changeRevision(self, action, name):
- nextrevision = self._txn.execSQL("""
+ nextrevision = yield self._txn.execSQL("""
select nextval('%(sequence)s')
""" % self._revisionsTable
)
if action == "delete":
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
update %(name)s
set (%(column_REVISION)s, %(column_DELETED)s) = (%%s, TRUE)
where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
@@ -1368,7 +1646,7 @@
[nextrevision, self._resourceID, name]
)
elif action == "update":
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
update %(name)s
set (%(column_REVISION)s) = (%%s)
where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
@@ -1380,15 +1658,14 @@
# was deleted. In that case an entry in the REVISIONS table still exists so we have to
# detect that and do db INSERT or UPDATE as appropriate
- self._txn.execSQL("""
+ found = bool( (yield self._txn.execSQL("""
select %(column_HOME_RESOURCE_ID)s from %(name)s
where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
""" % self._revisionsTable,
[self._resourceID, name, ]
- )
- found = self._txn._cursor.rowcount != 0
+ )))
if found:
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
update %(name)s
set (%(column_REVISION)s, %(column_DELETED)s) = (%%s, FALSE)
where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
@@ -1396,7 +1673,7 @@
[nextrevision, self._resourceID, name]
)
else:
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
insert into %(name)s
(%(column_HOME_RESOURCE_ID)s, %(column_RESOURCE_NAME)s, %(column_REVISION)s, %(column_DELETED)s)
values (%%s, %%s, %%s, FALSE)
@@ -1404,21 +1681,21 @@
[self._resourceID, name, nextrevision]
)
- @cached
+
def properties(self):
- return PropertyStore(
- self._uid,
- self._txn,
- self._resourceID
- )
+ return self._propertyStore
+
+
class NotificationObject(LoggingMixIn, FancyEqMixin):
+
implements(INotificationObject)
compareAttributes = '_resourceID _home'.split()
- def __init__(self, home, resourceID):
+ def __init__(self, home, uid, resourceID):
self._home = home
+ self._uid = uid
self._resourceID = resourceID
@@ -1435,22 +1712,28 @@
return self._home
+ def uid(self):
+ return self._uid
+
+
def name(self):
return self.uid() + ".xml"
+ @inlineCallbacks
def setData(self, uid, xmltype, xmldata, inserting=False):
xmltypeString = xmltype.toxml()
if inserting:
- rows = self._txn.execSQL(
+ rows = yield self._txn.execSQL(
"insert into NOTIFICATION (NOTIFICATION_HOME_RESOURCE_ID, NOTIFICATION_UID, XML_TYPE, XML_DATA) "
"values (%s, %s, %s, %s) returning RESOURCE_ID",
[self._home._resourceID, uid, xmltypeString, xmldata]
)
self._resourceID = rows[0][0]
+ yield self._loadPropertyStore()
else:
- self._txn.execSQL(
+ yield self._txn.execSQL(
"update NOTIFICATION set XML_TYPE = %s, XML_DATA = %s "
"where NOTIFICATION_HOME_RESOURCE_ID = %s and NOTIFICATION_UID = %s",
[xmltypeString, xmldata, self._home._resourceID, uid])
@@ -1458,33 +1741,34 @@
self.properties()[PropertyName.fromElement(NotificationType)] = NotificationType(xmltype)
+ @inlineCallbacks
def _fieldQuery(self, field):
- data = self._txn.execSQL(
+ data = yield self._txn.execSQL(
"select " + field + " from NOTIFICATION "
"where RESOURCE_ID = %s",
[self._resourceID]
)
- return data[0][0]
+ returnValue(data[0][0])
def xmldata(self):
return self._fieldQuery("XML_DATA")
- def uid(self):
- return self._fieldQuery("NOTIFICATION_UID")
+ def properties(self):
+ return self._propertyStore
- @cached
- def properties(self):
- props = PropertyStore(
+ @inlineCallbacks
+ def _loadPropertyStore(self):
+ self._propertyStore = yield PropertyStore.load(
self._home.uid(),
self._txn,
self._resourceID
)
- self.initPropertyStore(props)
- return props
+ self.initPropertyStore(self._propertyStore)
+
def initPropertyStore(self, props):
# Setup peruser special properties
props.setSpecialProperties(
@@ -1495,6 +1779,7 @@
),
)
+
def contentType(self):
"""
The content type of NotificationObjects is text/xml.
@@ -1502,32 +1787,40 @@
return MimeType.fromString("text/xml")
+ @inlineCallbacks
def md5(self):
- return hashlib.md5(self.xmldata()).hexdigest()
+ returnValue(hashlib.md5((yield self.xmldata())).hexdigest())
+ @inlineCallbacks
def size(self):
- size = self._txn.execSQL(
+ size = (yield self._txn.execSQL(
"select character_length(XML_DATA) from NOTIFICATION "
"where RESOURCE_ID = %s",
[self._resourceID]
- )[0][0]
- return size
+ ))[0][0]
+ returnValue(size)
+ @inlineCallbacks
def created(self):
- created = self._txn.execSQL(
+ created = (yield self._txn.execSQL(
"select CREATED from NOTIFICATION "
"where RESOURCE_ID = %s",
[self._resourceID]
- )[0][0]
+ ))[0][0]
utc = datetime.datetime.strptime(created, "%Y-%m-%d %H:%M:%S.%f")
- return datetimeMktime(utc)
+ returnValue(datetimeMktime(utc))
+
+ @inlineCallbacks
def modified(self):
- modified = self._txn.execSQL(
+ modified = (yield self._txn.execSQL(
"select MODIFIED from NOTIFICATION "
"where RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
+ ))[0][0]
utc = datetime.datetime.strptime(modified, "%Y-%m-%d %H:%M:%S.%f")
- return datetimeMktime(utc)
+ returnValue(datetimeMktime(utc))
+
+
+
Modified: CalendarServer/trunk/txdav/common/datastore/sql_legacy.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_legacy.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/datastore/sql_legacy.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -26,14 +26,13 @@
from twistedcaldav.sharing import SharedCollectionRecord
from twisted.python import hashlib
-from twisted.internet.defer import succeed
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue
from twext.python.log import Logger, LoggingMixIn
from twistedcaldav import carddavxml
from twistedcaldav.config import config
from twistedcaldav.dateops import normalizeForIndex
-from twistedcaldav.index import IndexedSearchException, ReservationError
from twistedcaldav.memcachepool import CachePoolUserMixIn
from twistedcaldav.notifications import NotificationRecord
from twistedcaldav.query import calendarqueryfilter, calendarquery, \
@@ -41,10 +40,12 @@
from twistedcaldav.query.sqlgenerator import sqlgenerator
from twistedcaldav.sharing import Invite
+from txdav.common.icommondatastore import IndexedSearchException, \
+ ReservationError
from txdav.common.datastore.sql_tables import \
_BIND_MODE_OWN, _BIND_MODE_READ, _BIND_MODE_WRITE, _BIND_MODE_DIRECT, \
- _BIND_STATUS_INVITED, _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED, _BIND_STATUS_INVALID,\
- CALENDAR_BIND_TABLE, CALENDAR_HOME_TABLE, ADDRESSBOOK_HOME_TABLE,\
+ _BIND_STATUS_INVITED, _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED, _BIND_STATUS_INVALID, \
+ CALENDAR_BIND_TABLE, CALENDAR_HOME_TABLE, ADDRESSBOOK_HOME_TABLE, \
ADDRESSBOOK_BIND_TABLE
log = Logger()
@@ -62,11 +63,18 @@
self._collection = notificationsCollection
+ @inlineCallbacks
def _recordForObject(self, notificationObject):
- return NotificationRecord(
- notificationObject.uid(),
- notificationObject.name(),
- notificationObject._fieldQuery("XML_TYPE")) if notificationObject else None
+ if notificationObject:
+ returnValue(
+ NotificationRecord(
+ notificationObject.uid(),
+ notificationObject.name(),
+ (yield notificationObject._fieldQuery("XML_TYPE"))
+ )
+ )
+ else:
+ returnValue(None)
def recordForName(self, name):
@@ -75,22 +83,22 @@
)
+ @inlineCallbacks
def recordForUID(self, uid):
- return self._recordForObject(
- self._collection.notificationObjectWithUID(uid)
- )
+ returnValue((yield self._recordForObject(
+ (yield self._collection.notificationObjectWithUID(uid))
+ )))
def removeRecordForUID(self, uid):
- self._collection.removeNotificationObjectWithUID(uid)
+ return self._collection.removeNotificationObjectWithUID(uid)
def removeRecordForName(self, name):
- self._collection.removeNotificationObjectWithName(name)
+ return self._collection.removeNotificationObjectWithName(name)
-
class SQLLegacyInvites(object):
"""
Emulator for the implicit interface specified by
@@ -106,27 +114,32 @@
# Since we do multi-table requests we need a dict that combines tables
self._combinedTable = {}
for key, value in self._homeTable.iteritems():
- self._combinedTable["HOME:%s" % (key,)] = value
+ self._combinedTable["HOME:%s" % (key,)] = value
for key, value in self._bindTable.iteritems():
- self._combinedTable["BIND:%s" % (key,)] = value
+ self._combinedTable["BIND:%s" % (key,)] = value
+
@property
def _txn(self):
return self._collection._txn
+
def _getHomeWithUID(self, uid):
raise NotImplementedError()
+
def create(self):
"No-op, because the index implicitly always exists in the database."
- pass
+
def remove(self):
"No-op, because the index implicitly always exists in the database."
- pass
+
+ @inlineCallbacks
def allRecords(self):
- for row in self._txn.execSQL(
+ values = []
+ for row in (yield self._txn.execSQL(
"""
select
INVITE.INVITE_UID,
@@ -147,11 +160,14 @@
INVITE.NAME asc
""" % self._combinedTable,
[self._collection._resourceID]
- ):
- yield self._makeInvite(row)
+ )):
+ values.append(self._makeInvite(row))
+ returnValue(values)
+
+ @inlineCallbacks
def recordForUserID(self, userid):
- rows = self._txn.execSQL(
+ rows = yield self._txn.execSQL(
"""
select
INVITE.INVITE_UID,
@@ -170,17 +186,19 @@
""" % self._combinedTable,
[userid]
)
- return self._makeInvite(rows[0]) if rows else None
+ returnValue(self._makeInvite(rows[0]) if rows else None)
+ @inlineCallbacks
def recordForPrincipalURL(self, principalURL):
- for record in self.allRecords():
+ for record in (yield self.allRecords()):
if record.principalURL == principalURL:
- return record
+ returnValue(record)
+ @inlineCallbacks
def recordForInviteUID(self, inviteUID):
- rows = self._txn.execSQL(
+ rows = yield self._txn.execSQL(
"""
select
INVITE.INVITE_UID,
@@ -199,8 +217,9 @@
""" % self._combinedTable,
[inviteUID]
)
- return self._makeInvite(rows[0]) if rows else None
+ returnValue(self._makeInvite(rows[0]) if rows else None)
+
def _makeInvite(self, row):
[inviteuid, common_name, userid, ownerUID,
bindMode, bindStatus, summary] = row
@@ -222,6 +241,8 @@
access, state, summary
)
+
+ @inlineCallbacks
def addOrUpdateRecord(self, record):
bindMode = {'read-only': _BIND_MODE_READ,
'read-write': _BIND_MODE_WRITE}[record.access]
@@ -235,15 +256,15 @@
# it will always contain the UID. The form is '/principals/__uids__/x'
# (and may contain a trailing slash).
principalUID = record.principalURL.split("/")[3]
- shareeHome = self._getHomeWithUID(principalUID)
- rows = self._txn.execSQL(
+ shareeHome = yield self._getHomeWithUID(principalUID)
+ rows = yield self._txn.execSQL(
"select RESOURCE_ID, HOME_RESOURCE_ID from INVITE where RECIPIENT_ADDRESS = %s",
[record.userid]
)
if rows:
[[resourceID, homeResourceID]] = rows
# Invite(inviteuid, userid, principalURL, common_name, access, state, summary)
- self._txn.execSQL(
+ yield self._txn.execSQL(
"""
update %(BIND:name)s
set %(BIND:column_BIND_MODE)s = %%s,
@@ -254,7 +275,7 @@
""" % self._combinedTable,
[bindMode, bindStatus, record.summary, resourceID, homeResourceID]
)
- self._txn.execSQL("""
+ yield self._txn.execSQL("""
update INVITE
set NAME = %s, INVITE_UID = %s
where RECIPIENT_ADDRESS = %s
@@ -262,7 +283,7 @@
[record.name, record.inviteuid, record.userid]
)
else:
- self._txn.execSQL(
+ yield self._txn.execSQL(
"""
insert into INVITE
(
@@ -277,7 +298,7 @@
shareeHome._resourceID, self._collection._resourceID,
record.userid
])
- self._txn.execSQL(
+ yield self._txn.execSQL(
"""
insert into %(BIND:name)s
(
@@ -302,11 +323,13 @@
False,
False,
record.summary
- ])
+ ]
+ )
+ @inlineCallbacks
def removeRecordForUserID(self, userid):
- self._txn.execSQL(
+ yield self._txn.execSQL(
"""
delete from %(BIND:name)s using INVITE
where INVITE.RECIPIENT_ADDRESS = %%s
@@ -315,14 +338,15 @@
""" % self._combinedTable,
[userid]
)
- self._txn.execSQL(
+ yield self._txn.execSQL(
"delete from INVITE where RECIPIENT_ADDRESS = %s",
[userid]
)
+ @inlineCallbacks
def removeRecordForInviteUID(self, inviteUID):
- self._txn.execSQL(
+ yield self._txn.execSQL(
"""
delete from %(BIND:name)s using INVITE
where INVITE.INVITE_UID = %s
@@ -331,11 +355,13 @@
""" % self._combinedTable,
[inviteUID]
)
- self._txn.execSQL(
+ yield self._txn.execSQL(
"delete from INVITE where INVITE_UID = %s",
[inviteUID]
)
+
+
class SQLLegacyCalendarInvites(SQLLegacyInvites):
"""
Emulator for the implicit interface specified by
@@ -347,9 +373,12 @@
self._bindTable = CALENDAR_BIND_TABLE
super(SQLLegacyCalendarInvites, self).__init__(calendar)
+
def _getHomeWithUID(self, uid):
return self._txn.calendarHomeWithUID(uid, create=True)
-
+
+
+
class SQLLegacyAddressBookInvites(SQLLegacyInvites):
"""
Emulator for the implicit interface specified by
@@ -361,9 +390,12 @@
self._bindTable = ADDRESSBOOK_BIND_TABLE
super(SQLLegacyAddressBookInvites, self).__init__(addressbook)
+
def _getHomeWithUID(self, uid):
return self._txn.addressbookHomeWithUID(uid, create=True)
+
+
class SQLLegacyShares(object):
_homeTable = None
@@ -390,12 +422,14 @@
pass
+ @inlineCallbacks
def allRecords(self):
# This should have been a smart join that got all these columns at
# once, but let's not bother to fix it, since the actual query we
# _want_ to do (just look for binds in a particular homes) is
# much simpler anyway; we should just do that.
- shareRows = self._txn.execSQL(
+ all = []
+ shareRows = yield self._txn.execSQL(
"""
select %(column_RESOURCE_ID)s, %(column_RESOURCE_NAME)s, %(column_BIND_MODE)s, %(column_MESSAGE)s
from %(name)s
@@ -407,7 +441,7 @@
)
for resourceID, resourceName, bindMode, summary in shareRows:
if bindMode != _BIND_MODE_DIRECT:
- [[shareuid]] = self._txn.execSQL(
+ [[shareuid]] = yield self._txn.execSQL(
"""
select INVITE_UID
from INVITE
@@ -416,7 +450,7 @@
[resourceID, self._home._resourceID]
)
sharetype = 'I'
- [[ownerHomeID, ownerResourceName]] = self._txn.execSQL(
+ [[ownerHomeID, ownerResourceName]] = yield self._txn.execSQL(
"""
select %(column_HOME_RESOURCE_ID)s, %(column_RESOURCE_NAME)s
from %(name)s
@@ -425,7 +459,7 @@
""" % self._bindTable,
[resourceID, _BIND_MODE_OWN]
)
- [[ownerUID]] = self._txn.execSQL(
+ [[ownerUID]] = yield self._txn.execSQL(
"""
select %(column_OWNER_UID)s from %(name)s
where %(column_RESOURCE_ID)s = %%s
@@ -439,10 +473,10 @@
record = SharedCollectionRecord(
shareuid, sharetype, hosturl, localname, summary
)
- yield record
+ all.append(record)
else:
sharetype = 'D'
- [[ownerHomeID, ownerResourceName]] = self._txn.execSQL(
+ [[ownerHomeID, ownerResourceName]] = yield self._txn.execSQL(
"""
select %(column_HOME_RESOURCE_ID)s, %(column_RESOURCE_NAME)s
from %(name)s
@@ -451,7 +485,7 @@
""" % self._bindTable,
[resourceID, _BIND_MODE_OWN]
)
- [[ownerUID]] = self._txn.execSQL(
+ [[ownerUID]] = yield self._txn.execSQL(
"""
select %(column_OWNER_UID)s from %(name)s
where %(column_RESOURCE_ID)s = %%s
@@ -466,42 +500,40 @@
record = SharedCollectionRecord(
synthesisedUID, sharetype, hosturl, localname, summary
)
- yield record
-
+ all.append(record)
+ returnValue(all)
+
+ @inlineCallbacks
def _search(self, **kw):
[[key, value]] = kw.items()
- for record in self.allRecords():
+ for record in (yield self.allRecords()):
if getattr(record, key) == value:
- return record
+ returnValue((record))
- def recordForLocalName(self, localname):
- return self._search(localname=localname)
def recordForShareUID(self, shareUID):
return self._search(shareuid=shareUID)
+ @inlineCallbacks
def addOrUpdateRecord(self, record):
-# print '*** SHARING***: Adding or updating this record:'
-# import pprint
-# pprint.pprint(record.__dict__)
# record.hosturl -> /.../__uids__/<uid>/<name>
splithost = record.hosturl.split('/')
ownerUID = splithost[3]
ownerCollectionName = splithost[4]
- ownerHome = self._getHomeWithUID(ownerUID)
- ownerCollection = ownerHome.childWithName(ownerCollectionName)
+ ownerHome = yield self._getHomeWithUID(ownerUID)
+ ownerCollection = yield ownerHome.childWithName(ownerCollectionName)
collectionResourceID = ownerCollection._resourceID
if record.sharetype == 'I':
-
+
# There needs to be a bind already, one that corresponds to the
# invitation. The invitation's UID is the same as the share UID. I
# just need to update its 'localname', i.e.
# XXX_BIND.XXX_RESOURCE_NAME.
-
- self._txn.execSQL(
+
+ yield self._txn.execSQL(
"""
update %(name)s
set %(column_RESOURCE_NAME)s = %%s
@@ -511,10 +543,9 @@
[record.localname, self._home._resourceID, collectionResourceID]
)
elif record.sharetype == 'D':
-
# There is no bind entry already so add one.
-
- self._txn.execSQL(
+
+ yield self._txn.execSQL(
"""
insert into %(name)s
(
@@ -539,17 +570,18 @@
True,
record.summary,
])
-
- shareeCollection = self._home.sharedChildWithName(record.localname)
+
+ shareeCollection = yield self._home.sharedChildWithName(record.localname)
shareeCollection._initSyncToken()
+
+ @inlineCallbacks
def removeRecordForLocalName(self, localname):
-
- record = self.recordForLocalName(localname)
- shareeCollection = self._home.sharedChildWithName(record.localname)
- shareeCollection._deletedSyncToken()
-
- self._txn.execSQL(
+ record = yield self.recordForLocalName(localname)
+ shareeCollection = yield self._home.sharedChildWithName(record.localname)
+ yield shareeCollection._deletedSyncToken()
+
+ returnValue((yield self._txn.execSQL(
"""
update %(name)s
set %(column_RESOURCE_NAME)s = NULL
@@ -557,17 +589,18 @@
and %(column_HOME_RESOURCE_ID)s = %%s
""" % self._bindTable,
[localname, self._home._resourceID]
- )
+ )))
+ @inlineCallbacks
def removeRecordForShareUID(self, shareUID):
- record = self.recordForShareUID(shareUID)
- shareeCollection = self._home.sharedChildWithName(record.localname)
- shareeCollection._deletedSyncToken()
-
+ record = yield self.recordForShareUID(shareUID)
+ shareeCollection = yield self._home.sharedChildWithName(record.localname)
+ yield shareeCollection._deletedSyncToken()
+
if not shareUID.startswith("Direct"):
- self._txn.execSQL(
+ yield self._txn.execSQL(
"""
update %(name)s
set %(column_RESOURCE_NAME)s = NULL
@@ -576,23 +609,24 @@
and %(name)s.%(column_HOME_RESOURCE_ID)s = INVITE.HOME_RESOURCE_ID
and %(name)s.%(column_RESOURCE_ID)s = INVITE.RESOURCE_ID
""" % self._bindTable,
- [shareUID,]
+ [shareUID, ]
)
else:
# Extract pieces from synthesised UID
homeID, resourceID = shareUID[len("Direct-"):].split("-")
-
+
# Now remove the binding for the direct share
- self._txn.execSQL(
+ yield self._txn.execSQL(
"""
delete from %(name)s
where %(column_HOME_RESOURCE_ID)s = %%s
and %(column_RESOURCE_ID)s = %%s
""" % self._bindTable,
- [homeID, resourceID,]
+ [homeID, resourceID, ]
)
+
class SQLLegacyCalendarShares(SQLLegacyShares):
"""
Emulator for the implicit interface specified by
@@ -603,12 +637,15 @@
self._homeTable = CALENDAR_HOME_TABLE
self._bindTable = CALENDAR_BIND_TABLE
self._urlTopSegment = "calendars"
-
+
super(SQLLegacyCalendarShares, self).__init__(home)
+
def _getHomeWithUID(self, uid):
return self._txn.calendarHomeWithUID(uid, create=True)
-
+
+
+
class SQLLegacyAddressBookShares(SQLLegacyShares):
"""
Emulator for the implicit interface specified by
@@ -624,8 +661,8 @@
def _getHomeWithUID(self, uid):
return self._txn.addressbookHomeWithUID(uid, create=True)
-
+
class MemcachedUIDReserver(CachePoolUserMixIn, LoggingMixIn):
def __init__(self, index, cachePool=None):
self.index = index
@@ -816,7 +853,38 @@
return "%%%s%%" % (arg,)
-class PostgresLegacyIndexEmulator(LoggingMixIn):
+class LegacyIndexHelper(LoggingMixIn, object):
+
+ @inlineCallbacks
+ def isAllowedUID(self, uid, *names):
+ """
+ Checks to see whether to allow an operation which would add the
+ specified UID to the index. Specifically, the operation may not
+ violate the constraint that UIDs must be unique.
+ @param uid: the UID to check
+ @param names: the names of resources being replaced or deleted by the
+ operation; UIDs associated with these resources are not checked.
+ @return: True if the UID is not in the index and is not reserved,
+ False otherwise.
+ """
+ rname = yield self.resourceNameForUID(uid)
+ returnValue(rname is None or rname in names)
+
+
+ def reserveUID(self, uid):
+ return self.reserver.reserveUID(uid)
+
+
+ def unreserveUID(self, uid):
+ return self.reserver.unreserveUID(uid)
+
+
+ def isReservedUID(self, uid):
+ return self.reserver.isReservedUID(uid)
+
+
+
+class PostgresLegacyIndexEmulator(LegacyIndexHelper):
"""
Emulator for L{twistedcaldv.index.Index} and
L{twistedcaldv.index.IndexSchedule}.
@@ -838,27 +906,7 @@
return self.calendar._txn
- def reserveUID(self, uid):
- if self.calendar._name == "inbox":
- return succeed(None)
- else:
- return self.reserver.reserveUID(uid)
-
-
- def unreserveUID(self, uid):
- if self.calendar._name == "inbox":
- return succeed(None)
- else:
- return self.reserver.unreserveUID(uid)
-
-
- def isReservedUID(self, uid):
- if self.calendar._name == "inbox":
- return succeed(False)
- else:
- return self.reserver.isReservedUID(uid)
-
-
+ @inlineCallbacks
def isAllowedUID(self, uid, *names):
"""
Checks to see whether to allow an operation which would add the
@@ -870,112 +918,140 @@
@return: True if the UID is not in the index and is not reserved,
False otherwise.
"""
- if self.calendar._name == "inbox":
- return True
- else:
- rname = self.resourceNameForUID(uid)
- return (rname is None or rname in names)
+ rname = yield self.resourceNameForUID(uid)
+ returnValue(rname is None or rname in names)
+
+ @inlineCallbacks
def resourceUIDForName(self, name):
- obj = self.calendar.calendarObjectWithName(name)
+ obj = yield self.calendar.calendarObjectWithName(name)
if obj is None:
- return None
- return obj.uid()
+ returnValue(None)
+ returnValue(obj.uid())
+ @inlineCallbacks
def resourceNameForUID(self, uid):
- obj = self.calendar.calendarObjectWithUID(uid)
+ obj = yield self.calendar.calendarObjectWithUID(uid)
if obj is None:
- return None
- return obj.name()
+ returnValue(None)
+ returnValue(obj.name())
+ @inlineCallbacks
def notExpandedBeyond(self, minDate):
"""
Gives all resources which have not been expanded beyond a given date
in the database. (Unused; see above L{postgresqlgenerator}.
"""
- return [row[0] for row in self._txn.execSQL(
+ returnValue([row[0] for row in (yield self._txn.execSQL(
"select RESOURCE_NAME from CALENDAR_OBJECT "
"where RECURRANCE_MAX < %s and CALENDAR_RESOURCE_ID = %s",
[normalizeForIndex(minDate), self.calendar._resourceID]
- )]
+ ))])
+ @inlineCallbacks
def reExpandResource(self, name, expand_until):
"""
Given a resource name, remove it from the database and re-add it
with a longer expansion.
"""
- obj = self.calendar.calendarObjectWithName(name)
- obj.updateDatabase(obj.component(), expand_until=expand_until, reCreate=True)
+ obj = yield self.calendar.calendarObjectWithName(name)
+ yield obj.updateDatabase(
+ (yield obj.component()), expand_until=expand_until, reCreate=True
+ )
+
+ @inlineCallbacks
def testAndUpdateIndex(self, minDate):
# Find out if the index is expanded far enough
- names = self.notExpandedBeyond(minDate)
+ names = yield self.notExpandedBeyond(minDate)
# Actually expand recurrence max
for name in names:
self.log_info("Search falls outside range of index for %s %s" % (name, minDate))
- self.reExpandResource(name, minDate)
+ yield self.reExpandResource(name, minDate)
+
+ @inlineCallbacks
def indexedSearch(self, filter, useruid='', fbtype=False):
"""
Finds resources matching the given qualifiers.
@param filter: the L{Filter} for the calendar-query to execute.
- @return: an iterable of tuples for each resource matching the
- given C{qualifiers}. The tuples are C{(name, uid, type)}, where
- C{name} is the resource name, C{uid} is the resource UID, and
- C{type} is the resource iCalendar component type.x
+
+ @return: a L{Deferred} which fires with an iterable of tuples for each
+ resource matching the given C{qualifiers}. The tuples are C{(name,
+ uid, type)}, where C{name} is the resource name, C{uid} is the
+ resource UID, and C{type} is the resource iCalendar component
+ type.
"""
-
# Make sure we have a proper Filter element and get the partial SQL
# statement to use.
if isinstance(filter, calendarqueryfilter.Filter):
- qualifiers = calendarquery.sqlcalendarquery(filter, self.calendar._resourceID, useruid, generator=postgresqlgenerator)
+ qualifiers = calendarquery.sqlcalendarquery(
+ filter, self.calendar._resourceID, useruid,
+ generator=postgresqlgenerator
+ )
if qualifiers is not None:
# Determine how far we need to extend the current expansion of
- # events. If we have an open-ended time-range we will expand one
- # year past the start. That should catch bounded recurrences - unbounded
- # will have been indexed with an "infinite" value always included.
+ # events. If we have an open-ended time-range we will expand
+ # one year past the start. That should catch bounded
+ # recurrences - unbounded will have been indexed with an
+ # "infinite" value always included.
maxDate, isStartDate = filter.getmaxtimerange()
if maxDate:
maxDate = maxDate.date()
if isStartDate:
maxDate += datetime.timedelta(days=365)
- self.testAndUpdateIndex(maxDate)
+ yield self.testAndUpdateIndex(maxDate)
else:
# We cannot handler this filter in an indexed search
raise IndexedSearchException()
-
else:
qualifiers = None
# Perform the search
if qualifiers is None:
- rowiter = self._txn.execSQL(
- "select RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = %s",
- [self.calendar._resourceID, ],
+ rowiter = yield self._txn.execSQL(
+ """
+ select RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE
+ from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = %s
+ """,
+ [self.calendar._resourceID],
)
else:
if fbtype:
# For a free-busy time-range query we return all instances
- rowiter = self._txn.execSQL(
- """select DISTINCT
- CALENDAR_OBJECT.RESOURCE_NAME, CALENDAR_OBJECT.ICALENDAR_UID, CALENDAR_OBJECT.ICALENDAR_TYPE, CALENDAR_OBJECT.ORGANIZER,
- TIME_RANGE.FLOATING, TIME_RANGE.START_DATE, TIME_RANGE.END_DATE, TIME_RANGE.FBTYPE, TIME_RANGE.TRANSPARENT, TRANSPARENCY.TRANSPARENT""" +
+ rowiter = yield self._txn.execSQL(
+ """
+ select DISTINCT
+ CALENDAR_OBJECT.RESOURCE_NAME,
+ CALENDAR_OBJECT.ICALENDAR_UID,
+ CALENDAR_OBJECT.ICALENDAR_TYPE,
+ CALENDAR_OBJECT.ORGANIZER,
+ TIME_RANGE.FLOATING, TIME_RANGE.START_DATE,
+ TIME_RANGE.END_DATE, TIME_RANGE.FBTYPE,
+ TIME_RANGE.TRANSPARENT, TRANSPARENCY.TRANSPARENT
+ """ +
qualifiers[0],
qualifiers[1]
)
else:
- rowiter = self._txn.execSQL(
- "select DISTINCT CALENDAR_OBJECT.RESOURCE_NAME, CALENDAR_OBJECT.ICALENDAR_UID, CALENDAR_OBJECT.ICALENDAR_TYPE" +
+ rowiter = yield self._txn.execSQL(
+ """
+ select
+ DISTINCT CALENDAR_OBJECT.RESOURCE_NAME,
+ CALENDAR_OBJECT.ICALENDAR_UID,
+ CALENDAR_OBJECT.ICALENDAR_TYPE
+ """ +
qualifiers[0],
qualifiers[1]
)
# Check result for missing resources
+ results = []
for row in rowiter:
if fbtype:
row = list(row)
@@ -983,7 +1059,8 @@
row[7] = indexfbtype_to_icalfbtype[row[7]]
row[8] = 'T' if row[9] else 'F'
del row[9]
- yield row
+ results.append(row)
+ returnValue(results)
def bruteForceSearch(self):
@@ -994,23 +1071,44 @@
)
+ @inlineCallbacks
def resourcesExist(self, names):
- return list(set(names).intersection(
- set(self.calendar.listCalendarObjects())))
+ returnValue(list(set(names).intersection(
+ set((yield self.calendar.listCalendarObjects())))))
+ @inlineCallbacks
def resourceExists(self, name):
- return bool(
- self._txn.execSQL(
+ returnValue((bool(
+ (yield self._txn.execSQL(
"select RESOURCE_NAME from CALENDAR_OBJECT where "
"RESOURCE_NAME = %s and CALENDAR_RESOURCE_ID = %s",
[name, self.calendar._resourceID]
- )
- )
+ ))
+ )))
+class PostgresLegacyInboxIndexEmulator(PostgresLegacyIndexEmulator):
+ """
+ UIDs need not be unique in the 'inbox' calendar, so override those
+ behaviors intended to ensure that.
+ """
+ def isAllowedUID(self):
+ return succeed(True)
+
+ def reserveUID(self, uid):
+ return succeed(None)
+
+ def unreserveUID(self, uid):
+ return succeed(None)
+
+ def isReservedUID(self, uid):
+ return succeed(False)
+
+
+
# CARDDAV
class postgresqladbkgenerator(sqlgenerator):
@@ -1077,7 +1175,7 @@
return "%%%s%%" % (arg,)
-class PostgresLegacyABIndexEmulator(object):
+class PostgresLegacyABIndexEmulator(LegacyIndexHelper):
"""
Emulator for L{twistedcaldv.index.Index} and
L{twistedcaldv.index.IndexSchedule}.
@@ -1100,45 +1198,20 @@
return self.addressbook._txn
- def reserveUID(self, uid):
- return self.reserver.reserveUID(uid)
-
-
- def unreserveUID(self, uid):
- return self.reserver.unreserveUID(uid)
-
-
- def isReservedUID(self, uid):
- return self.reserver.isReservedUID(uid)
-
-
- def isAllowedUID(self, uid, *names):
- """
- Checks to see whether to allow an operation which would add the
- specified UID to the index. Specifically, the operation may not
- violate the constraint that UIDs must be unique.
- @param uid: the UID to check
- @param names: the names of resources being replaced or deleted by the
- operation; UIDs associated with these resources are not checked.
- @return: True if the UID is not in the index and is not reserved,
- False otherwise.
- """
- rname = self.resourceNameForUID(uid)
- return (rname is None or rname in names)
-
-
+ @inlineCallbacks
def resourceUIDForName(self, name):
- obj = self.addressbook.addressbookObjectWithName(name)
+ obj = yield self.addressbook.addressbookObjectWithName(name)
if obj is None:
- return None
- return obj.uid()
+ returnValue(None)
+ returnValue(obj.uid())
+ @inlineCallbacks
def resourceNameForUID(self, uid):
- obj = self.addressbook.addressbookObjectWithUID(uid)
+ obj = yield self.addressbook.addressbookObjectWithUID(uid)
if obj is None:
- return None
- return obj.name()
+ returnValue(None)
+ returnValue(obj.name())
def searchValid(self, filter):
@@ -1149,6 +1222,8 @@
return qualifiers is not None
+
+ @inlineCallbacks
def search(self, filter):
"""
Finds resources matching the given qualifiers.
@@ -1165,20 +1240,20 @@
else:
qualifiers = None
if qualifiers is not None:
- rowiter = self._txn.execSQL(
+ rowiter = yield self._txn.execSQL(
"select DISTINCT ADDRESSBOOK_OBJECT.RESOURCE_NAME, ADDRESSBOOK_OBJECT.VCARD_UID" +
qualifiers[0],
qualifiers[1]
)
else:
- rowiter = self._txn.execSQL(
+ rowiter = yield self._txn.execSQL(
"select RESOURCE_NAME, VCARD_UID from ADDRESSBOOK_OBJECT where ADDRESSBOOK_RESOURCE_ID = %s",
[self.addressbook._resourceID, ],
)
- for row in rowiter:
- yield row
+ returnValue(list(rowiter))
+
def indexedSearch(self, filter, useruid='', fbtype=False):
"""
Always raise L{IndexedSearchException}, since these indexes are not
@@ -1195,16 +1270,18 @@
)
+ @inlineCallbacks
def resourcesExist(self, names):
- return list(set(names).intersection(
- set(self.addressbook.listAddressbookObjects())))
+ returnValue(list(set(names).intersection(
+ set((yield self.addressbook.listAddressbookObjects())))))
+ @inlineCallbacks
def resourceExists(self, name):
- return bool(
- self._txn.execSQL(
+ returnValue(bool(
+ (yield self._txn.execSQL(
"select RESOURCE_NAME from ADDRESSBOOK_OBJECT where "
"RESOURCE_NAME = %s and ADDRESSBOOK_RESOURCE_ID = %s",
[name, self.addressbook._resourceID]
- )
- )
+ ))
+ ))
Modified: CalendarServer/trunk/txdav/common/datastore/test/test_util.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/test/test_util.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/datastore/test/test_util.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -27,9 +27,9 @@
from txdav.common.datastore.util import UpgradeToDatabaseService
from txdav.common.datastore.file import CommonDataStore
from txdav.common.datastore.test.util import theStoreBuilder, \
- populateCalendarsFrom
-from txdav.caldav.datastore.test.common import StubNotifierFactory, CommonTests
-from twisted.internet.defer import inlineCallbacks, Deferred
+ populateCalendarsFrom, StubNotifierFactory
+from txdav.caldav.datastore.test.common import CommonTests
+from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
class HomeMigrationTests(TestCase):
@@ -63,7 +63,7 @@
)
self.upgrader.setServiceParent(self.topService)
requirements = CommonTests.requirements
- populateCalendarsFrom(requirements, fileStore)
+ yield populateCalendarsFrom(requirements, fileStore)
self.filesPath.child("calendars").child(
"__uids__").child("ho").child("me").child("home1").child(
".some-extra-data").setContent("some extra data")
@@ -82,7 +82,9 @@
self.addCleanup(txn.commit)
for uid in CommonTests.requirements:
if CommonTests.requirements[uid] is not None:
- self.assertNotIdentical(None, txn.calendarHomeWithUID(uid))
+ self.assertNotIdentical(
+ None, (yield txn.calendarHomeWithUID(uid))
+ )
# Un-migrated data should be preserved.
self.assertEquals(self.filesPath.child("calendars-migrated").child(
"__uids__").child("ho").child("me").child("home1").child(
@@ -98,17 +100,17 @@
homes.
"""
startTxn = self.sqlStore.newTransaction("populate empty sample")
- startTxn.calendarHomeWithUID("home1", create=True)
- startTxn.commit()
+ yield startTxn.calendarHomeWithUID("home1", create=True)
+ yield startTxn.commit()
self.topService.startService()
yield self.subStarted
vrfyTxn = self.sqlStore.newTransaction("verify sample still empty")
self.addCleanup(vrfyTxn.commit)
- home = vrfyTxn.calendarHomeWithUID("home1")
+ home = yield vrfyTxn.calendarHomeWithUID("home1")
# The default calendar is still there.
- self.assertNotIdentical(None, home.calendarWithName("calendar"))
+ self.assertNotIdentical(None, (yield home.calendarWithName("calendar")))
# The migrated calendar isn't.
- self.assertIdentical(None, home.calendarWithName("calendar_1"))
+ self.assertIdentical(None, (yield home.calendarWithName("calendar_1")))
@inlineCallbacks
@@ -117,32 +119,38 @@
L{UpgradeToDatabaseService.startService} upgrades calendar attachments
as well.
"""
+
txn = self.fileStore.newTransaction()
committed = []
def maybeCommit():
if not committed:
committed.append(True)
- txn.commit()
+ return txn.commit()
self.addCleanup(maybeCommit)
+
+ @inlineCallbacks
def getSampleObj():
- return txn.calendarHomeWithUID("home1").calendarWithName(
- "calendar_1").calendarObjectWithName("1.ics")
- inObject = getSampleObj()
+ home = (yield txn.calendarHomeWithUID("home1"))
+ calendar = (yield home.calendarWithName("calendar_1"))
+ object = (yield calendar.calendarObjectWithName("1.ics"))
+ returnValue(object)
+
+ inObject = yield getSampleObj()
someAttachmentName = "some-attachment"
someAttachmentType = MimeType.fromString("application/x-custom-type")
- transport = inObject.createAttachmentWithName(
+ transport = yield inObject.createAttachmentWithName(
someAttachmentName, someAttachmentType
)
someAttachmentData = "Here is some data for your attachment, enjoy."
transport.write(someAttachmentData)
transport.loseConnection()
- maybeCommit()
+ yield maybeCommit()
self.topService.startService()
yield self.subStarted
committed = []
txn = self.sqlStore.newTransaction()
- outObject = getSampleObj()
- outAttachment = outObject.attachmentWithName(someAttachmentName)
+ outObject = yield getSampleObj()
+ outAttachment = yield outObject.attachmentWithName(someAttachmentName)
allDone = Deferred()
class SimpleProto(Protocol):
data = ''
Modified: CalendarServer/trunk/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/test/util.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/datastore/test/util.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -20,12 +20,15 @@
"""
import gc
+from zope.interface.verify import verifyObject
+from zope.interface.exceptions import BrokenMethodImplementation,\
+ DoesNotImplement
from twext.python.filepath import CachingFilePath
from twext.python.vcomponent import VComponent
from twisted.internet import reactor
-from twisted.internet.defer import Deferred, succeed
+from twisted.internet.defer import Deferred, succeed, inlineCallbacks
from twisted.internet.task import deferLater
from twisted.python import log
@@ -33,6 +36,7 @@
from txdav.base.datastore.subpostgres import PostgresService,\
DiagnosticConnectionWrapper
from txdav.common.icommondatastore import NoSuchHomeChildError
+from twistedcaldav.notify import Notifier
def allInstancesOf(cls):
@@ -149,6 +153,7 @@
buildStore = theStoreBuilder.buildStore
+ at inlineCallbacks
def populateCalendarsFrom(requirements, store):
"""
Populate C{store} from C{requirements}.
@@ -162,23 +167,112 @@
for homeUID in requirements:
calendars = requirements[homeUID]
if calendars is not None:
- home = populateTxn.calendarHomeWithUID(homeUID, True)
+ home = yield populateTxn.calendarHomeWithUID(homeUID, True)
# We don't want the default calendar or inbox to appear unless it's
# explicitly listed.
try:
- home.removeCalendarWithName("calendar")
- home.removeCalendarWithName("inbox")
+ yield home.removeCalendarWithName("calendar")
+ yield home.removeCalendarWithName("inbox")
except NoSuchHomeChildError:
pass
for calendarName in calendars:
calendarObjNames = calendars[calendarName]
if calendarObjNames is not None:
- home.createCalendarWithName(calendarName)
- calendar = home.calendarWithName(calendarName)
+ # XXX should not be yielding! this SQL will be executed
+ # first!
+ yield home.createCalendarWithName(calendarName)
+ calendar = yield home.calendarWithName(calendarName)
for objectName in calendarObjNames:
objData = calendarObjNames[objectName]
calendar.createCalendarObjectWithName(
objectName, VComponent.fromString(objData)
)
- populateTxn.commit()
+ yield populateTxn.commit()
+
+def assertProvides(testCase, interface, provider):
+ """
+ Verify that C{provider} properly provides C{interface}
+
+ @type interface: L{zope.interface.Interface}
+ @type provider: C{provider}
+ """
+ try:
+ verifyObject(interface, provider)
+ except BrokenMethodImplementation, e:
+ testCase.fail(e)
+ except DoesNotImplement, e:
+ testCase.fail("%r does not provide %s.%s" %
+ (provider, interface.__module__, interface.getName()))
+
+
+
+class CommonCommonTests(object):
+ """
+ Common utility functionality for file/store combination tests.
+ """
+
+ lastTransaction = None
+ savedStore = None
+ assertProvides = assertProvides
+
+ def transactionUnderTest(self):
+ """
+ Create a transaction from C{storeUnderTest} and save it as
+ C[lastTransaction}. Also makes sure to use the same store, saving the
+ value from C{storeUnderTest}.
+ """
+ if self.lastTransaction is not None:
+ return self.lastTransaction
+ if self.savedStore is None:
+ self.savedStore = self.storeUnderTest()
+ self.counter += 1
+ txn = self.lastTransaction = self.savedStore.newTransaction(self.id() + " #" + str(self.counter))
+ return txn
+
+
+ def commit(self):
+ """
+ Commit the last transaction created from C{transactionUnderTest}, and
+ clear it.
+ """
+ result = self.lastTransaction.commit()
+ self.lastTransaction = None
+ return result
+
+
+ def abort(self):
+ """
+ Abort the last transaction created from C[transactionUnderTest}, and
+ clear it.
+ """
+ result = self.lastTransaction.abort()
+ self.lastTransaction = None
+ return result
+
+ def setUp(self):
+ self.counter = 0
+ self.notifierFactory = StubNotifierFactory()
+
+ def tearDown(self):
+ if self.lastTransaction is not None:
+ return self.commit()
+
+
+
+class StubNotifierFactory(object):
+ """
+ For testing push notifications without an XMPP server.
+ """
+
+ def __init__(self):
+ self.reset()
+
+ def newNotifier(self, label="default", id=None, prefix=None):
+ return Notifier(self, label=label, id=id, prefix=prefix)
+
+ def send(self, op, id):
+ self.history.append((op, id))
+
+ def reset(self):
+ self.history = []
Modified: CalendarServer/trunk/txdav/common/datastore/util.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/util.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/datastore/util.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -102,7 +102,8 @@
self.fileStore.eachCalendarHome,
lambda txn: txn.calendarHomeWithUID,
"calendars"),
- ("addressbook", migrateAddressbookHome, self.fileStore.eachAddressbookHome,
+ ("addressbook", migrateAddressbookHome,
+ self.fileStore.eachAddressbookHome,
lambda txn: txn.addressbookHomeWithUID,
"addressbooks")
]:
@@ -111,17 +112,19 @@
self.log_warn("Migrating %s UID %r" % (homeType, uid))
sqlTxn = self.sqlStore.newTransaction(migrating=True)
homeGetter = destFunc(sqlTxn)
- if homeGetter(uid, create=False) is not None:
+ if (yield homeGetter(uid, create=False)) is not None:
self.log_warn(
"%s home %r already existed not migrating" % (
homeType, uid))
- sqlTxn.abort()
- fileTxn.commit()
+ yield sqlTxn.abort()
+ yield fileTxn.commit()
continue
- sqlHome = homeGetter(uid, create=True)
+ sqlHome = yield homeGetter(uid, create=True)
+ if sqlHome is None:
+ raise RuntimeError("THIS SHOULD NOT BE POSSIBLE.")
yield migrateFunc(fileHome, sqlHome)
- fileTxn.commit()
- sqlTxn.commit()
+ yield fileTxn.commit()
+ yield sqlTxn.commit()
# FIXME: need a public remove...HomeWithUID() for de-
# provisioning
storePath = self.fileStore._path # Documents
Modified: CalendarServer/trunk/txdav/common/icommondatastore.py
===================================================================
--- CalendarServer/trunk/txdav/common/icommondatastore.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/icommondatastore.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -108,6 +108,20 @@
Uh, oh.
"""
+# Indexing / sync tokens
+
+class ReservationError(LookupError):
+ """
+ Attempt to reserve a UID which is already reserved or to unreserve a UID
+ which is not reserved.
+ """
+
+class IndexedSearchException(ValueError):
+ pass
+
+class SyncTokenValidException(ValueError):
+ pass
+
#
# Interfaces
#
Modified: CalendarServer/trunk/txdav/common/inotifications.py
===================================================================
--- CalendarServer/trunk/txdav/common/inotifications.py 2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/inotifications.py 2010-10-19 15:39:55 UTC (rev 6446)
@@ -105,8 +105,8 @@
@param name: a string.
@type name: C{str}
- @raise NoSuchObjectResourceError: if no such NoSuchObjectResourceError object
- exists.
+ @raise NoSuchObjectResourceError: if no such NoSuchObjectResourceError
+ object exists.
"""
def removeNotificationObjectWithUID(uid):
@@ -126,17 +126,6 @@
@return: a string containing a sync token.
"""
- def notificationObjectsSinceToken(token):
- """
- Retrieve all notification objects in this notification collection that have
- changed since the given C{token} was last valid.
-
- @param token: a sync token.
- @return: a 3-tuple containing an iterable of
- L{INotificationObject}s that have changed, an iterable of uids
- that have been removed, and the current sync token.
- """
-
def properties():
"""
Retrieve the property store for this notification.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20101019/48d5981f/attachment-0001.html>
More information about the calendarserver-changes
mailing list