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

source_changes at macosforge.org source_changes at macosforge.org
Mon Sep 15 12:12:50 PDT 2008


Revision: 2995
          http://trac.macosforge.org/projects/calendarserver/changeset/2995
Author:   sagen at apple.com
Date:     2008-09-15 12:12:49 -0700 (Mon, 15 Sep 2008)
Log Message:
-----------
Checkpoint of progress towards fast REPORT-based search.  This iteration directly queries OD rather than searching through every resource.

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

Modified: CalendarServer/branches/users/sagen/principal-property-search-2970/conf/accounts-test.xml
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/conf/accounts-test.xml	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/conf/accounts-test.xml	2008-09-15 19:12:49 UTC (rev 2995)
@@ -24,12 +24,16 @@
     <guid>admin</guid>
     <password>admin</password>
     <name>Super User</name>
+    <first-name>Super</first-name>
+    <last-name>User</last-name>
   </user>
   <user>
     <uid>apprentice</uid>
     <guid>apprentice</guid>
     <password>apprentice</password>
     <name>Apprentice Super User</name>
+    <first-name>Apprentice</first-name>
+    <last-name>Super User</last-name>
   </user>
   <user repeat="99">
     <uid>user%02d</uid>
@@ -37,6 +41,8 @@
     <password>user%02d</password>
     <name>User %02d</name>
     <cuaddr>mailto:user%02d at example.com</cuaddr>
+    <first-name>User</first-name>
+    <last-name>%02d</last-name>
   </user>
   <user repeat="10">
     <uid>public%02d</uid>
@@ -44,6 +50,8 @@
     <password>public%02d</password>
     <name>Public %02d</name>
     <cuaddr>mailto:public%02d at example.com</cuaddr>
+    <first-name>Public</first-name>
+    <last-name>%02d</last-name>
   </user>
   <location repeat="10">
     <uid>location%02d</uid>

Modified: CalendarServer/branches/users/sagen/principal-property-search-2970/lib-patches/Twisted/twisted.web2.dav.element.rfc3744.patch
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/lib-patches/Twisted/twisted.web2.dav.element.rfc3744.patch	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/lib-patches/Twisted/twisted.web2.dav.element.rfc3744.patch	2008-09-15 19:12:49 UTC (rev 2995)
@@ -81,3 +81,11 @@
  
          # This element can be empty when uses in supported-report-set
          if not len(self.children):
+@@ -705,6 +714,7 @@
+         (dav_namespace, "prop"                             ): (0, 1),
+         (dav_namespace, "apply-to-principal-collection-set"): (0, 1),
+     }
++    allowed_attributes = { "test": False }
+ 
+ class PropertySearch (WebDAVElement):
+     """

Modified: CalendarServer/branches/users/sagen/principal-property-search-2970/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch	2008-09-15 19:12:49 UTC (rev 2995)
@@ -2,7 +2,101 @@
 ===================================================================
 --- twisted/web2/dav/method/report_principal_property_search.py	(revision 19773)
 +++ twisted/web2/dav/method/report_principal_property_search.py	(working copy)
-@@ -127,13 +127,8 @@
+@@ -46,17 +46,23 @@
+     Generate a principal-property-search REPORT. (RFC 3744, section 9.4)
+     """
+ 
++
+     # Verify root element
+     if not isinstance(principal_property_search, davxml.PrincipalPropertySearch):
+         raise ValueError("%s expected as root element, not %s."
+                          % (davxml.PrincipalPropertySearch.sname(), principal_property_search.sname()))
+ 
++    # Should we AND (the default) or OR (if test="anyof")?
++    testMode = principal_property_search.attributes.get("test", "allof")
++    if testMode not in ("allof", "anyof"):
++        raise ValueError("Unknown value for test attribute: %s" % (testMode,))
++
+     # Only handle Depth: 0
+     depth = request.headers.getHeader("depth", "0")
+     if depth != "0":
+         log.err("Error in prinicpal-property-search REPORT, Depth set to %s" % (depth,))
+         raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
+-    
++
+     # Get a single DAV:prop element from the REPORT request body
+     propertiesForResource = None
+     propElement = None
+@@ -93,31 +99,53 @@
+         else:
+             return False
+         
+-    def propertySearch(resource, request):
++    def propertySearch(resource, request, testMode):
+         """
+         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)
+         @return:         True if the resource has matching properties, False otherwise.
+         """
+-        for props, match in propertySearches:
+-            # Test each property
+-            for prop in props:
+-                try:
+-                    propvalue = waitForDeferred(resource.readProperty(prop.qname(), request))
+-                    yield propvalue
+-                    propvalue = propvalue.getResult()
+-                    if propvalue and not nodeMatch(propvalue, match):
++
++        if testMode == "allof": # "AND"
++            for props, match in propertySearches:
++                # Test each property
++                for prop in props:
++                    try:
++                        propvalue = waitForDeferred(resource.readProperty(prop.qname(), request))
++                        yield propvalue
++                        propvalue = propvalue.getResult()
++                        if propvalue:
++                            if not nodeMatch(propvalue, match):
++                                yield False
++                                return
++                    except HTTPError:
++                        # No property => no match
+                         yield False
+                         return
+-                except HTTPError:
+-                    # No property => no match
+-                    yield False
+-                    return
+-        
+-        yield True
++            # we hit on every property
++            yield True
+ 
++        else: # "OR"
++            for props, match in propertySearches:
++                # Test each property
++                for prop in props:
++                    try:
++                        propvalue = waitForDeferred(resource.readProperty(prop.qname(), request))
++                        yield propvalue
++                        propvalue = propvalue.getResult()
++                        if propvalue:
++                            if nodeMatch(propvalue, match):
++                                yield True
++                                return
++                    except HTTPError:
++                        # No property
++                        pass
++            # we didn't hit any
++            yield False
++
+     propertySearch = deferredGenerator(propertySearch)
+ 
+     # Run report
+@@ -127,13 +155,8 @@
          matchcount = 0
  
          if applyTo:
@@ -18,3 +112,103 @@
                  resource = waitForDeferred(request.locateResource(uri))
                  yield resource
                  resource = resource.getResult()
+@@ -142,42 +165,67 @@
+         else:
+             resources.append((self, request.uri))
+ 
+-        # Loop over all collections and principal resources within
+-        for resource, ruri in resources:
++        if True:
+ 
+-            # 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()
+ 
+-            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
+ 
+-            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
+-    
+-                        d = waitForDeferred(prop_common.responseForHref(
+-                            request,
+-                            responses,
+-                            davxml.HRef.fromString(uri),
+-                            child,
+-                            propertiesForResource,
+-                            propElement
+-                        ))
++                d = waitForDeferred(prop_common.responseForHref(
++                    request,
++                    responses,
++                    davxml.HRef.fromString(url),
++                    resource,
++                    propertiesForResource,
++                    propElement
++                ))
++                yield d
++                d.getResult()
++
++        else:
++
++            # 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()
++
++                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")

Modified: CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/customxml.py	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/customxml.py	2008-09-15 19:12:49 UTC (rev 2995)
@@ -290,6 +290,22 @@
     protected = True
     hidden = True
 
+class FirstNameProperty (davxml.WebDAVTextElement):
+    """
+    A property representing first name of a principal
+    """
+    namespace = calendarserver_namespace
+    name = "first-name"
+    protected = True
+
+class LastNameProperty (davxml.WebDAVTextElement):
+    """
+    A property representing last name of a principal
+    """
+    namespace = calendarserver_namespace
+    name = "last-name"
+    protected = True
+
 class IScheduleInbox (davxml.WebDAVEmptyElement):
     """
     Denotes the resourcetype of a iSchedule Inbox.

Modified: CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/aggregate.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/aggregate.py	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/aggregate.py	2008-09-15 19:12:49 UTC (rev 2995)
@@ -103,6 +103,9 @@
     def recordWithCalendarUserAddress(self, address):
         return self._queryAll("recordWithCalendarUserAddress", address)
 
+    def recordsStartingWith(self, str):
+        return self._queryAll("recordsStartingWith", str)
+
     def serviceForRecordType(self, recordType):
         try:
             return self._recordTypes[recordType]

Modified: CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/apache.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/apache.py	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/apache.py	2008-09-15 19:12:49 UTC (rev 2995)
@@ -129,6 +129,8 @@
             guid                  = None,
             shortName             = shortName,
             fullName              = None,
+            firstName             = None,
+            lastName              = None,
             calendarUserAddresses = set(),
             autoSchedule          = False,
         )

Modified: CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/appleopendirectory.py	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/appleopendirectory.py	2008-09-15 19:12:49 UTC (rev 2995)
@@ -460,6 +460,62 @@
 
         return record
 
+    def recordsStartingWith(self, string):
+        results = opendirectory.queryRecordsWithAttributes(
+            self.directory,
+            dsquery.expression(dsquery.expression.OR,
+                (
+                    dsquery.match(dsattributes.kDS1AttrFirstName, string,
+                        dsattributes.eDSStartsWith),
+                    dsquery.match(dsattributes.kDS1AttrLastName, string,
+                        dsattributes.eDSStartsWith),
+                    dsquery.match(dsattributes.kDSNAttrEMailAddress, string,
+                        dsattributes.eDSStartsWith)
+                )
+            ).generate(),
+            True,
+            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],
+                    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))
@@ -533,6 +589,8 @@
             # Now get useful record info.
             recordGUID     = value.get(dsattributes.kDS1AttrGeneratedUID)
             recordFullName = value.get(dsattributes.kDS1AttrDistinguishedName)
+            recordFirstName = value.get(dsattributes.kDS1AttrFirstName)
+            recordLastName = value.get(dsattributes.kDS1AttrLastName)
             recordNodeName = value.get(dsattributes.kDSNAttrMetaNodeLocation)
 
             if not recordGUID:
@@ -581,6 +639,8 @@
                 nodeName              = recordNodeName,
                 shortName             = recordShortName,
                 fullName              = recordFullName,
+                firstName             = recordFirstName,
+                lastName              = recordLastName,
                 calendarUserAddresses = calendarUserAddresses,
                 autoSchedule          = autoSchedule,
                 enabledForCalendaring = enabledForCalendaring,
@@ -665,6 +725,8 @@
         attrs = [
             dsattributes.kDS1AttrGeneratedUID,
             dsattributes.kDS1AttrDistinguishedName,
+            dsattributes.kDS1AttrFirstName,
+            dsattributes.kDS1AttrLastName,
             dsattributes.kDSNAttrEMailAddress,
             dsattributes.kDSNAttrServicesLocator,
             dsattributes.kDSNAttrMetaNodeLocation,
@@ -827,6 +889,7 @@
     """
     def __init__(
         self, service, recordType, guid, nodeName, shortName, fullName,
+        firstName, lastName,
         calendarUserAddresses, autoSchedule, enabledForCalendaring,
         memberGUIDs, proxyGUIDs, readOnlyProxyGUIDs,
     ):
@@ -836,6 +899,8 @@
             guid                  = guid,
             shortName             = shortName,
             fullName              = fullName,
+            firstName             = firstName,
+            lastName              = lastName,
             calendarUserAddresses = calendarUserAddresses,
             autoSchedule          = autoSchedule,
             enabledForCalendaring = enabledForCalendaring,

Modified: CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/directory.py	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/directory.py	2008-09-15 19:12:49 UTC (rev 2995)
@@ -155,6 +155,7 @@
 
     def __init__(
         self, service, recordType, guid, shortName, fullName,
+        firstName, lastName,
         calendarUserAddresses, autoSchedule, enabledForCalendaring=True,
     ):
         assert service.realmName is not None
@@ -174,6 +175,8 @@
         self.guid                  = guid
         self.shortName             = shortName
         self.fullName              = fullName
+        self.firstName             = firstName
+        self.lastName              = lastName
         self.enabledForCalendaring = enabledForCalendaring
         self.calendarUserAddresses = calendarUserAddresses
         self.autoSchedule          = autoSchedule

Modified: CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/idirectory.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/idirectory.py	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/idirectory.py	2008-09-15 19:12:49 UTC (rev 2995)
@@ -72,6 +72,14 @@
             addresses.
         """
 
+    def recordsStartingWith(string):
+        """
+        @return: a sequence of L{IDirectoryRecord}s whose first name, last
+            name, or email address start with the given string (case
+            insensitive)
+        """
+
+
 class IDirectoryRecord(Interface):
     """
     Directory Record
@@ -81,6 +89,8 @@
     guid                  = Attribute("The GUID of this record.")
     shortName             = Attribute("The name of this record.")
     fullName              = Attribute("The full name of this record.")
+    firstName             = Attribute("The first name of this record.")
+    lastName              = Attribute("The last name 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-2970/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/principal.py	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/principal.py	2008-09-15 19:12:49 UTC (rev 2995)
@@ -437,6 +437,8 @@
             """Record type: %s\n"""            % (self.record.recordType,),
             """Short name: %s\n"""             % (self.record.shortName,),
             """Full name: %s\n"""              % (self.record.fullName,),
+            """First name: %s\n"""             % (self.record.firstName,),
+            """Last name: %s\n"""              % (self.record.lastName,),
             """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()),
@@ -597,6 +599,8 @@
             """Record type: %s\n"""            % (self.record.recordType,),
             """Short name: %s\n"""             % (self.record.shortName,),
             """Full name: %s\n"""              % (self.record.fullName,),
+            """First name: %s\n"""             % (self.record.firstName,),
+            """Last name: %s\n"""              % (self.record.lastName,),
             """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-2970/twistedcaldav/directory/sudo.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/sudo.py	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/sudo.py	2008-09-15 19:12:49 UTC (rev 2995)
@@ -98,6 +98,9 @@
             if entry['username'] == shortName:
                 return self._recordForEntry(entry)
 
+    def recordsStartingWith(self, string):
+        return None
+
     def requestAvatarId(self, credentials):
         # FIXME: ?
         # We were checking if principal is enabled; seems unnecessary in current
@@ -132,6 +135,8 @@
             guid=None,
             shortName=shortName,
             fullName=shortName,
+            firstName="",
+            lastName="",
             calendarUserAddresses=set(),
             autoSchedule=False,
             enabledForCalendaring=False)

Modified: CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/xmlaccountsparser.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/xmlaccountsparser.py	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/xmlaccountsparser.py	2008-09-15 19:12:49 UTC (rev 2995)
@@ -43,6 +43,8 @@
 ELEMENT_GUID              = "guid"
 ELEMENT_PASSWORD          = "password"
 ELEMENT_NAME              = "name"
+ELEMENT_FIRST_NAME        = "first-name"
+ELEMENT_LAST_NAME         = "last-name"
 ELEMENT_MEMBERS           = "members"
 ELEMENT_MEMBER            = "member"
 ELEMENT_CUADDR            = "cuaddr"
@@ -163,6 +165,8 @@
         self.guid = None
         self.password = None
         self.name = None
+        self.firstName = None
+        self.lastName = None
         self.members = set()
         self.groups = set()
         self.calendarUserAddresses = set()
@@ -195,6 +199,14 @@
             name = self.name % ctr
         else:
             name = self.name
+        if self.firstName and self.firstName.find("%") != -1:
+            firstName = self.firstName % ctr
+        else:
+            firstName = self.firstName
+        if self.lastName and self.lastName.find("%") != -1:
+            lastName = self.lastName % ctr
+        else:
+            lastName = self.lastName
         calendarUserAddresses = set()
         for cuaddr in self.calendarUserAddresses:
             if cuaddr.find("%") != -1:
@@ -207,6 +219,8 @@
         result.guid = guid
         result.password = password
         result.name = name
+        result.firstName = firstName
+        result.lastName = lastName
         result.members = self.members
         result.calendarUserAddresses = calendarUserAddresses
         result.autoSchedule = self.autoSchedule
@@ -237,6 +251,12 @@
             elif child_name == ELEMENT_NAME:
                 if child.firstChild is not None:
                     self.name = child.firstChild.data.encode("utf-8")
+            elif child_name == ELEMENT_FIRST_NAME:
+                if child.firstChild is not None:
+                    self.firstName = child.firstChild.data.encode("utf-8")
+            elif child_name == ELEMENT_LAST_NAME:
+                if child.firstChild is not None:
+                    self.lastName = 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-2970/twistedcaldav/directory/xmlfile.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/xmlfile.py	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/directory/xmlfile.py	2008-09-15 19:12:49 UTC (rev 2995)
@@ -108,6 +108,8 @@
             guid                  = xmlPrincipal.guid,
             shortName             = shortName,
             fullName              = xmlPrincipal.name,
+            firstName             = xmlPrincipal.firstName,
+            lastName              = xmlPrincipal.lastName,
             calendarUserAddresses = xmlPrincipal.calendarUserAddresses,
             autoSchedule          = xmlPrincipal.autoSchedule,
             enabledForCalendaring = xmlPrincipal.enabledForCalendaring,

Modified: CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/resource.py	2008-09-13 17:34:06 UTC (rev 2994)
+++ CalendarServer/branches/users/sagen/principal-property-search-2970/twistedcaldav/resource.py	2008-09-15 19:12:49 UTC (rev 2995)
@@ -705,6 +705,8 @@
         (caldav_namespace, "calendar-user-address-set"),
         (caldav_namespace, "schedule-inbox-URL"       ),
         (caldav_namespace, "schedule-outbox-URL"      ),
+        (calendarserver_namespace, "first-name"       ),
+        (calendarserver_namespace, "last-name"        ),
     )
 
     @classmethod
@@ -760,6 +762,21 @@
                     else:
                         return customxml.DropBoxHomeURL(davxml.HRef(url))
 
+                if name == "first-name":
+                    firstName = self.record.firstName
+                    if firstName:
+                        return customxml.FirstNameProperty(firstName)
+                    else:
+                        return None
+
+                if name == "last-name":
+                    lastName = self.record.lastName
+                    if lastName:
+                        return customxml.LastNameProperty(lastName)
+                    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/20080915/73b227ab/attachment-0001.html 


More information about the calendarserver-changes mailing list