<!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>[5482] 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.macosforge.org/projects/calendarserver/changeset/5482">5482</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2010-04-15 14:03:20 -0700 (Thu, 15 Apr 2010)</dd>
</dl>

<h3>Log Message</h3>
<pre>CardDAV global addressbook support. Some addition CardDAV specific clean-up.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunkcalendarservertaptesttest_caldavpy">CalendarServer/trunk/calendarserver/tap/test/test_caldav.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertaputilpy">CalendarServer/trunk/calendarserver/tap/util.py</a></li>
<li><a href="#CalendarServertrunktwextweb2responsecodepy">CalendarServer/trunk/twext/web2/responsecode.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavcarddavxmlpy">CalendarServer/trunk/twistedcaldav/carddavxml.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavcustomxmlpy">CalendarServer/trunk/twistedcaldav/customxml.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavdirectoryaddressbookpy">CalendarServer/trunk/twistedcaldav/directory/addressbook.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavdirectorycalendarpy">CalendarServer/trunk/twistedcaldav/directory/calendar.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavresourcepy">CalendarServer/trunk/twistedcaldav/resource.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavstaticpy">CalendarServer/trunk/twistedcaldav/static.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavstdconfigpy">CalendarServer/trunk/twistedcaldav/stdconfig.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavtesttest_sharingpy">CalendarServer/trunk/twistedcaldav/test/test_sharing.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#CalendarServertrunktwistedcaldavlinkresourcepy">CalendarServer/trunk/twistedcaldav/linkresource.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunkcalendarservertaptesttest_caldavpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tap/test/test_caldav.py (5481 => 5482)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tap/test/test_caldav.py        2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/calendarserver/tap/test/test_caldav.py        2010-04-15 21:03:20 UTC (rev 5482)
</span><span class="lines">@@ -344,7 +344,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class SocketGroupOwnership(BaseTestCase):
</del><ins>+class SocketGroupOwnership(TestCase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Tests for L{GroupOwnedUNIXServer}.
</span><span class="cx">     &quot;&quot;&quot;
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertaputilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tap/util.py (5481 => 5482)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tap/util.py        2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/calendarserver/tap/util.py        2010-04-15 21:03:20 UTC (rev 5482)
</span><span class="lines">@@ -25,7 +25,7 @@
</span><span class="cx"> from time import sleep
</span><span class="cx"> 
</span><span class="cx"> from twisted.python.reflect import namedClass
</span><del>-from twisted.internet import reactor
</del><ins>+from twisted.internet.reactor import addSystemEventTrigger
</ins><span class="cx"> from twisted.cred.portal import Portal
</span><span class="cx"> from twext.web2.http_headers import Headers
</span><span class="cx"> from twext.web2.dav import auth
</span><span class="lines">@@ -47,7 +47,8 @@
</span><span class="cx"> from twistedcaldav.notify import installNotificationClient
</span><span class="cx"> from twistedcaldav.resource import CalDAVResource, AuthenticationWrapper
</span><span class="cx"> from twistedcaldav.simpleresource import SimpleResource
</span><del>-from twistedcaldav.static import CalendarHomeProvisioningFile
</del><ins>+from twistedcaldav.static import CalendarHomeProvisioningFile,\
+    GlobalAddressBookFile
</ins><span class="cx"> from twistedcaldav.static import IScheduleInboxFile
</span><span class="cx"> from twistedcaldav.static import TimezoneServiceFile
</span><span class="cx"> from twistedcaldav.static import AddressBookHomeProvisioningFile, DirectoryBackedAddressBookFile
</span><span class="lines">@@ -91,6 +92,7 @@
</span><span class="cx">     webAdminResourceClass        = WebAdminResource
</span><span class="cx">     addressBookResourceClass     = AddressBookHomeProvisioningFile
</span><span class="cx">     directoryBackedAddressBookResourceClass = DirectoryBackedAddressBookFile
</span><ins>+    globalAddressBookResourceClass          = GlobalAddressBookFile
</ins><span class="cx"> 
</span><span class="cx">     #
</span><span class="cx">     # Setup the Directory
</span><span class="lines">@@ -290,10 +292,7 @@
</span><span class="cx">                 directoryPath,
</span><span class="cx">                 principalCollections=(principalCollection,)
</span><span class="cx">             )
</span><del>-            # do this after process is owned by carddav user, not root.  XXX
-            # this should be fixed to execute at a different stage of service
-            # startup entirely.
-            reactor.callLater(1.0, directoryBackedAddressBookCollection.provisionDirectory)
</del><ins>+            addSystemEventTrigger(&quot;after&quot;, &quot;startup&quot;, directoryBackedAddressBookCollection.provisionDirectory)
</ins><span class="cx">         else:
</span><span class="cx">             # remove /directory from previous runs that may have created it
</span><span class="cx">             try:
</span><span class="lines">@@ -303,6 +302,16 @@
</span><span class="cx">                 if e.errno != errno.ENOENT:
</span><span class="cx">                     log.error(&quot;Could not delete: %s : %r&quot; %  (directoryPath, e,))
</span><span class="cx"> 
</span><ins>+        if config.GlobalAddressBook.Enabled:
+            log.info(&quot;Setting up global address book collection: %r&quot; % (globalAddressBookResourceClass,))
+
+            globalAddressBookCollection = globalAddressBookResourceClass(
+                os.path.join(config.DocumentRoot, config.GlobalAddressBook.Name),
+                principalCollections=(principalCollection,)
+            )
+            if not globalAddressBookCollection.exists():
+                addSystemEventTrigger(&quot;after&quot;, &quot;startup&quot;, globalAddressBookCollection.createAddressBookCollection)
+
</ins><span class="cx">     log.info(&quot;Setting up root resource: %r&quot; % (rootResourceClass,))
</span><span class="cx"> 
</span><span class="cx">     root = rootResourceClass(
</span><span class="lines">@@ -322,6 +331,8 @@
</span><span class="cx">         root.putChild('addressbooks', addressBookCollection)
</span><span class="cx">         if config.DirectoryAddressBook.Enabled:
</span><span class="cx">             root.putChild(config.DirectoryAddressBook.name, directoryBackedAddressBookCollection)
</span><ins>+        if config.GlobalAddressBook.Enabled:
+            root.putChild(config.GlobalAddressBook.Name, globalAddressBookCollection)            
</ins><span class="cx"> 
</span><span class="cx">     # /.well-known
</span><span class="cx">     if config.EnableWellKnown:
</span></span></pre></div>
<a id="CalendarServertrunktwextweb2responsecodepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twext/web2/responsecode.py (5481 => 5482)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twext/web2/responsecode.py        2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twext/web2/responsecode.py        2010-04-15 21:03:20 UTC (rev 5482)
</span><span class="lines">@@ -71,6 +71,7 @@
</span><span class="cx"> SERVICE_UNAVAILABLE             = 503
</span><span class="cx"> GATEWAY_TIMEOUT                 = 504
</span><span class="cx"> HTTP_VERSION_NOT_SUPPORTED      = 505
</span><ins>+LOOP_DETECTED                   = 506
</ins><span class="cx"> INSUFFICIENT_STORAGE_SPACE      = 507
</span><span class="cx"> NOT_EXTENDED                    = 510
</span><span class="cx"> 
</span><span class="lines">@@ -116,7 +117,7 @@
</span><span class="cx">     REQUEST_ENTITY_TOO_LARGE: &quot;Request Entity Too Large&quot;,
</span><span class="cx">     REQUEST_URI_TOO_LONG: &quot;Request-URI Too Long&quot;,
</span><span class="cx">     UNSUPPORTED_MEDIA_TYPE: &quot;Unsupported Media Type&quot;,
</span><del>-    REQUESTED_RANGE_NOT_SATISFIABLE: &quot;Requested Range not satisfiable&quot;,
</del><ins>+    REQUESTED_RANGE_NOT_SATISFIABLE: &quot;Requested Range Not Satisfiable&quot;,
</ins><span class="cx">     EXPECTATION_FAILED: &quot;Expectation Failed&quot;,
</span><span class="cx">     UNPROCESSABLE_ENTITY: &quot;Unprocessable Entity&quot;,
</span><span class="cx">     LOCKED: &quot;Locked&quot;,
</span><span class="lines">@@ -128,7 +129,8 @@
</span><span class="cx">     BAD_GATEWAY: &quot;Bad Gateway&quot;,
</span><span class="cx">     SERVICE_UNAVAILABLE: &quot;Service Unavailable&quot;,
</span><span class="cx">     GATEWAY_TIMEOUT: &quot;Gateway Time-out&quot;,
</span><del>-    HTTP_VERSION_NOT_SUPPORTED: &quot;HTTP Version not supported&quot;,
</del><ins>+    HTTP_VERSION_NOT_SUPPORTED: &quot;HTTP Version Not Supported&quot;,
+    LOOP_DETECTED: &quot;Loop In Linked or Bound Resource&quot;,
</ins><span class="cx">     INSUFFICIENT_STORAGE_SPACE: &quot;Insufficient Storage Space&quot;,
</span><span class="cx">     NOT_EXTENDED: &quot;Not Extended&quot;
</span><span class="cx">     }
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavcarddavxmlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/carddavxml.py (5481 => 5482)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/carddavxml.py        2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/carddavxml.py        2010-04-15 21:03:20 UTC (rev 5482)
</span><span class="lines">@@ -151,7 +151,7 @@
</span><span class="cx">     hidden = True
</span><span class="cx">     protected = True
</span><span class="cx"> 
</span><del>-    allowed_children = { (carddav_namespace, &quot;addressbook-data&quot;): (0, None) }
</del><ins>+    allowed_children = { (carddav_namespace, &quot;address-data-type&quot;): (0, None) }
</ins><span class="cx"> 
</span><span class="cx"> class MaxResourceSize (CardDAVTextElement):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -221,6 +221,19 @@
</span><span class="cx">         self.filter = filter
</span><span class="cx">         self.limit = limit
</span><span class="cx"> 
</span><ins>+class AddressDataType (CardDAVEmptyElement):
+    &quot;&quot;&quot;
+    Defines which parts of a address component object should be returned by a
+    report.
+    (CardDAV, section 6.2.2)
+    &quot;&quot;&quot;
+    name = &quot;address-data-type&quot;
+
+    allowed_attributes = {
+        &quot;content-type&quot;: False,
+        &quot;version&quot;     : False,
+    }
+
</ins><span class="cx"> class AddressData (CardDAVElement):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Defines which parts of a address component object should be returned by a
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavcustomxmlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/customxml.py (5481 => 5482)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/customxml.py        2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/customxml.py        2010-04-15 21:03:20 UTC (rev 5482)
</span><span class="lines">@@ -28,7 +28,7 @@
</span><span class="cx"> from twext.web2.dav.element.base import twisted_private_namespace
</span><span class="cx"> from twext.web2.dav import davxml
</span><span class="cx"> 
</span><del>-from twistedcaldav import caldavxml
</del><ins>+from twistedcaldav import caldavxml, carddavxml
</ins><span class="cx"> from twistedcaldav.ical import Component as iComponent
</span><span class="cx"> 
</span><span class="cx"> from vobject.icalendar import utc
</span><span class="lines">@@ -891,4 +891,6 @@
</span><span class="cx"> davxml.ResourceType.ischeduleinbox = davxml.ResourceType(IScheduleInbox())
</span><span class="cx"> davxml.ResourceType.freebusyurl = davxml.ResourceType(FreeBusyURL())
</span><span class="cx"> davxml.ResourceType.notification = davxml.ResourceType(davxml.Collection(), Notification())
</span><del>-davxml.ResourceType.sharedcalendar = davxml.ResourceType(davxml.Collection(), caldavxml.Calendar(), SharedOwner())
</del><ins>+davxml.ResourceType.sharedownercalendar = davxml.ResourceType(davxml.Collection(), caldavxml.Calendar(), SharedOwner())
+davxml.ResourceType.sharedcalendar = davxml.ResourceType(davxml.Collection(), caldavxml.Calendar(), Shared())
+davxml.ResourceType.sharedaddressbook = davxml.ResourceType(davxml.Collection(), carddavxml.AddressBook(), Shared())
</ins></span></pre></div>
<a id="CalendarServertrunktwistedcaldavdirectoryaddressbookpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/directory/addressbook.py (5481 => 5482)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/directory/addressbook.py        2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/directory/addressbook.py        2010-04-15 21:03:20 UTC (rev 5482)
</span><span class="lines">@@ -25,6 +25,7 @@
</span><span class="cx">     &quot;DirectoryAddressBookHomeTypeProvisioningResource&quot;,
</span><span class="cx">     &quot;DirectoryAddressBookHomeUIDProvisioningResource&quot;,
</span><span class="cx">     &quot;DirectoryAddressBookHomeResource&quot;,
</span><ins>+    &quot;GlobalAddressBookResource&quot;,
</ins><span class="cx"> ]
</span><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="lines">@@ -377,3 +378,49 @@
</span><span class="cx">             is quota-controlled, or C{None} if not quota controlled.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return config.UserQuota if config.UserQuota != 0 else None
</span><ins>+
+class GlobalAddressBookResource (CalDAVResource):
+    &quot;&quot;&quot;
+    Global address book. All we care about is making sure permissions are setup.
+    &quot;&quot;&quot;
+
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.sharedaddressbook)
+
+    def url(self):
+        return joinURL(&quot;/&quot;, config.GlobalAddressBook.Name, &quot;/&quot;)
+
+    def canonicalURL(self, request):
+        return succeed(self.url())
+
+    def defaultAccessControlList(self):
+
+        aces = (
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(
+                    davxml.Privilege(davxml.Read()),
+                    davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+                    davxml.Privilege(davxml.Write()),
+                ),
+                davxml.Protected(),
+                TwistedACLInheritable(),
+           ),
+        )
+        
+        if config.GlobalAddressBook.EnableAnonymousReadAccess:
+            aces += (
+                davxml.ACE(
+                    davxml.Principal(davxml.Unauthenticated()),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                    ),
+                    davxml.Protected(),
+                    TwistedACLInheritable(),
+               ),
+            )
+        return davxml.ACL(*aces)
+
+    def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
+        # Permissions here are fixed, and are not subject to inheritance rules, etc.
+        return succeed(self.defaultAccessControlList())
</ins></span></pre></div>
<a id="CalendarServertrunktwistedcaldavdirectorycalendarpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/directory/calendar.py (5481 => 5482)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/directory/calendar.py        2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/directory/calendar.py        2010-04-15 21:03:20 UTC (rev 5482)
</span><span class="lines">@@ -41,7 +41,7 @@
</span><span class="cx"> from twistedcaldav.dropbox import DropBoxHomeResource
</span><span class="cx"> from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource
</span><span class="cx"> from twistedcaldav.freebusyurl import FreeBusyURLResource
</span><del>-from twistedcaldav.resource import CalDAVResource
</del><ins>+from twistedcaldav.resource import CalDAVResource, CalDAVComplianceMixIn
</ins><span class="cx"> from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource
</span><span class="cx"> from twistedcaldav.directory.idirectory import IDirectoryService
</span><span class="cx"> from twistedcaldav.directory.wiki import getWikiACL
</span><span class="lines">@@ -57,6 +57,7 @@
</span><span class="cx"> class DirectoryCalendarProvisioningResource (
</span><span class="cx">     AutoProvisioningResourceMixIn,
</span><span class="cx">     ReadOnlyResourceMixIn,
</span><ins>+    CalDAVComplianceMixIn,
</ins><span class="cx">     DAVResource,
</span><span class="cx"> ):
</span><span class="cx">     def defaultAccessControlList(self):
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavlinkresourcepy"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/twistedcaldav/linkresource.py (0 => 5482)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/linkresource.py                                (rev 0)
+++ CalendarServer/trunk/twistedcaldav/linkresource.py        2010-04-15 21:03:20 UTC (rev 5482)
</span><span class="lines">@@ -0,0 +1,114 @@
</span><ins>+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twext.python.log import LoggingMixIn
+
+from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
+
+from twistedcaldav.resource import CalDAVComplianceMixIn
+from twext.web2.http import HTTPError
+from twext.web2 import responsecode
+from twext.web2.resource import WrapperResource
+
+__all__ = [
+    &quot;LinkResource&quot;,
+]
+
+&quot;&quot;&quot;
+A resource that is a soft-link to another.
+&quot;&quot;&quot;
+
+class LinkResource(CalDAVComplianceMixIn, WrapperResource, LoggingMixIn):
+    &quot;&quot;&quot;
+    This is similar to a WrapperResource except that we locate our resource dynamically. 
+    &quot;&quot;&quot;
+    
+    def __init__(self, parent, link_url):
+        self.parent = parent
+        self.linkURL = link_url
+        super(LinkResource, self).__init__(self.parent.principalCollections())
+
+    @inlineCallbacks
+    def linkedResource(self, request):
+        
+        if not hasattr(self, &quot;_linkedResource&quot;):
+            self._linkedResource = (yield request.locateResource(self.linkURL))
+
+        if self._linkedResource is None:
+            raise HTTPError(responsecode.NOT_FOUND)
+            
+        returnValue(self._linkedResource)
+
+    def isCollection(self):
+        return True
+
+    @inlineCallbacks
+    def resourceType(self, request):
+        hosted = (yield self.linkedResource(request))
+        result = (yield hosted.resourceType(request))
+        returnValue(result)
+        
+    def locateChild(self, request, segments):
+        
+        def _defer(result):
+            return (result, segments)
+        d = self.linkedResource(request)
+        d.addCallback(_defer)
+        return d
+
+    def renderHTTP(self, request):
+        return self.linkedResource(request)
+
+    def getChild(self, name):
+        return self._hostedResource.getChild(name)
+
+    @inlineCallbacks
+    def hasProperty(self, property, request):
+        hosted = (yield self.linkedResource(request))
+        result = (yield hosted.hasProperty(property, request))
+        returnValue(result)
+
+    @inlineCallbacks
+    def readProperty(self, property, request):
+        hosted = (yield self.linkedResource(request))
+        result = (yield hosted.readProperty(property, request))
+        returnValue(result)
+
+    @inlineCallbacks
+    def writeProperty(self, property, request):
+        hosted = (yield self.linkedResource(request))
+        result = (yield hosted.writeProperty(property, request))
+        returnValue(result)
+
+class LinkFollowerMixIn(object):
+
+    @inlineCallbacks
+    def locateChild(self, req, segments):
+
+        resource, path = (yield maybeDeferred(super(LinkFollowerMixIn, self).locateChild, req, segments))
+        MAX_LINK_DEPTH = 10
+        ctr = 0
+        seenResource = set()
+        while isinstance(resource, LinkResource):
+            seenResource.add(resource)
+            ctr += 1
+            resource = (yield resource.linkedResource(req))
+            
+            if ctr &gt; MAX_LINK_DEPTH or resource in seenResource:
+                raise HTTPError(responsecode.LOOP_DETECTED)
+        
+        returnValue((resource, path))
+        
</ins></span></pre></div>
<a id="CalendarServertrunktwistedcaldavresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/resource.py (5481 => 5482)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/resource.py        2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/resource.py        2010-04-15 21:03:20 UTC (rev 5482)
</span><span class="lines">@@ -160,10 +160,22 @@
</span><span class="cx">     def liveProperties(self):
</span><span class="cx">         baseProperties = (
</span><span class="cx">             davxml.Owner.qname(),               # Private Events needs this but it is also OK to return empty
</span><del>-            caldavxml.SupportedCalendarComponentSet.qname(),
-            caldavxml.SupportedCalendarData.qname(),
</del><span class="cx">         )
</span><span class="cx">         
</span><ins>+        if self.isPseudoCalendarCollection():
+            baseProperties += (
+                caldavxml.SupportedCalendarComponentSet.qname(),
+                caldavxml.SupportedCalendarData.qname(),
+            )
+
+        if self.isAddressBookCollection():
+            baseProperties += (
+                carddavxml.SupportedAddressData.qname(),
+            )
+
+        if config.EnableSyncReport and (self.isPseudoCalendarCollection() or self.isAddressBookCollection()):
+            baseProperties += (davxml.SyncToken.qname(),)
+            
</ins><span class="cx">         if config.EnableAddMember and (self.isCalendarCollection() or self.isAddressBookCollection()):
</span><span class="cx">             baseProperties += (davxml.AddMember.qname(),)
</span><span class="cx">             
</span><span class="lines">@@ -285,6 +297,11 @@
</span><span class="cx">             owner = (yield self.owner(request))
</span><span class="cx">             returnValue(davxml.Owner(owner))
</span><span class="cx"> 
</span><ins>+        elif qname == davxml.SyncToken.qname() and config.EnableSyncReport and (
+            self.isPseudoCalendarCollection() or self.isAddressBookCollection()
+        ):
+            returnValue(davxml.SyncToken.fromString(self.getSyncToken()))
+
</ins><span class="cx">         elif qname == davxml.AddMember.qname() and config.EnableAddMember and (
</span><span class="cx">             self.isCalendarCollection() or self.isAddressBookCollection()
</span><span class="cx">         ):
</span><span class="lines">@@ -331,6 +348,15 @@
</span><span class="cx">                 opaque = url in fbset
</span><span class="cx">                 self.writeDeadProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Opaque() if opaque else caldavxml.Transparent()))
</span><span class="cx"> 
</span><ins>+        elif qname == carddavxml.SupportedAddressData.qname():
+            # CardDAV, section 6.2.2
+            returnValue(carddavxml.SupportedAddressData(
+                carddavxml.AddressDataType(**{
+                    &quot;content-type&quot;: &quot;text/vcard&quot;,
+                    &quot;version&quot;     : &quot;3.0&quot;,
+                }),
+            ))
+
</ins><span class="cx">         elif qname == customxml.Invite.qname():
</span><span class="cx">             result = (yield self.inviteProperty(request))
</span><span class="cx">             returnValue(result)
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavstaticpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/static.py (5481 => 5482)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/static.py        2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/static.py        2010-04-15 21:03:20 UTC (rev 5482)
</span><span class="lines">@@ -38,6 +38,7 @@
</span><span class="cx">     &quot;AddressBookHomeUIDProvisioningFile&quot;,
</span><span class="cx">     &quot;AddressBookHomeFile&quot;,
</span><span class="cx">     &quot;DirectoryBackedAddressBookFile&quot;,
</span><ins>+    &quot;GLobalAddressBookFile&quot;,
</ins><span class="cx"> ]
</span><span class="cx"> 
</span><span class="cx"> import datetime
</span><span class="lines">@@ -73,6 +74,7 @@
</span><span class="cx"> from twistedcaldav.customxml import TwistedCalendarAccessProperty, TwistedScheduleMatchETags
</span><span class="cx"> from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
</span><span class="cx"> from twistedcaldav.extensions import DAVFile, CachingPropertyStore
</span><ins>+from twistedcaldav.linkresource import LinkResource, LinkFollowerMixIn
</ins><span class="cx"> from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
</span><span class="cx"> from twistedcaldav.memcacheprops import MemcachePropertyCollection
</span><span class="cx"> from twistedcaldav.freebusyurl import FreeBusyURLResource
</span><span class="lines">@@ -85,7 +87,8 @@
</span><span class="cx"> from twistedcaldav.datafilters.privateevents import PrivateEventFilter
</span><span class="cx"> from twistedcaldav.dropbox import DropBoxHomeResource, DropBoxCollectionResource
</span><span class="cx"> from twistedcaldav.directorybackedaddressbook import DirectoryBackedAddressBookResource
</span><del>-from twistedcaldav.directory.addressbook import uidsResourceName as uidsResourceNameAddressBook
</del><ins>+from twistedcaldav.directory.addressbook import uidsResourceName as uidsResourceNameAddressBook,\
+    GlobalAddressBookResource
</ins><span class="cx"> from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeProvisioningResource
</span><span class="cx"> from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeTypeProvisioningResource
</span><span class="cx"> from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeUIDProvisioningResource
</span><span class="lines">@@ -107,7 +110,21 @@
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><del>-class CalDAVFile (CalDAVResource, DAVFile):
</del><ins>+class ReadOnlyResourceMixIn(object):
+
+    def http_PUT        (self, request): return responsecode.FORBIDDEN
+    def http_COPY       (self, request): return responsecode.FORBIDDEN
+    def http_MOVE       (self, request): return responsecode.FORBIDDEN
+    def http_DELETE     (self, request): return responsecode.FORBIDDEN
+    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
+
+    def http_MKCALENDAR(self, request):
+        return ErrorResponse(
+            responsecode.FORBIDDEN,
+            (caldav_namespace, &quot;calendar-collection-location-ok&quot;)
+        )
+
+class CalDAVFile (LinkFollowerMixIn, CalDAVResource, DAVFile):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     CalDAV-accessible L{DAVFile} resource.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -555,6 +572,14 @@
</span><span class="cx">         except:
</span><span class="cx">             return fail(Failure())
</span><span class="cx"> 
</span><ins>+    def getSyncToken(self):
+        &quot;&quot;&quot;
+        Return current sync-token value.
+        &quot;&quot;&quot;
+        assert self.isCollection()
+        
+        return str(self.readDeadProperty(customxml.GETCTag))
+
</ins><span class="cx">     def updateCTag(self, token=None):
</span><span class="cx">         assert self.isCollection()
</span><span class="cx">         
</span><span class="lines">@@ -738,7 +763,7 @@
</span><span class="cx">             if test(parent):
</span><span class="cx">                 returnValue(parent)
</span><span class="cx"> 
</span><del>-class AutoProvisioningFileMixIn (AutoProvisioningResourceMixIn):
</del><ins>+class AutoProvisioningFileMixIn (LinkFollowerMixIn, AutoProvisioningResourceMixIn):
</ins><span class="cx">     def provision(self):
</span><span class="cx">         self.provisionFile()
</span><span class="cx">         return super(AutoProvisioningFileMixIn, self).provision()
</span><span class="lines">@@ -1050,7 +1075,7 @@
</span><span class="cx">         return super(CalendarHomeFile, self).readProperty(property, request)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class ScheduleFile (AutoProvisioningFileMixIn, CalDAVFile):
</del><ins>+class ScheduleFile (ReadOnlyResourceMixIn, AutoProvisioningFileMixIn, CalDAVFile):
</ins><span class="cx">     def __init__(self, path, parent):
</span><span class="cx">         super(ScheduleFile, self).__init__(path, principalCollections=parent.principalCollections())
</span><span class="cx"> 
</span><span class="lines">@@ -1072,17 +1097,6 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return IndexSchedule(self)
</span><span class="cx"> 
</span><del>-    def http_COPY       (self, request): return responsecode.FORBIDDEN
-    def http_MOVE       (self, request): return responsecode.FORBIDDEN
-    def http_DELETE     (self, request): return responsecode.FORBIDDEN
-    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
-
-    def http_MKCALENDAR(self, request):
-        return ErrorResponse(
-            responsecode.FORBIDDEN,
-            (caldav_namespace, &quot;calendar-collection-location-ok&quot;)
-        )
-
</del><span class="cx"> class ScheduleInboxFile (ScheduleInboxResource, ScheduleFile):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Calendar scheduling inbox collection resource.
</span><span class="lines">@@ -1136,7 +1150,7 @@
</span><span class="cx">     def supportedPrivileges(self, request):
</span><span class="cx">         return succeed(sendSchedulePrivilegeSet)
</span><span class="cx"> 
</span><del>-class IScheduleInboxFile (IScheduleInboxResource, CalDAVFile):
</del><ins>+class IScheduleInboxFile (ReadOnlyResourceMixIn, IScheduleInboxResource, CalDAVFile):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Server-to-server scheduling inbox resource.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -1156,18 +1170,6 @@
</span><span class="cx">         else:
</span><span class="cx">             return responsecode.NOT_FOUND
</span><span class="cx"> 
</span><del>-    def http_PUT        (self, request): return responsecode.FORBIDDEN
-    def http_COPY       (self, request): return responsecode.FORBIDDEN
-    def http_MOVE       (self, request): return responsecode.FORBIDDEN
-    def http_DELETE     (self, request): return responsecode.FORBIDDEN
-    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
-
-    def http_MKCALENDAR(self, request):
-        return ErrorResponse(
-            responsecode.FORBIDDEN,
-            (caldav_namespace, &quot;calendar-collection-location-ok&quot;)
-        )
-
</del><span class="cx">     def deadProperties(self):
</span><span class="cx">         if not hasattr(self, &quot;_dead_properties&quot;):
</span><span class="cx">             self._dead_properties = NonePropertyStore(self)
</span><span class="lines">@@ -1188,7 +1190,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class FreeBusyURLFile (AutoProvisioningFileMixIn, FreeBusyURLResource, CalDAVFile):
</del><ins>+class FreeBusyURLFile (ReadOnlyResourceMixIn, AutoProvisioningFileMixIn, FreeBusyURLResource, CalDAVFile):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Free-busy URL resource.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -1208,18 +1210,6 @@
</span><span class="cx">         else:
</span><span class="cx">             return responsecode.NOT_FOUND
</span><span class="cx"> 
</span><del>-    def http_PUT        (self, request): return responsecode.FORBIDDEN
-    def http_COPY       (self, request): return responsecode.FORBIDDEN
-    def http_MOVE       (self, request): return responsecode.FORBIDDEN
-    def http_DELETE     (self, request): return responsecode.FORBIDDEN
-    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
-
-    def http_MKCALENDAR(self, request):
-        return ErrorResponse(
-            responsecode.FORBIDDEN,
-            (caldav_namespace, &quot;calendar-collection-location-ok&quot;)
-        )
-
</del><span class="cx">     ##
</span><span class="cx">     # ACL
</span><span class="cx">     ##
</span><span class="lines">@@ -1268,7 +1258,7 @@
</span><span class="cx">         else:
</span><span class="cx">             return responsecode.NOT_FOUND
</span><span class="cx"> 
</span><del>-class TimezoneServiceFile (TimezoneServiceResource, CalDAVFile):
</del><ins>+class TimezoneServiceFile (ReadOnlyResourceMixIn, TimezoneServiceResource, CalDAVFile):
</ins><span class="cx">     def __init__(self, path, parent):
</span><span class="cx">         CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
</span><span class="cx">         TimezoneServiceResource.__init__(self, parent)
</span><span class="lines">@@ -1281,18 +1271,6 @@
</span><span class="cx">         else:
</span><span class="cx">             return responsecode.NOT_FOUND
</span><span class="cx"> 
</span><del>-    def http_PUT        (self, request): return responsecode.FORBIDDEN
-    def http_COPY       (self, request): return responsecode.FORBIDDEN
-    def http_MOVE       (self, request): return responsecode.FORBIDDEN
-    def http_DELETE     (self, request): return responsecode.FORBIDDEN
-    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
-
-    def http_MKCALENDAR(self, request):
-        return ErrorResponse(
-            responsecode.FORBIDDEN,
-            (caldav_namespace, &quot;calendar-collection-location-ok&quot;)
-        )
-
</del><span class="cx">     def deadProperties(self):
</span><span class="cx">         if not hasattr(self, &quot;_dead_properties&quot;):
</span><span class="cx">             self._dead_properties = NonePropertyStore(self)
</span><span class="lines">@@ -1307,7 +1285,7 @@
</span><span class="cx">     def checkPrivileges(self, request, privileges, recurse=False, principal=None, inherited_aces=None):
</span><span class="cx">         return succeed(None)
</span><span class="cx"> 
</span><del>-class NotificationCollectionFile(AutoProvisioningFileMixIn, NotificationCollectionResource, CalDAVFile):
</del><ins>+class NotificationCollectionFile(ReadOnlyResourceMixIn, AutoProvisioningFileMixIn, NotificationCollectionResource, CalDAVFile):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Notification collection resource.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -1492,6 +1470,21 @@
</span><span class="cx">         CalDAVFile.__init__(self, path)
</span><span class="cx">         DirectoryAddressBookHomeResource.__init__(self, parent, record)
</span><span class="cx"> 
</span><ins>+    def provision(self):
+        result = super(AddressBookHomeFile, self).provision()
+        self.provisionLinks()
+        return result
+
+    def provisionLinks(self):
+        
+        if not hasattr(self, &quot;_provisionedLinks&quot;):
+            if config.GlobalAddressBook.Enabled:
+                self.putChild(
+                    config.GlobalAddressBook.Name,
+                    LinkResource(self, joinURL(&quot;/&quot;, config.GlobalAddressBook.Name, &quot;/&quot;)),
+                )
+            self._provisionedLinks = True
+
</ins><span class="cx">     def provisionChild(self, name):
</span><span class="cx">  
</span><span class="cx">         if config.Sharing.Enabled:
</span><span class="lines">@@ -1578,7 +1571,7 @@
</span><span class="cx">         return super(AddressBookHomeFile, self).readProperty(property, request)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class DirectoryBackedAddressBookFile (DirectoryBackedAddressBookResource, CalDAVFile):
</del><ins>+class DirectoryBackedAddressBookFile (ReadOnlyResourceMixIn, DirectoryBackedAddressBookResource, CalDAVFile):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Directory-backed address book, supporting directory vcard search.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -1617,6 +1610,22 @@
</span><span class="cx">             ).getChild(name)
</span><span class="cx">        
</span><span class="cx">  
</span><ins>+class GlobalAddressBookFile (ReadOnlyResourceMixIn, GlobalAddressBookResource, CalDAVFile):
+    &quot;&quot;&quot;
+    Directory-backed address book, supporting directory vcard search.
+    &quot;&quot;&quot;
+    def __init__(self, path, principalCollections):
+        CalDAVFile.__init__(self, path, principalCollections=principalCollections)
+        self.clientNotifier = ClientNotifier(self)
+
+    def createSimilarFile(self, path):
+        if self.comparePath(path):
+            return self
+        else:
+            similar = CalDAVFile(path, principalCollections=self.principalCollections())
+            similar.clientNotifier = self.clientNotifier
+            return similar
+
</ins><span class="cx"> ##
</span><span class="cx"> # Utilities
</span><span class="cx"> ##
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavstdconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py (5481 => 5482)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/stdconfig.py        2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py        2010-04-15 21:03:20 UTC (rev 5482)
</span><span class="lines">@@ -349,6 +349,12 @@
</span><span class="cx">     },
</span><span class="cx">     &quot;AnonymousDirectoryAddressBookAccess&quot;: False, # Anonymous users may access directory address book
</span><span class="cx"> 
</span><ins>+    &quot;GlobalAddressBook&quot;: {
+        &quot;Enabled&quot;:                   True,
+        &quot;Name&quot;:                      &quot;global-addressbook&quot;,
+        &quot;EnableAnonymousReadAccess&quot;: False,
+    },
+
</ins><span class="cx">     &quot;MaxAddressBookQueryResults&quot;:1000,
</span><span class="cx">     &quot;MaxAddressBookMultigetHrefs&quot;:5000,
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavtesttest_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/test/test_sharing.py (5481 => 5482)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/test/test_sharing.py        2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/test/test_sharing.py        2010-04-15 21:03:20 UTC (rev 5482)
</span><span class="lines">@@ -102,7 +102,7 @@
</span><span class="cx">         yield self.resource.upgradeToShare(request)
</span><span class="cx"> 
</span><span class="cx">         rtype = (yield self.resource.resourceType(request))
</span><del>-        self.assertEquals(rtype, davxml.ResourceType.sharedcalendar)
</del><ins>+        self.assertEquals(rtype, davxml.ResourceType.sharedownercalendar)
</ins><span class="cx">         propInvite = (yield self.resource.readProperty(customxml.Invite, request))
</span><span class="cx">         self.assertEquals(propInvite, customxml.Invite())
</span><span class="cx">         
</span><span class="lines">@@ -123,7 +123,7 @@
</span><span class="cx">         yield self.resource.upgradeToShare(request)
</span><span class="cx"> 
</span><span class="cx">         rtype = (yield self.resource.resourceType(request))
</span><del>-        self.assertEquals(rtype, davxml.ResourceType.sharedcalendar)
</del><ins>+        self.assertEquals(rtype, davxml.ResourceType.sharedownercalendar)
</ins><span class="cx">         propInvite = (yield self.resource.readProperty(customxml.Invite, request))
</span><span class="cx">         self.assertEquals(propInvite, customxml.Invite())
</span><span class="cx">         
</span><span class="lines">@@ -136,10 +136,10 @@
</span><span class="cx">     def test_downgradeFromShare(self):
</span><span class="cx">         request = SimpleRequest(self.site, &quot;PROPPATCH&quot;, &quot;/calendar/&quot;)
</span><span class="cx"> 
</span><del>-        self.resource.writeDeadProperty(davxml.ResourceType.sharedcalendar)
</del><ins>+        self.resource.writeDeadProperty(davxml.ResourceType.sharedownercalendar)
</ins><span class="cx">         self.resource.writeDeadProperty(customxml.Invite())
</span><span class="cx">         rtype = (yield self.resource.resourceType(request))
</span><del>-        self.assertEquals(rtype, davxml.ResourceType.sharedcalendar)
</del><ins>+        self.assertEquals(rtype, davxml.ResourceType.sharedownercalendar)
</ins><span class="cx">         propInvite = (yield self.resource.readProperty(customxml.Invite, None))
</span><span class="cx">         self.assertEquals(propInvite, customxml.Invite())
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>