[CalendarServer-changes] [4379] CalendarServer/branches/config-separation

source_changes at macosforge.org source_changes at macosforge.org
Thu Jun 25 10:52:06 PDT 2009


Revision: 4379
          http://trac.macosforge.org/projects/calendarserver/changeset/4379
Author:   william_short at apple.com
Date:     2009-06-25 10:52:06 -0700 (Thu, 25 Jun 2009)
Log Message:
-----------
separate config data into its own file

Modified Paths:
--------------
    CalendarServer/branches/config-separation/calendarserver/platform/darwin/_sacl.c
    CalendarServer/branches/config-separation/calendarserver/provision/root.py
    CalendarServer/branches/config-separation/calendarserver/provision/test/test_root.py
    CalendarServer/branches/config-separation/calendarserver/tap/caldav.py
    CalendarServer/branches/config-separation/calendarserver/tap/test/test_caldav.py
    CalendarServer/branches/config-separation/calendarserver/tools/principals.py
    CalendarServer/branches/config-separation/calendarserver/tools/util.py
    CalendarServer/branches/config-separation/calendarserver/tools/warmup.py
    CalendarServer/branches/config-separation/calendarserver/webcal/resource.py
    CalendarServer/branches/config-separation/conf/auth/accounts-test.xml
    CalendarServer/branches/config-separation/conf/caldavd-apple.plist
    CalendarServer/branches/config-separation/conf/caldavd-test.plist
    CalendarServer/branches/config-separation/conf/caldavd.plist
    CalendarServer/branches/config-separation/run
    CalendarServer/branches/config-separation/setup.py
    CalendarServer/branches/config-separation/support/Makefile.Apple
    CalendarServer/branches/config-separation/test
    CalendarServer/branches/config-separation/testcaldav
    CalendarServer/branches/config-separation/twext/internet/ssl.py
    CalendarServer/branches/config-separation/twisted/plugins/caldav.py
    CalendarServer/branches/config-separation/twistedcaldav/accesslog.py
    CalendarServer/branches/config-separation/twistedcaldav/caldavxml.py
    CalendarServer/branches/config-separation/twistedcaldav/config.py
    CalendarServer/branches/config-separation/twistedcaldav/directory/cachingdirectory.py
    CalendarServer/branches/config-separation/twistedcaldav/directory/calendar.py
    CalendarServer/branches/config-separation/twistedcaldav/directory/directory.py
    CalendarServer/branches/config-separation/twistedcaldav/directory/principal.py
    CalendarServer/branches/config-separation/twistedcaldav/directory/resource.py
    CalendarServer/branches/config-separation/twistedcaldav/directory/util.py
    CalendarServer/branches/config-separation/twistedcaldav/extensions.py
    CalendarServer/branches/config-separation/twistedcaldav/freebusyurl.py
    CalendarServer/branches/config-separation/twistedcaldav/ical.py
    CalendarServer/branches/config-separation/twistedcaldav/icaldav.py
    CalendarServer/branches/config-separation/twistedcaldav/index.py
    CalendarServer/branches/config-separation/twistedcaldav/mail.py
    CalendarServer/branches/config-separation/twistedcaldav/memcacheprops.py
    CalendarServer/branches/config-separation/twistedcaldav/method/delete.py
    CalendarServer/branches/config-separation/twistedcaldav/method/delete_common.py
    CalendarServer/branches/config-separation/twistedcaldav/method/get.py
    CalendarServer/branches/config-separation/twistedcaldav/method/mkcalendar.py
    CalendarServer/branches/config-separation/twistedcaldav/method/mkcol.py
    CalendarServer/branches/config-separation/twistedcaldav/method/propfind.py
    CalendarServer/branches/config-separation/twistedcaldav/method/put.py
    CalendarServer/branches/config-separation/twistedcaldav/method/put_common.py
    CalendarServer/branches/config-separation/twistedcaldav/method/report.py
    CalendarServer/branches/config-separation/twistedcaldav/method/report_calquery.py
    CalendarServer/branches/config-separation/twistedcaldav/method/report_common.py
    CalendarServer/branches/config-separation/twistedcaldav/method/report_freebusy.py
    CalendarServer/branches/config-separation/twistedcaldav/method/report_multiget.py
    CalendarServer/branches/config-separation/twistedcaldav/notify.py
    CalendarServer/branches/config-separation/twistedcaldav/resource.py
    CalendarServer/branches/config-separation/twistedcaldav/schedule.py
    CalendarServer/branches/config-separation/twistedcaldav/scheduling/addressmapping.py
    CalendarServer/branches/config-separation/twistedcaldav/scheduling/caldav.py
    CalendarServer/branches/config-separation/twistedcaldav/scheduling/implicit.py
    CalendarServer/branches/config-separation/twistedcaldav/scheduling/ischedule.py
    CalendarServer/branches/config-separation/twistedcaldav/scheduling/itip.py
    CalendarServer/branches/config-separation/twistedcaldav/scheduling/processing.py
    CalendarServer/branches/config-separation/twistedcaldav/scheduling/scheduler.py
    CalendarServer/branches/config-separation/twistedcaldav/static.py
    CalendarServer/branches/config-separation/twistedcaldav/test/test_config.py
    CalendarServer/branches/config-separation/twistedcaldav/test/test_memcacheprops.py
    CalendarServer/branches/config-separation/twistedcaldav/test/test_notify.py
    CalendarServer/branches/config-separation/twistedcaldav/test/test_static.py
    CalendarServer/branches/config-separation/twistedcaldav/test/test_timezones.py
    CalendarServer/branches/config-separation/twistedcaldav/test/util.py

Added Paths:
-----------
    CalendarServer/branches/config-separation/twistedcaldav/stdconfig.py

Property Changed:
----------------
    CalendarServer/branches/config-separation/


Property changes on: CalendarServer/branches/config-separation
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
   + /CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075


Modified: CalendarServer/branches/config-separation/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/config-separation/calendarserver/tap/caldav.py	2009-06-25 17:37:51 UTC (rev 4378)
+++ CalendarServer/branches/config-separation/calendarserver/tap/caldav.py	2009-06-25 17:52:06 UTC (rev 4379)
@@ -64,7 +64,8 @@
 from twistedcaldav.accesslog import RotatingFileAccessLoggingObserver
 from twistedcaldav.accesslog import AMPLoggingFactory
 from twistedcaldav.accesslog import AMPCommonAccessLoggingObserver
-from twistedcaldav.config import config, defaultConfig, defaultConfigFile
+from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
+from twistedcaldav.config import config
 from twistedcaldav.config import ConfigurationError
 from twistedcaldav.resource import CalDAVResource, AuthenticationWrapper
 from twistedcaldav.directory.digest import QopDigestCredentialFactory
@@ -127,7 +128,7 @@
 
 class CalDAVOptions (Options, LoggingMixIn):
     optParameters = [[
-        "config", "f", defaultConfigFile, "Path to configuration file."
+        "config", "f", DEFAULT_CONFIG_FILE, "Path to configuration file."
     ]]
 
     zsh_actions = {"config" : "_files -g '*.plist'"}
@@ -197,7 +198,7 @@
         if "=" in option:
             path, value = option.split("=")
             self.setOverride(
-                defaultConfig,
+                DEFAULT_CONFIG,
                 path.split("/"),
                 value,
                 self.overrides
@@ -208,6 +209,10 @@
     opt_o = opt_option
 
     def postOptions(self):
+        self.loadConfiguration()
+        self.checkConfiguration()
+            
+    def loadConfiguration(self):
         if not os.path.exists(self["config"]):
             self.log_info("Config file %s not found, using defaults"
                           % (self["config"],))
@@ -216,13 +221,14 @@
                       % (self["config"],))
 
         try:
-            config.loadConfig(self["config"])
+            config.load(self["config"])
         except ConfigurationError, e:
             log.err("Invalid configuration: %s" % (e,))
             sys.exit(1)
 
         config.updateDefaults(self.overrides)
-
+        
+    def checkConfiguration(self):
         uid, gid = None, None
 
         if self.parent["uid"] or self.parent["gid"]:
@@ -344,10 +350,15 @@
     timezoneServiceResourceClass = TimezoneServiceFile
     webCalendarResourceClass     = WebCalendarResource
     webAdminResourceClass        = WebAdminResource
+    
+    #
+    # Default tap names
+    #
+    mailGatewayTapName = "caldav_mailgateway"
+    notifierTapName = "caldav_notifier"
 
+
     def makeService(self, options):
-
-
         serviceMethod = getattr(self, "makeService_%s" % (config.ProcessType,), None)
 
         if not serviceMethod:
@@ -394,7 +405,10 @@
                 self.log_info("SIGHUP recieved at %s" % (location(frame),))
 
                 # Reload the config file
-                config.reload()
+                try:
+                    config.reload()
+                except ConfigurationError, e:
+                    self.log_error("Invalid configuration: {0}".format(e))
 
                 # If combined service send signal to all caldavd children
                 if hasattr(service, "processMonitor"):
@@ -738,11 +752,11 @@
             vary = True,
         )
 
-        def updateChannel(config, items):
-            channel.maxRequests = config.MaxRequests
-            channel.retryAfter = config.HTTPRetryAfter
+        def updateChannel(configDict):
+            channel.maxRequests = configDict.MaxRequests
+            channel.retryAfter = configDict.HTTPRetryAfter
 
-        config.addHook(updateChannel)
+        config.addPostUpdateHook(updateChannel)
 
         if not config.BindAddresses:
             config.BindAddresses = [""]
@@ -1080,7 +1094,7 @@
                 notificationsArgv.extend(("-g", config.GroupName))
             notificationsArgv.extend((
                 "--reactor=%s" % (config.Twisted.reactor,),
-                "-n", "caldav_notifier",
+                "-n", self.notifierTapName,
                 "-f", options["config"],
             ))
             monitor.addProcess("notifications", notificationsArgv,
@@ -1102,7 +1116,7 @@
                 mailGatewayArgv.extend(("-g", config.GroupName))
             mailGatewayArgv.extend((
                 "--reactor=%s" % (config.Twisted.reactor,),
-                "-n", "caldav_mailgateway",
+                "-n", self.mailGatewayTapName,
                 "-f", options["config"],
             ))
 

Modified: CalendarServer/branches/config-separation/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/branches/config-separation/calendarserver/tap/test/test_caldav.py	2009-06-25 17:37:51 UTC (rev 4378)
+++ CalendarServer/branches/config-separation/calendarserver/tap/test/test_caldav.py	2009-06-25 17:52:06 UTC (rev 4379)
@@ -27,7 +27,8 @@
 
 from twext.python.plistlib import writePlist
 
-from twistedcaldav.config import config, ConfigDict, defaultConfig, _mergeData
+from twistedcaldav.config import config, ConfigDict, _mergeData
+from twistedcaldav.stdconfig import DEFAULT_CONFIG
 
 from twistedcaldav.directory.aggregate import AggregateDirectoryService
 from twistedcaldav.directory.sudo import SudoDirectoryService
@@ -70,8 +71,7 @@
         self.config.parent["nodaemon"] = False
 
     def tearDown(self):
-        config.loadConfig(None)
-        config.setDefaults(defaultConfig)
+        config.setDefaults(DEFAULT_CONFIG)
         config.reload()
 
     def test_overridesConfig(self):
@@ -124,7 +124,7 @@
         Test that specifying a config file from the command line
         loads the global config with those values properly.
         """
-        myConfig = ConfigDict(defaultConfig)
+        myConfig = ConfigDict(DEFAULT_CONFIG)
 
         myConfig.Authentication.Basic.Enabled = False
         myConfig.MultiProcess.LoadBalancer.Enabled = False
@@ -177,7 +177,7 @@
         self.options.parent["uid"] = None
         self.options.parent["nodaemon"] = None
 
-        self.config = ConfigDict(defaultConfig)
+        self.config = ConfigDict(DEFAULT_CONFIG)
 
         accountsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/accounts.xml")
         pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem")
@@ -206,8 +206,7 @@
         self.writeConfig()
 
     def tearDown(self):
-        config.loadConfig(None)
-        config.setDefaults(defaultConfig)
+        config.setDefaults(DEFAULT_CONFIG)
         config.reload()
 
     def writeConfig(self):

Modified: CalendarServer/branches/config-separation/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/branches/config-separation/calendarserver/tools/principals.py	2009-06-25 17:37:51 UTC (rev 4378)
+++ CalendarServer/branches/config-separation/calendarserver/tools/principals.py	2009-06-25 17:52:06 UTC (rev 4379)
@@ -39,7 +39,8 @@
 
 from twistedcaldav import caldavxml
 from twistedcaldav import memcachepool
-from twistedcaldav.config import config, defaultConfigFile, ConfigurationError
+from twistedcaldav.config import config, ConfigurationError
+from twistedcaldav.stdconfig import DEFAULT_CONFIG
 from twistedcaldav.customxml import calendarserver_namespace
 from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource

Modified: CalendarServer/branches/config-separation/calendarserver/tools/util.py
===================================================================
--- CalendarServer/branches/config-separation/calendarserver/tools/util.py	2009-06-25 17:37:51 UTC (rev 4378)
+++ CalendarServer/branches/config-separation/calendarserver/tools/util.py	2009-06-25 17:52:06 UTC (rev 4379)
@@ -29,17 +29,18 @@
 from twisted.python.reflect import namedClass
 
 import socket
-from twistedcaldav.config import config, defaultConfigFile, ConfigurationError
+from twistedcaldav.config import config, ConfigurationError
 from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
 
 def loadConfig(configFileName):
     if configFileName is None:
-        configFileName = defaultConfigFile
+        configFileName = DEFAULT_CONFIG_FILE
 
     if not os.path.isfile(configFileName):
         raise ConfigurationError("No config file: %s" % (configFileName,))
 
-    config.loadConfig(configFileName)
+    config.load(configFileName)
 
     return config
 

Modified: CalendarServer/branches/config-separation/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/config-separation/twistedcaldav/config.py	2009-06-25 17:37:51 UTC (rev 4378)
+++ CalendarServer/branches/config-separation/twistedcaldav/config.py	2009-06-25 17:52:06 UTC (rev 4379)
@@ -14,33 +14,23 @@
 # limitations under the License.
 ##
 
+import copy
+import os
+
 __all__ = [
-    "defaultConfigFile",
-    "defaultConfig",
-    "ConfigDict",
     "Config",
+    "ConfigDict",
+    "ConfigProvider",
     "ConfigurationError",
     "config",
 ]
 
-import os
-import copy
-import re
+class ConfigurationError(RuntimeError):
+    """
+    Invalid server configuration.
+    """
 
-from twisted.web2.dav import davxml
-from twisted.web2.dav.resource import TwistedACLInheritable
-
-from twext.python.plistlib import readPlist
-
-from twistedcaldav.log import Logger
-from twistedcaldav.log import clearLogLevels, setLogLevelForNamespace, InvalidLogLevelError
-from twistedcaldav.util import (
-    KeychainAccessError, KeychainPasswordNotFound, getPasswordFromKeychain
-)
-
-log = Logger()
-
-class ConfigDict (dict):
+class ConfigDict(dict):
     def __init__(self, mapping=None):
         if mapping is not None:
             for key, value in mapping.iteritems():
@@ -67,563 +57,56 @@
         else:
             return dict.__getattr__(self, attr)
 
-defaultConfigFile = "/etc/caldavd/caldavd.plist"
 
-serviceDefaultParams = {
-    "twistedcaldav.directory.xmlfile.XMLDirectoryService": {
-        "xmlFile": "/etc/caldavd/accounts.xml",
-    },
-    "twistedcaldav.directory.appleopendirectory.OpenDirectoryService": {
-        "node": "/Search",
-        "restrictEnabledRecords": False,
-        "restrictToGroup": "",
-        "cacheTimeout": 30,
-    },
-}
-
-defaultConfig = {
-    # Note: Don't use None values below; that confuses the command-line parser.
-
-    #
-    # Public network address information
-    #
-    #    This is the server's public network address, which is provided to
-    #    clients in URLs and the like.  It may or may not be the network
-    #    address that the server is listening to directly, though it is by
-    #    default.  For example, it may be the address of a load balancer or
-    #    proxy which forwards connections to the server.
-    #
-    "ServerHostName": "", # Network host name.
-    "HTTPPort": 0,        # HTTP port (0 to disable HTTP)
-    "SSLPort" : 0,        # SSL port (0 to disable HTTPS)
-    "RedirectHTTPToHTTPS" : False, # If True, all nonSSL requests redirected to an SSL Port
-    "SSLMethod" : "SSLv3_METHOD", # SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD
-
-    #
-    # Network address configuration information
-    #
-    #    This configures the actual network address that the server binds to.
-    #
-    "BindAddresses": [],   # List of IP addresses to bind to [empty = all]
-    "BindHTTPPorts": [],   # List of port numbers to bind to for HTTP [empty = same as "Port"]
-    "BindSSLPorts" : [],   # List of port numbers to bind to for SSL [empty = same as "SSLPort"]
-
-    #
-    # Data store
-    #
-    "DataRoot"                : "/Library/CalendarServer/Data",
-    "DocumentRoot"            : "/Library/CalendarServer/Documents",
-    "UserQuota"               : 104857600, # User quota (in bytes)
-    "MaximumAttachmentSize"   :   1048576, # Attachment size limit (in bytes)
-    "MaxAttendeesPerInstance" :       100, # Maximum number of unique attendees
-    "MaxInstancesForRRULE"    :       400, # Maximum number of instances for an RRULE
-    "WebCalendarRoot"         : "/usr/share/collaboration",
-
-    "Aliases": {},
-
-    #
-    # Directory service
-    #
-    #    A directory service provides information about principals (eg.
-    #    users, groups, locations and resources) to the server.
-    #
-    "DirectoryService": {
-        "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService",
-        "params": serviceDefaultParams["twistedcaldav.directory.xmlfile.XMLDirectoryService"],
-    },
-
-    #
-    # Special principals
-    #
-    "AdminPrincipals": [],                       # Principals with "DAV:all" access (relative URLs)
-    "ReadPrincipals": [],                        # Principals with "DAV:read" access (relative URLs)
-    "SudoersFile": "/etc/caldavd/sudoers.plist", # Principals that can pose as other principals
-    "EnableProxyPrincipals": True,               # Create "proxy access" principals
-
-    #
-    # Permissions
-    #
-    "EnableAnonymousReadRoot": True,    # Allow unauthenticated read access to /
-    "EnableAnonymousReadNav": False,    # Allow unauthenticated read access to hierachcy
-    "EnablePrincipalListings": True,    # Allow listing of principal collections
-    "EnableMonolithicCalendars": True,  # Render calendar collections as a monolithic iCalendar object
-
-    #
-    # Client controls
-    #
-    "RejectClients": [], # List of regexes for clients to disallow
-
-    #
-    # Authentication
-    #
-    "Authentication": {
-        "Basic": { "Enabled": False },     # Clear text; best avoided
-        "Digest": {                        # Digest challenge/response
-            "Enabled": True,
-            "Algorithm": "md5",
-            "Qop": "",
-        },
-        "Kerberos": {                       # Kerberos/SPNEGO
-            "Enabled": False,
-            "ServicePrincipal": ""
-        },
-        "Wiki": {
-            "Enabled": False,
-            "Cookie": "sessionID",
-            "URL": "http://127.0.0.1:8086/RPC2",
-            "UserMethod": "userForSession",
-            "WikiMethod": "accessLevelForUserWikiCalendar",
-        },
-    },
-
-    #
-    # Logging
-    #
-    "AccessLogFile"  : "/var/log/caldavd/access.log",  # Apache-style access log
-    "ErrorLogFile"   : "/var/log/caldavd/error.log",   # Server activity log
-    "ServerStatsFile": "/var/run/caldavd/stats.plist",
-    "PIDFile"        : "/var/run/caldavd.pid",
-    "RotateAccessLog"         : False,
-    "EnableExtendedAccessLog" : True,
-    "DefaultLogLevel"   : "",
-    "LogLevels"         : {},
-
-    "AccountingCategories": {
-        "iTIP": False,
-    },
-    "AccountingPrincipals": [],
-    "AccountingLogRoot"   : "/var/log/caldavd/accounting",
-
-    "GlobalStatsSocket"           : "/var/run/caldavd-stats.sock", 
-    "GlobalStatsLoggingPeriod"    : 60, 
-    "GlobalStatsLoggingFrequency" : 12, 
-
-    #
-    # SSL/TLS
-    #
-    "SSLCertificate"     : "",  # Public key
-    "SSLPrivateKey"      : "",  # Private key
-    "SSLAuthorityChain"  : "",  # Certificate Authority Chain
-    "SSLPassPhraseDialog": "/etc/apache2/getsslpassphrase",
-    "SSLCertAdmin"       : "/usr/sbin/certadmin",
-
-    #
-    # Process management
-    #
-
-    # Username and Groupname to drop privileges to, if empty privileges will
-    # not be dropped.
-
-    "UserName": "",
-    "GroupName": "",
-    "ProcessType": "Combined",
-    "MultiProcess": {
-        "ProcessCount": 0,
-        "MinProcessCount": 4,
-        "LoadBalancer": {
-            "Enabled": True,
-            "Scheduler": "LeastConnections",
-        },
-        "StaggeredStartup": {
-            "Enabled": False,
-            "Interval": 15,
-        },
-    },
-
-    #
-    # Service ACLs
-    #
-    "EnableSACLs": False,
-
-    #
-    # Non-standard CalDAV extensions
-    #
-    "EnableDropBox"           : False, # Calendar Drop Box
-    "EnablePrivateEvents"     : False, # Private Events
-    "EnableTimezoneService"   : False, # Timezone service
-
-    #
-    # Web-based administration
-    #
-    "EnableWebAdmin"          : True,
-
-    #
-    # Scheduling related options
-    #
-
-    "Scheduling": {
-        
-        "CalDAV": {
-            "EmailDomain"                : "",    # Domain for mailto calendar user addresses on this server
-            "HTTPDomain"                 : "",    # Domain for http calendar user addresses on this server
-            "AddressPatterns"            : [],    # Reg-ex patterns to match local calendar user addresses
-            "OldDraftCompatibility"      : True,  # Whether to maintain compatibility with non-implicit mode
-            "ScheduleTagCompatibility"   : True,  # Whether to support older clients that do not use Schedule-Tag feature
-            "EnablePrivateComments"      : True,  # Private comments from attendees to organizer
-        },
-
-        "iSchedule": {
-            "Enabled"          : False, # iSchedule protocol
-            "AddressPatterns"  : [],    # Reg-ex patterns to match iSchedule-able calendar user addresses
-            "Servers"          : "/etc/caldavd/servertoserver.xml",    # iSchedule server configurations
-        },
-
-        "iMIP": {
-            "Enabled"          : False, # Server-to-iMIP protocol
-            "MailGatewayServer" : "localhost",
-            "MailGatewayPort"   : 62310,
-            "Username"          : "com.apple.calendarserver", # For account injecting replies
-            "Password"          : "",    # For account injecting replies
-            "Sending": {
-                "Server"        : "",    # SMTP server to relay messages through
-                "Port"          : 587,   # SMTP server port to relay messages through
-                "Address"       : "",    # 'From' address for server
-                "UseSSL"        : True,
-                "Username"      : "",    # For account sending mail
-                "Password"      : "",    # For account sending mail
-            },
-            "Receiving": {
-                "Server"        : "",    # Server to retrieve email messages from
-                "Port"          : 0,     # Server port to retrieve email messages from
-                "UseSSL"        : True,
-                "Type"          : "",    # Type of message access server: 'pop' or 'imap'
-                "PollingSeconds"    : 30,  # How often to fetch mail
-                "Username"      : "",    # For account receving mail
-                "Password"      : "",    # For account receving mail
-            },
-            "AddressPatterns"   : [],    # Reg-ex patterns to match iMIP-able calendar user addresses
-            "MailTemplatesDirectory": "/usr/share/caldavd/share/email_templates", # Directory containing HTML templates for email invitations (invite.html, cancel.html)
-            "MailIconsDirectory": "/usr/share/caldavd/share/date_icons", # Directory containing language-specific subdirectories containing date-specific icons for email invitations
-            "InvitationDaysToLive" : 90, # How many days invitations are valid
-        },
-
-        "Options" : {
-            "AllowGroupAsOrganizer"      : False, # Allow groups to be Organizers
-            "AllowLocationAsOrganizer"   : False, # Allow locations to be Organizers
-            "AllowResourceAsOrganizer"   : False, # Allow resources to be Organizers
-        }
-    },
-
-    "FreeBusyURL": {
-        "Enabled"          : False, # Per-user free-busy-url protocol
-        "TimePeriod"       : 14,    # Number of days into the future to generate f-b data if no explicit time-range is specified
-        "AnonymousAccess"  : False, # Allow anonymous read access to free-busy URL
-    },
-
-    #
-    # Notifications
-    #
-    "Notifications" : {
-        "Enabled": False,
-        "CoalesceSeconds" : 3,
-        "InternalNotificationHost" : "localhost",
-        "InternalNotificationPort" : 62309,
-        "BindAddress" : "127.0.0.1",
-
-        "Services" : {
-            "SimpleLineNotifier" : {
-                "Service" : "twistedcaldav.notify.SimpleLineNotifierService",
-                "Enabled" : False,
-                "Port" : 62308,
-            },
-            "XMPPNotifier" : {
-                "Service" : "twistedcaldav.notify.XMPPNotifierService",
-                "Enabled" : False,
-                "Host" : "", # "xmpp.host.name"
-                "Port" : 5222,
-                "JID" : "", # "jid at xmpp.host.name/resource"
-                "Password" : "",
-                "ServiceAddress" : "", # "pubsub.xmpp.host.name"
-                "NodeConfiguration" : {
-                    "pubsub#deliver_payloads" : "1",
-                    "pubsub#persist_items" : "1",
-                },
-                "KeepAliveSeconds" : 120,
-                "HeartbeatMinutes" : 30,
-                "AllowedJIDs": [],
-            },
-        }
-    },
-
-    #
-    # Performance tuning
-    #
-
-    # Set the maximum number of outstanding requests to this server.
-    "MaxRequests": 600,
-
-    "ListenBacklog": 50,
-    "IdleConnectionTimeOut": 15,
-    "UIDReservationTimeOut": 30 * 60,
-
-
-    #
-    # Localization
-    #
-    "Localization" : {
-        "TranslationsDirectory" : "/usr/share/caldavd/share/translations",
-        "LocalesDirectory" : "/usr/share/caldavd/share/locales",
-        "Language" : "English",
-    },
-
-
-    #
-    # Implementation details
-    #
-    #    The following are specific to how the server is built, and useful
-    #    for development, but shouldn't be needed by users.
-    #
-
-    # Twisted
-    "Twisted": {
-        "twistd": "/usr/share/caldavd/bin/twistd",
-        "reactor": "select",
-    },
-
-    # Python Director
-    "PythonDirector": {
-        "pydir": "/usr/share/caldavd/bin/pydir.py",
-        "ConfigFile": "/etc/caldavd/pydir.xml",
-        "ControlSocket": "/var/run/caldavd-pydir.sock",
-    },
-
-    # Umask
-    "umask": 0027,
-
-    # A TCP port used for communication between the child and master
-    # processes (bound to 127.0.0.1). Specify 0 to let OS assign a port.
-    "ControlPort": 0,
-
-    # A unix socket used for communication between the child and master
-    # processes. If blank, then an AF_INET socket is used instead.
-    "ControlSocket": "/var/run/caldavd.sock",
-
-
-    # Support for Content-Encoding compression options as specified in
-    # RFC2616 Section 3.5
-    "ResponseCompression": True,
-
-    # The retry-after value (in seconds) to return with a 503 error
-    "HTTPRetryAfter": 180,
-
-    # Profiling options
-    "Profiling": {
-        "Enabled": False,
-        "BaseDirectory": "/tmp/stats",
-    },
-
-    "Memcached": {
-        "MaxClients": 5,
-        "ClientEnabled": True,
-        "ServerEnabled": True,
-        "BindAddress": "127.0.0.1",
-        "Port": 11211,
-        "memcached": "memcached", # Find in PATH
-        "MaxMemory": 0, # Megabytes
-        "Options": [],
-    },
-
-    "EnableKeepAlive": True,
-
-    "ResponseCacheTimeout": 30, # Minutes
-}
-
-class Config (object):
-    """
-    @DynamicAttrs
-    """
-    def __init__(self, defaults):
-        if not isinstance(defaults, ConfigDict):
-            defaults = ConfigDict(defaults)
-
-        self.setDefaults(defaults)
-        self._data = copy.deepcopy(defaults)
-        self._configFile = None
-        self._hooks = [
-            self.updateHostName,
-            self.updateDirectoryService,
-            self.updateACLs,
-            self.updateRejectClients,
-            self.updateDropBox,
-            self.updateLogLevels,
-            self.updateNotifications,
-            self.updateScheduling,
-        ]
-
-    def __str__(self):
-        return str(self._data)
-
-    def addHook(self, hook):
-        self._hooks.append(hook)
-
-    def update(self, items):
-        if not isinstance(items, ConfigDict):
-            items = ConfigDict(items)
-
-        #
-        # Call hooks
-        #
-        for hook in self._hooks:
-            hook(self, items)
-
-    @staticmethod
-    def updateHostName(self, items):
-        if not self.ServerHostName:
-            from socket import getfqdn
-            hostname = getfqdn()
-            if not hostname:
-                hostname = "localhost"
-            self.ServerHostName = hostname
-
-    @staticmethod
-    def updateDirectoryService(self, items):
-        #
-        # Special handling for directory services configs
-        #
-        dsType = items.get("DirectoryService", {}).get("type", None)
-        if dsType is None:
-            dsType = self._data.DirectoryService.type
+class ConfigProvider(object):
+    """Configuration provider, abstraction for config storage/format/defaults"""
+    
+    def __init__(self, defaults=None):
+        """Create configuration provider with given defaults"""
+        self._configFileName = None
+        if defaults is None:
+            self._defaults = ConfigDict()
         else:
-            if dsType == self._data.DirectoryService.type:
-                oldParams = self._data.DirectoryService.params
-                newParams = items.DirectoryService.get("params", {})
-                _mergeData(oldParams, newParams)
-            else:
-                if dsType in serviceDefaultParams:
-                    self._data.DirectoryService.params = copy.deepcopy(serviceDefaultParams[dsType])
-                else:
-                    self._data.DirectoryService.params = {}
-
-        for param in items.get("DirectoryService", {}).get("params", {}):
-            if dsType in serviceDefaultParams and param not in serviceDefaultParams[dsType]:
-                log.warn("Parameter %s is not supported by service %s" % (param, dsType))
-
-        _mergeData(self._data, items)
-
-        if self._data.DirectoryService.type in serviceDefaultParams:
-            for param in tuple(self._data.DirectoryService.params):
-                if param not in serviceDefaultParams[self._data.DirectoryService.type]:
-                    del self._data.DirectoryService.params[param]
-
-    @staticmethod
-    def updateACLs(self, items):
-        #
-        # Base resource ACLs
-        #
-        def readOnlyACE(allowAnonymous):
-            if allowAnonymous:
-                reader = davxml.All()
-            else:
-                reader = davxml.Authenticated()
-
-            return davxml.ACE(
-                davxml.Principal(reader),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Read()),
-                    davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                ),
-                davxml.Protected(),
-            )
-
-        self.AdminACEs = tuple(
-            davxml.ACE(
-                davxml.Principal(davxml.HRef(principal)),
-                davxml.Grant(davxml.Privilege(davxml.All())),
-                davxml.Protected(),
-                TwistedACLInheritable(),
-            )
-            for principal in config.AdminPrincipals
-        )
-
-        self.ReadACEs = tuple(
-            davxml.ACE(
-                davxml.Principal(davxml.HRef(principal)),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Read()),
-                    davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                ),
-                davxml.Protected(),
-                TwistedACLInheritable(),
-            )
-            for principal in config.ReadPrincipals
-        )
-
-        self.RootResourceACL = davxml.ACL(
-            # Read-only for anon or authenticated, depending on config
-            readOnlyACE(self.EnableAnonymousReadRoot),
-
-            # Add inheritable all access for admins
-            *self.AdminACEs
-        )
-
-        log.debug("Root ACL: %s" % (self.RootResourceACL.toxml(),))
-
-        self.ProvisioningResourceACL = davxml.ACL(
-            # Read-only for anon or authenticated, depending on config
-            readOnlyACE(self.EnableAnonymousReadNav),
-
-            # Add read and read-acl access for admins
-            *[
-                davxml.ACE(
-                    davxml.Principal(davxml.HRef(principal)),
-                    davxml.Grant(
-                        davxml.Privilege(davxml.Read()),
-                        davxml.Privilege(davxml.ReadACL()),
-                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                    ),
-                    davxml.Protected(),
-                )
-                for principal in config.AdminPrincipals
-            ]
-        )
-
-        log.debug("Nav ACL: %s" % (self.ProvisioningResourceACL.toxml(),))
-
-    @staticmethod
-    def updateRejectClients(self, items):
-        #
-        # Compile RejectClients expressions for speed
-        #
-        try:
-            self.RejectClients = [re.compile(x) for x in self.RejectClients if x]
-        except re.error, e:
-            raise ConfigurationError("Invalid regular expression in RejectClients: %s" % (e,))
-
-    @staticmethod
-    def updateDropBox(self, items):
-        #
-        # FIXME: Use the config object instead of doing this here
-        #
-        from twistedcaldav.resource import CalendarPrincipalResource
-        CalendarPrincipalResource.enableDropBox(self.EnableDropBox)
-
-    @staticmethod
-    def updateLogLevels(self, items):
-        clearLogLevels()
-
-        try:
-            if "DefaultLogLevel" in self._data:
-                level = self._data["DefaultLogLevel"]
-                if not level:
-                    level = "warn"
-                setLogLevelForNamespace(None, level)
-
-            if "LogLevels" in self._data:
-                for namespace in self._data["LogLevels"]:
-                    setLogLevelForNamespace(namespace, self._data["LogLevels"][namespace])
-
-        except InvalidLogLevelError, e:
-            raise ConfigurationError("Invalid log level: %s" % (e.level))
-
-    def updateDefaults(self, items):
-        _mergeData(self._defaults, items)
-        self.update(items)
-
+            self._defaults = ConfigDict(copy.deepcopy(defaults))
+            
+    def getDefaults(self):
+        """Return defaults"""
+        return self._defaults
+    
     def setDefaults(self, defaults):
-        if not isinstance(defaults, ConfigDict):
-            defaults = ConfigDict(defaults)
-        self._defaults = copy.deepcopy(defaults)
+        """Change defaults"""
+        self._defaults = ConfigDict(copy.deepcopy(defaults))
+    
+    def getConfigFileName(self):
+        """Return current configuration file path+name"""
+        return self._configFileName
+    
+    def setConfigFileName(self, configFileName):
+        """Change configuration file path+name for next load operations"""
+        self._configFileName = configFileName
+        if self._configFileName:
+            self._configFileName = os.path.abspath(configFileName)
+    
+    def hasErrors(self):
+        """Return true if last load operation encountered any errors"""
+        return False
+            
+    def loadConfig(self):
+        """Load the configuration, return a dictionary of settings"""
+        return self._defaults
+    
 
+class Config(object):
+
+    def __init__(self, provider=None):
+        if not provider:
+            self._provider = ConfigProvider()
+        else:
+            self._provider = provider
+        self._preUpdateHooks = set()
+        self._postUpdateHooks = set()
+        self.reset()
+        
     def __setattr__(self, attr, value):
         if "_data" in self.__dict__ and attr in self.__dict__["_data"]:
             self._data[attr] = value
@@ -633,172 +116,101 @@
     def __getattr__(self, attr):
         if attr in self._data:
             return self._data[attr]
-
         raise AttributeError(attr)
 
-    def reload(self):
-        self._reloading = True
-        log.info("Reloading configuration from file: %s" % (self._configFile,))
-        self._data = copy.deepcopy(self._defaults)
-        self.loadConfig(self._configFile)
+    def __hasattr__(self, attr):
+        return attr in self._data
+    
+    def __str__(self):
+        return str(self._data)
 
-    def loadConfig(self, configFile):
-        self._configFile = configFile
+    def get(self, attr, defaultValue):
+        parts = attr.split(".")
+        lastDict = self._data
+        for part in parts[:-1]:
+            if not part in lastDict:
+                lastDict[attr] = ConfigDict()
+            lastDict = lastDict.__getattr__(part)
+        configItem = parts[-1]
+        if configItem in lastDict:
+            return lastDict[configItem]
+        else:
+            lastDict[configItem] = defaultValue
+            return defaultValue
 
-        if configFile:
-            try:
-                configDict = readPlist(configFile)
-            except (IOError, OSError):
-                log.error("Unable to open config file: %s" % (configFile,))
-            else:
-                configDict = _cleanup(configDict)
-                self.update(ConfigDict(configDict))
+    def getInt(self, attr, defaultValue):
+        return int(self.get(attr, defaultValue))
 
-    @staticmethod
-    def updateNotifications(self, items):
-        #
-        # Notifications
-        #
-
-        # Reloading not supported -- requires process running as root
-        if getattr(self, "_reloading", False):
-            return
-
-        for key, service in self.Notifications["Services"].iteritems():
-            if service["Enabled"]:
-                self.Notifications["Enabled"] = True
-                break
+    def addPreUpdateHook(self, hook):
+        if isinstance(hook, list) or isinstance(hook, tuple):
+            self._preUpdateHooks.update(hook)
         else:
-            self.Notifications["Enabled"] = False
+            self._preUpdateHooks.add(hook)
+        
+    def addPostUpdateHook(self, hook):
+        if isinstance(hook, list) or isinstance(hook, tuple):
+            self._postUpdateHooks.update(hook)
+        else:
+            self._postUpdateHooks.add(hook)
 
-        for key, service in self.Notifications["Services"].iteritems():
-            if (
-                service["Service"] == "twistedcaldav.notify.XMPPNotifierService" and
-                service["Enabled"]
-            ):
-                # Get password from keychain.  If not there, fall back to what
-                # is in the plist.
-                try:
-                    password = getPasswordFromKeychain(service["JID"])
-                    service["Password"] = password
-                    log.info("XMPP password successfully retreived from keychain")
-                except KeychainAccessError:
-                    # The system doesn't support keychain
-                    pass
-                except KeychainPasswordNotFound:
-                    # The password doesn't exist in the keychain.
-                    log.error("XMPP password not found in keychain")
+    def getProvider(self):
+        return self._provider
+    
+    def setProvider(self, provider):
+        self._provider = provider
+        self.reset()
 
-                # Check for empty fields
-                for key, value in service.iteritems():
-                    if not value and key not in ("AllowedJIDs", "HeartbeatMinutes", "Password"):
-                        raise ConfigurationError("Invalid %s for XMPPNotifierService: %r"
-                                                 % (key, value))
+    def setDefaults(self, defaults):
+        self._provider.setDefaults(defaults)
+        self.reset()
 
-    @staticmethod
-    def updateScheduling(self, items):
-        #
-        # Scheduling
-        #
+    def updateDefaults(self, items):
+        _mergeData(self._provider.getDefaults(), items)
+        self.update(items)
 
-        # Reloading not supported -- requires process running as root
-        if getattr(self, "_reloading", False):
-            return
+    def update(self, items):
+        if not isinstance(items, ConfigDict):
+            items = ConfigDict(items)
+        # Call hooks
+        for hook in self._preUpdateHooks:
+            hook(self._data, items)
+        _mergeData(self._data, items)
+        for hook in self._postUpdateHooks:
+            hook(self._data)
 
-        service = self.Scheduling["iMIP"]
+    def load(self, configFile):
+        self._provider.setConfigFileName(configFile)
+        configDict = ConfigDict(self._provider.loadConfig())
+        if not self._provider.hasErrors():
+            self.update(configDict)
+        else:
+            raise ConfigurationError("Invalid configuration in %s"
+                % (self._provider.getConfigFileName(), ))
 
-        if service["Enabled"]:
+    def reload(self):
+        configDict = ConfigDict(self._provider.loadConfig())
+        configDict._reloading = True
+        if not self._provider.hasErrors():
+            self.reset()
+            self.update(configDict)
+        else:
+            raise ConfigurationError("Invalid configuration in %s"
+                % (self._provider.getConfigFileName(), ))
 
-            # Get password for the user that is allowed to inject iMIP replies
-            # to the server's /inbox; if not available, fall back to plist
-            if service["Username"]:
-                try:
-                    service["Password"] = getPasswordFromKeychain(service["Username"])
-                except KeychainAccessError:
-                    # The system doesn't support keychain
-                    pass
-                except KeychainPasswordNotFound:
-                    # The password doesn't exist in the keychain.
-                    log.info("iMIP injecting password not found in keychain")
+    def reset(self):
+        self._data = ConfigDict(copy.deepcopy(self._provider.getDefaults()))
 
-            for direction in ("Sending", "Receiving"):
-                if service[direction].Username:
-                    # Get password from keychain.  If not there, fall back to
-                    # what is in the plist.
-                    try:
-                        account = "%s@%s" % (
-                            service[direction].Username,
-                            service[direction].Server
-                        )
-                        password = getPasswordFromKeychain(account)
-                        service[direction]["Password"] = password
-                        log.info("iMIP %s password successfully retreived from keychain" % (direction,))
-                    except KeychainAccessError:
-                        # The system doesn't support keychain
-                        pass
-                    except KeychainPasswordNotFound:
-                        # The password doesn't exist in the keychain.
-                        log.info("iMIP %s password not found in keychain" %
-                            (direction,))
 
-
 def _mergeData(oldData, newData):
     for key, value in newData.iteritems():
         if isinstance(value, (dict,)):
             if key in oldData:
-                assert isinstance(oldData[key], ConfigDict), "%r in %r is not a ConfigDict" % (oldData[key], oldData)
+                assert isinstance(oldData[key], ConfigDict), \
+                    "%r in %r is not a ConfigDict" % (oldData[key], oldData)
             else:
                 oldData[key] = {}
             _mergeData(oldData[key], value)
         else:
             oldData[key] = value
 
-def _cleanup(configDict):
-    cleanDict = copy.deepcopy(configDict)
-
-    def unknown(key):
-        log.err("Ignoring unknown configuration option: %r" % (key,))
-        del cleanDict[key]
-
-    def deprecated(oldKey, newKey):
-        log.err("Configuration option %r is deprecated in favor of %r." % (oldKey, newKey))
-        if oldKey in configDict and newKey in configDict:
-            raise ConfigurationError(
-                "Both %r and %r options are specified; use the %r option only."
-                % (oldKey, newKey, newKey)
-            )
-
-    def renamed(oldKey, newKey):
-        deprecated(oldKey, newKey)
-        cleanDict[newKey] = configDict[oldKey]
-        del cleanDict[oldKey]
-
-    renamedOptions = {
-#       "BindAddress": "BindAddresses",
-    }
-
-    for key in configDict:
-        if key in defaultConfig:
-            continue
-
-        elif key in renamedOptions:
-            renamed(key, renamedOptions[key])
-
-#       elif key == "pydirConfig":
-#           deprecated(key, "PythonDirector -> pydir")
-#           if "PythonDirector" not in cleanDict:
-#               cleanDict["PythonDirector"] = {}
-#           cleanDict["PythonDirector"]["ConfigFile"] = cleanDict["pydirConfig"]
-#           del cleanDict["pydirConfig"]
-
-        else:
-            unknown(key,)
-
-    return cleanDict
-
-class ConfigurationError (RuntimeError):
-    """
-    Invalid server configuration.
-    """
-
-config = Config(defaultConfig)
+config = Config()

Modified: CalendarServer/branches/config-separation/twistedcaldav/mail.py
===================================================================
--- CalendarServer/branches/config-separation/twistedcaldav/mail.py	2009-06-25 17:37:51 UTC (rev 4378)
+++ CalendarServer/branches/config-separation/twistedcaldav/mail.py	2009-06-25 17:52:06 UTC (rev 4379)
@@ -48,7 +48,7 @@
 
 from twistedcaldav import ical, caldavxml
 from twistedcaldav import memcachepool
-from twistedcaldav.config import config, defaultConfig, defaultConfigFile
+from twistedcaldav.config import config
 from twistedcaldav.directory.digest import QopDigestCredentialFactory
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
 from twistedcaldav.directory.util import NotFilePath
@@ -60,6 +60,9 @@
 from twistedcaldav.sql import AbstractSQLDatabase
 from twistedcaldav.static import CalDAVFile, deliverSchedulePrivilegeSet
 from twistedcaldav.util import AuthorizedHTTPGetter
+from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
+from twistedcaldav.sql import AbstractSQLDatabase
+from twistedcaldav.localization import translationTo
 
 from zope.interface import implements
 
@@ -91,7 +94,7 @@
 
 class MailGatewayOptions(Options):
     optParameters = [[
-        "config", "f", defaultConfigFile, "Path to configuration file."
+        "config", "f", DEFAULT_CONFIG_FILE, "Path to configuration file."
     ]]
 
     def __init__(self, *args, **kwargs):
@@ -159,7 +162,7 @@
         if "=" in option:
             path, value = option.split('=')
             self._setOverride(
-                defaultConfig,
+                DEFAULT_CONFIG,
                 path.split('/'),
                 value,
                 self.overrides
@@ -170,7 +173,7 @@
     opt_o = opt_option
 
     def postOptions(self):
-        config.loadConfig(self['config'])
+        config.load(self['config'])
         config.updateDefaults(self.overrides)
         self.parent['pidfile'] = None
 

Modified: CalendarServer/branches/config-separation/twistedcaldav/notify.py
===================================================================
--- CalendarServer/branches/config-separation/twistedcaldav/notify.py	2009-06-25 17:37:51 UTC (rev 4378)
+++ CalendarServer/branches/config-separation/twistedcaldav/notify.py	2009-06-25 17:52:06 UTC (rev 4379)
@@ -49,8 +49,9 @@
 from twisted.words.protocols.jabber.xmlstream import IQ
 from twisted.words.xish import domish
 from twistedcaldav.log import LoggingMixIn
-from twistedcaldav.config import config, defaultConfig, defaultConfigFile
+from twistedcaldav.config import config
 from twistedcaldav.memcacher import Memcacher
+from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
 from twistedcaldav import memcachepool
 from zope.interface import Interface, implements
 from fnmatch import fnmatch
@@ -1199,7 +1200,7 @@
 
 class NotificationOptions(Options):
     optParameters = [[
-        "config", "f", defaultConfigFile, "Path to configuration file."
+        "config", "f", DEFAULT_CONFIG_FILE, "Path to configuration file."
     ]]
 
     def __init__(self, *args, **kwargs):
@@ -1267,7 +1268,7 @@
         if "=" in option:
             path, value = option.split('=')
             self._setOverride(
-                defaultConfig,
+                DEFAULT_CONFIG,
                 path.split('/'),
                 value,
                 self.overrides
@@ -1278,7 +1279,7 @@
     opt_o = opt_option
 
     def postOptions(self):
-        config.loadConfig(self['config'])
+        config.load(self['config'])
         config.updateDefaults(self.overrides)
         self.parent['pidfile'] = None
 

Added: CalendarServer/branches/config-separation/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/config-separation/twistedcaldav/stdconfig.py	                        (rev 0)
+++ CalendarServer/branches/config-separation/twistedcaldav/stdconfig.py	2009-06-25 17:52:06 UTC (rev 4379)
@@ -0,0 +1,698 @@
+##
+# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import os
+import copy
+import re
+
+from twisted.web2.dav import davxml
+from twisted.web2.dav.resource import TwistedACLInheritable
+
+from twext.python.plistlib import readPlist
+
+from twistedcaldav.config import (
+    ConfigProvider, ConfigurationError, config, _mergeData, )
+from twistedcaldav.log import (
+    Logger, clearLogLevels, setLogLevelForNamespace, InvalidLogLevelError, )
+from twistedcaldav.util import (
+    KeychainAccessError, KeychainPasswordNotFound, getPasswordFromKeychain, )
+
+log = Logger()
+
+DEFAULT_CONFIG_FILE = "/etc/caldavd/caldavd.plist"
+
+DEFAULT_SERVICE_PARAMS = {
+    "twistedcaldav.directory.xmlfile.XMLDirectoryService": {
+        "xmlFile": "/etc/caldavd/accounts.xml",
+    },
+    "twistedcaldav.directory.appleopendirectory.OpenDirectoryService": {
+        "node": "/Search",
+        "restrictEnabledRecords": False,
+        "restrictToGroup": "",
+        "cacheTimeout": 30,
+    },
+}
+
+DEFAULT_CONFIG = {
+    # Note: Don't use None values below; that confuses the command-line parser.
+
+    #
+    # Public network address information
+    #
+    #    This is the server's public network address, which is provided to
+    #    clients in URLs and the like.  It may or may not be the network
+    #    address that the server is listening to directly, though it is by
+    #    default.  For example, it may be the address of a load balancer or
+    #    proxy which forwards connections to the server.
+    #
+    "ServerHostName": "", # Network host name.
+    "HTTPPort": 0,        # HTTP port (0 to disable HTTP)
+    "SSLPort" : 0,        # SSL port (0 to disable HTTPS)
+    "RedirectHTTPToHTTPS" : False, # If True, all nonSSL requests redirected to an SSL Port
+    "SSLMethod" : "SSLv3_METHOD", # SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD
+
+    #
+    # Network address configuration information
+    #
+    #    This configures the actual network address that the server binds to.
+    #
+    "BindAddresses": [],   # List of IP addresses to bind to [empty = all]
+    "BindHTTPPorts": [],   # List of port numbers to bind to for HTTP [empty = same as "Port"]
+    "BindSSLPorts" : [],   # List of port numbers to bind to for SSL [empty = same as "SSLPort"]
+
+    #
+    # Data store
+    #
+    "DataRoot"                : "/Library/CalendarServer/Data",
+    "DocumentRoot"            : "/Library/CalendarServer/Documents",
+    "UserQuota"               : 104857600, # User quota (in bytes)
+    "MaximumAttachmentSize"   :   1048576, # Attachment size limit (in bytes)
+    "MaxAttendeesPerInstance" :       100, # Maximum number of unique attendees
+    "MaxInstancesForRRULE"    :       400, # Maximum number of instances for an RRULE
+    "WebCalendarRoot"         : "/usr/share/collaboration",
+
+    "Aliases": {},
+
+    #
+    # Directory service
+    #
+    #    A directory service provides information about principals (eg.
+    #    users, groups, locations and resources) to the server.
+    #
+    "DirectoryService": {
+        "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService",
+        "params": DEFAULT_SERVICE_PARAMS["twistedcaldav.directory.xmlfile.XMLDirectoryService"],
+    },
+
+    #
+    # Special principals
+    #
+    "AdminPrincipals": [],                       # Principals with "DAV:all" access (relative URLs)
+    "ReadPrincipals": [],                        # Principals with "DAV:read" access (relative URLs)
+    "SudoersFile": "/etc/caldavd/sudoers.plist", # Principals that can pose as other principals
+    "EnableProxyPrincipals": True,               # Create "proxy access" principals
+
+    #
+    # Permissions
+    #
+    "EnableAnonymousReadRoot": True,    # Allow unauthenticated read access to /
+    "EnableAnonymousReadNav": False,    # Allow unauthenticated read access to hierachcy
+    "EnablePrincipalListings": True,    # Allow listing of principal collections
+    "EnableMonolithicCalendars": True,  # Render calendar collections as a monolithic iCalendar object
+
+    #
+    # Client controls
+    #
+    "RejectClients": [], # List of regexes for clients to disallow
+
+    #
+    # Authentication
+    #
+    "Authentication": {
+        "Basic": { "Enabled": False },     # Clear text; best avoided
+        "Digest": {                        # Digest challenge/response
+            "Enabled": True,
+            "Algorithm": "md5",
+            "Qop": "",
+        },
+        "Kerberos": {                       # Kerberos/SPNEGO
+            "Enabled": False,
+            "ServicePrincipal": ""
+        },
+        "Wiki": {
+            "Enabled": False,
+            "Cookie": "sessionID",
+            "URL": "http://127.0.0.1:8086/RPC2",
+            "UserMethod": "userForSession",
+            "WikiMethod": "accessLevelForUserWikiCalendar",
+        },
+    },
+
+    #
+    # Logging
+    #
+    "AccessLogFile"  : "/var/log/caldavd/access.log",  # Apache-style access log
+    "ErrorLogFile"   : "/var/log/caldavd/error.log",   # Server activity log
+    "ServerStatsFile": "/var/run/caldavd/stats.plist",
+    "PIDFile"        : "/var/run/caldavd.pid",
+    "RotateAccessLog"   : False,
+    "EnableExtendedAccessLog": True,
+    "DefaultLogLevel"   : "",
+    "LogLevels"         : {},
+
+    "AccountingCategories": {
+        "iTIP": False,
+    },
+    "AccountingPrincipals": [],
+    "AccountingLogRoot"   : "/var/log/caldavd/accounting",
+
+    "GlobalStatsSocket"           : "/var/run/caldavd-stats.sock", 
+    "GlobalStatsLoggingPeriod"    : 60, 
+    "GlobalStatsLoggingFrequency" : 12, 
+
+    #
+    # SSL/TLS
+    #
+    "SSLCertificate"     : "",  # Public key
+    "SSLPrivateKey"      : "",  # Private key
+    "SSLAuthorityChain"  : "",  # Certificate Authority Chain
+    "SSLPassPhraseDialog": "/etc/apache2/getsslpassphrase",
+    "SSLCertAdmin"       : "/usr/sbin/certadmin",
+
+    #
+    # Process management
+    #
+
+    # Username and Groupname to drop privileges to, if empty privileges will
+    # not be dropped.
+
+    "UserName": "",
+    "GroupName": "",
+    "ProcessType": "Combined",
+    "MultiProcess": {
+        "ProcessCount": 0,
+        "MinProcessCount": 4,
+        "LoadBalancer": {
+            "Enabled": True,
+            "Scheduler": "LeastConnections",
+        },
+        "StaggeredStartup": {
+            "Enabled": False,
+            "Interval": 15,
+        },
+    },
+
+    #
+    # Service ACLs
+    #
+    "EnableSACLs": False,
+
+    #
+    # Non-standard CalDAV extensions
+    #
+    "EnableDropBox"           : False, # Calendar Drop Box
+    "EnablePrivateEvents"     : False, # Private Events
+    "EnableTimezoneService"   : False, # Timezone service
+
+    #
+    # Web-based administration
+    #
+    "EnableWebAdmin"          : True,
+
+    #
+    # Scheduling related options
+    #
+
+    "Scheduling": {
+        
+        "CalDAV": {
+            "EmailDomain"                : "",    # Domain for mailto calendar user addresses on this server
+            "HTTPDomain"                 : "",    # Domain for http calendar user addresses on this server
+            "AddressPatterns"            : [],    # Reg-ex patterns to match local calendar user addresses
+            "OldDraftCompatibility"      : True,  # Whether to maintain compatibility with non-implicit mode
+            "ScheduleTagCompatibility"   : True,  # Whether to support older clients that do not use Schedule-Tag feature
+            "EnablePrivateComments"      : True,  # Private comments from attendees to organizer
+        },
+
+        "iSchedule": {
+            "Enabled"          : False, # iSchedule protocol
+            "AddressPatterns"  : [],    # Reg-ex patterns to match iSchedule-able calendar user addresses
+            "Servers"          : "/etc/caldavd/servertoserver.xml",    # iSchedule server configurations
+        },
+
+        "iMIP": {
+            "Enabled"          : False, # Server-to-iMIP protocol
+            "MailGatewayServer" : "localhost",
+            "MailGatewayPort"   : 62310,
+            "Username"          : "com.apple.calendarserver", # For account injecting replies
+            "Password"          : "",    # For account injecting replies
+            "Sending": {
+                "Server"        : "",    # SMTP server to relay messages through
+                "Port"          : 587,   # SMTP server port to relay messages through
+                "Address"       : "",    # 'From' address for server
+                "UseSSL"        : True,
+                "Username"      : "",    # For account sending mail
+                "Password"      : "",    # For account sending mail
+            },
+            "Receiving": {
+                "Server"        : "",    # Server to retrieve email messages from
+                "Port"          : 0,     # Server port to retrieve email messages from
+                "UseSSL"        : True,
+                "Type"          : "",    # Type of message access server: 'pop' or 'imap'
+                "PollingSeconds"    : 30,  # How often to fetch mail
+                "Username"      : "",    # For account receving mail
+                "Password"      : "",    # For account receving mail
+            },
+            "AddressPatterns"   : [],    # Reg-ex patterns to match iMIP-able calendar user addresses
+            "MailTemplatesDirectory": "/usr/share/caldavd/share/email_templates", # Directory containing HTML templates for email invitations (invite.html, cancel.html)
+            "MailIconsDirectory": "/usr/share/caldavd/share/date_icons", # Directory containing language-specific subdirectories containing date-specific icons for email invitations
+            "InvitationDaysToLive" : 90, # How many days invitations are valid
+        },
+
+        "Options" : {
+            "AllowGroupAsOrganizer"      : False, # Allow groups to be Organizers
+            "AllowLocationAsOrganizer"   : False, # Allow locations to be Organizers
+            "AllowResourceAsOrganizer"   : False, # Allow resources to be Organizers
+        }
+    },
+
+    "FreeBusyURL": {
+        "Enabled"          : False, # Per-user free-busy-url protocol
+        "TimePeriod"       : 14,    # Number of days into the future to generate f-b data if no explicit time-range is specified
+        "AnonymousAccess"  : False, # Allow anonymous read access to free-busy URL
+    },
+
+    #
+    # Notifications
+    #
+    "Notifications" : {
+        "Enabled": False,
+        "CoalesceSeconds" : 3,
+        "InternalNotificationHost" : "localhost",
+        "InternalNotificationPort" : 62309,
+        "BindAddress" : "127.0.0.1",
+
+        "Services" : {
+            "SimpleLineNotifier" : {
+                "Service" : "twistedcaldav.notify.SimpleLineNotifierService",
+                "Enabled" : False,
+                "Port" : 62308,
+            },
+            "XMPPNotifier" : {
+                "Service" : "twistedcaldav.notify.XMPPNotifierService",
+                "Enabled" : False,
+                "Host" : "", # "xmpp.host.name"
+                "Port" : 5222,
+                "JID" : "", # "jid at xmpp.host.name/resource"
+                "Password" : "",
+                "ServiceAddress" : "", # "pubsub.xmpp.host.name"
+                "NodeConfiguration" : {
+                    "pubsub#deliver_payloads" : "1",
+                    "pubsub#persist_items" : "1",
+                },
+                "KeepAliveSeconds" : 120,
+                "HeartbeatMinutes" : 30,
+                "AllowedJIDs": [],
+            },
+        }
+    },
+
+    #
+    # Performance tuning
+    #
+
+    # Set the maximum number of outstanding requests to this server.
+    "MaxRequests": 600,
+
+    "ListenBacklog": 50,
+    "IdleConnectionTimeOut": 15,
+    "UIDReservationTimeOut": 30 * 60,
+
+
+    #
+    # Localization
+    #
+    "Localization" : {
+        "TranslationsDirectory" : "/usr/share/caldavd/share/translations",
+        "LocalesDirectory" : "/usr/share/caldavd/share/locales",
+        "Language" : "English",
+    },
+
+
+    #
+    # Implementation details
+    #
+    #    The following are specific to how the server is built, and useful
+    #    for development, but shouldn't be needed by users.
+    #
+
+    # Twisted
+    "Twisted": {
+        "twistd": "/usr/share/caldavd/bin/twistd",
+        "reactor": "select",
+    },
+
+    # Python Director
+    "PythonDirector": {
+        "pydir": "/usr/share/caldavd/bin/pydir.py",
+        "ConfigFile": "/etc/caldavd/pydir.xml",
+        "ControlSocket": "/var/run/caldavd-pydir.sock",
+    },
+
+    # Umask
+    "umask": 0027,
+
+    # A TCP port used for communication between the child and master
+    # processes (bound to 127.0.0.1). Specify 0 to let OS assign a port.
+    "ControlPort": 0,
+
+    # A unix socket used for communication between the child and master
+    # processes. If blank, then an AF_INET socket is used instead.
+    "ControlSocket": "/var/run/caldavd.sock",
+
+
+    # Support for Content-Encoding compression options as specified in
+    # RFC2616 Section 3.5
+    "ResponseCompression": True,
+
+    # The retry-after value (in seconds) to return with a 503 error
+    "HTTPRetryAfter": 180,
+
+    # Profiling options
+    "Profiling": {
+        "Enabled": False,
+        "BaseDirectory": "/tmp/stats",
+    },
+
+    "Memcached": {
+        "MaxClients": 5,
+        "ClientEnabled": True,
+        "ServerEnabled": True,
+        "BindAddress": "127.0.0.1",
+        "Port": 11211,
+        "memcached": "memcached", # Find in PATH
+        "MaxMemory": 0, # Megabytes
+        "Options": [],
+    },
+
+    "EnableKeepAlive": True,
+    "ResponseCacheTimeout": 30, # Minutes
+}
+
+class PListConfigProvider(ConfigProvider):
+    
+    def loadConfig(self):
+        configDict = {}
+        if self._configFileName:
+            try:
+                configDict = readPlist(self._configFileName)
+            except (IOError, OSError):                                    
+                log.error("Configuration file does not exist or is inaccessible: %s" % (self._configFileName,))
+            else:
+                configDict = _cleanup(configDict, self._defaults)
+        return configDict
+
+
+def _updateHostName(configDict):
+    if not configDict.ServerHostName:
+        from socket import getfqdn
+        hostname = getfqdn()
+        if not hostname:
+            hostname = "localhost"
+        configDict.ServerHostName = hostname
+
+def _preUpdateDirectoryService(configDict, items):
+    # Special handling for directory services configs
+    dsType = items.get("DirectoryService", {}).get("type", None)
+    if dsType is None:
+        dsType = configDict.DirectoryService.type
+    else:
+        if dsType == configDict.DirectoryService.type:
+            oldParams = configDict.DirectoryService.params
+            newParams = items.DirectoryService.get("params", {})
+            _mergeData(oldParams, newParams)
+        else:
+            if dsType in DEFAULT_SERVICE_PARAMS:
+                configDict.DirectoryService.params = copy.deepcopy(DEFAULT_SERVICE_PARAMS[dsType])
+            else:
+                configDict.DirectoryService.params = {}
+
+    for param in items.get("DirectoryService", {}).get("params", {}):
+        if dsType in DEFAULT_SERVICE_PARAMS and param not in DEFAULT_SERVICE_PARAMS[dsType]:
+            log.warn("Parameter %s is not supported by service %s" % (param, dsType))
+            
+def _postUpdateDirectoryService(configDict):
+    if configDict.DirectoryService.type in DEFAULT_SERVICE_PARAMS:
+        for param in tuple(configDict.DirectoryService.params):
+            if param not in DEFAULT_SERVICE_PARAMS[configDict.DirectoryService.type]:
+                del configDict.DirectoryService.params[param]
+
+def _updateACLs(configDict):
+    #
+    # Base resource ACLs
+    #
+    def readOnlyACE(allowAnonymous):
+        if allowAnonymous:
+            reader = davxml.All()
+        else:
+            reader = davxml.Authenticated()
+
+        return davxml.ACE(
+            davxml.Principal(reader),
+            davxml.Grant(
+                davxml.Privilege(davxml.Read()),
+                davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+            ),
+            davxml.Protected(),
+        )
+
+    configDict.AdminACEs = tuple(
+        davxml.ACE(
+            davxml.Principal(davxml.HRef(principal)),
+            davxml.Grant(davxml.Privilege(davxml.All())),
+            davxml.Protected(),
+            TwistedACLInheritable(),
+        )
+        for principal in configDict.AdminPrincipals
+    )
+
+    configDict.ReadACEs = tuple(
+        davxml.ACE(
+            davxml.Principal(davxml.HRef(principal)),
+            davxml.Grant(
+                davxml.Privilege(davxml.Read()),
+                davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+            ),
+            davxml.Protected(),
+            TwistedACLInheritable(),
+        )
+        for principal in configDict.ReadPrincipals
+    )
+
+    configDict.RootResourceACL = davxml.ACL(
+        # Read-only for anon or authenticated, depending on config
+        readOnlyACE(configDict.EnableAnonymousReadRoot),
+
+        # Add inheritable all access for admins
+        *configDict.AdminACEs
+    )
+
+    log.debug("Root ACL: %s" % (configDict.RootResourceACL.toxml(),))
+
+    configDict.ProvisioningResourceACL = davxml.ACL(
+        # Read-only for anon or authenticated, depending on config
+        readOnlyACE(configDict.EnableAnonymousReadNav),
+
+        # Add read and read-acl access for admins
+        *[
+            davxml.ACE(
+                davxml.Principal(davxml.HRef(principal)),
+                davxml.Grant(
+                    davxml.Privilege(davxml.Read()),
+                    davxml.Privilege(davxml.ReadACL()),
+                    davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+                ),
+                davxml.Protected(),
+            )
+            for principal in configDict.AdminPrincipals
+        ]
+    )
+
+    log.debug("Nav ACL: %s" % (configDict.ProvisioningResourceACL.toxml(),))
+
+def _updateRejectClients(configDict):
+    #
+    # Compile RejectClients expressions for speed
+    #
+    try:
+        configDict.RejectClients = [re.compile(x) for x in configDict.RejectClients if x]
+    except re.error, e:
+        raise ConfigurationError("Invalid regular expression in RejectClients: %s" % (e,))
+
+def _updateDropBox(configDict):
+    #
+    # FIXME: Use the config object instead of doing this here
+    #
+    from twistedcaldav.resource import CalendarPrincipalResource
+    CalendarPrincipalResource.enableDropBox(configDict.EnableDropBox)
+
+def _updateLogLevels(configDict):
+    clearLogLevels()
+
+    try:
+        if "DefaultLogLevel" in configDict:
+            level = configDict["DefaultLogLevel"]
+            if not level:
+                level = "warn"
+            setLogLevelForNamespace(None, level)
+
+        if "LogLevels" in configDict:
+            for namespace in configDict["LogLevels"]:
+                setLogLevelForNamespace(namespace, configDict["LogLevels"][namespace])
+
+    except InvalidLogLevelError, e:
+        raise ConfigurationError("Invalid log level: %s" % (e.level))
+
+def _updateNotifications(configDict):
+    #
+    # Notifications
+    #
+
+    # Reloading not supported -- requires process running as root
+    if getattr(configDict, "_reloading", False):
+        return
+
+    for key, service in configDict.Notifications["Services"].iteritems():
+        if service["Enabled"]:
+            configDict.Notifications["Enabled"] = True
+            break
+    else:
+        configDict.Notifications["Enabled"] = False
+
+    for key, service in configDict.Notifications["Services"].iteritems():
+        if (
+            service["Service"] == "twistedcaldav.notify.XMPPNotifierService" and
+            service["Enabled"]
+        ):
+            # Get password from keychain.  If not there, fall back to what
+            # is in the plist.
+            try:
+                password = getPasswordFromKeychain(service["JID"])
+                service["Password"] = password
+                log.info("XMPP password successfully retreived from keychain")
+            except KeychainAccessError:
+                # The system doesn't support keychain
+                pass
+            except KeychainPasswordNotFound:
+                # The password doesn't exist in the keychain.
+                log.error("XMPP password not found in keychain")
+
+            # Check for empty fields
+            for key, value in service.iteritems():
+                if not value and key not in ("AllowedJIDs", "HeartbeatMinutes", "Password"):
+                    raise ConfigurationError("Invalid %s for XMPPNotifierService: %r"
+                                             % (key, value))
+
+def _updateScheduling(configDict):
+    #
+    # Scheduling
+    #
+
+    # Reloading not supported -- requires process running as root
+    if getattr(configDict, "_reloading", False):
+        return
+
+    service = configDict.Scheduling["iMIP"]
+
+    if service["Enabled"]:
+
+        # Get password for the user that is allowed to inject iMIP replies
+        # to the server's /inbox; if not available, fall back to plist
+        if service["Username"]:
+            try:
+                service["Password"] = getPasswordFromKeychain(service["Username"])
+            except KeychainAccessError:
+                # The system doesn't support keychain
+                pass
+            except KeychainPasswordNotFound:
+                # The password doesn't exist in the keychain.
+                log.info("iMIP injecting password not found in keychain")
+
+        for direction in ("Sending", "Receiving"):
+            if service[direction].Username:
+                # Get password from keychain.  If not there, fall back to
+                # what is in the plist.
+                try:
+                    account = "%s@%s" % (
+                        service[direction].Username,
+                        service[direction].Server
+                    )
+                    password = getPasswordFromKeychain(account)
+                    service[direction]["Password"] = password
+                    log.info("iMIP %s password successfully retreived from keychain" % (direction,))
+                except KeychainAccessError:
+                    # The system doesn't support keychain
+                    pass
+                except KeychainPasswordNotFound:
+                    # The password doesn't exist in the keychain.
+                    log.info("iMIP %s password not found in keychain" %
+                        (direction,))
+    
+PRE_UPDATE_HOOKS = (
+    _preUpdateDirectoryService,
+    )
+POST_UPDATE_HOOKS = (
+    _updateHostName,
+    _postUpdateDirectoryService,
+    _updateACLs,
+    _updateRejectClients,
+    _updateDropBox,
+    _updateLogLevels,
+    _updateNotifications,
+    _updateScheduling,
+    )
+    
+def _cleanup(configDict, defaultDict):
+    cleanDict = copy.deepcopy(configDict)
+
+    def unknown(key):
+        config_key = "ICAL_SERVER_CONFIG_VALIDATION"
+        config_key_value = "loose"
+        if config_key in os.environ and os.environ[config_key] == config_key_value:
+            pass
+        else:
+            log.err("Ignoring unknown configuration option: %r - you can optionally bypass this validation by setting a sys env var %s to '%s'" % (key, config_key, config_key_value))
+            del cleanDict[key]
+
+    def deprecated(oldKey, newKey):
+        log.err("Configuration option %r is deprecated in favor of %r." % (oldKey, newKey))
+        if oldKey in configDict and newKey in configDict:
+            raise ConfigurationError(
+                "Both %r and %r options are specified; use the %r option only."
+                % (oldKey, newKey, newKey)
+            )
+
+    def renamed(oldKey, newKey):
+        deprecated(oldKey, newKey)
+        cleanDict[newKey] = configDict[oldKey]
+        del cleanDict[oldKey]
+
+    renamedOptions = {
+#       "BindAddress": "BindAddresses",
+    }
+
+    for key in configDict:
+        if key in defaultDict:
+            continue
+
+        elif key in renamedOptions:
+            renamed(key, renamedOptions[key])
+
+#       elif key == "pydirConfig":
+#           deprecated(key, "PythonDirector -> pydir")
+#           if "PythonDirector" not in cleanDict:
+#               cleanDict["PythonDirector"] = {}
+#           cleanDict["PythonDirector"]["ConfigFile"] = cleanDict["pydirConfig"]
+#           del cleanDict["pydirConfig"]
+
+        else:
+            unknown(key,)
+
+    return cleanDict
+
+config.setProvider(PListConfigProvider(DEFAULT_CONFIG))
+config.addPreUpdateHook(PRE_UPDATE_HOOKS)
+config.addPostUpdateHook(POST_UPDATE_HOOKS)

Modified: CalendarServer/branches/config-separation/twistedcaldav/test/test_config.py
===================================================================
--- CalendarServer/branches/config-separation/twistedcaldav/test/test_config.py	2009-06-25 17:37:51 UTC (rev 4378)
+++ CalendarServer/branches/config-separation/twistedcaldav/test/test_config.py	2009-06-25 17:52:06 UTC (rev 4379)
@@ -17,8 +17,9 @@
 from twext.python.plistlib import writePlist
 
 from twistedcaldav.log import logLevelForNamespace
-from twistedcaldav.config import config, defaultConfig
+from twistedcaldav.config import config, ConfigProvider
 from twistedcaldav.static import CalDAVFile
+from twistedcaldav.stdconfig import DEFAULT_CONFIG, PListConfigProvider
 from twistedcaldav.test.util import TestCase
 
 testConfig = """<?xml version="1.0" encoding="UTF-8"?>
@@ -51,30 +52,29 @@
 class ConfigTests(TestCase):
     def setUp(self):
         TestCase.setUp(self)
-        config.update(defaultConfig)
+        config.setProvider(PListConfigProvider(DEFAULT_CONFIG))
         self.testConfig = self.mktemp()
         open(self.testConfig, "w").write(testConfig)
 
     def tearDown(self):
-        config.setDefaults(defaultConfig)
-        config.loadConfig(None)
-        config.reload()
+        config.setProvider(ConfigProvider(DEFAULT_CONFIG))
+        config.load(None)
 
     def testDefaults(self):
-        for key, value in defaultConfig.iteritems():
+        for key, value in DEFAULT_CONFIG.iteritems():
             self.assertEquals(getattr(config, key), value)
 
     def testLoadConfig(self):
         self.assertEquals(config.ResponseCompression, True)
 
-        config.loadConfig(self.testConfig)
+        config.load(self.testConfig)
 
         self.assertEquals(config.ResponseCompression, False)
 
     def testScoping(self):
         self.assertEquals(config.ResponseCompression, True)
 
-        config.loadConfig(self.testConfig)
+        config.load(self.testConfig)
 
         self.assertEquals(config.ResponseCompression, False)
 
@@ -83,7 +83,7 @@
     def testReloading(self):
         self.assertEquals(config.HTTPPort, 0)
 
-        config.loadConfig(self.testConfig)
+        config.load(self.testConfig)
 
         self.assertEquals(config.HTTPPort, 8008)
 
@@ -96,7 +96,7 @@
     def testUpdateAndReload(self):
         self.assertEquals(config.HTTPPort, 0)
 
-        config.loadConfig(self.testConfig)
+        config.load(self.testConfig)
 
         self.assertEquals(config.HTTPPort, 8008)
 
@@ -191,7 +191,7 @@
     def testUpdateDefaults(self):
         self.assertEquals(config.SSLPort, 0)
 
-        config.loadConfig(self.testConfig)
+        config.load(self.testConfig)
 
         config.updateDefaults({"SSLPort": 8009})
 
@@ -206,12 +206,12 @@
     def testMergeDefaults(self):
         config.updateDefaults({"MultiProcess": {}})
 
-        self.assertEquals(config._defaults["MultiProcess"]["LoadBalancer"]["Enabled"], True)
+        self.assertEquals(config._provider.getDefaults()["MultiProcess"]["LoadBalancer"]["Enabled"], True)
 
     def testSetDefaults(self):
         config.updateDefaults({"SSLPort": 8443})
 
-        config.setDefaults(defaultConfig)
+        config.setDefaults(DEFAULT_CONFIG)
 
         config.reload()
 
@@ -220,7 +220,7 @@
     def testCopiesDefaults(self):
         config.updateDefaults({"Foo": "bar"})
 
-        self.assertNotIn("Foo", defaultConfig)
+        self.assertNotIn("Foo", DEFAULT_CONFIG)
 
     def testComplianceClasses(self):
         resource = CalDAVFile("/")
@@ -238,7 +238,7 @@
         self.assertEquals(logLevelForNamespace(None), "warn")
         self.assertEquals(logLevelForNamespace("some.namespace"), "warn")
 
-        config.loadConfig(self.testConfig)
+        config.load(self.testConfig)
 
         self.assertEquals(logLevelForNamespace(None), "info")
         self.assertEquals(logLevelForNamespace("some.namespace"), "debug")

Modified: CalendarServer/branches/config-separation/twistedcaldav/test/test_notify.py
===================================================================
--- CalendarServer/branches/config-separation/twistedcaldav/test/test_notify.py	2009-06-25 17:37:51 UTC (rev 4378)
+++ CalendarServer/branches/config-separation/twistedcaldav/test/test_notify.py	2009-06-25 17:52:06 UTC (rev 4379)
@@ -20,6 +20,7 @@
 from twistedcaldav.notify import *
 from twistedcaldav import config as config_mod
 from twistedcaldav.config import Config
+from twistedcaldav.stdconfig import DEFAULT_CONFIG, PListConfigProvider
 from twistedcaldav.test.util import TestCase
 
 
@@ -42,7 +43,7 @@
         notificationClient = getNotificationClient()
         self.assertNotEquals(notificationClient, None)
 
-        enabledConfig = Config(config_mod.defaultConfig)
+        enabledConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
         enabledConfig.Notifications['Enabled'] = True
         clientNotifier = ClientNotifier(StubResource("a"),
             configOverride=enabledConfig)
@@ -342,12 +343,12 @@
 
 class XMPPNotifierTests(TestCase):
 
-    xmppEnabledConfig = Config(config_mod.defaultConfig)
+    xmppEnabledConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
     xmppEnabledConfig.Notifications['Services']['XMPPNotifier']['Enabled'] = True
     xmppEnabledConfig.ServerHostName = "server.example.com"
     xmppEnabledConfig.HTTPPort = 80
 
-    xmppDisabledConfig = Config(config_mod.defaultConfig)
+    xmppDisabledConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
     xmppDisabledConfig.Notifications['Services']['XMPPNotifier']['Enabled'] = False
 
     def setUp(self):
@@ -478,7 +479,7 @@
 
     def test_sendHeartbeat(self):
 
-        xmppConfig = Config(config_mod.defaultConfig)
+        xmppConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
         xmppConfig.Notifications['Services']['XMPPNotifier']['Enabled'] = True
         xmppConfig.ServerHostName = "server.example.com"
         xmppConfig.HTTPPort = 80
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20090625/16d0bdb4/attachment-0001.html>


More information about the calendarserver-changes mailing list