<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[13046] CalendarServer/branches/users/sagen/move2who-4</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/13046">13046</a></dd>
<dt>Author</dt> <dd>gaya@apple.com</dd>
<dt>Date</dt> <dd>2014-03-28 21:55:59 -0700 (Fri, 28 Mar 2014)</dd>
</dl>
<h3>Log Message</h3>
<pre>10 CDT CardDAV/report.xml directory gateway tests now succeed</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserssagenmove2who4twistedcaldavdirectorybackedaddressbookpy">CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directorybackedaddressbook.py</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserssagenmove2who4txdavwhovcardpy">CalendarServer/branches/users/sagen/move2who-4/txdav/who/vcard.py</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserssagenmove2who4twistedcaldavdirectorybackedaddressbookpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directorybackedaddressbook.py (13045 => 13046)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directorybackedaddressbook.py        2014-03-29 01:35:23 UTC (rev 13045)
+++ CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directorybackedaddressbook.py        2014-03-29 04:55:59 UTC (rev 13046)
</span><span class="lines">@@ -22,10 +22,12 @@
</span><span class="cx"> "DirectoryBackedAddressBookResource",
</span><span class="cx"> ]
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twext.who.expression import Operand, MatchType, MatchFlags, \
</span><span class="cx"> MatchExpression, CompoundExpression
</span><span class="cx"> from twext.who.idirectory import FieldName, RecordType
</span><ins>+from twisted.internet.defer import deferredGenerator
</ins><span class="cx"> from twisted.internet.defer import succeed, inlineCallbacks, maybeDeferred, \
</span><span class="cx"> returnValue
</span><span class="cx"> from twisted.python.constants import NamedConstant
</span><span class="lines">@@ -34,18 +36,25 @@
</span><span class="cx"> from twistedcaldav.resource import CalDAVResource
</span><span class="cx"> from txdav.carddav.datastore.query.filter import IsNotDefined, TextMatch, \
</span><span class="cx"> ParameterFilter
</span><del>-from txdav.who.idirectory import FieldName as CalFieldName, \
- RecordType as CalRecordType
</del><ins>+from txdav.who.idirectory import FieldName as CalFieldName
+from txdav.who.vcard import recordTypeToVCardKindMap, vCardPropToParamMap, \
+ vCardConstantProperties, vCardFromRecord
</ins><span class="cx"> from txdav.xml import element as davxml
</span><ins>+from txdav.xml.base import twisted_dav_namespace, dav_namespace, parse_date, \
+ twisted_private_namespace
</ins><span class="cx"> from txweb2 import responsecode
</span><ins>+from txweb2.dav.resource import DAVPropertyMixIn
</ins><span class="cx"> from txweb2.dav.resource import TwistedACLInheritable
</span><ins>+from txweb2.dav.util import joinURL
</ins><span class="cx"> from txweb2.http import HTTPError, StatusResponse
</span><ins>+from txweb2.http_headers import MimeType, generateContentType, ETag
+from xmlrpclib import datetime
+import hashlib
</ins><span class="cx"> import uuid
</span><span class="cx">
</span><span class="cx"> log = Logger()
</span><span class="cx">
</span><span class="cx">
</span><del>-
</del><span class="cx"> class DirectoryBackedAddressBookResource (CalDAVResource):
</span><span class="cx"> """
</span><span class="cx"> Directory-backed address book
</span><span class="lines">@@ -156,44 +165,31 @@
</span><span class="cx"> limited = False
</span><span class="cx"> maxQueryRecords = 0
</span><span class="cx">
</span><del>- schema = {
</del><ins>+ searchableFields = {
</ins><span class="cx"> RecordType.user: {
</span><del>- "FN": (
- FieldName.fullNames,
- FieldName.shortNames,
- ),
- "N": (
- FieldName.fullNames,
- FieldName.shortNames,
- ),
</del><ins>+ "FN": FieldName.fullNames,
+ "N": FieldName.fullNames,
</ins><span class="cx"> "EMAIL": FieldName.emailAddresses,
</span><span class="cx"> "UID": FieldName.uid,
</span><del>- "ADR": CalFieldName.streetAddress,
</del><ins>+ "ADR": (
+ CalFieldName.streetAddress,
+ CalFieldName.floor,
+ )
</ins><span class="cx"> },
</span><span class="cx"> RecordType.group: {
</span><del>- "FN": (
- FieldName.fullNames,
- FieldName.shortNames,
- ),
- "N": (
- FieldName.fullNames,
- FieldName.shortNames,
- ),
</del><ins>+ "FN": FieldName.fullNames,
+ "N": FieldName.fullNames,
</ins><span class="cx"> "EMAIL": FieldName.emailAddresses,
</span><span class="cx"> "UID": FieldName.uid,
</span><del>- "ADR": CalFieldName.streetAddress,
- # LATER "X-ADDRESSBOOKSERVER-MEMBER": FieldName.members,
</del><ins>+ "ADR": (
+ CalFieldName.streetAddress,
+ CalFieldName.floor,
+ )
+ # LATER "X-ADDRESSBOOKSERVER-MEMBER": FieldName.membersUIDs,
</ins><span class="cx"> },
</span><span class="cx"> }
</span><span class="cx">
</span><del>- recordTypeToKindMap = {
- RecordType.user: "individual",
- RecordType.group: "group",
- CalRecordType.location: "location",
- CalRecordType.resource: "device",
- }
-
- allowedRecordTypes = set(self.directory.recordTypes()) & set(recordTypeToKindMap.keys()) & set(schema.keys())
</del><ins>+ allowedRecordTypes = set(self.directory.recordTypes()) & set(recordTypeToVCardKindMap.keys()) & set(searchableFields.keys())
</ins><span class="cx"> log.debug("doAddressBookDirectoryQuery: allowedRecordTypes={allowedRecordTypes}", allowedRecordTypes=allowedRecordTypes,)
</span><span class="cx">
</span><span class="cx"> expressions = []
</span><span class="lines">@@ -201,14 +197,14 @@
</span><span class="cx">
</span><span class="cx"> #log.debug("doAddressBookDirectoryQuery: recordType={recordType}", recordType=recordType,)
</span><span class="cx">
</span><del>- vcardPropToRecordFieldMap = schema[recordType]
- kind = recordTypeToKindMap[recordType]
- constantProperties = ABDirectoryQueryResult.constantProperties.copy()
</del><ins>+ vcardPropToRecordFieldMap = searchableFields[recordType]
+ kind = recordTypeToVCardKindMap[recordType]
+ constantProperties = vCardConstantProperties.copy()
</ins><span class="cx"> constantProperties["KIND"] = kind
</span><span class="cx"> # add KIND as constant so that query can be skipped if addressBookFilter needs a different kind
</span><span class="cx">
</span><span class="cx"> propNames, expression = expressionFromABFilter(addressBookFilter, vcardPropToRecordFieldMap, recordType=recordType, constantProperties=constantProperties)
</span><del>- log.debug("doAddressBookDirectoryQuery: recordType={recordType}, expression={expression!r}, propNames={propNames}", recordType=recordType, expression=expression, propNames=propNames)
</del><ins>+ #log.debug("doAddressBookDirectoryQuery: recordType={recordType}, expression={expression!r}, propNames={propNames}", recordType=recordType, expression=expression, propNames=propNames)
</ins><span class="cx"> if expression:
</span><span class="cx"> expressions.append(expression)
</span><span class="cx">
</span><span class="lines">@@ -229,14 +225,14 @@
</span><span class="cx"> log.debug("doAddressBookDirectoryQuery: #records={n}, records={records!r}", n=len(records), records=records)
</span><span class="cx"> queryLimited = False
</span><span class="cx">
</span><del>- vCardsResults = []#[ABDirectoryQueryResult(self, record) for record in records]
</del><ins>+ vCardsResults = [ABDirectoryQueryResult(self, record) for record in records]
</ins><span class="cx">
</span><span class="cx"> filteredResults = []
</span><span class="cx"> for vCardResult in vCardsResults:
</span><span class="cx"> if addressBookFilter.match(vCardResult.vCard()):
</span><span class="cx"> filteredResults.append(vCardResult)
</span><span class="cx"> else:
</span><del>- log.debug("doAddressBookQuery: vCard did not match filter: {vCard}", vcard=vCardResult.vCard())
</del><ins>+ log.debug("doAddressBookDirectoryQuery: vCard did not match filter:\n{vcard}", vcard=vCardResult.vCard())
</ins><span class="cx">
</span><span class="cx"> #no more results
</span><span class="cx"> if not queryLimited:
</span><span class="lines">@@ -262,7 +258,7 @@
</span><span class="cx"> results = sorted(list(filteredResults), key=lambda result: result.vCard().propertyValue("UID"))
</span><span class="cx"> limited = maxResults and len(results) >= maxResults
</span><span class="cx">
</span><del>- log.info("limited={l} result count={n}", l=limited, n=len(results))
</del><ins>+ log.info("limited={l} #results={n}", l=limited, n=len(results))
</ins><span class="cx"> returnValue((results, limited,))
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -280,7 +276,7 @@
</span><span class="cx"> if isinstance(property, carddavxml.AddressData):
</span><span class="cx"> for addressProperty in property.children:
</span><span class="cx"> if isinstance(addressProperty, carddavxml.Property):
</span><del>- propertyNames += [addressProperty.attributes["name"], ]
</del><ins>+ propertyNames.append(addressProperty.attributes["name"])
</ins><span class="cx">
</span><span class="cx"> elif property.qname() == ("DAV:", "getetag"):
</span><span class="cx"> # for a real etag == md5(vCard), we need all properties
</span><span class="lines">@@ -302,6 +298,14 @@
</span><span class="cx">
</span><span class="cx"> def propFilterListQuery(filterAllOf, propFilters):
</span><span class="cx">
</span><ins>+ """
+ Create an expression for a list of prop-filter elements.
+
+ @param filterAllOf: the C{True} if parent filter test is "allof"
+ @param propFilters: the C{list} of L{ComponentFilter} elements.
+ @return: (filterProperyNames, expressions) tuple. expression==True means list all results, expression==False means no results
+ """
+
</ins><span class="cx"> def combineExpressionLists(expressionList, allOf, addedExpressions):
</span><span class="cx"> """
</span><span class="cx"> deal with the 4-state logic
</span><span class="lines">@@ -331,7 +335,7 @@
</span><span class="cx"> expressionList = addedExpressions # False or addedExpressions is addedExpressions
</span><span class="cx"> #else False and addedExpressions is False
</span><span class="cx"> else:
</span><del>- expressionList += addedExpressions
</del><ins>+ expressionList.extend(addedExpressions)
</ins><span class="cx"> return expressionList
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -365,7 +369,7 @@
</span><span class="cx">
</span><span class="cx"> def paramFilterElementExpression(propFilterAllOf, paramFilterElement): #@UnusedVariable
</span><span class="cx">
</span><del>- params = ABDirectoryQueryResult.vcardPropToParamMap.get(propFilter.filter_name.upper())
</del><ins>+ params = vCardPropToParamMap.get(propFilter.filter_name.upper())
</ins><span class="cx"> defined = params and paramFilterElement.filter_name.upper() in params
</span><span class="cx">
</span><span class="cx"> #defined test
</span><span class="lines">@@ -497,13 +501,6 @@
</span><span class="cx"> return propFilterExpressions
</span><span class="cx"> #end propFilterExpression
</span><span class="cx">
</span><del>- """
- Create an expression for a list of prop-filter elements.
-
- @param filterAllOf: the C{True} if parent filter test is "allof"
- @param propFilters: the C{list} of L{ComponentFilter} elements.
- @return: (filterProperyNames, expressions) tuple. expression==True means list all results, expression==False means no results
- """
</del><span class="cx"> expressions = None
</span><span class="cx"> for propFilter in propFilters:
</span><span class="cx">
</span><span class="lines">@@ -515,7 +512,7 @@
</span><span class="cx"> break
</span><span class="cx">
</span><span class="cx"> # convert to needsAllRecords to return
</span><del>- log.debug("expressionFromABFilter: expressions={q!r}", q=expressions,)
</del><ins>+ # log.debug("expressionFromABFilter: expressions={q!r}", q=expressions,)
</ins><span class="cx"> if isinstance(expressions, list):
</span><span class="cx"> expressions = list(set(expressions))
</span><span class="cx"> if len(expressions) > 1:
</span><span class="lines">@@ -534,8 +531,6 @@
</span><span class="cx">
</span><span class="cx"> return (tuple(set(properties)), expr)
</span><span class="cx">
</span><del>- # Assume the filter is valid
-
</del><span class="cx"> # Top-level filter contains zero or more prop-filters
</span><span class="cx"> properties = tuple()
</span><span class="cx"> expression = None
</span><span class="lines">@@ -559,228 +554,19 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-#===============================================================================
-# Taken from obsolete twistedcaldav.directory.opendirctorybacker
-# Work in Progress
-#===============================================================================
-
-from calendarserver.platform.darwin.od import dsattributes
-from pycalendar.datetime import DateTime
-from pycalendar.vcard.adr import Adr
-from pycalendar.vcard.n import N
-from twisted.internet.defer import deferredGenerator
-from twistedcaldav.vcard import Component, Property, vCardProductID
-from txdav.xml.base import twisted_dav_namespace, dav_namespace, parse_date, twisted_private_namespace
-from txweb2.dav.resource import DAVPropertyMixIn
-from txweb2.dav.util import joinURL
-from txweb2.http_headers import MimeType, generateContentType, ETag
-from xmlrpclib import datetime
-import hashlib
-
-addSourceProperty = False
-
-
</del><span class="cx"> class ABDirectoryQueryResult(DAVPropertyMixIn):
</span><span class="cx"> """
</span><span class="cx"> Result from ab query report or multiget on directory
</span><span class="cx"> """
</span><span class="cx">
</span><del>- log = Logger()
-
- # od attributes that may contribute to vcard properties
- # will be used to translate vCard queries to od queries
-
- vcardPropToDSAttrMap = {
-
- "FN": [
- dsattributes.kDS1AttrFirstName,
- dsattributes.kDS1AttrLastName,
- dsattributes.kDS1AttrMiddleName,
- dsattributes.kDSNAttrNamePrefix,
- dsattributes.kDSNAttrNameSuffix,
- dsattributes.kDS1AttrDistinguishedName,
- dsattributes.kDSNAttrRecordName,
- ],
- "N": [
- dsattributes.kDS1AttrFirstName,
- dsattributes.kDS1AttrLastName,
- dsattributes.kDS1AttrMiddleName,
- dsattributes.kDSNAttrNamePrefix,
- dsattributes.kDSNAttrNameSuffix,
- dsattributes.kDS1AttrDistinguishedName,
- dsattributes.kDSNAttrRecordName,
- ],
- "NICKNAME": [
- dsattributes.kDSNAttrNickName,
- ],
- # no binary searching
- "PHOTO": [
- (dsattributes.kDSNAttrJPEGPhoto, "base64"),
- ],
- "BDAY": [
- dsattributes.kDS1AttrBirthday,
- ],
- "ADR": [
- dsattributes.kDSNAttrBuilding,
- dsattributes.kDSNAttrStreet,
- dsattributes.kDSNAttrCity,
- dsattributes.kDSNAttrState,
- dsattributes.kDSNAttrPostalCode,
- dsattributes.kDSNAttrCountry,
- ],
- "LABEL": [
- dsattributes.kDSNAttrPostalAddress,
- dsattributes.kDSNAttrPostalAddressContacts,
- dsattributes.kDSNAttrAddressLine1,
- dsattributes.kDSNAttrAddressLine2,
- dsattributes.kDSNAttrAddressLine3,
- ],
- "TEL": [
- dsattributes.kDSNAttrPhoneNumber,
- dsattributes.kDSNAttrMobileNumber,
- dsattributes.kDSNAttrPagerNumber,
- dsattributes.kDSNAttrHomePhoneNumber,
- dsattributes.kDSNAttrPhoneContacts,
- dsattributes.kDSNAttrFaxNumber,
- #dsattributes.kDSNAttrAreaCode,
- ],
- "EMAIL": [
- dsattributes.kDSNAttrEMailAddress,
- dsattributes.kDSNAttrEMailContacts,
- ],
- "GEO": [
- dsattributes.kDSNAttrMapCoordinates,
- ],
- "TITLE": [
- dsattributes.kDSNAttrJobTitle,
- ],
- "ORG": [
- dsattributes.kDSNAttrCompany,
- dsattributes.kDSNAttrOrganizationName,
- dsattributes.kDSNAttrDepartment,
- ],
- "NOTE": [
- dsattributes.kDS1AttrComment,
- dsattributes.kDS1AttrNote,
- ],
- "REV": [
- dsattributes.kDS1AttrModificationTimestamp,
- ],
- "UID": [
- dsattributes.kDS1AttrGeneratedUID,
- dsattributes.kDSNAttrRecordName,
- ],
- "URL": [
- dsattributes.kDS1AttrWeblogURI,
- dsattributes.kDSNAttrURL,
- ],
- "KEY": [
- (dsattributes.kDSNAttrPGPPublicKey, "base64"),
- (dsattributes.kDS1AttrUserCertificate, "base64"),
- (dsattributes.kDS1AttrUserPKCS12Data, "base64"),
- (dsattributes.kDS1AttrUserSMIMECertificate, "base64"),
- ],
- "IMPP": [
- dsattributes.kDSNAttrIMHandle,
- ],
- "X-ABRELATEDNAMES": [
- dsattributes.kDSNAttrRelationships,
- ],
- "SOURCE": [
- dsattributes.kDS1AttrGeneratedUID,
- dsattributes.kDSNAttrRecordName,
- ],
- }
-
- allDSQueryAttributes = list(set([attr for lookupAttributes in vcardPropToDSAttrMap.values()
- for attr in lookupAttributes]))
- binaryDSAttrNames = [attr[0] for attr in allDSQueryAttributes
- if isinstance(attr, tuple)]
- stringDSAttrNames = [attr for attr in allDSQueryAttributes
- if isinstance(attr, str)]
- allDSAttrNames = stringDSAttrNames + binaryDSAttrNames
-
- # all possible generated parameters.
- vcardPropToParamMap = {
- "PHOTO": {"ENCODING": ("B",), "TYPE": ("JPEG",), },
- "ADR": {"TYPE": ("WORK", "PREF", "POSTAL", "PARCEL",), },
- "LABEL": {"TYPE": ("POSTAL", "PARCEL",)},
- "TEL": {"TYPE": None, }, # None means param can contain can be anything
- "EMAIL": {"TYPE": None, },
- "KEY": {"ENCODING": ("B",), "TYPE": ("PGPPUBILICKEY", "USERCERTIFICATE", "USERPKCS12DATA", "USERSMIMECERTIFICATE",)},
- "URL": {"TYPE": ("WEBLOG", "HOMEPAGE",)},
- "IMPP": {"TYPE": ("PREF",), "X-SERVICE-TYPE": None, },
- "X-ABRELATEDNAMES": {"TYPE": None, },
- "X-AIM": {"TYPE": ("PREF",), },
- "X-JABBER": {"TYPE": ("PREF",), },
- "X-MSN": {"TYPE": ("PREF",), },
- "X-ICQ": {"TYPE": ("PREF",), },
- }
-
- uidSeparator = "-cf07a1a2-"
-
- constantProperties = {
- # 3.6.3 PRODID Type Definition
- "PRODID": vCardProductID,
- # 3.6.9 VERSION Type Definition
- "VERSION": "3.0",
- }
-
-
- def __init__(self, directoryBackedAddressBook, recordAttributes,
</del><ins>+ def __init__(self, directoryBackedAddressBook, record,
</ins><span class="cx"> kind=None,
</span><del>- additionalVCardProps=None,
</del><ins>+ addProps=None,
</ins><span class="cx"> ):
</span><span class="cx">
</span><del>- self.log.debug("directoryBackedAddressBook={directoryBackedAddressBook}, attributes={attributes}, additionalVCardProps={additionalVCardProps}",
- directoryBackedAddressBook=directoryBackedAddressBook, attributes=recordAttributes, additionalVCardProps=additionalVCardProps,)
-
- constantProperties = ABDirectoryQueryResult.constantProperties.copy()
- if additionalVCardProps:
- for key, value in additionalVCardProps.iteritems():
- if key not in constantProperties:
- constantProperties[key] = value
- self.constantProperties = constantProperties
- self.log.debug("directoryBackedAddressBook={directoryBackedAddressBook}, attributes={attributes}, constantProperties={constantProperties}",
- directoryBackedAddressBook=directoryBackedAddressBook, attributes=recordAttributes, constantProperties=self.constantProperties,)
-
</del><span class="cx"> self._directoryBackedAddressBook = directoryBackedAddressBook
</span><del>- self._vCard = None
-
- #clean attributes
- self.attributes = {}
- for key, values in recordAttributes.items():
- if key in ABDirectoryQueryResult.stringDSAttrNames:
- if isinstance(values, list):
- self.attributes[key] = [removeControlChars(val).decode("utf8") for val in values]
- else:
- self.attributes[key] = removeControlChars(values).decode("utf8")
- else:
- self.attributes[key] = values
-
- # find or create guid
- guid = self.firstValueForAttribute(dsattributes.kDS1AttrGeneratedUID)
- if not guid:
- nameUUIDStr = "".join(self.firstValueForAttribute(dsattributes.kDSNAttrRecordName).encode("base64").split("\n"))
- guid = ABDirectoryQueryResult.uidSeparator.join(["00000000", nameUUIDStr, ])
- #guid = ABDirectoryQueryResult.uidSeparator.join(["d9a8e41b", nameUUIDStr,])
-
- self.attributes[dsattributes.kDS1AttrGeneratedUID] = guid
-
- if not kind:
- dsRecordTypeToKindMap = {
- #dsattributes.kDSStdRecordTypePeople:"individual",
- #dsattributes.kDSStdRecordTypeUsers:"individual",
- dsattributes.kDSStdRecordTypeGroups: "group",
- dsattributes.kDSStdRecordTypeLocations: "location",
- dsattributes.kDSStdRecordTypeResources: "device",
- }
- recordType = self.firstValueForAttribute(dsattributes.kDSNAttrRecordType)
- kind = dsRecordTypeToKindMap.get(recordType, "individual")
- self.kind = kind.lower()
-
</del><span class="cx"> #generate a vCard here. May throw an exception
</span><del>- self.vCard()
</del><ins>+ self._vCard = vCardFromRecord(record, kind, addProps, directoryBackedAddressBook.uri)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> def __repr__(self):
</span><span class="lines">@@ -790,572 +576,16 @@
</span><span class="cx"> uid=self.vCard().propertyValue("UID")
</span><span class="cx"> )
</span><span class="cx">
</span><del>-
</del><ins>+ '''
</ins><span class="cx"> def __hash__(self):
</span><span class="cx"> s = "".join([
</span><span class="cx"> "{attr}:{values}".format(attr=attribute, values=self.valuesForAttribute(attribute),)
</span><span class="cx"> for attribute in self.attributes
</span><span class="cx"> ])
</span><span class="cx"> return hash(s)
</span><ins>+ '''
</ins><span class="cx">
</span><del>-
- def hasAttribute(self, attributeName):
- return self.valuesForAttribute(attributeName, None) is not None
-
-
- def valuesForAttribute(self, attributeName, default_values=[]):
- values = self.attributes.get(attributeName)
- if (values is None):
- return default_values
- elif not isinstance(values, list):
- values = [values, ]
-
- # ds templates often return empty attribute values
- # get rid of them here
- nonEmptyValues = [(value.encode("utf-8") if isinstance(value, unicode) else value) for value in values if len(value) > 0]
-
- if len(nonEmptyValues) > 0:
- return nonEmptyValues
- else:
- return default_values
-
-
- def firstValueForAttribute(self, attributeName, default_value=""):
- values = self.attributes.get(attributeName)
- if values is None:
- return default_value
- elif isinstance(values, list):
- return values[0].encode("utf_8") if isinstance(values[0], unicode) else values[0]
- else:
- return values.encode("utf_8") if isinstance(values, unicode) else values
-
-
- def joinedValuesForAttribute(self, attributeName, separator=",", default_string=""):
- values = self.valuesForAttribute(attributeName, None)
- if not values:
- return default_string
- else:
- return separator.join(values)
-
-
- def isoDateStringForDateAttribute(self, attributeName, default_string=""):
- modDate = self.firstValueForAttribute(attributeName, default_string)
- revDate = None
- if modDate:
- if len(modDate) >= len("YYYYMMDD") and modDate[:8].isdigit():
- revDate = "{YYYY}-{MM}-{DD}".format(YYYY=modDate[:4], MM=modDate[4:6], DD=modDate[6:8],)
- if len(modDate) >= len("YYYYMMDDHHMMSS") and modDate[8:14].isdigit():
- revDate += "T{HH}:{MM}:{SS}Z".format(HH=modDate[8:10], MM=modDate[10:12], SS=modDate[12:14],)
- return revDate
-
-
</del><span class="cx"> def vCard(self):
</span><del>-
- def generateVCard():
-
- def isUniqueProperty(vcard, newProperty, ignoreParams=None):
- existingProperties = vcard.properties(newProperty.name())
- for existingProperty in existingProperties:
- if ignoreParams:
- existingProperty = existingProperty.duplicate()
- for paramname, paramvalue in ignoreParams:
- existingProperty.removeParameterValue(paramname, paramvalue)
- if existingProperty == newProperty:
- return False
- return True
-
-
- def addUniqueProperty(vcard, newProperty, ignoreParams=None, attrType=None, attrValue=None):
- if isUniqueProperty(vcard, newProperty, ignoreParams):
- vcard.addProperty(newProperty)
- else:
- if attrType and attrValue:
- self.log.info("Ignoring attribute %r with value %r in creating property %r. A duplicate property already exists." % (attrType, attrValue, newProperty,))
-
-
- def addPropertyAndLabel(groupCount, label, propertyName, propertyValue, parameters=None):
- groupCount[0] += 1
- groupPrefix = "item%d" % groupCount[0]
- vcard.addProperty(Property(propertyName, propertyValue, params=parameters, group=groupPrefix))
- vcard.addProperty(Property("X-ABLabel", label, group=groupPrefix))
-
-
- # for attributes of the form param:value
- def addPropertiesAndLabelsForPrefixedAttribute(groupCount, propertyPrefix, propertyName, attrType, defaultLabel, nolabelParamTypes=(), labelMap={}, specialParamType=None):
- preferred = True
- for attrValue in self.valuesForAttribute(attrType):
- try:
- colonIndex = attrValue.find(":")
- if (colonIndex > len(attrValue) - 2):
- raise ValueError("Nothing after colon.")
-
- propertyValue = attrValue[colonIndex + 1:]
- labelString = attrValue[:colonIndex] if colonIndex > 0 else defaultLabel
- paramTypeString = labelString.upper()
-
- if specialParamType:
- parameters = {specialParamType: (paramTypeString,)}
- if preferred:
- parameters["TYPE"] = ("PREF",)
- else:
- # add PREF to first prop's parameters
- paramTypeStrings = [paramTypeString, ]
- if preferred and "PREF" != paramTypeString:
- paramTypeStrings += ["PREF", ]
- parameters = {"TYPE": paramTypeStrings, }
-
- #special case for IMHandles which the param is the last part of the property like X-AIM or X-JABBER
- if propertyPrefix:
- propertyName = propertyPrefix + paramTypeString
-
- # only add label prop if needed
- if paramTypeString in nolabelParamTypes:
- addUniqueProperty(vcard, Property(propertyName, attrValue[colonIndex + 1:], params=parameters), None, attrValue, attrType)
- else:
- # use special localizable addressbook labels where possible
- localizedABLabelString = labelMap.get(labelString, labelString)
- addPropertyAndLabel(groupCount, localizedABLabelString, propertyName, propertyValue, parameters)
- preferred = False
-
- except Exception, e:
- self.log.debug(
- "addPropertiesAndLabelsForPrefixedAttribute(): groupCount={groupCount}, propertyPrefix={propertyPrefix}, propertyName={propertyName}, nolabelParamTypes={nolabelParamTypes}, labelMap={labelMap}, attrType={attrType}",
- groupCount=groupCount[0], propertyPrefix=propertyPrefix, propertyName=propertyName, nolabelParamTypes=nolabelParamTypes, labelMap=labelMap, attrType=attrType,
- )
- self.log.error(
- "addPropertiesAndLabelsForPrefixedAttribute(): Trouble parsing attribute {attrType}, with value \"{attrValue}\". Error = {e}",
- attrType=attrType, attrValue=attrValue, e=e
- )
-
- # create vCard
- vcard = Component("VCARD")
- groupCount = [0]
-
- # add constant properties - properties that are the same regardless of the record attributes
- for key, value in self.constantProperties.items():
- vcard.addProperty(Property(key, value))
-
- # 3.1 IDENTIFICATION TYPES http://tools.ietf.org/html/rfc2426#section-3.1
- # 3.1.1 FN Type Definition
- # dsattributes.kDS1AttrDistinguishedName, # Users distinguished or real name
- #
- # full name is required but this is set in OpenDiretoryBackingRecord.__init__
- #vcard.addProperty(Property("FN", self.firstValueForAttribute(dsattributes.kDS1AttrDistinguishedName)))
-
- # 3.1.2 N Type Definition
- # dsattributes.kDS1AttrFirstName, # Used for first name of user or person record.
- # dsattributes.kDS1AttrLastName, # Used for the last name of user or person record.
- # dsattributes.kDS1AttrMiddleName, #Used for the middle name of user or person record.
- # dsattributes.kDSNAttrNameSuffix, # Represents the name suffix of a user or person.
- # ie. Jr., Sr., etc.
- # Usually found in user or people records (kDSStdRecordTypeUsers or
- # dsattributes.kDSStdRecordTypePeople).
- # dsattributes.kDSNAttrNamePrefix, # Represents the title prefix of a user or person.
- # ie. Mr., Ms., Mrs., Dr., etc.
- # Usually found in user or people records (kDSStdRecordTypeUsers or
- # dsattributes.kDSStdRecordTypePeople).
-
- # name is required, so make sure we have one
- # vcard says: Each name attribute can be a string or a list of strings.
- if not self.hasAttribute(dsattributes.kDS1AttrFirstName) and not self.hasAttribute(dsattributes.kDS1AttrLastName):
- familyName = self.firstValueForAttribute(dsattributes.kDS1AttrDistinguishedName)
- else:
- familyName = self.valuesForAttribute(dsattributes.kDS1AttrLastName, "")
-
- nameObject = N(
- first=self.valuesForAttribute(dsattributes.kDS1AttrFirstName, ""),
- last=familyName,
- middle=self.valuesForAttribute(dsattributes.kDS1AttrMiddleName, ""),
- prefix=self.valuesForAttribute(dsattributes.kDSNAttrNamePrefix, ""),
- suffix=self.valuesForAttribute(dsattributes.kDSNAttrNameSuffix, ""),
- )
- vcard.addProperty(Property("N", nameObject))
-
- # set full name to Name with contiguous spaces stripped
- # it turns out that Address Book.app ignores FN and creates it fresh from N in ABRecord
- # so no reason to have FN distinct from N
- vcard.addProperty(Property("FN", nameObject.getFullName()))
-
- # 3.1.3 NICKNAME Type Definition
- # dsattributes.kDSNAttrNickName, # Represents the nickname of a user or person.
- # Usually found in user or people records (kDSStdRecordTypeUsers or
- # dsattributes.kDSStdRecordTypePeople).
- for nickname in self.valuesForAttribute(dsattributes.kDSNAttrNickName):
- addUniqueProperty(vcard, Property("NICKNAME", nickname), None, dsattributes.kDSNAttrNickName, nickname)
-
- # 3.1.4 PHOTO Type Definition
- # dsattributes.kDSNAttrJPEGPhoto, # Used to store binary picture data in JPEG format.
- # Usually found in user, people or group records (kDSStdRecordTypeUsers,
- # dsattributes.kDSStdRecordTypePeople,dsattributes.kDSStdRecordTypeGroups).
- # pyOpenDirectory always returns binary-encoded string
-
- for photo in self.valuesForAttribute(dsattributes.kDSNAttrJPEGPhoto):
- photo = "".join("".join(photo.split("\r")).split("\n")) # get rid of line folding: for PHOTO
- addUniqueProperty(vcard, Property("PHOTO", photo, params={"ENCODING": ["b", ], "TYPE": ["JPEG", ], }), None, dsattributes.kDSNAttrJPEGPhoto, photo)
-
- # 3.1.5 BDAY Type Definition
- # dsattributes.kDS1AttrBirthday, # Single-valued attribute that defines the user's birthday.
- # Format is x.208 standard YYYYMMDDHHMMSSZ which we will require as GMT time.
- # 012345678901234
-
- birthdate = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrBirthday)
- if birthdate:
- vcard.addProperty(Property("BDAY", DateTime.parseText(birthdate, fullISO=True)))
-
- # 3.2 Delivery Addressing Types http://tools.ietf.org/html/rfc2426#section-3.2
- #
- # 3.2.1 ADR Type Definition
-
- #address
- # vcard says: Each address attribute can be a string or a list of strings.
- extended = self.valuesForAttribute(dsattributes.kDSNAttrBuilding, "")
- street = self.valuesForAttribute(dsattributes.kDSNAttrStreet, "")
- city = self.valuesForAttribute(dsattributes.kDSNAttrCity, "")
- region = self.valuesForAttribute(dsattributes.kDSNAttrState, "")
- code = self.valuesForAttribute(dsattributes.kDSNAttrPostalCode, "")
- country = self.valuesForAttribute(dsattributes.kDSNAttrCountry, "")
-
- if len(extended) > 0 or len(street) > 0 or len(city) > 0 or len(region) > 0 or len(code) > 0 or len(country) > 0:
- vcard.addProperty(Property("ADR",
- Adr(
- #pobox = box,
- extended=extended,
- street=street,
- locality=city,
- region=region,
- postalcode=code,
- country=country,
- ),
- params={"TYPE": ["WORK", "PREF", "POSTAL", "PARCEL", ], }
- ))
-
- # 3.2.2 LABEL Type Definition
- #
- # dsattributes.kDSNAttrPostalAddress, # The postal address usually excluding postal code.
- # dsattributes.kDSNAttrPostalAddressContacts, # multi-valued attribute that defines a record's alternate postal addresses .
- # found in user records (kDSStdRecordTypeUsers) and resource records (kDSStdRecordTypeResources).
- # dsattributes.kDSNAttrAddressLine1, # Line one of multiple lines of address data for a user.
- # dsattributes.kDSNAttrAddressLine2, # Line two of multiple lines of address data for a user.
- # dsattributes.kDSNAttrAddressLine3, # Line three of multiple lines of address data for a user.
-
- for label in self.valuesForAttribute(dsattributes.kDSNAttrPostalAddress):
- addUniqueProperty(vcard, Property("LABEL", label, params={"TYPE": ["POSTAL", "PARCEL", ]}), None, dsattributes.kDSNAttrPostalAddress, label)
-
- for label in self.valuesForAttribute(dsattributes.kDSNAttrPostalAddressContacts):
- addUniqueProperty(vcard, Property("LABEL", label, params={"TYPE": ["POSTAL", "PARCEL", ]}), None, dsattributes.kDSNAttrPostalAddressContacts, label)
-
- address = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine1)
- addressLine2 = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine2)
- if len(addressLine2) > 0:
- address += "\n" + addressLine2
- addressLine3 = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine3)
- if len(addressLine3) > 0:
- address += "\n" + addressLine3
-
- if len(address) > 0:
- vcard.addProperty(Property("LABEL", address, params={"TYPE": ["POSTAL", "PARCEL", ]}))
-
- # 3.3 TELECOMMUNICATIONS ADDRESSING TYPES http://tools.ietf.org/html/rfc2426#section-3.3
- # 3.3.1 TEL Type Definition
- # TEL;TYPE=work,voice,pref,msg:+1-213-555-1234
-
- # dsattributes.kDSNAttrPhoneNumber, # Telephone number of a user.
- # dsattributes.kDSNAttrMobileNumber, # Represents the mobile numbers of a user or person.
- # Usually found in user or people records (kDSStdRecordTypeUsers or
- # dsattributes.kDSStdRecordTypePeople).
- # dsattributes.kDSNAttrFaxNumber, # Represents the FAX numbers of a user or person.
- # Usually found in user or people records (kDSStdRecordTypeUsers or
- # kDSStdRecordTypePeople).
- # dsattributes.kDSNAttrPagerNumber, # Represents the pager numbers of a user or person.
- # Usually found in user or people records (kDSStdRecordTypeUsers or
- # dsattributes.kDSStdRecordTypePeople).
- # dsattributes.kDSNAttrHomePhoneNumber, # Home telephone number of a user or person.
- # dsattributes.kDSNAttrPhoneContacts, # multi-valued attribute that defines a record's custom phone numbers .
- # found in user records (kDSStdRecordTypeUsers).
- # Example: home fax:408-555-4444
-
- params = {"TYPE": ["WORK", "PREF", "VOICE", ], }
- for phone in self.valuesForAttribute(dsattributes.kDSNAttrPhoneNumber):
- addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrPhoneNumber)
- params = {"TYPE": ["WORK", "VOICE", ], }
-
- params = {"TYPE": ["WORK", "PREF", "CELL", ], }
- for phone in self.valuesForAttribute(dsattributes.kDSNAttrMobileNumber):
- addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrMobileNumber)
- params = {"TYPE": ["WORK", "CELL", ], }
-
- params = {"TYPE": ["WORK", "PREF", "FAX", ], }
- for phone in self.valuesForAttribute(dsattributes.kDSNAttrFaxNumber):
- addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrFaxNumber)
- params = {"TYPE": ["WORK", "FAX", ], }
-
- params = {"TYPE": ["WORK", "PREF", "PAGER", ], }
- for phone in self.valuesForAttribute(dsattributes.kDSNAttrPagerNumber):
- addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrPagerNumber)
- params = {"TYPE": ["WORK", "PAGER", ], }
-
- params = {"TYPE": ["HOME", "PREF", "VOICE", ], }
- for phone in self.valuesForAttribute(dsattributes.kDSNAttrHomePhoneNumber):
- addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrHomePhoneNumber)
- params = {"TYPE": ["HOME", "VOICE", ], }
-
- addPropertiesAndLabelsForPrefixedAttribute(groupCount=groupCount, propertyPrefix=None, propertyName="TEL", defaultLabel="work",
- nolabelParamTypes=("VOICE", "CELL", "FAX", "PAGER",),
- attrType=dsattributes.kDSNAttrPhoneContacts,)
-
- """
- # EXTEND: Use this attribute
- # dsattributes.kDSNAttrAreaCode, # Area code of a user's phone number.
- """
-
- # 3.3.2 EMAIL Type Definition
- # dsattributes.kDSNAttrEMailAddress, # Email address of usually a user record.
-
- # setup some params
- preferredWorkParams = {"TYPE": ["WORK", "PREF", "INTERNET", ], }
- workParams = {"TYPE": ["WORK", "INTERNET", ], }
- params = preferredWorkParams
- for emailAddress in self.valuesForAttribute(dsattributes.kDSNAttrEMailAddress):
- addUniqueProperty(vcard, Property("EMAIL", emailAddress, params=params), (("TYPE", "PREF"),), emailAddress, dsattributes.kDSNAttrEMailAddress)
- params = workParams
-
- # dsattributes.kDSNAttrEMailContacts, # multi-valued attribute that defines a record's custom email addresses .
- # found in user records (kDSStdRecordTypeUsers).
- # Example: home:johndoe@mymail.com
-
- # check to see if parameters type are open ended. Could be any string
- addPropertiesAndLabelsForPrefixedAttribute(groupCount=groupCount, propertyPrefix=None, propertyName="EMAIL", defaultLabel="work",
- nolabelParamTypes=("WORK", "HOME",),
- attrType=dsattributes.kDSNAttrEMailContacts,)
-
- """
- # UNIMPLEMENTED:
- # 3.3.3 MAILER Type Definition
- """
- # 3.4 GEOGRAPHICAL TYPES http://tools.ietf.org/html/rfc2426#section-3.4
- """
- # UNIMPLEMENTED:
- # 3.4.1 TZ Type Definition
- """
- # 3.4.2 GEO Type Definition
- #dsattributes.kDSNAttrMapCoordinates, # attribute that defines coordinates for a user's location .
- # Found in user records (kDSStdRecordTypeUsers) and resource records (kDSStdRecordTypeResources).
- # Example: 7.7,10.6
- for coordinate in self.valuesForAttribute(dsattributes.kDSNAttrMapCoordinates):
- parts = coordinate.split(",")
- if (len(parts) == 2):
- vcard.addProperty(Property("GEO", parts))
- else:
- log.info("Ignoring malformed attribute %r with value %r." % (dsattributes.kDSNAttrMapCoordinates, coordinate))
- #
- # 3.5 ORGANIZATIONAL TYPES http://tools.ietf.org/html/rfc2426#section-3.5
- #
- # 3.5.1 TITLE Type Definition
- for jobTitle in self.valuesForAttribute(dsattributes.kDSNAttrJobTitle):
- addUniqueProperty(vcard, Property("TITLE", jobTitle), None, dsattributes.kDSNAttrJobTitle, jobTitle)
-
- """
- # UNIMPLEMENTED:
- # 3.5.2 ROLE Type Definition
- # 3.5.3 LOGO Type Definition
- # 3.5.4 AGENT Type Definition
- """
- # 3.5.5 ORG Type Definition
- company = self.joinedValuesForAttribute(dsattributes.kDSNAttrCompany)
- if len(company) == 0:
- company = self.joinedValuesForAttribute(dsattributes.kDSNAttrOrganizationName)
- department = self.joinedValuesForAttribute(dsattributes.kDSNAttrDepartment)
- extra = self.joinedValuesForAttribute(dsattributes.kDSNAttrOrganizationInfo)
- if len(company) > 0 or len(department) > 0:
- vcard.addProperty(Property("ORG", (company, department, extra,),))
-
- # 3.6 EXPLANATORY TYPES http://tools.ietf.org/html/rfc2426#section-3.6
- """
- # UNIMPLEMENTED:
- # 3.6.1 CATEGORIES Type Definition
- """
- # 3.6.2 NOTE Type Definition
- # dsattributes.kDS1AttrComment, # Attribute used for unformatted comment.
- # dsattributes.kDS1AttrNote, # Note attribute. Commonly used in printer records.
- notes = self.valuesForAttribute(dsattributes.kDS1AttrComment, []) + self.valuesForAttribute(dsattributes.kDS1AttrNote, [])
- if len(notes):
- vcard.addProperty(Property("NOTE", "\n".join(notes),))
-
- # 3.6.3 PRODID Type Definition
- #vcard.addProperty(Property("PRODID", vCardProductID + "//BUILD {build}".format(build=twistedcaldav.__version__))
- #vcard.addProperty(Property("PRODID", vCardProductID))
- # ADDED WITH CONTSTANT PROPERTIES
-
- # 3.6.4 REV Type Definition
- revDate = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrModificationTimestamp)
- if revDate:
- vcard.addProperty(Property("REV", DateTime.parseText(revDate, fullISO=True)))
-
- """
- # UNIMPLEMENTED:
- # 3.6.5 SORT-STRING Type Definition
- # 3.6.6 SOUND Type Definition
- """
- # 3.6.7 UID Type Definition
- # dsattributes.kDS1AttrGeneratedUID, # Used for 36 character (128 bit) unique ID. Usually found in user,
- # group, and computer records. An example value is "A579E95E-CDFE-4EBC-B7E7-F2158562170F".
- # The standard format contains 32 hex characters and four hyphen characters.
-
- vcard.addProperty(Property("UID", self.firstValueForAttribute(dsattributes.kDS1AttrGeneratedUID)))
-
- # 3.6.8 URL Type Definition
- # dsattributes.kDSNAttrURL, # List of URLs.
- # dsattributes.kDS1AttrWeblogURI, # Single-valued attribute that defines the URI of a user's weblog.
- # Usually found in user records (kDSStdRecordTypeUsers).
- # Example: http://example.com/blog/jsmith
- for url in self.valuesForAttribute(dsattributes.kDS1AttrWeblogURI):
- addPropertyAndLabel(groupCount, "weblog", "URL", url, parameters={"TYPE": ["WEBLOG", ]})
-
- for url in self.valuesForAttribute(dsattributes.kDSNAttrURL):
- addPropertyAndLabel(groupCount, "_$!<HomePage>!$_", "URL", url, parameters={"TYPE": ["HOMEPAGE", ]})
-
-
- # 3.6.9 VERSION Type Definition
- # ALREADY ADDED
- #
- # 3.7 SECURITY TYPES http://tools.ietf.org/html/rfc2426#section-3.7
- # 3.7.1 CLASS Type Definition
- # ALREADY ADDED
- #
- # 3.7.2 KEY Type Definition
- #
- # dsattributes.kDSNAttrPGPPublicKey, # Pretty Good Privacy public encryption key.
- # dsattributes.kDS1AttrUserCertificate, # Attribute containing the binary of the user's certificate.
- # Usually found in user records. The certificate is data which identifies a user.
- # This data is attested to by a known party, and can be independently verified
- # by a third party.
- # dsattributes.kDS1AttrUserPKCS12Data, # Attribute containing binary data in PKCS #12 format.
- # Usually found in user records. The value can contain keys, certificates,
- # and other related information and is encrypted with a passphrase.
- # dsattributes.kDS1AttrUserSMIMECertificate,# Attribute containing the binary of the user's SMIME certificate.
- # Usually found in user records. The certificate is data which identifies a user.
- # This data is attested to by a known party, and can be independently verified
- # by a third party. SMIME certificates are often used for signed or encrypted
- # emails.
-
- for key in self.valuesForAttribute(dsattributes.kDSNAttrPGPPublicKey):
- addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["PGPPublicKey", ]}), None, dsattributes.kDSNAttrPGPPublicKey, key)
-
- for key in self.valuesForAttribute(dsattributes.kDS1AttrUserCertificate):
- addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["UserCertificate", ]}), None, dsattributes.kDS1AttrUserCertificate, key)
-
- for key in self.valuesForAttribute(dsattributes.kDS1AttrUserPKCS12Data):
- addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["UserPKCS12Data", ]}), None, dsattributes.kDS1AttrUserPKCS12Data, key)
-
- for key in self.valuesForAttribute(dsattributes.kDS1AttrUserSMIMECertificate):
- addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["UserSMIMECertificate", ]}), None, dsattributes.kDS1AttrUserSMIMECertificate, key)
-
- """
- X- attributes, Address Book support
- """
- # X-AIM, X-JABBER, X-MSN, X-YAHOO, X-ICQ
- # instant messaging
- # dsattributes.kDSNAttrIMHandle, # Represents the Instant Messaging handles of a user.
- # Values should be prefixed with the appropriate IM type
- # ie. AIM:, Jabber:, MSN:, Yahoo:, or ICQ:
- # Usually found in user records (kDSStdRecordTypeUsers).
- imNolabelParamTypes = ("AIM", "FACEBOOK", "GAGU-GAGU", "GOOGLE TALK", "ICQ", "JABBER", "MSN", "QQ", "SKYPE", "YAHOO",)
- addPropertiesAndLabelsForPrefixedAttribute(groupCount=groupCount, propertyPrefix="X-", propertyName=None, defaultLabel="aim",
- nolabelParamTypes=imNolabelParamTypes,
- attrType=dsattributes.kDSNAttrIMHandle,)
-
- # IMPP
- # Address Book's implementation of http://tools.ietf.org/html/rfc6350#section-6.4.3
- # adding IMPP property allows ab query report search on one property
- addPropertiesAndLabelsForPrefixedAttribute(groupCount=groupCount, propertyPrefix=None, propertyName="IMPP", defaultLabel="aim",
- specialParamType="X-SERVICE-TYPE",
- nolabelParamTypes=imNolabelParamTypes,
- attrType=dsattributes.kDSNAttrIMHandle,)
-
- # X-ABRELATEDNAMES
- # dsattributes.kDSNAttrRelationships, # multi-valued attribute that defines the relationship to the record type .
- # found in user records (kDSStdRecordTypeUsers).
- # Example: brother:John
- addPropertiesAndLabelsForPrefixedAttribute(groupCount=groupCount, propertyPrefix=None, propertyName="X-ABRELATEDNAMES", defaultLabel="friend",
- labelMap={"FATHER": "_$!<Father>!$_",
- "MOTHER": "_$!<Mother>!$_",
- "PARENT": "_$!<Parent>!$_",
- "BROTHER": "_$!<Brother>!$_",
- "SISTER": "_$!<Sister>!$_",
- "CHILD": "_$!<Child>!$_",
- "FRIEND": "_$!<Friend>!$_",
- "SPOUSE": "_$!<Spouse>!$_",
- "PARTNER": "_$!<Partner>!$_",
- "ASSISTANT": "_$!<Assistant>!$_",
- "MANAGER": "_$!<Manager>!$_", },
- attrType=dsattributes.kDSNAttrRelationships,)
-
- # add apple-defined group vcard properties if record type is group
- if self.kind == "group":
- vcard.addProperty(Property("X-ADDRESSBOOKSERVER-KIND", "group"))
-
- # add members
- for memberguid in self.valuesForAttribute(dsattributes.kDSNAttrGroupMembers):
- vcard.addProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", "urn:uuid:" + memberguid))
-
- """
- # UNIMPLEMENTED: X- attributes
-
- X-MAIDENNAME
- X-PHONETIC-FIRST-NAME
- X-PHONETIC-MIDDLE-NAME
- X-PHONETIC-LAST-NAME
-
- sattributes.kDS1AttrPicture, # Represents the path of the picture for each user displayed in the login window.
- # Found in user records (kDSStdRecordTypeUsers).
-
- dsattributes.kDS1AttrMapGUID, # Represents the GUID for a record's map.
- dsattributes.kDSNAttrMapURI, # attribute that defines the URI of a user's location.
-
- dsattributes.kDSNAttrOrganizationInfo, # Usually the organization info of a user.
- dsattributes.kDSNAttrAreaCode, # Area code of a user's phone number.
-
- dsattributes.kDSNAttrMIME, # Data contained in this attribute type is a fully qualified MIME Type.
-
- """
-
- # 2.1.4 SOURCE Type http://tools.ietf.org/html/rfc2426#section-2.1.4
- # If the SOURCE type is present, then its value provides information
- # how to find the source for the vCard.
-
- # add the source, so that if the SOURCE is copied out and preserved, the client can refresh information
- # However, client should really do a ab-query report matching UID on /directory/ not a multiget.
- uri = joinURL(self._directoryBackedAddressBook.uri, vcard.propertyValue("UID") + ".vcf")
-
- # seems like this should be in some standard place.
- if config.EnableSSL and config.SSLPort:
- if config.SSLPort == 443:
- source = "https://{server}{uri}".format(server=config.ServerHostName, uri=uri)
- else:
- source = "https://{server}:{port}{uri}".format(server=config.ServerHostName, port=config.SSLPort, uri=uri)
- else:
- if config.HTTPPort == 80:
- source = "https://{server}{uri}".format(server=config.ServerHostName, uri=uri)
- else:
- source = "https://{server}:{port}{uri}".format(server=config.ServerHostName, port=config.HTTPPort, uri=uri)
- vcard.addProperty(Property("SOURCE", source))
-
- # in 4.0 spec:
- # 6.1.4. KIND http://tools.ietf.org/html/rfc6350#section-6.1.4
- #
- # see also: http://www.iana.org/assignments/vcard-elements/vcard-elements.xml
- #
- vcard.addProperty(Property("KIND", self.kind))
-
- # one more X- related to kind
- if self.kind == "org":
- vcard.addProperty(Property("X-ABShowAs", "COMPANY"))
-
- return vcard
-
- if not self._vCard:
- self._vCard = generateVCard()
-
</del><span class="cx"> return self._vCard
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -1397,12 +627,7 @@
</span><span class="cx"> if self.vCard().hasProperty("REV"):
</span><span class="cx"> modDatetime = parse_date(self.vCard().propertyValue("REV"))
</span><span class="cx"> else:
</span><del>- # use creation date attribute if it exists
- creationDateString = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrCreationTimestamp)
- if creationDateString:
- modDatetime = parse_date(creationDateString)
- else:
- modDatetime = datetime.datetime.utcnow()
</del><ins>+ modDatetime = datetime.datetime.utcnow()
</ins><span class="cx">
</span><span class="cx"> #strip time zone because time zones are unimplemented in davxml.GETLastModified.fromDate
</span><span class="cx"> d = modDatetime.date()
</span><span class="lines">@@ -1411,10 +636,7 @@
</span><span class="cx"> result = davxml.GETLastModified.fromDate(modDatetimeNoTZ)
</span><span class="cx"> return result
</span><span class="cx"> elif name == "creationdate":
</span><del>- creationDateString = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrCreationTimestamp)
- if creationDateString:
- creationDatetime = parse_date(creationDateString)
- elif self.vCard().hasProperty("REV"): # use modification date property if it exists
</del><ins>+ if self.vCard().hasProperty("REV"): # use modification date property if it exists
</ins><span class="cx"> creationDatetime = parse_date(self.vCard().propertyValue("REV"))
</span><span class="cx"> else:
</span><span class="cx"> creationDatetime = datetime.datetime.utcnow()
</span><span class="lines">@@ -1449,10 +671,3 @@
</span><span class="cx"> yield qnames
</span><span class="cx">
</span><span class="cx"> listProperties = deferredGenerator(listProperties)
</span><del>-
-
-
-#remove illegal XML
-def removeControlChars(utf8String):
- result = ''.join([c for c in utf8String if c not in "\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"])
- return result
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4txdavwhovcardpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/move2who-4/txdav/who/vcard.py (0 => 13046)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/txdav/who/vcard.py         (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-4/txdav/who/vcard.py        2014-03-29 04:55:59 UTC (rev 13046)
</span><span class="lines">@@ -0,0 +1,324 @@
</span><ins>+##
+# Copyright (c) 2006-2014 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+ Utilities to converting a Record to a vCard
+"""
+
+__all__ = [
+ "vCardFromRecord"
+]
+
+from pycalendar.vcard.adr import Adr
+from pycalendar.vcard.n import N
+from twext.python.log import Logger
+from twext.who.idirectory import FieldName, RecordType
+from twistedcaldav.config import config
+from twistedcaldav.vcard import Component, Property, vCardProductID
+from txdav.who.idirectory import FieldName as CalFieldName, \
+ RecordType as CalRecordType
+from txweb2.dav.util import joinURL
+
+log = Logger()
+
+
+recordTypeToVCardKindMap = {
+ RecordType.user: "individual",
+ RecordType.group: "group",
+ CalRecordType.location: "location",
+ CalRecordType.resource: "device",
+}
+
+
+# all possible generated parameters.
+vCardPropToParamMap = {
+ #"PHOTO": {"ENCODING": ("B",), "TYPE": ("JPEG",), },
+ "ADR": {"TYPE": ("WORK", "PREF", "POSTAL", "PARCEL",), },
+ #"LABEL": {"TYPE": ("POSTAL", "PARCEL",)},
+ #"TEL": {"TYPE": None, }, # None means param can contain can be anything
+ "EMAIL": {"TYPE": None, },
+ #"KEY": {"ENCODING": ("B",), "TYPE": ("PGPPUBILICKEY", "USERCERTIFICATE", "USERPKCS12DATA", "USERSMIMECERTIFICATE",)},
+ #"URL": {"TYPE": ("WEBLOG", "HOMEPAGE",)},
+ #"IMPP": {"TYPE": ("PREF",), "X-SERVICE-TYPE": None, },
+ #"X-ABRELATEDNAMES": {"TYPE": None, },
+ #"X-AIM": {"TYPE": ("PREF",), },
+ #"X-JABBER": {"TYPE": ("PREF",), },
+ #"X-MSN": {"TYPE": ("PREF",), },
+ #"X-ICQ": {"TYPE": ("PREF",), },
+}
+
+
+vCardConstantProperties = {
+ #===================================================================
+ # 3.6 EXPLANATORY TYPES http://tools.ietf.org/html/rfc2426#section-3.6
+ #===================================================================
+ # 3.6.3 PRODID
+ "PRODID": vCardProductID,
+ # 3.6.9 VERSION
+ "VERSION": "3.0",
+}
+
+
+def vCardFromRecord(record, kind=None, addProps=None, parentURI=None):
+
+ def isUniqueProperty(newProperty, ignoredParameters={}):
+ existingProperties = vcard.properties(newProperty.name())
+ for existingProperty in existingProperties:
+ if ignoredParameters:
+ existingProperty = existingProperty.duplicate()
+ for paramName, paramValues in ignoredParameters.iteritems():
+ for paramValue in paramValues:
+ existingProperty.removeParameterValue(paramName, paramValue)
+ if existingProperty == newProperty:
+ return False
+ return True
+
+
+ def addUniqueProperty(newProperty, ignoredParameters=None):
+ if isUniqueProperty(newProperty, ignoredParameters):
+ vcard.addProperty(newProperty)
+ else:
+ log.info(
+ "Ignoring property {prop!r} it is a duplicate",
+ prop=newProperty
+ )
+
+ #=======================================================================
+ # start
+ #=======================================================================
+
+ log.debug("vCardFromRecord: record={record}, kind={kind}, addProps={addProps}, parentURI={parentURI}",
+ record=record, kind=kind, addProps=addProps, parentURI=parentURI)
+
+ if kind is None:
+ kind = recordTypeToVCardKindMap.get(record.recordType, "individual")
+
+ constantProperties = vCardConstantProperties.copy()
+ if addProps:
+ for key, value in addProps.iteritems():
+ if key not in constantProperties:
+ constantProperties[key] = value
+
+ # create vCard
+ vcard = Component("VCARD")
+
+ # add constant properties
+ for key, value in constantProperties.items():
+ vcard.addProperty(Property(key, value))
+
+ #===========================================================================
+ # 2.1 Predefined Type Usage
+ #===========================================================================
+ # 2.1.4 SOURCE Type http://tools.ietf.org/html/rfc2426#section-2.1.4
+ if parentURI:
+ uri = joinURL(parentURI, record.fields[FieldName.uid].encode("utf-8") + ".vcf")
+
+ # seems like this should be in some standard place.
+ if config.EnableSSL and config.SSLPort:
+ if config.SSLPort == 443:
+ source = "https://{server}{uri}".format(server=config.ServerHostName, uri=uri)
+ else:
+ source = "https://{server}:{port}{uri}".format(server=config.ServerHostName, port=config.SSLPort, uri=uri)
+ else:
+ if config.HTTPPort == 80:
+ source = "https://{server}{uri}".format(server=config.ServerHostName, uri=uri)
+ else:
+ source = "https://{server}:{port}{uri}".format(server=config.ServerHostName, port=config.HTTPPort, uri=uri)
+ vcard.addProperty(Property("SOURCE", source))
+
+ #===================================================================
+ # 3.1 IDENTIFICATION TYPES http://tools.ietf.org/html/rfc2426#section-3.1
+ #===================================================================
+ # 3.1.1 FN
+ vcard.addProperty(Property("FN", record.fields[FieldName.fullNames][0].encode("utf-8")))
+
+ # 3.1.2 N
+ # TODO: Better parsing
+ fullNameParts = record.fields[FieldName.fullNames][0].split()
+ first = fullNameParts[0] if len(fullNameParts) >= 2 else None
+ last = fullNameParts[len(fullNameParts) - 1]
+ middle = fullNameParts[1] if len(fullNameParts) == 3 else None
+ prefix = None
+ suffix = None
+
+ nameObject = N(
+ first=first.encode("utf-8") if first else None,
+ last=last.encode("utf-8") if last else None,
+ middle=middle.encode("utf-8") if middle else None,
+ prefix=prefix.encode("utf-8") if prefix else None,
+ suffix=suffix.encode("utf-8") if suffix else None,
+ )
+ vcard.addProperty(Property("N", nameObject))
+
+ # 3.1.3 NICKNAME
+ nickname = record.fields.get(CalFieldName.abbreviatedName)
+ if nickname:
+ vcard.addProperty(Property("NICKNAME", nickname.encode("utf-8")))
+
+ # UNIMPLEMENTED
+ # 3.1.4 PHOTO
+ # 3.1.5 BDAY
+
+ #===========================================================================
+ # 3.2 Delivery Addressing Types http://tools.ietf.org/html/rfc2426#section-3.2
+ #===========================================================================
+ # 3.2.1 ADR
+
+ extended = record.fields.get(CalFieldName.floor)
+
+ #TODO: parse !
+ street = record.fields.get(CalFieldName.streetAddress)
+ city = None
+ region = None
+ postalcode = None
+ country = None
+
+ if extended or street or city or region or postalcode or country:
+ vcard.addProperty(
+ Property(
+ "ADR", Adr(
+ #pobox = box,
+ extended=extended.encode("utf-8") if extended else None,
+ street=street.encode("utf-8") if street else None,
+ locality=city.encode("utf-8") if city else None,
+ region=region.encode("utf-8") if region else None,
+ postalcode=postalcode.encode("utf-8") if postalcode else None,
+ country=country.encode("utf-8") if country else None,
+ ),
+ params={"TYPE": ("WORK", "PREF", "POSTAL", "PARCEL",), }
+ )
+ )
+
+ # UNIMPLEMENTED
+ # 3.2.2 LABEL
+
+ #===================================================================
+ # 3.3 TELECOMMUNICATIONS ADDRESSING TYPES http://tools.ietf.org/html/rfc2426#section-3.3
+ #===================================================================
+ #
+ # UNIMPLEMENTED
+ # 3.3.1 TEL
+
+ # 3.3.2 EMAIL
+ preferredWorkParams = {"TYPE": ("WORK", "PREF", "INTERNET",), }
+ workParams = {"TYPE": ("WORK", "INTERNET",), }
+ params = preferredWorkParams
+ for emailAddress in record.fields.get(FieldName.emailAddresses, []):
+ addUniqueProperty(Property("EMAIL", emailAddress.encode("utf-8"), params=params), ignoredParameters={"TYPE": ("PREF",)})
+ params = workParams
+
+ # UNIMPLEMENTED:
+ # 3.3.3 MAILER
+ #
+ #===================================================================
+ # 3.4 GEOGRAPHICAL TYPES http://tools.ietf.org/html/rfc2426#section-3.4
+ #===================================================================
+ #
+ # UNIMPLEMENTED:
+ # 3.4.1 TZ
+ # 3.4.2 GEO
+ #
+ #===================================================================
+ # 3.5 ORGANIZATIONAL TYPES http://tools.ietf.org/html/rfc2426#section-3.5
+ #===================================================================
+ #
+ # UNIMPLEMENTED:
+ # 3.5.1 TITLE
+ # 3.5.2 ROLE
+ # 3.5.3 LOGO
+ # 3.5.4 AGENT
+ # 3.5.5 ORG
+ #
+ #===================================================================
+ # 3.6 EXPLANATORY TYPES http://tools.ietf.org/html/rfc2426#section-3.6
+ #===================================================================
+ #
+ # UNIMPLEMENTED:
+ # 3.6.1 CATEGORIES
+ # 3.6.2 NOTE
+ #
+ # ADDED WITH CONTSTANT PROPERTIES:
+ # 3.6.3 PRODID
+ #
+ # UNIMPLEMENTED:
+ # 3.6.5 SORT-STRING
+ # 3.6.6 SOUND
+
+ # 3.6.7 UID
+ vcard.addProperty(Property("UID", record.fields[FieldName.uid].encode("utf-8")))
+
+ # UNIMPLEMENTED:
+ # 3.6.8 URL
+
+ # ADDED WITH CONTSTANT PROPERTIES:
+ # 3.6.9 VERSION
+
+ #===================================================================
+ # 3.7 SECURITY TYPES http://tools.ietf.org/html/rfc2426#section-3.7
+ #===================================================================
+ # UNIMPLEMENTED:
+ # 3.7.1 CLASS
+ # 3.7.2 KEY
+
+ #===================================================================
+ # X Properties
+ #===================================================================
+ # UNIMPLEMENTED:
+ # X-<instant messaging type> such as:
+ # "AIM", "FACEBOOK", "GAGU-GAGU", "GOOGLE TALK", "ICQ", "JABBER", "MSN", "QQ", "SKYPE", "YAHOO",
+ # X-MAIDENNAME
+ # X-PHONETIC-FIRST-NAME
+ # X-PHONETIC-MIDDLE-NAME
+ # X-PHONETIC-LAST-NAME
+ # X-ABRELATEDNAMES
+
+ # X-GEOLOCATION
+ # TODO: Just made this up. Check for standard. Perhaps GEO can be pulled out
+ geographicLocation = record.fields.get(CalFieldName.geographicLocation)
+ if geographicLocation:
+ vcard.addProperty(Property("X-GEOLOCATION", geographicLocation.encode("utf-8")))
+
+ # X-ADDRESSBOOKSERVER-KIND
+ if kind == "group":
+ vcard.addProperty(Property("X-ADDRESSBOOKSERVER-KIND", kind))
+
+ # add members
+ # FIXME: members() is a deferred. I really want non-deferred FieldName.memberUIDs
+ if record.members():
+ pass
+ '''
+ for memberRecord in (yield record.members()):
+ vcard.addProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", "urn:uuid:" + memberRecord.fields[FieldName.uid].encode("utf-8")))
+ '''
+
+ #===================================================================
+ # vCard 4.0 http://tools.ietf.org/html/rfc6350
+ #===================================================================
+ # UNIMPLEMENTED:
+ # 6.4.3 IMPP http://tools.ietf.org/html/rfc6350#section-6.4.3
+ #
+ # 6.1.4 KIND http://tools.ietf.org/html/rfc6350#section-6.1.4
+ #
+ # see also: http://www.iana.org/assignments/vcard-elements/vcard-elements.xml
+ #
+ vcard.addProperty(Property("KIND", kind))
+
+ # one more X- related to kind
+ if kind == "org":
+ vcard.addProperty(Property("X-ABShowAs", "COMPANY"))
+
+ log.debug("vCardFromRecord: vcard=\n{vcard}", vcard=vcard)
+ return vcard
</ins></span></pre>
</div>
</div>
</body>
</html>