<!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>[13683] CalendarServer/trunk</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/13683">13683</a></dd>
<dt>Author</dt> <dd>sagen@apple.com</dd>
<dt>Date</dt> <dd>2014-06-24 19:26:01 -0700 (Tue, 24 Jun 2014)</dd>
</dl>
<h3>Log Message</h3>
<pre>Groups' disappearance from the directory affects their extant value</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunkcalendarservertoolsprincipalspy">CalendarServer/trunk/calendarserver/tools/principals.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolstestdeprovisioncaldavdplist">CalendarServer/trunk/calendarserver/tools/test/deprovision/caldavd.plist</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolstestgatewaycaldavdplist">CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolstestprincipalscaldavdplist">CalendarServer/trunk/calendarserver/tools/test/principals/caldavd.plist</a></li>
<li><a href="#CalendarServertrunkconfresourcescaldavdresourcesplist">CalendarServer/trunk/conf/resources/caldavd-resources.plist</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoresqlpy">CalendarServer/trunk/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresqlpy">CalendarServer/trunk/txdav/common/datastore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavwhodelegatespy">CalendarServer/trunk/txdav/who/delegates.py</a></li>
<li><a href="#CalendarServertrunktxdavwhogroupspy">CalendarServer/trunk/txdav/who/groups.py</a></li>
<li><a href="#CalendarServertrunktxdavwhotesttest_delegatespy">CalendarServer/trunk/txdav/who/test/test_delegates.py</a></li>
<li><a href="#CalendarServertrunktxdavwhotesttest_group_attendeespy">CalendarServer/trunk/txdav/who/test/test_group_attendees.py</a></li>
<li><a href="#CalendarServertrunktxdavwhotesttest_groupspy">CalendarServer/trunk/txdav/who/test/test_groups.py</a></li>
<li><a href="#CalendarServertrunktxdavwhoutilpy">CalendarServer/trunk/txdav/who/util.py</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunkcalendarservertoolsprincipalspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/principals.py (13682 => 13683)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/principals.py        2014-06-25 02:24:15 UTC (rev 13682)
+++ CalendarServer/trunk/calendarserver/tools/principals.py        2014-06-25 02:26:01 UTC (rev 13683)
</span><span class="lines">@@ -787,7 +787,9 @@
</span><span class="cx"> txn = store.newTransaction()
</span><span class="cx"> groupUIDs = yield txn.allGroupDelegates()
</span><span class="cx"> for groupUID in groupUIDs:
</span><del>- groupID, name, _ignore_membershipHash, modified = yield txn.groupByUID(
</del><ins>+ (
+ groupID, name, _ignore_membershipHash, modified, extant
+ ) = yield txn.groupByUID(
</ins><span class="cx"> groupUID
</span><span class="cx"> )
</span><span class="cx"> print("Group: \"{name}\" ({uid})".format(name=name, uid=groupUID))
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolstestdeprovisioncaldavdplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/test/deprovision/caldavd.plist (13682 => 13683)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/test/deprovision/caldavd.plist        2014-06-25 02:24:15 UTC (rev 13682)
+++ CalendarServer/trunk/calendarserver/tools/test/deprovision/caldavd.plist        2014-06-25 02:26:01 UTC (rev 13683)
</span><span class="lines">@@ -115,12 +115,6 @@
</span><span class="cx"> <key>MaxAttendeesPerInstance</key>
</span><span class="cx"> <integer>100</integer>
</span><span class="cx">
</span><del>- <!-- Maximum number of instances allowed for a single RRULE -->
- <!-- 0 for no limit -->
- <key>MaxInstancesForRRULE</key>
- <integer>400</integer>
-
-
</del><span class="cx"> <!--
</span><span class="cx"> Directory service
</span><span class="cx">
</span><span class="lines">@@ -135,7 +129,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>xmlFile</key>
</span><span class="lines">@@ -155,7 +149,7 @@
</span><span class="cx"> <true/>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>xmlFile</key>
</span><span class="lines">@@ -167,14 +161,14 @@
</span><span class="cx"> </array>
</span><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><del>-
</del><ins>+
</ins><span class="cx"> <!-- Open Directory Service (Mac OS X) -->
</span><span class="cx"> <!--
</span><span class="cx"> <key>DirectoryService</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>node</key>
</span><span class="lines">@@ -198,7 +192,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.augment.AugmentXMLDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>xmlFiles</key>
</span><span class="lines">@@ -207,14 +201,14 @@
</span><span class="cx"> </array>
</span><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><del>-
</del><ins>+
</ins><span class="cx"> <!-- Sqlite Augment Service -->
</span><span class="cx"> <!--
</span><span class="cx"> <key>AugmentService</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.augment.AugmentSqliteDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>dbpath</key>
</span><span class="lines">@@ -229,7 +223,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.augment.AugmentPostgreSQLDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>host</key>
</span><span class="lines">@@ -245,7 +239,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.calendaruserproxy.ProxySqliteDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>dbpath</key>
</span><span class="lines">@@ -259,7 +253,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>host</key>
</span><span class="lines">@@ -687,7 +681,7 @@
</span><span class="cx"> <!-- Support for Content-Encoding compression options as specified in RFC2616 Section 3.5 -->
</span><span class="cx"> <key>ResponseCompression</key>
</span><span class="cx"> <false/>
</span><del>-
</del><ins>+
</ins><span class="cx"> <!-- The retry-after value (in seconds) to return with a 503 error. -->
</span><span class="cx"> <key>HTTPRetryAfter</key>
</span><span class="cx"> <integer>180</integer>
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolstestgatewaycaldavdplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist (13682 => 13683)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist        2014-06-25 02:24:15 UTC (rev 13682)
+++ CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist        2014-06-25 02:26:01 UTC (rev 13683)
</span><span class="lines">@@ -127,12 +127,6 @@
</span><span class="cx"> <key>MaxAttendeesPerInstance</key>
</span><span class="cx"> <integer>100</integer>
</span><span class="cx">
</span><del>- <!-- Maximum number of instances allowed for a single RRULE -->
- <!-- 0 for no limit -->
- <key>MaxInstancesForRRULE</key>
- <integer>400</integer>
-
-
</del><span class="cx"> <!--
</span><span class="cx"> Directory service
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolstestprincipalscaldavdplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/test/principals/caldavd.plist (13682 => 13683)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/test/principals/caldavd.plist        2014-06-25 02:24:15 UTC (rev 13682)
+++ CalendarServer/trunk/calendarserver/tools/test/principals/caldavd.plist        2014-06-25 02:26:01 UTC (rev 13683)
</span><span class="lines">@@ -119,12 +119,6 @@
</span><span class="cx"> <key>MaxAttendeesPerInstance</key>
</span><span class="cx"> <integer>100</integer>
</span><span class="cx">
</span><del>- <!-- Maximum number of instances allowed for a single RRULE -->
- <!-- 0 for no limit -->
- <key>MaxInstancesForRRULE</key>
- <integer>400</integer>
-
-
</del><span class="cx"> <!--
</span><span class="cx"> Directory service
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServertrunkconfresourcescaldavdresourcesplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/conf/resources/caldavd-resources.plist (13682 => 13683)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/conf/resources/caldavd-resources.plist        2014-06-25 02:24:15 UTC (rev 13682)
+++ CalendarServer/trunk/conf/resources/caldavd-resources.plist        2014-06-25 02:26:01 UTC (rev 13683)
</span><span class="lines">@@ -99,12 +99,6 @@
</span><span class="cx"> <key>MaxAttendeesPerInstance</key>
</span><span class="cx"> <integer>100</integer>
</span><span class="cx">
</span><del>- <!-- Maximum number of instances allowed for a single RRULE -->
- <!-- 0 for no limit -->
- <key>MaxInstancesForRRULE</key>
- <integer>400</integer>
-
-
</del><span class="cx"> <!--
</span><span class="cx"> Directory service
</span><span class="cx">
</span><span class="lines">@@ -119,7 +113,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>xmlFile</key>
</span><span class="lines">@@ -131,14 +125,14 @@
</span><span class="cx"> </array>
</span><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>ResourceService</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>Enabled</key>
</span><span class="cx"> <true/>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>xmlFile</key>
</span><span class="lines">@@ -150,14 +144,14 @@
</span><span class="cx"> </array>
</span><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><del>-
</del><ins>+
</ins><span class="cx"> <!-- Open Directory Service (Mac OS X) -->
</span><span class="cx"> <!--
</span><span class="cx"> <key>DirectoryService</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>node</key>
</span><span class="lines">@@ -181,7 +175,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.augment.AugmentXMLDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>xmlFiles</key>
</span><span class="lines">@@ -190,14 +184,14 @@
</span><span class="cx"> </array>
</span><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><del>-
</del><ins>+
</ins><span class="cx"> <!-- Sqlite Augment Service -->
</span><span class="cx"> <!--
</span><span class="cx"> <key>AugmentService</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.augment.AugmentSqliteDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>dbpath</key>
</span><span class="lines">@@ -212,7 +206,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.augment.AugmentPostgreSQLDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>host</key>
</span><span class="lines">@@ -228,7 +222,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.calendaruserproxy.ProxySqliteDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>dbpath</key>
</span><span class="lines">@@ -242,7 +236,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>host</key>
</span><span class="lines">@@ -672,7 +666,7 @@
</span><span class="cx"> <!-- Support for Content-Encoding compression -->
</span><span class="cx"> <key>ResponseCompression</key>
</span><span class="cx"> <false/>
</span><del>-
</del><ins>+
</ins><span class="cx"> <!-- The retry-after value (in seconds) to return with a 503 error. -->
</span><span class="cx"> <key>HTTPRetryAfter</key>
</span><span class="cx"> <integer>180</integer>
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py (13682 => 13683)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/sql.py        2014-06-25 02:24:15 UTC (rev 13682)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py        2014-06-25 02:26:01 UTC (rev 13683)
</span><span class="lines">@@ -1996,7 +1996,10 @@
</span><span class="cx"> groupUID = groupRecord.uid
</span><span class="cx"> else:
</span><span class="cx"> groupUID = uidFromCalendarUserAddress(groupCUA)
</span><del>- groupID, _ignore_name, membershipHash, _ignore_modDate = yield self._txn.groupByUID(groupUID)
</del><ins>+ (
+ groupID, _ignore_name, membershipHash, _ignore_modDate,
+ _ignore_extant
+ ) = yield self._txn.groupByUID(groupUID)
</ins><span class="cx">
</span><span class="cx"> if groupID in groupIDToMembershipHashMap:
</span><span class="cx"> if groupIDToMembershipHashMap[groupID] != membershipHash:
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/sql.py (13682 => 13683)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql.py        2014-06-25 02:24:15 UTC (rev 13682)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py        2014-06-25 02:26:01 UTC (rev 13683)
</span><span class="lines">@@ -1033,8 +1033,8 @@
</span><span class="cx"> {
</span><span class="cx"> gr.MEMBERSHIP_HASH: Parameter("membershipHash"),
</span><span class="cx"> gr.NAME: Parameter("name"),
</span><del>- gr.MODIFIED:
- Parameter("timestamp")
</del><ins>+ gr.MODIFIED: Parameter("timestamp"),
+ gr.EXTANT: Parameter("extant"),
</ins><span class="cx"> },
</span><span class="cx"> Where=(gr.GROUP_UID == Parameter("groupUID"))
</span><span class="cx"> )
</span><span class="lines">@@ -1044,7 +1044,7 @@
</span><span class="cx"> def _groupByUID(cls):
</span><span class="cx"> gr = schema.GROUPS
</span><span class="cx"> return Select(
</span><del>- [gr.GROUP_ID, gr.NAME, gr.MEMBERSHIP_HASH, gr.MODIFIED],
</del><ins>+ [gr.GROUP_ID, gr.NAME, gr.MEMBERSHIP_HASH, gr.MODIFIED, gr.EXTANT],
</ins><span class="cx"> From=gr,
</span><span class="cx"> Where=(gr.GROUP_UID == Parameter("groupUID"))
</span><span class="cx"> )
</span><span class="lines">@@ -1054,7 +1054,7 @@
</span><span class="cx"> def _groupByID(cls):
</span><span class="cx"> gr = schema.GROUPS
</span><span class="cx"> return Select(
</span><del>- [gr.GROUP_UID, gr.NAME, gr.MEMBERSHIP_HASH],
</del><ins>+ [gr.GROUP_UID, gr.NAME, gr.MEMBERSHIP_HASH, gr.EXTANT],
</ins><span class="cx"> From=gr,
</span><span class="cx"> Where=(gr.GROUP_ID == Parameter("groupID"))
</span><span class="cx"> )
</span><span class="lines">@@ -1083,11 +1083,12 @@
</span><span class="cx"> )
</span><span class="cx">
</span><span class="cx">
</span><del>- def updateGroup(self, groupUID, name, membershipHash):
</del><ins>+ def updateGroup(self, groupUID, name, membershipHash, extant=True):
</ins><span class="cx"> """
</span><span class="cx"> @type groupUID: C{unicode}
</span><span class="cx"> @type name: C{unicode}
</span><span class="cx"> @type membershipHash: C{str}
</span><ins>+ @type extant: C{boolean}
</ins><span class="cx"> """
</span><span class="cx"> timestamp = datetime.datetime.utcnow()
</span><span class="cx"> return self._updateGroupQuery.on(
</span><span class="lines">@@ -1095,7 +1096,8 @@
</span><span class="cx"> name=name.encode("utf-8"),
</span><span class="cx"> groupUID=groupUID.encode("utf-8"),
</span><span class="cx"> timestamp=timestamp,
</span><del>- membershipHash=membershipHash
</del><ins>+ membershipHash=membershipHash,
+ extant=(1 if extant else 0)
</ins><span class="cx"> )
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -1107,7 +1109,8 @@
</span><span class="cx"> @type groupUID: C{unicode}
</span><span class="cx">
</span><span class="cx"> @return: Deferred firing with tuple of group ID C{str}, group name
</span><del>- C{unicode}, membership hash C{str}, and modified timestamp
</del><ins>+ C{unicode}, membership hash C{str}, modified timestamp, and
+ extant C{boolean}
</ins><span class="cx"> """
</span><span class="cx"> results = (
</span><span class="cx"> yield self._groupByUID.on(
</span><span class="lines">@@ -1120,6 +1123,7 @@
</span><span class="cx"> results[0][1].decode("utf-8"), # name
</span><span class="cx"> results[0][2], # membership hash
</span><span class="cx"> results[0][3], # modified timestamp
</span><ins>+ bool(results[0][4]), # extant
</ins><span class="cx"> ))
</span><span class="cx"> elif create:
</span><span class="cx"> savepoint = SavepointAction("groupByUID")
</span><span class="lines">@@ -1139,6 +1143,7 @@
</span><span class="cx"> results[0][1].decode("utf-8"), # name
</span><span class="cx"> results[0][2], # membership hash
</span><span class="cx"> results[0][3], # modified timestamp
</span><ins>+ bool(results[0][4]), # extant
</ins><span class="cx"> ))
</span><span class="cx"> else:
</span><span class="cx"> raise
</span><span class="lines">@@ -1155,11 +1160,12 @@
</span><span class="cx"> results[0][1].decode("utf-8"), # name
</span><span class="cx"> results[0][2], # membership hash
</span><span class="cx"> results[0][3], # modified timestamp
</span><ins>+ bool(results[0][4]), # extant
</ins><span class="cx"> ))
</span><span class="cx"> else:
</span><span class="cx"> raise
</span><span class="cx"> else:
</span><del>- returnValue((None, None, None, None))
</del><ins>+ returnValue((None, None, None, None, None))
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><span class="lines">@@ -1169,7 +1175,7 @@
</span><span class="cx">
</span><span class="cx"> @type groupID: C{str}
</span><span class="cx"> @return: Deferred firing with a tuple of group UID C{unicode},
</span><del>- group name C{unicode}, and membership hash C{str}
</del><ins>+ group name C{unicode}, membership hash C{str}, and extant C{boolean}
</ins><span class="cx"> """
</span><span class="cx"> try:
</span><span class="cx"> results = (yield self._groupByID.on(self, groupID=groupID))[0]
</span><span class="lines">@@ -1177,7 +1183,8 @@
</span><span class="cx"> results = (
</span><span class="cx"> results[0].decode("utf-8"),
</span><span class="cx"> results[1].decode("utf-8"),
</span><del>- results[2]
</del><ins>+ results[2],
+ bool(results[3])
</ins><span class="cx"> )
</span><span class="cx"> returnValue(results)
</span><span class="cx"> except IndexError:
</span></span></pre></div>
<a id="CalendarServertrunktxdavwhodelegatespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/who/delegates.py (13682 => 13683)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/who/delegates.py        2014-06-25 02:24:15 UTC (rev 13682)
+++ CalendarServer/trunk/txdav/who/delegates.py        2014-06-25 02:26:01 UTC (rev 13683)
</span><span class="lines">@@ -236,7 +236,10 @@
</span><span class="cx"> """
</span><span class="cx"> if delegate.recordType == BaseRecordType.group:
</span><span class="cx"> # find the groupID
</span><del>- groupID, _ignore_name, _ignore_membershipHash, _ignore_modified = yield txn.groupByUID(
</del><ins>+ (
+ groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
+ _ignore_extant
+ ) = yield txn.groupByUID(
</ins><span class="cx"> delegate.uid
</span><span class="cx"> )
</span><span class="cx"> yield txn.addDelegateGroup(delegator.uid, groupID, readWrite)
</span><span class="lines">@@ -260,7 +263,10 @@
</span><span class="cx"> """
</span><span class="cx"> if delegate.recordType == BaseRecordType.group:
</span><span class="cx"> # find the groupID
</span><del>- groupID, _ignore_name, _ignore_membershipHash, _ignore_modified = yield txn.groupByUID(
</del><ins>+ (
+ groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
+ _ignore_extant
+ ) = yield txn.groupByUID(
</ins><span class="cx"> delegate.uid
</span><span class="cx"> )
</span><span class="cx"> yield txn.removeDelegateGroup(delegator.uid, groupID, readWrite)
</span></span></pre></div>
<a id="CalendarServertrunktxdavwhogroupspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/who/groups.py (13682 => 13683)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/who/groups.py        2014-06-25 02:24:15 UTC (rev 13682)
+++ CalendarServer/trunk/txdav/who/groups.py        2014-06-25 02:26:01 UTC (rev 13683)
</span><span class="lines">@@ -157,12 +157,14 @@
</span><span class="cx"> if (component.masterComponent() is None or not component.isRecurring()):
</span><span class="cx">
</span><span class="cx"> # skip non-recurring old events, no instances
</span><del>- if (yield calendarObject.removeOldEventGroupLink(
- component,
- instances=None,
- inserting=False,
- txn=self.transaction
- )):
</del><ins>+ if (
+ yield calendarObject.removeOldEventGroupLink(
+ component,
+ instances=None,
+ inserting=False,
+ txn=self.transaction
+ )
+ ):
</ins><span class="cx"> returnValue(None)
</span><span class="cx"> else:
</span><span class="cx"> # skip recurring old events
</span><span class="lines">@@ -180,12 +182,14 @@
</span><span class="cx"> lowerLimit=truncateLowerLimit,
</span><span class="cx"> ignoreInvalidInstances=True
</span><span class="cx"> )
</span><del>- if (yield calendarObject.removeOldEventGroupLink(
- component,
- instances=instances,
- inserting=False,
- txn=self.transaction
- )):
</del><ins>+ if (
+ yield calendarObject.removeOldEventGroupLink(
+ component,
+ instances=instances,
+ inserting=False,
+ txn=self.transaction
+ )
+ ):
</ins><span class="cx"> returnValue(None)
</span><span class="cx">
</span><span class="cx"> # split spanning events and only update present-future split result
</span><span class="lines">@@ -310,11 +314,17 @@
</span><span class="cx"> ) in changed:
</span><span class="cx"> readDelegateGroupID = writeDelegateGroupID = None
</span><span class="cx"> if readDelegateUID:
</span><del>- readDelegateGroupID, _ignore_name, hash, _ignore_modified = (
</del><ins>+ (
+ readDelegateGroupID, _ignore_name, hash,
+ _ignore_modified, _ignore_extant
+ ) = (
</ins><span class="cx"> yield txn.groupByUID(readDelegateUID)
</span><span class="cx"> )
</span><span class="cx"> if writeDelegateUID:
</span><del>- writeDelegateGroupID, _ignore_name, hash, _ignore_modified = (
</del><ins>+ (
+ writeDelegateGroupID, _ignore_name, hash,
+ _ignore_modified, _ignore_extant
+ ) = (
</ins><span class="cx"> yield txn.groupByUID(writeDelegateUID)
</span><span class="cx"> )
</span><span class="cx"> yield txn.assignExternalDelegates(
</span><span class="lines">@@ -344,20 +354,24 @@
</span><span class="cx"> else:
</span><span class="cx"> self.log.debug("Got group record: {u}", u=record.uid)
</span><span class="cx">
</span><del>- groupID, cachedName, cachedMembershipHash, _ignore_modified = (
- yield txn.groupByUID(
- groupUID,
- create=(record is not None)
- )
</del><ins>+ (
+ groupID, cachedName, cachedMembershipHash, _ignore_modified,
+ extant
+ ) = yield txn.groupByUID(
+ groupUID,
+ create=(record is not None)
</ins><span class="cx"> )
</span><ins>+
</ins><span class="cx"> wps = tuple()
</span><span class="cx"> if groupID:
</span><span class="cx"> if record is not None:
</span><span class="cx"> members = yield record.expandedMembers()
</span><del>- name = record.fullNames[0]
</del><ins>+ name = record.displayName
+ extant = True
</ins><span class="cx"> else:
</span><span class="cx"> members = frozenset()
</span><span class="cx"> name = cachedName
</span><ins>+ extant = False
</ins><span class="cx">
</span><span class="cx"> membershipHashContent = hashlib.md5()
</span><span class="cx"> members = list(members)
</span><span class="lines">@@ -376,7 +390,9 @@
</span><span class="cx">
</span><span class="cx"> if membershipChanged or record is not None:
</span><span class="cx"> # also updates group mod date
</span><del>- yield txn.updateGroup(groupUID, name, membershipHash)
</del><ins>+ yield txn.updateGroup(
+ groupUID, name, membershipHash, extant=extant
+ )
</ins><span class="cx">
</span><span class="cx"> if membershipChanged:
</span><span class="cx"> newMemberUIDs = set()
</span><span class="lines">@@ -461,7 +477,7 @@
</span><span class="cx"> "There are {count} group delegates", count=len(delegatedUIDs)
</span><span class="cx"> )
</span><span class="cx">
</span><del>- # Get groupUIDs for aoo group attendees
</del><ins>+ # Get groupUIDs for all group attendees
</ins><span class="cx"> groupAttendee = schema.GROUP_ATTENDEE
</span><span class="cx"> gr = schema.GROUPS
</span><span class="cx"> rows = yield Select(
</span></span></pre></div>
<a id="CalendarServertrunktxdavwhotesttest_delegatespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/who/test/test_delegates.py (13682 => 13683)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/who/test/test_delegates.py        2014-06-25 02:24:15 UTC (rev 13682)
+++ CalendarServer/trunk/txdav/who/test/test_delegates.py        2014-06-25 02:26:01 UTC (rev 13683)
</span><span class="lines">@@ -207,7 +207,10 @@
</span><span class="cx"> yield self.directory.recordWithShortName(RecordType.user, name)
</span><span class="cx"> )
</span><span class="cx"> newSet.add(record.uid)
</span><del>- groupID, name, _ignore_membershipHash, _ignore_modified = (yield txn.groupByUID(group1.uid))
</del><ins>+ (
+ groupID, name, _ignore_membershipHash, _ignore_modified,
+ _ignore_extant
+ ) = (yield txn.groupByUID(group1.uid))
</ins><span class="cx"> _ignore_numAdded, _ignore_numRemoved = (
</span><span class="cx"> yield self.groupCacher.synchronizeMembers(txn, groupID, newSet)
</span><span class="cx"> )
</span></span></pre></div>
<a id="CalendarServertrunktxdavwhotesttest_group_attendeespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/who/test/test_group_attendees.py (13682 => 13683)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/who/test/test_group_attendees.py        2014-06-25 02:24:15 UTC (rev 13682)
+++ CalendarServer/trunk/txdav/who/test/test_group_attendees.py        2014-06-25 02:26:01 UTC (rev 13683)
</span><span class="lines">@@ -868,7 +868,10 @@
</span><span class="cx"> #finally, simulate an event that has become old
</span><span class="cx"> self.patch(CalendarDirectoryRecordMixin, "expandedMembers", unpatchedExpandedMembers)
</span><span class="cx">
</span><del>- groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self.transactionUnderTest().groupByUID("20000000-0000-0000-0000-000000000001")
</del><ins>+ (
+ groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate,
+ _ignore_extant
+ ) = yield self.transactionUnderTest().groupByUID("20000000-0000-0000-0000-000000000001")
</ins><span class="cx"> ga = schema.GROUP_ATTENDEE
</span><span class="cx"> yield Insert({
</span><span class="cx"> ga.RESOURCE_ID: cobj._resourceID,
</span><span class="lines">@@ -1029,7 +1032,10 @@
</span><span class="cx"> #finally, simulate an event that has become old
</span><span class="cx"> self.patch(CalendarDirectoryRecordMixin, "expandedMembers", unpatchedExpandedMembers)
</span><span class="cx">
</span><del>- groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self.transactionUnderTest().groupByUID("20000000-0000-0000-0000-000000000001")
</del><ins>+ (
+ groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate,
+ _ignore_extant
+ ) = yield self.transactionUnderTest().groupByUID("20000000-0000-0000-0000-000000000001")
</ins><span class="cx"> ga = schema.GROUP_ATTENDEE
</span><span class="cx"> yield Insert({
</span><span class="cx"> ga.RESOURCE_ID: cobj._resourceID,
</span></span></pre></div>
<a id="CalendarServertrunktxdavwhotesttest_groupspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/who/test/test_groups.py (13682 => 13683)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/who/test/test_groups.py        2014-06-25 02:24:15 UTC (rev 13682)
+++ CalendarServer/trunk/txdav/who/test/test_groups.py        2014-06-25 02:26:01 UTC (rev 13683)
</span><span class="lines">@@ -18,11 +18,12 @@
</span><span class="cx"> Group membership caching implementation tests
</span><span class="cx"> """
</span><span class="cx">
</span><del>-from txdav.who.groups import GroupCacher, diffAssignments
</del><span class="cx"> from twext.who.idirectory import RecordType
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks
</span><span class="cx"> from twistedcaldav.test.util import StoreTestCase
</span><span class="cx"> from txdav.common.icommondatastore import NotFoundError
</span><ins>+from txdav.who.groups import GroupCacher, diffAssignments
+from txdav.who.test.support import TestRecord, CalendarInMemoryDirectoryService
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -44,8 +45,8 @@
</span><span class="cx"> txn = store.newTransaction()
</span><span class="cx">
</span><span class="cx"> record = yield self.directory.recordWithUID(u"__top_group_1__")
</span><del>- _ignore_groupID, _ignore_name, _ignore_membershipHash, _ignore_modified = (yield txn.groupByUID(record.uid))
- _ignore_groupID, _ignore_name, _ignore_membershipHash, _ignore_modified = (yield txn.groupByUID(record.uid))
</del><ins>+ yield txn.groupByUID(record.uid)
+ yield txn.groupByUID(record.uid)
</ins><span class="cx">
</span><span class="cx"> yield txn.commit()
</span><span class="cx">
</span><span class="lines">@@ -63,14 +64,19 @@
</span><span class="cx"> record = yield self.directory.recordWithUID(u"__top_group_1__")
</span><span class="cx"> yield self.groupCacher.refreshGroup(txn, record.uid)
</span><span class="cx">
</span><del>- groupID, _ignore_name, membershipHash, _ignore_modified = (yield txn.groupByUID(record.uid))
</del><ins>+ (
+ groupID, _ignore_name, membershipHash, _ignore_modified,
+ extant
+ ) = (yield txn.groupByUID(record.uid))
</ins><span class="cx">
</span><ins>+ self.assertEquals(extant, True)
</ins><span class="cx"> self.assertEquals(membershipHash, "553eb54e3bbb26582198ee04541dbee4")
</span><span class="cx">
</span><del>- groupUID, name, membershipHash = (yield txn.groupByID(groupID))
</del><ins>+ groupUID, name, membershipHash, extant = (yield txn.groupByID(groupID))
</ins><span class="cx"> self.assertEquals(groupUID, record.uid)
</span><span class="cx"> self.assertEquals(name, u"Top Group 1")
</span><span class="cx"> self.assertEquals(membershipHash, "553eb54e3bbb26582198ee04541dbee4")
</span><ins>+ self.assertEquals(extant, True)
</ins><span class="cx">
</span><span class="cx"> members = (yield txn.membersOfGroup(groupID))
</span><span class="cx"> self.assertEquals(
</span><span class="lines">@@ -107,7 +113,10 @@
</span><span class="cx"> # Refresh the group so it's assigned a group_id
</span><span class="cx"> uid = u"__top_group_1__"
</span><span class="cx"> yield self.groupCacher.refreshGroup(txn, uid)
</span><del>- groupID, name, _ignore_membershipHash, _ignore_modified = (yield txn.groupByUID(uid))
</del><ins>+ (
+ groupID, name, _ignore_membershipHash, _ignore_modified,
+ _ignore_extant
+ ) = yield txn.groupByUID(uid)
</ins><span class="cx">
</span><span class="cx"> # Remove two members, and add one member
</span><span class="cx"> newSet = set()
</span><span class="lines">@@ -156,9 +165,12 @@
</span><span class="cx"> uid = u"__top_group_1__"
</span><span class="cx"> hash = "553eb54e3bbb26582198ee04541dbee4"
</span><span class="cx"> yield self.groupCacher.refreshGroup(txn, uid)
</span><del>- groupID, _ignore_name, _ignore_membershipHash, _ignore_modified = yield txn.groupByUID(uid)
- results = (yield txn.groupByID(groupID))
- self.assertEquals((uid, u"Top Group 1", hash), results)
</del><ins>+ (
+ groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
+ extant
+ ) = yield txn.groupByUID(uid)
+ results = yield txn.groupByID(groupID)
+ self.assertEquals((uid, u"Top Group 1", hash, True), results)
</ins><span class="cx">
</span><span class="cx"> yield txn.commit()
</span><span class="cx">
</span><span class="lines">@@ -414,3 +426,125 @@
</span><span class="cx"> {"D": ("7", "8"), "C": ("4", "5"), "A": ("1", "2")},
</span><span class="cx"> )
</span><span class="cx"> )
</span><ins>+
+
+class DynamicGroupTest(StoreTestCase):
+
+
+ @inlineCallbacks
+ def setUp(self):
+ yield super(DynamicGroupTest, self).setUp()
+
+ self.directory = CalendarInMemoryDirectoryService(None)
+ self.store.setDirectoryService(self.directory)
+ self.groupCacher = GroupCacher(self.directory)
+
+ self.numUsers = 100
+
+ # Add users
+ records = []
+ fieldName = self.directory.fieldName
+ for i in xrange(self.numUsers):
+ records.append(
+ TestRecord(
+ self.directory,
+ {
+ fieldName.uid: u"foo{ctr:05d}".format(ctr=i),
+ fieldName.shortNames: (u"foo{ctr:05d}".format(ctr=i),),
+ fieldName.fullNames: (u"foo{ctr:05d}".format(ctr=i),),
+ fieldName.recordType: RecordType.user,
+ }
+ )
+ )
+
+ # Add a group
+ records.append(
+ TestRecord(
+ self.directory,
+ {
+ fieldName.uid: u"testgroup",
+ fieldName.recordType: RecordType.group,
+ }
+ )
+ )
+
+ yield self.directory.updateRecords(records, create=True)
+
+ group = yield self.directory.recordWithUID(u"testgroup")
+ members = yield self.directory.recordsWithRecordType(RecordType.user)
+ yield group.setMembers(members)
+
+
+ @inlineCallbacks
+ def test_extant(self):
+ """
+ Verify that once a group is removed from the directory, the next call
+ to refreshGroup() will set the "extent" to False. Add the group back
+ to the directory and "extent" becomes True.
+ """
+ store = self.storeUnderTest()
+
+ txn = store.newTransaction()
+ yield self.groupCacher.refreshGroup(txn, u"testgroup")
+ (
+ groupID, _ignore_name, membershipHash, _ignore_modified,
+ extant
+ ) = (yield txn.groupByUID(u"testgroup"))
+ yield txn.commit()
+
+ self.assertTrue(extant)
+
+ # Remove the group
+ yield self.directory.removeRecords([u"testgroup"])
+
+ txn = store.newTransaction()
+ yield self.groupCacher.refreshGroup(txn, u"testgroup")
+ (
+ groupID, _ignore_name, membershipHash, _ignore_modified,
+ extant
+ ) = (yield txn.groupByUID(u"testgroup"))
+ yield txn.commit()
+
+ # Extant = False
+ self.assertFalse(extant)
+
+ # The list of members stored in the DB for this group is now empty
+ txn = store.newTransaction()
+ members = yield txn.membersOfGroup(groupID)
+ yield txn.commit()
+ self.assertEquals(members, set())
+
+ # Add the group back into the directory
+ fieldName = self.directory.fieldName
+ yield self.directory.updateRecords(
+ (
+ TestRecord(
+ self.directory,
+ {
+ fieldName.uid: u"testgroup",
+ fieldName.recordType: RecordType.group,
+ }
+ ),
+ ),
+ create=True
+ )
+ group = yield self.directory.recordWithUID(u"testgroup")
+ members = yield self.directory.recordsWithRecordType(RecordType.user)
+ yield group.setMembers(members)
+
+ txn = store.newTransaction()
+ yield self.groupCacher.refreshGroup(txn, u"testgroup")
+ (
+ groupID, _ignore_name, membershipHash, _ignore_modified,
+ extant
+ ) = (yield txn.groupByUID(u"testgroup"))
+ yield txn.commit()
+
+ # Extant = True
+ self.assertTrue(extant)
+
+ # The list of members stored in the DB for this group has 100 users
+ txn = store.newTransaction()
+ members = yield txn.membersOfGroup(groupID)
+ yield txn.commit()
+ self.assertEquals(len(members), 100)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavwhoutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/who/util.py (13682 => 13683)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/who/util.py        2014-06-25 02:24:15 UTC (rev 13682)
+++ CalendarServer/trunk/txdav/who/util.py        2014-06-25 02:26:01 UTC (rev 13683)
</span><span class="lines">@@ -172,8 +172,8 @@
</span><span class="cx"> )
</span><span class="cx">
</span><span class="cx"> elif "inmemory" in directoryType:
</span><del>- from txdav.who.test.support import InMemoryDirectoryService
- directory = InMemoryDirectoryService()
</del><ins>+ from txdav.who.test.support import CalendarInMemoryDirectoryService
+ directory = CalendarInMemoryDirectoryService()
</ins><span class="cx">
</span><span class="cx"> else:
</span><span class="cx"> log.error("Invalid DirectoryType: {dt}", dt=directoryType)
</span></span></pre>
</div>
</div>
</body>
</html>