[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