[CalendarServer-changes] [5047] CalendarServer/branches/users/sagen/locations-resources

source_changes at macosforge.org source_changes at macosforge.org
Sat Feb 6 11:46:23 PST 2010


Revision: 5047
          http://trac.macosforge.org/projects/calendarserver/changeset/5047
Author:   sagen at apple.com
Date:     2010-02-06 11:46:22 -0800 (Sat, 06 Feb 2010)
Log Message:
-----------
Adds a new command line utility for use by servermgr_calendar to carry out various location/resource manipulation commands

Modified Paths:
--------------
    CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/principals.py
    CalendarServer/branches/users/sagen/locations-resources/setup.py
    CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/test/test_modify.py
    CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/xmlfile.py

Added Paths:
-----------
    CalendarServer/branches/users/sagen/locations-resources/bin/calendarserver_command_gateway
    CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/gateway.py

Added: CalendarServer/branches/users/sagen/locations-resources/bin/calendarserver_command_gateway
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/bin/calendarserver_command_gateway	                        (rev 0)
+++ CalendarServer/branches/users/sagen/locations-resources/bin/calendarserver_command_gateway	2010-02-06 19:46:22 UTC (rev 5047)
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2006-2010 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
+
+#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()
+
+        path = path.rstrip("\n")
+
+        if child.wait() == 0:
+            sys.path[0:0] = path.split(":")
+
+        sys.argv[1:1] = ["-f", join(home, "conf", "caldavd-dev.plist")]
+
+    from calendarserver.tools.gateway import main
+    main()


Property changes on: CalendarServer/branches/users/sagen/locations-resources/bin/calendarserver_command_gateway
___________________________________________________________________
Added: svn:executable
   + *

Added: CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/gateway.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/gateway.py	                        (rev 0)
+++ CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/gateway.py	2010-02-06 19:46:22 UTC (rev 5047)
@@ -0,0 +1,190 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2006-2010 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
+import plistlib
+import xml
+
+import operator
+from getopt import getopt, GetoptError
+from pwd import getpwnam
+from grp import getgrnam
+
+from twisted.python.util import switchUID
+
+from twistedcaldav.config import config, ConfigurationError
+from twistedcaldav.directory.directory import DirectoryError
+
+from calendarserver.tools.util import loadConfig, getDirectory, autoDisableMemcached
+
+def usage(e=None):
+
+    name = os.path.basename(sys.argv[0])
+    print "usage: %s [options]" % (name,)
+    print ""
+    print "  TODO: describe usage"
+    print ""
+    print "options:"
+    print "  -h --help: print this help and exit"
+    print "  -f --config <path>: Specify caldavd.plist configuration path"
+    print ""
+
+    if e:
+        sys.exit(64)
+    else:
+        sys.exit(0)
+
+
+def main():
+
+    try:
+        (optargs, args) = getopt(
+            sys.argv[1:], "hf:", [
+                "help",
+                "config=",
+            ],
+        )
+    except GetoptError, e:
+        usage(e)
+
+    #
+    # Get configuration
+    #
+    configFileName = None
+
+    for opt, arg in optargs:
+        if opt in ("-h", "--help"):
+            usage()
+
+        elif opt in ("-f", "--config"):
+            configFileName = arg
+
+        else:
+            raise NotImplementedError(opt)
+
+    try:
+        loadConfig(configFileName)
+
+        # 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)
+
+        try:
+            config.directory = getDirectory()
+        except DirectoryError, e:
+            abort(e)
+        autoDisableMemcached(config)
+    except ConfigurationError, e:
+        abort(e)
+
+    #
+    # Read commands from stdin
+    #
+    rawInput = sys.stdin.read()
+    try:
+        plist = plistlib.readPlistFromString(rawInput)
+    except xml.parsers.expat.ExpatError, e:
+        abort(str(e))
+
+    # If the plist is an array, each element of the array is a separate
+    # command dictionary.
+    if isinstance(plist, list):
+        commands = plist
+    else:
+        commands = [plist]
+
+    # Make sure 'command' is specified
+    for command in commands:
+        if not command.has_key('command'):
+            abort("'command' missing from plist")
+
+    run(commands)
+
+def run(commands):
+    for command in commands:
+        commandName = command['command']
+
+        methodName = "command_%s" % (commandName,)
+        if hasattr(Commands, methodName):
+            getattr(Commands, methodName)(command)
+        else:
+            abort("Unknown command '%s'" % (commandName,))
+
+class Commands(object):
+
+    @classmethod
+    def command_getLocationList(cls, command):
+        directory = config.directory
+        result = []
+        for record in directory.listRecords("locations"):
+            result.append( {
+                'GeneratedUID' : record.guid,
+                'RecordName' : [n for n in record.shortNames],
+                'RealName' : record.fullName,
+                'AutoSchedule' : record.autoSchedule,
+            } )
+        respond(command, result)
+
+    @classmethod
+    def command_createLocation(cls, command):
+        directory = config.directory
+
+        try:
+            directory.createRecord("locations", guid=command['GeneratedUID'],
+                shortNames=command['RecordName'], fullName=command['RealName'])
+        except DirectoryError, e:
+            abort(str(e))
+
+        result = []
+        for record in directory.listRecords("locations"):
+            result.append( {
+                'GeneratedUID' : record.guid,
+                'RecordName' : [n for n in record.shortNames],
+                'RealName' : record.fullName,
+                'AutoSchedule' : record.autoSchedule,
+            } )
+        respond(command, result)
+
+
+    @classmethod
+    def command_getResourceList(cls, command):
+        directory = config.directory
+        result = []
+        for record in directory.listRecords("resources"):
+            result.append( {
+                'GeneratedUID' : record.guid,
+                'RecordName' : [n for n in record.shortNames],
+                'RealName' : record.fullName,
+                'AutoSchedule' : record.autoSchedule,
+            } )
+        respond(command, result)
+
+def respond(command, result):
+    sys.stdout.write(plistlib.writePlistToString( { 'command' : command['command'], 'result' : result } ) )
+
+def abort(msg, status=1):
+    sys.stdout.write(plistlib.writePlistToString( { 'error' : msg, } ) )
+    sys.exit(status)
+
+if __name__ == "__main__":
+    main()


Property changes on: CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/gateway.py
___________________________________________________________________
Added: svn:executable
   + *

Modified: CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/principals.py	2010-02-05 19:37:39 UTC (rev 5046)
+++ CalendarServer/branches/users/sagen/locations-resources/calendarserver/tools/principals.py	2010-02-06 19:46:22 UTC (rev 5047)
@@ -103,7 +103,6 @@
                #"search=",
                 "list-principal-types",
                 "list-principals=",
-                "create-principal=",
                 "read-property=",
                 "list-read-proxies",
                 "list-write-proxies",
@@ -124,7 +123,6 @@
     configFileName = None
     listPrincipalTypes = False
     listPrincipals = None
-    createPrincipal = None
     principalActions = []
 
     for opt, arg in optargs:
@@ -140,9 +138,6 @@
         elif opt in ("", "--list-principals"):
             listPrincipals = arg
 
-        elif opt in ("", "--create-principal"):
-            createPrincipal = arg
-
         elif opt in ("", "--read-property"):
             try:
                 qname = sname2qname(arg)
@@ -245,18 +240,6 @@
         return
 
     #
-    # Create principal
-    #
-    if createPrincipal:
-        if args:
-            usage("Too many arguments")
-
-        recordType, shortName = createPrincipal.split(":", 1)
-        config.directory.createRecord(recordType, shortNames=(shortName,))
-        return
-
-
-    #
     # Do a quick sanity check that arguments look like principal
     # identifiers.
     #

Modified: CalendarServer/branches/users/sagen/locations-resources/setup.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/setup.py	2010-02-05 19:37:39 UTC (rev 5046)
+++ CalendarServer/branches/users/sagen/locations-resources/setup.py	2010-02-06 19:46:22 UTC (rev 5047)
@@ -109,6 +109,7 @@
                          "bin/caldavd",
                          "bin/calendarserver_export",
                          "bin/calendarserver_manage_principals"
+                         "bin/calendarserver_command_gateway"
                        ],
     data_files       = [ ("caldavd", ["conf/caldavd.plist"]) ],
     ext_modules      = extensions,

Modified: CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/test/test_modify.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/test/test_modify.py	2010-02-05 19:37:39 UTC (rev 5046)
+++ CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/test/test_modify.py	2010-02-06 19:46:22 UTC (rev 5047)
@@ -19,6 +19,7 @@
 from twistedcaldav.test.util import TestCase
 from calendarserver.tools.util import getDirectory
 from twisted.python.filepath import FilePath
+from twistedcaldav.directory.directory import DirectoryError
 
 
 class ModificationTestCase(TestCase):
@@ -119,3 +120,9 @@
         # Make sure old records are still there:
         record = directory.recordWithUID("location01")
         self.assertNotEquals(record, None)
+
+    def test_createDuplicateRecord(self):
+        directory = getDirectory()
+
+        directory.createRecord("resources", "resource01", shortNames=("resource01",), uid="resource01")
+        self.assertRaises(DirectoryError, directory.createRecord, "resources", "resource01", shortNames=("resource01",), uid="resource01")

Modified: CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/xmlfile.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/xmlfile.py	2010-02-05 19:37:39 UTC (rev 5046)
+++ CalendarServer/branches/users/sagen/locations-resources/twistedcaldav/directory/xmlfile.py	2010-02-06 19:46:22 UTC (rev 5047)
@@ -30,7 +30,7 @@
 from twisted.python.filepath import FilePath
 
 from twistedcaldav.directory import augment
-from twistedcaldav.directory.directory import DirectoryService
+from twistedcaldav.directory.directory import DirectoryService, DirectoryError
 from twistedcaldav.directory.cachingdirectory import CachingDirectoryService,\
     CachingDirectoryRecord
 from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser, XMLAccountRecord
@@ -81,13 +81,24 @@
         return self._recordTypes
 
     def listRecords(self, recordType):
+        self._lastCheck = 0
         for xmlPrincipal in self._accounts()[recordType].itervalues():
             record = self.recordWithGUID(xmlPrincipal.guid)
             if record is not None:
                 yield record
 
     def queryDirectory(self, recordTypes, indexType, indexKey):
-        
+        """
+        If the query is a miss, re-read from the XML file and try again
+        """
+        if not self._queryDirectory(recordTypes, indexType, indexKey):
+            self._lastCheck = 0
+            self._queryDirectory(recordTypes, indexType, indexKey)
+
+    def _queryDirectory(self, recordTypes, indexType, indexKey):
+
+        anyMatches = False
+
         for recordType in recordTypes:
             for xmlPrincipal in self._accounts()[recordType].itervalues():
                 record = XMLDirectoryRecord(
@@ -112,7 +123,10 @@
                     matched = indexKey in record.calendarUserAddresses
                 
                 if matched:
+                    anyMatches = True
                     self.recordCacheForType(recordType).addRecord(record, indexType, indexKey)
+
+        return anyMatches
             
     def recordsMatchingFields(self, fields, operand="or", recordType=None):
         # Default, brute force method search of underlying XML data
@@ -221,7 +235,26 @@
 
         return element
 
+
     def _persistRecords(self, element):
+
+        def indent(elem, level=0):
+            i = "\n" + level*"  "
+            if len(elem):
+                if not elem.text or not elem.text.strip():
+                    elem.text = i + "  "
+                if not elem.tail or not elem.tail.strip():
+                    elem.tail = i
+                for elem in elem:
+                    indent(elem, level+1)
+                if not elem.tail or not elem.tail.strip():
+                    elem.tail = i
+            else:
+                if level and (not elem.tail or not elem.tail.strip()):
+                    elem.tail = i
+
+        indent(element)
+
         # TODO: make this robust:
         ET.ElementTree(element).write(self.xmlFile.path)
 
@@ -252,6 +285,8 @@
         accountsElement = ET.Element("accounts", realm=self.realmName)
         for recType in self.recordTypes():
             for xmlPrincipal in accounts[recType].itervalues():
+                if xmlPrincipal.guid == guid:
+                    raise DirectoryError("Duplicate guid: %s" % (guid,))
                 self._addElement(accountsElement, xmlPrincipal)
 
         xmlPrincipal = XMLAccountRecord(recordType)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100206/f5abff73/attachment-0001.html>


More information about the calendarserver-changes mailing list