[CalendarServer-changes] [3000] CalendarServer/branches/users/sagen/principal-property-search-2995

source_changes at macosforge.org source_changes at macosforge.org
Tue Sep 16 11:37:35 PDT 2008


Revision: 3000
          http://trac.macosforge.org/projects/calendarserver/changeset/3000
Author:   sagen at apple.com
Date:     2008-09-16 11:37:34 -0700 (Tue, 16 Sep 2008)
Log Message:
-----------
Checkpoint of progress: when doing a principal-property-search REPORT, the search now takes advantage of any properties that map to directory record fields; also added email-address DAV property.

Modified Paths:
--------------
    CalendarServer/branches/users/sagen/principal-property-search-2995/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch
    CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/customxml.py
    CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/aggregate.py
    CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/apache.py
    CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/appleopendirectory.py
    CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/directory.py
    CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/idirectory.py
    CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/principal.py
    CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/sudo.py
    CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/xmlaccountsparser.py
    CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/xmlfile.py
    CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/resource.py

Modified: CalendarServer/branches/users/sagen/principal-property-search-2995/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2995/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch	2008-09-15 19:28:14 UTC (rev 2999)
+++ CalendarServer/branches/users/sagen/principal-property-search-2995/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch	2008-09-16 18:37:34 UTC (rev 3000)
@@ -2,7 +2,7 @@
 ===================================================================
 --- twisted/web2/dav/method/report_principal_property_search.py	(revision 19773)
 +++ twisted/web2/dav/method/report_principal_property_search.py	(working copy)
-@@ -46,17 +46,23 @@
+@@ -46,17 +46,24 @@
      Generate a principal-property-search REPORT. (RFC 3744, section 9.4)
      """
  
@@ -16,6 +16,7 @@
 +    testMode = principal_property_search.attributes.get("test", "allof")
 +    if testMode not in ("allof", "anyof"):
 +        raise ValueError("Unknown value for test attribute: %s" % (testMode,))
++    operand = "and" if testMode == "allof" else "or"
 +
      # Only handle Depth: 0
      depth = request.headers.getHeader("depth", "0")
@@ -27,18 +28,18 @@
      # Get a single DAV:prop element from the REPORT request body
      propertiesForResource = None
      propElement = None
-@@ -93,31 +99,53 @@
+@@ -93,73 +100,157 @@
          else:
              return False
          
 -    def propertySearch(resource, request):
-+    def propertySearch(resource, request, testMode):
++    def propertySearch(resource, request, operand):
          """
          Test the resource to see if it contains properties matching the
          property-search specification in this report.
          @param resource: the L{DAVFile} for the resource to test.
          @param request:  the current request.
-+        @param testMode:  "allof" (AND operation) or "anyof" (OR operation)
++        @param operand:  "and" or "or"
          @return:         True if the resource has matching properties, False otherwise.
          """
 -        for props, match in propertySearches:
@@ -50,7 +51,7 @@
 -                    propvalue = propvalue.getResult()
 -                    if propvalue and not nodeMatch(propvalue, match):
 +
-+        if testMode == "allof": # "AND"
++        if operand == "and":
 +            for props, match in propertySearches:
 +                # Test each property
 +                for prop in props:
@@ -75,7 +76,7 @@
 +            # we hit on every property
 +            yield True
  
-+        else: # "OR"
++        else: # "or"
 +            for props, match in propertySearches:
 +                # Test each property
 +                for prop in props:
@@ -95,60 +96,146 @@
 +
      propertySearch = deferredGenerator(propertySearch)
  
++
      # Run report
-@@ -127,13 +155,8 @@
+     try:
+-        resources = []
+-        responses = []
++        # propertySearches is a list of tuple(tuple(props), match)
++
++        # TODO: should this mapping live elsewhere?
++        # See if there are any that are recognized by record fields
++        cs_ns = "http://calendarserver.org/ns/"
++        fieldMap = {
++            "<{%s}%s>" % (cs_ns, "first-name") : "firstName",
++            "<{%s}%s>" % (cs_ns, "last-name") : "lastName",
++            "<{%s}%s>" % (cs_ns, "email-address") : "emailAddress",
++        }
++
++        fields = []
++        nonDirectorySearches = []
++        for props, match in propertySearches:
++            nonDirectoryProps = []
++            for prop in props:
++                uri = repr(prop)
++                if fieldMap.has_key(uri):
++                    fieldName = fieldMap[uri]
++                    fields.append((fieldName, match))
++                else:
++                    nonDirectoryProps.append(prop)
++            if nonDirectoryProps:
++                nonDirectorySearches.append((nonDirectoryProps, match))
++
++        matchingResources = []
          matchcount = 0
  
-         if applyTo:
+-        if applyTo:
 -            # Get the principal collection set
 -            pset = waitForDeferred(self.principalCollections(request))
 -            yield pset
 -            pset = pset.getResult()
--
++        if (
++            (operand == "or" and nonDirectorySearches) or
++            (operand == "and" and nonDirectorySearches and not fields)
++        ):
++            # These are the situations in which we need to iterate all the
++            # resources
++            resources = []
+ 
 -            for phref in pset:
 -                uri = str(phref)
-+            for principalCollection in self.principalCollections():
-+                uri = principalCollection.principalCollectionURL()
-                 resource = waitForDeferred(request.locateResource(uri))
-                 yield resource
-                 resource = resource.getResult()
-@@ -142,42 +165,67 @@
-         else:
-             resources.append((self, request.uri))
+-                resource = waitForDeferred(request.locateResource(uri))
+-                yield resource
+-                resource = resource.getResult()
+-                if resource:
+-                    resources.append((resource, uri))
+-        else:
+-            resources.append((self, request.uri))
++            if applyTo:
++                for principalCollection in self.principalCollections():
++                    uri = principalCollection.principalCollectionURL()
++                    resource = waitForDeferred(request.locateResource(uri))
++                    yield resource
++                    resource = resource.getResult()
++                    if resource:
++                        resources.append((resource, uri))
++            else:
++                resources.append((self, request.uri))
  
 -        # Loop over all collections and principal resources within
 -        for resource, ruri in resources:
-+        if True:
++            # Loop over all collections and principal resources within
++            for resource, ruri in resources:
  
 -            # Do some optimisation of access control calculation by determining any inherited ACLs outside of
 -            # the child resource loop and supply those to the checkPrivileges on each child.
 -            filteredaces = waitForDeferred(resource.inheritedACEsforChildren(request))
 -            yield filteredaces
 -            filteredaces = filteredaces.getResult()
-+            for record in self.directory.recordsStartingWith("mor"):
-+                resource = self.principalForRecord(record)
-+                url = resource.url()
++                # Do some optimisation of access control calculation by determining any inherited ACLs outside of
++                # the child resource loop and supply those to the checkPrivileges on each child.
++                filteredaces = waitForDeferred(resource.inheritedACEsforChildren(request))
++                yield filteredaces
++                filteredaces = filteredaces.getResult()
  
 -            children = []
 -            d = waitForDeferred(resource.findChildren("infinity", request, lambda x, y: children.append((x,y)),
 -                                                      privileges=(davxml.Read(),), inherited_aces=filteredaces))
 -            yield d
 -            d.getResult()
-+                # Check size of results is within limit
-+                matchcount += 1
-+                if matchcount > max_number_of_matches:
-+                    raise NumberOfMatchesWithinLimits
++                children = []
++                d = waitForDeferred(resource.findChildren("infinity", request,
++                    lambda x, y: children.append((x,y)),
++                    privileges=(davxml.Read(),), inherited_aces=filteredaces))
++                yield d
++                d.getResult()
  
 -            for child, uri in children:
 -                if isPrincipalResource(child):
 -                    d = waitForDeferred(propertySearch(child, request))
--                    yield d
--                    d = d.getResult()
--                    if d:
--                        # Check size of results is within limit
--                        matchcount += 1
--                        if matchcount > max_number_of_matches:
--                            raise NumberOfMatchesWithinLimits
++                for child, uri in children:
++                    if isPrincipalResource(child):
++                        d = waitForDeferred(propertySearch(child, request,
++                            operand))
++                        yield d
++                        d = d.getResult()
++                        if d:
++                            # Check size of results is within limit
++                            matchcount += 1
++                            if matchcount > max_number_of_matches:
++                                raise NumberOfMatchesWithinLimits
++
++                            matchingResources.append(child)
++
++
++        elif fields: # search the directory
++            for record in self.directory.recordsMatchingFields(fields,
++                operand=operand):
++
++                resource = self.principalForRecord(record)
++                url = resource.url()
++
++                if not nonDirectorySearches:
++                    # We've determined this is a matching resource
++
++                    matchcount += 1
++                    if matchcount > max_number_of_matches:
++                        raise NumberOfMatchesWithinLimits
++                    matchingResources.append(resource)
++
++                elif operand == "and":
++                    # Further narrowing down needs to take place by examining
++                    # the resource's DAV properties
++
++                    d = waitForDeferred(propertySearch(resource, request,
++                        operand))
+                     yield d
+                     d = d.getResult()
+                     if d:
+@@ -167,18 +258,26 @@
+                         matchcount += 1
+                         if matchcount > max_number_of_matches:
+                             raise NumberOfMatchesWithinLimits
 -    
 -                        d = waitForDeferred(prop_common.responseForHref(
 -                            request,
@@ -158,57 +245,28 @@
 -                            propertiesForResource,
 -                            propElement
 -                        ))
-+                d = waitForDeferred(prop_common.responseForHref(
-+                    request,
-+                    responses,
-+                    davxml.HRef.fromString(url),
-+                    resource,
-+                    propertiesForResource,
-+                    propElement
-+                ))
-+                yield d
-+                d.getResult()
+-                        yield d
+-                        d.getResult()
++                        matchingResources.append(resource)
+ 
 +
-+        else:
 +
-+            # Loop over all collections and principal resources within
-+            for resource, ruri in resources:
++        # Generate the response
++        responses = []
++        for resource in matchingResources:
++            url = resource.url()
++            d = waitForDeferred(prop_common.responseForHref(
++                request,
++                responses,
++                davxml.HRef.fromString(url),
++                resource,
++                propertiesForResource,
++                propElement
++            ))
++            yield d
++            d.getResult()
 +
-+                # Do some optimisation of access control calculation by determining any inherited ACLs outside of
-+                # the child resource loop and supply those to the checkPrivileges on each child.
-+                filteredaces = waitForDeferred(resource.inheritedACEsforChildren(request))
-+                yield filteredaces
-+                filteredaces = filteredaces.getResult()
 +
-+                children = []
-+                # d = waitForDeferred(resource.findChildren("infinity", request, lambda x, y: children.append((x,y)),
-+                d = waitForDeferred(resource.findChildren("1", request, lambda x, y: children.append((x,y)),
-+                                                          privileges=(davxml.Read(),), inherited_aces=filteredaces))
-+                yield d
-+                d.getResult()
-+
-+                for child, uri in children:
-+                    if isPrincipalResource(child):
-+                        d = waitForDeferred(propertySearch(child, request, testMode))
-                         yield d
--                        d.getResult()
-+                        d = d.getResult()
-+                        if d:
-+                            # Check size of results is within limit
-+                            matchcount += 1
-+                            if matchcount > max_number_of_matches:
-+                                raise NumberOfMatchesWithinLimits
-+        
-+                            d = waitForDeferred(prop_common.responseForHref(
-+                                request,
-+                                responses,
-+                                davxml.HRef.fromString(uri),
-+                                child,
-+                                propertiesForResource,
-+                                propElement
-+                            ))
-+                            yield d
-+                            d.getResult()
- 
      except NumberOfMatchesWithinLimits:
          log.err("Too many matching components in prinicpal-property-search report")
+         raise HTTPError(ErrorResponse(

Modified: CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/customxml.py	2008-09-15 19:28:14 UTC (rev 2999)
+++ CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/customxml.py	2008-09-16 18:37:34 UTC (rev 3000)
@@ -306,6 +306,14 @@
     name = "last-name"
     protected = True
 
+class EMailProperty (davxml.WebDAVTextElement):
+    """
+    A property representing email address of a principal
+    """
+    namespace = calendarserver_namespace
+    name = "email-address"
+    protected = True
+
 class IScheduleInbox (davxml.WebDAVEmptyElement):
     """
     Denotes the resourcetype of a iSchedule Inbox.

Modified: CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/aggregate.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/aggregate.py	2008-09-15 19:28:14 UTC (rev 2999)
+++ CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/aggregate.py	2008-09-16 18:37:34 UTC (rev 3000)
@@ -106,6 +106,10 @@
     def recordsStartingWith(self, str):
         return self._queryAll("recordsStartingWith", str)
 
+    def recordsMatchingFields(self, fields, caseInsensitive=True, operand="or"):
+        return self._queryAll("recordsMatchingFields", fields,
+            caseInsensitive=caseInsensitive, operand=operand)
+
     def serviceForRecordType(self, recordType):
         try:
             return self._recordTypes[recordType]
@@ -123,9 +127,9 @@
             *[a[len(service.recordTypePrefix):] for a in args]
         )
 
-    def _queryAll(self, query, *args):
+    def _queryAll(self, query, *args, **kwds):
         for service in self._recordTypes.values():
-            record = getattr(service, query)(*args)
+            record = getattr(service, query)(*args, **kwds)
             if record is not None:
                 return record
         else:

Modified: CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/apache.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/apache.py	2008-09-15 19:28:14 UTC (rev 2999)
+++ CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/apache.py	2008-09-16 18:37:34 UTC (rev 3000)
@@ -131,6 +131,7 @@
             fullName              = None,
             firstName             = None,
             lastName              = None,
+            emailAddress          = None,
             calendarUserAddresses = set(),
             autoSchedule          = False,
         )

Modified: CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/appleopendirectory.py	2008-09-15 19:28:14 UTC (rev 2999)
+++ CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/appleopendirectory.py	2008-09-16 18:37:34 UTC (rev 3000)
@@ -512,10 +512,75 @@
             except Exception, e:
                 print e
                 import pdb; pdb.set_trace()
-                
+
         return returning
 
+    _ODFields = {
+        'firstName' : dsattributes.kDS1AttrFirstName,
+        'lastName' : dsattributes.kDS1AttrLastName,
+        'emailAddress' : dsattributes.kDSNAttrEMailAddress,
+    }
 
+    def recordsMatchingFields(self, fields, caseInsensitive=True, operand="or"):
+
+        comparison = dsattributes.eDSStartsWith
+        operand = (dsquery.expression.OR if operand == "or"
+            else dsquery.expression.AND)
+
+        expressions = []
+        for field, value in fields:
+            if field in self._ODFields:
+                ODField = self._ODFields[field]
+                expressions.append(dsquery.match(ODField, value, comparison))
+
+
+        results = opendirectory.queryRecordsWithAttributes(
+            self.directory,
+            dsquery.expression(operand, expressions).generate(),
+            caseInsensitive,
+            dsattributes.kDSStdRecordTypeUsers,
+            [
+                dsattributes.kDS1AttrGeneratedUID,
+                dsattributes.kDS1AttrFirstName,
+                dsattributes.kDS1AttrLastName,
+                dsattributes.kDSNAttrEMailAddress,
+                dsattributes.kDS1AttrDistinguishedName,
+                dsattributes.kDSNAttrMetaNodeLocation,
+            ]
+        )
+        returning = []
+        for key, val in results.iteritems():
+            try:
+                calendarUserAddresses = set()
+                enabledForCalendaring = False
+                if val.has_key(dsattributes.kDSNAttrEMailAddress):
+                    enabledForCalendaring = True
+                    calendarUserAddresses.add(val[dsattributes.kDSNAttrEMailAddress])
+                rec = OpenDirectoryRecord(
+                    service = self,
+                    recordType = DirectoryService.recordType_users,
+                    guid = val[dsattributes.kDS1AttrGeneratedUID],
+                    nodeName = val[dsattributes.kDSNAttrMetaNodeLocation],
+                    shortName = key,
+                    fullName = val[dsattributes.kDS1AttrDistinguishedName],
+                    firstName = val[dsattributes.kDS1AttrFirstName],
+                    lastName = val[dsattributes.kDS1AttrLastName],
+                    emailAddress = val.get(dsattributes.kDSNAttrEMailAddress, ""),
+                    calendarUserAddresses = calendarUserAddresses,
+                    autoSchedule = False,
+                    enabledForCalendaring = enabledForCalendaring,
+                    memberGUIDs = (),
+                    proxyGUIDs = (),
+                    readOnlyProxyGUIDs = (),
+                )
+                returning.append(rec)
+            except Exception, e:
+                print e
+                import pdb; pdb.set_trace()
+
+        return returning
+
+
     def reloadCache(self, recordType, shortName=None, guid=None):
         if shortName:
             self.log_info("Faulting record %s into %s record cache" % (shortName, recordType))
@@ -591,6 +656,7 @@
             recordFullName = value.get(dsattributes.kDS1AttrDistinguishedName)
             recordFirstName = value.get(dsattributes.kDS1AttrFirstName)
             recordLastName = value.get(dsattributes.kDS1AttrLastName)
+            recordEmailAddress = value.get(dsattributes.kDSNAttrEMailAddress)
             recordNodeName = value.get(dsattributes.kDSNAttrMetaNodeLocation)
 
             if not recordGUID:
@@ -641,6 +707,7 @@
                 fullName              = recordFullName,
                 firstName             = recordFirstName,
                 lastName              = recordLastName,
+                emailAddress          = recordEmailAddress,
                 calendarUserAddresses = calendarUserAddresses,
                 autoSchedule          = autoSchedule,
                 enabledForCalendaring = enabledForCalendaring,
@@ -889,7 +956,7 @@
     """
     def __init__(
         self, service, recordType, guid, nodeName, shortName, fullName,
-        firstName, lastName,
+        firstName, lastName, emailAddress,
         calendarUserAddresses, autoSchedule, enabledForCalendaring,
         memberGUIDs, proxyGUIDs, readOnlyProxyGUIDs,
     ):
@@ -901,6 +968,7 @@
             fullName              = fullName,
             firstName             = firstName,
             lastName              = lastName,
+            emailAddress          = emailAddress,
             calendarUserAddresses = calendarUserAddresses,
             autoSchedule          = autoSchedule,
             enabledForCalendaring = enabledForCalendaring,

Modified: CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/directory.py	2008-09-15 19:28:14 UTC (rev 2999)
+++ CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/directory.py	2008-09-16 18:37:34 UTC (rev 3000)
@@ -138,6 +138,8 @@
             for record in self.listRecords(recordType):
                 yield record
 
+    def recordsMatchingFields(self, fields, caseInsensitive=True, operand="or"):
+        return None
 
 class DirectoryRecord(LoggingMixIn):
     implements(IDirectoryRecord)
@@ -155,7 +157,7 @@
 
     def __init__(
         self, service, recordType, guid, shortName, fullName,
-        firstName, lastName,
+        firstName, lastName, emailAddress,
         calendarUserAddresses, autoSchedule, enabledForCalendaring=True,
     ):
         assert service.realmName is not None
@@ -177,6 +179,7 @@
         self.fullName              = fullName
         self.firstName             = firstName
         self.lastName              = lastName
+        self.emailAddress          = emailAddress
         self.enabledForCalendaring = enabledForCalendaring
         self.calendarUserAddresses = calendarUserAddresses
         self.autoSchedule          = autoSchedule

Modified: CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/idirectory.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/idirectory.py	2008-09-15 19:28:14 UTC (rev 2999)
+++ CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/idirectory.py	2008-09-16 18:37:34 UTC (rev 3000)
@@ -79,7 +79,13 @@
             insensitive)
         """
 
+    def recordsMatchingFields(fields):
+        """
+        @return: a sequence of L{IDirectoryRecord}s which match the given
+            fields.
+        """
 
+
 class IDirectoryRecord(Interface):
     """
     Directory Record
@@ -91,6 +97,7 @@
     fullName              = Attribute("The full name of this record.")
     firstName             = Attribute("The first name of this record.")
     lastName              = Attribute("The last name of this record.")
+    emailAddress          = Attribute("The email address of this record.")
     calendarUserAddresses = Attribute("A set of calendar user addresses for this record.")
     autoSchedule          = Attribute("Principal identified by this record should automatically accept/deny meetings.")
     enabledForCalendaring = Attribute("Determines whether this record should be provisioned with a calendar home.")

Modified: CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/principal.py	2008-09-15 19:28:14 UTC (rev 2999)
+++ CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/principal.py	2008-09-16 18:37:34 UTC (rev 3000)
@@ -439,6 +439,7 @@
             """Full name: %s\n"""              % (self.record.fullName,),
             """First name: %s\n"""             % (self.record.firstName,),
             """Last name: %s\n"""              % (self.record.lastName,),
+            """Email address: %s\n"""          % (self.record.emailAddress,),
             """Principal UID: %s\n"""          % (self.principalUID(),),
             """Principal URL: %s\n"""          % (format_link(self.principalURL()),),
             """\nAlternate URIs:\n"""          , format_list(format_link(u) for u in self.alternateURIs()),
@@ -601,6 +602,7 @@
             """Full name: %s\n"""              % (self.record.fullName,),
             """First name: %s\n"""             % (self.record.firstName,),
             """Last name: %s\n"""              % (self.record.lastName,),
+            """Email address: %s\n"""          % (self.record.emailAddress,),
             """Principal UID: %s\n"""          % (self.principalUID(),),
             """Principal URL: %s\n"""          % (format_link(self.principalURL()),),
             """\nAlternate URIs:\n"""          , format_list(format_link(u) for u in self.alternateURIs()),

Modified: CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/sudo.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/sudo.py	2008-09-15 19:28:14 UTC (rev 2999)
+++ CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/sudo.py	2008-09-16 18:37:34 UTC (rev 3000)
@@ -137,6 +137,7 @@
             fullName=shortName,
             firstName="",
             lastName="",
+            emailAddress="",
             calendarUserAddresses=set(),
             autoSchedule=False,
             enabledForCalendaring=False)

Modified: CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/xmlaccountsparser.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/xmlaccountsparser.py	2008-09-15 19:28:14 UTC (rev 2999)
+++ CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/xmlaccountsparser.py	2008-09-16 18:37:34 UTC (rev 3000)
@@ -45,6 +45,7 @@
 ELEMENT_NAME              = "name"
 ELEMENT_FIRST_NAME        = "first-name"
 ELEMENT_LAST_NAME         = "last-name"
+ELEMENT_EMAIL_ADDRESS     = "email-address"
 ELEMENT_MEMBERS           = "members"
 ELEMENT_MEMBER            = "member"
 ELEMENT_CUADDR            = "cuaddr"
@@ -167,6 +168,7 @@
         self.name = None
         self.firstName = None
         self.lastName = None
+        self.emailAddress = None
         self.members = set()
         self.groups = set()
         self.calendarUserAddresses = set()
@@ -207,6 +209,10 @@
             lastName = self.lastName % ctr
         else:
             lastName = self.lastName
+        if self.emailAddress and self.emailAddress.find("%") != -1:
+            emailAddress = self.emailAddress % ctr
+        else:
+            emailAddress = self.emailAddress
         calendarUserAddresses = set()
         for cuaddr in self.calendarUserAddresses:
             if cuaddr.find("%") != -1:
@@ -221,6 +227,7 @@
         result.name = name
         result.firstName = firstName
         result.lastName = lastName
+        result.emailAddress = emailAddress
         result.members = self.members
         result.calendarUserAddresses = calendarUserAddresses
         result.autoSchedule = self.autoSchedule
@@ -257,6 +264,9 @@
             elif child_name == ELEMENT_LAST_NAME:
                 if child.firstChild is not None:
                     self.lastName = child.firstChild.data.encode("utf-8")
+            elif child_name == ELEMENT_EMAIL_ADDRESS:
+                if child.firstChild is not None:
+                    self.emailAddress = child.firstChild.data.encode("utf-8")
             elif child_name == ELEMENT_MEMBERS:
                 self._parseMembers(child, self.members)
             elif child_name == ELEMENT_CUADDR:

Modified: CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/xmlfile.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/xmlfile.py	2008-09-15 19:28:14 UTC (rev 2999)
+++ CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/directory/xmlfile.py	2008-09-16 18:37:34 UTC (rev 3000)
@@ -110,6 +110,7 @@
             fullName              = xmlPrincipal.name,
             firstName             = xmlPrincipal.firstName,
             lastName              = xmlPrincipal.lastName,
+            emailAddress          = xmlPrincipal.emailAddress,
             calendarUserAddresses = xmlPrincipal.calendarUserAddresses,
             autoSchedule          = xmlPrincipal.autoSchedule,
             enabledForCalendaring = xmlPrincipal.enabledForCalendaring,

Modified: CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/resource.py	2008-09-15 19:28:14 UTC (rev 2999)
+++ CalendarServer/branches/users/sagen/principal-property-search-2995/twistedcaldav/resource.py	2008-09-16 18:37:34 UTC (rev 3000)
@@ -707,6 +707,7 @@
         (caldav_namespace, "schedule-outbox-URL"      ),
         (calendarserver_namespace, "first-name"       ),
         (calendarserver_namespace, "last-name"        ),
+        (calendarserver_namespace, "email-address"    ),
     )
 
     @classmethod
@@ -776,7 +777,14 @@
                     else:
                         return None
 
+                if name == "email-address":
+                    emailAddress = self.record.emailAddress
+                    if emailAddress:
+                        return customxml.EMailProperty(emailAddress)
+                    else:
+                        return None
 
+
             return super(CalendarPrincipalResource, self).readProperty(property, request)
 
         return maybeDeferred(defer)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080916/6ef8bf60/attachment-0001.html 


More information about the calendarserver-changes mailing list