Revision: 3635 http://trac.macosforge.org/projects/calendarserver/changeset/3635 Author: cdaboo@apple.com Date: 2009-02-04 11:04:46 -0800 (Wed, 04 Feb 2009) Log Message: ----------- Support multiple shortnames in directory records. Modified Paths: -------------- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/calendarserver/provision/root.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/conf/auth/accounts-test.xml CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/conf/auth/accounts.dtd CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/accesslog.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/apache.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/appleopendirectory.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/calendar.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/calendaruserproxy.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/directory.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/idirectory.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/principal.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/sqldb.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/sudo.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_opendirectory.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_opendirectoryrecords.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_principal.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_sudo.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_xmlfile.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/util.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/wiki.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/xmlaccountsparser.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/xmlfile.py CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/static.py Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/calendarserver/provision/root.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/calendarserver/provision/root.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/calendarserver/provision/root.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -127,7 +127,7 @@ request.checkingSACL = True principal = (yield request.locateResource(authzUser.children[0].children[0].data)) delattr(request, "checkingSACL") - username = principal.record.shortName + username = principal.record.shortNames[0] if RootResource.CheckSACL(username, self.saclService) != 0: log.msg("User %r is not enabled with the %r SACL" % (username, self.saclService,)) Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/conf/auth/accounts-test.xml =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/conf/auth/accounts-test.xml 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/conf/auth/accounts-test.xml 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -Copyright (c) 2006-2007 Apple Inc. All rights reserved. +Copyright (c) 2006-2009 Apple Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -40,18 +40,18 @@ <guid>user%02d</guid> <password>user%02d</password> <name>User %02d</name> - <cuaddr>mailto:user%02d@example.com</cuaddr> <first-name>User</first-name> <last-name>%02d</last-name> + <cuaddr>mailto:user%02d@example.com</cuaddr> </user> <user repeat="10"> <uid>public%02d</uid> <guid>public%02d</guid> <password>public%02d</password> <name>Public %02d</name> - <cuaddr>mailto:public%02d@example.com</cuaddr> <first-name>Public</first-name> <last-name>%02d</last-name> + <cuaddr>mailto:public%02d@example.com</cuaddr> </user> <location repeat="10"> <uid>location%02d</uid> Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/conf/auth/accounts.dtd =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/conf/auth/accounts.dtd 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/conf/auth/accounts.dtd 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ <!-- -Copyright (c) 2006-2007 Apple Inc. All rights reserved. +Copyright (c) 2006-2009 Apple Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,16 +17,16 @@ <!ELEMENT accounts (user*, group*, resource*, location*) > <!ATTLIST accounts realm CDATA ""> - <!ELEMENT user (uid, guid, password, name, cuaddr*, disable-calendar?)> + <!ELEMENT user (uid+, guid, password, name, first-name?, last-name?, email-address*, cuaddr*, disable-calendar?)> <!ATTLIST user repeat CDATA "1"> - <!ELEMENT group (uid, guid, password, name, members, cuaddr*, disable-calendar?)> + <!ELEMENT group (uid+, guid, password, name, members, cuaddr*, disable-calendar?)> <!ATTLIST group repeat CDATA "1"> - <!ELEMENT resource (uid, guid, password, name, cuaddr*, auto-schedule?, proxies?, read-only-proxies?)> + <!ELEMENT resource (uid+, guid, password, name, cuaddr*, auto-schedule?, proxies?, read-only-proxies?)> <!ATTLIST resource repeat CDATA "1"> - <!ELEMENT location (uid, guid, password, name, cuaddr*, auto-schedule?, proxies?, read-only-proxies?)> + <!ELEMENT location (uid+, guid, password, name, cuaddr*, auto-schedule?, proxies?, read-only-proxies?)> <!ATTLIST location repeat CDATA "1"> <!ELEMENT member (#PCDATA)> Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/accesslog.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/accesslog.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/accesslog.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2006-2008 Apple Inc. All rights reserved. +# Copyright (c) 2006-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -84,9 +84,9 @@ record = request.site.resource.getDirectory().recordWithUID(uid) if record: if record.recordType == DirectoryService.recordType_users: - return record.shortName + return record.shortNames[0] else: - return "(%s)%s" % (record.recordType, record.shortName,) + return "(%s)%s" % (record.recordType, record.shortNames[0],) else: return uid Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/apache.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/apache.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/apache.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2006-2007 Apple Inc. All rights reserved. +# Copyright (c) 2006-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -143,7 +143,7 @@ service = service, recordType = recordType, guid = None, - shortName = shortName, + shortNames = (shortName,), fullName = None, firstName = None, lastName = None, Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/appleopendirectory.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/appleopendirectory.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/appleopendirectory.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -170,11 +170,10 @@ ): yield GUID - def _calendarUserAddresses(self, recordType, recordName, recordData): + def _calendarUserAddresses(self, recordType, recordData): """ Extract specific attributes from the directory record for use as calendar user address. - @param recordName: a C{str} containing the record name being operated on. @param recordData: a C{dict} containing the attributes retrieved from the directory. @return: a C{set} of C{str} for each expanded calendar user address. """ @@ -475,12 +474,17 @@ for (recordShortName, value) in results: # 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) + recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID) + recordShortNames = value.get(dsattributes.kDSNAttrRecordName) + if isinstance(recordShortNames, str): + recordShortNames = (recordShortNames,) + else: + recordShortNames = tuple(recordShortNames) if recordShortNames else () + 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) + recordNodeName = value.get(dsattributes.kDSNAttrMetaNodeLocation) if not recordGUID: self.log_debug("Record (%s)%s in node %s has no GUID; ignoring." @@ -520,7 +524,7 @@ # Get calendar user addresses from directory record. if enabledForCalendaring: - calendarUserAddresses = self._calendarUserAddresses(recordType, recordShortName, value) + calendarUserAddresses = self._calendarUserAddresses(recordType, value) else: calendarUserAddresses = () @@ -568,7 +572,7 @@ recordType = recordType, guid = recordGUID, nodeName = recordNodeName, - shortName = recordShortName, + shortNames = recordShortNames, fullName = recordFullName, firstName = recordFirstName, lastName = recordLastName, @@ -584,39 +588,48 @@ def disableRecord(record): self.log_warn("Record disabled due to conflict (record name and GUID must match): %s" % (record,)) - shortName = record.shortName - guid = record.guid + shortNames = record.shortNames + guid = record.guid - disabledNames.add(shortName) + disabledNames.update(shortNames) disabledGUIDs.add(guid) - if shortName in records: - del records[shortName] + for shortName in shortNames: + if shortName in records: + del records[shortName] if guid in guids: del guids[guid] # Check for disabled items - if record.shortName in disabledNames or record.guid in disabledGUIDs: + if disabledNames.intersection(record.shortNames) or record.guid in disabledGUIDs: disableRecord(record) else: # Check for duplicate items and disable all names/guids for mismatched duplicates. - if record.shortName in records: - existing_record = records[record.shortName] - elif record.guid in guids: - existing_record = guids[record.guid] - else: - existing_record = None + existing_records = set() + for shortName in record.shortNames: + if shortName in records: + existing_records.add(records[shortName]) + if record.guid in guids: + existing_records.add(guids[record.guid]) - if existing_record is not None: - if record.guid != existing_record.guid or record.shortName != existing_record.shortName: - disableRecord(existing_record) + if existing_records: + disable = False + for existing_record in existing_records: + if record.guid != existing_record.guid or record.shortNames != existing_record.shortNames: + disable = True + break + + if disable: + for existing_record in existing_records: + disableRecord(existing_record) + if existing_record.enabledForCalendaring: + enabled_count -= 1 disableRecord(record) - - if existing_record.enabledForCalendaring: - enabled_count -= 1 - if record.shortName not in disabledNames: - records[record.shortName] = guids[record.guid] = record + if len(disabledNames.intersection(record.shortNames)) == 0: + guids[record.guid] = record + for shortName in record.shortNames: + records[shortName] = record self.log_debug("Added record %s to OD record cache" % (record,)) # Do group indexing if needed @@ -701,6 +714,7 @@ def _queryDirectory(self, recordType, lookup=None): attrs = [ dsattributes.kDS1AttrGeneratedUID, + dsattributes.kDSNAttrRecordName, dsattributes.kDS1AttrDistinguishedName, dsattributes.kDS1AttrFirstName, dsattributes.kDS1AttrLastName, @@ -817,7 +831,7 @@ Open Directory implementation of L{IDirectoryRecord}. """ def __init__( - self, service, recordType, guid, nodeName, shortName, fullName, + self, service, recordType, guid, nodeName, shortNames, fullName, firstName, lastName, emailAddresses, calendarUserAddresses, autoSchedule, enabledForCalendaring, memberGUIDs, proxyGUIDs, readOnlyProxyGUIDs, @@ -826,7 +840,7 @@ service = service, recordType = recordType, guid = guid, - shortName = shortName, + shortNames = shortNames, fullName = fullName, firstName = firstName, lastName = lastName, @@ -852,7 +866,7 @@ self.service.guid, location, self.guid, - self.shortName, + ",".join(self.shortNames), self.fullName ) @@ -875,7 +889,11 @@ for guid in self._proxyGUIDs: proxyRecord = self.service.recordWithGUID(guid) if proxyRecord is None: - self.log_error("No record for proxy in %s with GUID %s" % (self.shortName, guid)) + self.log_error("No record for proxy in (%s)%s with GUID %s" % ( + self.recordType, + self.shortNames[0], + guid, + )) else: yield proxyRecord @@ -892,7 +910,11 @@ for guid in self._readOnlyProxyGUIDs: proxyRecord = self.service.recordWithGUID(guid) if proxyRecord is None: - self.log_error("No record for proxy in %s with GUID %s" % (self.shortName, guid)) + self.log_error("No record for proxy in (%s)%s with GUID %s" % ( + self.recordType, + self.shortNames[0], + guid, + )) else: yield proxyRecord @@ -913,13 +935,13 @@ # Check with directory services try: - if opendirectory.authenticateUserBasic(self.service.directory, self.nodeName, self.shortName, credentials.password): + if opendirectory.authenticateUserBasic(self.service.directory, self.nodeName, self.shortNames[0], credentials.password): # Cache the password to avoid future DS queries self.password = credentials.password return True except opendirectory.ODError, e: self.log_error("Open Directory (node=%s) error while performing basic authentication for user %s: %s" - % (self.service.realmName, self.shortName, e)) + % (self.service.realmName, self.shortNames[0], e)) return False @@ -942,7 +964,7 @@ self.log_error( "Open Directory (node=%s) error while performing digest authentication for user %s: " "missing digest response field: %s in: %s" - % (self.service.realmName, self.shortName, e, credentials.fields) + % (self.service.realmName, self.shortNames[0], e, credentials.fields) ) return False @@ -956,7 +978,7 @@ if opendirectory.authenticateUserDigest( self.service.directory, self.nodeName, - self.shortName, + self.shortNames[0], challenge, response, credentials.method @@ -977,12 +999,12 @@ Challenge: %s Response: %s Method: %s -""" % (self.nodeName, self.shortName, challenge, response, credentials.method)) +""" % (self.nodeName, self.shortNames[0], challenge, response, credentials.method)) except opendirectory.ODError, e: self.log_error( "Open Directory (node=%s) error while performing digest authentication for user %s: %s" - % (self.service.realmName, self.shortName, e) + % (self.service.realmName, self.shortNames[0], e) ) return False Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/calendar.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/calendar.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/calendar.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2006-2007 Apple Inc. All rights reserved. +# Copyright (c) 2006-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -165,11 +165,14 @@ def listChildren(self): if config.EnablePrincipalListings: - return ( - record.shortName - for record in self.directory.listRecords(self.recordType) - if record.enabledForCalendaring - ) + + def _recordShortnameExpand(): + for record in self.directory.listRecords(self.recordType): + if record.enabledForCalendaring: + for shortName in record.shortNames: + yield shortName + + return _recordShortnameExpand() else: # Not a listable collection raise HTTPError(responsecode.FORBIDDEN) Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/calendaruserproxy.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/calendaruserproxy.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/calendaruserproxy.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -239,7 +239,7 @@ """---------------------\n""" """GUID: %s\n""" % (self.parent.record.guid,), """Record type: %s\n""" % (self.parent.record.recordType,), - """Short name: %s\n""" % (self.parent.record.shortName,), + """Short names: %s\n""" % (",".join(self.parent.record.shortNames,)), """Full name: %s\n""" % (self.parent.record.fullName,), """Principal UID: %s\n""" % (self.parent.principalUID(),), """Principal URL: %s\n""" % (format_link(self.parent.principalURL()),), Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/directory.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/directory.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/directory.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2006-2008 Apple Inc. All rights reserved. +# Copyright (c) 2006-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -251,22 +251,22 @@ self.service.guid, self.service.realmName, self.guid, - self.shortName, + ",".join(self.shortNames), self.fullName ) def __init__( - self, service, recordType, guid, shortName, fullName, + self, service, recordType, guid, shortNames, fullName, firstName, lastName, emailAddresses, calendarUserAddresses, autoSchedule, enabledForCalendaring=True, uid=None, ): assert service.realmName is not None assert recordType - assert shortName + assert shortNames and isinstance(shortNames, tuple) if not guid: - guid = uuidFromName(service.guid, "%s:%s" % (recordType, shortName)) + guid = uuidFromName(service.guid, "%s:%s" % (recordType, ",".join(shortNames))) if uid is None: uid = guid @@ -281,7 +281,7 @@ self.recordType = recordType self.guid = guid self.uid = uid - self.shortName = shortName + self.shortNames = shortNames self.fullName = fullName self.firstName = firstName self.lastName = lastName @@ -294,7 +294,7 @@ if not isinstance(other, DirectoryRecord): return NotImplemented - for attr in ("service", "recordType", "shortName", "guid"): + for attr in ("service", "recordType", "shortNames", "guid"): diff = cmp(getattr(self, attr), getattr(other, attr)) if diff != 0: return diff @@ -302,7 +302,7 @@ def __hash__(self): h = hash(self.__class__) - for attr in ("service", "recordType", "shortName", "guid", + for attr in ("service", "recordType", "shortNames", "guid", "enabledForCalendaring"): h = (h + hash(getattr(self, attr))) & sys.maxint Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/idirectory.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/idirectory.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/idirectory.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2006-2007 Apple Inc. All rights reserved. +# Copyright (c) 2006-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -94,7 +94,7 @@ recordType = Attribute("The type of this record.") guid = Attribute("The GUID of this record.") uid = Attribute("The UID of this record.") - shortName = Attribute("The name of this record.") + shortNames = Attribute("The names for 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.") Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/principal.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/principal.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/principal.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -33,6 +33,7 @@ from cgi import escape from urllib import unquote from urlparse import urlparse +import itertools from twisted.python.failure import Failure from twisted.internet.defer import inlineCallbacks, returnValue @@ -380,7 +381,13 @@ def listChildren(self): if config.EnablePrincipalListings: - return (record.shortName for record in self.directory.listRecords(self.recordType)) + + def _recordShortnameExpand(): + for record in self.directory.listRecords(self.recordType): + for shortName in record.shortNames: + yield shortName + + return _recordShortnameExpand() else: # Not a listable collection raise HTTPError(responsecode.FORBIDDEN) @@ -491,12 +498,12 @@ self.parent = parent self._url = url - self._alternate_urls = ( - joinURL(parent.parent.principalCollectionURL(), record.recordType, record.shortName) + slash, - ) + self._alternate_urls = tuple([ + joinURL(parent.parent.principalCollectionURL(), record.recordType, shortName) + slash for shortName in record.shortNames + ]) def __str__(self): - return "(%s) %s" % (self.record.recordType, self.record.shortName) + return "(%s) %s" % (self.record.recordType, self.record.shortNames[0]) def deadProperties(self): if not hasattr(self, "_dead_properties"): @@ -536,7 +543,7 @@ """---------------------\n""" """GUID: %s\n""" % (self.record.guid,), """Record type: %s\n""" % (self.record.recordType,), - """Short name: %s\n""" % (self.record.shortName,), + """Short names: %s\n""" % (",".join(self.record.shortNames),), """Full name: %s\n""" % (self.record.fullName,), """First name: %s\n""" % (self.record.firstName,), """Last name: %s\n""" % (self.record.lastName,), @@ -563,7 +570,7 @@ if self.record.fullName: return self.record.fullName else: - return self.record.shortName + return self.record.shortNames[0] ## # ACL @@ -749,7 +756,7 @@ """---------------------\n""" """GUID: %s\n""" % (self.record.guid,), """Record type: %s\n""" % (self.record.recordType,), - """Short name: %s\n""" % (self.record.shortName,), + """Short names: %s\n""" % (",".join(self.record.shortNames),), """Full name: %s\n""" % (self.record.fullName,), """First name: %s\n""" % (self.record.firstName,), """Last name: %s\n""" % (self.record.lastName,), @@ -905,20 +912,17 @@ return "".join(genlist()) def format_principals(principals): - def sort(a, b): - def sortkey(principal): + def recordKey(principal): + try: + record = principal.record + except AttributeError: try: - record = principal.record - except AttributeError: - try: - record = principal.parent.record - except: - return None + record = principal.parent.record + except: + return None - return [record.recordType, record.shortName] + return (record.recordType, record.shortNames[0]) - return cmp(sortkey(a), sortkey(b)) - def describe(principal): if hasattr(principal, "record"): return " - %s" % (principal.record.fullName,) @@ -928,7 +932,7 @@ return format_list( """<a href="%s">%s%s</a>""" % (principal.principalURL(), escape(str(principal)), describe(principal)) - for principal in sorted(principals, sort) + for principal in sorted(principals, key=recordKey) ) def format_link(url): Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/sqldb.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/sqldb.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/sqldb.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2006-2007 Apple Inc. All rights reserved. +# Copyright (c) 2006-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -173,7 +173,7 @@ def _add_to_db(self, record): # Do regular account entry recordType = record.recordType - shortName = record.shortName + shortName = record.shortNames[0] guid = record.guid password = record.password name = record.name @@ -348,7 +348,7 @@ service = service, recordType = recordType, guid = guid, - shortName = shortName, + shortNames = (shortName,), fullName = name, firstName = None, lastName = None, Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/sudo.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/sudo.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/sudo.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2006-2007 Apple Inc. All rights reserved. +# Copyright (c) 2006-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -127,7 +127,7 @@ service=service, recordType=recordType, guid=None, - shortName=shortName, + shortNames=(shortName,), fullName=shortName, firstName="", lastName="", Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_opendirectory.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_opendirectory.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_opendirectory.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2005-2007 Apple Inc. All rights reserved. +# Copyright (c) 2005-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -65,7 +65,7 @@ recordType = DirectoryService.recordType_users, guid = "B1F93EB1-DA93-4772-9141-81C250DA35B3", nodeName = "/LDAPv2/127.0.0.1", - shortName = "user", + shortNames = ("user",), fullName = "Some user", firstName = "Some", lastName = "User", Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_opendirectoryrecords.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_opendirectoryrecords.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_opendirectoryrecords.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2005-2008 Apple Inc. All rights reserved. +# Copyright (c) 2005-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -675,6 +675,7 @@ attrs = { dsattributes.kDS1AttrDistinguishedName: fullName, dsattributes.kDS1AttrGeneratedUID: guid, + dsattributes.kDSNAttrRecordName: shortName, dsattributes.kDSNAttrEMailAddress: email, dsattributes.kDSNAttrMetaNodeLocation: "/LDAPv3/127.0.0.1", } Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_principal.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_principal.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_principal.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2005-2007 Apple Inc. All rights reserved. +# Copyright (c) 2005-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -102,7 +102,7 @@ self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections)) shortNames = set(typeResource.listChildren()) - self.assertEquals(shortNames, set(r.shortName for r in directory.listRecords(recordType))) + self.assertEquals(shortNames, set(r.shortNames[0] for r in directory.listRecords(recordType))) for shortName in shortNames: #print " -> %s" % (shortName,) @@ -131,7 +131,7 @@ DirectoryPrincipalProvisioningResource.principalForShortName() """ for provisioningResource, recordType, recordResource, record in self._allRecords(): - principal = provisioningResource.principalForShortName(recordType, record.shortName) + principal = provisioningResource.principalForShortName(recordType, record.shortNames[0]) self.failIf(principal is None) self.assertEquals(record, principal.record) @@ -143,7 +143,7 @@ provisioningResource = self.principalRootResources[directory.__class__.__name__] for user in directory.listRecords(DirectoryService.recordType_users): - userResource = provisioningResource.principalForUser(user.shortName) + userResource = provisioningResource.principalForUser(user.shortNames[0]) self.failIf(userResource is None) self.assertEquals(user, userResource.record) @@ -193,7 +193,7 @@ self.failIf(principal is None) if record.enabledForCalendaring: self.assertEquals(record.autoSchedule, principal.autoSchedule()) - if record.shortName == "gemini": + if record.shortNames[0] == "gemini": self.assertTrue(principal.autoSchedule()) else: self.assertFalse(principal.autoSchedule()) Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_sudo.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_sudo.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_sudo.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2005-2007 Apple Inc. All rights reserved. +# Copyright (c) 2005-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -53,8 +53,8 @@ def test_listRecords(self): for record in self.service().listRecords(self.recordType): - self.failUnless(record.shortName in self.sudoers) - self.assertEqual(self.sudoers[record.shortName]['password'], + self.failUnless(record.shortNames[0] in self.sudoers) + self.assertEqual(self.sudoers[record.shortNames[0]]['password'], record.password) def test_recordWithShortName(self): Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_xmlfile.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_xmlfile.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/test_xmlfile.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2005-2007 Apple Inc. All rights reserved. +# Copyright (c) 2005-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -116,7 +116,7 @@ ( DirectoryService.recordType_resources , () ), ): self.assertEquals( - set(r.shortName for r in service.listRecords(recordType)), + set(r.shortNames[0] for r in service.listRecords(recordType)), set(expectedRecords) ) @@ -143,7 +143,7 @@ ( DirectoryService.recordType_resources , () ), ): self.assertEquals( - set(r.shortName for r in service.listRecords(recordType)), + set(r.shortNames[0] for r in service.listRecords(recordType)), set(expectedRecords) ) self.assertTrue(service.recordWithShortName(DirectoryService.recordType_locations, "my office").autoSchedule) @@ -166,7 +166,7 @@ ) def _findRecords(): - set(r.shortName for r in service.listRecords(DirectoryService.recordType_users)) + set(r.shortNames[0] for r in service.listRecords(DirectoryService.recordType_users)) self.assertRaises(ValueError, _findRecords) @@ -198,7 +198,7 @@ ( DirectoryService.recordType_resources , () ), ): self.assertEquals( - set(r.shortName for r in service.listRecords(recordType)), + set(r.shortNames[0] for r in service.listRecords(recordType)), set(expectedRecords) ) self.assertTrue(service.recordWithShortName(DirectoryService.recordType_groups, "enabled").enabledForCalendaring) @@ -222,7 +222,7 @@ ) def _findRecords(): - set(r.shortName for r in service.listRecords(DirectoryService.recordType_users)) + set(r.shortNames[0] for r in service.listRecords(DirectoryService.recordType_users)) self.assertRaises(ValueError, _findRecords) @@ -257,7 +257,7 @@ ( DirectoryService.recordType_resources , () ), ): self.assertEquals( - set(r.shortName for r in service.listRecords(recordType)), + set(r.shortNames[0] for r in service.listRecords(recordType)), set(expectedRecords) ) self.assertEqual(set([("users", "test",)],), service.recordWithShortName(DirectoryService.recordType_locations, "my office")._proxies) @@ -283,6 +283,6 @@ ) def _findRecords(): - set(r.shortName for r in service.listRecords(DirectoryService.recordType_users)) + set(r.shortNames[0] for r in service.listRecords(DirectoryService.recordType_users)) self.assertRaises(ValueError, _findRecords) Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/util.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/util.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/test/util.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2005-2007 Apple Inc. All rights reserved. +# Copyright (c) 2005-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -163,7 +163,7 @@ for group, info in self.groups.iteritems(): prefix = info.get("prefix", "") groupRecord = service.recordWithShortName(prefix + DirectoryService.recordType_groups, group) - result = set((m.recordType, prefix + m.shortName) for m in groupRecord.members()) + result = set((m.recordType, prefix + m.shortNames[0]) for m in groupRecord.members()) expected = set(self.groups[group]["members"]) self.assertEquals( result, expected, @@ -187,7 +187,7 @@ for shortName, info in data.iteritems(): prefix = info.get("prefix", "") record = service.recordWithShortName(prefix + recordType, shortName) - result = set(prefix + g.shortName for g in record.groups()) + result = set(prefix + g.shortNames[0] for g in record.groups()) expected = set(g for g in self.groups if (record.recordType, shortName) in self.groups[g]["members"]) self.assertEquals( result, expected, @@ -204,7 +204,7 @@ continue assert records is not None, "%r(%r) returned None" % (service.listRecords, recordType) for record in records: - names.add(prefix + record.shortName) + names.add(prefix + record.shortNames[0]) return names @@ -238,7 +238,7 @@ else: prefix = "" - self.assertEquals(prefix + record.shortName, shortName) + self.assertEquals(prefix + record.shortNames[0], shortName) self.assertEquals(set(record.calendarUserAddresses), addresses) if value("guid"): Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/wiki.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/wiki.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/wiki.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2006-2007 Apple Inc. All rights reserved. +# Copyright (c) 2006-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -121,7 +121,7 @@ recordType=recordType, guid=None, uid="%s%s" % (WikiDirectoryService.UIDPrefix, shortName), - shortName=shortName, + shortNames=(shortName,), fullName=shortName, firstName="", lastName="", @@ -146,7 +146,7 @@ wikiConfig = config.Authentication.Wiki userID = "unauthenticated" - wikiID = resource.record.shortName + wikiID = resource.record.shortNames[0] try: url = str(request.authzUser.children[0]) Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/xmlaccountsparser.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/xmlaccountsparser.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/xmlaccountsparser.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2006-2007 Apple Inc. All rights reserved. +# Copyright (c) 2006-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -106,22 +106,22 @@ def updateMembership(group): # Update group membership for recordType, shortName in group.members: - item = self.items[recordType].get(shortName, None) + item = self.items[recordType].get(shortName) if item is not None: - item.groups.add(group.shortName) + item.groups.add(group.shortNames[0]) def updateProxyFor(proxier): # Update proxy membership for recordType, shortName in proxier.proxies: - item = self.items[recordType].get(shortName, None) + item = self.items[recordType].get(shortName) if item is not None: - item.proxyFor.add((proxier.recordType, proxier.shortName)) + item.proxyFor.add((proxier.recordType, proxier.shortNames[0])) # Update read-only proxy membership for recordType, shortName in proxier.readOnlyProxies: - item = self.items[recordType].get(shortName, None) + item = self.items[recordType].get(shortName) if item is not None: - item.readOnlyProxyFor.add((proxier.recordType, proxier.shortName)) + item.readOnlyProxyFor.add((proxier.recordType, proxier.shortNames[0])) for child in node._get_childNodes(): child_name = child._get_localName() @@ -143,9 +143,9 @@ if repeat > 1: for i in xrange(1, repeat+1): newprincipal = principal.repeat(i) - self.items[recordType][newprincipal.shortName] = newprincipal + self.items[recordType][newprincipal.shortNames[0]] = newprincipal else: - self.items[recordType][principal.shortName] = principal + self.items[recordType][principal.shortNames[0]] = principal # Do reverse membership mapping only after all records have been read in for records in self.items.itervalues(): @@ -162,7 +162,7 @@ @param recordType: record type for directory entry. """ self.recordType = recordType - self.shortName = None + self.shortNames = [] self.guid = None self.password = None self.name = None @@ -185,10 +185,12 @@ done on them with the numeric value provided. @param ctr: an integer to substitute into text. """ - if self.shortName.find("%") != -1: - shortName = self.shortName % ctr - else: - shortName = self.shortName + shortNames = [] + for shortName in self.shortNames: + if shortName.find("%") != -1: + shortNames.append(shortName % ctr) + else: + shortNames.append(shortName) if self.guid and self.guid.find("%") != -1: guid = self.guid % ctr else: @@ -223,7 +225,7 @@ calendarUserAddresses.add(cuaddr) result = XMLAccountRecord(self.recordType) - result.shortName = shortName + result.shortNames = shortNames result.guid = guid result.password = password result.name = name @@ -245,7 +247,7 @@ continue elif child_name == ELEMENT_SHORTNAME: if child.firstChild is not None: - self.shortName = child.firstChild.data.encode("utf-8") + self.shortNames.append(child.firstChild.data.encode("utf-8")) elif child_name == ELEMENT_GUID: if child.firstChild is not None: guid = child.firstChild.data.encode("utf-8") Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/xmlfile.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/xmlfile.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/directory/xmlfile.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2006-2008 Apple Inc. All rights reserved. +# Copyright (c) 2006-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -64,30 +64,30 @@ return recordTypes def listRecords(self, recordType): - for entryShortName, xmlPrincipal in self._entriesForRecordType(recordType): + for _ignore_entryShortName, xmlPrincipal in self._entriesForRecordType(recordType): yield XMLDirectoryRecord( service = self, recordType = recordType, - shortName = entryShortName, + shortNames = tuple(xmlPrincipal.shortNames), xmlPrincipal = xmlPrincipal, ) def recordWithShortName(self, recordType, shortName): - for entryShortName, xmlprincipal in self._entriesForRecordType(recordType): - if entryShortName == shortName: + for _ignore_entryShortName, xmlPrincipal in self._entriesForRecordType(recordType): + if shortName in xmlPrincipal.shortNames: return XMLDirectoryRecord( service = self, recordType = recordType, - shortName = entryShortName, - xmlPrincipal = xmlprincipal, + shortNames = tuple(xmlPrincipal.shortNames), + xmlPrincipal = xmlPrincipal, ) return None def _entriesForRecordType(self, recordType): try: - for entry in sorted(self._accounts()[recordType].itervalues(), key=lambda x: x.shortName): - yield entry.shortName, entry + for shortName, entry in sorted(self._accounts()[recordType].iteritems(), key=lambda x: x[0]): + yield shortName, entry except KeyError: return @@ -108,12 +108,12 @@ """ XML based implementation implementation of L{IDirectoryRecord}. """ - def __init__(self, service, recordType, shortName, xmlPrincipal): + def __init__(self, service, recordType, shortNames, xmlPrincipal): super(XMLDirectoryRecord, self).__init__( service = service, recordType = recordType, guid = xmlPrincipal.guid, - shortName = shortName, + shortNames = shortNames, fullName = xmlPrincipal.name, firstName = xmlPrincipal.firstName, lastName = xmlPrincipal.lastName, Modified: CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/static.py =================================================================== --- CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/static.py 2009-02-04 17:52:18 UTC (rev 3634) +++ CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/twistedcaldav/static.py 2009-02-04 19:04:46 UTC (rev 3635) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2005-2008 Apple Inc. All rights reserved. +# Copyright (c) 2005-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -48,6 +48,7 @@ from twisted.web2.dav.fileop import mkcollection, rmdir from twisted.web2.dav.http import ErrorResponse from twisted.web2.dav.idav import IDAVResource +from twisted.web2.dav.noneprops import NonePropertyStore from twisted.web2.dav.resource import AccessDeniedError from twisted.web2.dav.resource import davPrivilegeSet from twisted.web2.dav.util import parentForURL, bindMethods @@ -610,7 +611,7 @@ # Pre 2.0: All in one directory self.fp.child(name), # Pre 1.2: In types hierarchy instead of the GUID hierarchy - self.parent.getChild(record.recordType).fp.child(record.shortName), + self.parent.getChild(record.recordType).fp.child(record.shortNames[0]), ): if oldPath.exists(): # The child exists at an old location. Move to new location. @@ -864,9 +865,6 @@ def __init__(self, path, parent): CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections()) IScheduleInboxResource.__init__(self, parent) - - self.fp.open("w").close() - self.fp.restat(False) def __repr__(self): return "<%s (server-to-server inbox resource): %s>" % (self.__class__.__name__, self.fp.path) @@ -892,8 +890,10 @@ (caldav_namespace, "calendar-collection-location-ok") ) - def hasDeadProperty(self, name): - return False + def deadProperties(self): + if not hasattr(self, "_dead_properties"): + self._dead_properties = NonePropertyStore(self) + return self._dead_properties def etag(self): return None @@ -1015,8 +1015,10 @@ (caldav_namespace, "calendar-collection-location-ok") ) - def hasDeadProperty(self, name): - return False + def deadProperties(self): + if not hasattr(self, "_dead_properties"): + self._dead_properties = NonePropertyStore(self) + return self._dead_properties def etag(self): return None
participants (1)
-
source_changes@macosforge.org