[CalendarServer-changes] [10306] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Tue Jan 15 13:15:07 PST 2013


Revision: 10306
          http://trac.calendarserver.org//changeset/10306
Author:   cdaboo at apple.com
Date:     2013-01-15 13:15:06 -0800 (Tue, 15 Jan 2013)
Log Message:
-----------
Tweak Prefer header syntax for changes in (now approved) draft RFC.

Modified Paths:
--------------
    CalendarServer/trunk/twext/web2/dav/method/propfind.py
    CalendarServer/trunk/twext/web2/dav/method/proppatch.py
    CalendarServer/trunk/twext/web2/http_headers.py
    CalendarServer/trunk/twext/web2/test/test_http_headers.py
    CalendarServer/trunk/twistedcaldav/method/mkcol.py
    CalendarServer/trunk/twistedcaldav/method/post.py
    CalendarServer/trunk/twistedcaldav/method/propfind.py
    CalendarServer/trunk/twistedcaldav/method/put.py
    CalendarServer/trunk/twistedcaldav/method/report_common.py
    CalendarServer/trunk/twistedcaldav/storebridge.py

Modified: CalendarServer/trunk/twext/web2/dav/method/propfind.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/propfind.py	2013-01-15 18:55:31 UTC (rev 10305)
+++ CalendarServer/trunk/twext/web2/dav/method/propfind.py	2013-01-15 21:15:06 UTC (rev 10306)
@@ -8,10 +8,10 @@
 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 # copies of the Software, and to permit persons to whom the Software is
 # furnished to do so, subject to the following conditions:
-# 
+#
 # The above copyright notice and this permission notice shall be included in all
 # copies or substantial portions of the Software.
-# 
+#
 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -40,7 +40,7 @@
 from twext.web2 import responsecode
 from twext.web2.http import StatusResponse
 from txdav.xml import element as davxml
-from twext.web2.dav.http import MultiStatusResponse, statusForFailure,\
+from twext.web2.dav.http import MultiStatusResponse, statusForFailure, \
     ErrorResponse
 from twext.web2.dav.util import normalizeURL, davXMLFromStream
 
@@ -107,15 +107,15 @@
     #
     request_uri = request.uri
     depth = request.headers.getHeader("depth", "infinity")
-    
+
     # By policy we will never allow a depth:infinity propfind
     if depth == "infinity":
         raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, davxml.PropfindFiniteDepth()))
 
     # Look for Prefer header first, then try Brief
     prefer = request.headers.getHeader("prefer", {})
-    returnMinimal = "return-minimal" in prefer
-    noRoot = "depth-noroot" in prefer
+    returnMinimal = any([key == "return" and value == "minimal" for key, value, _ignore_args in prefer])
+    noRoot = any([key == "depth-noroot" and value is None for key, value, _ignore_args in prefer])
     if not returnMinimal:
         returnMinimal = request.headers.getHeader("brief", False)
 
@@ -198,11 +198,12 @@
 
         for status in properties_by_status:
             properties = properties_by_status[status]
-            if not properties: continue
+            if not properties:
+                continue
 
-            xml_status    = davxml.Status.fromResponseCode(status)
+            xml_status = davxml.Status.fromResponseCode(status)
             xml_container = davxml.PropertyContainer(*properties)
-            xml_propstat  = davxml.PropertyStatus(xml_container, xml_status)
+            xml_propstat = davxml.PropertyStatus(xml_container, xml_status)
 
             propstats.append(xml_propstat)
 

Modified: CalendarServer/trunk/twext/web2/dav/method/proppatch.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/proppatch.py	2013-01-15 18:55:31 UTC (rev 10305)
+++ CalendarServer/trunk/twext/web2/dav/method/proppatch.py	2013-01-15 21:15:06 UTC (rev 10306)
@@ -8,10 +8,10 @@
 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 # copies of the Software, and to permit persons to whom the Software is
 # furnished to do so, subject to the following conditions:
-# 
+#
 # The above copyright notice and this permission notice shall be included in all
 # copies or substantial portions of the Software.
-# 
+#
 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -86,7 +86,7 @@
 
     # Look for Prefer header
     prefer = request.headers.getHeader("prefer", {})
-    returnMinimal = "return-minimal" in prefer
+    returnMinimal = any([key == "return" and value == "minimal" for key, value, _ignore_args in prefer])
 
     try:
         #
@@ -146,7 +146,7 @@
                 else:
                     responses.add(responsecode.OK, property)
 
-                    # Only add undo action for those that succeed because those that fail will not have changed               
+                    # Only add undo action for those that succeed because those that fail will not have changed
                     undoActions.append(undo)
 
                     yield True
@@ -196,7 +196,7 @@
         responses.error()
 
     #
-    # Return response - use 200 if Prefer:return-minimal set and no errors
+    # Return response - use 200 if Prefer:return=minimal set and no errors
     #
     if returnMinimal and not gotError:
         yield responsecode.OK

Modified: CalendarServer/trunk/twext/web2/http_headers.py
===================================================================
--- CalendarServer/trunk/twext/web2/http_headers.py	2013-01-15 18:55:31 UTC (rev 10305)
+++ CalendarServer/trunk/twext/web2/http_headers.py	2013-01-15 21:15:06 UTC (rev 10306)
@@ -855,6 +855,13 @@
 
 
 
+def parsePrefer(field):
+    etype, args = parseArgs(field)
+    etype = parseKeyValue(etype)
+    return (etype[0], etype[1], args)
+
+
+
 #### Header generators
 def generateAccept(accept):
     mimeType, q = accept
@@ -1032,6 +1039,18 @@
 
 
 
+def generatePrefer(items):
+    key, value, args = items
+    if value is None:
+        out = '%s' % (key,)
+    else:
+        out = '%s=%s' % (key, value)
+    if args:
+        out += ';' + generateKeyValues(args)
+    return out
+
+
+
 ####
 class ETag(object):
     def __init__(self, tag, weak=False):
@@ -1732,7 +1751,7 @@
     'If-Range': (parseIfRange,),
     'If-Unmodified-Since': (last, parseDateTime),
     'Max-Forwards': (last, int),
-    'Prefer': (tokenize, listParser(parseExpect), dict), # Prefer like Expect
+    'Prefer': (tokenize, listParser(parsePrefer), list),
 #    'Proxy-Authorization': str, # what is "credentials"
     'Range': (tokenize, parseRange),
     'Referer': (last, str), # TODO: URI object?
@@ -1756,7 +1775,7 @@
     'If-Range': (generateIfRange, singleHeader),
     'If-Unmodified-Since': (generateDateTime, singleHeader),
     'Max-Forwards': (str, singleHeader),
-    'Prefer': (iteritems, listGenerator(generateExpect), singleHeader), # Prefer like Expect
+    'Prefer': (listGenerator(generatePrefer), singleHeader),
 #    'Proxy-Authorization': str, # what is "credentials"
     'Range': (generateRange, singleHeader),
     'Referer': (str, singleHeader),

Modified: CalendarServer/trunk/twext/web2/test/test_http_headers.py
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_http_headers.py	2013-01-15 18:55:31 UTC (rev 10305)
+++ CalendarServer/trunk/twext/web2/test/test_http_headers.py	2013-01-15 21:15:06 UTC (rev 10306)
@@ -449,6 +449,18 @@
         self.runRoundtripTest("Expect", table)
 
 
+    def testPrefer(self):
+        table = (
+            ("wait",
+             [("wait", None, [])]),
+            ("return = representation",
+             [("return", "representation", [])]),
+            ("return =minimal;arg1;arg2=val2",
+             [("return", "minimal", [("arg1", None), ("arg2", "val2")])]),
+            )
+        self.runRoundtripTest("Prefer", table)
+
+
     def testFrom(self):
         self.runRoundtripTest("From", (("webmaster at w3.org", "webmaster at w3.org"),))
 

Modified: CalendarServer/trunk/twistedcaldav/method/mkcol.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/mkcol.py	2013-01-15 18:55:31 UTC (rev 10305)
+++ CalendarServer/trunk/twistedcaldav/method/mkcol.py	2013-01-15 21:15:06 UTC (rev 10306)
@@ -111,7 +111,7 @@
 
     if doc is not None:
 
-        # Can ignore Prefer:return-minimal as we don't return a body for success by default
+        # Can ignore Prefer:return=minimal as we don't return a body for success by default
 
         # Parse response body
         mkcol = doc.root_element

Modified: CalendarServer/trunk/twistedcaldav/method/post.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/post.py	2013-01-15 18:55:31 UTC (rev 10305)
+++ CalendarServer/trunk/twistedcaldav/method/post.py	2013-01-15 21:15:06 UTC (rev 10306)
@@ -133,7 +133,7 @@
 
             # Look for Prefer header
             prefer = request.headers.getHeader("prefer", {})
-            returnRepresentation = "return-representation" in prefer
+            returnRepresentation = any([key == "return" and value == "representation" for key, value, _ignore_args in prefer])
 
             if returnRepresentation and result.code / 100 == 2:
                 result = (yield newchild.http_GET(request))
@@ -201,7 +201,7 @@
 
             # Look for Prefer header
             prefer = request.headers.getHeader("prefer", {})
-            returnRepresentation = "return-representation" in prefer
+            returnRepresentation = any([key == "return" and value == "representation" for key, value, _ignore_args in prefer])
 
             if returnRepresentation and result.code / 100 == 2:
                 result = (yield newchild.http_GET(request))

Modified: CalendarServer/trunk/twistedcaldav/method/propfind.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/propfind.py	2013-01-15 18:55:31 UTC (rev 10305)
+++ CalendarServer/trunk/twistedcaldav/method/propfind.py	2013-01-15 21:15:06 UTC (rev 10306)
@@ -115,8 +115,8 @@
 
     # Look for Prefer header first, then try Brief
     prefer = request.headers.getHeader("prefer", {})
-    returnMinimal = "return-minimal" in prefer
-    noRoot = "depth-noroot" in prefer
+    returnMinimal = any([key == "return" and value == "minimal" for key, value, _ignore_args in prefer])
+    noRoot = any([key == "depth-noroot" and value is None for key, value, _ignore_args in prefer])
     if not returnMinimal:
         returnMinimal = request.headers.getHeader("brief", False)
 

Modified: CalendarServer/trunk/twistedcaldav/method/put.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put.py	2013-01-15 18:55:31 UTC (rev 10305)
+++ CalendarServer/trunk/twistedcaldav/method/put.py	2013-01-15 21:15:06 UTC (rev 10306)
@@ -31,7 +31,7 @@
 from twistedcaldav.caldavxml import caldav_namespace
 
 from twistedcaldav.method.put_common import StoreCalendarObjectResource
-from twistedcaldav.resource import isPseudoCalendarCollectionResource,\
+from twistedcaldav.resource import isPseudoCalendarCollectionResource, \
     CalDAVResource
 
 log = Logger()
@@ -57,7 +57,7 @@
                 (caldav_namespace, "supported-calendar-data"),
                 "Invalid MIME type for calendar collection",
             ))
-            
+
         # Read the calendar component from the stream
         try:
             calendardata = (yield allDataFromStream(request.stream))
@@ -75,24 +75,24 @@
                 ))
 
             storer = StoreCalendarObjectResource(
-                request = request,
-                destination = self,
-                destination_uri = request.uri,
-                destinationcal = True,
-                destinationparent = parent,
-                calendar = calendardata,
+                request=request,
+                destination=self,
+                destination_uri=request.uri,
+                destinationcal=True,
+                destinationparent=parent,
+                calendar=calendardata,
             )
             result = (yield storer.run())
 
             # Look for Prefer header
             prefer = request.headers.getHeader("prefer", {})
-            returnRepresentation = "return-representation" in prefer
+            returnRepresentation = any([key == "return" and value == "representation" for key, value, _ignore_args in prefer])
 
             if returnRepresentation and result.code / 100 == 2:
                 oldcode = result.code
                 result = (yield self.http_GET(request))
                 if oldcode == responsecode.CREATED:
-                    result.code =  responsecode.CREATED
+                    result.code = responsecode.CREATED
                 result.headers.setHeader("content-location", request.path)
 
             returnValue(result)
@@ -112,7 +112,7 @@
                 (carddav_namespace, "supported-address-data"),
                 "Invalid MIME type for address book collection",
             ))
-            
+
         # Read the vcard component from the stream
         try:
             vcarddata = (yield allDataFromStream(request.stream))
@@ -130,25 +130,25 @@
                 ))
 
             storer = StoreAddressObjectResource(
-                request = request,
-                sourceadbk = False,
-                vcard = vcarddata,
-                destination = self,
-                destination_uri = request.uri,
-                destinationadbk = True,
-                destinationparent = parent,
+                request=request,
+                sourceadbk=False,
+                vcard=vcarddata,
+                destination=self,
+                destination_uri=request.uri,
+                destinationadbk=True,
+                destinationparent=parent,
             )
             result = (yield storer.run())
 
             # Look for Prefer header
             prefer = request.headers.getHeader("prefer", {})
-            returnRepresentation = "return-representation" in prefer
+            returnRepresentation = any([key == "return" and value == "representation" for key, value, _ignore_args in prefer])
 
             if returnRepresentation and result.code / 100 == 2:
                 oldcode = result.code
                 result = (yield self.http_GET(request))
                 if oldcode == responsecode.CREATED:
-                    result.code =  responsecode.CREATED
+                    result.code = responsecode.CREATED
                 result.headers.setHeader("content-location", request.path)
 
             returnValue(result)
@@ -166,5 +166,5 @@
         if clength == 0:
             clength = self.contentLength()
         request.extendedLogItems["cl"] = str(clength)
-        
+
         returnValue(result)

Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_common.py	2013-01-15 18:55:31 UTC (rev 10305)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py	2013-01-15 21:15:06 UTC (rev 10306)
@@ -59,7 +59,7 @@
 from twistedcaldav.datafilters.hiddeninstance import HiddenInstanceFilter
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 from twistedcaldav.datafilters.addressdata import AddressDataFilter
-from twistedcaldav.dateops import clipPeriod, normalizePeriodList, timeRangesOverlap,\
+from twistedcaldav.dateops import clipPeriod, normalizePeriodList, timeRangesOverlap, \
     compareDateTime, normalizeToUTC, parseSQLTimestampToPyCalendar
 from twistedcaldav.ical import Component, Property, iCalendarProductID
 from twistedcaldav.instance import InstanceList
@@ -76,8 +76,8 @@
 
 log = Logger()
 
-COLLECTION_TYPE_REGULAR     = "collection"
-COLLECTION_TYPE_CALENDAR    = "calendar"
+COLLECTION_TYPE_REGULAR = "collection"
+COLLECTION_TYPE_CALENDAR = "calendar"
 COLLECTION_TYPE_ADDRESSBOOK = "adressbook"
 
 @inlineCallbacks
@@ -86,7 +86,7 @@
     Run an operation on all calendar collections, starting at the specified
     root, to the specified depth. This involves scanning the URI hierarchy
     down from the root. Return a MultiStatus element of all responses.
-    
+
     @param request: the L{IRequest} for the current request.
     @param resource: the L{CalDAVResource} representing the root to start scanning
         for calendar collections.
@@ -110,20 +110,22 @@
         resources = [(resource, request_uri)]
     else:
         resources = []
-        yield resource.findCalendarCollections(depth, request, lambda x, y: resources.append((x, y)), privileges = privileges)
-         
+        yield resource.findCalendarCollections(depth, request, lambda x, y: resources.append((x, y)), privileges=privileges)
+
     for calresource, uri in resources:
         result = (yield apply(calresource, uri))
         if not result:
             break
 
+
+
 @inlineCallbacks
 def applyToAddressBookCollections(resource, request, request_uri, depth, apply, privileges):
     """
     Run an operation on all address book collections, starting at the specified
     root, to the specified depth. This involves scanning the URI hierarchy
     down from the root. Return a MultiStatus element of all responses.
-    
+
     @param request: the L{IRequest} for the current request.
     @param resource: the L{CalDAVResource} representing the root to start scanning
         for address book collections.
@@ -138,7 +140,7 @@
         try:
             yield resource.checkPrivileges(request, privileges)
         except AccessDeniedError:
-            returnValue( None )
+            returnValue(None)
 
     # When scanning we only go down as far as an address book collection - not into one
     if resource.isAddressBookCollection():
@@ -147,13 +149,15 @@
         resources = [(resource, request_uri)]
     else:
         resources = []
-        yield resource.findAddressBookCollections(depth, request, lambda x, y: resources.append((x, y)), privileges = privileges)
-         
+        yield resource.findAddressBookCollections(depth, request, lambda x, y: resources.append((x, y)), privileges=privileges)
+
     for addrresource, uri in resources:
         result = yield apply(addrresource, uri)
         if not result:
             break
 
+
+
 def responseForHref(request, responses, href, resource, propertiesForResource, propertyreq, isowner=True, calendar=None, timezone=None, vcard=None):
     """
     Create an appropriate property status response for the given resource.
@@ -197,6 +201,8 @@
     d.addCallback(_defer)
     return d
 
+
+
 def allPropertiesForResource(request, prop, resource, calendar=None, timezone=None, vcard=None, isowner=True):
     """
     Return all (non-hidden) properties for the specified resource.
@@ -223,6 +229,8 @@
     d.addCallback(_defer)
     return d
 
+
+
 def propertyNamesForResource(request, prop, resource, calendar=None, timezone=None, vcard=None, isowner=True): #@UnusedVariable
     """
     Return property names for all properties on the specified resource.
@@ -243,11 +251,13 @@
             responsecode.OK: [propertyName(p) for p in props]
         }
         return properties_by_status
-    
+
     d = resource.listProperties(request)
     d.addCallback(_defer)
     return d
 
+
+
 def propertyListForResource(request, prop, resource, calendar=None, timezone=None, vcard=None, isowner=True):
     """
     Return the specified properties on the specified resource.
@@ -262,9 +272,11 @@
         C{False} otherwise.
     @return: a map of OK and NOT FOUND property values.
     """
-    
+
     return _namedPropertiesForResource(request, prop.children, resource, calendar, timezone, vcard, isowner)
 
+
+
 def validPropertyListCalendarDataTypeVersion(prop):
     """
     If the supplied prop element includes a calendar-data element, verify that
@@ -274,7 +286,7 @@
     @return:     a tuple: (True/False if the calendar-data element is one we can handle or not present,
                            error message).
     """
-    
+
     result = True
     message = ""
     generate_calendar_data = False
@@ -282,12 +294,14 @@
         if isinstance(property, caldavxml.CalendarData):
             if not property.verifyTypeVersion([("text/calendar", "2.0")]):
                 result = False
-                message = "Calendar-data element type/version not supported: content-type: %s, version: %s" % (property.content_type,property.version)
+                message = "Calendar-data element type/version not supported: content-type: %s, version: %s" % (property.content_type, property.version)
             generate_calendar_data = True
             break
 
     return result, message, generate_calendar_data
 
+
+
 def validPropertyListAddressDataTypeVersion(prop):
     """
     If the supplied prop element includes an address-data element, verify that
@@ -297,7 +311,7 @@
     @return:     a tuple: (True/False if the address-data element is one we can handle or not present,
                            error message).
     """
-    
+
     result = True
     message = ""
     generate_address_data = False
@@ -305,12 +319,14 @@
         if isinstance(property, carddavxml.AddressData):
             if not property.verifyTypeVersion([("text/vcard", "3.0")]):
                 result = False
-                message = "Address-data element type/version not supported: content-type: %s, version: %s" % (property.content_type,property.version)
+                message = "Address-data element type/version not supported: content-type: %s, version: %s" % (property.content_type, property.version)
             generate_address_data = True
             break
 
     return result, message, generate_address_data
 
+
+
 @inlineCallbacks
 def _namedPropertiesForResource(request, props, resource, calendar=None, timezone=None, vcard=None, isowner=True):
     """
@@ -333,10 +349,10 @@
         responsecode.OK        : [],
         responsecode.NOT_FOUND : [],
     }
-    
+
     # Look for Prefer header first, then try Brief
     prefer = request.headers.getHeader("prefer", {})
-    returnMinimal = "return-minimal" in prefer
+    returnMinimal = any([key == "return" and value == "minimal" for key, value, _ignore_args in prefer])
     if not returnMinimal:
         returnMinimal = request.headers.getHeader("brief", False)
 
@@ -351,7 +367,7 @@
             propvalue = CalendarData().fromCalendar(filtered)
             properties_by_status[responsecode.OK].append(propvalue)
             continue
-    
+
         if isinstance(property, carddavxml.AddressData):
             if vcard is None:
                 vcard = (yield resource.vCard())
@@ -359,12 +375,12 @@
             propvalue = AddressData().fromAddress(filtered)
             properties_by_status[responsecode.OK].append(propvalue)
             continue
-    
+
         if isinstance(property, element.WebDAVElement):
             qname = property.qname()
         else:
             qname = property
-    
+
         has = (yield resource.hasProperty(property, request))
 
         if has:
@@ -377,12 +393,13 @@
             except HTTPError:
                 f = Failure()
                 status = statusForFailure(f, "getting property: %s" % (qname,))
-                if status not in properties_by_status: properties_by_status[status] = []
+                if status not in properties_by_status:
+                        properties_by_status[status] = []
                 if not returnMinimal or status != responsecode.NOT_FOUND:
                     properties_by_status[status].append(propertyName(qname))
         elif not returnMinimal:
             properties_by_status[responsecode.NOT_FOUND].append(propertyName(qname))
-    
+
     returnValue(properties_by_status)
 
 fbtype_mapper = {"BUSY": 0, "BUSY-TENTATIVE": 1, "BUSY-UNAVAILABLE": 2}
@@ -391,38 +408,40 @@
 fbcacher = Memcacher("FBCache", pickle=True)
 
 class FBCacheEntry(object):
-    
+
     CACHE_DAYS_FLOATING_ADJUST = 1
-    
+
     def __init__(self, key, token, timerange, fbresults):
         self.key = key
         self.token = token
         self.timerange = timerange
         self.fbresults = fbresults
-    
+
+
     @classmethod
     @inlineCallbacks
     def getCacheEntry(cls, calresource, useruid, timerange):
-        
+
         key = calresource.resourceID() + "/" + useruid
         token = (yield calresource.getInternalSyncToken())
         entry = (yield fbcacher.get(key))
-        
+
         if entry:
-    
+
             # Offset one day at either end to account for floating
             cached_start = entry.timerange.start + PyCalendarDuration(days=FBCacheEntry.CACHE_DAYS_FLOATING_ADJUST)
             cached_end = entry.timerange.end - PyCalendarDuration(days=FBCacheEntry.CACHE_DAYS_FLOATING_ADJUST)
 
             # Verify that the requested timerange lies within the cache timerange
             if compareDateTime(timerange.end, cached_end) <= 0 and compareDateTime(timerange.start, cached_start) >= 0:
-                
+
                 # Verify that cached entry is still valid
                 if token == entry.token:
                     returnValue(entry.fbresults)
-        
-        returnValue(None) 
 
+        returnValue(None)
+
+
     @classmethod
     @inlineCallbacks
     def makeCacheEntry(cls, calresource, useruid, timerange, fbresults):
@@ -432,6 +451,8 @@
         entry = cls(key, token, timerange, fbresults)
         yield fbcacher.set(key, entry)
 
+
+
 @inlineCallbacks
 def generateFreeBusyInfo(
     request,
@@ -479,7 +500,7 @@
     organizer_principal = calresource.principalForCalendarUserAddress(organizer) if organizer else None
     organizer_uid = organizer_principal.principalUID() if organizer_principal else ""
 
-    # Free busy is per-user 
+    # Free busy is per-user
     userPrincipal = (yield calresource.resourceOwnerPrincipal(request))
     if userPrincipal:
         useruid = userPrincipal.principalUID()
@@ -501,7 +522,7 @@
     }
     do_event_details = False
     if event_details is not None and organizer_principal is not None and userPrincipal is not None:
-         
+
         # Check if organizer is attendee
         if organizer_principal == userPrincipal:
             do_event_details = True
@@ -518,12 +539,11 @@
             do_event_details = True
             rich_options["resource"] = True
 
-
     # Try cache
     resources = (yield FBCacheEntry.getCacheEntry(calresource, useruid, timerange)) if config.EnableFreeBusyCache else None
 
     if resources is None:
-        
+
         caching = False
         if config.EnableFreeBusyCache:
             # Log extended item
@@ -532,22 +552,22 @@
             request.extendedLogItems["fb-uncached"] = request.extendedLogItems.get("fb-uncached", 0) + 1
 
             # We want to cache a large range of time based on the current date
-            cache_start = normalizeToUTC(PyCalendarDateTime.getToday() + PyCalendarDuration(days=-config.FreeBusyCacheDaysBack))
+            cache_start = normalizeToUTC(PyCalendarDateTime.getToday() + PyCalendarDuration(days=0 - config.FreeBusyCacheDaysBack))
             cache_end = normalizeToUTC(PyCalendarDateTime.getToday() + PyCalendarDuration(days=config.FreeBusyCacheDaysForward))
-            
+
             # If the requested timerange would fit in our allowed cache range, trigger the cache creation
             if compareDateTime(timerange.start, cache_start) >= 0 and compareDateTime(timerange.end, cache_end) <= 0:
                 cache_timerange = TimeRange(start=cache_start.getText(), end=cache_end.getText())
                 caching = True
- 
+
         #
         # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range.
         # We then take those results and merge them into one VFREEBUSY component
         # with appropriate FREEBUSY properties, and return that single item as iCal data.
         #
-    
+
         # Create fake filter element to match time-range
-        filter =  caldavxml.Filter(
+        filter = caldavxml.Filter(
                       caldavxml.ComponentFilter(
                           caldavxml.ComponentFilter(
                               cache_timerange if caching else timerange,
@@ -558,7 +578,7 @@
                   )
         filter = calendarqueryfilter.Filter(filter)
         tzinfo = filter.settimezone(tz)
-    
+
         try:
             resources = yield maybeDeferred(calresource.index().indexedSearch,
                 filter, useruid=useruid, fbtype=True
@@ -567,7 +587,7 @@
                 yield FBCacheEntry.makeCacheEntry(calresource, useruid, cache_timerange, resources)
         except IndexedSearchException:
             resources = yield maybeDeferred(calresource.index().bruteForceSearch)
-            
+
     else:
         # Log extended item
         if not hasattr(request, "extendedLogItems"):
@@ -592,13 +612,13 @@
         if type == "VEVENT" and aggregated_resources[key][0][3] != '?':
 
             matchedResource = False
-            
+
             # Look at each instance
             for float, start, end, fbtype in aggregated_resources[key]:
                 # Ignore free time or unknown
                 if fbtype in ('F', '?'):
                     continue
-                
+
                 # Ignore ones of this UID
                 if excludeuid:
                     # See if we have a UID match
@@ -612,7 +632,7 @@
                         # Check for no ORGANIZER and check by same calendar user
                         elif (test_uid == "") and same_calendar_user:
                             continue
-                        
+
                 # Apply a timezone to any floating times
                 fbstart = parseSQLTimestampToPyCalendar(start)
                 if float == 'Y':
@@ -624,9 +644,9 @@
                     fbend.setTimezone(tzinfo)
                 else:
                     fbend.setTimezone(PyCalendarTimezone(utc=True))
-                
+
                 # Clip instance to time range
-                clipped = clipPeriod(PyCalendarPeriod(fbstart, duration=fbend-fbstart), PyCalendarPeriod(timerange.start, timerange.end))
+                clipped = clipPeriod(PyCalendarPeriod(fbstart, duration=fbend - fbstart), PyCalendarPeriod(timerange.start, timerange.end))
 
                 # Double check for overlap
                 if clipped:
@@ -638,7 +658,7 @@
                 matchtotal += 1
                 if matchtotal > max_number_of_matches:
                     raise NumberOfMatchesWithinLimits(max_number_of_matches)
-                
+
                 # Add extended details
                 if do_event_details:
                     child = (yield request.locateChildResource(calresource, name))
@@ -648,14 +668,14 @@
         else:
             child = (yield request.locateChildResource(calresource, name))
             calendar = (yield child.iCalendarForUser(request))
-            
+
             # The calendar may come back as None if the resource is being changed, or was deleted
             # between our initial index query and getting here. For now we will ignore this error, but in
             # the longer term we need to implement some form of locking, perhaps.
             if calendar is None:
                 log.err("Calendar %s is missing from calendar collection %r" % (name, calresource))
                 continue
-            
+
             # Ignore ones of this UID
             if excludeuid:
                 # See if we have a UID match
@@ -663,20 +683,20 @@
                     test_organizer = calendar.getOrganizer()
                     test_principal = calresource.principalForCalendarUserAddress(test_organizer) if test_organizer else None
                     test_uid = test_principal.principalUID() if test_principal else ""
-    
+
                     # Check that ORGANIZER's match (security requirement)
                     if (organizer is None) or (organizer_uid == test_uid):
                         continue
                     # Check for no ORGANIZER and check by same calendar user
                     elif (test_organizer is None) and same_calendar_user:
                         continue
-    
+
             if filter.match(calendar, None):
                 # Check size of results is within limit
                 matchtotal += 1
                 if matchtotal > max_number_of_matches:
                     raise NumberOfMatchesWithinLimits(max_number_of_matches)
-    
+
                 if calendar.mainType() == "VEVENT":
                     processEventFreeBusy(calendar, fbinfo, timerange, tzinfo)
                 elif calendar.mainType() == "VFREEBUSY":
@@ -691,14 +711,16 @@
                     child = (yield request.locateChildResource(calresource, name))
                     calendar = (yield child.iCalendarForUser(request))
                     _addEventDetails(calendar, event_details, rich_options, timerange, tzinfo)
-    
+
     returnValue(matchtotal)
 
+
+
 def _addEventDetails(calendar, event_details, rich_options, timerange, tzinfo):
     """
     Expand events within the specified time range and limit the set of properties to those allowed for
     delegate extended free busy.
-    
+
     @param calendar: the calendar object to expand
     @type calendar: L{Component}
     @param event_details: list to append VEVENT components to
@@ -739,6 +761,7 @@
     event_details.extend([subcomponent for subcomponent in expanded.subcomponents() if subcomponent.name() == "VEVENT"])
 
 
+
 def processEventFreeBusy(calendar, fbinfo, timerange, tzinfo):
     """
     Extract free busy data from a VEVENT component.
@@ -747,10 +770,10 @@
     @param timerange: the time range to restrict free busy data to.
     @param tzinfo: the L{PyCalendarTimezone} for the timezone to use for floating/all-day events.
     """
-    
+
     # Expand out the set of instances for the event with in the required range
     instances = calendar.expandTimeRanges(timerange.end, lowerLimit=timerange.start, ignoreInvalidInstances=True)
-    
+
     # Can only do timed events
     for key in instances:
         instance = instances[key]
@@ -759,7 +782,7 @@
         break
     else:
         return
-        
+
     for key in instances:
         instance = instances[key]
 
@@ -770,19 +793,19 @@
         fbend = instance.end
         if fbend.floating():
             fbend.setTimezone(tzinfo)
-        
+
         # Check TRANSP property of underlying component
         if instance.component.hasProperty("TRANSP"):
             # If its TRANSPARENT we always ignore it
             if instance.component.propertyValue("TRANSP") == "TRANSPARENT":
                 continue
-        
+
         # Determine status
         if instance.component.hasProperty("STATUS"):
             status = instance.component.propertyValue("STATUS")
         else:
             status = "CONFIRMED"
-        
+
         # Ignore cancelled
         if status == "CANCELLED":
             continue
@@ -790,11 +813,11 @@
         # Clip period for this instance - use duration for period end if that
         # is what original component used
         if instance.component.hasProperty("DURATION"):
-            period = PyCalendarPeriod(fbstart, duration=fbend-fbstart)
+            period = PyCalendarPeriod(fbstart, duration=fbend - fbstart)
         else:
             period = PyCalendarPeriod(fbstart, fbend)
         clipped = clipPeriod(period, PyCalendarPeriod(timerange.start, timerange.end))
-        
+
         # Double check for overlap
         if clipped:
             if status == "TENTATIVE":
@@ -802,6 +825,8 @@
             else:
                 fbinfo[0].append(clipped)
 
+
+
 def processFreeBusyFreeBusy(calendar, fbinfo, timerange):
     """
     Extract FREEBUSY data from a VFREEBUSY component.
@@ -817,14 +842,14 @@
         if start and end:
             if not timeRangesOverlap(start, end, timerange.start, timerange.end):
                 continue
-        
+
         # Now look at each FREEBUSY property
         for fb in vfb.properties("FREEBUSY"):
             # Check the type
             fbtype = fb.parameterValue("FBTYPE", default="BUSY")
             if fbtype == "FREE":
                 continue
-            
+
             # Look at each period in the property
             assert isinstance(fb.value(), list), "FREEBUSY property does not contain a list of values: %r" % (fb,)
             for period in fb.value():
@@ -833,6 +858,8 @@
                 if clipped:
                     fbinfo[fbtype_mapper.get(fbtype, 0)].append(clipped)
 
+
+
 def processAvailabilityFreeBusy(calendar, fbinfo, timerange):
     """
     Extract free-busy data from a VAVAILABILITY component.
@@ -840,7 +867,7 @@
     @param fbinfo: the tuple used to store the three types of fb data.
     @param timerange: the time range to restrict free busy data to.
     """
-    
+
     for vav in [x for x in calendar.subcomponents() if x.name() == "VAVAILABILITY"]:
 
         # Get overall start/end
@@ -854,10 +881,10 @@
         overall = clipPeriod(period, PyCalendarPeriod(timerange.start, timerange.end))
         if overall is None:
             continue
-        
+
         # Now get periods for each instance of AVAILABLE sub-components
         periods = processAvailablePeriods(vav, timerange)
-        
+
         # Now invert the periods and store in accumulator
         busyperiods = []
         last_end = timerange.start
@@ -874,15 +901,16 @@
             fbtype = "BUSY-UNAVAILABLE"
 
         fbinfo[fbtype_mapper.get(fbtype, 2)].extend(busyperiods)
-            
 
+
+
 def processAvailablePeriods(calendar, timerange):
     """
     Extract instance period data from an AVAILABLE component.
     @param calendar: the L{Component} that is the VAVAILABILITY containing the AVAILABLE's.
     @param timerange: the time range to restrict free busy data to.
     """
-    
+
     periods = []
 
     # First we need to group all AVAILABLE sub-components by UID
@@ -891,12 +919,12 @@
         if component.name() == "AVAILABLE":
             uid = component.propertyValue("UID")
             uidmap.setdefault(uid, []).append(component)
-            
+
     # Then we expand each uid set separately
     for componentSet in uidmap.itervalues():
         instances = InstanceList(ignoreInvalidInstances=True)
         instances.expandTimeRanges(componentSet, timerange.end)
-        
+
         # Now convert instances into period list
         for key in instances:
             instance = instances[key]
@@ -912,16 +940,18 @@
             # Clip period for this instance - use duration for period end if that
             # is what original component used
             if instance.component.hasProperty("DURATION"):
-                period = PyCalendarPeriod(start, duration=end-start)
+                period = PyCalendarPeriod(start, duration=end - start)
             else:
                 period = PyCalendarPeriod(start, end)
             clipped = clipPeriod(period, PyCalendarPeriod(timerange.start, timerange.end))
             if clipped:
                 periods.append(clipped)
-            
+
     normalizePeriodList(periods)
     return periods
 
+
+
 def buildFreeBusyResult(fbinfo, timerange, organizer=None, attendee=None, uid=None, method=None, event_details=None):
     """
     Generate a VCALENDAR object containing a single VFREEBUSY that is the
@@ -936,12 +966,12 @@
     @param event_details: VEVENT components to add.
     @return:              the L{Component} containing the calendar data.
     """
-    
+
     # Merge overlapping time ranges in each fb info section
     normalizePeriodList(fbinfo[0])
     normalizePeriodList(fbinfo[1])
     normalizePeriodList(fbinfo[2])
-    
+
     # Now build a new calendar object with the free busy info we have
     fbcalendar = Component("VCALENDAR")
     fbcalendar.addProperty(Property("VERSION", "2.0"))

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2013-01-15 18:55:31 UTC (rev 10305)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2013-01-15 21:15:06 UTC (rev 10306)
@@ -2774,7 +2774,9 @@
         result = (yield storer.run())
 
         # Look for Prefer header
-        if "return-representation" in request.headers.getHeader("prefer", {}) and result.code / 100 == 2:
+        prefer = request.headers.getHeader("prefer", {})
+        returnRepresentation = any([key == "return" and value == "representation" for key, value, _ignore_args in prefer])
+        if returnRepresentation and result.code / 100 == 2:
             result = (yield self.render(request))
             result.code = OK
             result.headers.setHeader("content-location", request.path)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130115/5b9a34ee/attachment-0001.html>


More information about the calendarserver-changes mailing list