<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[13590] CalendarServer/trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/13590">13590</a></dd>
<dt>Author</dt> <dd>gaya@apple.com</dd>
<dt>Date</dt> <dd>2014-06-01 19:10:46 -0700 (Sun, 01 Jun 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Use recordsMatchingFields() for directory gateway; enable &lt;feature&gt;directory-gateway&lt;/feature&gt; in caldavtester</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalDAVTestertrunkscriptsserverserverinfoxml">CalDAVTester/trunk/scripts/server/serverinfo.xml</a></li>
<li><a href="#CalDAVTestertrunkscriptstestsCardDAVreportsxml">CalDAVTester/trunk/scripts/tests/CardDAV/reports.xml</a></li>
<li><a href="#CalendarServertrunktwistedcaldavdirectorybackedaddressbookpy">CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastorequeryfilterpy">CalendarServer/trunk/txdav/carddav/datastore/query/filter.py</a></li>
<li><a href="#CalendarServertrunktxdavwhodirectorypy">CalendarServer/trunk/txdav/who/directory.py</a></li>
<li><a href="#CalendarServertrunktxdavwhovcardpy">CalendarServer/trunk/txdav/who/vcard.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>CalendarServer/trunk/calendarserver/</li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li>CalendarServer/trunk/calendarserver/</li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalDAVTestertrunkscriptsserverserverinfoxml"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/scripts/server/serverinfo.xml (13589 => 13590)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/scripts/server/serverinfo.xml        2014-06-02 00:38:06 UTC (rev 13589)
+++ CalDAVTester/trunk/scripts/server/serverinfo.xml        2014-06-02 02:10:46 UTC (rev 13590)
</span><span class="lines">@@ -110,7 +110,7 @@
</span><span class="cx">                 &lt;feature&gt;carddav&lt;/feature&gt;                                                 &lt;!-- Basic CardDAV feature enabler --&gt;
</span><span class="cx">                 &lt;feature&gt;default-addressbook&lt;/feature&gt;                         &lt;!-- Default address book behavior --&gt;
</span><span class="cx">                 &lt;feature&gt;shared-addressbooks&lt;/feature&gt;                        &lt;!-- Shared address books extension --&gt;
</span><del>-                &lt;!-- &lt;feature&gt;directory-gateway&lt;/feature&gt; --&gt;        &lt;!-- Directory gateway extension --&gt;
</del><ins>+                &lt;feature&gt;directory-gateway&lt;/feature&gt;                        &lt;!-- Directory gateway extension --&gt;
</ins><span class="cx"> 
</span><span class="cx">         &lt;/features&gt;
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalDAVTestertrunkscriptstestsCardDAVreportsxml"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/scripts/tests/CardDAV/reports.xml (13589 => 13590)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/scripts/tests/CardDAV/reports.xml        2014-06-02 00:38:06 UTC (rev 13589)
+++ CalDAVTester/trunk/scripts/tests/CardDAV/reports.xml        2014-06-02 02:10:46 UTC (rev 13590)
</span><span class="lines">@@ -1024,7 +1024,7 @@
</span><span class="cx">                                 &lt;/verify&gt;
</span><span class="cx">                         &lt;/request&gt;
</span><span class="cx">                 &lt;/test&gt;
</span><del>-                &lt;test name='3' ignore='no'&gt;
</del><ins>+                &lt;test name='3' ignore='yes'&gt;
</ins><span class="cx">                         &lt;description&gt;query for EMAIL does not contain &quot;b&quot;&lt;/description&gt;
</span><span class="cx">                         &lt;request print-response=&quot;no&quot;&gt;
</span><span class="cx">                                 &lt;method&gt;REPORT&lt;/method&gt;
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavdirectorybackedaddressbookpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py (13589 => 13590)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py        2014-06-02 00:38:06 UTC (rev 13589)
+++ CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py        2014-06-02 02:10:46 UTC (rev 13590)
</span><span class="lines">@@ -182,48 +182,63 @@
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         propNames, expression = expressionFromABFilter(
</span><del>-            addressBookFilter, vcardPropToRecordFieldMap, vCardConstantProperties
</del><ins>+            addressBookFilter, vcardPropToRecordFieldMap, vCardConstantProperties,
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         if expression:
</span><del>-            if defaultKind and &quot;KIND&quot; not in propNames:
-                defaultRecordExpression = MatchExpression(
-                    FieldName.recordType,
-                    vCardKindToRecordTypeMap[defaultKind],
-                    MatchType.equals
-                )
-                if expression is True:
-                    expression = defaultRecordExpression
-                else:
-                    expression = CompoundExpression(
-                        (expression, defaultRecordExpression,),
-                        Operand.AND
</del><ins>+
+            # if CompoundExpression of MatchExpression: recordsWithFieldValue() else recordsMatchingType()
+            fields = []
+            if expression is not True:
+
+                def fieldForMatchExpression(match):
+                    return (
+                        match.fieldName.name,
+                        match.fieldValue,
+                        match.flags,
+                        match.matchType,
</ins><span class="cx">                     )
</span><del>-            elif expression is True: # True means all records
-                allowedRecordTypes = set(self.directory.recordTypes()) &amp; set(recordTypeToVCardKindMap.keys())
-                expression = CompoundExpression(
-                    [
-                        MatchExpression(FieldName.recordType, recordType, MatchType.equals)
-                            for recordType in allowedRecordTypes
-                    ], Operand.OR
-                )
</del><span class="cx"> 
</span><ins>+                if isinstance(expression, CompoundExpression):
+                    operand = expression.operand
+                    for match in expression.expressions:
+                        if isinstance(match, MatchExpression):
+                            if match.fieldName != FieldName.recordType:
+                                fields.append(fieldForMatchExpression(match))
+                        else:
+                            fields = []
+                            break
+                elif isinstance(expression, MatchExpression):
+                    operand = Operand.OR
+                    if expression.fieldName != FieldName.recordType:
+                        fields.append(fieldForMatchExpression(expression))
+
</ins><span class="cx">             maxRecords = int(maxResults * 1.2)
</span><span class="cx"> 
</span><span class="cx">             # keep trying query till we get results based on filter.  Especially when doing &quot;all results&quot; query
</span><span class="cx">             while True:
</span><ins>+                queryLimited = False
</ins><span class="cx"> 
</span><span class="cx">                 log.debug(&quot;doAddressBookDirectoryQuery: expression={expression!r}, propNames={propNames}&quot;, expression=expression, propNames=propNames)
</span><span class="cx"> 
</span><del>-                records = yield self.directory.recordsFromExpression(expression)
-                log.debug(&quot;doAddressBookDirectoryQuery: #records={n}, records={records!r}&quot;, n=len(records), records=records)
-                queryLimited = False
</del><ins>+                allRecords = set()
+                if fields:
+                    records = yield self.directory.recordsMatchingFields(fields, operand)
+                    log.debug(&quot;doAddressBookDirectoryQuery: recordsMatchingFields({f}, {o}): #records={n}, records={records!r}&quot;,
+                              f=fields, o=operand, n=len(records), records=records)
+                    allRecords = set(records)
+                else:
+                    for recordType in set(self.directory.recordTypes()) &amp; set(recordTypeToVCardKindMap.keys()):
+                        records = yield self.directory.recordsWithRecordType(recordType)
+                        log.debug(&quot;doAddressBookDirectoryQuery: #records={n}, records={records!r}&quot;, n=len(records), records=records)
+                        allRecords |= set(records)
</ins><span class="cx"> 
</span><del>-                vCardsResults = [(yield ABDirectoryQueryResult(self).generate(record)) for record in records]
</del><ins>+                vCardsResults = [(yield ABDirectoryQueryResult(self).generate(record)) for record in allRecords]
</ins><span class="cx"> 
</span><span class="cx">                 filteredResults = set()
</span><span class="cx">                 for vCardResult in vCardsResults:
</span><span class="cx">                     if addressBookFilter.match(vCardResult.vCard()):
</span><ins>+                        log.debug(&quot;doAddressBookDirectoryQuery: vCard did match filter:\n{vcard}&quot;, vcard=vCardResult.vCard())
</ins><span class="cx">                         filteredResults.add(vCardResult)
</span><span class="cx">                     else:
</span><span class="cx">                         log.debug(&quot;doAddressBookDirectoryQuery: vCard did not match filter:\n{vcard}&quot;, vcard=vCardResult.vCard())
</span><span class="lines">@@ -266,9 +281,9 @@
</span><span class="cx">     propertyNames = []
</span><span class="cx">     if addressBookQuery.qname() == (&quot;DAV:&quot;, &quot;prop&quot;):
</span><span class="cx"> 
</span><del>-        for property in addressBookQuery.children:
-            if isinstance(property, carddavxml.AddressData):
-                for addressProperty in property.children:
</del><ins>+        for prop in addressBookQuery.children:
+            if isinstance(prop, carddavxml.AddressData):
+                for addressProperty in prop.children:
</ins><span class="cx">                     if isinstance(addressProperty, carddavxml.Property):
</span><span class="cx">                         propertyNames.append(addressProperty.attributes[&quot;name&quot;])
</span><span class="cx"> 
</span><span class="lines">@@ -360,7 +375,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">             def definedExpression(defined, allOf):
</span><del>-                if constant or propFilter.filter_name in (&quot;N&quot; , &quot;FN&quot;, &quot;UID&quot;, &quot;SOURCE&quot;, &quot;KIND&quot;,):
</del><ins>+                if constant or propFilter.filter_name in (&quot;N&quot; , &quot;FN&quot;, &quot;UID&quot;, &quot;KIND&quot;,):
</ins><span class="cx">                     return defined  # all records have this property so no records do not have it
</span><span class="cx">                 else:
</span><span class="cx">                     # FIXME: The startsWith expression below, which works with LDAP and OD. is not currently supported
</span><span class="lines">@@ -386,11 +401,11 @@
</span><span class="cx">                 params = vCardPropToParamMap.get(propFilter.filter_name.upper())
</span><span class="cx">                 defined = params and paramFilterElement.filter_name.upper() in params
</span><span class="cx"> 
</span><del>-                #defined test
</del><ins>+                # defined test
</ins><span class="cx">                 if defined != paramFilterElement.defined:
</span><span class="cx">                     return False
</span><span class="cx"> 
</span><del>-                #parameter value text match
</del><ins>+                # parameter value text match
</ins><span class="cx">                 if defined and paramFilterElement.filters:
</span><span class="cx">                     paramValues = params[paramFilterElement.filter_name.upper()]
</span><span class="cx">                     if paramValues and paramFilterElement.filters[0].text.upper() not in paramValues:
</span><span class="lines">@@ -401,10 +416,10 @@
</span><span class="cx"> 
</span><span class="cx">             def textMatchElementExpression(propFilterAllOf, textMatchElement):
</span><span class="cx"> 
</span><del>-                # pre process text match strings for ds query
</del><ins>+                # preprocess text match strings for ds query
</ins><span class="cx">                 def getMatchStrings(propFilter, matchString):
</span><span class="cx"> 
</span><del>-                    if propFilter.filter_name in (&quot;REV&quot; , &quot;BDAY&quot;,):
</del><ins>+                    if propFilter.filter_name in (&quot;REV&quot;, &quot;BDAY&quot;,):
</ins><span class="cx">                         rawString = matchString
</span><span class="cx">                         matchString = &quot;&quot;
</span><span class="cx">                         for c in rawString:
</span></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastorequeryfilterpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/carddav/datastore/query/filter.py (13589 => 13590)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/query/filter.py        2014-06-02 00:38:06 UTC (rev 13589)
+++ CalendarServer/trunk/txdav/carddav/datastore/query/filter.py        2014-06-02 02:10:46 UTC (rev 13590)
</span><span class="lines">@@ -136,7 +136,7 @@
</span><span class="cx">                     return not allof
</span><span class="cx">             return allof
</span><span class="cx">         else:
</span><del>-            return False
</del><ins>+            return True
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def valid(self):
</span><span class="lines">@@ -242,8 +242,8 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         allof = self.propfilter_test == &quot;allof&quot;
</span><del>-        if self.qualifier and allof != self.qualifier.match(item):
-            return not allof
</del><ins>+        if self.qualifier and allof and not self.qualifier.match(item):
+            return False
</ins><span class="cx"> 
</span><span class="cx">         if len(self.filters) &gt; 0:
</span><span class="cx">             for filter in self.filters:
</span></span></pre></div>
<a id="CalendarServertrunktxdavwhodirectorypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/who/directory.py (13589 => 13590)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/who/directory.py        2014-06-02 00:38:06 UTC (rev 13589)
+++ CalendarServer/trunk/txdav/who/directory.py        2014-06-02 02:10:46 UTC (rev 13590)
</span><span class="lines">@@ -326,19 +326,7 @@
</span><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @property
-    def calendarUserAddresses(self):
-        try:
-            if not (
-                self.hasCalendars or (
-                    config.GroupAttendees.Enabled and
-                    self.recordType == BaseRecordType.group
-                )
-            ):
-                return frozenset()
-        except AttributeError:
-            pass
-
</del><ins>+    def _calendarAddresses(self):
</ins><span class="cx">         cuas = set()
</span><span class="cx"> 
</span><span class="cx">         # urn:x-uid:
</span><span class="lines">@@ -366,6 +354,22 @@
</span><span class="cx"> 
</span><span class="cx">         return frozenset(cuas)
</span><span class="cx"> 
</span><ins>+
+    @property
+    def calendarUserAddresses(self):
+        try:
+            if not (
+                self.hasCalendars or (
+                    config.GroupAttendees.Enabled and
+                    self.recordType == BaseRecordType.group
+                )
+            ):
+                return frozenset()
+        except AttributeError:
+            pass
+
+        return self._calendarAddresses()
+
</ins><span class="cx">     # Mapping from directory record.recordType to RFC2445 CUTYPE values
</span><span class="cx">     _cuTypes = {
</span><span class="cx">         BaseRecordType.user: 'INDIVIDUAL',
</span><span class="lines">@@ -427,7 +431,7 @@
</span><span class="cx">         ))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def canonicalCalendarUserAddress(self):
</del><ins>+    def canonicalCalendarUserAddress(self, checkCal=True):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">             Return a CUA for this record, preferring in this order:
</span><span class="cx">             urn:x-uid: form
</span><span class="lines">@@ -437,8 +441,13 @@
</span><span class="cx">             first in calendarUserAddresses list (sorted)
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        sortedCuas = sorted(self.calendarUserAddresses)
</del><ins>+        if checkCal:
+            cuas = self.calendarUserAddresses
+        else:
+            cuas = self._calendarAddresses()
</ins><span class="cx"> 
</span><ins>+        sortedCuas = sorted(cuas)
+
</ins><span class="cx">         for prefix in (
</span><span class="cx">             &quot;urn:x-uid:&quot;,
</span><span class="cx">             &quot;urn:uuid:&quot;,
</span><span class="lines">@@ -450,7 +459,7 @@
</span><span class="cx">                     return candidate
</span><span class="cx"> 
</span><span class="cx">         # fall back to using the first one
</span><del>-        return sortedCuas[0]
</del><ins>+        return sortedCuas[0] if sortedCuas else None # groups may not have cua
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def enabledAsOrganizer(self):
</span></span></pre></div>
<a id="CalendarServertrunktxdavwhovcardpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/who/vcard.py (13589 => 13590)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/who/vcard.py        2014-06-02 00:38:06 UTC (rev 13589)
+++ CalendarServer/trunk/txdav/who/vcard.py        2014-06-02 02:10:46 UTC (rev 13590)
</span><span class="lines">@@ -323,8 +323,9 @@
</span><span class="cx">     # add members
</span><span class="cx">     # FIXME:  members() is a deferred, so all of vCardFromRecord is deferred.
</span><span class="cx">     for memberRecord in (yield record.members()):
</span><del>-        if memberRecord:
-            vcard.addProperty(Property(&quot;X-ADDRESSBOOKSERVER-MEMBER&quot;, memberRecord.canonicalCalendarUserAddress().encode(&quot;utf-8&quot;)))
</del><ins>+        cua = memberRecord.canonicalCalendarUserAddress(False)
+        if cua:
+            vcard.addProperty(Property(&quot;X-ADDRESSBOOKSERVER-MEMBER&quot;, cua.encode(&quot;utf-8&quot;)))
</ins><span class="cx"> 
</span><span class="cx">     #===================================================================
</span><span class="cx">     # vCard 4.0  http://tools.ietf.org/html/rfc6350
</span></span></pre>
</div>
</div>

</body>
</html>