[CalendarServer-changes] [4721] CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/ twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Mon Nov 9 07:42:58 PST 2009


Revision: 4721
          http://trac.macosforge.org/projects/calendarserver/changeset/4721
Author:   cdaboo at apple.com
Date:     2009-11-09 07:42:57 -0800 (Mon, 09 Nov 2009)
Log Message:
-----------
Move calendar-query logic out of caldavxml.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/caldavxml.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/index.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/method/report_calquery.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/method/report_common.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/calendarquery.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/scheduling/processing.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/test/test_calendarquery.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/test/test_index.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/test/test_xml.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/queryfilter.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/test/
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/test/__init__.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/test/test_calendarquery.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/test/test_queryfilter.py

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/caldavxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/caldavxml.py	2009-11-09 15:42:26 UTC (rev 4720)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/caldavxml.py	2009-11-09 15:42:57 UTC (rev 4721)
@@ -25,17 +25,12 @@
 See draft spec: http://ietf.webdav.org/caldav/draft-dusseault-caldav.txt
 """
 
-import datetime
-
-from vobject.icalendar import utc, TimezoneComponent
-
 from twisted.web2.dav import davxml
-
-from twistedcaldav.dateops import timeRangesOverlap
 from twistedcaldav.ical import Component as iComponent
-from twistedcaldav.ical import Property as iProperty
 from twistedcaldav.ical import parse_date_or_datetime
 from twistedcaldav.log import Logger
+from vobject.icalendar import  TimezoneComponent, utc
+import datetime
 
 log = Logger()
 
@@ -97,41 +92,34 @@
         self.start = parse_date_or_datetime(attributes["start"]) if "start" in attributes else None
         self.end = parse_date_or_datetime(attributes["end"]) if "end" in attributes else None
 
+    def valid(self, level=0):
+        """
+        Indicate whether the time-range is valid (must be date-time in UTC).
+        
+        @return:      True if valid, False otherwise
+        """
+        
+        if self.start is not None and not isinstance(self.start, datetime.datetime):
+            log.msg("start attribute in <time-range> is not a date-time: %s" % (self.start,))
+            return False
+        if self.end is not None and not isinstance(self.end, datetime.datetime):
+            log.msg("end attribute in <time-range> is not a date-time: %s" % (self.end,))
+            return False
+        if self.start is not None and self.start.tzinfo != utc:
+            log.msg("start attribute in <time-range> is not UTC: %s" % (self.start,))
+            return False
+        if self.end is not None and self.end.tzinfo != utc:
+            log.msg("end attribute in <time-range> is not UTC: %s" % (self.end,))
+            return False
+
+        # No other tests
+        return True
+
 class CalDAVTimeZoneElement (CalDAVTextElement):
     """
     CalDAV element containing iCalendar data with a single VTIMEZONE component.
     """
-    def __init__(self, *children, **attributes):
-        super(CalDAVTimeZoneElement, self).__init__(*children, **attributes)
 
-        # An error in the data needs to be reported as a pre-condition error rather than
-        # an XML parse error. So this test is moved out of here into a separate method that
-        # gets called and can cause the proper WebDAV DAV:error response.
-        
-        # TODO: Remove the comment above and commented code below once this has been properly tested.
-#
-#        try:
-#            calendar = self.calendar()
-#            if calendar is None: raise ValueError("No data")
-#        except ValueError, e:
-#            log.err("Invalid iCalendar data (%s): %r" % (calendar, e))
-#            raise
-#
-#        found = False
-#
-#        for subcomponent in calendar.subcomponents():
-#            if subcomponent.name() == "VTIMEZONE":
-#                if found:
-#                    raise ValueError("CalDAV:%s may not contain iCalendar data with more than one VTIMEZONE component" % (self.name,))
-#                else:
-#                    found = True
-#            else:
-#                # FIXME: Spec doesn't seem to really disallow this; it's unclear...
-#                raise ValueError("%s component not allowed in CalDAV:timezone data" % (subcomponent.name(),))
-#
-#        if not found:
-#            raise ValueError("CalDAV:%s must contain iCalendar data with a VTIMEZONE component" % (self.name,))
-
     def calendar(self):
         """
         Returns a calendar component derived from this element, which contains
@@ -169,64 +157,6 @@
 
         return found
         
-class CalDAVFilterElement (CalDAVElement):
-    """
-    CalDAV filter element.
-    """
-    def __init__(self, *children, **attributes):
-        # FIXME: is-defined is obsoleted by CalDAV-access-09.  Filter it out here for compatibility.
-        children = [c for c in children if c is not None and c.qname() != (caldav_namespace, "is-defined")]
-
-        super(CalDAVFilterElement, self).__init__(*children, **attributes)
-
-        qualifier = None
-        filters = []
-
-        for child in self.children:
-            qname = child.qname()
-            
-            if qname in (
-                (caldav_namespace, "is-not-defined"),
-                (caldav_namespace, "time-range"),
-                (caldav_namespace, "text-match"),
-            ):
-                if qualifier is not None:
-                    raise ValueError("Only one of CalDAV:time-range, CalDAV:text-match allowed")
-                qualifier = child
-
-            else:
-                filters.append(child)
-
-        if qualifier and (qualifier.qname() == (caldav_namespace, "is-not-defined")) and (len(filters) != 0):
-            raise ValueError("No other tests allowed when CalDAV:is-not-defined is present")
-            
-        self.qualifier   = qualifier
-        self.filters     = filters
-        self.filter_name = attributes["name"]
-        if isinstance(self.filter_name, unicode):
-            self.filter_name = self.filter_name.encode("utf-8")
-        self.defined     = not self.qualifier or (self.qualifier.qname() != (caldav_namespace, "is-not-defined"))
-
-    def match(self, item, access=None):
-        """
-        Returns True if the given calendar item (either a component, property or parameter value)
-        matches this filter, False otherwise.
-        """
-        
-        # Always return True for the is-not-defined case as the result of this will
-        # be negated by the caller
-        if not self.defined: return True
-
-        if self.qualifier and not self.qualifier.match(item, access): return False
-
-        if len(self.filters) > 0:
-            for filter in self.filters:
-                if filter._match(item, access):
-                    return True
-            return False
-        else:
-            return True
-
 class CalendarHomeSet (CalDAVElement):
     """
     The calendar collections URLs for this principal's calendar user.
@@ -341,7 +271,7 @@
     def __init__(self, *children, **attributes):
         super(CalendarQuery, self).__init__(*children, **attributes)
 
-        query = None
+        props = None
         filter = None
         timezone = None
 
@@ -353,9 +283,9 @@
                 (davxml.dav_namespace, "propname"),
                 (davxml.dav_namespace, "prop"    ),
             ):
-                if query is not None:
+                if props is not None:
                     raise ValueError("Only one of CalDAV:allprop, CalDAV:propname, CalDAV:prop allowed")
-                query = child
+                props = child
 
             elif qname == (caldav_namespace, "filter"):
                 filter = child
@@ -370,7 +300,7 @@
             if filter is None:
                 raise ValueError("CALDAV:filter required")
 
-        self.query  = query
+        self.props  = props
         self.filter = filter
         self.timezone = timezone
 
@@ -689,79 +619,7 @@
 
     allowed_children = { (caldav_namespace, "comp-filter"): (1, 1) }
 
-    def match(self, component, access=None):
-        """
-        Returns True if the given calendar component matches this filter, False
-        otherwise.
-        """
-        
-        # We only care about certain access restrictions.
-        if access not in (iComponent.ACCESS_CONFIDENTIAL, iComponent.ACCESS_RESTRICTED):
-            access = None
-
-        # We need to prepare ourselves for a time-range query by pre-calculating
-        # the set of instances up to the latest time-range limit. That way we can
-        # avoid having to do some form of recurrence expansion for each query sub-part.
-        maxend, isStartTime = self.getmaxtimerange()
-        if maxend:
-            if isStartTime:
-                if component.isRecurringUnbounded():
-                    # Unbounded recurrence is always within a start-only time-range
-                    instances = None
-                else:
-                    # Expand the instances up to infinity
-                    instances = component.expandTimeRanges(datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc), ignoreInvalidInstances=True)
-            else:
-                instances = component.expandTimeRanges(maxend, ignoreInvalidInstances=True)
-        else:
-            instances = None
-        self.children[0].setInstances(instances)
-
-        # <filter> contains exactly one <comp-filter>
-        return self.children[0].match(component, access)
-
-    def valid(self):
-        """
-        Indicate whether this filter element's structure is valid wrt iCalendar
-        data object model.
-        
-        @return: True if valid, False otherwise
-        """
-        
-        # Must have one child element for VCALENDAR
-        return self.children[0].valid(0)
-        
-    def settimezone(self, tzelement):
-        """
-        Set the default timezone to use with this query.
-        @param calendar: a L{Component} for the VCALENDAR containing the one
-            VTIMEZONE that we want
-        @return: the L{datetime.tzinfo} derived from the VTIMEZONE or utc.
-        """
-        assert tzelement is None or isinstance(tzelement, CalDAVTimeZoneElement)
-
-        if tzelement is not None:
-            calendar = tzelement.calendar()
-            if calendar is not None:
-                for subcomponent in calendar.subcomponents():
-                    if subcomponent.name() == "VTIMEZONE":
-                        # <filter> contains exactly one <comp-filter>
-                        tzinfo = subcomponent.gettzinfo()
-                        self.children[0].settzinfo(tzinfo)
-                        return tzinfo
-
-        # Default to using utc tzinfo
-        self.children[0].settzinfo(utc)
-        return utc
-
-    def getmaxtimerange(self):
-        """
-        Get the date farthest into the future in any time-range elements
-        """
-        
-        return self.children[0].getmaxtimerange(None, False)
-
-class ComponentFilter (CalDAVFilterElement):
+class ComponentFilter (CalDAVElement):
     """
     Limits a search to only the chosen component types.
     (CalDAV-access-09, section 9.6.1)
@@ -769,7 +627,6 @@
     name = "comp-filter"
 
     allowed_children = {
-        (caldav_namespace, "is-defined"     ): (0, 1), # FIXME: obsoleted in CalDAV-access-09
         (caldav_namespace, "is-not-defined" ): (0, 1),
         (caldav_namespace, "time-range"     ): (0, 1),
         (caldav_namespace, "comp-filter"    ): (0, None),
@@ -777,153 +634,7 @@
     }
     allowed_attributes = { "name": True }
 
-    def match(self, item, access):
-        """
-        Returns True if the given calendar item (which is a component)
-        matches this filter, False otherwise.
-        This specialization uses the instance matching option of the time-range filter
-        to minimize instance expansion.
-        """
-
-        # Always return True for the is-not-defined case as the result of this will
-        # be negated by the caller
-        if not self.defined: return True
-
-        if self.qualifier and not self.qualifier.matchinstance(item, self.instances): return False
-
-        if len(self.filters) > 0:
-            for filter in self.filters:
-                if filter._match(item, access):
-                    return True
-            return False
-        else:
-            return True
-
-    def _match(self, component, access):
-        # At least one subcomponent must match (or is-not-defined is set)
-        for subcomponent in component.subcomponents():
-            # If access restrictions are in force, restrict matching to specific components only.
-            # In particular do not match VALARM.
-            if access and subcomponent.name() not in ("VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VTIMEZONE",):
-                continue
-            
-            # Try to match the component name
-            if isinstance(self.filter_name, str):
-                if subcomponent.name() != self.filter_name: continue
-            else:
-                if subcomponent.name() not in self.filter_name: continue
-            if self.match(subcomponent, access): break
-        else:
-            return not self.defined
-        return self.defined
-        
-    def setInstances(self, instances):
-        """
-        Give the list of instances to each comp-filter element.
-        @param instances: the list of instances.
-        """
-        self.instances = instances
-        for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
-            compfilter.setInstances(instances)
-        
-    def valid(self, level):
-        """
-        Indicate whether this filter element's structure is valid wrt iCalendar
-        data object model.
-        
-        @param level: the nesting level of this filter element, 0 being the top comp-filter.
-        @return:      True if valid, False otherwise
-        """
-        
-        # Check for time-range
-        timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
-
-        if level == 0:
-            # Must have VCALENDAR at the top
-            if (self.filter_name != "VCALENDAR") or timerange:
-                log.msg("Top-level comp-filter must be VCALENDAR, instead: %s" % (self.filter_name,))
-                return False
-        elif level == 1:
-            # Disallow VCALENDAR, VALARM, STANDARD, DAYLIGHT, AVAILABLE at the top, everything else is OK
-            if self.filter_name in ("VCALENDAR", "VALARM", "STANDARD", "DAYLIGHT", "AVAILABLE"):
-                log.msg("comp-filter wrong component type: %s" % (self.filter_name,))
-                return False
-            
-            # time-range only on VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY
-            if timerange and self.filter_name not in ("VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VAVAILABILITY"):
-                log.msg("time-range cannot be used with component %s" % (self.filter_name,))
-                return False
-        elif level == 2:
-            # Disallow VCALENDAR, VTIMEZONE, VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY at the top, everything else is OK
-            if (self.filter_name in ("VCALENDAR", "VTIMEZONE", "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VAVAILABILITY")):
-                log.msg("comp-filter wrong sub-component type: %s" % (self.filter_name,))
-                return False
-            
-            # time-range only on VALARM, AVAILABLE
-            if timerange and self.filter_name not in ("VALARM", "AVAILABLE",):
-                log.msg("time-range cannot be used with sub-component %s" % (self.filter_name,))
-                return False
-        else:
-            # Disallow all standard iCal components anywhere else
-            if (self.filter_name in ("VCALENDAR", "VTIMEZONE", "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VALARM", "STANDARD", "DAYLIGHT", "AVAILABLE")) or timerange:
-                log.msg("comp-filter wrong standard component type: %s" % (self.filter_name,))
-                return False
-        
-        # Test each property
-        for propfilter in [x for x in self.filters if isinstance(x, PropertyFilter)]:
-            if not propfilter.valid():
-                return False
-
-        # Test each component
-        for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
-            if not compfilter.valid(level + 1):
-                return False
-
-        # Test the time-range
-        if timerange:
-            if not self.qualifier.valid():
-                return False
-
-        return True
-
-    def settzinfo(self, tzinfo):
-        """
-        Set the default timezone to use with this query.
-        @param tzinfo: a L{datetime.tzinfo} to use.
-        """
-        
-        # Give tzinfo to any TimeRange we have
-        if isinstance(self.qualifier, TimeRange):
-            self.qualifier.settzinfo(tzinfo)
-        
-        # Pass down to sub components/properties
-        for x in self.filters:
-            x.settzinfo(tzinfo)
-
-    def getmaxtimerange(self, currentMaximum, currentIsStartTime):
-        """
-        Get the date furthest into the future in any time-range elements
-        
-        @param currentMaximum: current future value to compare with
-        @type currentMaximum: L{datetime.datetime}
-        """
-        
-        # Give tzinfo to any TimeRange we have
-        isStartTime = False
-        if isinstance(self.qualifier, TimeRange):
-            isStartTime = self.qualifier.end is None
-            compareWith = self.qualifier.start if isStartTime else self.qualifier.end
-            if currentMaximum is None or currentMaximum < compareWith:
-                currentMaximum = compareWith
-                currentIsStartTime = isStartTime
-        
-        # Pass down to sub components/properties
-        for x in self.filters:
-            currentMaximum, currentIsStartTime = x.getmaxtimerange(currentMaximum, currentIsStartTime)
-
-        return currentMaximum, currentIsStartTime
-
-class PropertyFilter (CalDAVFilterElement):
+class PropertyFilter (CalDAVElement):
     """
     Limits a search to specific properties.
     (CalDAV-access-09, section 9.6.2)
@@ -931,7 +642,6 @@
     name = "prop-filter"
 
     allowed_children = {
-        (caldav_namespace, "is-defined"     ): (0, 1), # FIXME: obsoleted in CalDAV-access-09
         (caldav_namespace, "is-not-defined" ): (0, 1),
         (caldav_namespace, "time-range"     ): (0, 1),
         (caldav_namespace, "text-match"     ): (0, 1),
@@ -939,80 +649,7 @@
     }
     allowed_attributes = { "name": True }
 
-    def _match(self, component, access):
-        # When access restriction is in force, we need to only allow matches against the properties
-        # allowed by the access restriction level.
-        if access:
-            allowedProperties = iComponent.confidentialPropertiesMap.get(component.name(), None)
-            if allowedProperties and access == iComponent.ACCESS_RESTRICTED:
-                allowedProperties += iComponent.extraRestrictedProperties
-        else:
-            allowedProperties = None
-
-        # At least one property must match (or is-not-defined is set)
-        for property in component.properties():
-            # Apply access restrictions, if any.
-            if allowedProperties is not None and property.name() not in allowedProperties:
-                continue
-            if property.name() == self.filter_name and self.match(property, access): break
-        else:
-            return not self.defined
-        return self.defined
-
-    def valid(self):
-        """
-        Indicate whether this filter element's structure is valid wrt iCalendar
-        data object model.
-        
-        @return:      True if valid, False otherwise
-        """
-        
-        # Check for time-range
-        timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
-        
-        # time-range only on COMPLETED, CREATED, DTSTAMP, LAST-MODIFIED
-        if timerange and self.filter_name not in ("COMPLETED", "CREATED", "DTSTAMP", "LAST-MODIFIED"):
-            log.msg("time-range cannot be used with property %s" % (self.filter_name,))
-            return False
-
-        # Test the time-range
-        if timerange:
-            if not self.qualifier.valid():
-                return False
-
-        # No other tests
-        return True
-
-    def settzinfo(self, tzinfo):
-        """
-        Set the default timezone to use with this query.
-        @param tzinfo: a L{datetime.tzinfo} to use.
-        """
-        
-        # Give tzinfo to any TimeRange we have
-        if isinstance(self.qualifier, TimeRange):
-            self.qualifier.settzinfo(tzinfo)
-
-    def getmaxtimerange(self, currentMaximum, currentIsStartTime):
-        """
-        Get the date furthest into the future in any time-range elements
-        
-        @param currentMaximum: current future value to compare with
-        @type currentMaximum: L{datetime.datetime}
-        """
-        
-        # Give tzinfo to any TimeRange we have
-        isStartTime = False
-        if isinstance(self.qualifier, TimeRange):
-            isStartTime = self.qualifier.end is None
-            compareWith = self.qualifier.start if isStartTime else self.qualifier.end
-            if currentMaximum is None or currentMaximum < compareWith:
-                currentMaximum = compareWith
-                currentIsStartTime = isStartTime
-
-        return currentMaximum, currentIsStartTime
-
-class ParameterFilter (CalDAVFilterElement):
+class ParameterFilter (CalDAVElement):
     """
     Limits a search to specific parameters.
     (CalDAV-access-09, section 9.6.3)
@@ -1020,42 +657,11 @@
     name = "param-filter"
 
     allowed_children = {
-        (caldav_namespace, "is-defined"     ): (0, 1), # FIXME: obsoleted in CalDAV-access-09
         (caldav_namespace, "is-not-defined" ): (0, 1),
         (caldav_namespace, "text-match"     ): (0, 1),
     }
     allowed_attributes = { "name": True }
 
-    def _match(self, property, access):
-        # We have to deal with the problem that the 'Native' form of a property
-        # will be missing the TZID parameter due to the conversion performed. Converting
-        # to non-native for the entire calendar object causes problems elsewhere, so its
-        # best to do it here for this one special case.
-        if self.filter_name == "TZID":
-            transformed = property.transformAllFromNative()
-        else:
-            transformed = False
-
-        # At least one property must match (or is-not-defined is set)
-        result = not self.defined
-        for parameterName in property.params().keys():
-            if parameterName == self.filter_name and self.match(property.params()[parameterName], access):
-                result = self.defined
-                break
-
-        if transformed:
-            property.transformAllToNative()
-        return result
-
-class IsDefined (CalDAVEmptyElement):
-    """
-    FIXME: Removed from spec.
-    """
-    name = "is-defined"
-
-    def match(self, component, access):
-        return component is not None
-
 class IsNotDefined (CalDAVEmptyElement):
     """
     Specifies that the named iCalendar item does not exist.
@@ -1063,13 +669,6 @@
     """
     name = "is-not-defined"
 
-    def match(self, component, access):
-        # Oddly, this needs always to return True so that it appears there is
-        # a match - but we then "negate" the result if is-not-defined is set.
-        # Actually this method should never be called as we special case the
-        # is-not-defined option.
-        return True
-
 class TextMatch (CalDAVTextElement):
     """
     Specifies a substring match on a property or parameter value.
@@ -1097,68 +696,6 @@
         "negate-condition": False
     }
 
-    def __init__(self, *children, **attributes):
-        super(TextMatch, self).__init__(*children, **attributes)
-
-        if "caseless" in attributes:
-            caseless = attributes["caseless"]
-            if caseless == "yes":
-                self.caseless = True
-            elif caseless == "no":
-                self.caseless = False
-        else:
-            self.caseless = True
-
-        if "negate-condition" in attributes:
-            negate = attributes["negate-condition"]
-            if negate == "yes":
-                self.negate = True
-            elif caseless == "no":
-                self.negate = False
-        else:
-            self.negate = False
-
-    def match(self, item, access):
-        """
-        Match the text for the item.
-        If the item is a property, then match the property value,
-        otherwise it may be a list of parameter values - try to match anyone of those
-        """
-        if item is None: return False
-
-        if isinstance(item, iProperty):
-            values = [item.value()]
-        else:
-            values = item
-
-        test = unicode(str(self), "utf-8")
-        if self.caseless:
-            test = test.lower()
-
-        def _textCompare(s):
-            if self.caseless:
-                if s.lower().find(test) != -1:
-                    return True, not self.negate
-            else:
-                if s.find(test) != -1:
-                    return True, not self.negate
-            return False, False
-
-        for value in values:
-            # NB Its possible that we have a text list value which appears as a Python list,
-            # so we need to check for that an iterate over the list.
-            if isinstance(value, list):
-                for subvalue in value:
-                    matched, result = _textCompare(subvalue)
-                    if matched:
-                        return result
-            else:
-                matched, result = _textCompare(value)
-                if matched:
-                    return result
-        
-        return self.negate
-
 class TimeZone (CalDAVTimeZoneElement):
     """
     Specifies a time zone component.
@@ -1173,103 +710,6 @@
     """
     name = "time-range"
 
-    def __init__(self, *children, **attributes):
-        super(TimeRange, self).__init__(*children, **attributes)
-        self.tzinfo = None
-
-    def settzinfo(self, tzinfo):
-        """
-        Set the default timezone to use with this query.
-        @param tzinfo: a L{datetime.tzinfo} to use.
-        """
-        
-        # Give tzinfo to any TimeRange we have
-        self.tzinfo = tzinfo
-
-    def valid(self):
-        """
-        Indicate whether the time-range is valid (must be date-time in UTC).
-        
-        @return:      True if valid, False otherwise
-        """
-        
-        if self.start is not None and not isinstance(self.start, datetime.datetime):
-            log.msg("start attribute in <time-range> is not a date-time: %s" % (self.start,))
-            return False
-        if self.end is not None and not isinstance(self.end, datetime.datetime):
-            log.msg("end attribute in <time-range> is not a date-time: %s" % (self.end,))
-            return False
-        if self.start is not None and self.start.tzinfo != utc:
-            log.msg("start attribute in <time-range> is not UTC: %s" % (self.start,))
-            return False
-        if self.end is not None and self.end.tzinfo != utc:
-            log.msg("end attribute in <time-range> is not UTC: %s" % (self.end,))
-            return False
-
-        # No other tests
-        return True
-
-    def match(self, property, access):
-        """
-        NB This is only called when doing a time-range match on a property.
-        """
-        if property is None:
-            return False
-        else:
-            return property.containsTimeRange(self.start, self.end, self.tzinfo)
-
-    def matchinstance(self, component, instances):
-        """
-        Test whether this time-range element causes a match to the specified component
-        using the specified set of instances to determine the expanded time ranges.
-        @param component: the L{Component} to test.
-        @param instances: the list of expanded instances.
-        @return: True if the time-range query matches, False otherwise.
-        """
-        if component is None:
-            return False
-        
-        assert instances is not None or self.end is None, "Failure to expand instance for time-range filter: %r" % (self,)
-        
-        # Special case open-ended unbounded
-        if instances is None:
-            if component.getRecurrenceIDUTC() is None:
-                return True
-            else:
-                # See if the overridden component's start is past the start
-                start, _ignore_end = component.getEffectiveStartEnd()
-                if start is None:
-                    return True
-                else:
-                    return start >= self.start
-
-        # Handle alarms as a special case
-        alarms = (component.name() == "VALARM")
-        if alarms:
-            testcomponent = component._parent
-        else:
-            testcomponent = component
-            
-        for key in instances:
-            instance = instances[key]
-            
-            # First make sure components match
-            if not testcomponent.same(instance.component):
-                continue
-
-            if alarms:
-                # Get all the alarm triggers for this instance and test each one
-                triggers = instance.getAlarmTriggers()
-                for trigger in triggers:
-                    if timeRangesOverlap(trigger, None, self.start, self.end, self.tzinfo):
-                        return True
-            else:
-                # Regular instance overlap test
-                if timeRangesOverlap(instance.start, instance.end, self.start, self.end, self.tzinfo):
-                    return True
-
-        return False
-
 class CalendarMultiGet (CalDAVElement):
     """
     CalDAV report used to retrieve specific calendar component items via their

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/index.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/index.py	2009-11-09 15:42:26 UTC (rev 4720)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/index.py	2009-11-09 15:42:57 UTC (rev 4721)
@@ -46,9 +46,9 @@
 
 from twistedcaldav.ical import Component
 from twistedcaldav.query import calendarquery
+from twistedcaldav.query import queryfilter
 from twistedcaldav.sql import AbstractSQLDatabase
 from twistedcaldav.sql import db_prefix
-from twistedcaldav import caldavxml
 from twistedcaldav.instance import InvalidOverriddenInstanceError
 from twistedcaldav.log import Logger, LoggingMixIn
 from twistedcaldav.config import config
@@ -286,7 +286,7 @@
 
         # Make sure we have a proper Filter element and get the partial SQL
         # statement to use.
-        if isinstance(filter, caldavxml.Filter):
+        if isinstance(filter, queryfilter.Filter):
             qualifiers = calendarquery.sqlcalendarquery(filter)
             if qualifiers is not None:
                 # Determine how far we need to extend the current expansion of

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/method/report_calquery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/method/report_calquery.py	2009-11-09 15:42:26 UTC (rev 4720)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/method/report_calquery.py	2009-11-09 15:42:57 UTC (rev 4721)
@@ -40,6 +40,7 @@
 from twistedcaldav.instance import TooManyInstancesError
 from twistedcaldav.log import Logger
 from twistedcaldav.method import report_common
+from twistedcaldav.query import queryfilter
 
 log = Logger()
 
@@ -64,10 +65,11 @@
 
     responses = []
 
-    filter = calendar_query.filter
-    query  = calendar_query.query
+    xmlfilter = calendar_query.filter
+    filter = queryfilter.Filter(xmlfilter)
+    props  = calendar_query.props
 
-    assert query is not None
+    assert props is not None
     
     # Get the original timezone provided in the query, if any, and validate it now
     query_timezone = None
@@ -80,19 +82,19 @@
         filter.settimezone(query_tz)
         query_timezone = tuple(calendar_query.timezone.calendar().subcomponents())[0]
 
-    if query.qname() == ("DAV:", "allprop"):
+    if props.qname() == ("DAV:", "allprop"):
         propertiesForResource = report_common.allPropertiesForResource
         generate_calendar_data = False
 
-    elif query.qname() == ("DAV:", "propname"):
+    elif props.qname() == ("DAV:", "propname"):
         propertiesForResource = report_common.propertyNamesForResource
         generate_calendar_data = False
 
-    elif query.qname() == ("DAV:", "prop"):
+    elif props.qname() == ("DAV:", "prop"):
         propertiesForResource = report_common.propertyListForResource
         
         # Verify that any calendar-data element matches what we can handle
-        result, message, generate_calendar_data = report_common.validPropertyListCalendarDataTypeVersion(query)
+        result, message, generate_calendar_data = report_common.validPropertyListCalendarDataTypeVersion(props)
         if not result:
             log.err(message)
             raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data")))
@@ -102,7 +104,7 @@
 
     # Verify that the filter element is valid
     if (filter is None) or not filter.valid():
-        log.err("Invalid filter element: %r" % (filter,))
+        log.err("Invalid filter element: %r" % (xmlfilter,))
         raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-filter")))
 
     matchcount = [0]
@@ -145,7 +147,7 @@
                 else:
                     href = davxml.HRef.fromString(uri)
             
-                return report_common.responseForHref(request, responses, href, resource, calendar, timezone, propertiesForResource, query, isowner)
+                return report_common.responseForHref(request, responses, href, resource, calendar, timezone, propertiesForResource, props, isowner)
             else:
                 return succeed(None)
     

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/method/report_common.py	2009-11-09 15:42:26 UTC (rev 4720)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/method/report_common.py	2009-11-09 15:42:57 UTC (rev 4721)
@@ -60,6 +60,7 @@
 from twistedcaldav.instance import InstanceList
 from twistedcaldav.index import IndexedSearchException
 from twistedcaldav.log import Logger
+from twistedcaldav.query import queryfilter
 
 log = Logger()
 
@@ -335,6 +336,7 @@
                       name="VCALENDAR",
                    )
               )
+    filter = queryfilter.Filter(filter)
 
     # Get the timezone property from the collection, and store in the query filter
     # for use during the query itself.

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/calendarquery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/calendarquery.py	2009-11-09 15:42:26 UTC (rev 4720)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/calendarquery.py	2009-11-09 15:42:57 UTC (rev 4721)
@@ -27,9 +27,7 @@
 ]
 
 from twistedcaldav.dateops import floatoffset
-from twistedcaldav.query import sqlgenerator
-from twistedcaldav.query import expression
-from twistedcaldav import caldavxml
+from twistedcaldav.query import expression, sqlgenerator, queryfilter
 
 # SQL Index column (field) names
 
@@ -51,17 +49,17 @@
     # Lets assume we have a valid filter from the outset.
     
     # Top-level filter contains exactly one comp-filter element
-    assert len(filter.children) == 1
-    vcalfilter = filter.children[0]
-    assert isinstance(vcalfilter, caldavxml.ComponentFilter)
+    assert filter.child is not None
+    vcalfilter = filter.child
+    assert isinstance(vcalfilter, queryfilter.ComponentFilter)
     assert vcalfilter.filter_name == "VCALENDAR"
     
-    if len(vcalfilter.children) > 0:
+    if len(vcalfilter.filters) > 0:
         # Only comp-filters are handled
-        for _ignore in [x for x in vcalfilter.children if not isinstance(x, caldavxml.ComponentFilter)]:
+        for _ignore in [x for x in vcalfilter.filters if not isinstance(x, queryfilter.ComponentFilter)]:
             raise ValueError
         
-        return compfilterListExpression(vcalfilter.children)
+        return compfilterListExpression(vcalfilter.filters)
     else:
         return expression.allExpression()
 
@@ -98,13 +96,13 @@
         expressions.append(expression.inExpression(FIELD_TYPE, compfilter.filter_name, True))
     
     # Handle time-range    
-    if compfilter.qualifier and isinstance(compfilter.qualifier, caldavxml.TimeRange):
+    if compfilter.qualifier and isinstance(compfilter.qualifier, queryfilter.TimeRange):
         start, end, startfloat, endfloat = getTimerangeArguments(compfilter.qualifier)
         expressions.append(expression.timerangeExpression(start, end, startfloat, endfloat))
         
     # Handle properties - we can only do UID right now
     props = []
-    for p in [x for x in compfilter.filters if isinstance(x, caldavxml.PropertyFilter)]:
+    for p in [x for x in compfilter.filters if isinstance(x, queryfilter.PropertyFilter)]:
         props.append(propfilterExpression(p))
     if len(props) > 1:
         propsExpression = expression.orExpression[props]
@@ -115,7 +113,7 @@
         
     # Handle embedded components - we do not right now as our Index does not handle them
     comps = []
-    for _ignore in [x for x in compfilter.filters if isinstance(x, caldavxml.ComponentFilter)]:
+    for _ignore in [x for x in compfilter.filters if isinstance(x, queryfilter.ComponentFilter)]:
         raise ValueError
     if len(comps) > 1:
         compsExpression = expression.orExpression[comps]
@@ -153,16 +151,16 @@
         return expression.isExpression(FIELD_UID, "", True)
     
     # Handle time-range - we cannot do this with our Index right now
-    if propfilter.qualifier and isinstance(propfilter.qualifier, caldavxml.TimeRange):
+    if propfilter.qualifier and isinstance(propfilter.qualifier, queryfilter.TimeRange):
         raise ValueError
     
     # Handle text-match
     tm = None
-    if propfilter.qualifier and isinstance(propfilter.qualifier, caldavxml.TextMatch):
+    if propfilter.qualifier and isinstance(propfilter.qualifier, queryfilter.TextMatch):
         if propfilter.qualifier.negate:
-            tm = expression.notcontainsExpression(propfilter.filter_name, str(propfilter.qualifier), propfilter.qualifier)
+            tm = expression.notcontainsExpression(propfilter.filter_name, propfilter.qualifier.text, propfilter.qualifier.caseless)
         else:
-            tm = expression.containsExpression(propfilter.filter_name, str(propfilter.qualifier), propfilter.qualifier)
+            tm = expression.containsExpression(propfilter.filter_name, propfilter.qualifier.text, propfilter.qualifier.caseless)
     
     # Handle embedded parameters - we do not right now as our Index does not handle them
     params = []
@@ -227,79 +225,3 @@
         return sql.generate()
     except ValueError:
         return None
-
-
-if __name__ == "__main__":
-    import datetime
-
-    filter = caldavxml.Filter(
-                 caldavxml.ComponentFilter(
-                     *[caldavxml.ComponentFilter(
-                           *[caldavxml.TimeRange(**{"start":"20060605T160000Z", "end":"20060605T170000Z"})],
-                           **{"name":("VEVENT", "VFREEBUSY", "VAVAILABILITY")}
-                       )],
-                     **{"name":"VCALENDAR"}
-                 )
-             )
-
-    # A complete implementation of current DST rules for major US time zones.
-    
-    def first_sunday_on_or_after(dt):
-        days_to_go = 6 - dt.weekday()
-        if days_to_go:
-            dt += datetime.timedelta(days_to_go)
-        return dt
-    
-    # In the US, DST starts at 2am (standard time) on the first Sunday in April.
-    DSTSTART = datetime.datetime(1, 4, 1, 2)
-    # and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.
-    # which is the first Sunday on or after Oct 25.
-    DSTEND = datetime.datetime(1, 10, 25, 1)
-    
-    ZERO = datetime.timedelta(0)
-    HOUR = datetime.timedelta(hours=1)
-
-    class USTimeZone(datetime.tzinfo):
-    
-        def __init__(self, hours, reprname, stdname, dstname):
-            self.stdoffset = datetime.timedelta(hours=hours)
-            self.reprname = reprname
-            self.stdname = stdname
-            self.dstname = dstname
-    
-        def __repr__(self):
-            return self.reprname
-    
-        def tzname(self, dt):
-            if self.dst(dt):
-                return self.dstname
-            else:
-                return self.stdname
-    
-        def utcoffset(self, dt):
-            return self.stdoffset + self.dst(dt)
-    
-        def dst(self, dt):
-            if dt is None or dt.tzinfo is None:
-                # An exception may be sensible here, in one or both cases.
-                # It depends on how you want to treat them.  The default
-                # fromutc() implementation (called by the default astimezone()
-                # implementation) passes a datetime with dt.tzinfo is self.
-                return ZERO
-            assert dt.tzinfo is self
-    
-            # Find first Sunday in April & the last in October.
-            start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
-            end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
-    
-            # Can't compare naive to aware objects, so strip the timezone from
-            # dt first.
-            if start <= dt.replace(tzinfo=None) < end:
-                return HOUR
-            else:
-                return ZERO
-
-    Eastern  = USTimeZone(-5, "Eastern",  "EST", "EDT")
-    filter.children[0].settzinfo(Eastern)
-    
-    print sqlcalendarquery(filter)

Added: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/queryfilter.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/queryfilter.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/queryfilter.py	2009-11-09 15:42:57 UTC (rev 4721)
@@ -0,0 +1,649 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Object model of CALDAV:filter element used in a calendar-query.
+"""
+
+__all__ = [
+    "Filter",
+]
+
+from twistedcaldav.caldavxml import caldav_namespace, CalDAVTimeZoneElement
+from twistedcaldav.dateops import timeRangesOverlap
+from twistedcaldav.ical import Component, Property, parse_date_or_datetime
+from twistedcaldav.log import Logger
+from vobject.icalendar import utc
+import datetime
+
+log = Logger()
+
+class FilterBase(object):
+    """
+    Determines which matching components are returned.
+    """
+
+    def __init__(self, xml_element):
+        self.xmlelement = xml_element
+
+    def match(self, item, access=None):
+        raise NotImplementedError
+
+    def valid(self, level=0):
+        raise NotImplementedError
+
+class Filter(FilterBase):
+    """
+    Determines which matching components are returned.
+    """
+
+    def __init__(self, xml_element):
+
+        super(Filter, self).__init__(xml_element)
+
+        # One comp-filter element must be present
+        if len(xml_element.children) != 1 or xml_element.children[0].qname() != (caldav_namespace, "comp-filter"):
+            raise ValueError("Invalid CALDAV:filter element: %s" % (xml_element,))
+        
+        self.child = ComponentFilter(xml_element.children[0])
+
+    def match(self, component, access=None):
+        """
+        Returns True if the given calendar component matches this filter, False
+        otherwise.
+        """
+        
+        # We only care about certain access restrictions.
+        if access not in (Component.ACCESS_CONFIDENTIAL, Component.ACCESS_RESTRICTED):
+            access = None
+
+        # We need to prepare ourselves for a time-range query by pre-calculating
+        # the set of instances up to the latest time-range limit. That way we can
+        # avoid having to do some form of recurrence expansion for each query sub-part.
+        maxend, isStartTime = self.getmaxtimerange()
+        if maxend:
+            if isStartTime:
+                if component.isRecurringUnbounded():
+                    # Unbounded recurrence is always within a start-only time-range
+                    instances = None
+                else:
+                    # Expand the instances up to infinity
+                    instances = component.expandTimeRanges(datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc), ignoreInvalidInstances=True)
+            else:
+                instances = component.expandTimeRanges(maxend, ignoreInvalidInstances=True)
+        else:
+            instances = None
+        self.child.setInstances(instances)
+
+        # <filter> contains exactly one <comp-filter>
+        return self.child.match(component, access)
+
+    def valid(self):
+        """
+        Indicate whether this filter element's structure is valid wrt iCalendar
+        data object model.
+        
+        @return: True if valid, False otherwise
+        """
+        
+        # Must have one child element for VCALENDAR
+        return self.child.valid(0)
+        
+    def settimezone(self, tzelement):
+        """
+        Set the default timezone to use with this query.
+        @param calendar: a L{Component} for the VCALENDAR containing the one
+            VTIMEZONE that we want
+        @return: the L{datetime.tzinfo} derived from the VTIMEZONE or utc.
+        """
+        assert tzelement is None or isinstance(tzelement, CalDAVTimeZoneElement)
+
+        if tzelement is not None:
+            calendar = tzelement.calendar()
+            if calendar is not None:
+                for subcomponent in calendar.subcomponents():
+                    if subcomponent.name() == "VTIMEZONE":
+                        # <filter> contains exactly one <comp-filter>
+                        tzinfo = subcomponent.gettzinfo()
+                        self.child.settzinfo(tzinfo)
+                        return tzinfo
+
+        # Default to using utc tzinfo
+        self.child.settzinfo(utc)
+        return utc
+
+    def getmaxtimerange(self):
+        """
+        Get the date farthest into the future in any time-range elements
+        """
+        
+        return self.child.getmaxtimerange(None, False)
+
+class FilterChildBase(FilterBase):
+    """
+    CalDAV filter element.
+    """
+
+    def __init__(self, xml_element):
+
+        super(FilterChildBase, self).__init__(xml_element)
+
+        qualifier = None
+        filters = []
+
+        for child in xml_element.children:
+            qname = child.qname()
+            
+            if qname in (
+                (caldav_namespace, "is-not-defined"),
+                (caldav_namespace, "time-range"),
+                (caldav_namespace, "text-match"),
+            ):
+                if qualifier is not None:
+                    raise ValueError("Only one of CalDAV:time-range, CalDAV:text-match allowed")
+                
+                if qname == (caldav_namespace, "is-not-defined"):
+                    qualifier = IsNotDefined(child)
+                elif qname == (caldav_namespace, "time-range"):
+                    qualifier = TimeRange(child)
+                elif qname == (caldav_namespace, "text-match"):
+                    qualifier = TextMatch(child)
+
+            elif qname == (caldav_namespace, "comp-filter"):
+                filters.append(ComponentFilter(child))
+            elif qname == (caldav_namespace, "prop-filter"):
+                filters.append(PropertyFilter(child))
+            elif qname == (caldav_namespace, "param-filter"):
+                filters.append(ParameterFilter(child))
+            else:
+                raise ValueError("Unknown child element: %s" % (qname,))
+
+        if qualifier and isinstance(qualifier, IsNotDefined) and (len(filters) != 0):
+            raise ValueError("No other tests allowed when CalDAV:is-not-defined is present")
+            
+        self.qualifier = qualifier
+        self.filters = filters
+        self.filter_name = xml_element.attributes["name"]
+        if isinstance(self.filter_name, unicode):
+            self.filter_name = self.filter_name.encode("utf-8")
+        self.defined = not self.qualifier or not isinstance(qualifier, IsNotDefined)
+
+    def match(self, item, access=None):
+        """
+        Returns True if the given calendar item (either a component, property or parameter value)
+        matches this filter, False otherwise.
+        """
+        
+        # Always return True for the is-not-defined case as the result of this will
+        # be negated by the caller
+        if not self.defined: return True
+
+        if self.qualifier and not self.qualifier.match(item, access): return False
+
+        if len(self.filters) > 0:
+            for filter in self.filters:
+                if filter._match(item, access):
+                    return True
+            return False
+        else:
+            return True
+
+class ComponentFilter (FilterChildBase):
+    """
+    Limits a search to only the chosen component types.
+    """
+
+    def match(self, item, access):
+        """
+        Returns True if the given calendar item (which is a component)
+        matches this filter, False otherwise.
+        This specialization uses the instance matching option of the time-range filter
+        to minimize instance expansion.
+        """
+
+        # Always return True for the is-not-defined case as the result of this will
+        # be negated by the caller
+        if not self.defined: return True
+
+        if self.qualifier and not self.qualifier.matchinstance(item, self.instances): return False
+
+        if len(self.filters) > 0:
+            for filter in self.filters:
+                if filter._match(item, access):
+                    return True
+            return False
+        else:
+            return True
+
+    def _match(self, component, access):
+        # At least one subcomponent must match (or is-not-defined is set)
+        for subcomponent in component.subcomponents():
+            # If access restrictions are in force, restrict matching to specific components only.
+            # In particular do not match VALARM.
+            if access and subcomponent.name() not in ("VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VTIMEZONE",):
+                continue
+            
+            # Try to match the component name
+            if isinstance(self.filter_name, str):
+                if subcomponent.name() != self.filter_name: continue
+            else:
+                if subcomponent.name() not in self.filter_name: continue
+            if self.match(subcomponent, access): break
+        else:
+            return not self.defined
+        return self.defined
+        
+    def setInstances(self, instances):
+        """
+        Give the list of instances to each comp-filter element.
+        @param instances: the list of instances.
+        """
+        self.instances = instances
+        for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
+            compfilter.setInstances(instances)
+        
+    def valid(self, level):
+        """
+        Indicate whether this filter element's structure is valid wrt iCalendar
+        data object model.
+        
+        @param level: the nesting level of this filter element, 0 being the top comp-filter.
+        @return:      True if valid, False otherwise
+        """
+        
+        # Check for time-range
+        timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
+
+        if level == 0:
+            # Must have VCALENDAR at the top
+            if (self.filter_name != "VCALENDAR") or timerange:
+                log.msg("Top-level comp-filter must be VCALENDAR, instead: %s" % (self.filter_name,))
+                return False
+        elif level == 1:
+            # Disallow VCALENDAR, VALARM, STANDARD, DAYLIGHT, AVAILABLE at the top, everything else is OK
+            if self.filter_name in ("VCALENDAR", "VALARM", "STANDARD", "DAYLIGHT", "AVAILABLE"):
+                log.msg("comp-filter wrong component type: %s" % (self.filter_name,))
+                return False
+            
+            # time-range only on VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY
+            if timerange and self.filter_name not in ("VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VAVAILABILITY"):
+                log.msg("time-range cannot be used with component %s" % (self.filter_name,))
+                return False
+        elif level == 2:
+            # Disallow VCALENDAR, VTIMEZONE, VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY at the top, everything else is OK
+            if (self.filter_name in ("VCALENDAR", "VTIMEZONE", "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VAVAILABILITY")):
+                log.msg("comp-filter wrong sub-component type: %s" % (self.filter_name,))
+                return False
+            
+            # time-range only on VALARM, AVAILABLE
+            if timerange and self.filter_name not in ("VALARM", "AVAILABLE",):
+                log.msg("time-range cannot be used with sub-component %s" % (self.filter_name,))
+                return False
+        else:
+            # Disallow all standard iCal components anywhere else
+            if (self.filter_name in ("VCALENDAR", "VTIMEZONE", "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VALARM", "STANDARD", "DAYLIGHT", "AVAILABLE")) or timerange:
+                log.msg("comp-filter wrong standard component type: %s" % (self.filter_name,))
+                return False
+        
+        # Test each property
+        for propfilter in [x for x in self.filters if isinstance(x, PropertyFilter)]:
+            if not propfilter.valid():
+                return False
+
+        # Test each component
+        for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
+            if not compfilter.valid(level + 1):
+                return False
+
+        # Test the time-range
+        if timerange:
+            if not self.qualifier.valid():
+                return False
+
+        return True
+
+    def settzinfo(self, tzinfo):
+        """
+        Set the default timezone to use with this query.
+        @param tzinfo: a L{datetime.tzinfo} to use.
+        """
+        
+        # Give tzinfo to any TimeRange we have
+        if isinstance(self.qualifier, TimeRange):
+            self.qualifier.settzinfo(tzinfo)
+        
+        # Pass down to sub components/properties
+        for x in self.filters:
+            x.settzinfo(tzinfo)
+
+    def getmaxtimerange(self, currentMaximum, currentIsStartTime):
+        """
+        Get the date furthest into the future in any time-range elements
+        
+        @param currentMaximum: current future value to compare with
+        @type currentMaximum: L{datetime.datetime}
+        """
+        
+        # Give tzinfo to any TimeRange we have
+        isStartTime = False
+        if isinstance(self.qualifier, TimeRange):
+            isStartTime = self.qualifier.end is None
+            compareWith = self.qualifier.start if isStartTime else self.qualifier.end
+            if currentMaximum is None or currentMaximum < compareWith:
+                currentMaximum = compareWith
+                currentIsStartTime = isStartTime
+        
+        # Pass down to sub components/properties
+        for x in self.filters:
+            currentMaximum, currentIsStartTime = x.getmaxtimerange(currentMaximum, currentIsStartTime)
+
+        return currentMaximum, currentIsStartTime
+
+class PropertyFilter (FilterChildBase):
+    """
+    Limits a search to specific properties.
+    """
+
+    def _match(self, component, access):
+        # When access restriction is in force, we need to only allow matches against the properties
+        # allowed by the access restriction level.
+        if access:
+            allowedProperties = Component.confidentialPropertiesMap.get(component.name(), None)
+            if allowedProperties and access == Component.ACCESS_RESTRICTED:
+                allowedProperties += Component.extraRestrictedProperties
+        else:
+            allowedProperties = None
+
+        # At least one property must match (or is-not-defined is set)
+        for property in component.properties():
+            # Apply access restrictions, if any.
+            if allowedProperties is not None and property.name() not in allowedProperties:
+                continue
+            if property.name() == self.filter_name and self.match(property, access): break
+        else:
+            return not self.defined
+        return self.defined
+
+    def valid(self):
+        """
+        Indicate whether this filter element's structure is valid wrt iCalendar
+        data object model.
+        
+        @return:      True if valid, False otherwise
+        """
+        
+        # Check for time-range
+        timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
+        
+        # time-range only on COMPLETED, CREATED, DTSTAMP, LAST-MODIFIED
+        if timerange and self.filter_name not in ("COMPLETED", "CREATED", "DTSTAMP", "LAST-MODIFIED"):
+            log.msg("time-range cannot be used with property %s" % (self.filter_name,))
+            return False
+
+        # Test the time-range
+        if timerange:
+            if not self.qualifier.valid():
+                return False
+
+        # No other tests
+        return True
+
+    def settzinfo(self, tzinfo):
+        """
+        Set the default timezone to use with this query.
+        @param tzinfo: a L{datetime.tzinfo} to use.
+        """
+        
+        # Give tzinfo to any TimeRange we have
+        if isinstance(self.qualifier, TimeRange):
+            self.qualifier.settzinfo(tzinfo)
+
+    def getmaxtimerange(self, currentMaximum, currentIsStartTime):
+        """
+        Get the date furthest into the future in any time-range elements
+        
+        @param currentMaximum: current future value to compare with
+        @type currentMaximum: L{datetime.datetime}
+        """
+        
+        # Give tzinfo to any TimeRange we have
+        isStartTime = False
+        if isinstance(self.qualifier, TimeRange):
+            isStartTime = self.qualifier.end is None
+            compareWith = self.qualifier.start if isStartTime else self.qualifier.end
+            if currentMaximum is None or currentMaximum < compareWith:
+                currentMaximum = compareWith
+                currentIsStartTime = isStartTime
+
+        return currentMaximum, currentIsStartTime
+
+class ParameterFilter (FilterChildBase):
+    """
+    Limits a search to specific parameters.
+    """
+
+    def _match(self, property, access):
+
+        # We have to deal with the problem that the 'Native' form of a property
+        # will be missing the TZID parameter due to the conversion performed. Converting
+        # to non-native for the entire calendar object causes problems elsewhere, so its
+        # best to do it here for this one special case.
+        if self.filter_name == "TZID":
+            transformed = property.transformAllFromNative()
+        else:
+            transformed = False
+
+        # At least one property must match (or is-not-defined is set)
+        result = not self.defined
+        for parameterName in property.params().keys():
+            if parameterName == self.filter_name and self.match(property.params()[parameterName], access):
+                result = self.defined
+                break
+
+        if transformed:
+            property.transformAllToNative()
+        return result
+
+class IsNotDefined (FilterBase):
+    """
+    Specifies that the named iCalendar item does not exist.
+    """
+
+    def match(self, component, access=None):
+        # Oddly, this needs always to return True so that it appears there is
+        # a match - but we then "negate" the result if is-not-defined is set.
+        # Actually this method should never be called as we special case the
+        # is-not-defined option.
+        return True
+
+class TextMatch (FilterBase):
+    """
+    Specifies a substring match on a property or parameter value.
+    (CalDAV-access-09, section 9.6.4)
+    """
+    def __init__(self, xml_element):
+
+        super(TextMatch, self).__init__(xml_element)
+
+        self.text = str(xml_element)
+        if "caseless" in xml_element.attributes:
+            caseless = xml_element.attributes["caseless"]
+            if caseless == "yes":
+                self.caseless = True
+            elif caseless == "no":
+                self.caseless = False
+        else:
+            self.caseless = True
+
+        if "negate-condition" in xml_element.attributes:
+            negate = xml_element.attributes["negate-condition"]
+            if negate == "yes":
+                self.negate = True
+            elif caseless == "no":
+                self.negate = False
+        else:
+            self.negate = False
+
+    def match(self, item, access):
+        """
+        Match the text for the item.
+        If the item is a property, then match the property value,
+        otherwise it may be a list of parameter values - try to match anyone of those
+        """
+        if item is None: return False
+
+        if isinstance(item, Property):
+            values = [item.value()]
+        else:
+            values = item
+
+        test = unicode(self.text, "utf-8")
+        if self.caseless:
+            test = test.lower()
+
+        def _textCompare(s):
+            if self.caseless:
+                if s.lower().find(test) != -1:
+                    return True, not self.negate
+            else:
+                if s.find(test) != -1:
+                    return True, not self.negate
+            return False, False
+
+        for value in values:
+            # NB Its possible that we have a text list value which appears as a Python list,
+            # so we need to check for that an iterate over the list.
+            if isinstance(value, list):
+                for subvalue in value:
+                    matched, result = _textCompare(subvalue)
+                    if matched:
+                        return result
+            else:
+                matched, result = _textCompare(value)
+                if matched:
+                    return result
+        
+        return self.negate
+
+class TimeRange (FilterBase):
+    """
+    Specifies a time for testing components against.
+    """
+
+    def __init__(self, xml_element):
+
+        super(TimeRange, self).__init__(xml_element)
+
+        # One of start or end must be present
+        if "start" not in xml_element.attributes and "end" not in xml_element.attributes:
+            raise ValueError("One of 'start' or 'end' must be present in CALDAV:time-range")
+        
+        self.start = parse_date_or_datetime(xml_element.attributes["start"]) if "start" in xml_element.attributes else None
+        self.end = parse_date_or_datetime(xml_element.attributes["end"]) if "end" in xml_element.attributes else None
+        self.tzinfo = None
+
+    def settzinfo(self, tzinfo):
+        """
+        Set the default timezone to use with this query.
+        @param tzinfo: a L{datetime.tzinfo} to use.
+        """
+        
+        # Give tzinfo to any TimeRange we have
+        self.tzinfo = tzinfo
+
+    def valid(self, level=0):
+        """
+        Indicate whether the time-range is valid (must be date-time in UTC).
+        
+        @return:      True if valid, False otherwise
+        """
+        
+        if self.start is not None and not isinstance(self.start, datetime.datetime):
+            log.msg("start attribute in <time-range> is not a date-time: %s" % (self.start,))
+            return False
+        if self.end is not None and not isinstance(self.end, datetime.datetime):
+            log.msg("end attribute in <time-range> is not a date-time: %s" % (self.end,))
+            return False
+        if self.start is not None and self.start.tzinfo != utc:
+            log.msg("start attribute in <time-range> is not UTC: %s" % (self.start,))
+            return False
+        if self.end is not None and self.end.tzinfo != utc:
+            log.msg("end attribute in <time-range> is not UTC: %s" % (self.end,))
+            return False
+
+        # No other tests
+        return True
+
+    def match(self, property, access=None):
+        """
+        NB This is only called when doing a time-range match on a property.
+        """
+        if property is None:
+            return False
+        else:
+            return property.containsTimeRange(self.start, self.end, self.tzinfo)
+
+    def matchinstance(self, component, instances):
+        """
+        Test whether this time-range element causes a match to the specified component
+        using the specified set of instances to determine the expanded time ranges.
+        @param component: the L{Component} to test.
+        @param instances: the list of expanded instances.
+        @return: True if the time-range query matches, False otherwise.
+        """
+        if component is None:
+            return False
+        
+        assert instances is not None or self.end is None, "Failure to expand instance for time-range filter: %r" % (self,)
+        
+        # Special case open-ended unbounded
+        if instances is None:
+            if component.getRecurrenceIDUTC() is None:
+                return True
+            else:
+                # See if the overridden component's start is past the start
+                start, _ignore_end = component.getEffectiveStartEnd()
+                if start is None:
+                    return True
+                else:
+                    return start >= self.start
+
+        # Handle alarms as a special case
+        alarms = (component.name() == "VALARM")
+        if alarms:
+            testcomponent = component._parent
+        else:
+            testcomponent = component
+            
+        for key in instances:
+            instance = instances[key]
+            
+            # First make sure components match
+            if not testcomponent.same(instance.component):
+                continue
+
+            if alarms:
+                # Get all the alarm triggers for this instance and test each one
+                triggers = instance.getAlarmTriggers()
+                for trigger in triggers:
+                    if timeRangesOverlap(trigger, None, self.start, self.end, self.tzinfo):
+                        return True
+            else:
+                # Regular instance overlap test
+                if timeRangesOverlap(instance.start, instance.end, self.start, self.end, self.tzinfo):
+                    return True
+
+        return False

Added: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/test/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/test/__init__.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/test/__init__.py	2009-11-09 15:42:57 UTC (rev 4721)
@@ -0,0 +1,19 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Tests for the twistedcaldav.query module.
+"""

Added: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/test/test_calendarquery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/test/test_calendarquery.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/test/test_calendarquery.py	2009-11-09 15:42:57 UTC (rev 4721)
@@ -0,0 +1,99 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav import caldavxml
+from twistedcaldav.query import queryfilter
+from twistedcaldav.query.calendarquery import sqlcalendarquery
+import datetime
+import twistedcaldav.test.util
+
+class Tests(twistedcaldav.test.util.TestCase):
+
+    def test_query(self):
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    *[caldavxml.TimeRange(**{"start":"20060605T160000Z", "end":"20060605T170000Z"})],
+                    **{"name":("VEVENT", "VFREEBUSY", "VAVAILABILITY")}
+                )],
+                **{"name":"VCALENDAR"}
+            )
+        )
+        filter = queryfilter.Filter(filter)
+    
+        # A complete implementation of current DST rules for major US time zones.
+        
+        def first_sunday_on_or_after(dt):
+            days_to_go = 6 - dt.weekday()
+            if days_to_go:
+                dt += datetime.timedelta(days_to_go)
+            return dt
+        
+        # In the US, DST starts at 2am (standard time) on the first Sunday in April.
+        DSTSTART = datetime.datetime(1, 4, 1, 2)
+        # and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.
+        # which is the first Sunday on or after Oct 25.
+        DSTEND = datetime.datetime(1, 10, 25, 1)
+        
+        ZERO = datetime.timedelta(0)
+        HOUR = datetime.timedelta(hours=1)
+    
+        class USTimeZone(datetime.tzinfo):
+        
+            def __init__(self, hours, reprname, stdname, dstname):
+                self.stdoffset = datetime.timedelta(hours=hours)
+                self.reprname = reprname
+                self.stdname = stdname
+                self.dstname = dstname
+        
+            def __repr__(self):
+                return self.reprname
+        
+            def tzname(self, dt):
+                if self.dst(dt):
+                    return self.dstname
+                else:
+                    return self.stdname
+        
+            def utcoffset(self, dt):
+                return self.stdoffset + self.dst(dt)
+        
+            def dst(self, dt):
+                if dt is None or dt.tzinfo is None:
+                    # An exception may be sensible here, in one or both cases.
+                    # It depends on how you want to treat them.  The default
+                    # fromutc() implementation (called by the default astimezone()
+                    # implementation) passes a datetime with dt.tzinfo is self.
+                    return ZERO
+                assert dt.tzinfo is self
+        
+                # Find first Sunday in April & the last in October.
+                start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
+                end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
+        
+                # Can't compare naive to aware objects, so strip the timezone from
+                # dt first.
+                if start <= dt.replace(tzinfo=None) < end:
+                    return HOUR
+                else:
+                    return ZERO
+    
+        Eastern  = USTimeZone(-5, "Eastern",  "EST", "EDT")
+        filter.child.settzinfo(Eastern)
+        
+        print sqlcalendarquery(filter)
+        
\ No newline at end of file

Added: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/test/test_queryfilter.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/test/test_queryfilter.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/query/test/test_queryfilter.py	2009-11-09 15:42:57 UTC (rev 4721)
@@ -0,0 +1,77 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav import caldavxml
+from twistedcaldav.query import queryfilter
+import twistedcaldav.test.util
+
+class Tests(twistedcaldav.test.util.TestCase):
+
+    def test_allQuery(self):
+
+        xml_element = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                **{"name":"VCALENDAR"}
+            )
+        )
+
+        queryfilter.Filter(xml_element)
+        
+    def test_simpleSummaryRangeQuery(self):
+
+        xml_element = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                caldavxml.ComponentFilter(
+                    caldavxml.PropertyFilter(
+                        caldavxml.TextMatch.fromString("test"),
+                        **{"name":"SUMMARY",}
+                    ),
+                    **{"name":"VEVENT"}
+                ),
+                **{"name":"VCALENDAR"}
+            )
+        )
+
+        queryfilter.Filter(xml_element)
+        
+    def test_simpleTimeRangeQuery(self):
+
+        xml_element = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                caldavxml.ComponentFilter(
+                    caldavxml.TimeRange(**{"start":"20060605T160000Z", "end":"20060605T170000Z"}),
+                    **{"name":"VEVENT"}
+                ),
+                **{"name":"VCALENDAR"}
+            )
+        )
+
+        queryfilter.Filter(xml_element)
+        
+    def test_multipleTimeRangeQuery(self):
+
+        xml_element = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                caldavxml.ComponentFilter(
+                    caldavxml.TimeRange(**{"start":"20060605T160000Z", "end":"20060605T170000Z"}),
+                    **{"name":("VEVENT", "VFREEBUSY", "VAVAILABILITY")}
+                ),
+                **{"name":"VCALENDAR"}
+            )
+        )
+
+        queryfilter.Filter(xml_element)
+        
\ No newline at end of file

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/scheduling/processing.py	2009-11-09 15:42:26 UTC (rev 4720)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/scheduling/processing.py	2009-11-09 15:42:57 UTC (rev 4721)
@@ -29,7 +29,7 @@
 from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
 from twistedcaldav.scheduling.itip import iTipProcessing, iTIPRequestStatus
 from twistedcaldav.scheduling.utils import getCalendarObjectForPrincipals
-from vobject.icalendar import utc
+from vobject.icalendar import dateTimeToString, utc
 import datetime
 import time
 
@@ -483,9 +483,10 @@
                                 dt = dt.replace(tzinfo=tzinfo).astimezone(utc)
                             return dt
                         
-                        tr = caldavxml.TimeRange(start="20000101", end="20000101")
-                        tr.start = makeTimedUTC(instance.start)
-                        tr.end = makeTimedUTC(instance.end)
+                        tr = caldavxml.TimeRange(
+                            start=dateTimeToString(makeTimedUTC(instance.start)),
+                            end=dateTimeToString(makeTimedUTC(instance.end)),
+                        )
 
                         yield report_common.generateFreeBusyInfo(self.request, testcal, fbinfo, tr, 0, uid)
                         

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/test/test_calendarquery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/test/test_calendarquery.py	2009-11-09 15:42:26 UTC (rev 4720)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/test/test_calendarquery.py	2009-11-09 15:42:57 UTC (rev 4721)
@@ -30,6 +30,7 @@
 from twistedcaldav import caldavxml
 from twistedcaldav import ical
 from twistedcaldav.index import db_basename
+from twistedcaldav.query import queryfilter
 
 class CalendarQuery (twistedcaldav.test.util.TestCase):
     """
@@ -116,7 +117,7 @@
                             cal = property.calendar()
                             instances = cal.expandTimeRanges(query_timerange.end)
                             vevents = [x for x in cal.subcomponents() if x.name() == "VEVENT"]
-                            if not query_timerange.matchinstance(vevents[0], instances):
+                            if not queryfilter.TimeRange(query_timerange).matchinstance(vevents[0], instances):
                                 self.fail("REPORT property %r returned calendar %s outside of request time range %r"
                                           % (property, property.calendar, query_timerange))
 

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/test/test_index.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/test/test_index.py	2009-11-09 15:42:26 UTC (rev 4720)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/test/test_index.py	2009-11-09 15:42:57 UTC (rev 4721)
@@ -16,22 +16,21 @@
 
 from twisted.internet import reactor
 from twisted.internet.task import deferLater
-
+from twistedcaldav import caldavxml
+from twistedcaldav.caldavxml import TimeRange
 from twistedcaldav.ical import Component
 from twistedcaldav.index import Index, default_future_expansion_duration,\
     maximum_future_expansion_duration, IndexedSearchException,\
     AbstractCalendarIndex
 from twistedcaldav.index import ReservationError, MemcachedUIDReserver
 from twistedcaldav.instance import InvalidOverriddenInstanceError
+from twistedcaldav.query import queryfilter
 from twistedcaldav.test.util import InMemoryMemcacheProtocol
-import twistedcaldav.test.util
-from twistedcaldav import caldavxml
-from twistedcaldav.caldavxml import TimeRange
 from vobject.icalendar import utc
-import sqlite3
-
 import datetime
 import os
+import sqlite3
+import twistedcaldav.test.util
 
 class SQLIndexTests (twistedcaldav.test.util.TestCase):
     """
@@ -414,17 +413,18 @@
 
             # Create fake filter element to match time-range
             filter =  caldavxml.Filter(
-                  caldavxml.ComponentFilter(
-                      caldavxml.ComponentFilter(
-                          TimeRange(
-                              start=trstart,
-                              end=trend,
-                          ),
-                          name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
-                      ),
-                      name="VCALENDAR",
-                   )
-              )
+                caldavxml.ComponentFilter(
+                    caldavxml.ComponentFilter(
+                        TimeRange(
+                            start=trstart,
+                            end=trend,
+                        ),
+                        name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
+                    ),
+                    name="VCALENDAR",
+                )
+            )
+            filter = queryfilter.Filter(filter)
 
             resources = self.db.indexedSearch(filter, fbtype=True)
             index_results = set()

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/test/test_xml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/test/test_xml.py	2009-11-09 15:42:26 UTC (rev 4720)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/test/test_xml.py	2009-11-09 15:42:57 UTC (rev 4721)
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
+from twistedcaldav.query import queryfilter
 
 import os
 
@@ -41,11 +42,13 @@
             if has: no = "no "
             else:   no = ""
 
-            if has != ComponentFilter(
+            if has != queryfilter.ComponentFilter(
                 ComponentFilter(
-                    name=component_name
-                ),
-                name="VCALENDAR"
+                    ComponentFilter(
+                        name=component_name
+                    ),
+                    name="VCALENDAR"
+                )
             ).match(self.calendar, None):
                 self.fail("Calendar has %s%s?" % (no, component_name))
 
@@ -60,14 +63,16 @@
             if has: no = "no "
             else:   no = ""
 
-            if has != ComponentFilter(
+            if has != queryfilter.ComponentFilter(
                 ComponentFilter(
-                    PropertyFilter(
-                        name=property_name
+                    ComponentFilter(
+                        PropertyFilter(
+                            name=property_name
+                        ),
+                        name="VEVENT"
                     ),
-                    name="VEVENT"
-                ),
-                name="VCALENDAR"
+                    name="VCALENDAR"
+                )
             ).match(self.calendar, None):
                 self.fail("Calendar has %sVEVENT with %s?" % (no, property_name))
 
@@ -90,15 +95,17 @@
             if has: no = "no "
             else:   no = ""
 
-            if has != ComponentFilter(
+            if has != queryfilter.ComponentFilter(
                 ComponentFilter(
-                    PropertyFilter(
-                        TextMatch.fromString(uid, caseless=caseless),
-                        name="UID"
+                    ComponentFilter(
+                        PropertyFilter(
+                            TextMatch.fromString(uid, caseless=caseless),
+                            name="UID"
+                        ),
+                        name="VEVENT"
                     ),
-                    name="VEVENT"
-                ),
-                name="VCALENDAR"
+                    name="VCALENDAR"
+                )
             ).match(self.calendar, None):
                 self.fail("Calendar has %sVEVENT with UID %s? (caseless=%s)" % (no, uid, caseless))
 
@@ -127,13 +134,17 @@
             if has: no = "no "
             else:   no = ""
 
-            if has != Filter(ComponentFilter(
-                ComponentFilter(
-                    TimeRange(start=start, end=end),
-                    name="VEVENT"
-                ),
-                name="VCALENDAR"
-            )).match(self.calendar):
+            if has != queryfilter.Filter(
+                Filter(
+                    ComponentFilter(
+                        ComponentFilter(
+                            TimeRange(start=start, end=end),
+                            name="VEVENT"
+                        ),
+                        name="VCALENDAR"
+                    )
+                )
+            ).match(self.calendar):
                 self.fail("Calendar has %sVEVENT with timerange %s?" % (no, (start, end)))
 
     test_TimeRange.todo = "recurrence expansion"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20091109/670eb3d5/attachment-0001.html>


More information about the calendarserver-changes mailing list