[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