[CalendarServer-changes] [5226] CalendarServer/branches/users/cdaboo/shared-calendars-5187

source_changes at macosforge.org source_changes at macosforge.org
Mon Mar 1 13:23:11 PST 2010


Revision: 5226
          http://trac.macosforge.org/projects/calendarserver/changeset/5226
Author:   cdaboo at apple.com
Date:     2010-03-01 13:23:11 -0800 (Mon, 01 Mar 2010)
Log Message:
-----------
Implementation of per-user WebDAV properties. Not hooked up to anything yet.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/auth.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/noneprops.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/test/test_xattrprops.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/xattrprops.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/authkerb.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/customxml.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/directory.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/sudo.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/stdconfig.py

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/auth.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/auth.py	2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/auth.py	2010-03-01 21:23:11 UTC (rev 5226)
@@ -96,7 +96,12 @@
         pswd = str(pcreds.authnPrincipal.readDeadProperty(TwistedPasswordProperty))
 
         d = defer.maybeDeferred(credentials.checkPassword, pswd)
-        d.addCallback(self._cbPasswordMatch, (pcreds.authnPrincipal.principalURL(), pcreds.authzPrincipal.principalURL()))
+        d.addCallback(self._cbPasswordMatch, (
+            pcreds.authnPrincipal.principalURL(),
+            pcreds.authzPrincipal.principalURL(),
+            pcreds.authnPrincipal,
+            pcreds.authzPrincipal,
+        ))
         return d
 
 ##

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/noneprops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/noneprops.py	2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/noneprops.py	2010-03-01 21:23:11 UTC (rev 5226)
@@ -49,19 +49,19 @@
     def __init__(self, resource):
         pass
 
-    def get(self, qname):
+    def get(self, qname, uid=None):
         raise HTTPError(StatusResponse(responsecode.NOT_FOUND, "No such property: {%s}%s" % qname))
 
-    def set(self, property):
+    def set(self, property, uid=None):
         raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Permission denied for setting property: %s" % (property,)))
 
-    def delete(self, qname):
+    def delete(self, qname, uid=None):
         # RFC 2518 Section 12.13.1 says that removal of
         # non-existing property is not an error.
         pass
 
-    def contains(self, qname):
+    def contains(self, qname, uid=None):
         return False
 
-    def list(self):
+    def list(self, uid=None):
         return ()

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/test/test_xattrprops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/test/test_xattrprops.py	2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/test/test_xattrprops.py	2010-03-01 21:23:11 UTC (rev 5226)
@@ -81,40 +81,42 @@
         self._forbiddenTest('get')
 
 
-    def _makeValue(self):
+    def _makeValue(self, uid=None):
         """
         Create and return any old WebDAVDocument for use by the get tests.
         """
-        element = Depth("0")
+        element = Depth(uid if uid is not None else "0")
         document = WebDAVDocument(element)
         return document
 
 
-    def _setValue(self, originalDocument, value):
+    def _setValue(self, originalDocument, value, uid=None):
         element = originalDocument.root_element
         attribute = (
             self.propertyStore.deadPropertyXattrPrefix +
+            (uid if uid is not None else "") +
             "{%s}%s" % element.qname())
         self.attrs[attribute] = value
 
 
-    def _getValue(self, originalDocument):
+    def _getValue(self, originalDocument, uid=None):
         element = originalDocument.root_element
         attribute = (
             self.propertyStore.deadPropertyXattrPrefix +
+            (uid if uid is not None else "") +
             "{%s}%s" % element.qname())
         return self.attrs[attribute]
 
 
-    def _checkValue(self, originalDocument):
+    def _checkValue(self, originalDocument, uid=None):
         property = originalDocument.root_element.qname()
 
         # Try to load it via xattrPropertyStore.get
-        loadedDocument = self.propertyStore.get(property)
+        loadedDocument = self.propertyStore.get(property, uid)
 
         # XXX Why isn't this a WebDAVDocument?
         self.assertIsInstance(loadedDocument, Depth)
-        self.assertEquals(str(loadedDocument), "0")
+        self.assertEquals(str(loadedDocument), uid if uid else "0")
 
 
     def test_getXML(self):
@@ -306,3 +308,99 @@
         # Make sure that the status is FORBIDDEN, a roughly reasonable mapping
         # of the EPERM failure.
         self.assertEquals(error.response.code, FORBIDDEN)
+
+    def test_get_uids(self):
+        """
+        L{xattrPropertyStore.get} accepts a L{WebDAVElement} and stores a
+        compressed XML document representing it in an extended attribute.
+        """
+        
+        for uid in (None, "123", "456",):
+            document = self._makeValue(uid)
+            self._setValue(document, document.toxml(), uid=uid)
+
+        for uid in (None, "123", "456",):
+            document = self._makeValue(uid)
+            self._checkValue(document, uid=uid)
+
+
+    def test_set_uids(self):
+        """
+        L{xattrPropertyStore.set} accepts a L{WebDAVElement} and stores a
+        compressed XML document representing it in an extended attribute.
+        """
+        
+        for uid in (None, "123", "456",):
+            document = self._makeValue(uid)
+            self.propertyStore.set(document.root_element, uid=uid)
+            self.assertEquals(
+                decompress(self._getValue(document, uid)), document.toxml())
+
+    def test_delete_uids(self):
+        """
+        L{xattrPropertyStore.set} accepts a L{WebDAVElement} and stores a
+        compressed XML document representing it in an extended attribute.
+        """
+        
+        for delete_uid in (None, "123", "456",):
+            for uid in (None, "123", "456",):
+                document = self._makeValue(uid)
+                self.propertyStore.set(document.root_element, uid=uid)
+            self.propertyStore.delete(document.root_element.qname(), uid=delete_uid)
+            self.assertRaises(KeyError, self._getValue, document, uid=delete_uid)
+            for uid in (None, "123", "456",):
+                if uid == delete_uid:
+                    continue
+                document = self._makeValue(uid)
+                self.assertEquals(
+                    decompress(self._getValue(document, uid)), document.toxml())
+        
+    def test_contains_uids(self):
+        """
+        L{xattrPropertyStore.contains} returns C{True} if the given property
+        has a value, C{False} otherwise.
+        """
+        for uid in (None, "123", "456",):
+            document = self._makeValue(uid)
+            self.assertFalse(
+                self.propertyStore.contains(document.root_element.qname(), uid=uid))
+            self._setValue(document, document.toxml(), uid=uid)
+            self.assertTrue(
+                self.propertyStore.contains(document.root_element.qname(), uid=uid))
+
+    def test_list_uids(self):
+        """
+        L{xattrPropertyStore.list} returns a C{list} of property names
+        associated with the wrapped file.
+        """
+        prefix = self.propertyStore.deadPropertyXattrPrefix
+        for uid in (None, "123", "456",):
+            user = uid if uid is not None else ""
+            self.attrs[prefix + '%s{foo}bar' % (user,)] = 'baz%s' % (user,)
+            self.attrs[prefix + '%s{bar}baz' % (user,)] = 'quux%s' % (user,)
+            self.attrs[prefix + '%s{moo}mar%s' % (user, user,)] = 'quux%s' % (user,)
+
+        for uid in (None, "123", "456",):
+            user = uid if uid is not None else ""
+            self.assertEquals(
+                set(self.propertyStore.list(uid)),
+                set([
+                    (u'foo', u'bar'),
+                    (u'bar', u'baz'),
+                    (u'moo', u'mar%s' % (user,)),
+                ]))
+
+        self.assertEquals(
+            set(self.propertyStore.list(filterByUID=False)),
+            set([
+                (u'foo', u'bar', None),
+                (u'bar', u'baz', None),
+                (u'moo', u'mar', None),
+                (u'foo', u'bar', "123"),
+                (u'bar', u'baz', "123"),
+                (u'moo', u'mar123', "123"),
+                (u'foo', u'bar', "456"),
+                (u'bar', u'baz', "456"),
+                (u'moo', u'mar456', "456"),
+            ]))
+

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/xattrprops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/xattrprops.py	2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/xattrprops.py	2010-03-01 21:23:11 UTC (rev 5226)
@@ -84,20 +84,29 @@
     if sys.platform == "linux2":
         deadPropertyXattrPrefix = "user."
 
-    def _encode(clazz, name):
+    def _encode(clazz, name, uid=None):
         result = urllib.quote("{%s}%s" % name, safe='{}:')
+        if uid:
+            result = uid + result
         r = clazz.deadPropertyXattrPrefix + result
         return r
 
     def _decode(clazz, name):
         name = urllib.unquote(name[len(clazz.deadPropertyXattrPrefix):])
 
-        index = name.find("}")
+        index1 = name.find("{")
+        index2 = name.find("}")
 
-        if (index is -1 or not len(name) > index or not name[0] == "{"):
+        if (index1 is -1 or index2 is -1 or not len(name) > index2):
             raise ValueError("Invalid encoded name: %r" % (name,))
+        if index1 == 0:
+            uid = None
+        else:
+            uid = name[:index1]
+        propnamespace = name[index1+1:index2]
+        propname = name[index2+1:]
 
-        return (name[1:index], name[index+1:])
+        return (propnamespace, propname, uid)
 
     _encode = classmethod(_encode)
     _decode = classmethod(_decode)
@@ -107,7 +116,7 @@
         self.attrs = xattr.xattr(self.resource.fp.path)
 
 
-    def get(self, qname):
+    def get(self, qname, uid=None):
         """
         Retrieve the value of a property stored as an extended attribute on the
         wrapped path.
@@ -115,6 +124,8 @@
         @param qname: The property to retrieve as a two-tuple of namespace URI
             and local name.
 
+        @param uid: The per-user identifier for per user properties.
+
         @raise HTTPError: If there is no value associated with the given
             property.
 
@@ -122,7 +133,7 @@
             given property.
         """
         try:
-            data = self.attrs.get(self._encode(qname))
+            data = self.attrs.get(self._encode(qname, uid))
         except KeyError:
             raise HTTPError(StatusResponse(
                     responsecode.NOT_FOUND,
@@ -176,13 +187,15 @@
         return doc.root_element
 
 
-    def set(self, property):
+    def set(self, property, uid=None):
         """
         Store the given property as an extended attribute on the wrapped path.
 
+        @param uid: The per-user identifier for per user properties.
+
         @param property: A L{WebDAVElement} to store.
         """
-        key = self._encode(property.qname())
+        key = self._encode(property.qname(), uid)
         value = compress(property.toxml())
         untilConcludes(setitem, self.attrs, key, value)
 
@@ -190,15 +203,17 @@
         self.resource.fp.restat()
 
 
-    def delete(self, qname):
+    def delete(self, qname, uid=None):
         """
         Remove the extended attribute from the wrapped path which stores the
         property given by C{qname}.
 
+        @param uid: The per-user identifier for per user properties.
+
         @param qname: The property to delete as a two-tuple of namespace URI
             and local name.
         """
-        key = self._encode(qname)
+        key = self._encode(qname, uid)
         try:
             try:
                 self.attrs.remove(key)
@@ -214,7 +229,7 @@
                     "Unable to delete property: " + key))
 
 
-    def contains(self, qname):
+    def contains(self, qname, uid=None):
         """
         Determine whether the property given by C{qname} is stored in an
         extended attribute of the wrapped path.
@@ -222,9 +237,11 @@
         @param qname: The property to look up as a two-tuple of namespace URI
             and local name.
 
+        @param uid: The per-user identifier for per user properties.
+
         @return: C{True} if the property exists, C{False} otherwise.
         """
-        key = self._encode(qname)
+        key = self._encode(qname, uid)
         try:
             self.attrs.get(key)
         except KeyError:
@@ -240,11 +257,13 @@
             return True
 
 
-    def list(self):
+    def list(self, uid=None, filterByUID=True):
         """
         Enumerate the property names stored in extended attributes of the
         wrapped path.
 
+        @param uid: The per-user identifier for per user properties.
+
         @return: A C{list} of property names as two-tuples of namespace URI and
             local name.
         """
@@ -257,8 +276,16 @@
                     statusForFailure(Failure()),
                     "Unable to list properties: " + self.resource.fp.path))
         else:
-            return [
+            results = [
                 self._decode(name)
-                for name
-                in attrs
-                if name.startswith(prefix)]
+                for name in attrs
+                if name.startswith(prefix)
+            ]
+            if filterByUID:
+                return [ 
+                    (namespace, name)
+                    for namespace, name, propuid in results
+                    if propuid == uid
+                ]
+            else:
+                return results

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/authkerb.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/authkerb.py	2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/authkerb.py	2010-03-01 21:23:11 UTC (rev 5226)
@@ -179,7 +179,12 @@
                 self.log_error("%s" % (ex[0],))
                 raise error.UnauthorizedLogin("Bad credentials for: %s (%s: %s)" % (pcreds.authnURI, ex[0], ex[1],))
             else:
-                return succeed((pcreds.authnURI, pcreds.authzURI,))
+                return succeed((
+                    pcreds.authnPrincipal.principalURL(),
+                    pcreds.authzPrincipal.principalURL(),
+                    pcreds.authnPrincipal,
+                    pcreds.authzPrincipal,
+                ))
         
         raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.authnURI,))
 
@@ -307,7 +312,12 @@
 
         creds = pcreds.credentials
         if isinstance(creds, NegotiateCredentials):
-            return succeed((pcreds.authnURI, pcreds.authzURI,))
+            return succeed((
+                pcreds.authnPrincipal.principalURL(),
+                pcreds.authzPrincipal.principalURL(),
+                pcreds.authnPrincipal,
+                pcreds.authzPrincipal,
+            ))
         
         raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.authnURI,))
 

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/customxml.py	2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/customxml.py	2010-03-01 21:23:11 UTC (rev 5226)
@@ -579,6 +579,87 @@
 # Notifications
 ##
 
+class SharedOwner (davxml.WebDAVEmptyElement):
+    """
+    Denotes a shared collection.
+    """
+    namespace = calendarserver_namespace
+    name = "shared-owner"
+
+class Shared (davxml.WebDAVEmptyElement):
+    """
+    Denotes a shared collection.
+    """
+    namespace = calendarserver_namespace
+    name = "shared"
+
+class Subscribed (davxml.WebDAVEmptyElement):
+    """
+    Denotes a subscribed calendar collection.
+    """
+    namespace = calendarserver_namespace
+    name = "subscribed"
+
+class SharedURL (davxml.WebDAVTextElement):
+    """
+    The source url for a shared calendar.
+    """
+    namespace = calendarserver_namespace
+    name = "shared-url"
+
+class SharedAcceptEmailNotification (davxml.WebDAVTextElement):
+    """
+    The accept email flag for a shared calendar.
+    """
+    namespace = calendarserver_namespace
+    name = "shared-accept-email-notification"
+
+class Birthday (davxml.WebDAVEmptyElement):
+    """
+    Denotes a birthday calendar collection.
+    """
+    namespace = calendarserver_namespace
+    name = "birthday"
+
+class InviteShare (davxml.WebDAVElement):
+    namespace = calendarserver_namespace
+    name = "share"
+
+    allowed_children = {
+        (calendarserver_namespace, "set" )   : (0, None),
+    }
+
+class InviteSet (davxml.WebDAVElement):
+    namespace = calendarserver_namespace
+    name = "set"
+
+    allowed_children = {
+        (calendarserver_namespace, "summary" )   : (0, 1),
+        (calendarserver_namespace, "attendee" )  : (1, 1),
+        (calendarserver_namespace, "read" )      : (0, 1),
+        (calendarserver_namespace, "read-write" ): (0, 1),
+    }
+
+class InviteRemove (davxml.WebDAVElement):
+    namespace = calendarserver_namespace
+    name = "remove"
+
+    allowed_children = {
+        (calendarserver_namespace, "attendee" )  : (1, 1),
+        (calendarserver_namespace, "read" )      : (0, 1),
+        (calendarserver_namespace, "read-write" ): (0, 1),
+    }
+
+class InviteUser (davxml.WebDAVElement):
+    namespace = calendarserver_namespace
+    name = "user"
+
+    allowed_children = {
+        (calendarserver_namespace, "summary" )   : (0, 1),
+        (calendarserver_namespace, "attendee" )  : (1, 1),
+        (calendarserver_namespace, "access" )    : (0, 1),
+    }
+
 class InviteAccess (davxml.WebDAVElement):
     namespace = calendarserver_namespace
     name = "access"
@@ -588,6 +669,14 @@
         (calendarserver_namespace, "read-write" ): (0, 1),
     }
 
+class Invite (davxml.WebDAVElement):
+    namespace = calendarserver_namespace
+    name = "invite"
+
+    allowed_children = {
+        (calendarserver_namespace, "user" )   : (0, None),
+    }
+
 class InviteSummary (davxml.WebDAVTextElement):
     namespace = calendarserver_namespace
     name = "summary"

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/directory.py	2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/directory.py	2010-03-01 21:23:11 UTC (rev 5226)
@@ -124,12 +124,16 @@
             return (
                 credentials.authnPrincipal.principalURL(),
                 credentials.authzPrincipal.principalURL(),
+                credentials.authnPrincipal,
+                credentials.authzPrincipal,
             )
         else:
             if credentials.authnPrincipal.record.verifyCredentials(credentials.credentials):
                 return (
                     credentials.authnPrincipal.principalURL(),
                     credentials.authzPrincipal.principalURL(),
+                    credentials.authnPrincipal,
+                    credentials.authzPrincipal,
                 )
             else:
                 raise UnauthorizedLogin("Incorrect credentials for %s" % (credentials.credentials.username,)) 

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/sudo.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/sudo.py	2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/sudo.py	2010-03-01 21:23:11 UTC (rev 5226)
@@ -109,6 +109,8 @@
             return (
                 credentials.authnPrincipal.principalURL(),
                 credentials.authzPrincipal.principalURL(),
+                credentials.authnPrincipal,
+                credentials.authzPrincipal,
                 )
         else:
             raise UnauthorizedLogin(

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py	2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py	2010-03-01 21:23:11 UTC (rev 5226)
@@ -77,6 +77,26 @@
 else:
     serverVersion = twext.web2.server.VERSION + " TwistedCardDAV/?"
 
+##
+# Sharing Conts
+##
+SHARE_ACCEPT_STATE_NEEDS_ACTION = "0"
+SHARE_ACCEPT_STATE_ACCEPTED = "1"
+SHARE_ACCEPT_STATE_DECLINED = "2"
+SHARE_ACCEPT_STATE_DELETED = "-1"
+
+shareAccpetStates = {}
+shareAccpetStates[SHARE_ACCEPT_STATE_NEEDS_ACTION] = "NEEDS-ACTION"
+shareAccpetStates[SHARE_ACCEPT_STATE_ACCEPTED] = "ACCEPTED"
+shareAccpetStates[SHARE_ACCEPT_STATE_DECLINED] = "DECLINED"
+shareAccpetStates[SHARE_ACCEPT_STATE_DELETED] = "DELETED"
+
+shareAcceptStatesByXML = {}
+shareAcceptStatesByXML["NEEDS-ACTION"] = customxml.InviteStatusNoResponse()
+shareAcceptStatesByXML["ACCEPTED"] = customxml.InviteStatusAccepted()
+shareAcceptStatesByXML["DECLINED"] = customxml.InviteStatusDeclined()
+shareAcceptStatesByXML["DELETED"] = customxml.InviteStatusDeleted()
+
 class CalDAVComplianceMixIn(object):
 
     def davComplianceClasses(self):
@@ -206,6 +226,14 @@
         *[caldavxml.CalendarComponent(name=item) for item in allowedComponents]
     )
 
+    @classmethod
+    def enableSharing(clz, enable):
+        qname = (calendarserver_namespace, "invite" )
+        if enable and qname not in clz.liveProperties:
+            clz.liveProperties += (qname,)
+        elif not enable and qname in clz.liveProperties:
+            clz.liveProperties = tuple([p for p in clz.liveProperties if p != qname])
+
     def hasProperty(self, property, request):
         """
         Need to special case schedule-calendar-transp for backwards compatability.
@@ -222,6 +250,23 @@
         else:
             return super(CalDAVResource, self).hasProperty(property, request)
 
+    def isShadowableProperty(self, qname):
+        """
+        Shadowable properties are ones on shared resources where a "default" exists until
+        a user overrides with their own value.
+        """
+        return False
+
+    def isGlobalProperty(self, qname):
+        """
+        A global property is one that is the same for all users.
+        """
+        if qname == (u'DAV:', u'displayname'): return False # XXX HACK
+        if qname in self.liveProperties:
+            return True
+        else:
+            return False
+
     @inlineCallbacks
     def readProperty(self, property, request):
         if type(property) is tuple:
@@ -229,6 +274,30 @@
         else:
             qname = property.qname()
 
+        isvirt = (yield self.isVirtualShare(request))
+        if self.isShadowableProperty(qname):
+            if isvirt:
+                p = self.deadProperties().get(qname, uid=request.authzPrincipal.principalUID())
+                if p is not None:
+                    returnValue(p)
+            else:
+                res = (yield self._readGlobalProperty(qname, property, request))
+                returnValue(res)
+            
+        elif (not self.isGlobalProperty(qname)) and isvirt:
+            p = self.deadProperties().get(qname, uid=request.authzPrincipal.principalUID())
+            if p is not None:
+                returnValue(p)
+
+        if qname == customxml.Invite.qname():
+            isShared = (yield self.isShared(request))
+            if not isShared:
+                returnValue(None)
+        res = (yield self._readGlobalProperty(qname, property, request))
+        returnValue(res)
+
+    @inlineCallbacks
+    def _readGlobalProperty(self, qname, property, request):
         namespace, name = qname
 
         if namespace == dav_namespace:
@@ -275,6 +344,28 @@
                     opaque = url in fbset
                     self.writeDeadProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Opaque() if opaque else caldavxml.Transparent()))
 
+        elif namespace == calendarserver_namespace:
+            if name == "invite":
+                shared = (yield self.isShared(request))
+                if shared:
+                    userlist = []
+                    accesses = (yield self.getInviteUsers(request))
+                    if accesses:
+                        for access in accesses:
+                            acl = access()
+                            for principal, metaData in accesses[access]:
+                                children = []
+                                mailtoemail = "mailto:" + metaData["email"]
+                                children.append(davxml.HRef(mailtoemail))
+                                children.append(customxml.InviteAccess(acl))
+                                if "inviteStatus" in metaData and metaData["inviteStatus"]:
+                                    children.append(shareAcceptStatesByXML[metaData["inviteStatus"]])
+                                else: 
+                                    children.append(customxml.InviteStatusNoResponse())
+                                userlist.append(customxml.InviteUser(*children))
+                    result = customxml.Invite(*userlist)
+                    returnValue(result)
+
         result = (yield super(CalDAVResource, self).readProperty(property, request))
         returnValue(result)
 
@@ -283,7 +374,18 @@
         assert isinstance(property, davxml.WebDAVElement), (
             "%r is not a WebDAVElement instance" % (property,)
         )
+        
+        # Per-user Dav props currently only apply to a sharee's copy of a calendar
+        isvirt = (yield self.isVirtualShare(request))
+        if isvirt and (self.isShadowableProperty(property.qname()) or (not self.isGlobalProperty(property.qname()))):
+            p = (yield self.deadProperties().set(property, uid=request.authzPrincipal.principalUID()))
+            returnValue(p)
+ 
+        res = (yield self._writeGlobalProperty(property, request))
+        returnValue(res)
 
+    @inlineCallbacks
+    def _writeGlobalProperty(self, property, request):
         if property.qname() == (caldav_namespace, "supported-calendar-component-set"):
             if not self.isPseudoCalendarCollection():
                 raise HTTPError(StatusResponse(
@@ -331,12 +433,24 @@
                 myurl = (yield self.canonicalURL(request))
                 inbox.processFreeBusyCalendar(myurl, property.children[0] == caldavxml.Opaque())
 
+        elif property.qname() == (dav_namespace, "resourcetype"):
+            if self.isCalendarCollection() and config.EnableSharing:
+                # Check if adding or removing share
+                shared = (yield self.isShared(request))
+                sawShare = [child for child in property.children if child.qname() == (calendarserver_namespace, "shared-owner")]
+                if not shared and sawShare:
+                    # Owner is trying to share a collection
+                    yield self.upgradeToShare(request)
+                elif shared and not sawShare:
+                    # Remove share
+                    yield self.downgradeFromShare(request)
+                returnValue(None)
+
         result = (yield super(CalDAVResource, self).writeProperty(property, request))
         returnValue(result)
 
     def writeDeadProperty(self, property):
         val = super(CalDAVResource, self).writeDeadProperty(property)
-
         return val
 
 
@@ -851,6 +965,45 @@
         """
         return None
 
+    ##
+    # Sharing operations
+    ##
+
+    def upgradeToShare(self, request):
+        """ Upgrade this collection to a shared state """
+        return succeed(True)
+    
+    def downgradeFromShare(self, request):
+        return succeed(True)
+
+    def addUserToShare(self, userid, request, ace):
+        """ Add a user to this shared calendar """
+        return succeed(True)
+
+    def removeUserFromShare(self, userid, request):
+        """ Remove a user from this shared calendar """
+        return succeed(True)
+
+    def isShared(self, request):
+        """ Return True if this is an owner shared calendar collection """
+        succeed(False)
+
+    def isVirtualShare(self, request):
+        """ Return True if this is a shared calendar collection """
+        succeed(False)
+
+    def removeVirtualShare(self, request):
+        """ As user of a shared calendar, unlink this calendar collection """
+        succeed(False) 
+
+    def getInviteUsers(self, request):
+        succeed(True)
+
+    def sendNotificationOnChange(self, icalendarComponent, request, state="added"):
+        """ Possibly send a push and or email notification on a change to a resource in a shared collection """
+        succeed(True)
+ 
+
 class CalendarPrincipalCollectionResource (DAVPrincipalCollectionResource, CalDAVResource):
     """
     CalDAV principal collection.

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/stdconfig.py	2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/stdconfig.py	2010-03-01 21:23:11 UTC (rev 5226)
@@ -903,7 +903,8 @@
     #
     # FIXME: Use the config object instead of doing this here
     #
-    from twistedcaldav.resource import CalendarPrincipalResource
+    from twistedcaldav.resource import CalDAVResource, CalendarPrincipalResource
+    CalDAVResource.enableSharing(configDict.EnableSharing)
     CalendarPrincipalResource.enableSharing(configDict.EnableSharing)
 
 def _updatePartitions(configDict):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100301/c4eff2f6/attachment-0001.html>


More information about the calendarserver-changes mailing list