[CalendarServer-changes] [647] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Dec 1 17:06:19 PST 2006


Revision: 647
          http://trac.macosforge.org/projects/calendarserver/changeset/647
Author:   dreid at apple.com
Date:     2006-12-01 17:06:17 -0800 (Fri, 01 Dec 2006)

Log Message:
-----------
Merge caladmin-tool-2, this creates a flexible administration tool and framework for the commandline with various output mechanisms

Modified Paths:
--------------
    CalendarServer/trunk/conf/caldavd-test.plist
    CalendarServer/trunk/conf/caldavd.plist

Added Paths:
-----------
    CalendarServer/trunk/bin/caladmin
    CalendarServer/trunk/bin/caldavd
    CalendarServer/trunk/caladmin/
    CalendarServer/trunk/caladmin/__init__.py
    CalendarServer/trunk/caladmin/formatters.py
    CalendarServer/trunk/caladmin/logs.py
    CalendarServer/trunk/caladmin/options.py
    CalendarServer/trunk/caladmin/principals.py
    CalendarServer/trunk/caladmin/purge.py
    CalendarServer/trunk/caladmin/script.py
    CalendarServer/trunk/caladmin/stats.py
    CalendarServer/trunk/caladmin/util.py
    CalendarServer/trunk/twistedcaldav/caldavd.py

Removed Paths:
-------------
    CalendarServer/trunk/bin/caldavd
    CalendarServer/trunk/caladmin/__init__.py
    CalendarServer/trunk/caladmin/formatters.py
    CalendarServer/trunk/caladmin/logs.py
    CalendarServer/trunk/caladmin/options.py
    CalendarServer/trunk/caladmin/principals.py
    CalendarServer/trunk/caladmin/purge.py
    CalendarServer/trunk/caladmin/script.py
    CalendarServer/trunk/caladmin/stats.py
    CalendarServer/trunk/caladmin/util.py

Copied: CalendarServer/trunk/bin/caladmin (from rev 646, CalendarServer/branches/caladmin-tool-2/bin/caladmin)
===================================================================
--- CalendarServer/trunk/bin/caladmin	                        (rev 0)
+++ CalendarServer/trunk/bin/caladmin	2006-12-02 01:06:17 UTC (rev 647)
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+import sys, os
+
+sys.path.insert(0, os.path.join(os.environ['HOME'],
+								'projects', 'CalendarServer',
+								'Twisted'))
+
+if __name__ == '__main__':
+   from caladmin.script import run
+   run()

Deleted: CalendarServer/trunk/bin/caldavd
===================================================================
--- CalendarServer/trunk/bin/caldavd	2006-12-01 22:28:39 UTC (rev 646)
+++ CalendarServer/trunk/bin/caldavd	2006-12-02 01:06:17 UTC (rev 647)
@@ -1,416 +0,0 @@
-#!/usr/bin/env python
-
-##
-# Copyright (c) 2005-2006 Apple Computer, 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.
-#
-# DRI: Cyrus Daboo, cdaboo at apple.com
-##
-
-import sys
-import os
-import getopt
-import signal
-from tempfile import mkstemp
-
-try:
-    #
-    # plistlib is only included in Mac OS distributions of Python.
-    # This may change in Python 2.6, see:
-    #   https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1555501&group_id=5470
-    #
-    from plistlib import readPlist
-except ImportError:
-    from twistedcaldav.py.plistlib import readPlist
-
-sys.path.insert(0, "/usr/share/caldavd/lib/python")
-
-"""
-Parse the command line and read in a configuration file and then launch the server.
-"""
-
-class caldavd(object):
-    """
-    Runs the caldav server.
-    """
-    
-    def __init__(self):
-        # Option defaults
-        self.plistfile = "/etc/caldavd/caldavd.plist"
-
-        self.verbose = False
-        self.daemonize = True
-        self.docroot = "/Library/CalendarServer/Documents"
-
-        self.repo = "/etc/caldavd/repository.xml"
-        self.doacct = False
-        self.doacl = False
-
-        self.port = 8008
-        self.dossl = False
-        self.sslport = 8443
-        self.onlyssl = False
-        self.keyfile = "/etc/certificates/Default.key"
-        self.certfile = "/etc/certificates/Default.crt"
-        self.manhole = 0
-
-        self.directoryservice = {
-            "type": "OpenDirectoryService",
-            "params": { "node": "/Search" },
-        }
-
-        self.dropbox = True
-        self.dropboxName = "dropbox"
-        self.dropboxACLs = True
-        self.notifications = False
-        self.notificationName = "notifications"
-
-        self.serverlogfile = "/var/log/caldavd/server.log"
-        self.errorlogfile = "/var/log/caldavd/error.log"
-        self.pidfile = "/var/run/caldavd.pid"
-        
-        self.twistd = "/usr/share/caldavd/bin/twistd"
-
-        self.maxsize  =  1048576    # 1 Mb
-        self.quota    =  104857600  # 100 Mb
-        
-        self.action = None
-    
-    def printit(self):
-        """
-        Print out details about the current configuration.
-        """
-
-        print "Current Configuration"
-        print ""
-        print "Configuration File:               %s" % (self.plistfile,)
-        print ""
-        print "Run as daemon:                    %s" % (self.daemonize,)
-        print "Document Root:                    %s" % (self.docroot,)
-        print "Repository Configuration:         %s" % (self.repo,)
-        print "Generate Accounts in Repository:  %s" % (self.doacct,)
-        print "Reset ACLs on Generated Accounts: %s" % (self.doacl,)
-        print "Non-ssl Port:                     %s" % (self.port,)
-        print "Use SSL:                          %s" % (self.dossl,)
-        print "SSL Port:                         %s" % (self.sslport,)
-        print "Only Use SSL:                     %s" % (self.onlyssl,)
-        print "SSL Private Key File:             %s" % (self.keyfile,)
-        print "SSL Certificate File:             %s" % (self.certfile,)
-        print "Directory Service:                %s" % (self.directoryservice["type"],)
-        print "Directory Service Parameters:     %r" % (self.directoryservice["params"],)
-        print "Drop Box Enabled:                 %s" % (self.dropbox,)
-        print "Drop Box Name:                    %s" % (self.dropboxName,)
-        print "Drop Box ACLs are Inherited       %s" % (self.dropboxACLs,)
-        print "Notifications Enabled:            %s" % (self.notifications,)
-        print "Notification Collection Name:     %s" % (self.notificationName,)
-        print "Server Log File:                  %s" % (self.serverlogfile,)
-        print "Error Log File:                   %s" % (self.errorlogfile,)
-        print "PID File:                         %s" % (self.pidfile,)
-        print "twistd Location:                  %s" % (self.twistd,)
-        print "Maximum Calendar Resource Size:   %d bytes" % (self.maxsize,)
-        print "Global per-user quota limit:      %d bytes" % (self.quota,)
-
-    def run(self):
-        """
-        Run the caldavd server using the provided options and configuration.
-
-        @raise: C:{ValueError} if options or configuration are wrong.
-        """
-
-        # Parse command line options and config file
-        self.commandLine()
-        if self.action is None:
-            return
-        
-        # Dispatch action
-        {"start":   self.start,
-         "stop":    self.stop,
-         "restart": self.restart}[self.action]()
-
-    def start(self):
-        """
-        Start the caldavd server.
-        """
-        
-        print "Starting CalDAV Server",
-        try:
-            fd, tac = mkstemp(prefix="caldav")
-            os.write(fd, self.generateTAC())
-            os.close(fd)
-        except Exception, e:
-            print "        [Failed]"
-            print "Unable to create temporary file for server configuration."
-            print e
-            sys.exit(1)
-        
-        # Create arguments for twistd
-        args = [os.path.basename(sys.executable)]
-        args.append(self.twistd)
-        if not self.daemonize:
-            args.append("-n")
-        args.append("--logfile=%s" % (self.errorlogfile,))
-        args.append("--pidfile=%s" % (self.pidfile,))
-        args.append("-y")
-        args.append(tac)
-
-        # Create environment for twistd
-        environment = dict(os.environ)
-        environment["PYTHONPATH"] = ":".join(sys.path)
-
-        # spawn the twistd python process
-        try:
-            os.spawnve(os.P_WAIT, sys.executable, args, environment)
-        except OSError, why:
-            print "        [Failed]"
-            print "Error: %s" % (why[1],)
-        
-        # Get rid of temp file
-        try:
-            os.unlink(tac)
-        except:
-            pass
-        print "        [Done]"
-    
-    def stop(self):
-        """
-        Stop the caldavd server.
-        """
-        
-        if os.path.exists(self.pidfile):
-            try:
-                pid = int(open(self.pidfile).read())
-            except ValueError:
-                sys.exit("Pidfile %s contains non-numeric value" % self.pidfile)
-            try:
-                print "Stopping CalDAV Server",
-                os.kill(pid, signal.SIGTERM)
-                print "        [Done]"
-            except OSError, why:
-                print "        [Failed]"
-                print "Error: %s" % (why[1],)
-        else:
-            print "CalDAV server is not running"
-    
-    def restart(self):
-        """
-        Restart the caldavd server.
-        """
-        self.stop()
-        self.start()
-        
-    def commandLine(self):
-        """
-        Parse the command line options into the config object.
-        
-        @return: the C{str} for the requested action, or C{None} when
-            immediate exit is called for.
-        @raise: C{ValueError} when a problem occurs with the options.
-        """
-        options, args = getopt.getopt(sys.argv[1:], "hvf:XT:p")
-        
-        # Process the plist file first, then the options, so that command line
-        # options get to override plist options
-        pls = [p for p in options if p[0] == "-f"]
-        if len(pls) == 1:
-            self.plistfile = pls[0][1]
-        if not os.path.exists(self.plistfile):
-            print "Configuration file does not exist: %s" % (self.plistfile,)
-            raise ValueError
-        self.parsePlist()
-    
-        # Parse all the options
-        do_print = False
-        for option, value in options:
-            if option == "-h":
-                self.usage()
-                return
-            elif option == "-v":
-                self.verbose = True
-            elif option == "-f":
-                # We should have handled this already
-                pass
-            elif option == "-X":
-                self.daemonize = False
-            elif option == "-T":
-                self.twistd = value
-            elif option == "-p":
-                do_print = True
-            else:
-                print "Unrecognized option: %s" % (option,)
-                self.usage()
-                raise ValueError
-        
-        # Print out config if requested
-        if do_print:
-            self.printit()
-            return
-    
-        # Process arguments
-        if len(args) == 0:
-            print "No arguments given. One of start, stop or restart must be present."
-            self.usage()
-            raise ValueError
-        elif len(args) > 1:
-            print "Too many arguments given. Only one of start, stop or restart must be present."
-            self.usage()
-            raise ValueError
-        elif args[0] not in ("start", "stop", "restart"):
-            print "Wrong arguments given: %s" % (args[0],)
-            self.usage()
-            raise ValueError
-        
-        # Verify that configuration is valid
-        if not self.validate():
-            raise ValueError
-    
-        self.action = args[0]
-    
-    def parsePlist(self):
-        print "Reading configuration file %s." % (self.plistfile,)
-
-        root = readPlist(self.plistfile)
-        
-        # dict that maps between plist keys and class attributes
-        mapper = {
-                   "Verbose":                    "verbose",
-                   "RunStandalone":              "daemonize",
-                   "DocumentRoot":               "docroot",
-                   "Port":                       "port",
-                   "SSLEnable":                  "dossl",
-                   "SSLPort":                    "sslport",
-                   "SSLOnly":                    "onlyssl",
-                   "SSLPrivateKey":              "keyfile",
-                   "SSLCertificate":             "certfile",
-                   "ManholePort":                "manhole",
-                   "DirectoryService":           "directoryservice",
-                   "DropBoxEnabled":             "dropbox",
-                   "DropBoxName":                "dropboxName",
-                   "DropBoxInheritedACLs":       "dropboxACLs",
-                   "NotificationsEnabled":       "notifications",
-                   "NotificationCollectionName": "notificationName",
-                   "ServerLogFile":              "serverlogfile",
-                   "ErrorLogFile":               "errorlogfile",
-                   "PIDFile":                    "pidfile",
-                   "Repository":                 "repo",
-                   "CreateAccounts":             "doacct",
-                   "ResetAccountACLs":           "doacl",
-                   "twistdLocation":             "twistd",
-                   "MaximumAttachmentSizeBytes": "maxsize",
-                   "UserQuotaBytes":             "quota",
-                  }
-        
-        for k,v in root.items():
-            if mapper.has_key(k) and hasattr(self, mapper[k]):
-                setattr(self, mapper[k], v)
-            else:
-                print "Unknown option: %s" % (k,)
-
-    def validate(self):
-        
-        result = True
-
-        if not os.path.exists(self.docroot):
-            print "Document Root does not exist: %s" % (self.docroot,)
-            result = False
-
-        if not os.path.exists(self.repo):
-            print "Repository File does not exist: %s" % (self.repo,)
-            result = False
-
-        if self.dossl and not os.path.exists(self.keyfile):
-            print "SSL Private Key File does not exist: %s" % (self.keyfile,)
-            result = False
-
-        if self.dossl and not os.path.exists(self.certfile):
-            print "SSL Certificate File does not exist: %s" % (self.certfile,)
-            result = False
-
-        if not self.dossl and self.onlyssl:
-            self.dossl = True
-
-        if not self.daemonize:
-            self.errorlogfile = "-"
-
-        if not os.path.exists(self.twistd):
-            print "twistd does not exist: %s" % (self.twistd,)
-            result = False
-            
-        return result
-
-    def usage(self):
-        default = caldavd()
-        print """Usage: caldavd [options] start|stop|restart
-Options:
-    -h          Print this help and exit
-    -v          Be verbose
-    -f config   Specify path to configuration file [""" + default.plistfile + """]
-    -X          Do not daemonize
-    -T twistd   Specify path to twistd [""" + default.twistd + """]
-    -p          Print current configuration and exit
-"""
-    
-    def generateTAC(self):
-        return """
-from twistedcaldav.repository import startServer
-
-application, site = startServer(
-    %(docroot)r,
-    %(repo)r,
-    %(doacct)s,
-    %(doacl)s,
-    %(dossl)s,
-    %(keyfile)r,
-    %(certfile)r,
-    %(onlyssl)s,
-    %(port)d,
-    %(sslport)d,
-    %(maxsize)d,
-    %(quota)d,
-    %(serverlogfile)r,
-    %(directoryservice)r,
-    %(dropbox)r,
-    %(dropboxName)r,
-    %(dropboxACLs)r,
-    %(notifications)r,
-    %(notificationName)r,
-    %(manhole)d,
-)
-""" % {
-    "docroot":          self.docroot,
-    "repo":             self.repo,
-    "doacct":           self.doacct,
-    "doacl":            self.doacl,
-    "dossl":            self.dossl,
-    "keyfile":          self.keyfile,
-    "certfile":         self.certfile,
-    "onlyssl":          self.onlyssl,
-    "port":             self.port,
-    "sslport":          self.sslport,
-    "maxsize":          self.maxsize,
-    "quota":            self.quota,
-    "serverlogfile":    self.serverlogfile,
-    "directoryservice": self.directoryservice,
-    "dropbox":          self.dropbox,
-    "dropboxName":      self.dropboxName,
-    "dropboxACLs":      self.dropboxACLs,
-    "notifications":    self.notifications,
-    "notificationName": self.notificationName,
-    "manhole":          self.manhole,
-}
-
-if __name__ == "__main__":
-    try:
-        caldavd().run()
-    except Exception, e:
-        sys.exit(str(e))

Copied: CalendarServer/trunk/bin/caldavd (from rev 646, CalendarServer/branches/caladmin-tool-2/bin/caldavd)
===================================================================
--- CalendarServer/trunk/bin/caldavd	                        (rev 0)
+++ CalendarServer/trunk/bin/caldavd	2006-12-02 01:06:17 UTC (rev 647)
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2005-2006 Apple Computer, 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.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+
+if __name__ == "__main__":     
+    from twistedcaldav.caldavd import caldavd
+
+    import sys
+
+    try:
+        caldavd().run()
+    except Exception, e:
+        sys.exit(str(e))

Copied: CalendarServer/trunk/caladmin (from rev 646, CalendarServer/branches/caladmin-tool-2/caladmin)

Deleted: CalendarServer/trunk/caladmin/__init__.py
===================================================================
--- CalendarServer/branches/caladmin-tool-2/caladmin/__init__.py	2006-12-01 22:28:39 UTC (rev 646)
+++ CalendarServer/trunk/caladmin/__init__.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -1,17 +0,0 @@
-##
-# Copyright (c) 2006 Apple Computer, 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.
-#
-# DRI: David Reid, dreid at apple.com
-##

Copied: CalendarServer/trunk/caladmin/__init__.py (from rev 646, CalendarServer/branches/caladmin-tool-2/caladmin/__init__.py)
===================================================================
--- CalendarServer/trunk/caladmin/__init__.py	                        (rev 0)
+++ CalendarServer/trunk/caladmin/__init__.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -0,0 +1,17 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: David Reid, dreid at apple.com
+##

Deleted: CalendarServer/trunk/caladmin/formatters.py
===================================================================
--- CalendarServer/branches/caladmin-tool-2/caladmin/formatters.py	2006-12-01 22:28:39 UTC (rev 646)
+++ CalendarServer/trunk/caladmin/formatters.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -1,238 +0,0 @@
-##
-# Copyright (c) 2006 Apple Computer, 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.
-#
-# DRI: David Reid, dreid at apple.com
-##
-
-import sys
-
-FORMATTERS = {}
-
-def registerFormatter(formatter):
-    FORMATTERS[formatter.name] = formatter
-
-def listFormatters():
-    return FORMATTERS.keys()
-
-def getFormatter(short):
-    return FORMATTERS[short]
-
-
-class BaseFormatter(object):
-    config = None
-
-    def __init__(self, dest=None, options=None):
-        self.dest = dest
-        
-        if not self.dest:
-            self.dest = sys.stdout
-
-        self.options = options
-
-        if not options:
-            self.options = {}
-
-        self.reportTypes = []
-
-        for attr in self.__dict__:
-            if attr.startswith('report_'):
-                self.reportTypes.append(attr.split('_', 1)[1])
-
-    def write(self, data):
-        self.dest.write(data)
-        self.dest.flush()
-
-    def close(self):
-        self.dest.close()
-
-    def printReport(self, report):
-        reportPrinter = getattr(self, 'report_%s' % (report['type'],), None)
-
-        if reportPrinter:
-            reportPrinter(report)
-
-        else:
-            self.report_default(report)
-    
-    def report_default(self, report):
-        import pprint
-
-        preport = pprint.pformat(report)
-
-        self.write(''.join([preport, '\n']))
-        self.close()
-
-
-class PPrintFormatter(BaseFormatter):
-    name = "pprint"
-
-registerFormatter(PPrintFormatter)
-
-
-class PlainFormatter(BaseFormatter):
-    name = "plain"
-
-    def writeLine(self, fields, spacing=None):
-
-        if not spacing:
-            spacing = self.options.get('spacing', 16)
-
-        for f in fields:
-            self.write(str(f))
-            self.write(' '*(int(spacing) - len(str(f))))
-
-        self.write('\n')
-
-    def writeTable(self, report, fields, headings):
-        if self.options.has_key('fields'):
-            fields = self.options.get('fields', '').split(',')
-
-        self.writeLine((headings[f] for f in fields))
-
-        for record in report['records']:
-            self.writeLine((record[f] for f in fields))
-
-    def writeReport(self, report, name, fields, headings):
-        if self.options.has_key('fields'):
-            fields = self.options.get('fields', '').split(',')
-        
-        if name:
-            self.write('%s:\n' % (name,))
-
-        for f in fields:
-            self.write('  %s: %s\n' % (headings[f], report['data'][f]))
-
-    def report_principals(self, report):
-        fields = ('principalName', 'calendarCount', 'eventCount', 'todoCount',
-                  'quotaRoot', 'quotaUsed', 'quotaAvail')
-
-        headings = {
-            'principalName': 'Name',
-            'calendarCount': '# Calendars',
-            'eventCount': '# Events',
-            'todoCount': '# Todos',
-            'quotaRoot': 'Quota',
-            'quotaUsed': 'Used',
-            'quotaAvail': 'Available',
-            'disabled': 'Disaabled',
-            'quotaFree': 'Free %',
-            'calendarHome': 'Home',
-            }
-
-        self.writeTable(report, fields, headings)
-
-    report_user = report_group = report_resource = report_principals
-
-    def report_stats(self, report):
-        fields = ('accountCount', 'groupCount', 'calendarCount', 'eventCount', 
-                  'todoCount', 'diskUsage')
-
-        headings = {
-            'accountCount': '# Accounts',
-            'groupCount': '# Groups',
-            'calendarCount': '# Calendars',
-            'eventCount': '# Events',
-            'todoCount': '# Todos',
-            'diskUsage': 'Disk Usage',
-            }
-
-        self.writeReport(report, 'Statistics', fields, headings)
-
-    def report_logs(self, report):
-        self.write('Log Statistics:\n')
-
-        self.write('  Bytes Out: %s\n' % (report['data']['bytesOut'],))
-        self.write('  # Requests:\n')
-
-        for req, count in report['data']['requestCounts'].iteritems():
-            self.write('    %s: %s\n' % (req, count))
-
-        self.write('  User Agents:\n')
-
-        for ua, count in report['data']['userAgents'].iteritems():
-            self.write('    %s: %s\n' % (ua, count))
-
-registerFormatter(PlainFormatter)
-
-
-import csv
-
-class CsvFormatter(BaseFormatter):
-    name = "csv"
-
-    def writeList(self, fieldnames, l):
-        dw = csv.DictWriter(self.dest,
-                            **self.options)
-
-        dw.writerow(dict(zip(fieldnames,
-                             fieldnames)))
-
-        dw.writerows(l)
-
-    def report_principals(self, report):
-        if 'fieldnames' not in self.options:
-            self.options['fieldnames'] = [
-                'principalName',
-                'calendarHome',
-                'calendarCount',
-                'eventCount',
-                'todoCount',
-                'disabled',
-                'diskUsage',
-                'quotaRoot',
-                'quotaUsed',
-                'quotaAvail',
-                'quotaFree']
-            
-        self.writeDict(self.options['fieldnames'],
-                       report['records'])
-        
-    report_user = report_group = report_resource = report_principals
-
-    def report_stats(self, report):
-        if 'fieldnames' not in self.options:
-            self.options['fieldnames'] = report['data'].keys()
-            self.options['fieldnames'].sort()
-
-        self.writeList(self.options['fieldnames'],
-                       [report['data']])
-                
-    report_logs = report_stats
-
-registerFormatter(CsvFormatter)
-
-import plistlib
-
-class PlistFormatter(BaseFormatter):
-    name = "plist"
-
-    def report_principals(self, report):
-        plist = plistlib.Dict()
-
-        plist[report['type']] = list(report['records'])
-
-        plistlib.writePlist(plist, self.dest)
-
-    report_user = report_group = report_resource = report_principals
-
-    def report_stats(self, report):
-        plist = plistlib.Dict()
-        plist[report['type']] = report['data']
-
-        plistlib.writePlist(plist, self.dest)
-
-    report_logs = report_stats
-
-registerFormatter(PlistFormatter)

Copied: CalendarServer/trunk/caladmin/formatters.py (from rev 646, CalendarServer/branches/caladmin-tool-2/caladmin/formatters.py)
===================================================================
--- CalendarServer/trunk/caladmin/formatters.py	                        (rev 0)
+++ CalendarServer/trunk/caladmin/formatters.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -0,0 +1,238 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+import sys
+
+FORMATTERS = {}
+
+def registerFormatter(formatter):
+    FORMATTERS[formatter.name] = formatter
+
+def listFormatters():
+    return FORMATTERS.keys()
+
+def getFormatter(short):
+    return FORMATTERS[short]
+
+
+class BaseFormatter(object):
+    config = None
+
+    def __init__(self, dest=None, options=None):
+        self.dest = dest
+        
+        if not self.dest:
+            self.dest = sys.stdout
+
+        self.options = options
+
+        if not options:
+            self.options = {}
+
+        self.reportTypes = []
+
+        for attr in self.__dict__:
+            if attr.startswith('report_'):
+                self.reportTypes.append(attr.split('_', 1)[1])
+
+    def write(self, data):
+        self.dest.write(data)
+        self.dest.flush()
+
+    def close(self):
+        self.dest.close()
+
+    def printReport(self, report):
+        reportPrinter = getattr(self, 'report_%s' % (report['type'],), None)
+
+        if reportPrinter:
+            reportPrinter(report)
+
+        else:
+            self.report_default(report)
+    
+    def report_default(self, report):
+        import pprint
+
+        preport = pprint.pformat(report)
+
+        self.write(''.join([preport, '\n']))
+        self.close()
+
+
+class PPrintFormatter(BaseFormatter):
+    name = "pprint"
+
+registerFormatter(PPrintFormatter)
+
+
+class PlainFormatter(BaseFormatter):
+    name = "plain"
+
+    def writeLine(self, fields, spacing=None):
+
+        if not spacing:
+            spacing = self.options.get('spacing', 16)
+
+        for f in fields:
+            self.write(str(f))
+            self.write(' '*(int(spacing) - len(str(f))))
+
+        self.write('\n')
+
+    def writeTable(self, report, fields, headings):
+        if self.options.has_key('fields'):
+            fields = self.options.get('fields', '').split(',')
+
+        self.writeLine((headings[f] for f in fields))
+
+        for record in report['records']:
+            self.writeLine((record[f] for f in fields))
+
+    def writeReport(self, report, name, fields, headings):
+        if self.options.has_key('fields'):
+            fields = self.options.get('fields', '').split(',')
+        
+        if name:
+            self.write('%s:\n' % (name,))
+
+        for f in fields:
+            self.write('  %s: %s\n' % (headings[f], report['data'][f]))
+
+    def report_principals(self, report):
+        fields = ('principalName', 'calendarCount', 'eventCount', 'todoCount',
+                  'quotaRoot', 'quotaUsed', 'quotaAvail')
+
+        headings = {
+            'principalName': 'Name',
+            'calendarCount': '# Calendars',
+            'eventCount': '# Events',
+            'todoCount': '# Todos',
+            'quotaRoot': 'Quota',
+            'quotaUsed': 'Used',
+            'quotaAvail': 'Available',
+            'disabled': 'Disaabled',
+            'quotaFree': 'Free %',
+            'calendarHome': 'Home',
+            }
+
+        self.writeTable(report, fields, headings)
+
+    report_user = report_group = report_resource = report_principals
+
+    def report_stats(self, report):
+        fields = ('accountCount', 'groupCount', 'calendarCount', 'eventCount', 
+                  'todoCount', 'diskUsage')
+
+        headings = {
+            'accountCount': '# Accounts',
+            'groupCount': '# Groups',
+            'calendarCount': '# Calendars',
+            'eventCount': '# Events',
+            'todoCount': '# Todos',
+            'diskUsage': 'Disk Usage',
+            }
+
+        self.writeReport(report, 'Statistics', fields, headings)
+
+    def report_logs(self, report):
+        self.write('Log Statistics:\n')
+
+        self.write('  Bytes Out: %s\n' % (report['data']['bytesOut'],))
+        self.write('  # Requests:\n')
+
+        for req, count in report['data']['requestCounts'].iteritems():
+            self.write('    %s: %s\n' % (req, count))
+
+        self.write('  User Agents:\n')
+
+        for ua, count in report['data']['userAgents'].iteritems():
+            self.write('    %s: %s\n' % (ua, count))
+
+registerFormatter(PlainFormatter)
+
+
+import csv
+
+class CsvFormatter(BaseFormatter):
+    name = "csv"
+
+    def writeList(self, fieldnames, l):
+        dw = csv.DictWriter(self.dest,
+                            **self.options)
+
+        dw.writerow(dict(zip(fieldnames,
+                             fieldnames)))
+
+        dw.writerows(l)
+
+    def report_principals(self, report):
+        if 'fieldnames' not in self.options:
+            self.options['fieldnames'] = [
+                'principalName',
+                'calendarHome',
+                'calendarCount',
+                'eventCount',
+                'todoCount',
+                'disabled',
+                'diskUsage',
+                'quotaRoot',
+                'quotaUsed',
+                'quotaAvail',
+                'quotaFree']
+            
+        self.writeDict(self.options['fieldnames'],
+                       report['records'])
+        
+    report_user = report_group = report_resource = report_principals
+
+    def report_stats(self, report):
+        if 'fieldnames' not in self.options:
+            self.options['fieldnames'] = report['data'].keys()
+            self.options['fieldnames'].sort()
+
+        self.writeList(self.options['fieldnames'],
+                       [report['data']])
+                
+    report_logs = report_stats
+
+registerFormatter(CsvFormatter)
+
+import plistlib
+
+class PlistFormatter(BaseFormatter):
+    name = "plist"
+
+    def report_principals(self, report):
+        plist = plistlib.Dict()
+
+        plist[report['type']] = list(report['records'])
+
+        plistlib.writePlist(plist, self.dest)
+
+    report_user = report_group = report_resource = report_principals
+
+    def report_stats(self, report):
+        plist = plistlib.Dict()
+        plist[report['type']] = report['data']
+
+        plistlib.writePlist(plist, self.dest)
+
+    report_logs = report_stats
+
+registerFormatter(PlistFormatter)

Deleted: CalendarServer/trunk/caladmin/logs.py
===================================================================
--- CalendarServer/branches/caladmin-tool-2/caladmin/logs.py	2006-12-01 22:28:39 UTC (rev 646)
+++ CalendarServer/trunk/caladmin/logs.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -1,164 +0,0 @@
-##
-# Copyright (c) 2006 Apple Computer, 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.
-#
-# DRI: David Reid, dreid at apple.com
-##
-"""
- Log Stats:
-  # Invitations sent per day/week/month
-  # bytes out (bytes in not provided in the current log format.)/
-  # requests
-  user agents
-
-"""
-
-import plistlib
-
-from caladmin import util
-
-statsTemplate = plistlib.Dict(
-    bytesOut=0, 
-    requestCounts=plistlib.Dict(
-        ), 
-    invitations=plistlib.Dict(
-        day=0, 
-        week=0, 
-        month=0, 
-        ),
-    userAgents=plistlib.Dict(),
-    )
-
-class Stats(object):
-    def __init__(self, fp):
-        self.fp = fp
-
-        if self.fp.exists():
-            self._data = plistlib.readPlist(self.fp.path)
-        else:
-            self._data = statsTemplate
-            self.save()
-
-    def getBytes(self):
-        return self._data.bytesOut
-
-    def addBytes(self, bytes):
-        self._data.bytesOut += bytes
-
-    def addRequest(self, request):
-        if request in self._data.requestCounts:
-            self._data.requestCounts[request] += 1
-        else:
-            self._data.requestCounts[request] = 1
-    
-    def getRequests(self):
-        return self._data.requestCounts
-
-    def addUserAgent(self, useragent):
-        if useragent in self._data.userAgents:
-            self._data.userAgents[useragent] += 1
-        else:
-            self._data.userAgents[useragent] = 1
-
-    def getUserAgents(self):
-        return self._data.userAgents
-
-    def save(self):
-        plistlib.writePlist(self._data, self.fp.path)
-
-NORMAL = 1
-INDATE = 2
-INSTRING = 3
-
-def parseCLFLine(line):
-    state = NORMAL
-    elements = []
-    
-    rest = []
-
-    for c in line:
-        if c == ' ':
-            if state == NORMAL:
-                elements.append(''.join(rest))
-                rest = []
-
-            elif state == INSTRING or state == INDATE:
-                rest.append(c)
-                    
-        elif c == '[':
-            if state != INSTRING:
-                state = INDATE
-                        
-        elif c == ']':
-            if state == INDATE:
-                state = NORMAL
-
-        elif c == '"':
-            if state == INSTRING:
-                state = NORMAL
-            else:
-                state = INSTRING
-        elif c == '\n':
-            if state == NORMAL:
-                elements.append(''.join(rest))
-                rest = []
-
-        else:
-            rest.append(c)
-
-    return elements
-
-                    
-class LogAction(object):
-    def __init__(self, config):
-        self.config = config
-
-        self.noOutput = self.config['nooutput']
-        self.readOnly = self.config['readonly']
-
-        self.logfile = self.config['logfile']
-        self.stats = Stats(self.config['statsfile'])
-
-    def run(self):
-
-        if not self.readOnly:
-            for line in self.logfile.open():
-                if (line.startswith('Log opened') or 
-                    line.startswith('Log closed')):
-                    continue
-                else:
-                    pline = parseCLFLine(line)
-                    
-                    self.stats.addBytes(int(pline[6]))
-                    self.stats.addRequest(pline[4].split(' ')[0])
-
-                    if len(pline) > 7:
-                        self.stats.addUserAgent(pline[8])
-
-            self.stats.save()    
-
-        if not self.noOutput:
-            report = {
-                'type': 'logs',
-                'data': {
-                    'bytesOut': util.prepareByteValue(self.config, 
-                                                      self.stats.getBytes()),
-                    'requestCounts': self.stats.getRequests(),
-                    'userAgents': self.stats.getUserAgents(),
-                    }
-                }
-
-            return report
-
-        return None

Copied: CalendarServer/trunk/caladmin/logs.py (from rev 646, CalendarServer/branches/caladmin-tool-2/caladmin/logs.py)
===================================================================
--- CalendarServer/trunk/caladmin/logs.py	                        (rev 0)
+++ CalendarServer/trunk/caladmin/logs.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -0,0 +1,164 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: David Reid, dreid at apple.com
+##
+"""
+ Log Stats:
+  # Invitations sent per day/week/month
+  # bytes out (bytes in not provided in the current log format.)/
+  # requests
+  user agents
+
+"""
+
+import plistlib
+
+from caladmin import util
+
+statsTemplate = plistlib.Dict(
+    bytesOut=0, 
+    requestCounts=plistlib.Dict(
+        ), 
+    invitations=plistlib.Dict(
+        day=0, 
+        week=0, 
+        month=0, 
+        ),
+    userAgents=plistlib.Dict(),
+    )
+
+class Stats(object):
+    def __init__(self, fp):
+        self.fp = fp
+
+        if self.fp.exists():
+            self._data = plistlib.readPlist(self.fp.path)
+        else:
+            self._data = statsTemplate
+            self.save()
+
+    def getBytes(self):
+        return self._data.bytesOut
+
+    def addBytes(self, bytes):
+        self._data.bytesOut += bytes
+
+    def addRequest(self, request):
+        if request in self._data.requestCounts:
+            self._data.requestCounts[request] += 1
+        else:
+            self._data.requestCounts[request] = 1
+    
+    def getRequests(self):
+        return self._data.requestCounts
+
+    def addUserAgent(self, useragent):
+        if useragent in self._data.userAgents:
+            self._data.userAgents[useragent] += 1
+        else:
+            self._data.userAgents[useragent] = 1
+
+    def getUserAgents(self):
+        return self._data.userAgents
+
+    def save(self):
+        plistlib.writePlist(self._data, self.fp.path)
+
+NORMAL = 1
+INDATE = 2
+INSTRING = 3
+
+def parseCLFLine(line):
+    state = NORMAL
+    elements = []
+    
+    rest = []
+
+    for c in line:
+        if c == ' ':
+            if state == NORMAL:
+                elements.append(''.join(rest))
+                rest = []
+
+            elif state == INSTRING or state == INDATE:
+                rest.append(c)
+                    
+        elif c == '[':
+            if state != INSTRING:
+                state = INDATE
+                        
+        elif c == ']':
+            if state == INDATE:
+                state = NORMAL
+
+        elif c == '"':
+            if state == INSTRING:
+                state = NORMAL
+            else:
+                state = INSTRING
+        elif c == '\n':
+            if state == NORMAL:
+                elements.append(''.join(rest))
+                rest = []
+
+        else:
+            rest.append(c)
+
+    return elements
+
+                    
+class LogAction(object):
+    def __init__(self, config):
+        self.config = config
+
+        self.noOutput = self.config['nooutput']
+        self.readOnly = self.config['readonly']
+
+        self.logfile = self.config['logfile']
+        self.stats = Stats(self.config['statsfile'])
+
+    def run(self):
+
+        if not self.readOnly:
+            for line in self.logfile.open():
+                if (line.startswith('Log opened') or 
+                    line.startswith('Log closed')):
+                    continue
+                else:
+                    pline = parseCLFLine(line)
+                    
+                    self.stats.addBytes(int(pline[6]))
+                    self.stats.addRequest(pline[4].split(' ')[0])
+
+                    if len(pline) > 7:
+                        self.stats.addUserAgent(pline[8])
+
+            self.stats.save()    
+
+        if not self.noOutput:
+            report = {
+                'type': 'logs',
+                'data': {
+                    'bytesOut': util.prepareByteValue(self.config, 
+                                                      self.stats.getBytes()),
+                    'requestCounts': self.stats.getRequests(),
+                    'userAgents': self.stats.getUserAgents(),
+                    }
+                }
+
+            return report
+
+        return None

Deleted: CalendarServer/trunk/caladmin/options.py
===================================================================
--- CalendarServer/branches/caladmin-tool-2/caladmin/options.py	2006-12-01 22:28:39 UTC (rev 646)
+++ CalendarServer/trunk/caladmin/options.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -1,187 +0,0 @@
-##
-# Copyright (c) 2006 Apple Computer, 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.
-#
-# DRI: David Reid, dreid at apple.com
-##
-
-""" "pluggable" subcommands for the caladmin script
-"""
-
-from twisted.python import usage
-
-COMMANDS = {}
-
-def registerCommand(command):
-    COMMANDS[command.name] = command
-
-def listCommands():
-    return COMMANDS.keys()
-
-def genSubCommandsDef():
-    sc = listCommands()
-    sc.sort()
-
-    for name in sc:
-        command = COMMANDS[name]
-        yield [command.name, command.shortcut, command, command.help]
-
-
-from twisted.python import reflect
-
-class SubCommand(usage.Options):
-    name = None
-    shortcut = None
-    help = "FIXME"
-    action = None
-
-    params = ()
-
-    def parseArgs(self, *rest):
-        self.params += rest
-
-    def postOptions(self):
-        
-        report = reflect.namedAny(self.action)(self).run()
-        if report:
-            self.parent.formatter.config = self
-            self.parent.formatter.printReport(report)
-
-
-PARAM_HUMAN = ['human', 'h', 'Display byte values in a human readable form.']
-PARAM_MEGA = ['megabytes', 'm', 'Display byte values in megabytes']
-PARAM_KILO = ['kilobytes', 'k', 'Display byte values in kilobytes']
-PARAM_GIGA = ['gigabytes', 'g', 'Display byte values in gigabytes']
-
-
-class PurgeOptions(SubCommand):
-    name = 'purge'
-    help = ('Keep your store from becoming unnecessarily large by purging '
-            'old events.')
-    action = 'caladmin.purge.PurgeAction'
-
-    optParameters = [
-        ['days', 'n', 30, 'Age threshold for purging events.'],
-        ]
-
-registerCommand(PurgeOptions)
-
-
-class StatsOptions(SubCommand):
-    name = 'stats'
-    help = ('Overall usage statistics.')
-    action = 'caladmin.stats.StatsAction'
-
-    optFlags = [
-        PARAM_HUMAN,
-        PARAM_KILO,
-        PARAM_MEGA,
-        PARAM_GIGA,
-        ]
-
-registerCommand(StatsOptions)
-
-
-from twisted.python import filepath
-from twistedcaldav.caldavd import DEFAULTS
-
-class LogOptions(SubCommand):
-    name = 'logs'
-    help = ('Gather and report useful information from the logfiles.')
-    action = 'caladmin.logs.LogAction'
-
-    optFlags = [
-        ['nooutput', 'n', 'Do not output anything to stdout'],
-        ['readonly', 'r', 'Just read the current stats in the statistics file'],
-        PARAM_HUMAN,
-        PARAM_KILO,
-        PARAM_MEGA,
-        PARAM_GIGA,
-        ]
-    
-    def __init__(self):
-        SubCommand.__init__(self)
-
-        self['logfile'] = None
-        self['statsfile'] = None
-
-    def opt_logfile(self, path):
-        """Path to input logfile
-        """
-
-        self['logfile'] = path
-
-    def opt_statsfile(self, path):
-        """Path to destination statistics plist
-        """
-        self['statsfile'] = path
-
-    def postOptions(self):
-        if not self['logfile']:
-            self['logfile'] = filepath.FilePath(
-                self.parent.config['ServerLogFile'])
-        else:
-            self['logfile'] = filepath.FilePath(self['logfile'])
-
-        if not self['statsfile']:
-            self['statsfile'] = filepath.FilePath(
-                self.parent.config['ServerStatsFile'])
-        else:
-            self['statsfile'] = filepath.FilePath(self['statsfile'])
-
-        SubCommand.postOptions(self)
-
-registerCommand(LogOptions)
-
-
-class PrincipalOptions(SubCommand):
-    name = None
-    help = ("Gather statistics and act on %s")
-    action = 'caladmin.principals.PrincipalAction'
-
-    optFlags = [
-        ['list', '1', 'List principal names'],
-        ['disabled', 'd', 'List disabled principals'],
-        PARAM_HUMAN,
-        PARAM_KILO,
-        PARAM_MEGA,
-        PARAM_GIGA,
-        ]
-
-    def postOptions(self):
-        report = reflect.namedAny(self.action)(self, self.name).run()
-        self.parent.formatter.printReport(report)
-
-
-class UserOptions(PrincipalOptions):
-    name = "user"
-    help = PrincipalOptions.help % (name,)
-
-registerCommand(UserOptions)
-
-
-class GroupOptions(PrincipalOptions):
-    name = "group"
-    help = PrincipalOptions.help % (name,)
-
-registerCommand(GroupOptions)
-
-
-class ResourceOptions(PrincipalOptions):
-    name = "resource"
-    help = PrincipalOptions.help % (name,)
-
-registerCommand(ResourceOptions)
-
-    

Copied: CalendarServer/trunk/caladmin/options.py (from rev 646, CalendarServer/branches/caladmin-tool-2/caladmin/options.py)
===================================================================
--- CalendarServer/trunk/caladmin/options.py	                        (rev 0)
+++ CalendarServer/trunk/caladmin/options.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -0,0 +1,187 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+""" "pluggable" subcommands for the caladmin script
+"""
+
+from twisted.python import usage
+
+COMMANDS = {}
+
+def registerCommand(command):
+    COMMANDS[command.name] = command
+
+def listCommands():
+    return COMMANDS.keys()
+
+def genSubCommandsDef():
+    sc = listCommands()
+    sc.sort()
+
+    for name in sc:
+        command = COMMANDS[name]
+        yield [command.name, command.shortcut, command, command.help]
+
+
+from twisted.python import reflect
+
+class SubCommand(usage.Options):
+    name = None
+    shortcut = None
+    help = "FIXME"
+    action = None
+
+    params = ()
+
+    def parseArgs(self, *rest):
+        self.params += rest
+
+    def postOptions(self):
+        
+        report = reflect.namedAny(self.action)(self).run()
+        if report:
+            self.parent.formatter.config = self
+            self.parent.formatter.printReport(report)
+
+
+PARAM_HUMAN = ['human', 'h', 'Display byte values in a human readable form.']
+PARAM_MEGA = ['megabytes', 'm', 'Display byte values in megabytes']
+PARAM_KILO = ['kilobytes', 'k', 'Display byte values in kilobytes']
+PARAM_GIGA = ['gigabytes', 'g', 'Display byte values in gigabytes']
+
+
+class PurgeOptions(SubCommand):
+    name = 'purge'
+    help = ('Keep your store from becoming unnecessarily large by purging '
+            'old events.')
+    action = 'caladmin.purge.PurgeAction'
+
+    optParameters = [
+        ['days', 'n', 30, 'Age threshold for purging events.'],
+        ]
+
+registerCommand(PurgeOptions)
+
+
+class StatsOptions(SubCommand):
+    name = 'stats'
+    help = ('Overall usage statistics.')
+    action = 'caladmin.stats.StatsAction'
+
+    optFlags = [
+        PARAM_HUMAN,
+        PARAM_KILO,
+        PARAM_MEGA,
+        PARAM_GIGA,
+        ]
+
+registerCommand(StatsOptions)
+
+
+from twisted.python import filepath
+from twistedcaldav.caldavd import DEFAULTS
+
+class LogOptions(SubCommand):
+    name = 'logs'
+    help = ('Gather and report useful information from the logfiles.')
+    action = 'caladmin.logs.LogAction'
+
+    optFlags = [
+        ['nooutput', 'n', 'Do not output anything to stdout'],
+        ['readonly', 'r', 'Just read the current stats in the statistics file'],
+        PARAM_HUMAN,
+        PARAM_KILO,
+        PARAM_MEGA,
+        PARAM_GIGA,
+        ]
+    
+    def __init__(self):
+        SubCommand.__init__(self)
+
+        self['logfile'] = None
+        self['statsfile'] = None
+
+    def opt_logfile(self, path):
+        """Path to input logfile
+        """
+
+        self['logfile'] = path
+
+    def opt_statsfile(self, path):
+        """Path to destination statistics plist
+        """
+        self['statsfile'] = path
+
+    def postOptions(self):
+        if not self['logfile']:
+            self['logfile'] = filepath.FilePath(
+                self.parent.config['ServerLogFile'])
+        else:
+            self['logfile'] = filepath.FilePath(self['logfile'])
+
+        if not self['statsfile']:
+            self['statsfile'] = filepath.FilePath(
+                self.parent.config['ServerStatsFile'])
+        else:
+            self['statsfile'] = filepath.FilePath(self['statsfile'])
+
+        SubCommand.postOptions(self)
+
+registerCommand(LogOptions)
+
+
+class PrincipalOptions(SubCommand):
+    name = None
+    help = ("Gather statistics and act on %s")
+    action = 'caladmin.principals.PrincipalAction'
+
+    optFlags = [
+        ['list', '1', 'List principal names'],
+        ['disabled', 'd', 'List disabled principals'],
+        PARAM_HUMAN,
+        PARAM_KILO,
+        PARAM_MEGA,
+        PARAM_GIGA,
+        ]
+
+    def postOptions(self):
+        report = reflect.namedAny(self.action)(self, self.name).run()
+        self.parent.formatter.printReport(report)
+
+
+class UserOptions(PrincipalOptions):
+    name = "user"
+    help = PrincipalOptions.help % (name,)
+
+registerCommand(UserOptions)
+
+
+class GroupOptions(PrincipalOptions):
+    name = "group"
+    help = PrincipalOptions.help % (name,)
+
+registerCommand(GroupOptions)
+
+
+class ResourceOptions(PrincipalOptions):
+    name = "resource"
+    help = PrincipalOptions.help % (name,)
+
+registerCommand(ResourceOptions)
+
+    

Deleted: CalendarServer/trunk/caladmin/principals.py
===================================================================
--- CalendarServer/branches/caladmin-tool-2/caladmin/principals.py	2006-12-01 22:28:39 UTC (rev 646)
+++ CalendarServer/trunk/caladmin/principals.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -1,89 +0,0 @@
-##
-# Copyright (c) 2006 Apple Computer, 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.
-#
-# DRI: David Reid, dreid at apple.com
-##
-"""
- Account Stats: 
-  # of calendars
-  # of events
-  # storage used (including things that don't count against quota?)
-  Last login?
-"""
-
-from caladmin import util
-
-class PrincipalAction(object):
-    def __init__(self, config, type):
-        self.config = config
-        self.type = type
-
-        self.formatter = self.config.parent.formatter
-        self.root = self.config.parent.root
-        self.calendarCollection = self.config.parent.calendarCollection
-        self.principalCollection = self.config.parent.principalCollection
-    
-    def run(self):
-        report = {'type': self.type,
-                  'records': []}
-
-        if not self.config.params:
-            principals = util.getPrincipalList(self.principalCollection,
-                                               self.type,
-                                               disabled=self.config['disabled'])
-
-        else:
-            principals = []
-            for p in self.config.params:
-                p = self.principalCollection.child(self.type).child(p)
-
-                if p.exists():
-                    if self.config['disabled']:
-                        if util.isPrincipalDisabled(p):
-                            principals.append(p)
-
-                    else:
-                        principals.append(p)
-
-        def _getRecords():
-            for p in principals:
-                precord = {}
-                
-                pcal = self.calendarCollection.child(
-                    self.type
-                    ).child(p.basename())
-            
-                precord['principalName'] = p.basename()
-                
-                precord['calendarHome'] = pcal.path
-
-                precord.update(
-                    util.getQuotaStatsForPrincipal(
-                        self.config,
-                        pcal,
-                        self.config.parent.config['UserQuotaBytes']))
-
-                precord.update(
-                    util.getCalendarDataCounts(pcal))
-
-                precord['diskUsage'] = util.getDiskUsage(self.config, pcal)
-                
-                precord['disabled'] = util.isPrincipalDisabled(p)
-                
-                yield precord
-
-        report['records'] = _getRecords()
-        
-        return report

Copied: CalendarServer/trunk/caladmin/principals.py (from rev 646, CalendarServer/branches/caladmin-tool-2/caladmin/principals.py)
===================================================================
--- CalendarServer/trunk/caladmin/principals.py	                        (rev 0)
+++ CalendarServer/trunk/caladmin/principals.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -0,0 +1,89 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: David Reid, dreid at apple.com
+##
+"""
+ Account Stats: 
+  # of calendars
+  # of events
+  # storage used (including things that don't count against quota?)
+  Last login?
+"""
+
+from caladmin import util
+
+class PrincipalAction(object):
+    def __init__(self, config, type):
+        self.config = config
+        self.type = type
+
+        self.formatter = self.config.parent.formatter
+        self.root = self.config.parent.root
+        self.calendarCollection = self.config.parent.calendarCollection
+        self.principalCollection = self.config.parent.principalCollection
+    
+    def run(self):
+        report = {'type': self.type,
+                  'records': []}
+
+        if not self.config.params:
+            principals = util.getPrincipalList(self.principalCollection,
+                                               self.type,
+                                               disabled=self.config['disabled'])
+
+        else:
+            principals = []
+            for p in self.config.params:
+                p = self.principalCollection.child(self.type).child(p)
+
+                if p.exists():
+                    if self.config['disabled']:
+                        if util.isPrincipalDisabled(p):
+                            principals.append(p)
+
+                    else:
+                        principals.append(p)
+
+        def _getRecords():
+            for p in principals:
+                precord = {}
+                
+                pcal = self.calendarCollection.child(
+                    self.type
+                    ).child(p.basename())
+            
+                precord['principalName'] = p.basename()
+                
+                precord['calendarHome'] = pcal.path
+
+                precord.update(
+                    util.getQuotaStatsForPrincipal(
+                        self.config,
+                        pcal,
+                        self.config.parent.config['UserQuotaBytes']))
+
+                precord.update(
+                    util.getCalendarDataCounts(pcal))
+
+                precord['diskUsage'] = util.getDiskUsage(self.config, pcal)
+                
+                precord['disabled'] = util.isPrincipalDisabled(p)
+                
+                yield precord
+
+        report['records'] = _getRecords()
+        
+        return report

Deleted: CalendarServer/trunk/caladmin/purge.py
===================================================================
--- CalendarServer/branches/caladmin-tool-2/caladmin/purge.py	2006-12-01 22:28:39 UTC (rev 646)
+++ CalendarServer/trunk/caladmin/purge.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -1,99 +0,0 @@
-##
-# Copyright (c) 2006 Apple Computer, 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.
-#
-# DRI: David Reid, dreid at apple.com
-##
-
-import os
-
-import datetime, dateutil.tz
-
-def purgeEvents(collection, purgeDate):
-    """
-    Recursively purge all events older than purgeDate.
-
-    for VTODO: 
-     * if completed
-       * purge if it's dueDate is older than purgeDate.
-
-    for V*:
-     * purge if endDate is older than purgeDate
-     """
-
-    from twistedcaldav import ical
-
-    files = []
-    directories = []
-
-    for child in collection.children():
-        if child.basename() == '.db.sqlite':
-            continue
-
-        if child.isdir():
-            directories.append(child)
-
-        elif child.isfile():
-            files.append(child)
-
-    for directory in directories:
-        purgeEvents(directory, purgeDate)
-
-    for f in files:
-        try:
-            component = ical.Component.fromStream(f.open())
-        except ValueError:
-            # Not a calendar file?
-            continue
-
-        endDate = component.mainComponent().getEndDateUTC()
-        
-        if component.resourceType() == 'VTODO':
-            if component.mainComponent().hasProperty('COMPLETED'):
-                endDate = component.mainComponent().getDueDateUTC()
-            else:
-                endDate = None
-
-        if isinstance(endDate, datetime.datetime):
-            endDate = endDate.date()
-
-        if endDate:
-            if purgeDate > endDate:
-                print "Purging %s, %s, %s" % (component.resourceType(), 
-                                               component.resourceUID(), 
-                                               endDate.isoformat())
-                f.remove()
-
-
-class PurgeAction(object):
-    def __init__(self, config):
-        self.config = config
-        self.calendarCollection = config.parent.calendarCollection
-
-    def run(self):
-        if self.config.params:
-            collections = [self.calendarCollection.child(p) 
-                           for p in self.config.params]
-            
-        else:
-            collections = []
-            
-            for type in self.calendarCollection.children():
-                collections.extend(type.children())
-                    
-        purgeDate = datetime.date.today()
-        purgeDate = purgeDate - datetime.timedelta(int(self.config['days']))
-
-        for collection in collections:
-            purgeEvents(collection, purgeDate)

Copied: CalendarServer/trunk/caladmin/purge.py (from rev 646, CalendarServer/branches/caladmin-tool-2/caladmin/purge.py)
===================================================================
--- CalendarServer/trunk/caladmin/purge.py	                        (rev 0)
+++ CalendarServer/trunk/caladmin/purge.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -0,0 +1,99 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+import os
+
+import datetime, dateutil.tz
+
+def purgeEvents(collection, purgeDate):
+    """
+    Recursively purge all events older than purgeDate.
+
+    for VTODO: 
+     * if completed
+       * purge if it's dueDate is older than purgeDate.
+
+    for V*:
+     * purge if endDate is older than purgeDate
+     """
+
+    from twistedcaldav import ical
+
+    files = []
+    directories = []
+
+    for child in collection.children():
+        if child.basename() == '.db.sqlite':
+            continue
+
+        if child.isdir():
+            directories.append(child)
+
+        elif child.isfile():
+            files.append(child)
+
+    for directory in directories:
+        purgeEvents(directory, purgeDate)
+
+    for f in files:
+        try:
+            component = ical.Component.fromStream(f.open())
+        except ValueError:
+            # Not a calendar file?
+            continue
+
+        endDate = component.mainComponent().getEndDateUTC()
+        
+        if component.resourceType() == 'VTODO':
+            if component.mainComponent().hasProperty('COMPLETED'):
+                endDate = component.mainComponent().getDueDateUTC()
+            else:
+                endDate = None
+
+        if isinstance(endDate, datetime.datetime):
+            endDate = endDate.date()
+
+        if endDate:
+            if purgeDate > endDate:
+                print "Purging %s, %s, %s" % (component.resourceType(), 
+                                               component.resourceUID(), 
+                                               endDate.isoformat())
+                f.remove()
+
+
+class PurgeAction(object):
+    def __init__(self, config):
+        self.config = config
+        self.calendarCollection = config.parent.calendarCollection
+
+    def run(self):
+        if self.config.params:
+            collections = [self.calendarCollection.child(p) 
+                           for p in self.config.params]
+            
+        else:
+            collections = []
+            
+            for type in self.calendarCollection.children():
+                collections.extend(type.children())
+                    
+        purgeDate = datetime.date.today()
+        purgeDate = purgeDate - datetime.timedelta(int(self.config['days']))
+
+        for collection in collections:
+            purgeEvents(collection, purgeDate)

Deleted: CalendarServer/trunk/caladmin/script.py
===================================================================
--- CalendarServer/branches/caladmin-tool-2/caladmin/script.py	2006-12-01 22:28:39 UTC (rev 646)
+++ CalendarServer/trunk/caladmin/script.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -1,142 +0,0 @@
-##
-# Copyright (c) 2006 Apple Computer, 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.
-#
-# DRI: David Reid, dreid at apple.com
-##
-
-"""
-Examples:
-
-  caladmin users
-
-  caladmin purge
-
-  caladmin backup
-
-  caladmin restore
-"""
-
-import sys, os
-
-from twisted.python import usage
-from twisted.python import filepath
-
-from plistlib import readPlist
-
-from caladmin import options
-from caladmin import formatters
-
-from twistedcaldav.caldavd import DEFAULTS, caldavd
-
-class AdminOptions(usage.Options):
-    recursing = 0
-    params = ()
-
-    optParameters = [
-        ['config', 'c', caldavd().plistfile, "Path to the caldavd.plist"],
-        ['format', 'f', 'plain', ("Select an appropriate output formatter: "
-                                  "%s" % (formatters.listFormatters(),))]
-        ]
-
-    def __init__(self):
-        usage.Options.__init__(self)
-
-        self.config = None
-        self.format_options = {}
-
-    def opt_option(self, option):
-        if '=' in option:
-            k,v = option.split('=', 1)
-        
-            self.format_options[k] = v
-        else:
-            self.format_options[option] = True
-
-    opt_o = opt_option
-
-    def parseArgs(self, *rest):
-        self.params += rest
-
-    def parseOptions(self, opts=None):
-        if not opts:
-            opts = ['--help']
-
-        if opts == ['--help']:
-            self.subCommands = options.genSubCommandsDef()
-
-        usage.Options.parseOptions(self, opts)
-    
-    def postOptions(self):
-        if self.recursing:
-            return
-
-        if self['config']:
-            self['config'] = os.path.abspath(self['config'])
-            try:
-                self.config = readPlist(self['config'])
-            except IOError, err:
-                sys.stderr.write(("Could not open configuration file: %s (%s)\n"
-                                  ) % (err.filename,
-                                       err.strerror))
-                sys.stderr.flush()
-
-                self.config = DEFAULTS
-
-        self.root = filepath.FilePath(self.config['DocumentRoot'])
-        self.calendarCollection = self.root.child('calendars')
-        self.principalCollection = self.root.child('principals')
-
-        lf = formatters.listFormatters()
-        lf.sort()
-
-        if self['format'] in lf:
-            self.formatter = formatters.getFormatter(self['format'])
-            self.formatter = self.formatter(options=self.format_options)
-        else:
-            raise usage.UsageError("Please specify a valid formatter: %s" % (
-                    ', '.join(lf)))
-
-        sc = options.listCommands()
-        sc.sort()
-
-        self.subCommands = options.genSubCommandsDef()
-
-        self.recursing = 1
-
-        self.parseOptions(self.params)
-
-        if self.subCommand not in sc:
-            raise usage.UsageError("Please select one of: %s" % (
-                    ', '.join(sc)))
-
-    
-def run():
-    config = AdminOptions()
-
-    try:
-        config.parseOptions(sys.argv[1:])
-
-    except usage.UsageError, ue:
-        print config
-        if len(sys.argv) > 1:
-            cmd = sys.argv[1]
-        else:
-            cmd = sys.argv[0]
-
-        print "%s: %s" % (cmd, ue)
-
-
-    except KeyboardInterrupt:
-        sys.exit(1)

Copied: CalendarServer/trunk/caladmin/script.py (from rev 646, CalendarServer/branches/caladmin-tool-2/caladmin/script.py)
===================================================================
--- CalendarServer/trunk/caladmin/script.py	                        (rev 0)
+++ CalendarServer/trunk/caladmin/script.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -0,0 +1,142 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+"""
+Examples:
+
+  caladmin users
+
+  caladmin purge
+
+  caladmin backup
+
+  caladmin restore
+"""
+
+import sys, os
+
+from twisted.python import usage
+from twisted.python import filepath
+
+from plistlib import readPlist
+
+from caladmin import options
+from caladmin import formatters
+
+from twistedcaldav.caldavd import DEFAULTS, caldavd
+
+class AdminOptions(usage.Options):
+    recursing = 0
+    params = ()
+
+    optParameters = [
+        ['config', 'c', caldavd().plistfile, "Path to the caldavd.plist"],
+        ['format', 'f', 'plain', ("Select an appropriate output formatter: "
+                                  "%s" % (formatters.listFormatters(),))]
+        ]
+
+    def __init__(self):
+        usage.Options.__init__(self)
+
+        self.config = None
+        self.format_options = {}
+
+    def opt_option(self, option):
+        if '=' in option:
+            k,v = option.split('=', 1)
+        
+            self.format_options[k] = v
+        else:
+            self.format_options[option] = True
+
+    opt_o = opt_option
+
+    def parseArgs(self, *rest):
+        self.params += rest
+
+    def parseOptions(self, opts=None):
+        if not opts:
+            opts = ['--help']
+
+        if opts == ['--help']:
+            self.subCommands = options.genSubCommandsDef()
+
+        usage.Options.parseOptions(self, opts)
+    
+    def postOptions(self):
+        if self.recursing:
+            return
+
+        if self['config']:
+            self['config'] = os.path.abspath(self['config'])
+            try:
+                self.config = readPlist(self['config'])
+            except IOError, err:
+                sys.stderr.write(("Could not open configuration file: %s (%s)\n"
+                                  ) % (err.filename,
+                                       err.strerror))
+                sys.stderr.flush()
+
+                self.config = DEFAULTS
+
+        self.root = filepath.FilePath(self.config['DocumentRoot'])
+        self.calendarCollection = self.root.child('calendars')
+        self.principalCollection = self.root.child('principals')
+
+        lf = formatters.listFormatters()
+        lf.sort()
+
+        if self['format'] in lf:
+            self.formatter = formatters.getFormatter(self['format'])
+            self.formatter = self.formatter(options=self.format_options)
+        else:
+            raise usage.UsageError("Please specify a valid formatter: %s" % (
+                    ', '.join(lf)))
+
+        sc = options.listCommands()
+        sc.sort()
+
+        self.subCommands = options.genSubCommandsDef()
+
+        self.recursing = 1
+
+        self.parseOptions(self.params)
+
+        if self.subCommand not in sc:
+            raise usage.UsageError("Please select one of: %s" % (
+                    ', '.join(sc)))
+
+    
+def run():
+    config = AdminOptions()
+
+    try:
+        config.parseOptions(sys.argv[1:])
+
+    except usage.UsageError, ue:
+        print config
+        if len(sys.argv) > 1:
+            cmd = sys.argv[1]
+        else:
+            cmd = sys.argv[0]
+
+        print "%s: %s" % (cmd, ue)
+
+
+    except KeyboardInterrupt:
+        sys.exit(1)

Deleted: CalendarServer/trunk/caladmin/stats.py
===================================================================
--- CalendarServer/branches/caladmin-tool-2/caladmin/stats.py	2006-12-01 22:28:39 UTC (rev 646)
+++ CalendarServer/trunk/caladmin/stats.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -1,93 +0,0 @@
-##
-# Copyright (c) 2006 Apple Computer, 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.
-#
-# DRI: David Reid, dreid at apple.com
-##
-
-"""
-Statisitcs Types:
-
- Overall Stats:
-  # of accounts
-  # of calendars
-  # of events
-
-"""
-import os
-import xattr
-import commands
-
-from twisted.web import microdom
-
-from twistedcaldav import ical
-
-from caladmin import util        
-
-class StatsAction(object):
-    def __init__(self, config):
-        self.config = config
-        self.formatter = self.config.parent.formatter
-        self.root = self.config.parent.root
-        self.calendarCollection = self.config.parent.calendarCollection
-        self.principalCollection = self.config.parent.principalCollection
-        
-        self.calCount = 0
-        self.eventCount = 0
-        self.todoCount = 0
-
-        self.gatherers = [
-            self.getAccountCount,
-            self.getGroupCount,
-            self.getResourceCount,
-            self.getDiskUsage]
-
-    def getDiskUsage(self):
-        return ("diskUsage", 
-                util.getDiskUsage(self.config, self.root))
-
-    def getAccountCount(self):
-        return ("accountCount", 
-                len(util.getPrincipalList(
-                    self.principalCollection,
-                    'user')))
-
-    def getGroupCount(self):
-        return ("groupCount", 
-                len(util.getPrincipalList(
-                    self.principalCollection,
-                    'group')))
-
-    def getResourceCount(self):
-        return ("resourceCount", 
-                len(util.getPrincipalList(
-                    self.principalCollection,
-                    'resource')))
-
-    def run(self):
-        assert self.root.exists()
-        stats = []
-
-        report = {'type': 'stats',
-                  'data': {}}
-
-        report['data'].update(
-            util.getCalendarDataCounts(
-                self.calendarCollection))
-
-        for gatherer in self.gatherers:
-            stat, value = gatherer()
-            report['data'][stat] = value
-
-        return report

Copied: CalendarServer/trunk/caladmin/stats.py (from rev 646, CalendarServer/branches/caladmin-tool-2/caladmin/stats.py)
===================================================================
--- CalendarServer/trunk/caladmin/stats.py	                        (rev 0)
+++ CalendarServer/trunk/caladmin/stats.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -0,0 +1,93 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+"""
+Statisitcs Types:
+
+ Overall Stats:
+  # of accounts
+  # of calendars
+  # of events
+
+"""
+import os
+import xattr
+import commands
+
+from twisted.web import microdom
+
+from twistedcaldav import ical
+
+from caladmin import util        
+
+class StatsAction(object):
+    def __init__(self, config):
+        self.config = config
+        self.formatter = self.config.parent.formatter
+        self.root = self.config.parent.root
+        self.calendarCollection = self.config.parent.calendarCollection
+        self.principalCollection = self.config.parent.principalCollection
+        
+        self.calCount = 0
+        self.eventCount = 0
+        self.todoCount = 0
+
+        self.gatherers = [
+            self.getAccountCount,
+            self.getGroupCount,
+            self.getResourceCount,
+            self.getDiskUsage]
+
+    def getDiskUsage(self):
+        return ("diskUsage", 
+                util.getDiskUsage(self.config, self.root))
+
+    def getAccountCount(self):
+        return ("accountCount", 
+                len(util.getPrincipalList(
+                    self.principalCollection,
+                    'user')))
+
+    def getGroupCount(self):
+        return ("groupCount", 
+                len(util.getPrincipalList(
+                    self.principalCollection,
+                    'group')))
+
+    def getResourceCount(self):
+        return ("resourceCount", 
+                len(util.getPrincipalList(
+                    self.principalCollection,
+                    'resource')))
+
+    def run(self):
+        assert self.root.exists()
+        stats = []
+
+        report = {'type': 'stats',
+                  'data': {}}
+
+        report['data'].update(
+            util.getCalendarDataCounts(
+                self.calendarCollection))
+
+        for gatherer in self.gatherers:
+            stat, value = gatherer()
+            report['data'][stat] = value
+
+        return report

Deleted: CalendarServer/trunk/caladmin/util.py
===================================================================
--- CalendarServer/branches/caladmin-tool-2/caladmin/util.py	2006-12-01 22:28:39 UTC (rev 646)
+++ CalendarServer/trunk/caladmin/util.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -1,195 +0,0 @@
-##
-# Copyright (c) 2006 Apple Computer, 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.
-#
-# DRI: David Reid, dreid at apple.com
-##
-
-import xattr
-
-import commands
-
-from twisted.web import microdom
-
-from twistedcaldav import ical
- 
-def prepareByteValue(config, value):
-    if config.get('human', None):
-        KB = value/1024.0
-        if KB < 1:
-            return '%d' % (value,)
-
-        MB = KB/1024.0
-        if MB < 1:
-            return '%5.2fKB' % (KB,)
-
-        GB = MB/1024.0
-        if GB < 1:
-            return '%5.2fMB' % (MB,)
-
-        return '%5.2fGB' % (GB,)
-
-    elif config.get('gigabytes', None):
-        G = value/1024.0/1024.0/1024.0
-
-        return '%5.2fGB' % (G,)
-
-    elif config.get('megabytes', None):
-        M = value/1024.0/1024.0
-
-        return '%5.2fMB' % (M,)
-
-    elif config.get('kilobytes', None):
-        K = value/1024.0
-        return '%5.2fKB' % (K,)
-
-    return value
-
-
-def getPrincipalList(principalCollection, type, disabled=False):
-    typeRoot = principalCollection.child(type)
-    assert typeRoot.exists(), "Does not exist: %s" % typeRoot.path 
-    
-    pl = []
-    
-    for child in typeRoot.listdir():
-        if child not in ['.db.sqlite']:
-            p = typeRoot.child(child)
-
-            if disabled:
-                if isPrincipalDisabled(p):
-                    pl.append(p)
-            else:
-                pl.append(p)
-
-    return pl
-
-
-def getDiskUsage(config, fp):
-    status, output = commands.getstatusoutput(
-        ' '.join(['/usr/bin/du', '-s', fp.path]))
-    
-    if status != 0:
-        return 0
-
-    return prepareByteValue(config, int(output.split()[0]))
-
-
-def getResourceType(fp):
-    rt = 'WebDAV:{DAV:}resourcetype'
-    x = xattr.xattr(fp.path)
-    if not x.has_key(rt):
-        return None
-    
-    collection = False
-
-    type = None
-
-    dom = microdom.parseString(x[rt])
-    rt = microdom.getElementsByTagName(dom, 'resourcetype')
-
-    for child in rt[0].childNodes:
-        if child.tagName == 'collection':
-            collection = True
-        else:
-            type = child.tagName
-
-    return (collection, type)
-
-
-def getCalendarDataCounts(calendarCollection):
-    calCount = 0
-    eventCount = 0
-    todoCount = 0
-
-    for child in calendarCollection.walk():
-        if child.isdir():
-            if getResourceType(child) == (True, 'calendar'):
-                calCount += 1
-
-        elif child.isfile():
-            try:
-                component = ical.Component.fromStream(child.open())
-            except ValueError:
-                # not a calendar file
-                continue
-            
-            if component.resourceType() == 'VEVENT':
-                eventCount += 1
-                
-            elif component.resourceType() == 'VTODO':
-                todoCount += 1
-
-    return {'calendarCount': calCount, 
-            'eventCount': eventCount,
-            'todoCount': todoCount}
-
-
-def isPrincipalDisabled(principal):
-    return False
-
-
-from twisted.web2.dav.resource import TwistedQuotaRootProperty, TwistedQuotaUsedProperty
-
-quotaRoot = "WebDAV:" + TwistedQuotaRootProperty.sname().replace("/", "%2F")
-quotaUsed = "WebDAV:" + TwistedQuotaUsedProperty.sname().replace("/", "%2F")
-
-def getQuotaRoot(fp):
-    x = xattr.xattr(fp.path)
-    if not x.has_key(quotaRoot):
-        return None
-
-    dom = microdom.parseString(x[quotaRoot])
-
-    qr = microdom.getElementsByTagName(dom, 'quota-root')[0]
-
-    return int(qr.firstChild().value)
-
-
-def getQuotaUsed(fp):
-    x = xattr.xattr(fp.path)
-    if not x.has_key(quotaUsed):
-        return None
-
-    dom = microdom.parseString(x[quotaUsed])
-
-    qu = microdom.getElementsByTagName(dom, 'quota-used')[0]
-
-    return int(qu.firstChild().value)
-
-
-def getQuotaStatsForPrincipal(config, principal, defaultQuota=None, depth=2):
-    quotaRoot = principal
-
-    principalQuota = getQuotaRoot(quotaRoot)
-
-    while not principalQuota and depth > 0:
-        depth -= 1
-        quotaRoot = quotaRoot.parent()
-        principalQuota = getQuotaRoot(quotaRoot)
-
-    if not principalQuota:
-        principalQuota = defaultQuota
-
-    principalUsed = getQuotaUsed(principal)
-    if not principalUsed:
-        principalUsed = 0
-        
-    principalAvail = principalQuota - principalUsed
-    principalFree = (float(principalAvail)/principalQuota)*100
-
-    return {'quotaRoot': prepareByteValue(config, principalQuota), 
-            'quotaUsed': prepareByteValue(config, principalUsed),
-            'quotaAvail': prepareByteValue(config, principalAvail),
-            'quotaFree': principalFree}

Copied: CalendarServer/trunk/caladmin/util.py (from rev 646, CalendarServer/branches/caladmin-tool-2/caladmin/util.py)
===================================================================
--- CalendarServer/trunk/caladmin/util.py	                        (rev 0)
+++ CalendarServer/trunk/caladmin/util.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -0,0 +1,195 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+import xattr
+
+import commands
+
+from twisted.web import microdom
+
+from twistedcaldav import ical
+ 
+def prepareByteValue(config, value):
+    if config.get('human', None):
+        KB = value/1024.0
+        if KB < 1:
+            return '%d' % (value,)
+
+        MB = KB/1024.0
+        if MB < 1:
+            return '%5.2fKB' % (KB,)
+
+        GB = MB/1024.0
+        if GB < 1:
+            return '%5.2fMB' % (MB,)
+
+        return '%5.2fGB' % (GB,)
+
+    elif config.get('gigabytes', None):
+        G = value/1024.0/1024.0/1024.0
+
+        return '%5.2fGB' % (G,)
+
+    elif config.get('megabytes', None):
+        M = value/1024.0/1024.0
+
+        return '%5.2fMB' % (M,)
+
+    elif config.get('kilobytes', None):
+        K = value/1024.0
+        return '%5.2fKB' % (K,)
+
+    return value
+
+
+def getPrincipalList(principalCollection, type, disabled=False):
+    typeRoot = principalCollection.child(type)
+    assert typeRoot.exists(), "Does not exist: %s" % typeRoot.path 
+    
+    pl = []
+    
+    for child in typeRoot.listdir():
+        if child not in ['.db.sqlite']:
+            p = typeRoot.child(child)
+
+            if disabled:
+                if isPrincipalDisabled(p):
+                    pl.append(p)
+            else:
+                pl.append(p)
+
+    return pl
+
+
+def getDiskUsage(config, fp):
+    status, output = commands.getstatusoutput(
+        ' '.join(['/usr/bin/du', '-s', fp.path]))
+    
+    if status != 0:
+        return 0
+
+    return prepareByteValue(config, int(output.split()[0]))
+
+
+def getResourceType(fp):
+    rt = 'WebDAV:{DAV:}resourcetype'
+    x = xattr.xattr(fp.path)
+    if not x.has_key(rt):
+        return None
+    
+    collection = False
+
+    type = None
+
+    dom = microdom.parseString(x[rt])
+    rt = microdom.getElementsByTagName(dom, 'resourcetype')
+
+    for child in rt[0].childNodes:
+        if child.tagName == 'collection':
+            collection = True
+        else:
+            type = child.tagName
+
+    return (collection, type)
+
+
+def getCalendarDataCounts(calendarCollection):
+    calCount = 0
+    eventCount = 0
+    todoCount = 0
+
+    for child in calendarCollection.walk():
+        if child.isdir():
+            if getResourceType(child) == (True, 'calendar'):
+                calCount += 1
+
+        elif child.isfile():
+            try:
+                component = ical.Component.fromStream(child.open())
+            except ValueError:
+                # not a calendar file
+                continue
+            
+            if component.resourceType() == 'VEVENT':
+                eventCount += 1
+                
+            elif component.resourceType() == 'VTODO':
+                todoCount += 1
+
+    return {'calendarCount': calCount, 
+            'eventCount': eventCount,
+            'todoCount': todoCount}
+
+
+def isPrincipalDisabled(principal):
+    return False
+
+
+from twisted.web2.dav.resource import TwistedQuotaRootProperty, TwistedQuotaUsedProperty
+
+quotaRoot = "WebDAV:" + TwistedQuotaRootProperty.sname().replace("/", "%2F")
+quotaUsed = "WebDAV:" + TwistedQuotaUsedProperty.sname().replace("/", "%2F")
+
+def getQuotaRoot(fp):
+    x = xattr.xattr(fp.path)
+    if not x.has_key(quotaRoot):
+        return None
+
+    dom = microdom.parseString(x[quotaRoot])
+
+    qr = microdom.getElementsByTagName(dom, 'quota-root')[0]
+
+    return int(qr.firstChild().value)
+
+
+def getQuotaUsed(fp):
+    x = xattr.xattr(fp.path)
+    if not x.has_key(quotaUsed):
+        return None
+
+    dom = microdom.parseString(x[quotaUsed])
+
+    qu = microdom.getElementsByTagName(dom, 'quota-used')[0]
+
+    return int(qu.firstChild().value)
+
+
+def getQuotaStatsForPrincipal(config, principal, defaultQuota=None, depth=2):
+    quotaRoot = principal
+
+    principalQuota = getQuotaRoot(quotaRoot)
+
+    while not principalQuota and depth > 0:
+        depth -= 1
+        quotaRoot = quotaRoot.parent()
+        principalQuota = getQuotaRoot(quotaRoot)
+
+    if not principalQuota:
+        principalQuota = defaultQuota
+
+    principalUsed = getQuotaUsed(principal)
+    if not principalUsed:
+        principalUsed = 0
+        
+    principalAvail = principalQuota - principalUsed
+    principalFree = (float(principalAvail)/principalQuota)*100
+
+    return {'quotaRoot': prepareByteValue(config, principalQuota), 
+            'quotaUsed': prepareByteValue(config, principalUsed),
+            'quotaAvail': prepareByteValue(config, principalAvail),
+            'quotaFree': principalFree}

Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist	2006-12-01 22:28:39 UTC (rev 646)
+++ CalendarServer/trunk/conf/caldavd-test.plist	2006-12-02 01:06:17 UTC (rev 647)
@@ -53,6 +53,9 @@
   <key>ErrorLogFile</key>
   <string>error.log</string>
 
+  <key>ServerStatsFile</key>
+  <string>stats.plist</key>
+
   <key>PIDFile</key>
   <string>caldavd.pid</string>
 

Modified: CalendarServer/trunk/conf/caldavd.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd.plist	2006-12-01 22:28:39 UTC (rev 646)
+++ CalendarServer/trunk/conf/caldavd.plist	2006-12-02 01:06:17 UTC (rev 647)
@@ -50,6 +50,9 @@
   <key>ServerLogFile</key>
   <string>/var/log/caldavd/server.log</string>
 
+  <key>ServerStatsFile</key>
+  <string>/Library/CalendarServer/Documents/stats.plist</string>
+
   <key>ErrorLogFile</key>
   <string>/var/log/caldavd/error.log</string>
 

Copied: CalendarServer/trunk/twistedcaldav/caldavd.py (from rev 646, CalendarServer/branches/caladmin-tool-2/twistedcaldav/caldavd.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/caldavd.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/caldavd.py	2006-12-02 01:06:17 UTC (rev 647)
@@ -0,0 +1,363 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2005-2006 Apple Computer, 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.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+import sys
+import os
+import getopt
+import signal
+from tempfile import mkstemp
+
+try:
+    #
+    # plistlib is only included in Mac OS distributions of Python.
+    # This may change in Python 2.6, see:
+    #   https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1555501&group_id=5470
+    #
+    from plistlib import readPlist
+except ImportError:
+    from twistedcaldav.py.plistlib import readPlist
+
+sys.path.insert(0, "/usr/share/caldavd/lib/python")
+
+"""
+Parse the command line and read in a configuration file and then launch the server.
+"""
+
+DEFAULTS = {
+    'CreateAccounts': False,
+    'DirectoryService': {'params': {'node': '/Search'},
+                         'type': 'OpenDirectoryService'},
+    'DocumentRoot': '/Library/CalendarServer/Documents',
+    'DropBoxEnabled': True,
+    'DropBoxInheritedACLs': True,
+    'DropBoxName': 'dropbox',
+    'ErrorLogFile': '/var/log/caldavd/error.log',
+    'ManholePort': 0,
+    'MaximumAttachmentSizeBytes': 1048576,
+    'NotificationCollectionName': 'notifications',
+    'NotificationsEnabled': False,
+    'PIDFile': '/var/run/caldavd.pid',
+    'Port': 8008,
+    'Repository': '/etc/caldavd/repository.xml',
+    'ResetAccountACLs': False,
+    'RunStandalone': True,
+    'SSLCertificate': '/etc/certificates/Default.crt',
+    'SSLEnable': False,
+    'SSLOnly': False,
+    'SSLPort': 8443,
+    'SSLPrivateKey': '/etc/certificates/Default.key',
+    'ServerLogFile': '/var/log/caldavd/server.log',
+    'ServerStatsFile': '/Library/CalendarServer/Documents/stats.plist',
+    'UserQuotaBytes': 104857600,
+    'Verbose': False,
+    'twistdLocation': '/usr/share/caldavd/bin/twistd'}
+
+
+class caldavd(object):
+    """
+    Runs the caldav server.
+    """
+    
+    def __init__(self):
+        # Option defaults
+        self.plistfile = "/etc/caldavd/caldavd.plist"
+
+        self.config = DEFAULTS.copy()
+
+        self.action = None
+    
+    def printit(self):
+        """
+        Print out details about the current configuration.
+        """
+
+        print "Current Configuration"
+        print ""
+        print "Configuration File:               %s" % (self.plistfile,)
+        print ""
+        print "Run as daemon:                    %s" % (self.config['RunStandalone'],)
+        print "Document Root:                    %s" % (self.config['DocumentRoot'],)
+        print "Repository Configuration:         %s" % (self.config['Repository'],)
+        print "Generate Accounts in Repository:  %s" % (self.config['CreateAccounts'],)
+        print "Reset ACLs on Generated Accounts: %s" % (self.config['ResetAccountACLs'],)
+        print "Non-ssl Port:                     %s" % (self.config['Port'],)
+        print "Use SSL:                          %s" % (self.config['SSLEnable'],)
+        print "SSL Port:                         %s" % (self.config['SSLPort'],)
+        print "Only Use SSL:                     %s" % (self.config['SSLOnly'],)
+        print "SSL Private Key File:             %s" % (self.config['SSLPrivateKey'],)
+        print "SSL Certificate File:             %s" % (self.config['SSLCertificate'],)
+        print "Directory Service:                %s" % (self.config['DirectoryService']["type"],)
+        print "Directory Service Parameters:     %r" % (self.config['DirectoryService']["params"],)
+        print "Drop Box Enabled:                 %s" % (self.config['DropBoxEnabled'],)
+        print "Drop Box Name:                    %s" % (self.config['DropBoxName'],)
+        print "Drop Box ACLs are Inherited       %s" % (self.config['DropBoxInheritedACLs'],)
+        print "Notifications Enabled:            %s" % (self.config['NotificationsEnabled'],)
+        print "Notification Collection Name:     %s" % (self.config['NotificationCollectionName'],)
+        print "Server Log File:                  %s" % (self.config['ServerLogFile'],)
+        print "Error Log File:                   %s" % (self.config['ErrorLogFile'],)
+        print "PID File:                         %s" % (self.config['PIDFile'],)
+        print "twistd Location:                  %s" % (self.config['twistdLocation'],)
+        print "Maximum Calendar Resource Size:   %d bytes" % (self.config['MaximumAttachmentSizeBytes'],)
+        print "Global per-user quota limit:      %d bytes" % (self.config['UserQuotaBytes'],)
+
+    def run(self):
+        """
+        Run the caldavd server using the provided options and configuration.
+
+        @raise: C:{ValueError} if options or configuration are wrong.
+        """
+
+        # Parse command line options and config file
+        self.commandLine()
+        if self.action is None:
+            return
+        
+        # Dispatch action
+        {"start":   self.start,
+         "stop":    self.stop,
+         "restart": self.restart}[self.action]()
+
+    def start(self):
+        """
+        Start the caldavd server.
+        """
+        
+        print "Starting CalDAV Server",
+        try:
+            fd, tac = mkstemp(prefix="caldav")
+            os.write(fd, self.generateTAC())
+            os.close(fd)
+        except Exception, e:
+            print "        [Failed]"
+            print "Unable to create temporary file for server configuration."
+            print e
+            sys.exit(1)
+        
+        # Create arguments for twistd
+        args = [os.path.basename(sys.executable)]
+        args.append(self.config['twistdLocation'])
+        if not self.config['RunStandalone']:
+            args.append("-n")
+        args.append("--logfile=%s" % (self.config['ErrorLogFile'],))
+        args.append("--pidfile=%s" % (self.config['PIDFile'],))
+        args.append("-y")
+        args.append(tac)
+
+        # Create environment for twistd
+        environment = dict(os.environ)
+        environment["PYTHONPATH"] = ":".join(sys.path)
+
+        # spawn the twistd python process
+        try:
+            os.spawnve(os.P_WAIT, sys.executable, args, environment)
+        except OSError, why:
+            print "        [Failed]"
+            print "Error: %s" % (why[1],)
+        
+        # Get rid of temp file
+        try:
+            os.unlink(tac)
+        except:
+            pass
+        print "        [Done]"
+    
+    def stop(self):
+        """
+        Stop the caldavd server.
+        """
+        
+        if os.path.exists(self.config['PIDFile']):
+            try:
+                pid = int(open(self.config['PIDFile']).read())
+            except ValueError:
+                sys.exit("Pidfile %s contains non-numeric value" % self.config['PIDFile'])
+            try:
+                print "Stopping CalDAV Server",
+                os.kill(pid, signal.SIGTERM)
+                print "        [Done]"
+            except OSError, why:
+                print "        [Failed]"
+                print "Error: %s" % (why[1],)
+        else:
+            print "CalDAV server is not running"
+    
+    def restart(self):
+        """
+        Restart the caldavd server.
+        """
+        self.stop()
+        self.start()
+        
+    def commandLine(self):
+        """
+        Parse the command line options into the config object.
+        
+        @return: the C{str} for the requested action, or C{None} when
+            immediate exit is called for.
+        @raise: C{ValueError} when a problem occurs with the options.
+        """
+        options, args = getopt.getopt(sys.argv[1:], "hvf:XT:p")
+        
+        # Process the plist file first, then the options, so that command line
+        # options get to override plist options
+        pls = [p for p in options if p[0] == "-f"]
+        if len(pls) == 1:
+            self.plistfile = pls[0][1]
+        if not os.path.exists(self.plistfile):
+            print "Configuration file does not exist: %s" % (self.plistfile,)
+            raise ValueError
+        self.parsePlist()
+    
+        # Parse all the options
+        do_print = False
+        for option, value in options:
+            if option == "-h":
+                self.usage()
+                return
+            elif option == "-v":
+                self.config['Verbose'] = True
+            elif option == "-f":
+                # We should have handled this already
+                pass
+            elif option == "-X":
+                self.config['RunStandalone'] = False
+            elif option == "-T":
+                self.config['twistdLocation'] = value
+            elif option == "-p":
+                do_print = True
+            else:
+                print "Unrecognized option: %s" % (option,)
+                self.usage()
+                raise ValueError
+        
+        # Print out config if requested
+        if do_print:
+            self.printit()
+            return
+    
+        # Process arguments
+        if len(args) == 0:
+            print "No arguments given. One of start, stop or restart must be present."
+            self.usage()
+            raise ValueError
+        elif len(args) > 1:
+            print "Too many arguments given. Only one of start, stop or restart must be present."
+            self.usage()
+            raise ValueError
+        elif args[0] not in ("start", "stop", "restart"):
+            print "Wrong arguments given: %s" % (args[0],)
+            self.usage()
+            raise ValueError
+        
+        # Verify that configuration is valid
+        if not self.validate():
+            raise ValueError
+    
+        self.action = args[0]
+    
+    def parsePlist(self):
+        print "Reading configuration file %s." % (self.plistfile,)
+
+        root = readPlist(self.plistfile)
+        
+        for k,v in root.items():
+            if k in self.config:
+                self.config[k] = v
+            else:
+                print "Unknown option: %s" % (k,)
+
+    def validate(self):
+        
+        result = True
+
+        if not os.path.exists(self.config['DocumentRoot']):
+            print "Document Root does not exist: %s" % (self.config['DocumentRoot'],)
+            result = False
+
+        if not os.path.exists(self.config['Repository']):
+            print "Repository File does not exist: %s" % (self.config['Repository'],)
+            result = False
+
+        if self.config['SSLEnable'] and not os.path.exists(self.config['SSLPrivateKey']):
+            print "SSL Private Key File does not exist: %s" % (self.config['SSLPrivateKey'],)
+            result = False
+
+        if self.config['SSLEnable'] and not os.path.exists(self.config['SSLCertificate']):
+            print "SSL Certificate File does not exist: %s" % (self.config['SSLCertificate'],)
+            result = False
+
+        if not self.config['SSLEnable'] and self.config['SSLOnly']:
+            self.config['SSLEnable'] = True
+
+        if not self.config['RunStandalone']:
+            self.config['ErrorLogFile'] = "-"
+
+        if not os.path.exists(self.config['twistdLocation']):
+            print "twistd does not exist: %s" % (self.config['twistdLocation'],)
+            result = False
+            
+        return result
+
+    def usage(self):
+        default = caldavd()
+        print """Usage: caldavd [options] start|stop|restart
+Options:
+    -h          Print this help and exit
+    -v          Be verbose
+    -f config   Specify path to configuration file [""" + default.plistfile + """]
+    -X          Do not daemonize
+    -T twistd   Specify path to twistd [""" + default.twistd + """]
+    -p          Print current configuration and exit
+"""
+    
+    def generateTAC(self):
+        return """
+from twistedcaldav.repository import startServer
+
+application, site = startServer(
+    %(DocumentRoot)r,
+    %(Repository)r,
+    %(CreateAccounts)s,
+    %(ResetAccountACLs)s,
+    %(SSLEnable)s,
+    %(SSLPrivateKey)r,
+    %(SSLCertificate)r,
+    %(SSLOnly)s,
+    %(Port)d,
+    %(SSLPort)d,
+    %(MaximumAttachmentSizeBytes)d,
+    %(UserQuotaBytes)d,
+    %(ServerLogFile)r,
+    %(DirectoryService)r,
+    %(DropBoxEnabled)r,
+    %(DropBoxName)r,
+    %(DropBoxInheritedACLs)r,
+    %(NotificationsEnabled)r,
+    %(NotificationCollectionName)r,
+    %(ManholePort)d,
+)
+""" % self.config
+
+
+if __name__ == "__main__":
+    try:
+        caldavd().run()
+    except Exception, e:
+        sys.exit(str(e))

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20061201/98553d3c/attachment.html


More information about the calendarserver-changes mailing list