[CalendarServer-changes] [4753] CalendarServer/branches/users/cdaboo/deployment-partition-4722

source_changes at macosforge.org source_changes at macosforge.org
Tue Nov 17 09:11:51 PST 2009


Revision: 4753
          http://trac.macosforge.org/projects/calendarserver/changeset/4753
Author:   cdaboo at apple.com
Date:     2009-11-17 09:11:47 -0800 (Tue, 17 Nov 2009)
Log Message:
-----------
Need to manage principals via the command line.

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/deployment-partition-4722/bin/calendarserver_manage_principals
    CalendarServer/branches/users/cdaboo/deployment-partition-4722/calendarserver/tools/principals.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/
    CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/__init__.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/python/
    CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/python/__init__.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/python/log.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/
    CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/__init__.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/dav/
    CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/dav/__init__.py
    CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/dav/davxml.py

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4722/bin/calendarserver_manage_principals
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4722/bin/calendarserver_manage_principals	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4722/bin/calendarserver_manage_principals	2009-11-17 17:11:47 UTC (rev 4753)
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from __future__ import with_statement
+import sys
+
+#PYTHONPATH
+
+if __name__ == "__main__":
+    if "PYTHONPATH" in globals():
+        sys.path.insert(0, PYTHONPATH)
+    else:
+        from os.path import dirname, abspath, join
+        from subprocess import Popen, PIPE
+
+        home = dirname(dirname(abspath(__file__)))
+        run = join(home, "run")
+
+        child = Popen((run, "-p"), stdout=PIPE)
+        path, stderr = child.communicate()
+
+        if child.wait() == 0:
+            sys.path[0:0] = path.split(":")
+
+    from calendarserver.tools.principals import main
+    main()


Property changes on: CalendarServer/branches/users/cdaboo/deployment-partition-4722/bin/calendarserver_manage_principals
___________________________________________________________________
Added: svn:executable
   + *

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4722/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4722/calendarserver/tools/principals.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4722/calendarserver/tools/principals.py	2009-11-17 17:11:47 UTC (rev 4753)
@@ -0,0 +1,445 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import sys
+import os
+from getopt import getopt, GetoptError
+from uuid import UUID
+from pwd import getpwnam
+from grp import getgrnam
+
+from twisted.python.util import switchUID
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks
+from twisted.web2.dav import davxml
+
+from twext.python.log import StandardIOObserver
+from twext.web2.dav.davxml import sname2qname, qname2sname
+
+from twistedcaldav import memcachepool
+from twistedcaldav.config import config, ConfigurationError
+from twistedcaldav.log import setLogLevelForNamespace
+from twistedcaldav.notify import installNotificationClient
+from twistedcaldav.root import RootResource
+from twistedcaldav.static import CalendarHomeProvisioningFile
+
+from calendarserver.tools.util import booleanArgument, autoDisableMemcached
+from calendarserver.tools.util import loadConfig, getDirectory
+
+def usage(e=None):
+    if e:
+        print e
+        print ""
+
+    name = os.path.basename(sys.argv[0])
+    print "usage: %s [options] actions principal [principal ...]" % (name,)
+    print ""
+    print "  Performs the given actions against the giving principals."
+    print ""
+    print "  Principals are identified by one of the following:"
+    print "    Type and shortname (eg.: users:wsanchez)"
+    #print "    A principal path (eg.: /principals/users/wsanchez/)"
+    print "    A GUID (eg.: E415DBA7-40B5-49F5-A7CC-ACC81E4DEC79)"
+    print ""
+    print "options:"
+    print "  -h --help: print this help and exit"
+    print "  -f --config <path>: Specify caldavd.plist configuration path"
+    print ""
+    print "actions:"
+    #print "  --search <search-string>: search for matching resources"
+    print "  -P, --read-property=property: read DAV property (eg.: {DAV:}group-member-set)"
+    print "  --list-read-proxies: list proxies with read-only access"
+    print "  --list-write-proxies: list proxies with read-write access"
+    print "  --list-proxies: list all proxies"
+    print "  --add-read-proxy=principal: add a read-only proxy"
+    print "  --add-write-proxy=principal: add a read-write proxy"
+    print "  --remove-proxy=principal: remove a proxy"
+    print "  --set-auto-schedule={true|false}: set auto-accept state"
+    print "  --get-auto-schedule: read auto-schedule state"
+
+    if e:
+        sys.exit(64)
+    else:
+        sys.exit(0)
+
+def main():
+    #
+    # Send logging output to stdout
+    #
+    observer = StandardIOObserver()
+    observer.start()
+
+    try:
+        (optargs, args) = getopt(
+            sys.argv[1:], "hf:P:", [
+                "help",
+                "config=",
+                "read-property=",
+                "list-read-proxies",
+                "list-write-proxies",
+                "list-proxies",
+                "add-read-proxy=",
+                "add-write-proxy=",
+                "remove-proxy=",
+                "set-auto-schedule=",
+                "get-auto-schedule",
+            ],
+        )
+    except GetoptError, e:
+        usage(e)
+
+    #
+    # Get configuration
+    #
+    configFileName = None
+    actions = []
+
+    for opt, arg in optargs:
+        if opt in ("-h", "--help"):
+            usage()
+
+        elif opt in ("-f", "--config"):
+            configFileName = arg
+
+        elif opt in ("-P", "--read-property"):
+            try:
+                qname = sname2qname(arg)
+            except ValueError, e:
+                abort(e)
+            actions.append((action_readProperty, qname))
+
+        elif opt in ("", "--list-read-proxies"):
+            actions.append((action_listProxies, "read"))
+
+        elif opt in ("", "--list-write-proxies"):
+            actions.append((action_listProxies, "write"))
+
+        elif opt in ("-L", "--list-proxies"):
+            actions.append((action_listProxies, "read", "write"))
+
+        elif opt in ("--add-read-proxy", "--add-write-proxy"):
+            if "read" in opt:
+                proxyType = "read"
+            elif "write" in opt:
+                proxyType = "write"
+            else:
+                raise AssertionError("Unknown proxy type")
+
+            try:
+                principalForPrincipalID(arg, checkOnly=True)
+            except ValueError, e:
+                abort(e)
+
+            actions.append((action_addProxy, proxyType, arg))
+
+        elif opt in ("", "--remove-proxy"):
+            try:
+                principalForPrincipalID(arg, checkOnly=True)
+            except ValueError, e:
+                abort(e)
+
+            actions.append((action_removeProxy, arg))
+
+        elif opt in ("", "--set-auto-schedule"):
+            try:
+                autoSchedule = booleanArgument(arg)
+            except ValueError, e:
+                abort(e)
+
+            actions.append((action_setAutoSchedule, autoSchedule))
+
+        elif opt in ("", "--get-auto-schedule"):
+            actions.append((action_getAutoSchedule,))
+
+        else:
+            raise NotImplementedError(opt)
+
+    if not args:
+        usage("No principals specified.")
+
+    #
+    # Get configuration
+    #
+    try:
+        loadConfig(configFileName)
+        setLogLevelForNamespace(None, "warn")
+
+        # Shed privileges
+        if config.UserName and config.GroupName and os.getuid() == 0:
+            uid = getpwnam(config.UserName).pw_uid
+            gid = getgrnam(config.GroupName).gr_gid
+            switchUID(uid, uid, gid)
+
+        os.umask(config.umask)
+
+        config.directory = getDirectory()
+        autoDisableMemcached(config)
+    except ConfigurationError, e:
+        abort(e)
+
+    #
+    # Do a quick sanity check that arguments look like principal
+    # identifiers.
+    #
+    for arg in args:
+        try:
+            principalForPrincipalID(arg, checkOnly=True)
+        except ValueError, e:
+            abort(e)
+
+    #
+    # Start the reactor
+    #
+    reactor.callLater(0, run, args, actions)
+    reactor.run()
+
+ at inlineCallbacks
+def run(principalIDs, actions):
+    try:
+        #
+        # Connect to memcached, notifications
+        #
+        memcachepool.installPools(
+            config.Memcached.Pools,
+            config.Memcached.MaxClients
+        )
+        if config.Notifications.Enabled:
+            installNotificationClient(
+                config.Notifications.InternalNotificationHost,
+                config.Notifications.InternalNotificationPort,
+            )
+
+        #
+        # Wire up the resource hierarchy
+        #
+        principalCollection = config.directory.getPrincipalCollection()
+        root = RootResource(
+            config.DocumentRoot,
+            principalCollections=(principalCollection,),
+        )
+        root.putChild("principals", principalCollection)
+        calendarCollection = CalendarHomeProvisioningFile(
+            os.path.join(config.DocumentRoot, "calendars"),
+            config.directory, "/calendars/",
+        )
+        root.putChild("calendars", calendarCollection)
+
+        #
+        # Wrap root resource
+        #
+        # FIXME: not a fan -wsanchez
+        #root = ResourceWrapper(root)
+
+        for principalID in principalIDs:
+            # Resolve the given principal IDs to principals
+            try:
+                principal = principalForPrincipalID(principalID)
+            except ValueError:
+                principal = None
+
+            if principal is None:
+                sys.stderr.write("Invalid principal ID: %s\n" % (principalID,))
+                continue
+
+            # Performs requested actions
+            for action in actions:
+                (yield action[0](principal, *action[1:]))
+                print ""
+
+    finally:
+        #
+        # Stop the reactor
+        #
+        reactor.stop()
+
+def principalForPrincipalID(principalID, checkOnly=False, directory=None):
+    
+    # Allow a directory parameter to be passed in, but default to config.directory
+    # But config.directory isn't set right away, so only use it when we're doing more 
+    # than checking.
+    if not checkOnly and not directory:
+        directory = config.directory
+
+    if principalID.startswith("/"):
+        raise ValueError("Can't resolve paths yet")
+
+        if checkOnly:
+            return None
+
+    if principalID.startswith("("):
+        try:
+            i = principalID.index(")")
+
+            if checkOnly:
+                return None
+
+            recordType = principalID[1:i]
+            shortName = principalID[i+1:]
+
+            if not recordType or not shortName or "(" in recordType:
+                raise ValueError()
+
+            return directory.principalCollection.principalForShortName(recordType, shortName)
+
+        except ValueError:
+            pass
+
+    if ":" in principalID:
+        if checkOnly:
+            return None
+
+        recordType, shortName = principalID.split(":", 1)
+
+        return directory.principalCollection.principalForShortName(recordType, shortName)
+
+    try:
+        guid = UUID(principalID)
+
+        if checkOnly:
+            return None
+
+        return directory.principalCollection.principalForUID(guid)
+    except ValueError:
+        pass
+
+    raise ValueError("Invalid principal identifier: %s" % (principalID,))
+
+def proxySubprincipal(principal, proxyType):
+    return principal.getChild("calendar-proxy-" + proxyType)
+
+ at inlineCallbacks
+def action_readProperty(resource, qname):
+    property = (yield resource.readProperty(qname, None))
+    print "%r on %s:" % (qname2sname(qname), resource)
+    print ""
+    print property.toxml()
+
+ at inlineCallbacks
+def action_listProxies(principal, *proxyTypes):
+    for proxyType in proxyTypes:
+        subPrincipal = proxySubprincipal(principal, proxyType)
+        if subPrincipal is None:
+            print "No %s proxies for %s" % (proxyType, principal)
+            continue
+
+        membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
+
+        if membersProperty.children:
+            print "%s proxies for %s:" % (
+                {"read": "Read-only", "write": "Read/write"}[proxyType],
+                principal,
+            )
+            for member in membersProperty.children:
+                print " *", member
+        else:
+            print "No %s proxies for %s" % (proxyType, principal)
+
+ at inlineCallbacks
+def action_addProxy(principal, proxyType, *proxyIDs):
+    for proxyID in proxyIDs:
+        proxyPrincipal = principalForPrincipalID(proxyID)
+        (yield action_addProxyPrincipal(principal, proxyType, proxyPrincipal))
+
+ at inlineCallbacks
+def action_addProxyPrincipal(principal, proxyType, proxyPrincipal):
+    proxyURL = proxyPrincipal.url()
+
+    subPrincipal = proxySubprincipal(principal, proxyType)
+    if subPrincipal is None:
+        sys.stderr.write("Unable to edit %s proxies for %s\n" % (proxyType, principal))
+        return
+
+    membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
+
+    for memberURL in membersProperty.children:
+        if str(memberURL) == proxyURL:
+            print "%s is already a %s proxy for %s" % (proxyPrincipal, proxyType, principal)
+            break
+    else:
+        memberURLs = list(membersProperty.children)
+        memberURLs.append(davxml.HRef(proxyURL))
+        membersProperty = davxml.GroupMemberSet(*memberURLs)
+        (yield subPrincipal.writeProperty(membersProperty, None))
+        print "Added %s as a %s proxy for %s" % (proxyPrincipal, proxyType, principal)
+
+    proxyTypes = ["read", "write"]
+    proxyTypes.remove(proxyType)
+
+    (yield action_removeProxyPrincipal(principal, proxyPrincipal, proxyTypes=proxyTypes))
+
+ at inlineCallbacks
+def action_removeProxy(principal, *proxyIDs, **kwargs):
+    for proxyID in proxyIDs:
+        proxyPrincipal = principalForPrincipalID(proxyID)
+        (yield action_removeProxyPrincipal(principal, proxyPrincipal, **kwargs))
+
+ at inlineCallbacks
+def action_removeProxyPrincipal(principal, proxyPrincipal, **kwargs):
+    proxyTypes = kwargs.get("proxyTypes", ("read", "write"))
+    for proxyType in proxyTypes:
+        proxyURL = proxyPrincipal.url()
+
+        subPrincipal = proxySubprincipal(principal, proxyType)
+        if subPrincipal is None:
+            sys.stderr.write("Unable to edit %s proxies for %s\n" % (proxyType, principal))
+            continue
+
+        membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
+
+        memberURLs = [
+            m for m in membersProperty.children
+            if str(m) != proxyURL
+        ]
+
+        if len(memberURLs) == len(membersProperty.children):
+            # No change
+            continue
+
+        membersProperty = davxml.GroupMemberSet(*memberURLs)
+        (yield subPrincipal.writeProperty(membersProperty, None))
+        print "Removed %s as a %s proxy for %s" % (proxyPrincipal, proxyType, principal)
+
+ at inlineCallbacks
+def action_setAutoSchedule(principal, autoSchedule):
+    if autoSchedule and principal.record.recordType in ("users", "groups"):
+        print "Enabling auto-schedule for %s is not allowed." % (principal,)
+    else:
+        print "Setting auto-schedule to %s for %s" % (
+            { True: "true", False: "false" }[autoSchedule],
+            principal,
+        )
+        principal.setAutoSchedule(autoSchedule)
+
+ at inlineCallbacks
+def action_getAutoSchedule(principal):
+    autoSchedule = principal.getAutoSchedule()
+    print "Autoschedule for %s is %s" % (
+        principal,
+        { True: "true", False: "false" }[autoSchedule],
+    )
+
+def abort(msg, status=1):
+    sys.stdout.write("%s\n" % (msg,))
+    try:
+        reactor.stop()
+    except RuntimeError:
+        pass
+    sys.exit(status)
+
+if __name__ == "__main__":
+    main()


Property changes on: CalendarServer/branches/users/cdaboo/deployment-partition-4722/calendarserver/tools/principals.py
___________________________________________________________________
Added: svn:executable
   + *

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/__init__.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/__init__.py	2009-11-17 17:11:47 UTC (rev 4753)
@@ -0,0 +1,19 @@
+##
+# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Extensions to the Twisted Framework.
+"""

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/python/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/python/__init__.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/python/__init__.py	2009-11-17 17:11:47 UTC (rev 4753)
@@ -0,0 +1,19 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Extensions to twisted.python.
+"""

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/python/log.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/python/log.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/python/log.py	2009-11-17 17:11:47 UTC (rev 4753)
@@ -0,0 +1,49 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Extensions to twisted.python.log.
+"""
+
+from sys import stdout, stderr
+
+from twisted.python.log import addObserver, removeObserver
+
+class StandardIOObserver (object):
+    """
+    Log observer that writes to standard I/O.
+    """
+    def emit(self, eventDict):
+        text = None
+
+        if eventDict["isError"]:
+            output = stderr
+            if "failure" in eventDict:
+                text = eventDict["failure"].getTraceback()
+        else:
+            output = stdout
+
+        if not text:
+            text = " ".join([str(m) for m in eventDict["message"]]) + "\n"
+
+        output.write(text)
+        output.flush()
+
+    def start(self):
+        addObserver(self.emit)
+
+    def stop(self):
+        removeObserver(self.emit)

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/__init__.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/__init__.py	2009-11-17 17:11:47 UTC (rev 4753)
@@ -0,0 +1,19 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Extensions to twisted.web2
+"""

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/dav/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/dav/__init__.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/dav/__init__.py	2009-11-17 17:11:47 UTC (rev 4753)
@@ -0,0 +1,19 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Extensions to twisted.web2.dav
+"""

Added: CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/dav/davxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/dav/davxml.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/deployment-partition-4722/twext/web2/dav/davxml.py	2009-11-17 17:11:47 UTC (rev 4753)
@@ -0,0 +1,122 @@
+##
+# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Extensions to twisted.web2.dav.davxml
+"""
+
+__all__ = [
+    "sname2qname",
+    "qname2sname",
+    "ErrorResponse",
+]
+
+from twisted.web2.http import Response
+from twisted.web2.dav.http import ErrorResponse as SuperErrorResponse
+from twisted.web2.dav.davxml import twisted_dav_namespace, WebDAVTextElement
+from twisted.web2.dav.davxml import WebDAVUnknownElement, Error
+from twisted.web2.http_headers import MimeType
+
+
+def sname2qname(sname):
+    """
+    Convert an sname into a qname.
+
+    That is, parse a property name string (eg: C{"{DAV:}displayname"})
+    into a tuple (eg: C{("DAV:", "displayname")}).
+
+    @raise ValueError is input is not valid. Note, however, that this
+    function does not attempt to fully validate C{sname}.
+    """
+    def raiseIf(condition):
+        if condition:
+            raise ValueError("Invalid sname: %s" % (sname,))
+
+    raiseIf(not sname.startswith("{"))
+
+    try:
+        i = sname.index("}")
+    except ValueError:
+        raiseIf(True)
+
+    namespace = sname[1:i]
+    name = sname [i+1:]
+
+    raiseIf("{" in namespace or not name)
+
+    return namespace, name
+
+def qname2sname(qname):
+    """
+    Convert a qname into an sname.
+    """
+    try:
+        return "{%s}%s" % qname
+    except TypeError:
+        raise ValueError("Invalid qname: %r" % (qname,))
+
+
+
+
+
+class ErrorDescription(WebDAVTextElement):
+    """
+    The human-readable description of a failed precondition
+    """
+    namespace = twisted_dav_namespace
+    name = "error-description"
+    protected = True
+
+
+class ErrorResponse(SuperErrorResponse):
+    """
+    A L{Response} object which contains a status code and a L{davxml.Error}
+    element.
+    Renders itself as a DAV:error XML document.
+    """
+    error = None
+
+    def __init__(self, code, error, description=None):
+        """
+        @param code: a response code.
+        @param error: an L{davxml.WebDAVElement} identifying the error, or a
+            tuple C{(namespace, name)} with which to create an empty element
+            denoting the error.  (The latter is useful in the case of
+            preconditions ans postconditions, not all of which have defined
+            XML element classes.)
+        @param description: an optional string that, if present, will get
+            wrapped in a (twisted_dav_namespace, error-description) element.
+        """
+        if type(error) is tuple:
+            xml_namespace, xml_name = error
+            error = WebDAVUnknownElement()
+            error.namespace = xml_namespace
+            error.name = xml_name
+
+        if description:
+            output = Error(error, ErrorDescription(description)).toxml()
+        else:
+            output = Error(error).toxml()
+
+        Response.__init__(self, code=code, stream=output)
+
+        self.headers.setHeader("content-type", MimeType("text", "xml"))
+
+        self.error = error
+
+    def __repr__(self):
+        return "<%s %s %s>" % (self.__class__.__name__, self.code, self.error.sname())
+
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20091117/6acaf09c/attachment-0001.html>


More information about the calendarserver-changes mailing list