<!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>[12243] CalendarServer/branches/release/CalendarServer-5.2-dev</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/12243">12243</a></dd>
<dt>Author</dt> <dd>sagen@apple.com</dd>
<dt>Date</dt> <dd>2014-01-06 14:03:14 -0800 (Mon, 06 Jan 2014)</dd>
</dl>
<h3>Log Message</h3>
<pre>Port structured location feature to branch</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolsgatewaypy">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/gateway.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolsprincipalspy">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/principals.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolstestgatewaycaldavdplist">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/test/gateway/caldavd.plist</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolstesttest_gatewaypy">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/test/test_gateway.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devconfauthaugmentstestxml">CalendarServer/branches/release/CalendarServer-5.2-dev/conf/auth/augments-test.xml</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devconfauthresourcestestxml">CalendarServer/branches/release/CalendarServer-5.2-dev/conf/auth/resources-test.xml</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devconfcaldavdtestplist">CalendarServer/branches/release/CalendarServer-5.2-dev/conf/caldavd-test.plist</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdatafiltersperuserdatapy">CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/datafilters/peruserdata.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectoryaugmentpy">CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/augment.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectorydirectorypy">CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/directory.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectoryldapdirectorypy">CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/ldapdirectory.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectorytesttest_aggregatepy">CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/test_aggregate.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectorytesttest_ldapdirectorypy">CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/test_ldapdirectory.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectorytesttest_xmlfilepy">CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/test_xmlfile.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectorytestutilpy">CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/util.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectoryxmlaccountsparserpy">CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/xmlaccountsparser.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectoryxmlfilepy">CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/xmlfile.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavstdconfigpy">CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/stdconfig.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtxdavcaldavdatastoresqlpy">CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtxdavcaldavdatastoretesttest_sqlpy">CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtxdavcaldavdatastoretestutilpy">CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/test/util.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtxdavcommondatastoretestutilpy">CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/test/util.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtxdavcommonidirectoryservicepy">CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/idirectoryservice.py</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolsgatewaypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/gateway.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/gateway.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/gateway.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -22,7 +22,7 @@
</span><span class="cx"> import sys
</span><span class="cx"> import xml
</span><span class="cx">
</span><del>-from twext.python.plistlib import readPlistFromString, writePlistToString
</del><ins>+from plistlib import readPlistFromString, writePlistToString
</ins><span class="cx">
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, succeed
</span><span class="cx"> from twistedcaldav.directory.directory import DirectoryError
</span><span class="lines">@@ -32,13 +32,15 @@
</span><span class="cx"> principalForPrincipalID, proxySubprincipal, addProxy, removeProxy,
</span><span class="cx"> ProxyError, ProxyWarning, autoDisableMemcached
</span><span class="cx"> )
</span><del>-from calendarserver.tools.principals import getProxies, setProxies, updateRecord
</del><ins>+from calendarserver.tools.principals import (
+ getProxies, setProxies, updateRecord, attrMap
+)
</ins><span class="cx"> from calendarserver.tools.purge import WorkerService, PurgeOldEventsService, DEFAULT_BATCH_SIZE, DEFAULT_RETAIN_DAYS
</span><span class="cx"> from calendarserver.tools.cmdline import utilityMain
</span><span class="cx">
</span><span class="cx"> from pycalendar.datetime import PyCalendarDateTime
</span><span class="cx">
</span><del>-from twistedcaldav.config import config, ConfigDict
</del><ins>+from twistedcaldav.config import config, ConfigDict
</ins><span class="cx">
</span><span class="cx"> from calendarserver.tools.config import WRITABLE_CONFIG_KEYS, setKeyPath, getKeyPath, flattenDictionary, WritableConfig
</span><span class="cx">
</span><span class="lines">@@ -140,25 +142,6 @@
</span><span class="cx"> utilityMain(configFileName, RunnerService, verbose=debug)
</span><span class="cx">
</span><span class="cx">
</span><del>-attrMap = {
- 'GeneratedUID' : { 'attr' : 'guid', },
- 'RealName' : { 'attr' : 'fullName', },
- 'RecordName' : { 'attr' : 'shortNames', },
- 'Comment' : { 'extras' : True, 'attr' : 'comment', },
- 'Description' : { 'extras' : True, 'attr' : 'description', },
- 'Type' : { 'extras' : True, 'attr' : 'type', },
- 'Capacity' : { 'extras' : True, 'attr' : 'capacity', },
- 'Building' : { 'extras' : True, 'attr' : 'building', },
- 'Floor' : { 'extras' : True, 'attr' : 'floor', },
- 'Street' : { 'extras' : True, 'attr' : 'street', },
- 'City' : { 'extras' : True, 'attr' : 'city', },
- 'State' : { 'extras' : True, 'attr' : 'state', },
- 'ZIP' : { 'extras' : True, 'attr' : 'zip', },
- 'Country' : { 'extras' : True, 'attr' : 'country', },
- 'Phone' : { 'extras' : True, 'attr' : 'phone', },
- 'AutoSchedule' : { 'attr' : 'autoSchedule', },
- 'AutoAcceptGroup' : { 'attr' : 'autoAcceptGroup', },
-}
</del><span class="cx">
</span><span class="cx"> class Runner(object):
</span><span class="cx">
</span><span class="lines">@@ -217,9 +200,9 @@
</span><span class="cx"> self.respondWithError("Command failed: '%s'" % (str(e),))
</span><span class="cx"> raise
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> # Locations
</span><span class="cx">
</span><del>-
</del><span class="cx"> def command_getLocationList(self, command):
</span><span class="cx"> self.respondWithRecordsOfTypes(self.dir, command, ["locations"])
</span><span class="cx">
</span><span class="lines">@@ -265,6 +248,7 @@
</span><span class="cx">
</span><span class="cx"> command_getResourceAttributes = command_getLocationAttributes
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> @inlineCallbacks
</span><span class="cx"> def command_setLocationAttributes(self, command):
</span><span class="cx">
</span><span class="lines">@@ -305,9 +289,9 @@
</span><span class="cx"> return
</span><span class="cx"> self.respondWithRecordsOfTypes(self.dir, command, ["locations"])
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> # Resources
</span><span class="cx">
</span><del>-
</del><span class="cx"> def command_getResourceList(self, command):
</span><span class="cx"> self.respondWithRecordsOfTypes(self.dir, command, ["resources"])
</span><span class="cx">
</span><span class="lines">@@ -373,11 +357,72 @@
</span><span class="cx"> return
</span><span class="cx"> self.respondWithRecordsOfTypes(self.dir, command, ["resources"])
</span><span class="cx">
</span><del>-
</del><ins>+
</ins><span class="cx"> def command_getLocationAndResourceList(self, command):
</span><span class="cx"> self.respondWithRecordsOfTypes(self.dir, command, ["locations", "resources"])
</span><span class="cx">
</span><span class="cx">
</span><ins>+ # Addresses
+
+ def command_getAddressList(self, command):
+ self.respondWithRecordsOfTypes(self.dir, command, ["addresses"])
+
+
+ @inlineCallbacks
+ def command_createAddress(self, command):
+ kwargs = {}
+ for key, info in attrMap.iteritems():
+ if key in command:
+ kwargs[info['attr']] = command[key]
+
+ try:
+ yield updateRecord(True, self.dir, "addresses", **kwargs)
+ except DirectoryError, e:
+ self.respondWithError(str(e))
+ return
+
+ self.respondWithRecordsOfTypes(self.dir, command, ["addresses"])
+
+
+ def command_getAddressAttributes(self, command):
+ guid = command['GeneratedUID']
+ record = self.dir.recordWithGUID(guid)
+ if record is None:
+ self.respondWithError("Principal not found: %s" % (guid,))
+ return
+ recordDict = recordToDict(record)
+ self.respond(command, recordDict)
+ return succeed(None)
+
+
+ @inlineCallbacks
+ def command_setAddressAttributes(self, command):
+ kwargs = {}
+ for key, info in attrMap.iteritems():
+ if key in command:
+ kwargs[info['attr']] = command[key]
+ try:
+ yield updateRecord(False, self.dir, "addresses", **kwargs)
+ except DirectoryError, e:
+ self.respondWithError(str(e))
+ return
+
+ yield self.command_getAddressAttributes(command)
+
+
+ def command_deleteAddress(self, command):
+ kwargs = {}
+ for key, info in attrMap.iteritems():
+ if key in command:
+ kwargs[info['attr']] = command[key]
+ try:
+ self.dir.destroyRecord("addresses", **kwargs)
+ except DirectoryError, e:
+ self.respondWithError(str(e))
+ return
+ self.respondWithRecordsOfTypes(self.dir, command, ["addresses"])
+
+
</ins><span class="cx"> # Config
</span><span class="cx">
</span><span class="cx"> def command_readConfig(self, command):
</span><span class="lines">@@ -424,10 +469,8 @@
</span><span class="cx"> self.command_readConfig(command)
</span><span class="cx">
</span><span class="cx">
</span><del>-
</del><span class="cx"> # Proxies
</span><span class="cx">
</span><del>-
</del><span class="cx"> @inlineCallbacks
</span><span class="cx"> def command_listWriteProxies(self, command):
</span><span class="cx"> principal = principalForPrincipalID(command['Principal'], directory=self.dir)
</span><span class="lines">@@ -545,7 +588,6 @@
</span><span class="cx"> self.respond(command, {'EventsRemoved' : eventCount, "RetainDays" : retainDays})
</span><span class="cx">
</span><span class="cx">
</span><del>-
</del><span class="cx"> @inlineCallbacks
</span><span class="cx"> def respondWithProxies(self, directory, command, principal, proxyType):
</span><span class="cx"> proxies = []
</span><span class="lines">@@ -562,7 +604,6 @@
</span><span class="cx"> })
</span><span class="cx">
</span><span class="cx">
</span><del>-
</del><span class="cx"> def respondWithRecordsOfTypes(self, directory, command, recordTypes):
</span><span class="cx"> result = []
</span><span class="cx"> for recordType in recordTypes:
</span><span class="lines">@@ -572,7 +613,6 @@
</span><span class="cx"> self.respond(command, result)
</span><span class="cx">
</span><span class="cx">
</span><del>-
</del><span class="cx"> def respond(self, command, result):
</span><span class="cx"> self.output.write(writePlistToString({'command' : command['command'], 'result' : result}))
</span><span class="cx">
</span><span class="lines">@@ -581,6 +621,7 @@
</span><span class="cx"> self.output.write(writePlistToString({'error' : msg, }))
</span><span class="cx">
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> def recordToDict(record):
</span><span class="cx"> recordDict = {}
</span><span class="cx"> for key, info in attrMap.iteritems():
</span><span class="lines">@@ -596,6 +637,8 @@
</span><span class="cx"> pass
</span><span class="cx"> return recordDict
</span><span class="cx">
</span><ins>+
+
</ins><span class="cx"> def respondWithError(msg, status=1):
</span><span class="cx"> sys.stdout.write(writePlistToString({'error' : msg, }))
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolsprincipalspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/principals.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/principals.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/principals.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -87,8 +87,14 @@
</span><span class="cx"> print(" --get-auto-schedule-mode: read auto-schedule mode")
</span><span class="cx"> print(" --set-auto-accept-group=principal: set auto-accept-group")
</span><span class="cx"> print(" --get-auto-accept-group: read auto-accept-group")
</span><del>- print(" --add {locations|resources} 'full name' [record name] [GUID]: add a principal")
</del><ins>+ print(" --add {locations|resources|addresses} 'full name' [record name] [GUID]: add a principal")
</ins><span class="cx"> print(" --remove: remove a principal")
</span><ins>+ print(" --set-geo=url: set the geo: url for an address (e.g. geo:37.331741,-122.030333)")
+ print(" --get-geo: get the geo: url for an address")
+ print(" --set-street-address=streetaddress: set the street address string for an address")
+ print(" --get-street-address: get the street address string for an address")
+ print(" --set-address=guid: associate principal with an address (by guid)")
+ print(" --get-address: get the associated address's guid")
</ins><span class="cx">
</span><span class="cx"> if e:
</span><span class="cx"> sys.exit(64)
</span><span class="lines">@@ -116,8 +122,29 @@
</span><span class="cx"> directory = rootResource.getDirectory()
</span><span class="cx"> yield self.function(rootResource, directory, self.store, *self.params)
</span><span class="cx">
</span><ins>+attrMap = {
+ 'GeneratedUID' : { 'attr' : 'guid', },
+ 'RealName' : { 'attr' : 'fullName', },
+ 'RecordName' : { 'attr' : 'shortNames', },
+ 'AutoSchedule' : { 'attr' : 'autoSchedule', },
+ 'AutoAcceptGroup' : { 'attr' : 'autoAcceptGroup', },
</ins><span class="cx">
</span><ins>+ 'Comment' : { 'extras' : True, 'attr' : 'comment', },
+ 'Description' : { 'extras' : True, 'attr' : 'description', },
+ 'Type' : { 'extras' : True, 'attr' : 'type', },
</ins><span class="cx">
</span><ins>+ # For "Locations", i.e. scheduled spaces
+ 'Capacity' : { 'extras' : True, 'attr' : 'capacity', },
+ 'Floor' : { 'extras' : True, 'attr' : 'floor', },
+ 'AssociatedAddress' : { 'extras' : True, 'attr' : 'associatedAddress', },
+
+ # For "Addresses", i.e. nonscheduled areas containing Locations
+ 'AbbreviatedName' : { 'extras' : True, 'attr' : 'abbreviatedName', },
+ 'StreetAddress' : { 'extras' : True, 'attr' : 'streetAddress', },
+ 'Geo' : { 'extras' : True, 'attr' : 'geo', },
+}
+
+
</ins><span class="cx"> def main():
</span><span class="cx"> try:
</span><span class="cx"> (optargs, args) = getopt(
</span><span class="lines">@@ -142,6 +169,12 @@
</span><span class="cx"> "get-auto-schedule-mode",
</span><span class="cx"> "set-auto-accept-group=",
</span><span class="cx"> "get-auto-accept-group",
</span><ins>+ "set-geo=",
+ "get-geo",
+ "set-address=",
+ "get-address",
+ "set-street-address=",
+ "get-street-address",
</ins><span class="cx"> "verbose",
</span><span class="cx"> ],
</span><span class="cx"> )
</span><span class="lines">@@ -258,6 +291,24 @@
</span><span class="cx"> elif opt in ("", "--get-auto-accept-group"):
</span><span class="cx"> principalActions.append((action_getAutoAcceptGroup,))
</span><span class="cx">
</span><ins>+ elif opt in ("", "--set-geo"):
+ principalActions.append((action_setValue, "Geo", arg))
+
+ elif opt in ("", "--get-geo"):
+ principalActions.append((action_getValue, "Geo"))
+
+ elif opt in ("", "--set-street-address"):
+ principalActions.append((action_setValue, "StreetAddress", arg))
+
+ elif opt in ("", "--get-street-address"):
+ principalActions.append((action_getValue, "StreetAddress"))
+
+ elif opt in ("", "--set-address"):
+ principalActions.append((action_setValue, "AssociatedAddress", arg))
+
+ elif opt in ("", "--get-address"):
+ principalActions.append((action_getValue, "AssociatedAddress"))
+
</ins><span class="cx"> else:
</span><span class="cx"> raise NotImplementedError(opt)
</span><span class="cx">
</span><span class="lines">@@ -274,7 +325,7 @@
</span><span class="cx"> elif addType:
</span><span class="cx">
</span><span class="cx"> try:
</span><del>- addType = matchStrings(addType, ["locations", "resources"])
</del><ins>+ addType = matchStrings(addType, ["locations", "resources", "addresses"])
</ins><span class="cx"> except ValueError, e:
</span><span class="cx"> print(e)
</span><span class="cx"> return
</span><span class="lines">@@ -296,7 +347,7 @@
</span><span class="cx"> elif listPrincipals:
</span><span class="cx"> try:
</span><span class="cx"> listPrincipals = matchStrings(listPrincipals, ["users", "groups",
</span><del>- "locations", "resources"])
</del><ins>+ "locations", "resources", "addresses"])
</ins><span class="cx"> except ValueError, e:
</span><span class="cx"> print(e)
</span><span class="cx"> return
</span><span class="lines">@@ -393,6 +444,7 @@
</span><span class="cx"> "groups" : "Group",
</span><span class="cx"> "locations" : "Place",
</span><span class="cx"> "resources" : "Resource",
</span><ins>+ "addresses" : "Address",
</ins><span class="cx"> }.get(record.recordType),
</span><span class="cx"> ))
</span><span class="cx"> print(" GUID: %s" % (record.guid,))
</span><span class="lines">@@ -667,7 +719,30 @@
</span><span class="cx"> print("No auto-accept-group assigned to %s" % (prettyPrincipal(principal),))
</span><span class="cx">
</span><span class="cx">
</span><ins>+@inlineCallbacks
+def action_setValue(rootResource, directory, store, principal, name, value):
+ print("Setting %s to %s for %s" % (
+ name, value, prettyPrincipal(principal),
+ ))
</ins><span class="cx">
</span><ins>+ principal.record.extras[attrMap[name]["attr"]] = value
+ (yield updateRecord(False, directory,
+ principal.record.recordType,
+ guid=principal.record.guid,
+ shortNames=principal.record.shortNames,
+ fullName=principal.record.fullName,
+ **principal.record.extras
+ ))
+
+
+def action_getValue(rootResource, directory, store, principal, name):
+ print("%s for %s is %s" % (
+ name,
+ prettyPrincipal(principal),
+ principal.record.extras[attrMap[name]["attr"]]
+ ))
+
+
</ins><span class="cx"> def abort(msg, status=1):
</span><span class="cx"> sys.stdout.write("%s\n" % (msg,))
</span><span class="cx"> try:
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolstestgatewaycaldavdplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/test/gateway/caldavd.plist (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/test/gateway/caldavd.plist        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/test/gateway/caldavd.plist        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -176,6 +176,7 @@
</span><span class="cx"> <array>
</span><span class="cx"> <string>resources</string>
</span><span class="cx"> <string>locations</string>
</span><ins>+ <string>addresses</string>
</ins><span class="cx"> </array>
</span><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolstesttest_gatewaypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/test/test_gateway.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/test/test_gateway.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/test/test_gateway.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -17,7 +17,7 @@
</span><span class="cx">
</span><span class="cx"> import os
</span><span class="cx"> import sys
</span><del>-from twext.python.plistlib import readPlistFromString
</del><ins>+from plistlib import readPlistFromString
</ins><span class="cx"> import xml
</span><span class="cx">
</span><span class="cx"> from twext.python.filepath import CachingFilePath as FilePath
</span><span class="lines">@@ -127,15 +127,9 @@
</span><span class="cx"> def test_getLocationAttributes(self):
</span><span class="cx"> yield self.runCommand(command_createLocation)
</span><span class="cx"> results = yield self.runCommand(command_getLocationAttributes)
</span><del>- self.assertEquals(results["result"]["Building"], "Test Building")
- self.assertEquals(results["result"]["City"], "Cupertino")
</del><span class="cx"> self.assertEquals(results["result"]["Capacity"], "40")
</span><span class="cx"> self.assertEquals(results["result"]["Description"], "Test Description")
</span><del>- self.assertEquals(results["result"]["ZIP"], "95014")
- self.assertEquals(results["result"]["Floor"], "First")
</del><span class="cx"> self.assertEquals(results["result"]["RecordName"], ["createdlocation01"])
</span><del>- self.assertEquals(results["result"]["State"], "CA")
- self.assertEquals(results["result"]["Street"], "1 Infinite Loop")
</del><span class="cx"> self.assertEquals(results["result"]["RealName"],
</span><span class="cx"> "Created Location 01 %s %s" % (unichr(208), u"\ud83d\udca3"))
</span><span class="cx"> self.assertEquals(results["result"]["Comment"], "Test Comment")
</span><span class="lines">@@ -162,6 +156,45 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><ins>+ def test_createAddress(self):
+ directory = getDirectory()
+
+ record = directory.recordWithUID("C701069D-9CA1-4925-A1A9-5CD94767B74B")
+ self.assertEquals(record, None)
+ yield self.runCommand(command_createAddress)
+
+ directory.flushCaches()
+
+ record = directory.recordWithUID("C701069D-9CA1-4925-A1A9-5CD94767B74B")
+ self.assertEquals(record.fullName.decode("utf-8"),
+ "Created Address 01 %s %s" % (unichr(208), u"\ud83d\udca3"))
+
+ self.assertNotEquals(record, None)
+
+ self.assertEquals(record.extras["abbreviatedName"], "Addr1")
+ self.assertEquals(record.extras["streetAddress"], "1 Infinite Loop\nCupertino, 95014\nCA")
+ self.assertEquals(record.extras["geo"], "geo:37.331,-122.030")
+
+ results = yield self.runCommand(command_getAddressList)
+ self.assertEquals(len(results["result"]), 1)
+
+ results = yield self.runCommand(command_getAddressAttributes)
+ self.assertEquals(results["result"]["RealName"], u'Created Address 01 \xd0 \U0001f4a3')
+
+ results = yield self.runCommand(command_setAddressAttributes)
+
+ results = yield self.runCommand(command_getAddressAttributes)
+ self.assertEquals(results["result"]["RealName"], u'Updated Address')
+ self.assertEquals(results["result"]["StreetAddress"], u'Updated Street Address')
+ self.assertEquals(results["result"]["Geo"], u'Updated Geo')
+
+ results = yield self.runCommand(command_deleteAddress)
+
+ results = yield self.runCommand(command_getAddressList)
+ self.assertEquals(len(results["result"]), 0)
+
+
+ @inlineCallbacks
</ins><span class="cx"> def test_createLocation(self):
</span><span class="cx"> directory = getDirectory()
</span><span class="cx">
</span><span class="lines">@@ -184,15 +217,8 @@
</span><span class="cx"> self.assertEquals(record.autoSchedule, True)
</span><span class="cx">
</span><span class="cx"> self.assertEquals(record.extras["comment"], "Test Comment")
</span><del>- self.assertEquals(record.extras["building"], "Test Building")
</del><span class="cx"> self.assertEquals(record.extras["floor"], "First")
</span><span class="cx"> self.assertEquals(record.extras["capacity"], "40")
</span><del>- self.assertEquals(record.extras["street"], "1 Infinite Loop")
- self.assertEquals(record.extras["city"], "Cupertino")
- self.assertEquals(record.extras["state"], "CA")
- self.assertEquals(record.extras["zip"], "95014")
- self.assertEquals(record.extras["country"], "USA")
- self.assertEquals(record.extras["phone"], "(408) 555-1212")
</del><span class="cx">
</span><span class="cx"> results = yield self.runCommand(command_getLocationAttributes)
</span><span class="cx"> self.assertEquals(set(results["result"]["ReadProxies"]), set(['user03', 'user04']))
</span><span class="lines">@@ -215,15 +241,9 @@
</span><span class="cx"> record = directory.recordWithUID("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
</span><span class="cx">
</span><span class="cx"> self.assertEquals(record.extras["comment"], "Updated Test Comment")
</span><del>- self.assertEquals(record.extras["building"], "Updated Test Building")
</del><span class="cx"> self.assertEquals(record.extras["floor"], "Second")
</span><span class="cx"> self.assertEquals(record.extras["capacity"], "41")
</span><del>- self.assertEquals(record.extras["street"], "2 Infinite Loop")
- self.assertEquals(record.extras["city"], "Updated Cupertino")
- self.assertEquals(record.extras["state"], "Updated CA")
- self.assertEquals(record.extras["zip"], "95015")
- self.assertEquals(record.extras["country"], "Updated USA")
- self.assertEquals(record.extras["phone"], "(408) 555-1213")
</del><ins>+ self.assertEquals(record.extras["streetAddress"], "2 Infinite Loop\nCupertino, 95014\nCA")
</ins><span class="cx"> self.assertEquals(record.autoSchedule, True)
</span><span class="cx"> self.assertEquals(record.autoAcceptGroup, "F5A6142C-4189-4E9E-90B0-9CD0268B314B")
</span><span class="cx">
</span><span class="lines">@@ -384,6 +404,31 @@
</span><span class="cx"> </plist>
</span><span class="cx"> """
</span><span class="cx">
</span><ins>+command_createAddress = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>command</key>
+ <string>createAddress</string>
+ <key>GeneratedUID</key>
+ <string>C701069D-9CA1-4925-A1A9-5CD94767B74B</string>
+ <key>RealName</key>
+ <string>Created Address 01 %s %s</string>
+ <key>AbbreviatedName</key>
+ <string>Addr1</string>
+ <key>RecordName</key>
+ <array>
+ <string>createdaddress01</string>
+ </array>
+ <key>StreetAddress</key>
+ <string>1 Infinite Loop\nCupertino, 95014\nCA</string>
+ <key>Geo</key>
+ <string>geo:37.331,-122.030</string>
+</dict>
+</plist>
+""" % (unichr(208), u"\ud83d\udca3")
+
+
</ins><span class="cx"> command_createLocation = """<?xml version="1.0" encoding="UTF-8"?>
</span><span class="cx"> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
</span><span class="cx"> <plist version="1.0">
</span><span class="lines">@@ -406,24 +451,12 @@
</span><span class="cx"> <string>Test Comment</string>
</span><span class="cx"> <key>Description</key>
</span><span class="cx"> <string>Test Description</string>
</span><del>- <key>Building</key>
- <string>Test Building</string>
</del><span class="cx"> <key>Floor</key>
</span><span class="cx"> <string>First</string>
</span><span class="cx"> <key>Capacity</key>
</span><span class="cx"> <string>40</string>
</span><del>- <key>Street</key>
- <string>1 Infinite Loop</string>
- <key>City</key>
- <string>Cupertino</string>
- <key>State</key>
- <string>CA</string>
- <key>ZIP</key>
- <string>95014</string>
- <key>Country</key>
- <string>USA</string>
- <key>Phone</key>
- <string>(408) 555-1212</string>
</del><ins>+ <key>AssociatedAddress</key>
+ <string>C701069D-9CA1-4925-A1A9-5CD94767B74B</string>
</ins><span class="cx"> <key>ReadProxies</key>
</span><span class="cx"> <array>
</span><span class="cx"> <string>users:user03</string>
</span><span class="lines">@@ -451,14 +484,16 @@
</span><span class="cx"> <string>AF575A61-CFA6-49E1-A0F6-B5662C9D9801</string>
</span><span class="cx"> <key>RealName</key>
</span><span class="cx"> <string>Laptop 1</string>
</span><ins>+ <key>Comment</key>
+ <string>Test Comment</string>
+ <key>Description</key>
+ <string>Test Description</string>
</ins><span class="cx"> <key>Type</key>
</span><span class="cx"> <string>Computer</string>
</span><span class="cx"> <key>RecordName</key>
</span><span class="cx"> <array>
</span><span class="cx"> <string>laptop1</string>
</span><span class="cx"> </array>
</span><del>- <key>Comment</key>
- <string>Test Comment</string>
</del><span class="cx"> <key>ReadProxies</key>
</span><span class="cx"> <array>
</span><span class="cx"> <string>users:user03</string>
</span><span class="lines">@@ -497,6 +532,19 @@
</span><span class="cx"> </plist>
</span><span class="cx"> """
</span><span class="cx">
</span><ins>+
+command_deleteAddress = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>command</key>
+ <string>deleteAddress</string>
+ <key>GeneratedUID</key>
+ <string>C701069D-9CA1-4925-A1A9-5CD94767B74B</string>
+</dict>
+</plist>
+"""
+
</ins><span class="cx"> command_getLocationAndResourceList = """<?xml version="1.0" encoding="UTF-8"?>
</span><span class="cx"> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
</span><span class="cx"> <plist version="1.0">
</span><span class="lines">@@ -527,6 +575,17 @@
</span><span class="cx"> </plist>
</span><span class="cx"> """
</span><span class="cx">
</span><ins>+command_getAddressList = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>command</key>
+ <string>getAddressList</string>
+</dict>
+</plist>
+"""
+
+
</ins><span class="cx"> command_listReadProxies = """<?xml version="1.0" encoding="UTF-8"?>
</span><span class="cx"> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
</span><span class="cx"> <plist version="1.0">
</span><span class="lines">@@ -601,24 +660,12 @@
</span><span class="cx"> <string>Updated Test Comment</string>
</span><span class="cx"> <key>Description</key>
</span><span class="cx"> <string>Updated Test Description</string>
</span><del>- <key>Building</key>
- <string>Updated Test Building</string>
</del><span class="cx"> <key>Floor</key>
</span><span class="cx"> <string>Second</string>
</span><span class="cx"> <key>Capacity</key>
</span><span class="cx"> <string>41</string>
</span><del>- <key>Street</key>
- <string>2 Infinite Loop</string>
- <key>City</key>
- <string>Updated Cupertino</string>
- <key>State</key>
- <string>Updated CA</string>
- <key>ZIP</key>
- <string>95015</string>
- <key>Country</key>
- <string>Updated USA</string>
- <key>Phone</key>
- <string>(408) 555-1213</string>
</del><ins>+ <key>StreetAddress</key>
+ <string>2 Infinite Loop\nCupertino, 95014\nCA</string>
</ins><span class="cx"> <key>ReadProxies</key>
</span><span class="cx"> <array>
</span><span class="cx"> <string>users:user03</string>
</span><span class="lines">@@ -645,6 +692,38 @@
</span><span class="cx"> </plist>
</span><span class="cx"> """
</span><span class="cx">
</span><ins>+command_getAddressAttributes = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>command</key>
+ <string>getAddressAttributes</string>
+ <key>GeneratedUID</key>
+ <string>C701069D-9CA1-4925-A1A9-5CD94767B74B</string>
+</dict>
+</plist>
+"""
+
+command_setAddressAttributes = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>command</key>
+ <string>setAddressAttributes</string>
+ <key>GeneratedUID</key>
+ <string>C701069D-9CA1-4925-A1A9-5CD94767B74B</string>
+ <key>RealName</key>
+ <string>Updated Address</string>
+ <key>StreetAddress</key>
+ <string>Updated Street Address</string>
+ <key>Geo</key>
+ <string>Updated Geo</string>
+
+</dict>
+</plist>
+"""
+
+
</ins><span class="cx"> command_setResourceAttributes = """<?xml version="1.0" encoding="UTF-8"?>
</span><span class="cx"> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
</span><span class="cx"> <plist version="1.0">
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devconfauthaugmentstestxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/conf/auth/augments-test.xml (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/conf/auth/augments-test.xml        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/conf/auth/augments-test.xml        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -109,6 +109,7 @@
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> <enable-login>true</enable-login>
</span><span class="cx"> <auto-schedule>true</auto-schedule>
</span><ins>+ <auto-schedule-mode>default</auto-schedule-mode>
</ins><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>C38BEE7A-36EE-478C-9DCB-CBF4612AFE65</uid>
</span><span class="lines">@@ -136,4 +137,49 @@
</span><span class="cx"> <enable-login>true</enable-login>
</span><span class="cx"> <auto-schedule>true</auto-schedule>
</span><span class="cx"> </record>
</span><ins>+ <record>
+ <uid>6F9EE33B-78F6-481B-9289-3D0812FF0D64</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>false</auto-schedule>
+ <auto-schedule-mode>default</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>76E7ECA6-08BC-4AE7-930D-F2E7453993A5</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>false</auto-schedule>
+ <auto-schedule-mode>default</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>false</auto-schedule>
+ <auto-schedule-mode>default</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>06E3BDCB-9C19-485A-B14E-F146A80ADDC6</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>default</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>4D66A20A-1437-437D-8069-2F14E8322234</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>default</auto-schedule-mode>
+ </record>
</ins><span class="cx"> </augments>
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devconfauthresourcestestxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/conf/auth/resources-test.xml (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/conf/auth/resources-test.xml        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/conf/auth/resources-test.xml        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -1,5 +1,13 @@
</span><span class="cx"> <accounts realm="Test Realm">
</span><span class="cx"> <location>
</span><ins>+ <uid>fantastic</uid>
+ <guid>4D66A20A-1437-437D-8069-2F14E8322234</guid>
+ <name>Fantastic Conference Room</name>
+ <extras>
+ <associatedAddress>63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3</associatedAddress>
+ </extras>
+ </location>
+ <location>
</ins><span class="cx"> <uid>jupiter</uid>
</span><span class="cx"> <guid>jupiter</guid>
</span><span class="cx"> <name>Jupiter Conference Room, Building 2, 1st Floor</name>
</span><span class="lines">@@ -78,6 +86,9 @@
</span><span class="cx"> <uid>sharissroom</uid>
</span><span class="cx"> <guid>80689D41-DAF8-4189-909C-DB017B271892</guid>
</span><span class="cx"> <name>Shari's Room</name>
</span><ins>+ <extras>
+ <associatedAddress>6F9EE33B-78F6-481B-9289-3D0812FF0D64</associatedAddress>
+ </extras>
</ins><span class="cx"> </location>
</span><span class="cx"> <location>
</span><span class="cx"> <uid>pluto</uid>
</span><span class="lines">@@ -95,6 +106,14 @@
</span><span class="cx"> <name>Room 10</name>
</span><span class="cx"> </location>
</span><span class="cx"> <location>
</span><ins>+ <uid>pretend</uid>
+ <guid>06E3BDCB-9C19-485A-B14E-F146A80ADDC6</guid>
+ <name>Pretend Conference Room</name>
+ <extras>
+ <associatedAddress>76E7ECA6-08BC-4AE7-930D-F2E7453993A5</associatedAddress>
+ </extras>
+ </location>
+ <location>
</ins><span class="cx"> <uid>neptune</uid>
</span><span class="cx"> <guid>neptune</guid>
</span><span class="cx"> <name>Neptune Conference Room, Building 2, 1st Floor</name>
</span><span class="lines">@@ -224,4 +243,31 @@
</span><span class="cx"> <guid>resource09</guid>
</span><span class="cx"> <name>Resource 09</name>
</span><span class="cx"> </resource>
</span><ins>+ <address>
+ <uid>testaddress1</uid>
+ <guid>6F9EE33B-78F6-481B-9289-3D0812FF0D64</guid>
+ <name>Test Address One</name>
+ <extras>
+ <streetAddress>20300 Stevens Creek Blvd, Cupertino, CA 95014</streetAddress>
+ <geo>37.322281,-122.028345</geo>
+ </extras>
+ </address>
+ <address>
+ <uid>il2</uid>
+ <guid>63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3</guid>
+ <name>IL2</name>
+ <extras>
+ <streetAddress>2 Infinite Loop, Cupertino, CA 95014</streetAddress>
+ <geo>37.332633,-122.030502</geo>
+ </extras>
+ </address>
+ <address>
+ <uid>il1</uid>
+ <guid>76E7ECA6-08BC-4AE7-930D-F2E7453993A5</guid>
+ <name>IL1</name>
+ <extras>
+ <streetAddress>1 Infinite Loop, Cupertino, CA 95014</streetAddress>
+ <geo>37.331741,-122.030333</geo>
+ </extras>
+ </address>
</ins><span class="cx"> </accounts>
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devconfcaldavdtestplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/conf/caldavd-test.plist (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/conf/caldavd-test.plist        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/conf/caldavd-test.plist        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -284,29 +284,20 @@
</span><span class="cx"> <string>mail</string>
</span><span class="cx"> <string>mailAlias</string>
</span><span class="cx"> </array>
</span><del>- <key>firstName</key>
- <string></string>
- <key>lastName</key>
- <string></string>
</del><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><span class="cx"> <key>locations</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>rdn</key>
</span><span class="cx"> <string>ou=locations</string>
</span><ins>+ <key>associatedAddressAttr</key>
+ <string></string>
</ins><span class="cx"> <key>mapping</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>recordName</key>
</span><span class="cx"> <string>cn</string>
</span><span class="cx"> <key>fullName</key>
</span><span class="cx"> <string>cn</string>
</span><del>- <key>emailAddresses</key>
- <array>
- </array>
- <key>firstName</key>
- <string></string>
- <key>lastName</key>
- <string></string>
</del><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><span class="cx"> <key>resources</key>
</span><span class="lines">@@ -319,15 +310,24 @@
</span><span class="cx"> <string>cn</string>
</span><span class="cx"> <key>fullName</key>
</span><span class="cx"> <string>cn</string>
</span><del>- <key>emailAddresses</key>
- <array>
- </array>
- <key>firstName</key>
- <string></string>
- <key>lastName</key>
- <string></string>
</del><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><ins>+ <key>addresses</key>
+ <dict>
+ <key>rdn</key>
+ <string>ou=buildings</string>
+ <key>geoAttr</key>
+ <string></string>
+ <key>streetAddressAttr</key>
+ <string></string>
+ <key>mapping</key>
+ <dict>
+ <key>recordName</key>
+ <string>cn</string>
+ <key>fullName</key>
+ <string>cn</string>
+ </dict>
+ </dict>
</ins><span class="cx"> </dict>
</span><span class="cx"> <key>groupSchema</key>
</span><span class="cx"> <dict>
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdatafiltersperuserdatapy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/datafilters/peruserdata.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/datafilters/peruserdata.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/datafilters/peruserdata.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -67,7 +67,7 @@
</span><span class="cx">
</span><span class="cx"> # X- properties that are ignored - by default all X- properties are treated as per-user except for the
</span><span class="cx"> # ones listed here
</span><del>- IGNORE_X_PROPERTIES = (Component.HIDDEN_INSTANCE_PROPERTY,)
</del><ins>+ IGNORE_X_PROPERTIES = [Component.HIDDEN_INSTANCE_PROPERTY]
</ins><span class="cx">
</span><span class="cx"> def __init__(self, uid):
</span><span class="cx"> """
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectoryaugmentpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/augment.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/augment.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/augment.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -81,6 +81,7 @@
</span><span class="cx"> "groups" : "Group",
</span><span class="cx"> "locations" : "Location",
</span><span class="cx"> "resources" : "Resource",
</span><ins>+ "addresses" : "Address",
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> class AugmentDB(object):
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectorydirectorypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/directory.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/directory.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/directory.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -86,6 +86,7 @@
</span><span class="cx"> recordType_groups = "groups"
</span><span class="cx"> recordType_locations = "locations"
</span><span class="cx"> recordType_resources = "resources"
</span><ins>+ recordType_addresses = "addresses"
</ins><span class="cx">
</span><span class="cx"> searchContext_location = "location"
</span><span class="cx"> searchContext_resource = "resource"
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectoryldapdirectorypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/ldapdirectory.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/ldapdirectory.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/ldapdirectory.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -1,6 +1,6 @@
</span><span class="cx"> ##
</span><span class="cx"> # Copyright (c) 2008-2009 Aymeric Augustin. All rights reserved.
</span><del>-# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
</del><ins>+# Copyright (c) 2006-2014 Apple Inc. All rights reserved.
</ins><span class="cx"> #
</span><span class="cx"> # Licensed under the Apache License, Version 2.0 (the "License");
</span><span class="cx"> # you may not use this file except in compliance with the License.
</span><span class="lines">@@ -112,8 +112,6 @@
</span><span class="cx"> "guidAttr": "entryUUID",
</span><span class="cx"> "users": {
</span><span class="cx"> "rdn": "ou=People",
</span><del>- "attr": "uid", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
</del><span class="cx"> "filter": None, # additional filter for this type
</span><span class="cx"> "loginEnabledAttr" : "", # attribute controlling login
</span><span class="cx"> "loginEnabledValue" : "yes", # "True" value of above attribute
</span><span class="lines">@@ -129,8 +127,6 @@
</span><span class="cx"> },
</span><span class="cx"> "groups": {
</span><span class="cx"> "rdn": "ou=Group",
</span><del>- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
</del><span class="cx"> "filter": None, # additional filter for this type
</span><span class="cx"> "mapping" : { # maps internal record names to LDAP
</span><span class="cx"> "recordName": "cn",
</span><span class="lines">@@ -142,23 +138,18 @@
</span><span class="cx"> },
</span><span class="cx"> "locations": {
</span><span class="cx"> "rdn": "ou=Places",
</span><del>- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
</del><span class="cx"> "filter": None, # additional filter for this type
</span><span class="cx"> "calendarEnabledAttr" : "", # attribute controlling enabledForCalendaring
</span><span class="cx"> "calendarEnabledValue" : "yes", # "True" value of above attribute
</span><ins>+ "associatedAddressAttr" : "",
</ins><span class="cx"> "mapping" : { # maps internal record names to LDAP
</span><span class="cx"> "recordName": "cn",
</span><span class="cx"> "fullName" : "cn",
</span><span class="cx"> "emailAddresses" : ["mail"], # multiple LDAP fields supported
</span><del>- "firstName" : "givenName",
- "lastName" : "sn",
</del><span class="cx"> },
</span><span class="cx"> },
</span><span class="cx"> "resources": {
</span><span class="cx"> "rdn": "ou=Resources",
</span><del>- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
</del><span class="cx"> "filter": None, # additional filter for this type
</span><span class="cx"> "calendarEnabledAttr" : "", # attribute controlling enabledForCalendaring
</span><span class="cx"> "calendarEnabledValue" : "yes", # "True" value of above attribute
</span><span class="lines">@@ -166,10 +157,18 @@
</span><span class="cx"> "recordName": "cn",
</span><span class="cx"> "fullName" : "cn",
</span><span class="cx"> "emailAddresses" : ["mail"], # multiple LDAP fields supported
</span><del>- "firstName" : "givenName",
- "lastName" : "sn",
</del><span class="cx"> },
</span><span class="cx"> },
</span><ins>+ "addresses": {
+ "rdn": "ou=Buildings",
+ "filter": None, # additional filter for this type
+ "streetAddressAttr" : "",
+ "geoAttr" : "",
+ "mapping" : { # maps internal record names to LDAP
+ "recordName": "cn",
+ "fullName" : "cn",
+ },
+ },
</ins><span class="cx"> },
</span><span class="cx"> "groupSchema": {
</span><span class="cx"> "membersAttr": "member", # how members are specified
</span><span class="lines">@@ -238,8 +237,10 @@
</span><span class="cx"> for recordType in self.recordTypes():
</span><span class="cx"> if self.rdnSchema[recordType]["attr"]:
</span><span class="cx"> attrSet.add(self.rdnSchema[recordType]["attr"])
</span><del>- if self.rdnSchema[recordType].get("calendarEnabledAttr", False):
- attrSet.add(self.rdnSchema[recordType]["calendarEnabledAttr"])
</del><ins>+ for n in ("calendarEnabledAttr", "associatedAddressAttr",
+ "streetAddressAttr", "geoAttr"):
+ if self.rdnSchema[recordType].get(n, False):
+ attrSet.add(self.rdnSchema[recordType][n])
</ins><span class="cx"> for attrList in self.rdnSchema[recordType]["mapping"].values():
</span><span class="cx"> if attrList:
</span><span class="cx"> # Since emailAddresses can map to multiple LDAP fields,
</span><span class="lines">@@ -308,7 +309,7 @@
</span><span class="cx">
</span><span class="cx"> # Build filter
</span><span class="cx"> filterstr = "(!(objectClass=organizationalUnit))"
</span><del>- typeFilter = self.rdnSchema[recordType]["filter"]
</del><ins>+ typeFilter = self.rdnSchema[recordType].get("filter", "")
</ins><span class="cx"> if typeFilter:
</span><span class="cx"> filterstr = "(&%s%s)" % (filterstr, typeFilter)
</span><span class="cx">
</span><span class="lines">@@ -779,6 +780,7 @@
</span><span class="cx"> enabledForAddressBooks = None
</span><span class="cx"> uid = None
</span><span class="cx"> enabledForLogin = True
</span><ins>+ extras = {}
</ins><span class="cx">
</span><span class="cx"> shortNames = tuple(self._getMultipleLdapAttributes(attrs, self.rdnSchema[recordType]["mapping"]["recordName"]))
</span><span class="cx"> if not shortNames:
</span><span class="lines">@@ -795,17 +797,17 @@
</span><span class="cx">
</span><span class="cx"> # Find or build email
</span><span class="cx"> # (The emailAddresses mapping is a list of ldap fields)
</span><del>- emailAddressesMappedTo = self.rdnSchema[recordType]["mapping"]["emailAddresses"]
</del><ins>+ emailAddressesMappedTo = self.rdnSchema[recordType]["mapping"].get("emailAddresses", "")
</ins><span class="cx"> # Supporting either string or list for emailAddresses:
</span><span class="cx"> if isinstance(emailAddressesMappedTo, str):
</span><del>- emailAddresses = set(self._getMultipleLdapAttributes(attrs, self.rdnSchema[recordType]["mapping"]["emailAddresses"]))
</del><ins>+ emailAddresses = set(self._getMultipleLdapAttributes(attrs, self.rdnSchema[recordType]["mapping"].get("emailAddresses", "")))
</ins><span class="cx"> else:
</span><span class="cx"> emailAddresses = set(self._getMultipleLdapAttributes(attrs, *self.rdnSchema[recordType]["mapping"]["emailAddresses"]))
</span><del>- emailSuffix = self.rdnSchema[recordType]["emailSuffix"]
</del><ins>+ emailSuffix = self.rdnSchema[recordType].get("emailSuffix", None)
</ins><span class="cx">
</span><span class="cx"> if len(emailAddresses) == 0 and emailSuffix:
</span><span class="cx"> emailPrefix = self._getUniqueLdapAttribute(attrs,
</span><del>- self.rdnSchema[recordType]["attr"])
</del><ins>+ self.rdnSchema[recordType].get("attr", "cn"))
</ins><span class="cx"> emailAddresses.add(emailPrefix + emailSuffix)
</span><span class="cx">
</span><span class="cx"> proxyGUIDs = ()
</span><span class="lines">@@ -891,6 +893,25 @@
</span><span class="cx"> autoAcceptGroup = self._getUniqueLdapAttribute(attrs,
</span><span class="cx"> self.resourceSchema["autoAcceptGroupAttr"])
</span><span class="cx">
</span><ins>+ if recordType == self.recordType_locations:
+ if self.rdnSchema[recordType]["associatedAddressAttr"]:
+ associatedAddress = self._getUniqueLdapAttribute(attrs,
+ self.rdnSchema[recordType]["associatedAddressAttr"])
+ if associatedAddress:
+ extras["associatedAddress"] = associatedAddress
+
+ elif recordType == self.recordType_addresses:
+ if self.rdnSchema[recordType].get("geoAttr", ""):
+ geo = self._getUniqueLdapAttribute(attrs,
+ self.rdnSchema[recordType]["geoAttr"])
+ if geo:
+ extras["geo"] = geo
+ if self.rdnSchema[recordType].get("streetAddressAttr", ""):
+ street = self._getUniqueLdapAttribute(attrs,
+ self.rdnSchema[recordType]["streetAddressAttr"])
+ if street:
+ extras["streetAddress"] = street
+
</ins><span class="cx"> serverID = partitionID = None
</span><span class="cx"> if self.partitionSchema["serverIdAttr"]:
</span><span class="cx"> serverID = self._getUniqueLdapAttribute(attrs,
</span><span class="lines">@@ -915,6 +936,7 @@
</span><span class="cx"> extProxies = proxyGUIDs,
</span><span class="cx"> extReadOnlyProxies = readOnlyProxyGUIDs,
</span><span class="cx"> attrs = attrs,
</span><ins>+ **extras
</ins><span class="cx"> )
</span><span class="cx">
</span><span class="cx"> if self.augmentService is not None:
</span><span class="lines">@@ -986,7 +1008,7 @@
</span><span class="cx">
</span><span class="cx"> # Build filter
</span><span class="cx"> filterstr = "(!(objectClass=organizationalUnit))"
</span><del>- typeFilter = self.rdnSchema[recordType]["filter"]
</del><ins>+ typeFilter = self.rdnSchema[recordType].get("filter", "")
</ins><span class="cx"> if typeFilter:
</span><span class="cx"> filterstr = "(&%s%s)" % (filterstr, typeFilter)
</span><span class="cx">
</span><span class="lines">@@ -1007,17 +1029,17 @@
</span><span class="cx"> elif indexType == self.INDEX_TYPE_CUA:
</span><span class="cx"> # indexKey is of the form "mailto:test@example.net"
</span><span class="cx"> email = indexKey[7:] # strip "mailto:"
</span><del>- emailSuffix = self.rdnSchema[recordType]["emailSuffix"]
</del><ins>+ emailSuffix = self.rdnSchema[recordType].get("emailSuffix", None)
</ins><span class="cx"> if emailSuffix is not None and email.partition("@")[2] == emailSuffix:
</span><span class="cx"> filterstr = "(&%s(|(&(!(mail=*))(%s=%s))(mail=%s)))" % (
</span><span class="cx"> filterstr,
</span><del>- self.rdnSchema[recordType]["attr"],
</del><ins>+ self.rdnSchema[recordType].get("attr", "cn"),
</ins><span class="cx"> email.partition("@")[0],
</span><span class="cx"> ldapEsc(email)
</span><span class="cx"> )
</span><span class="cx"> else:
</span><span class="cx"> # emailAddresses can map to multiple LDAP fields
</span><del>- ldapFields = self.rdnSchema[recordType]["mapping"]["emailAddresses"]
</del><ins>+ ldapFields = self.rdnSchema[recordType]["mapping"].get("emailAddresses", "")
</ins><span class="cx"> if isinstance(ldapFields, str):
</span><span class="cx"> if ldapFields:
</span><span class="cx"> subfilter = "(%s=%s)" % (ldapFields, ldapEsc(email))
</span><span class="lines">@@ -1112,7 +1134,7 @@
</span><span class="cx"> typeCounts[recordType] = 0
</span><span class="cx"> base = self.typeDNs[recordType]
</span><span class="cx"> scope = ldap.SCOPE_SUBTREE
</span><del>- extraFilter = self.rdnSchema[recordType]["filter"]
</del><ins>+ extraFilter = self.rdnSchema[recordType].get("filter", "")
</ins><span class="cx"> filterstr = buildFilterFromTokens(recordType, self.rdnSchema[recordType]["mapping"],
</span><span class="cx"> tokens, extra=extraFilter)
</span><span class="cx">
</span><span class="lines">@@ -1453,7 +1475,7 @@
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> filterStr = None
</span><del>- tokens = [ldapEsc(t) for t in tokens if len(t) > 2]
</del><ins>+ tokens = [ldapEsc(t) for t in tokens]
</ins><span class="cx"> if len(tokens) == 0:
</span><span class="cx"> return None
</span><span class="cx">
</span><span class="lines">@@ -1506,7 +1528,7 @@
</span><span class="cx"> guid, shortNames, authIDs, fullName,
</span><span class="cx"> firstName, lastName, emailAddresses,
</span><span class="cx"> uid, dn, memberGUIDs, extProxies, extReadOnlyProxies,
</span><del>- attrs
</del><ins>+ attrs, **kwargs
</ins><span class="cx"> ):
</span><span class="cx"> super(LdapDirectoryRecord, self).__init__(
</span><span class="cx"> service = service,
</span><span class="lines">@@ -1521,6 +1543,7 @@
</span><span class="cx"> extProxies = extProxies,
</span><span class="cx"> extReadOnlyProxies = extReadOnlyProxies,
</span><span class="cx"> uid = uid,
</span><ins>+ **kwargs
</ins><span class="cx"> )
</span><span class="cx">
</span><span class="cx"> # Save attributes of dn and attrs in case you might need them later
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectorytesttest_aggregatepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/test_aggregate.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/test_aggregate.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/test_aggregate.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -55,6 +55,7 @@
</span><span class="cx"> groups = property(_records("groups"))
</span><span class="cx"> locations = property(_records("locations"))
</span><span class="cx"> resources = property(_records("resources"))
</span><ins>+ addresses = property(_records("addresses"))
</ins><span class="cx">
</span><span class="cx"> recordTypePrefixes = tuple(s[0] for s in testServices)
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectorytesttest_ldapdirectorypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/test_ldapdirectory.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/test_ldapdirectory.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/test_ldapdirectory.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -540,8 +540,6 @@
</span><span class="cx"> "recordName": "cn",
</span><span class="cx"> "fullName" : "cn",
</span><span class="cx"> "emailAddresses" : ["mail", "emailAliases"],
</span><del>- "firstName" : "givenName",
- "lastName" : "sn",
</del><span class="cx"> },
</span><span class="cx"> },
</span><span class="cx"> "locations": {
</span><span class="lines">@@ -551,12 +549,11 @@
</span><span class="cx"> "filter": "(objectClass=apple-resource)", # additional filter for this type
</span><span class="cx"> "calendarEnabledAttr" : "", # attribute controlling calendaring
</span><span class="cx"> "calendarEnabledValue" : "yes", # "True" value of above attribute
</span><ins>+ "associatedAddressAttr" : "assocAddr",
</ins><span class="cx"> "mapping": { # maps internal record names to LDAP
</span><span class="cx"> "recordName": "cn",
</span><span class="cx"> "fullName" : "cn",
</span><span class="cx"> "emailAddresses" : "", # old style, single string
</span><del>- "firstName" : "givenName",
- "lastName" : "sn",
</del><span class="cx"> },
</span><span class="cx"> },
</span><span class="cx"> "resources": {
</span><span class="lines">@@ -570,10 +567,17 @@
</span><span class="cx"> "recordName": "cn",
</span><span class="cx"> "fullName" : "cn",
</span><span class="cx"> "emailAddresses" : [], # new style, array
</span><del>- "firstName" : "givenName",
- "lastName" : "sn",
</del><span class="cx"> },
</span><span class="cx"> },
</span><ins>+ "addresses": {
+ "rdn": "cn=Buildings",
+ "geoAttr" : "coordinates",
+ "streetAddressAttr" : "postal",
+ "mapping": { # maps internal record names to LDAP
+ "recordName": "cn",
+ "fullName" : "cn",
+ },
+ },
</ins><span class="cx"> },
</span><span class="cx"> "groupSchema": {
</span><span class="cx"> "membersAttr": "uniqueMember", # how members are specified
</span><span class="lines">@@ -1521,6 +1525,39 @@
</span><span class="cx"> self.service.recordType_users)
</span><span class="cx"> self.assertEquals(record.guid, guid.upper())
</span><span class="cx">
</span><ins>+ # Location with associated Address
+
+ dn = "cn=odtestlocation,cn=locations,dc=example,dc=com"
+ guid = "D3094652-344B-4633-8DB8-09639FA00FB6"
+ attrs = {
+ "apple-generateduid": [guid],
+ "cn": ["odtestlocation"],
+ "assocAddr" : ["6C6CD280-E6E3-11DF-9492-0800200C9A66"],
+ }
+ record = self.service._ldapResultToRecord(dn, attrs,
+ self.service.recordType_locations)
+ self.assertEquals(record.extras, {
+ "associatedAddress": "6C6CD280-E6E3-11DF-9492-0800200C9A66"
+ })
+
+ # Address with street and geo
+
+ dn = "cn=odtestaddress,cn=buildings,dc=example,dc=com"
+ guid = "6C6CD280-E6E3-11DF-9492-0800200C9A66"
+ attrs = {
+ "apple-generateduid": [guid],
+ "cn": ["odtestaddress"],
+ "coordinates" : ["geo:1,2"],
+ "postal" : ["1 Infinite Loop, Cupertino, CA"],
+ }
+ record = self.service._ldapResultToRecord(dn, attrs,
+ self.service.recordType_addresses)
+ self.assertEquals(record.extras, {
+ "geo": "geo:1,2",
+ "streetAddress" : "1 Infinite Loop, Cupertino, CA",
+ })
+
+
</ins><span class="cx"> def test_listRecords(self):
</span><span class="cx"> """
</span><span class="cx"> listRecords makes an LDAP query (with fake results in this test)
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectorytesttest_xmlfilepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/test_xmlfile.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/test_xmlfile.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/test_xmlfile.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -34,7 +34,8 @@
</span><span class="cx"> DirectoryService.recordType_users,
</span><span class="cx"> DirectoryService.recordType_groups,
</span><span class="cx"> DirectoryService.recordType_locations,
</span><del>- DirectoryService.recordType_resources
</del><ins>+ DirectoryService.recordType_resources,
+ DirectoryService.recordType_addresses,
</ins><span class="cx"> ))
</span><span class="cx">
</span><span class="cx"> users = {
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectorytestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/util.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/util.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/test/util.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -44,6 +44,10 @@
</span><span class="cx"> # Subclass should init this to a dict of resourcenames keys and dict values.
</span><span class="cx"> resources = {}
</span><span class="cx">
</span><ins>+ # Subclass should init this to a dict of addressname keys and dict values.
+ addresses = {}
+
+
</ins><span class="cx"> # Subclass should init this to an IDirectoryService implementation class.
</span><span class="cx"> def service(self):
</span><span class="cx"> """
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectoryxmlaccountsparserpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/xmlaccountsparser.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/xmlaccountsparser.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/xmlaccountsparser.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -41,6 +41,7 @@
</span><span class="cx"> ELEMENT_GROUP = "group"
</span><span class="cx"> ELEMENT_LOCATION = "location"
</span><span class="cx"> ELEMENT_RESOURCE = "resource"
</span><ins>+ELEMENT_ADDRESS = "address"
</ins><span class="cx">
</span><span class="cx"> ELEMENT_SHORTNAME = "uid"
</span><span class="cx"> ELEMENT_GUID = "guid"
</span><span class="lines">@@ -65,6 +66,7 @@
</span><span class="cx"> ELEMENT_GROUP : DirectoryService.recordType_groups,
</span><span class="cx"> ELEMENT_LOCATION : DirectoryService.recordType_locations,
</span><span class="cx"> ELEMENT_RESOURCE : DirectoryService.recordType_resources,
</span><ins>+ ELEMENT_ADDRESS : DirectoryService.recordType_addresses,
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> class XMLAccountsParser(object):
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavdirectoryxmlfilepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/xmlfile.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/xmlfile.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/directory/xmlfile.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -71,6 +71,7 @@
</span><span class="cx"> self.recordType_groups,
</span><span class="cx"> self.recordType_locations,
</span><span class="cx"> self.recordType_resources,
</span><ins>+ self.recordType_addresses,
</ins><span class="cx"> ),
</span><span class="cx"> 'realmName' : '/Search',
</span><span class="cx"> 'statSeconds' : 15,
</span><span class="lines">@@ -373,6 +374,7 @@
</span><span class="cx"> 'groups' : 'group',
</span><span class="cx"> 'locations' : 'location',
</span><span class="cx"> 'resources' : 'resource',
</span><ins>+ 'addresses' : 'address',
</ins><span class="cx"> }
</span><span class="cx"> xmlType = xmlTypes[principal.recordType]
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtwistedcaldavstdconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/stdconfig.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/stdconfig.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/twistedcaldav/stdconfig.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -34,6 +34,7 @@
</span><span class="cx"> from twistedcaldav.util import getPasswordFromKeychain
</span><span class="cx"> from twistedcaldav.util import KeychainAccessError, KeychainPasswordNotFound
</span><span class="cx"> from twistedcaldav.util import computeProcessCount
</span><ins>+from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
</ins><span class="cx">
</span><span class="cx"> from calendarserver.push.util import getAPNTopicFromCertificate
</span><span class="cx">
</span><span class="lines">@@ -166,7 +167,7 @@
</span><span class="cx"> DEFAULT_RESOURCE_PARAMS = {
</span><span class="cx"> "twistedcaldav.directory.xmlfile.XMLDirectoryService": {
</span><span class="cx"> "xmlFile": "resources.xml",
</span><del>- "recordTypes" : ("locations", "resources"),
</del><ins>+ "recordTypes" : ("locations", "resources", "addresses"),
</ins><span class="cx"> },
</span><span class="cx"> "twistedcaldav.directory.appleopendirectory.OpenDirectoryService": {
</span><span class="cx"> "node": "/Search",
</span><span class="lines">@@ -583,10 +584,13 @@
</span><span class="cx">
</span><span class="cx"> "Calendars" : {
</span><span class="cx"> "Enabled" : True, # Calendar on/off switch
</span><ins>+ "IgnorePerUserProperties" : [
+ "X-APPLE-STRUCTURED-LOCATION",
+ ],
</ins><span class="cx"> },
</span><span class="cx"> "AddressBooks" : {
</span><span class="cx"> "Enabled" : False, # Address Books on/off switch
</span><del>- }
</del><ins>+ },
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> "RestrictCalendarsToOneComponentType" : True, # Only allow calendars to be created with a single component type
</span><span class="lines">@@ -1516,7 +1520,16 @@
</span><span class="cx"> (direction,))
</span><span class="cx">
</span><span class="cx">
</span><ins>+def _updateSharing(configDict, reloading=False):
+ #
+ # Sharing
+ #
</ins><span class="cx">
</span><ins>+ # Transfer configured non-per-user property names to PerUserDataFilter
+ for propertyName in configDict.Sharing.Calendars.IgnorePerUserProperties:
+ PerUserDataFilter.IGNORE_X_PROPERTIES.append(propertyName)
+
+
</ins><span class="cx"> def _updateServers(configDict, reloading=False):
</span><span class="cx"> from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers
</span><span class="cx"> if configDict.Servers.Enabled:
</span><span class="lines">@@ -1594,6 +1607,7 @@
</span><span class="cx"> _updateLogLevels,
</span><span class="cx"> _updateNotifications,
</span><span class="cx"> _updateScheduling,
</span><ins>+ _updateSharing,
</ins><span class="cx"> _updateServers,
</span><span class="cx"> _updateCompliance,
</span><span class="cx"> )
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/sql.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/sql.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/sql.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -1938,6 +1938,43 @@
</span><span class="cx"> self._componentChanged = True
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def addStructuredLocation(self, component):
+ """
+ Scan the component for ROOM attendees; if any are associated with an
+ address record which has street address and geo coordinates, add an
+ X-APPLE-STRUCTURED-LOCATION property and update the LOCATION property
+ to contain the name and street address.
+ """
+ for sub in component.subcomponents():
+ for attendee in sub.getAllAttendeeProperties():
+ if attendee.parameterValue("CUTYPE") == "ROOM":
+ value = attendee.value()
+ if value.startswith("urn:uuid:"):
+ guid = value[9:]
+ loc = self.directoryService().recordWithGUID(guid)
+ if loc is not None:
+ guid = loc.extras.get("associatedAddress",
+ None)
+ if guid is not None:
+ addr = self.directoryService().recordWithGUID(guid)
+ if addr is not None:
+ street = addr.extras.get("streetAddress", "")
+ geo = addr.extras.get("geo", "")
+ if street and geo:
+ title = attendee.parameterValue("CN")
+ params = {
+ "X-ADDRESS" : street,
+ "X-APPLE-RADIUS" : "71",
+ "X-TITLE" : title,
+ }
+ structured = Property("X-APPLE-STRUCTURED-LOCATION",
+ "geo:%s" % (geo,), params=params,
+ valuetype=PyCalendarValue.VALUETYPE_URI)
+ sub.replaceProperty(structured)
+ sub.replaceProperty(Property("LOCATION",
+ "%s\n%s" % (title, street)))
+
+
</ins><span class="cx"> @inlineCallbacks
</span><span class="cx"> def doImplicitScheduling(self, component, inserting, internal_state, split_details=None):
</span><span class="cx">
</span><span class="lines">@@ -2150,6 +2187,9 @@
</span><span class="cx"> # Default/duplicate alarms
</span><span class="cx"> self.processAlarms(component, inserting)
</span><span class="cx">
</span><ins>+ # Process structured location
+ self.addStructuredLocation(component)
+
</ins><span class="cx"> # Do scheduling
</span><span class="cx"> implicit_result = (yield self.doImplicitScheduling(component, inserting, internal_state))
</span><span class="cx"> if isinstance(implicit_result, int):
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtxdavcaldavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/test/test_sql.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/test/test_sql.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/test/test_sql.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -2134,7 +2134,82 @@
</span><span class="cx"> self.assertEqual(len(self.flushLoggedErrors(InvalidOverriddenInstanceError)), 1)
</span><span class="cx">
</span><span class="cx">
</span><ins>+ @inlineCallbacks
+ def test_setComponent_structuredLocation(self):
+ """
+ Verify ROOM attendees who have street address and geo information
+ within the directory will get X-APPLE-STRUCTURED-LOCATION properties
+ added, as well as updated LOCATION properties.
+ """
</ins><span class="cx">
</span><ins>+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//Mac OS X 10.9.1//EN
+BEGIN:VEVENT
+UID:561F5DBB-3F38-4B3A-986F-DD05CBAF554F
+DTSTART;TZID=America/Los_Angeles:20131211T164500
+DTEND;TZID=America/Los_Angeles:20131211T174500
+ATTENDEE;CN=Conference Room One;CUTYPE=ROOM;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPAN
+ T;SCHEDULE-STATUS=2.0:urn:uuid:room1
+ATTENDEE;CN=User 01;CUTYPE=INDIVIDUAL;EMAIL=user01@example.com;PARTSTAT=AC
+ CEPTED:urn:uuid:user01
+CREATED:20131211T221854Z
+DTSTAMP:20131211T230632Z
+ORGANIZER;CN=User 01;EMAIL=user01@example.com:urn:uuid:user01
+RRULE:FREQ=DAILY;COUNT=5
+SEQUENCE:8
+SUMMARY:locations
+TRANSP:OPAQUE
+END:VEVENT
+BEGIN:VEVENT
+UID:561F5DBB-3F38-4B3A-986F-DD05CBAF554F
+RECURRENCE-ID;TZID=America/Los_Angeles:20131214T164500
+DTSTART;TZID=America/Los_Angeles:20131214T160000
+DTEND;TZID=America/Los_Angeles:20131214T170000
+ATTENDEE;CN=Conference Room Two;CUTYPE=ROOM;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPAN
+ T;SCHEDULE-STATUS=2.0:urn:uuid:room2
+ATTENDEE;CN=User 01;CUTYPE=INDIVIDUAL;EMAIL=user01@example.com;PARTSTAT=AC
+ CEPTED:urn:uuid:user01
+CREATED:20131211T221854Z
+DTSTAMP:20131211T230632Z
+ORGANIZER;CN=User 01;EMAIL=user01@example.com:urn:uuid:user01
+SEQUENCE:8
+SUMMARY:locations
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ calendar = yield self.calendarUnderTest(name="calendar", home="user01")
+ yield calendar.createCalendarObjectWithName("structured.ics",
+ Component.fromString(data))
+ cobj = yield self.calendarObjectUnderTest(name="structured.ics",
+ calendar_name="calendar", home="user01")
+ comp = yield cobj.component()
+ components = list(comp.subcomponents())
+
+ # Check first component
+ locProp = components[0].getProperty("LOCATION")
+ self.assertEquals(locProp.value(),
+ "Conference Room One\n1 Infinite Loop, Cupertino, CA 95014")
+ structProp = components[0].getProperty("X-APPLE-STRUCTURED-LOCATION")
+ self.assertEquals(structProp.value(),
+ "geo:37.331741,-122.030333")
+
+ # Check second component
+ locProp = components[1].getProperty("LOCATION")
+ self.assertEquals(locProp.value(),
+ "Conference Room Two\n2 Infinite Loop, Cupertino, CA 95014")
+ structProp = components[1].getProperty("X-APPLE-STRUCTURED-LOCATION")
+ self.assertEquals(structProp.value(),
+ "geo:37.332633,-122.030502")
+
+ yield self.commit()
+
+
+
+
</ins><span class="cx"> class CalendarObjectSplitting(CommonCommonTests, unittest.TestCase):
</span><span class="cx"> """
</span><span class="cx"> CalendarObject splitting tests
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtxdavcaldavdatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/test/util.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/test/util.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/test/util.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -63,9 +63,11 @@
</span><span class="cx"> cutype="INDIVIDUAL",
</span><span class="cx"> locallyHosted=True,
</span><span class="cx"> thisServer=True,
</span><ins>+ extras={},
</ins><span class="cx"> ):
</span><span class="cx">
</span><del>- super(TestCalendarStoreDirectoryRecord, self).__init__(uid, shortNames, fullName)
</del><ins>+ super(TestCalendarStoreDirectoryRecord, self).__init__(uid, shortNames,
+ fullName, extras=extras)
</ins><span class="cx"> self.uid = uid
</span><span class="cx"> self.shortNames = shortNames
</span><span class="cx"> self.fullName = fullName
</span><span class="lines">@@ -164,6 +166,36 @@
</span><span class="cx"> for uid in homes:
</span><span class="cx"> directory.addRecord(buildDirectoryRecord(uid))
</span><span class="cx">
</span><ins>+ # Structured Locations
+ directory.addRecord(TestCalendarStoreDirectoryRecord(
+ "il1", ("il1",), "1 Infinite Loop", [],
+ extras={
+ "geo" : "37.331741,-122.030333",
+ "streetAddress" : "1 Infinite Loop, Cupertino, CA 95014",
+ }
+ ))
+ directory.addRecord(TestCalendarStoreDirectoryRecord(
+ "il2", ("il2",), "2 Infinite Loop", [],
+ extras={
+ "geo" : "37.332633,-122.030502",
+ "streetAddress" : "2 Infinite Loop, Cupertino, CA 95014",
+ }
+ ))
+ directory.addRecord(TestCalendarStoreDirectoryRecord(
+ "room1", ("room1",), "Conference Room One",
+ frozenset(("urn:uuid:room1",)),
+ extras={
+ "associatedAddress" : "il1",
+ }
+ ))
+ directory.addRecord(TestCalendarStoreDirectoryRecord(
+ "room2", ("room2",), "Conference Room Two",
+ frozenset(("urn:uuid:room2",)),
+ extras={
+ "associatedAddress" : "il2",
+ }
+ ))
+
</ins><span class="cx"> return directory
</span><span class="cx">
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtxdavcommondatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/test/util.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/test/util.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/test/util.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -111,6 +111,11 @@
</span><span class="cx"> def recordWithUID(self, uid):
</span><span class="cx"> return self.records.get(uid)
</span><span class="cx">
</span><ins>+ def recordWithGUID(self, guid):
+ for record in self.records.itervalues():
+ if record.guid == guid:
+ return record
+ return None
</ins><span class="cx">
</span><span class="cx"> def addRecord(self, record):
</span><span class="cx"> self.records[record.uid] = record
</span><span class="lines">@@ -121,11 +126,13 @@
</span><span class="cx">
</span><span class="cx"> implements(IStoreDirectoryRecord)
</span><span class="cx">
</span><del>- def __init__(self, uid, shortNames, fullName):
</del><ins>+ def __init__(self, uid, shortNames, fullName, extras={}):
</ins><span class="cx"> self.uid = uid
</span><ins>+ self.guid = uid
</ins><span class="cx"> self.shortNames = shortNames
</span><span class="cx"> self.fullName = fullName
</span><span class="cx"> self.displayName = self.fullName if self.fullName else self.shortNames[0]
</span><ins>+ self.extras = extras
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtxdavcommonidirectoryservicepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/idirectoryservice.py (12242 => 12243)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/idirectoryservice.py        2014-01-06 21:46:00 UTC (rev 12242)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/idirectoryservice.py        2014-01-06 22:03:14 UTC (rev 12243)
</span><span class="lines">@@ -39,8 +39,15 @@
</span><span class="cx"> @rtype: L{IStoreDirectoryRecord}
</span><span class="cx"> """
</span><span class="cx">
</span><ins>+ def recordWithGUID(guid): #@NoSelf
+ """
+ Return the record for the specified store guid.
</ins><span class="cx">
</span><ins>+ @return: the record.
+ @rtype: L{IStoreDirectoryRecord}
+ """
</ins><span class="cx">
</span><ins>+
</ins><span class="cx"> class IStoreDirectoryRecord(Interface):
</span><span class="cx"> """
</span><span class="cx"> Directory record object
</span></span></pre>
</div>
</div>
</body>
</html>