[CalendarServer-changes] [7204] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Wed Mar 16 18:27:44 PDT 2011
Revision: 7204
http://trac.macosforge.org/projects/calendarserver/changeset/7204
Author: cdaboo at apple.com
Date: 2011-03-16 18:27:43 -0700 (Wed, 16 Mar 2011)
Log Message:
-----------
Support bulk POST operations.
Modified Paths:
--------------
CalendarServer/trunk/conf/caldavd-test.plist
CalendarServer/trunk/twext/web2/dav/element/base.py
CalendarServer/trunk/twistedcaldav/caldavxml.py
CalendarServer/trunk/twistedcaldav/carddavxml.py
CalendarServer/trunk/twistedcaldav/customxml.py
CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
CalendarServer/trunk/twistedcaldav/ical.py
CalendarServer/trunk/twistedcaldav/method/put_common.py
CalendarServer/trunk/twistedcaldav/sharing.py
CalendarServer/trunk/twistedcaldav/stdconfig.py
CalendarServer/trunk/twistedcaldav/storebridge.py
CalendarServer/trunk/twistedcaldav/vcard.py
Property Changed:
----------------
CalendarServer/trunk/
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
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/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/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/purge_old_events:6735-6746
/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/batchupload-6699:6700-7198
/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/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/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/purge_old_events:6735-6746
/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/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist 2011-03-17 01:22:16 UTC (rev 7203)
+++ CalendarServer/trunk/conf/caldavd-test.plist 2011-03-17 01:27:43 UTC (rev 7204)
@@ -806,6 +806,10 @@
<key>EnableTimezoneService</key>
<true/>
+ <!-- Batch Upload via POST -->
+ <key>EnableBatchUpload</key>
+ <true/>
+
<!-- Shared Calendars & Address Books -->
<key>Sharing</key>
<dict>
Modified: CalendarServer/trunk/twext/web2/dav/element/base.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/element/base.py 2011-03-17 01:22:16 UTC (rev 7203)
+++ CalendarServer/trunk/twext/web2/dav/element/base.py 2011-03-17 01:27:43 UTC (rev 7204)
@@ -483,6 +483,13 @@
PCDATAElement: (0, None),
}
+ @classmethod
+ def fromQname(cls, namespace, name):
+ child = cls()
+ child.namespace = namespace
+ child.name = name
+ return child
+
def qname(self):
return (self.namespace, self.name)
Modified: CalendarServer/trunk/twistedcaldav/caldavxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/caldavxml.py 2011-03-17 01:22:16 UTC (rev 7203)
+++ CalendarServer/trunk/twistedcaldav/caldavxml.py 2011-03-17 01:27:43 UTC (rev 7204)
@@ -340,6 +340,8 @@
else:
raise ValueError("Not a calendar: %s" % (calendar,))
+ fromTextData = fromCalendar
+
def __init__(self, *children, **attributes):
super(CalendarData, self).__init__(*children, **attributes)
@@ -433,6 +435,8 @@
else:
return None
+ generateComponent = calendar
+
def calendarData(self):
"""
Returns the calendar data derived from this element.
Modified: CalendarServer/trunk/twistedcaldav/carddavxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/carddavxml.py 2011-03-17 01:22:16 UTC (rev 7203)
+++ CalendarServer/trunk/twistedcaldav/carddavxml.py 2011-03-17 01:27:43 UTC (rev 7204)
@@ -200,6 +200,8 @@
"""
return clazz(davxml.PCDATAElement(addressdata))
+ fromTextData = fromAddressData
+
def __init__(self, *children, **attributes):
super(AddressData, self).__init__(*children, **attributes)
@@ -280,6 +282,8 @@
else:
return None
+ generateComponent = address
+
def addressData(self):
"""
Returns an address component derived from this element.
Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py 2011-03-17 01:22:16 UTC (rev 7203)
+++ CalendarServer/trunk/twistedcaldav/customxml.py 2011-03-17 01:27:43 UTC (rev 7204)
@@ -995,7 +995,83 @@
namespace = calendarserver_namespace
name = "link"
+mm_namespace = "http://me.com/_namespace/"
+class Multiput (davxml.WebDAVElement):
+ namespace = mm_namespace
+ name = "multiput"
+
+ allowed_children = {
+ (mm_namespace, "resource") : (1, None),
+ }
+
+class Resource (davxml.WebDAVElement):
+ namespace = mm_namespace
+ name = "resource"
+
+ allowed_children = {
+ (davxml, "href") : (0, 1),
+ (mm_namespace, "if-match") : (0, 1),
+ (davxml, "set") : (0, 1),
+ (davxml, "remove") : (0, 1),
+ (mm_namespace, "delete") : (0, 1),
+ }
+
+class IfMatch (davxml.WebDAVElement):
+ namespace = mm_namespace
+ name = "if-match"
+
+ allowed_children = {
+ (davxml, "getetag") : (1, 1),
+ }
+
+class Delete (davxml.WebDAVEmptyElement):
+ namespace = mm_namespace
+ name = "delete"
+
+
+class BulkRequests (davxml.WebDAVElement):
+ namespace = mm_namespace
+ name = "bulk-requests"
+ hidden = True
+ protected = True
+
+ allowed_children = {
+ (mm_namespace, "simple") : (0, 1),
+ (mm_namespace, "crud") : (0, 1),
+ }
+
+class Simple (davxml.WebDAVElement):
+ namespace = mm_namespace
+ name = "simple"
+ hidden = True
+ protected = True
+
+ allowed_children = {
+ (mm_namespace, "max-resources") : (1, 1),
+ (mm_namespace, "max-bytes") : (1, 1),
+ }
+
+class CRUD (davxml.WebDAVElement):
+ namespace = mm_namespace
+ name = "crud"
+ hidden = True
+ protected = True
+
+ allowed_children = {
+ (mm_namespace, "max-resources") : (1, 1),
+ (mm_namespace, "max-bytes") : (1, 1),
+ }
+
+class MaxBulkResources (davxml.WebDAVTextElement):
+ namespace = mm_namespace
+ name = "max-resources"
+
+class MaxBulkBytes (davxml.WebDAVTextElement):
+ namespace = mm_namespace
+ name = "max-bytes"
+
+
##
# Extensions to davxml.ResourceType
##
Modified: CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py 2011-03-17 01:22:16 UTC (rev 7203)
+++ CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py 2011-03-17 01:27:43 UTC (rev 7204)
@@ -44,7 +44,7 @@
from twext.web2.dav.element.base import twisted_dav_namespace, dav_namespace, parse_date, twisted_private_namespace
from twext.web2.dav.resource import DAVPropertyMixIn
from twext.web2.dav.util import joinURL
-from twext.web2.http_headers import MimeType, generateContentType
+from twext.web2.http_headers import MimeType, generateContentType, ETag
from twistedcaldav import customxml, carddavxml
@@ -1908,7 +1908,7 @@
#print("VCardResource.readProperty: qname = %s, result = %s" % (qname, result))
return result
elif name == "getetag":
- result = davxml.GETETag( hashlib.md5(self.vCardText()).hexdigest() )
+ result = davxml.GETETag( ETag(hashlib.md5(self.vCardText()).hexdigest()).generate() )
#print("VCardResource.readProperty: qname = %s, result = %s" % (qname, result))
return result
elif name == "getcontenttype":
Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py 2011-03-17 01:22:16 UTC (rev 7203)
+++ CalendarServer/trunk/twistedcaldav/ical.py 2011-03-17 01:27:43 UTC (rev 7204)
@@ -283,6 +283,37 @@
extraRestrictedProperties = ("SUMMARY", "LOCATION",)
@classmethod
+ def allFromString(clazz, string):
+ """
+ Construct a L{Component} from a string.
+ @param string: a string containing iCalendar data.
+ @return: a C{list} of L{Component}s representing the components described by
+ C{string}.
+ """
+ if type(string) is unicode:
+ string = string.encode("utf-8")
+ return clazz.allFromStream(StringIO.StringIO(string))
+
+ @classmethod
+ def allFromStream(clazz, stream):
+ """
+ Construct possibly multiple L{Component}s from a stream.
+ @param stream: a C{read()}able stream containing iCalendar data.
+ @return: a C{list} of L{Component}s representing the components described by
+ C{stream}.
+ """
+
+ results = []
+ try:
+ for vobject in readComponents(stream):
+ results.append(clazz(None, vobject=vobject))
+ return results
+ except vParseError, e:
+ raise InvalidICalendarDataError(e)
+ except StopIteration, e:
+ raise InvalidICalendarDataError(e)
+
+ @classmethod
def fromString(clazz, string):
"""
Construct a L{Component} from a string.
Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py 2011-03-17 01:22:16 UTC (rev 7203)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py 2011-03-17 01:27:43 UTC (rev 7204)
@@ -815,6 +815,9 @@
@inlineCallbacks
def doStore(self, implicit):
+ # Stash the current calendar data as we may need to return it
+ self.returndata = str(self.calendar)
+
# Always do the per-user data merge right before we store
yield self.mergePerUserData()
Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py 2011-03-17 01:22:16 UTC (rev 7203)
+++ CalendarServer/trunk/twistedcaldav/sharing.py 2011-03-17 01:27:43 UTC (rev 7204)
@@ -115,7 +115,7 @@
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(customxml.calendarserver_namespace, "valid-request"),
- "invalid share",
+ "Invalid share",
))
record = yield self.invitesDB().recordForInviteUID(inviteUID)
@@ -123,7 +123,7 @@
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(customxml.calendarserver_namespace, "valid-request"),
- "invalid invitation uid: %s" % (inviteUID,),
+ "Invalid invitation uid: %s" % (inviteUID,),
))
# Only certain states are sharer controlled
@@ -597,182 +597,177 @@
# Add to collections
yield notifications.deleteNotifictionMessageByUID(request, record.inviteuid)
- def xmlPOSTNoAuth(self, encoding, request):
- def _handleErrorResponse(error):
- if isinstance(error.value, HTTPError) and hasattr(error.value, "response"):
- return error.value.response
- return error
-
- def _handleInvite(invitedoc):
- def _handleInviteSet(inviteset):
- userid = None
- cn = None
- access = None
- summary = None
- for item in inviteset.children:
- if isinstance(item, davxml.HRef):
- userid = str(item)
- continue
- if isinstance(item, customxml.CommonName):
- cn = str(item)
- continue
- if isinstance(item, customxml.InviteSummary):
- summary = str(item)
- continue
- if isinstance(item, customxml.ReadAccess) or isinstance(item, customxml.ReadWriteAccess):
- access = item
- continue
- if userid and access and summary:
- return (userid, cn, access, summary)
- else:
- error_text = []
- if userid is None:
- error_text.append("missing href")
- if access is None:
- error_text.append("missing access")
- if summary is None:
- error_text.append("missing summary")
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- (customxml.calendarserver_namespace, "valid-request"),
- "%s: %s" % (", ".join(error_text), inviteset,),
- ))
-
- def _handleInviteRemove(inviteremove):
- userid = None
- access = []
- for item in inviteremove.children:
- if isinstance(item, davxml.HRef):
- userid = str(item)
- continue
- if isinstance(item, customxml.ReadAccess) or isinstance(item, customxml.ReadWriteAccess):
- access.append(item)
- continue
+ @inlineCallbacks
+ def _xmlHandleInvite(self, request, docroot):
+ yield self.authorize(request, (davxml.Read(), davxml.Write()))
+ result = (yield self._handleInvite(request, docroot))
+ returnValue(result)
+
+ def _handleInvite(self, request, invitedoc):
+ def _handleInviteSet(inviteset):
+ userid = None
+ cn = None
+ access = None
+ summary = None
+ for item in inviteset.children:
+ if isinstance(item, davxml.HRef):
+ userid = str(item)
+ continue
+ if isinstance(item, customxml.CommonName):
+ cn = str(item)
+ continue
+ if isinstance(item, customxml.InviteSummary):
+ summary = str(item)
+ continue
+ if isinstance(item, customxml.ReadAccess) or isinstance(item, customxml.ReadWriteAccess):
+ access = item
+ continue
+ if userid and access and summary:
+ return (userid, cn, access, summary)
+ else:
+ error_text = []
if userid is None:
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- (customxml.calendarserver_namespace, "valid-request"),
- "missing href: %s" % (inviteremove,),
- ))
- if len(access) == 0:
- access = None
- else:
- access = set(access)
- return (userid, access)
-
- def _autoShare(isShared, request):
- if not isShared:
- self.upgradeToShare()
-
- @inlineCallbacks
- def _processInviteDoc(_, request):
- setDict, removeDict, updateinviteDict = {}, {}, {}
- okusers = set()
- badusers = set()
- for item in invitedoc.children:
- if isinstance(item, customxml.InviteSet):
- userid, cn, access, summary = _handleInviteSet(item)
- setDict[userid] = (cn, access, summary)
-
- # Validate each userid on add only
- (okusers if self.validUserIDForShare(userid) else badusers).add(userid)
- elif isinstance(item, customxml.InviteRemove):
- userid, access = _handleInviteRemove(item)
- removeDict[userid] = access
-
- # Treat removed userids as valid as we will fail invalid ones silently
- okusers.add(userid)
-
- # Only make changes if all OK
- if len(badusers) == 0:
- # Special case removing and adding the same user and treat that as an add
- sameUseridInRemoveAndSet = [u for u in removeDict.keys() if u in setDict]
- for u in sameUseridInRemoveAndSet:
- removeACL = removeDict[u]
- cn, newACL, summary = setDict[u]
- updateinviteDict[u] = (cn, removeACL, newACL, summary)
- del removeDict[u]
- del setDict[u]
- for userid, access in removeDict.iteritems():
- result = (yield self.uninviteUserToShare(userid, access, request))
- (okusers if result else badusers).add(userid)
- for userid, (cn, access, summary) in setDict.iteritems():
- result = (yield self.inviteUserToShare(userid, cn, access, summary, request))
- (okusers if result else badusers).add(userid)
- for userid, (cn, removeACL, newACL, summary) in updateinviteDict.iteritems():
- result = (yield self.inviteUserUpdateToShare(userid, cn, removeACL, newACL, summary, request))
- (okusers if result else badusers).add(userid)
-
- # Do a final validation of the entire set of invites
- yield self.validateInvites()
-
- # Create the multistatus response - only needed if some are bad
- if badusers:
- xml_responses = []
- xml_responses.extend([
- davxml.StatusResponse(davxml.HRef(userid), davxml.Status.fromResponseCode(responsecode.FAILED_DEPENDENCY))
- for userid in sorted(okusers)
- ])
- xml_responses.extend([
- davxml.StatusResponse(davxml.HRef(userid), davxml.Status.fromResponseCode(responsecode.FORBIDDEN))
- for userid in sorted(badusers)
- ])
-
- #
- # Return response
- #
- returnValue(MultiStatusResponse(xml_responses))
- else:
- returnValue(responsecode.OK)
-
-
- return self.isShared(request).addCallback(_autoShare, request).addCallback(_processInviteDoc, request)
-
- def _getData(data):
- try:
- doc = davxml.WebDAVDocument.fromString(data)
- except ValueError, e:
- self.log_error("Error parsing doc (%s) Doc:\n %s" % (str(e), data,))
+ error_text.append("missing href")
+ if access is None:
+ error_text.append("missing access")
+ if summary is None:
+ error_text.append("missing summary")
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(customxml.calendarserver_namespace, "valid-request"),
- "Invalid XML",
+ "%s: %s" % (", ".join(error_text), inviteset,),
))
- root = doc.root_element
- xmlDocHanders = {
- customxml.InviteShare: _handleInvite,
- }
- if type(root) in xmlDocHanders:
- return xmlDocHanders[type(root)](root).addErrback(_handleErrorResponse)
- else:
- self.log_error("Unsupported XML (%s)" % (root,))
+ def _handleInviteRemove(inviteremove):
+ userid = None
+ access = []
+ for item in inviteremove.children:
+ if isinstance(item, davxml.HRef):
+ userid = str(item)
+ continue
+ if isinstance(item, customxml.ReadAccess) or isinstance(item, customxml.ReadWriteAccess):
+ access.append(item)
+ continue
+ if userid is None:
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(customxml.calendarserver_namespace, "valid-request"),
- "Unsupported XML",
+ "Missing href: %s" % (inviteremove,),
))
+ if len(access) == 0:
+ access = None
+ else:
+ access = set(access)
+ return (userid, access)
- return allDataFromStream(request.stream).addCallback(_getData)
+ def _autoShare(isShared, request):
+ if not isShared:
+ self.upgradeToShare()
- def xmlPOSTPreconditions(self, _, request):
- if request.headers.hasHeader("Content-Type"):
- mimetype = request.headers.getHeader("Content-Type")
- if mimetype.mediaType in ("application", "text",) and mimetype.mediaSubtype == "xml":
- encoding = mimetype.params["charset"] if "charset" in mimetype.params else "utf8"
- return succeed(encoding)
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- (customxml.calendarserver_namespace, "valid-request"),
- "Invalid request content-type",
- ))
+ @inlineCallbacks
+ def _processInviteDoc(_, request):
+ setDict, removeDict, updateinviteDict = {}, {}, {}
+ okusers = set()
+ badusers = set()
+ for item in invitedoc.children:
+ if isinstance(item, customxml.InviteSet):
+ userid, cn, access, summary = _handleInviteSet(item)
+ setDict[userid] = (cn, access, summary)
+
+ # Validate each userid on add only
+ (okusers if self.validUserIDForShare(userid) else badusers).add(userid)
+ elif isinstance(item, customxml.InviteRemove):
+ userid, access = _handleInviteRemove(item)
+ removeDict[userid] = access
+
+ # Treat removed userids as valid as we will fail invalid ones silently
+ okusers.add(userid)
- def xmlPOSTAuth(self, request):
- d = self.authorize(request, (davxml.Read(), davxml.Write()))
- d.addCallback(self.xmlPOSTPreconditions, request)
- d.addCallback(self.xmlPOSTNoAuth, request)
- return d
+ # Only make changes if all OK
+ if len(badusers) == 0:
+ # Special case removing and adding the same user and treat that as an add
+ sameUseridInRemoveAndSet = [u for u in removeDict.keys() if u in setDict]
+ for u in sameUseridInRemoveAndSet:
+ removeACL = removeDict[u]
+ cn, newACL, summary = setDict[u]
+ updateinviteDict[u] = (cn, removeACL, newACL, summary)
+ del removeDict[u]
+ del setDict[u]
+ for userid, access in removeDict.iteritems():
+ result = (yield self.uninviteUserToShare(userid, access, request))
+ (okusers if result else badusers).add(userid)
+ for userid, (cn, access, summary) in setDict.iteritems():
+ result = (yield self.inviteUserToShare(userid, cn, access, summary, request))
+ (okusers if result else badusers).add(userid)
+ for userid, (cn, removeACL, newACL, summary) in updateinviteDict.iteritems():
+ result = (yield self.inviteUserUpdateToShare(userid, cn, removeACL, newACL, summary, request))
+ (okusers if result else badusers).add(userid)
+
+ # Do a final validation of the entire set of invites
+ yield self.validateInvites()
+
+ # Create the multistatus response - only needed if some are bad
+ if badusers:
+ xml_responses = []
+ xml_responses.extend([
+ davxml.StatusResponse(davxml.HRef(userid), davxml.Status.fromResponseCode(responsecode.FAILED_DEPENDENCY))
+ for userid in sorted(okusers)
+ ])
+ xml_responses.extend([
+ davxml.StatusResponse(davxml.HRef(userid), davxml.Status.fromResponseCode(responsecode.FORBIDDEN))
+ for userid in sorted(badusers)
+ ])
+
+ #
+ # Return response
+ #
+ returnValue(MultiStatusResponse(xml_responses))
+ else:
+ returnValue(responsecode.OK)
+
+ return self.isShared(request).addCallback(_autoShare, request).addCallback(_processInviteDoc, request)
+
+ @inlineCallbacks
+ def _xmlHandleInviteReply(self, request, docroot):
+ yield self.authorize(request, (davxml.Read(), davxml.Write()))
+ result = (yield self._handleInviteReply(request, docroot))
+ returnValue(result)
+ def _handleInviteReply(self, request, docroot):
+ raise NotImplementedError
+
+ @inlineCallbacks
+ def xmlRequestHandler(self, request):
+
+ # Need to read the data and get the root element first
+ xmldata = (yield allDataFromStream(request.stream))
+ try:
+ doc = davxml.WebDAVDocument.fromString(xmldata)
+ except ValueError, e:
+ self.log_error("Error parsing doc (%s) Doc:\n %s" % (str(e), xmldata,))
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (customxml.calendarserver_namespace, "valid-request"),
+ "Invalid XML",
+ ))
+
+ root = doc.root_element
+ if type(root) in self.xmlDocHanders:
+ result = (yield self.xmlDocHanders[type(root)](self, request, root))
+ returnValue(result)
+ else:
+ self.log_error("Unsupported XML (%s)" % (root,))
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (customxml.calendarserver_namespace, "valid-request"),
+ "Unsupported XML",
+ ))
+
+ xmlDocHanders = {
+ customxml.InviteShare: _xmlHandleInvite,
+ customxml.InviteReply: _xmlHandleInviteReply,
+ }
+
def POST_handler_content_type(self, request, contentType):
if self.isCollection():
if contentType:
@@ -785,8 +780,8 @@
return succeed(responsecode.FORBIDDEN)
_postHandlers = {
- ("application", "xml") : xmlPOSTAuth,
- ("text", "xml") : xmlPOSTAuth,
+ ("application", "xml") : xmlRequestHandler,
+ ("text", "xml") : xmlRequestHandler,
}
inviteAccessMapToXML = {
@@ -1146,67 +1141,37 @@
# Add to collections
yield notifications.addNotification(request, notificationUID, xmltype, xmldata)
- def xmlPOSTNoAuth(self, encoding, request):
+ def _handleInviteReply(self, request, invitereplydoc):
+ """ Handle a user accepting or declining a sharing invite """
+ hostUrl = None
+ accepted = None
+ summary = None
+ replytoUID = None
+ for item in invitereplydoc.children:
+ if isinstance(item, customxml.InviteStatusAccepted):
+ accepted = True
+ elif isinstance(item, customxml.InviteStatusDeclined):
+ accepted = False
+ elif isinstance(item, customxml.InviteSummary):
+ summary = str(item)
+ elif isinstance(item, customxml.HostURL):
+ for hosturlItem in item.children:
+ if isinstance(hosturlItem, davxml.HRef):
+ hostUrl = str(hosturlItem)
+ elif isinstance(item, customxml.InReplyTo):
+ replytoUID = str(item)
+
+ if accepted is None or hostUrl is None or replytoUID is None:
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (customxml.calendarserver_namespace, "valid-request"),
+ "Missing required XML elements",
+ ))
+ if accepted:
+ return self.acceptInviteShare(request, hostUrl, replytoUID, displayname=summary)
+ else:
+ return self.declineShare(request, hostUrl, replytoUID)
- def _handleErrorResponse(error):
- if isinstance(error.value, HTTPError) and hasattr(error.value, "response"):
- return error.value.response
- return error
-
- def _handleInviteReply(invitereplydoc):
- """ Handle a user accepting or declining a sharing invite """
- hostUrl = None
- accepted = None
- summary = None
- replytoUID = None
- for item in invitereplydoc.children:
- if isinstance(item, customxml.InviteStatusAccepted):
- accepted = True
- elif isinstance(item, customxml.InviteStatusDeclined):
- accepted = False
- elif isinstance(item, customxml.InviteSummary):
- summary = str(item)
- elif isinstance(item, customxml.HostURL):
- for hosturlItem in item.children:
- if isinstance(hosturlItem, davxml.HRef):
- hostUrl = str(hosturlItem)
- elif isinstance(item, customxml.InReplyTo):
- replytoUID = str(item)
-
- if accepted is None or hostUrl is None or replytoUID is None:
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- (customxml.calendarserver_namespace, "valid-request"),
- "Missing required XML elements",
- ))
- if accepted:
- return self.acceptInviteShare(request, hostUrl, replytoUID, displayname=summary)
- else:
- return self.declineShare(request, hostUrl, replytoUID)
-
- def _getData(data):
- try:
- doc = davxml.WebDAVDocument.fromString(data)
- except ValueError, e:
- print "Error parsing doc (%s) Doc:\n %s" % (str(e), data,)
- raise
-
- root = doc.root_element
- xmlDocHanders = {
- customxml.InviteReply: _handleInviteReply,
- }
- if type(root) in xmlDocHanders:
- return xmlDocHanders[type(root)](root).addErrback(_handleErrorResponse)
- else:
- self.log_error("Unsupported XML (%s)" % (root,))
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- (customxml.calendarserver_namespace, "valid-request"),
- "Unsupported XML",
- ))
-
- return allDataFromStream(request.stream).addCallback(_getData)
-
class SharedCollectionRecord(object):
def __init__(self, shareuid, sharetype, hosturl, localname, summary):
Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py 2011-03-17 01:22:16 UTC (rev 7203)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2011-03-17 01:27:43 UTC (rev 7204)
@@ -436,6 +436,10 @@
"EnableDropBox" : False, # Calendar Drop Box
"EnablePrivateEvents" : False, # Private Events
"EnableTimezoneService" : False, # Timezone service
+
+ "EnableBatchUpload" : False, # POST batch uploads
+ "MaxResourcesBatchUpload" : 100, # Maximum number of resources in a batch POST
+ "MaxBytesBatchUpload" : 10485760, # Maximum size of a batch POST (10 MB)
"Sharing": {
"Enabled" : False, # Overall on/off switch
Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py 2011-03-17 01:22:16 UTC (rev 7203)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py 2011-03-17 01:27:43 UTC (rev 7204)
@@ -15,59 +15,51 @@
# limitations under the License.
##
-"""
-Wrappers to translate between the APIs in L{txdav.caldav.icalendarstore} and
-L{txdav.carddav.iaddressbookstore} and those in L{twistedcaldav}.
-"""
-
-from urlparse import urlsplit
-
-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
-
from twext.python.log import Logger
-
from twext.web2 import responsecode
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.element.base import dav_namespace, WebDAVUnknownElement
+from twext.web2.dav.http import ErrorResponse, ResponseQueue, MultiStatusResponse
from twext.web2.dav.noneprops import NonePropertyStore
from twext.web2.dav.resource import TwistedACLInheritable, AccessDeniedError
-from twext.web2.dav.util import parentForURL, allDataFromStream, joinURL, \
- davXMLFromStream
+from twext.web2.dav.util import parentForURL, allDataFromStream, joinURL, davXMLFromStream
from twext.web2.http import HTTPError, StatusResponse, Response
from twext.web2.http_headers import ETag, MimeType
-from twext.web2.responsecode import (
- FORBIDDEN, NO_CONTENT, NOT_FOUND, CREATED, CONFLICT, PRECONDITION_FAILED,
- BAD_REQUEST, OK,
-)
+from twext.web2.responsecode import FORBIDDEN, NO_CONTENT, NOT_FOUND, CREATED, CONFLICT, PRECONDITION_FAILED, BAD_REQUEST, OK
from twext.web2.stream import ProducerStream, readStream, MemoryStream
-
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue, maybeDeferred
+from twisted.internet.protocol import Protocol
+from twisted.python.hashlib import md5
+from twisted.python.log import err as logDefaultException
+from twisted.python.util import FancyEqMixin
+from twistedcaldav import customxml, carddavxml, caldavxml
from twistedcaldav.cache import CacheStoreNotifier, ResponseCacheMixin,\
DisabledCacheNotifier
from twistedcaldav.caldavxml import caldav_namespace
from twistedcaldav.config import config
-from twistedcaldav import customxml
+from twistedcaldav.ical import Component as VCalendar, Property as VProperty,\
+ InvalidICalendarDataError
from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
-from twistedcaldav.notifications import NotificationCollectionResource, \
- NotificationResource
+from twistedcaldav.method.put_addressbook_common import StoreAddressObjectResource
+from twistedcaldav.method.put_common import StoreCalendarObjectResource
+from twistedcaldav.notifications import NotificationCollectionResource, NotificationResource
from twistedcaldav.resource import CalDAVResource, GlobalAddressBookResource
from twistedcaldav.schedule import ScheduleInboxResource
from twistedcaldav.scheduling.implicit import ImplicitScheduler
-from twistedcaldav.ical import Component as VCalendar
-from twistedcaldav.ical import Property as VProperty
-from twistedcaldav.vcard import Component as VCard
-
+from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError
from txdav.base.propertystore.base import PropertyName
from txdav.common.icommondatastore import NoSuchObjectResourceError
+from urlparse import urlsplit
+import time
from txdav.idav import PropertyChangeNotAllowedError
+"""
+Wrappers to translate between the APIs in L{txdav.caldav.icalendarstore} and
+L{txdav.carddav.iaddressbookstore} and those in L{twistedcaldav}.
+"""
+
log = Logger()
-
class _NewStorePropertiesWrapper(object):
"""
Wrap a new-style property store (a L{txdav.idav.IPropertyStore}) in the old-
@@ -230,6 +222,9 @@
if config.MaxResourcesPerCollection:
props += (customxml.MaxResources.qname(),)
+ if config.EnableBatchUpload:
+ props += (customxml.BulkRequests.qname(),)
+
return props
@inlineCallbacks
@@ -496,6 +491,377 @@
yield self._newStoreObject.rename(basename)
returnValue(NO_CONTENT)
+ @inlineCallbacks
+ def _readGlobalProperty(self, qname, property, request):
+
+ if config.EnableBatchUpload and qname == customxml.BulkRequests.qname():
+ returnValue(customxml.BulkRequests(
+ customxml.Simple(
+ customxml.MaxBulkResources.fromString(str(config.MaxResourcesBatchUpload)),
+ customxml.MaxBulkBytes.fromString(str(config.MaxBytesBatchUpload)),
+ ),
+ customxml.CRUD(
+ customxml.MaxBulkResources.fromString(str(config.MaxResourcesBatchUpload)),
+ customxml.MaxBulkBytes.fromString(str(config.MaxBytesBatchUpload)),
+ ),
+ ))
+ else:
+ result = (yield super(_CommonHomeChildCollectionMixin, self)._readGlobalProperty(qname, property, request))
+ returnValue(result)
+
+ @inlineCallbacks
+ def checkCTagPrecondition(self, request):
+ if request.headers.hasHeader("If"):
+ iffy = request.headers.getRawHeaders("If")[0]
+ prefix = "<%sctag/" % (customxml.mm_namespace,)
+ if prefix in iffy:
+ testctag = iffy[iffy.find(prefix):]
+ testctag = testctag[len(prefix):]
+ testctag = testctag.split(">", 1)[0]
+ ctag = (yield self.getSyncToken())
+ if testctag != ctag:
+ raise HTTPError(StatusResponse(responsecode.PRECONDITION_FAILED, "CTag pre-condition failure"))
+
+ def checkReturnChanged(self, request):
+ if request.headers.hasHeader("X-MobileMe-DAV-Options"):
+ return_changed = request.headers.getRawHeaders("X-MobileMe-DAV-Options")[0]
+ return ("return-changed-data" in return_changed)
+ else:
+ return False
+
+ @requiresPermissions(davxml.Bind())
+ @inlineCallbacks
+ def simpleBatchPOST(self, request):
+
+ # If CTag precondition
+ yield self.checkCTagPrecondition(request)
+
+ # Look for return changed data option
+ return_changed = self.checkReturnChanged(request)
+
+ # Read in all data
+ data = (yield allDataFromStream(request.stream))
+
+ components = self.componentsFromData(data)
+ if components is None:
+ raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Could not parse valid data from request body"))
+
+ # Build response
+ xmlresponses = []
+ for component in components:
+
+ code = None
+ error = None
+ dataChanged = None
+ try:
+ componentdata = str(component)
+
+ # Create a new name if one was not provided
+ name = md5(str(componentdata) + str(time.time()) + request.path).hexdigest() + self.resourceSuffix()
+
+ # Get a resource for the new item
+ newchildURL = joinURL(request.path, name)
+ newchild = (yield request.locateResource(newchildURL))
+ dataChanged = (yield self.storeResourceData(request, newchild, newchildURL, componentdata))
+
+ except HTTPError, e:
+ # Extract the pre-condition
+ code = e.response.code
+ if isinstance(e.response, ErrorResponse):
+ error = e.response.error
+ error = (error.namespace, error.name,)
+ except Exception:
+ code = responsecode.BAD_REQUEST
+
+ if code is None:
+
+ if not return_changed or dataChanged is None:
+ xmlresponses.append(
+ davxml.PropertyStatusResponse(
+ davxml.HRef.fromString(newchildURL),
+ davxml.PropertyStatus(
+ davxml.PropertyContainer(
+ davxml.GETETag.fromString(newchild.etag().generate()),
+ customxml.UID.fromString(component.resourceUID()),
+ ),
+ davxml.Status.fromResponseCode(responsecode.OK),
+ )
+ )
+ )
+ else:
+ xmlresponses.append(
+ davxml.PropertyStatusResponse(
+ davxml.HRef.fromString(newchildURL),
+ davxml.PropertyStatus(
+ davxml.PropertyContainer(
+ davxml.GETETag.fromString(newchild.etag().generate()),
+ self.xmlDataElementType().fromTextData(dataChanged),
+ ),
+ davxml.Status.fromResponseCode(responsecode.OK),
+ )
+ )
+ )
+
+ else:
+ xmlresponses.append(
+ davxml.StatusResponse(
+ davxml.HRef.fromString(""),
+ davxml.Status.fromResponseCode(code),
+ davxml.Error(
+ WebDAVUnknownElement.fromQname(*error),
+ customxml.UID.fromString(component.resourceUID()),
+ ) if error else None,
+ )
+ )
+
+ result = MultiStatusResponse(xmlresponses)
+
+ newctag = (yield self.getSyncToken())
+ result.headers.setRawHeaders("CTag", (newctag,))
+
+ # Setup some useful logging
+ request.submethod = "Simple batch"
+ if not hasattr(request, "extendedLogItems"):
+ request.extendedLogItems = {}
+ request.extendedLogItems["rcount"] = len(xmlresponses)
+
+ returnValue(result)
+
+ @inlineCallbacks
+ def crudBatchPOST(self, request, xmlroot):
+
+ # Need to force some kind of overall authentication on the request
+ yield self.authorize(request, (davxml.Read(), davxml.Write(),))
+
+ # If CTag precondition
+ yield self.checkCTagPrecondition(request)
+
+ # Look for return changed data option
+ return_changed = self.checkReturnChanged(request)
+
+ # Build response
+ xmlresponses = []
+ checkedBindPrivelege = None
+ checkedUnbindPrivelege = None
+ for xmlchild in xmlroot.children:
+
+ # Determine the multiput operation: create, update, delete
+ href = xmlchild.childOfType(davxml.HRef.qname())
+ set = xmlchild.childOfType(davxml.Set.qname())
+ prop = set.childOfType(davxml.PropertyContainer.qname()) if set is not None else None
+ xmldata_root = prop if prop else set
+ xmldata = xmldata_root.childOfType(self.xmlDataElementType().qname()) if xmldata_root is not None else None
+ if href is None:
+
+ if xmldata is None:
+ raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Could not parse valid data from request body without a DAV:Href present"))
+
+ # Do privilege check on collection once
+ if checkedBindPrivelege is None:
+ try:
+ yield self.authorize(request, (davxml.Bind(),))
+ checkedBindPrivelege = True
+ except HTTPError, e:
+ checkedBindPrivelege = e
+
+ # Create operations
+ yield self.crudCreate(request, xmldata.generateComponent(), xmlresponses, return_changed, checkedBindPrivelege)
+ else:
+ delete = xmlchild.childOfType(customxml.Delete.qname())
+ ifmatch = xmlchild.childOfType(customxml.IfMatch.qname())
+ if ifmatch:
+ ifmatch = str(ifmatch.children[0]) if len(ifmatch.children) == 1 else None
+ if delete is None:
+ if set is None:
+ raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Could not parse valid data from request body - no set of delete operation"))
+ if xmldata is None:
+ raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Could not parse valid data from request body for set operation"))
+ yield self.crudUpdate(request, str(href), xmldata.generateComponent(), ifmatch, return_changed, xmlresponses)
+ else:
+ # Do privilege check on collection once
+ if checkedUnbindPrivelege is None:
+ try:
+ yield self.authorize(request, (davxml.Unbind(),))
+ checkedUnbindPrivelege = True
+ except HTTPError, e:
+ checkedUnbindPrivelege = e
+
+ yield self.crudDelete(request, str(href), ifmatch, xmlresponses, checkedUnbindPrivelege);
+
+ result = MultiStatusResponse(xmlresponses)
+
+ newctag = (yield self.getSyncToken())
+ result.headers.setRawHeaders("CTag", (newctag,))
+
+ # Setup some useful logging
+ request.submethod = "CRUD batch"
+ if not hasattr(request, "extendedLogItems"):
+ request.extendedLogItems = {}
+ request.extendedLogItems["rcount"] = len(xmlresponses)
+
+ returnValue(result)
+
+ @inlineCallbacks
+ def crudCreate(self, request, component, xmlresponses, return_changed, hasPrivilege):
+
+ code = None
+ error = None
+ try:
+ componentdata = str(component)
+ if isinstance(hasPrivilege, HTTPError):
+ raise hasPrivilege
+
+ # Create a new name if one was not provided
+ name = md5(str(componentdata) + str(time.time()) + request.path).hexdigest() + self.resourceSuffix()
+
+ # Get a resource for the new item
+ newchildURL = joinURL(request.path, name)
+ newchild = (yield request.locateResource(newchildURL))
+ yield self.storeResourceData(request, newchild, newchildURL, componentdata)
+
+ # FIXME: figure out return_changed behavior
+
+ except HTTPError, e:
+ # Extract the pre-condition
+ code = e.response.code
+ if isinstance(e.response, ErrorResponse):
+ error = e.response.error
+ error = (error.namespace, error.name,)
+
+ except Exception:
+ code = responsecode.BAD_REQUEST
+
+ if code is None:
+ xmlresponses.append(
+ davxml.PropertyStatusResponse(
+ davxml.HRef.fromString(newchildURL),
+ davxml.PropertyStatus(
+ davxml.PropertyContainer(
+ davxml.GETETag.fromString(newchild.etag().generate()),
+ customxml.UID.fromString(component.resourceUID()),
+ ),
+ davxml.Status.fromResponseCode(responsecode.OK),
+ )
+ )
+ )
+ else:
+ xmlresponses.append(
+ davxml.StatusResponse(
+ davxml.HRef.fromString(""),
+ davxml.Status.fromResponseCode(code),
+ davxml.Error(
+ WebDAVUnknownElement.fromQname(*error),
+ customxml.UID.fromString(component.resourceUID()),
+ ) if error else None,
+ )
+ )
+
+ @inlineCallbacks
+ def crudUpdate(self, request, href, component, ifmatch, return_changed, xmlresponses):
+ code = None
+ error = None
+ try:
+ componentdata = str(component)
+
+ updateResource = (yield request.locateResource(href))
+ if not updateResource.exists():
+ raise HTTPError(responsecode.NOT_FOUND)
+
+ # Check privilege
+ yield updateResource.authorize(request, (davxml.Write(),))
+
+ # Check if match
+ if ifmatch and ifmatch != updateResource.etag().generate():
+ raise HTTPError(responsecode.PRECONDITION_FAILED)
+
+ yield self.storeResourceData(request, updateResource, href, componentdata)
+
+ # FIXME: figure out return_changed behavior
+
+ except HTTPError, e:
+ # Extract the pre-condition
+ code = e.response.code
+ if isinstance(e.response, ErrorResponse):
+ error = e.response.error
+ error = (error.namespace, error.name,)
+
+ except Exception:
+ code = responsecode.BAD_REQUEST
+
+ if code is None:
+ xmlresponses.append(
+ davxml.PropertyStatusResponse(
+ davxml.HRef.fromString(href),
+ davxml.PropertyStatus(
+ davxml.PropertyContainer(
+ davxml.GETETag.fromString(updateResource.etag().generate()),
+ ),
+ davxml.Status.fromResponseCode(responsecode.OK),
+ )
+ )
+ )
+ else:
+ xmlresponses.append(
+ davxml.StatusResponse(
+ davxml.HRef.fromString(href),
+ davxml.Status.fromResponseCode(code),
+ davxml.Error(
+ WebDAVUnknownElement.fromQname(*error),
+ ) if error else None,
+ )
+ )
+
+ @inlineCallbacks
+ def crudDelete(self, request, href, ifmatch, xmlresponses, hasPrivilege):
+ code = None
+ error = None
+ try:
+ if isinstance(hasPrivilege, HTTPError):
+ raise hasPrivilege
+
+ deleteResource = (yield request.locateResource(href))
+ if not deleteResource.exists():
+ raise HTTPError(responsecode.NOT_FOUND)
+
+ # Check if match
+ if ifmatch and ifmatch != deleteResource.etag().generate():
+ raise HTTPError(responsecode.PRECONDITION_FAILED)
+
+ yield deleteResource.storeRemove(
+ request,
+ True,
+ href,
+ )
+
+ except HTTPError, e:
+ # Extract the pre-condition
+ code = e.response.code
+ if isinstance(e.response, ErrorResponse):
+ error = e.response.error
+ error = (error.namespace, error.name,)
+
+ except Exception:
+ code = responsecode.BAD_REQUEST
+
+ if code is None:
+ xmlresponses.append(
+ davxml.StatusResponse(
+ davxml.HRef.fromString(href),
+ davxml.Status.fromResponseCode(responsecode.OK),
+ )
+ )
+ else:
+ xmlresponses.append(
+ davxml.StatusResponse(
+ davxml.HRef.fromString(href),
+ davxml.Status.fromResponseCode(code),
+ davxml.Error(
+ WebDAVUnknownElement.fromQname(*error),
+ ) if error else None,
+ )
+ )
+
+
def notifierID(self, label="default"):
self._newStoreObject.notifierID(label)
@@ -519,6 +885,9 @@
self._initializeWithHomeChild(calendar, home)
self._name = calendar.name() if calendar else name
+ if config.EnableBatchUpload:
+ self._postHandlers[("text", "calendar")] = _CommonHomeChildCollectionMixin.simpleBatchPOST
+ self.xmlDocHanders[customxml.Multiput] = _CommonHomeChildCollectionMixin.crudBatchPOST
def __repr__(self):
return "<Calendar Collection Resource %r:%r %s>" % (
@@ -597,8 +966,80 @@
createCalendarCollection = _CommonHomeChildCollectionMixin.createCollection
+ @classmethod
+ def componentsFromData(cls, data):
+ """
+ Need to split a single VCALENDAR into separate ones based on UID with the
+ appropriate VTIEMZONES included.
+ """
+
+ results = []
+ # Split into components by UID and TZID
+ try:
+ vcal = VCalendar.fromString(data)
+ except InvalidICalendarDataError:
+ return None
+
+ by_uid = {}
+ by_tzid = {}
+ for subcomponent in vcal.subcomponents():
+ if subcomponent.name() == "VTIMEZONE":
+ by_tzid[subcomponent.propertyValue("TZID")] = subcomponent
+ else:
+ by_uid.setdefault(subcomponent.propertyValue("UID"), []).append(subcomponent)
+
+ # Re-constitute as separate VCALENDAR objects
+ for components in by_uid.values():
+
+ newvcal = VCalendar("VCALENDAR")
+ newvcal.addProperty(VProperty("PRODID", vcal.propertyValue("PRODID")))
+
+ # Get the set of TZIDs and include them
+ tzids = set()
+ for component in components:
+ tzids.update(component.timezoneIDs())
+ for tzid in tzids:
+ try:
+ tz = by_tzid[tzid]
+ newvcal.addComponent(tz)
+ except KeyError:
+ # We ignore the error and generate invalid ics which someone will
+ # complain about at some point
+ pass
+
+ # Now add each component
+ for component in components:
+ newvcal.addComponent(component)
+
+ results.append(newvcal)
+
+ return results
+
+ @classmethod
+ def resourceSuffix(cls):
+ return ".ics"
+
+ @classmethod
+ def xmlDataElementType(cls):
+ return caldavxml.CalendarData
+
@inlineCallbacks
+ def storeResourceData(self, request, newchild, newchildURL, data):
+ storer = StoreCalendarObjectResource(
+ request = request,
+ destination = newchild,
+ destination_uri = newchildURL,
+ destinationcal = True,
+ destinationparent = self,
+ calendar = data,
+ )
+ yield storer.run()
+
+ returnValue(storer.returndata if hasattr(storer, "returndata") else None)
+
+
+ @inlineCallbacks
def storeRemove(self, request, implicitly, where):
"""
Delete this calendar collection resource, first deleting each contained
@@ -1462,6 +1903,9 @@
self._initializeWithHomeChild(addressbook, home)
self._name = addressbook.name() if addressbook else name
+ if config.EnableBatchUpload:
+ self._postHandlers[("text", "vcard")] = _CommonHomeChildCollectionMixin.simpleBatchPOST
+ self.xmlDocHanders[customxml.Multiput] = _CommonHomeChildCollectionMixin.crudBatchPOST
def __repr__(self):
return "<AddressBook Collection Resource %r:%r %s>" % (
@@ -1484,7 +1928,36 @@
createAddressBookCollection = _CommonHomeChildCollectionMixin.createCollection
+ @classmethod
+ def componentsFromData(cls, data):
+ try:
+ return VCard.allFromString(data)
+ except InvalidVCardDataError:
+ return None
+ @classmethod
+ def resourceSuffix(cls):
+ return ".vcf"
+
+ @classmethod
+ def xmlDataElementType(cls):
+ return carddavxml.AddressData
+
+ @inlineCallbacks
+ def storeResourceData(self, request, newchild, newchildURL, data):
+ storer = StoreAddressObjectResource(
+ request = request,
+ sourceadbk = False,
+ destination = newchild,
+ destination_uri = newchildURL,
+ destinationadbk = True,
+ destinationparent = self,
+ vcard = data,
+ )
+ yield storer.run()
+
+ returnValue(storer.returndata if hasattr(storer, "returndata") else None)
+
class GlobalAddressBookCollectionResource(GlobalAddressBookResource, AddressBookCollectionResource):
"""
Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
Modified: CalendarServer/trunk/twistedcaldav/vcard.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/vcard.py 2011-03-17 01:22:16 UTC (rev 7203)
+++ CalendarServer/trunk/twistedcaldav/vcard.py 2011-03-17 01:27:43 UTC (rev 7204)
@@ -142,6 +142,37 @@
raise InvalidVCardDataError(e)
@classmethod
+ def allFromString(clazz, string):
+ """
+ Construct a L{Component} from a string.
+ @param string: a string containing vCard data.
+ @return: a C{list} of L{Component}s representing the components described by
+ C{string}.
+ """
+ if type(string) is unicode:
+ string = string.encode("utf-8")
+ return clazz.allFromStream(StringIO.StringIO(string))
+
+ @classmethod
+ def allFromStream(clazz, stream):
+ """
+ Construct possibly multiple L{Component}s from a stream.
+ @param stream: a C{read()}able stream containing vCard data.
+ @return: a C{list} of L{Component}s representing the components described by
+ C{stream}.
+ """
+
+ results = []
+ try:
+ for vobject in readComponents(stream):
+ results.append(clazz(None, vobject=vobject))
+ return results
+ except vParseError, e:
+ raise InvalidVCardDataError(e)
+ except StopIteration, e:
+ raise InvalidVCardDataError(e)
+
+ @classmethod
def fromIStream(clazz, stream):
"""
Construct a L{Component} from a stream.
Property changes on: CalendarServer/trunk/txdav/caldav/datastore/index_file.py
___________________________________________________________________
Modified: 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/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/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/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/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/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/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
+ /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-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/index_file.py:6700-7198
/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/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/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-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/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
Property changes on: CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: 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/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/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/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/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/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/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
+ /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-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/test/test_index_file.py:6700-7198
/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/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/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-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/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
Property changes on: CalendarServer/trunk/txdav/carddav/datastore/index_file.py
___________________________________________________________________
Modified: 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/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/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/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/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/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/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
+ /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-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/index_file.py:6700-7198
/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/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/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-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/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
Property changes on: CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: 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/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/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/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/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/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/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
+ /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-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/test/test_index_file.py:6700-7198
/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/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/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-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110316/360ad2da/attachment-0001.html>
More information about the calendarserver-changes
mailing list