[CalendarServer-changes] [8971] CalendarServer/trunk/twistedcaldav
source_changes at macosforge.org
source_changes at macosforge.org
Tue Apr 3 11:15:08 PDT 2012
Revision: 8971
http://trac.macosforge.org/projects/calendarserver/changeset/8971
Author: sagen at apple.com
Date: 2012-04-03 11:15:08 -0700 (Tue, 03 Apr 2012)
Log Message:
-----------
Performance improvements:
1) Skip some validation for "internal" requests
2) Cache icalendar serialization when possible
3) Pass components directly to storebridge when available
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/ical.py
CalendarServer/trunk/twistedcaldav/method/put_common.py
CalendarServer/trunk/twistedcaldav/storebridge.py
CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py 2012-04-03 16:38:22 UTC (rev 8970)
+++ CalendarServer/trunk/twistedcaldav/ical.py 2012-04-03 18:15:08 UTC (rev 8971)
@@ -171,7 +171,7 @@
for attrname, attrvalue in params.items():
self._pycalendar.addAttribute(PyCalendarAttribute(attrname, attrvalue))
- def __str__ (self): return str(self._pycalendar)
+ def __str__(self): return str(self._pycalendar)
def __repr__(self): return "<%s: %r: %r>" % (self.__class__.__name__, self.name(), self.value())
def __hash__(self):
@@ -201,15 +201,21 @@
@return: the duplicated calendar.
"""
return Property(None, None, None, pycalendar=self._pycalendar.duplicate())
-
- def name (self): return self._pycalendar.getName()
- def value (self): return self._pycalendar.getValue().getValue()
+ def name(self): return self._pycalendar.getName()
- def strvalue (self): return str(self._pycalendar.getValue())
+ def value(self): return self._pycalendar.getValue().getValue()
+ def strvalue(self): return str(self._pycalendar.getValue())
+
+ def _markAsDirty(self):
+ parent = getattr(self, "_parent", None)
+ if parent is not None:
+ parent._markAsDirty()
+
def setValue(self, value):
self._pycalendar.setValue(value)
+ self._markAsDirty()
def parameterNames(self):
"""
@@ -236,12 +242,15 @@
def setParameter(self, paramname, paramvalue):
self._pycalendar.replaceAttribute(PyCalendarAttribute(paramname, paramvalue))
+ self._markAsDirty()
def removeParameter(self, paramname):
self._pycalendar.removeAttributes(paramname)
+ self._markAsDirty()
def removeAllParameters(self):
self._pycalendar.setAttributes({})
+ self._markAsDirty()
def removeParameterValue(self, paramname, paramvalue):
@@ -253,6 +262,7 @@
if value == paramvalue:
if not attr.removeValue(value):
self._pycalendar.removeAttributes(paramname)
+ self._markAsDirty()
def containsTimeRange(self, start, end, defaulttz=None):
"""
@@ -416,8 +426,6 @@
else:
raise AssertionError("name may not be None")
- # FIXME: _parent is not use internally, and appears to be used elsewhere,
- # even though it's names as a private variable.
if "parent" in kwargs:
parent = kwargs["parent"]
@@ -433,12 +441,25 @@
self._pycalendar = PyCalendar(add_defaults=False) if name == "VCALENDAR" else PyCalendar.makeComponent(name, None)
self._parent = None
- def __str__ (self):
+ def __str__(self):
"""
NB This does not automatically include timezones in VCALENDAR objects.
"""
- return str(self._pycalendar)
+ cachedCopy = getattr(self, "_cachedCopy", None)
+ if cachedCopy is not None:
+ return cachedCopy
+ self._cachedCopy = str(self._pycalendar)
+ return self._cachedCopy
+ def _markAsDirty(self):
+ """
+ Invalidate the cached copy of serialized icalendar data
+ """
+ self._cachedCopy = None
+ parent = getattr(self, "_parent", None)
+ if parent is not None:
+ parent._markAsDirty()
+
def __repr__(self):
return "<%s: %r>" % (self.__class__.__name__, str(self._pycalendar))
@@ -590,6 +611,7 @@
"""
self._pycalendar.addComponent(component._pycalendar)
component._parent = self
+ self._markAsDirty()
def removeComponent(self, component):
"""
@@ -597,6 +619,8 @@
@param component: the L{Component} to remove.
"""
self._pycalendar.removeComponent(component._pycalendar)
+ component._parent = None
+ self._markAsDirty()
def hasProperty(self, name):
"""
@@ -807,6 +831,8 @@
"""
self._pycalendar.addProperty(property._pycalendar)
self._pycalendar.finalise()
+ property._parent = self
+ self._markAsDirty()
def removeProperty(self, property):
"""
@@ -815,6 +841,8 @@
"""
self._pycalendar.removeProperty(property._pycalendar)
self._pycalendar.finalise()
+ property._parent = None
+ self._markAsDirty()
def replaceProperty(self, property):
"""
@@ -825,6 +853,7 @@
# Remove all existing ones first
self._pycalendar.removeProperties(property.name())
self.addProperty(property)
+ self._markAsDirty()
def timezoneIDs(self):
"""
@@ -910,6 +939,8 @@
rrules.changed()
changed = True
+ if changed:
+ self._markAsDirty()
return changed
def expand(self, start, end, timezone=None):
Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py 2012-04-03 16:38:22 UTC (rev 8970)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py 2012-04-03 18:15:08 UTC (rev 8971)
@@ -208,44 +208,48 @@
self.validIfScheduleMatch()
if self.destinationcal:
- # Valid resource name check
- result, message = self.validResourceName()
- if not result:
- log.err(message)
- raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Resource name not allowed"))
- # Valid collection size check on the destination parent resource
- result, message = (yield self.validCollectionSize())
- if not result:
- log.err(message)
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- customxml.MaxResources(),
- "Too many resources in collection",
- ))
+ # Skip validation on internal requests
+ if not self.internal_request:
- # Valid data sizes - do before parsing the data
- if self.source is not None:
- # Valid content length check on the source resource
- result, message = self.validContentLength()
+ # Valid resource name check
+ result, message = self.validResourceName()
if not result:
log.err(message)
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- (caldav_namespace, "max-resource-size"),
- "Calendar data too large",
- ))
- else:
- # Valid calendar data size check
- result, message = self.validSizeCheck()
+ raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Resource name not allowed"))
+
+ # Valid collection size check on the destination parent resource
+ result, message = (yield self.validCollectionSize())
if not result:
log.err(message)
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
- (caldav_namespace, "max-resource-size"),
- "Calendar data too large",
+ customxml.MaxResources(),
+ "Too many resources in collection",
))
+ # Valid data sizes - do before parsing the data
+ if self.source is not None:
+ # Valid content length check on the source resource
+ result, message = self.validContentLength()
+ if not result:
+ log.err(message)
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (caldav_namespace, "max-resource-size"),
+ "Calendar data too large",
+ ))
+ else:
+ # Valid calendar data size check
+ result, message = self.validSizeCheck()
+ if not result:
+ log.err(message)
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (caldav_namespace, "max-resource-size"),
+ "Calendar data too large",
+ ))
+
if not self.sourcecal:
# Valid content type check on the source resource if its not in a calendar collection
if self.source is not None:
@@ -286,51 +290,54 @@
if self.calendar.stripKnownTimezones():
self.calendardata = None
- # Valid calendar data check
- result, message = self.validCalendarDataCheck()
- if not result:
- log.err(message)
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- (caldav_namespace, "valid-calendar-data"),
- description=message
- ))
-
- # Valid calendar data for CalDAV check
- result, message = self.validCalDAVDataCheck()
- if not result:
- log.err(message)
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- (caldav_namespace, "valid-calendar-object-resource"),
- "Invalid calendar data",
- ))
+ # Skip validation on internal requests
+ if not self.internal_request:
- # Valid calendar component for check
- result, message = self.validComponentType()
- if not result:
- log.err(message)
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- (caldav_namespace, "supported-component"),
- "Invalid calendar data",
- ))
+ # Valid calendar data check
+ result, message = self.validCalendarDataCheck()
+ if not result:
+ log.err(message)
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (caldav_namespace, "valid-calendar-data"),
+ description=message
+ ))
- # Valid attendee list size check
- result, message = (yield self.validAttendeeListSizeCheck())
- if not result:
- log.err(message)
- raise HTTPError(
- ErrorResponse(
+ # Valid calendar data for CalDAV check
+ result, message = self.validCalDAVDataCheck()
+ if not result:
+ log.err(message)
+ raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
- MaxAttendeesPerInstance.fromString(str(config.MaxAttendeesPerInstance)),
- "Too many attendees in calendar data",
+ (caldav_namespace, "valid-calendar-object-resource"),
+ "Invalid calendar data",
+ ))
+
+ # Valid calendar component for check
+ result, message = self.validComponentType()
+ if not result:
+ log.err(message)
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (caldav_namespace, "supported-component"),
+ "Invalid calendar data",
+ ))
+
+ # Valid attendee list size check
+ result, message = (yield self.validAttendeeListSizeCheck())
+ if not result:
+ log.err(message)
+ raise HTTPError(
+ ErrorResponse(
+ responsecode.FORBIDDEN,
+ MaxAttendeesPerInstance.fromString(str(config.MaxAttendeesPerInstance)),
+ "Too many attendees in calendar data",
+ )
)
- )
- # Normalize the calendar user addresses once we know we have valid
- # calendar data
- self.destination.iCalendarAddressDoNormalization(self.calendar)
+ # Normalize the calendar user addresses once we know we have valid
+ # calendar data
+ self.destination.iCalendarAddressDoNormalization(self.calendar)
# Must have a valid UID at this point
self.uid = self.calendar.resourceUID()
@@ -555,6 +562,7 @@
result = True
message = ""
if config.MaxResourceSize:
+ # FIXME PERF could be done more efficiently?
calsize = len(str(self.calendar))
if calsize > config.MaxResourceSize:
result = False
@@ -1052,9 +1060,14 @@
def doStorePut(self, data=None):
if data is None:
+ # We'll be passing this component directly to storeComponent( )
+ componentToStore = self.calendar
if self.calendardata is None:
self.calendardata = str(self.calendar)
data = self.calendardata
+ else:
+ # We'll be passing data as a stream to storeStream( )
+ componentToStore = None
# Update calendar-access property value on the resource. We need to do this before the
# store as the store will "commit" the new value.
@@ -1116,8 +1129,12 @@
self.destination.scheduleEtags = ()
- stream = MemoryStream(data)
- response = yield self.destination.storeStream(stream)
+ if componentToStore is None:
+ stream = MemoryStream(data)
+ response = yield self.destination.storeStream(stream)
+ else:
+ # Since we already have a component, we can pass it directly
+ response = yield self.destination.storeComponent(componentToStore)
response = IResponse(response)
if self.isScheduleResource:
Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py 2012-04-03 16:38:22 UTC (rev 8970)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py 2012-04-03 18:15:08 UTC (rev 8971)
@@ -1762,7 +1762,24 @@
returnValue(CREATED)
+ @inlineCallbacks
+ def storeComponent(self, component):
+ if self._newStoreObject:
+ yield self._newStoreObject.setComponent(component)
+ returnValue(NO_CONTENT)
+ else:
+ self._newStoreObject = (yield self._newStoreParent.createObjectResourceWithName(
+ self.name(), component, self._metadata
+ ))
+
+ # Re-initialize to get stuff setup again now we have no object
+ self._initializeWithObject(self._newStoreObject, self._newStoreParent)
+
+ returnValue(CREATED)
+
+
+
@inlineCallbacks
def storeRemove(self, request, implicitly, where):
"""
Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py 2012-04-03 16:38:22 UTC (rev 8970)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py 2012-04-03 18:15:08 UTC (rev 8971)
@@ -6086,3 +6086,83 @@
self.assertEquals("urn:uuid:buz", prop.value())
self.assertEquals(prop.parameterValue("CALENDARSERVER-OLD-CUA"),
"http://example.com/principals/users/buz")
+
+
+ def test_serializationCaching(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VEVENT
+UID:12345-67890
+END:VEVENT
+END:VCALENDAR
+"""
+
+ component = Component.fromString(data)
+
+ str(component) # to serialize and cache
+ self.assertNotEquals(component._cachedCopy, None)
+ component._markAsDirty()
+ self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+ str(component) # to serialize and cache
+ self.assertNotEquals(component._cachedCopy, None)
+ prop = Property("PRODID", "FOO")
+ component.addProperty(prop)
+ self.assertEquals(prop._parent, component)
+ self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+ str(component) # to serialize and cache
+ component.removeProperty(prop)
+ self.assertEquals(prop._parent, None)
+ self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+ str(component) # to serialize and cache
+ prop2 = Property("PRODID", "BAR")
+ component.replaceProperty(prop2)
+ self.assertEquals(prop2._parent, component)
+ self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+ str(component) # to serialize and cache
+ prop2.setValue("BAZ")
+ self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+ str(component) # to serialize and cache
+ prop2.setParameter("XYZZY", "PLUGH")
+ self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+ str(component) # to serialize and cache
+ prop2.removeParameter("XYZZY")
+ self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+ str(component) # to serialize and cache
+ prop2.removeAllParameters()
+ self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+ prop2.setParameter("XYZZY", ["PLUGH", "PLUGH2"])
+ str(component) # to serialize and cache
+ prop2.removeParameterValue("XYZZY", "PLUGH2")
+ self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+ compData = """BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VEVENT
+UID:C31854DA-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
+DTSTART;VALUE=DATE:20020214
+DTEND;VALUE=DATE:20020215
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2
+SUMMARY:Valentine's Day
+END:VEVENT
+END:VCALENDAR
+"""
+ str(component) # to serialize and cache
+ [subComponent] = Component.fromString(compData).subcomponents()
+ component.addComponent(subComponent)
+ self.assertEquals(subComponent._parent, component)
+ self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+ str(component) # to serialize and cache
+ component.removeComponent(subComponent)
+ self.assertEquals(subComponent._parent, None)
+ self.assertEquals(component._cachedCopy, None) # cache is invalidated
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120403/45747b01/attachment-0001.html>
More information about the calendarserver-changes
mailing list