[CalendarServer-changes] [12167] CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav

source_changes at macosforge.org source_changes at macosforge.org
Wed Mar 12 11:24:59 PDT 2014


Revision: 12167
          http://trac.calendarserver.org//changeset/12167
Author:   cdaboo at apple.com
Date:     2013-12-20 08:02:05 -0800 (Fri, 20 Dec 2013)
Log Message:
-----------
Checkpoint: cross-pod searching.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/builder.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/filter.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/test_filter.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/filter.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/test_filter.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/builder.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/builder.py	2013-12-20 15:57:11 UTC (rev 12166)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/builder.py	2013-12-20 16:02:05 UTC (rev 12167)
@@ -94,7 +94,7 @@
     logical = expression.andExpression if compfilter.filter_test == "allof" else expression.orExpression
 
     expressions = []
-    if isinstance(compfilter.filter_name, str):
+    if isinstance(compfilter.filter_name, str) or isinstance(compfilter.filter_name, unicode):
         expressions.append(expression.isExpression(fields["TYPE"], compfilter.filter_name, True))
     else:
         expressions.append(expression.inExpression(fields["TYPE"], compfilter.filter_name, True))

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/filter.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/filter.py	2013-12-20 15:57:11 UTC (rev 12166)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/filter.py	2013-12-20 16:02:05 UTC (rev 12167)
@@ -39,10 +39,44 @@
     Determines which matching components are returned.
     """
 
+    serialized_name = None
+    deserialize_names = {}
+
+    @classmethod
+    def serialize_register(cls, register):
+        cls.deserialize_names[register.serialized_name] = register
+
+
     def __init__(self, xml_element):
-        self.xmlelement = xml_element
+        pass
 
 
+    @classmethod
+    def deserialize(cls, data):
+        """
+        Convert a JSON compatible serialization of this object into the actual object.
+        """
+        obj = cls.deserialize_names[data["type"]](None)
+        obj._deserialize(data)
+        return obj
+
+
+    def _deserialize(self, data):
+        """
+        Convert a JSON compatible serialization of this object into the actual object.
+        """
+        pass
+
+
+    def serialize(self):
+        """
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        """
+        return {
+            "type": self.serialized_name,
+        }
+
+
     def match(self, item, access=None):
         raise NotImplementedError
 
@@ -57,9 +91,13 @@
     Determines which matching components are returned.
     """
 
+    serialized_name = "Filter"
+
     def __init__(self, xml_element):
 
         super(Filter, self).__init__(xml_element)
+        if xml_element is None:
+            return
 
         # One comp-filter element must be present
         if len(xml_element.children) != 1 or xml_element.children[0].qname() != (caldav_namespace, "comp-filter"):
@@ -68,6 +106,24 @@
         self.child = ComponentFilter(xml_element.children[0])
 
 
+    def _deserialize(self, data):
+        """
+        Convert a JSON compatible serialization of this object into the actual object.
+        """
+        self.child = FilterBase.deserialize(data["child"])
+
+
+    def serialize(self):
+        """
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        """
+        result = super(Filter, self).serialize()
+        result.update({
+            "child": self.child.serialize(),
+        })
+        return result
+
+
     def match(self, component, access=None):
         """
         Returns True if the given calendar component matches this filter, False
@@ -148,8 +204,10 @@
 
         return self.child.getmintimerange(None, False)
 
+FilterBase.serialize_register(Filter)
 
 
+
 class FilterChildBase(FilterBase):
     """
     CalDAV filter element.
@@ -158,6 +216,8 @@
     def __init__(self, xml_element):
 
         super(FilterChildBase, self).__init__(xml_element)
+        if xml_element is None:
+            return
 
         qualifier = None
         filters = []
@@ -205,6 +265,32 @@
         self.filter_test = filter_test
 
 
+    def _deserialize(self, data):
+        """
+        Convert a JSON compatible serialization of this object into the actual object.
+        """
+        self.qualifier = FilterBase.deserialize(data["qualifier"]) if data["qualifier"] else None
+        self.filters = [FilterBase.deserialize(filter) for filter in data["filters"]]
+        self.filter_name = data["filter_name"]
+        self.defined = data["defined"]
+        self.filter_test = data["filter_test"]
+
+
+    def serialize(self):
+        """
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        """
+        result = super(FilterChildBase, self).serialize()
+        result.update({
+            "qualifier": self.qualifier.serialize() if self.qualifier else None,
+            "filters": [filter.serialize() for filter in self.filters],
+            "filter_name": self.filter_name,
+            "defined": self.defined,
+            "filter_test": self.filter_test,
+        })
+        return result
+
+
     def match(self, item, access=None):
         """
         Returns True if the given calendar item (either a component, property or parameter value)
@@ -235,6 +321,8 @@
     Limits a search to only the chosen component types.
     """
 
+    serialized_name = "ComponentFilter"
+
     def match(self, item, access):
         """
         Returns True if the given calendar item (which is a component)
@@ -414,13 +502,17 @@
 
         return currentMinimum, currentIsEndTime
 
+FilterBase.serialize_register(ComponentFilter)
 
 
+
 class PropertyFilter (FilterChildBase):
     """
     Limits a search to specific properties.
     """
 
+    serialized_name = "PropertyFilter"
+
     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.
@@ -516,13 +608,17 @@
 
         return currentMinimum, currentIsEndTime
 
+FilterBase.serialize_register(PropertyFilter)
 
 
+
 class ParameterFilter (FilterChildBase):
     """
     Limits a search to specific parameters.
     """
 
+    serialized_name = "ParameterFilter"
+
     def _match(self, property, access):
 
         # At least one parameter must match (or is-not-defined is set)
@@ -534,13 +630,17 @@
 
         return result
 
+FilterBase.serialize_register(ParameterFilter)
 
 
+
 class IsNotDefined (FilterBase):
     """
     Specifies that the named iCalendar item does not exist.
     """
 
+    serialized_name = "IsNotDefined"
+
     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.
@@ -548,6 +648,7 @@
         # is-not-defined option.
         return True
 
+FilterBase.serialize_register(IsNotDefined)
 
 
 class TextMatch (FilterBase):
@@ -555,9 +656,13 @@
     Specifies a substring match on a property or parameter value.
     (CalDAV-access-09, section 9.6.4)
     """
+    serialized_name = "TextMatch"
+
     def __init__(self, xml_element):
 
         super(TextMatch, self).__init__(xml_element)
+        if xml_element is None:
+            return
 
         self.text = str(xml_element)
         if "caseless" in xml_element.attributes:
@@ -591,6 +696,30 @@
             self.match_type = "contains"
 
 
+    def _deserialize(self, data):
+        """
+        Convert a JSON compatible serialization of this object into the actual object.
+        """
+        self.text = data["text"]
+        self.caseless = data["caseless"]
+        self.negate = data["negate"]
+        self.match_type = data["match_type"]
+
+
+    def serialize(self):
+        """
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        """
+        result = super(TextMatch, self).serialize()
+        result.update({
+            "text": self.text,
+            "caseless": self.caseless,
+            "negate": self.negate,
+            "match_type": self.match_type,
+        })
+        return result
+
+
     def match(self, item, access):
         """
         Match the text for the item.
@@ -637,16 +766,22 @@
 
         return self.negate
 
+FilterBase.serialize_register(TextMatch)
 
 
+
 class TimeRange (FilterBase):
     """
     Specifies a time for testing components against.
     """
 
+    serialized_name = "TimeRange"
+
     def __init__(self, xml_element):
 
         super(TimeRange, self).__init__(xml_element)
+        if xml_element is None:
+            return
 
         # One of start or end must be present
         if "start" not in xml_element.attributes and "end" not in xml_element.attributes:
@@ -657,6 +792,28 @@
         self.tzinfo = None
 
 
+    def _deserialize(self, data):
+        """
+        Convert a JSON compatible serialization of this object into the actual object.
+        """
+        self.start = DateTime.parseText(data["start"]) if data["start"] else None
+        self.end = DateTime.parseText(data["end"]) if data["end"] else None
+        self.tzinfo = Timezone(tzid=data["tzinfo"]) if data["tzinfo"] else None
+
+
+    def serialize(self):
+        """
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        """
+        result = super(TimeRange, self).serialize()
+        result.update({
+            "start": self.start.getText() if self.start else None,
+            "end": self.end.getText() if self.end else None,
+            "tzinfo": self.tzinfo.getTimezoneID() if self.tzinfo else None,
+        })
+        return result
+
+
     def settzinfo(self, tzinfo):
         """
         Set the default timezone to use with this query.
@@ -752,3 +909,5 @@
                     return True
 
         return False
+
+FilterBase.serialize_register(TimeRange)

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/test_filter.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/test_filter.py	2013-12-20 15:57:11 UTC (rev 12166)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/test_filter.py	2013-12-20 16:02:05 UTC (rev 12167)
@@ -24,12 +24,14 @@
 
 from txdav.caldav.datastore.index_file import sqlcalendarquery
 from txdav.caldav.datastore.query.builder import buildExpression
-from txdav.caldav.datastore.query.filter import Filter
+from txdav.caldav.datastore.query.filter import Filter, FilterBase, TimeRange, \
+    PropertyFilter, TextMatch
 from txdav.caldav.datastore.query.generator import CalDAVSQLQueryGenerator
 from txdav.common.datastore.sql_tables import schema
 
 from dateutil.tz import tzutc
 import datetime
+from twistedcaldav.ical import Component
 
 class TestQueryFilter(TestCase):
 
@@ -218,3 +220,216 @@
         self.assertTrue(sql.find("TIMESPAN") == -1)
         self.assertTrue(sql.find("TRANSPARENCY") == -1)
         self.assertTrue("VEVENT" in args)
+
+
+
+class TestQueryFilterSerialize(TestCase):
+
+    def setUp(self):
+        super(TestQueryFilterSerialize, self).setUp()
+        TimezoneCache.create()
+
+
+    def test_query(self):
+        """
+        Basic query test - no time range
+        """
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    **{"name":("VEVENT", "VFREEBUSY", "VAVAILABILITY")}
+                )],
+                **{"name": "VCALENDAR"}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid="America/New_York"))
+        j = filter.serialize()
+        self.assertEqual(j["type"], "Filter")
+
+        f = FilterBase.deserialize(j)
+        self.assertTrue(isinstance(f, Filter))
+
+
+    def test_timerange_query(self):
+        """
+        Basic query test with time range
+        """
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    *[caldavxml.TimeRange(**{"start":"20060605T160000Z", "end":"20060605T170000Z"})],
+                    **{"name":("VEVENT", "VFREEBUSY", "VAVAILABILITY")}
+                )],
+                **{"name": "VCALENDAR"}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid="America/New_York"))
+        j = filter.serialize()
+        self.assertEqual(j["type"], "Filter")
+
+        f = FilterBase.deserialize(j)
+        self.assertTrue(isinstance(f, Filter))
+        self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
+        self.assertTrue(isinstance(f.child.filters[0].qualifier.tzinfo, Timezone))
+        self.assertEqual(f.child.filters[0].qualifier.tzinfo.getTimezoneID(), "America/New_York")
+
+
+    def test_query_not_extended(self):
+        """
+        Basic query test with time range
+        """
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[
+                    caldavxml.ComponentFilter(
+                        **{"name":("VEVENT")}
+                    ),
+                    caldavxml.ComponentFilter(
+                        **{"name":("VTODO")}
+                    ),
+                ],
+                **{"name": "VCALENDAR"}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid="America/New_York"))
+        j = filter.serialize()
+        self.assertEqual(j["type"], "Filter")
+
+        f = FilterBase.deserialize(j)
+        self.assertTrue(isinstance(f, Filter))
+        self.assertEqual(len(f.child.filters), 2)
+
+
+    def test_query_extended(self):
+        """
+        Basic query test with time range
+        """
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[
+                    caldavxml.ComponentFilter(
+                        *[caldavxml.TimeRange(**{"start":"20060605T160000Z", })],
+                        **{"name":("VEVENT")}
+                    ),
+                    caldavxml.ComponentFilter(
+                        **{"name":("VTODO")}
+                    ),
+                ],
+                **{"name": "VCALENDAR", "test": "anyof"}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid="America/New_York"))
+        j = filter.serialize()
+        self.assertEqual(j["type"], "Filter")
+
+        f = FilterBase.deserialize(j)
+        self.assertTrue(isinstance(f, Filter))
+        self.assertEqual(len(f.child.filters), 2)
+        self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
+
+
+    def test_query_text(self):
+        """
+        Basic query test with time range
+        """
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[
+                    caldavxml.ComponentFilter(
+                        caldavxml.PropertyFilter(
+                            caldavxml.TextMatch.fromString("1234", False),
+                            name="UID",
+                        ),
+                        **{"name":("VEVENT")}
+                    ),
+                ],
+                **{"name": "VCALENDAR", "test": "anyof"}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid="America/New_York"))
+        j = filter.serialize()
+        self.assertEqual(j["type"], "Filter")
+
+        f = FilterBase.deserialize(j)
+        self.assertTrue(isinstance(f, Filter))
+        self.assertTrue(isinstance(f.child.filters[0].filters[0], PropertyFilter))
+        self.assertTrue(isinstance(f.child.filters[0].filters[0].qualifier, TextMatch))
+        self.assertEqual(f.child.filters[0].filters[0].qualifier.text, "1234")
+
+
+
+class TestQueryFilterMatch(TestCase):
+
+    def setUp(self):
+        super(TestQueryFilterMatch, self).setUp()
+        TimezoneCache.create()
+
+
+    def test_vlarm_undefined(self):
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    *[caldavxml.ComponentFilter(
+                        caldavxml.IsNotDefined(),
+                        **{"name":"VALARM"}
+                    )],
+                    **{"name":"VEVENT"}
+                )],
+                **{"name": "VCALENDAR"}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid="America/New_York"))
+
+        self.assertFalse(filter.match(
+            Component.fromString("""BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VTIMEZONE
+LAST-MODIFIED:20040110T032845Z
+TZID:US/Eastern
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20051222T210412Z
+CREATED:20060102T150000Z
+DTSTART;TZID=US/Eastern:20130102T100000
+DURATION:PT1H
+RRULE:FREQ=DAILY;COUNT=5
+SUMMARY:event 5
+UID:945113826375CBB89184DC36 at ninevah.local
+CATEGORIES:cool,hot
+CATEGORIES:warm
+BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+"""
+        )))

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py	2013-12-20 15:57:11 UTC (rev 12166)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py	2013-12-20 16:02:05 UTC (rev 12167)
@@ -1349,6 +1349,13 @@
             C{name} is the resource name, C{uid} is the resource UID.
         """
 
+        # We might be passed an L{Filter} or a serialization of one
+        if isinstance(filter, dict):
+            try:
+                filter = Filter.deserialize(filter)
+            except Exception:
+                file = None
+
         # Make sure we have a proper Filter element and get the partial SQL statement to use.
         sql_stmt = self._sqlquery(filter, useruid, fbtype)
 

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/filter.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/filter.py	2013-12-20 15:57:11 UTC (rev 12166)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/filter.py	2013-12-20 16:02:05 UTC (rev 12167)
@@ -34,10 +34,44 @@
     Determines which matching components are returned.
     """
 
+    serialized_name = None
+    deserialize_names = {}
+
+    @classmethod
+    def serialize_register(cls, register):
+        cls.deserialize_names[register.serialized_name] = register
+
+
     def __init__(self, xml_element):
-        self.xmlelement = xml_element
+        pass
 
 
+    @classmethod
+    def deserialize(cls, data):
+        """
+        Convert a JSON compatible serialization of this object into the actual object.
+        """
+        obj = cls.deserialize_names[data["type"]](None)
+        obj._deserialize(data)
+        return obj
+
+
+    def _deserialize(self, data):
+        """
+        Convert a JSON compatible serialization of this object into the actual object.
+        """
+        pass
+
+
+    def serialize(self):
+        """
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        """
+        return {
+            "type": self.serialized_name,
+        }
+
+
     def match(self, item, access=None):
         raise NotImplementedError
 
@@ -52,9 +86,13 @@
     Determines which matching components are returned.
     """
 
+    serialized_name = "Filter"
+
     def __init__(self, xml_element):
 
         super(Filter, self).__init__(xml_element)
+        if xml_element is None:
+            return
 
         filter_test = xml_element.attributes.get("test", "anyof")
         if filter_test not in ("anyof", "allof"):
@@ -65,6 +103,26 @@
         self.children = [PropertyFilter(child) for child in xml_element.children]
 
 
+    def _deserialize(self, data):
+        """
+        Convert a JSON compatible serialization of this object into the actual object.
+        """
+        self.filter_test = data["filter_test"]
+        self.child = [FilterBase.deserialize(child) for child in data["children"]]
+
+
+    def serialize(self):
+        """
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        """
+        result = super(Filter, self).serialize()
+        result.update({
+            "filter_test": self.filter_test,
+            "children": [child.serialize() for child in self.children],
+        })
+        return result
+
+
     def match(self, vcard):
         """
         Returns True if the given address property matches this filter, False
@@ -96,8 +154,10 @@
         else:
             return True
 
+FilterBase.serialize_register(Filter)
 
 
+
 class FilterChildBase(FilterBase):
     """
     CardDAV filter element.
@@ -106,6 +166,8 @@
     def __init__(self, xml_element):
 
         super(FilterChildBase, self).__init__(xml_element)
+        if xml_element is None:
+            return
 
         qualifier = None
         filters = []
@@ -147,6 +209,32 @@
         self.defined = not self.qualifier or not isinstance(qualifier, IsNotDefined)
 
 
+    def _deserialize(self, data):
+        """
+        Convert a JSON compatible serialization of this object into the actual object.
+        """
+        self.propfilter_test = data["propfilter_test"]
+        self.qualifier = FilterBase.deserialize(data["qualifier"]) if data["qualifier"] else None
+        self.filters = [FilterBase.deserialize(filter) for filter in data["filters"]]
+        self.filter_name = data["filter_name"]
+        self.defined = data["defined"]
+
+
+    def serialize(self):
+        """
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        """
+        result = super(FilterChildBase, self).serialize()
+        result.update({
+            "propfilter_test": self.propfilter_test,
+            "qualifier": self.qualifier.serialize() if self.qualifier else None,
+            "filters": [filter.serialize() for filter in self.filters],
+            "filter_name": self.filter_name,
+            "defined": self.defined,
+        })
+        return result
+
+
     def match(self, item):
         """
         Returns True if the given address book item (either a property or parameter value)
@@ -177,6 +265,8 @@
     Limits a search to specific properties.
     """
 
+    serialized_name = "PropertyFilter"
+
     def _match(self, vcard):
         # At least one property must match (or is-not-defined is set)
         for property in vcard.properties():
@@ -198,13 +288,17 @@
         # No tests
         return True
 
+FilterBase.serialize_register(PropertyFilter)
 
 
+
 class ParameterFilter (FilterChildBase):
     """
     Limits a search to specific parameters.
     """
 
+    serialized_name = "ParameterFilter"
+
     def _match(self, property):
 
         # At least one parameter must match (or is-not-defined is set)
@@ -216,13 +310,17 @@
 
         return result
 
+FilterBase.serialize_register(ParameterFilter)
 
 
+
 class IsNotDefined (FilterBase):
     """
     Specifies that the named iCalendar item does not exist.
     """
 
+    serialized_name = "IsNotDefined"
+
     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.
@@ -230,15 +328,21 @@
         # is-not-defined option.
         return True
 
+FilterBase.serialize_register(IsNotDefined)
 
 
+
 class TextMatch (FilterBase):
     """
     Specifies a substring match on a property or parameter value.
     """
+    serialized_name = "TextMatch"
+
     def __init__(self, xml_element):
 
         super(TextMatch, self).__init__(xml_element)
+        if xml_element is None:
+            return
 
         self.text = str(xml_element)
 
@@ -268,6 +372,30 @@
             self.match_type = "contains"
 
 
+    def _deserialize(self, data):
+        """
+        Convert a JSON compatible serialization of this object into the actual object.
+        """
+        self.text = data["text"]
+        self.collation = data["collation"]
+        self.negate = data["negate"]
+        self.match_type = data["match_type"]
+
+
+    def serialize(self):
+        """
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        """
+        result = super(TextMatch, self).serialize()
+        result.update({
+            "text": self.text,
+            "collation": self.collation,
+            "negate": self.negate,
+            "match_type": self.match_type,
+        })
+        return result
+
+
     def _match(self, item):
         """
         Match the text for the item.
@@ -312,3 +440,5 @@
                     return not self.negate
 
         return self.negate
+
+FilterBase.serialize_register(TextMatch)

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/test_filter.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/test_filter.py	2013-12-20 15:57:11 UTC (rev 12166)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/test_filter.py	2013-12-20 16:02:05 UTC (rev 12167)
@@ -20,7 +20,7 @@
 
 from twistedcaldav import carddavxml
 
-from txdav.carddav.datastore.query.filter import Filter
+from txdav.carddav.datastore.query.filter import Filter, FilterBase
 from txdav.common.datastore.sql_tables import schema
 from txdav.carddav.datastore.query.builder import buildExpression
 from txdav.common.datastore.query.generator import SQLQueryGenerator
@@ -72,3 +72,25 @@
 
         self.assertEqual(sql, " from RESOURCE where RESOURCE.UID GLOB :1")
         self.assertEqual(args, ["*Example*"])
+
+
+
+class TestQueryFilterSerialize(TestCase):
+
+    def test_query(self):
+        """
+        Basic query test - no time range
+        """
+
+        filter = carddavxml.Filter(
+            *[carddavxml.PropertyFilter(
+                carddavxml.TextMatch.fromString("Example"),
+                **{"name":"UID"}
+            )]
+        )
+        filter = Filter(filter)
+        j = filter.serialize()
+        self.assertEqual(j["type"], "Filter")
+
+        f = FilterBase.deserialize(j)
+        self.assertTrue(isinstance(f, Filter))

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py	2013-12-20 15:57:11 UTC (rev 12166)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py	2013-12-20 16:02:05 UTC (rev 12167)
@@ -806,6 +806,13 @@
             C{name} is the resource name, C{uid} is the resource UID.
         """
 
+        # We might be passed an L{Filter} or a serialization of one
+        if isinstance(filter, dict):
+            try:
+                filter = Filter.deserialize(filter)
+            except Exception:
+                filter = None
+
         # Make sure we have a proper Filter element and get the partial SQL statement to use.
         sql_stmt = self._sqlquery(filter)
 

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py	2013-12-20 15:57:11 UTC (rev 12166)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py	2013-12-20 16:02:05 UTC (rev 12167)
@@ -713,6 +713,7 @@
 PoddingConduit._make_simple_homechild_action("resourcenamessincerevision", "resourceNamesSinceRevision", transform_send=PoddingConduit._to_tuple)
 PoddingConduit._make_simple_homechild_action("resourceuidforname", "resourceUIDForName")
 PoddingConduit._make_simple_homechild_action("resourcenameforuid", "resourceNameForUID")
+PoddingConduit._make_simple_homechild_action("search", "search")
 
 # Calls on L{CommonObjectResource} objects
 PoddingConduit._make_simple_object_action("loadallobjects", "loadAllObjects", transform_recv=PoddingConduit._to_externalize)

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py	2013-12-20 15:57:11 UTC (rev 12166)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py	2013-12-20 16:02:05 UTC (rev 12167)
@@ -14,28 +14,35 @@
 # limitations under the License.
 ##
 
+from pycalendar.datetime import DateTime
+from pycalendar.period import Period
+
 from twext.python.clsprop import classproperty
-import twext.web2.dav.test.util
+
 from twisted.internet.defer import inlineCallbacks, succeed, returnValue
+
+from twistedcaldav import caldavxml
+from twistedcaldav.caldavxml import TimeRange
+from twistedcaldav.ical import Component, normalize_iCalStr
+
+from txdav.caldav.datastore.query.filter import Filter
+from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
 from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers, Server
 from txdav.caldav.datastore.test.util import buildCalendarStore, \
     TestCalendarStoreDirectoryRecord
-from txdav.common.datastore.podding.resource import ConduitResource
-from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests
 from txdav.common.datastore.podding.conduit import PoddingConduit, \
     FailedCrossPodRequestError
-from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
+from txdav.common.datastore.podding.resource import ConduitResource
 from txdav.common.datastore.podding.test.util import MultiStoreConduitTest, \
     FakeConduitRequest
 from txdav.common.datastore.sql_tables import _BIND_STATUS_ACCEPTED
-from pycalendar.datetime import DateTime
-from twistedcaldav.ical import Component, normalize_iCalStr
+from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests
 from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError, \
     ObjectResourceNameNotAllowedError
-from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
-from twistedcaldav.caldavxml import TimeRange
-from pycalendar.period import Period
+from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
 
+import twext.web2.dav.test.util
+
 class TestConduit (CommonCommonTests, twext.web2.dav.test.util.TestCase):
 
     class FakeConduit(object):
@@ -533,6 +540,39 @@
 
 
     @inlineCallbacks
+    def test_search(self):
+        """
+        Test that action=resourcenameforuid works.
+        """
+
+        yield self.createShare("user01", "puser01")
+
+        calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+        yield  calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
+        yield self.commit()
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    **{"name":("VEVENT", "VFREEBUSY", "VAVAILABILITY")}
+                )],
+                **{"name": "VCALENDAR"}
+            )
+        )
+        filter = Filter(filter)
+
+        calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+        names = [item[0] for item in (yield calendar1.search(filter))]
+        self.assertEqual(names, ["1.ics", ])
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+        names = [item[0] for item in (yield shared.search(filter))]
+        self.assertEqual(names, ["1.ics", ])
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
     def test_loadallobjects(self):
         """
         Test that action=loadallobjects works.

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py	2013-12-20 15:57:11 UTC (rev 12166)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py	2013-12-20 16:02:05 UTC (rev 12167)
@@ -341,7 +341,18 @@
         returnValue(names)
 
 
+    @inlineCallbacks
+    def search(self, filter, **kwargs):
+        try:
+            results = yield self._txn.store().conduit.send_search(self, filter.serialize(), **kwargs)
+        except NonExistentExternalShare:
+            yield self.fixNonExistentExternalShare()
+            raise ExternalShareFailed("External share does not exist")
 
+        returnValue(results)
+
+
+
 class CommonObjectResourceExternal(CommonObjectResource):
     """
     A CommonObjectResource for a resource not hosted on this system, but on another pod. This will forward
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/250ab6c6/attachment.html>


More information about the calendarserver-changes mailing list