[CalendarServer-changes] [7486] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Mon May 16 08:02:28 PDT 2011


Revision: 7486
          http://trac.macosforge.org/projects/calendarserver/changeset/7486
Author:   glyph at apple.com
Date:     2011-05-16 08:02:27 -0700 (Mon, 16 May 2011)
Log Message:
-----------
Merge new-export branch, making calendarserver_export work again.  Also includes several refactorings, especially in tests, to reduce some duplication.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tools/export.py
    CalendarServer/trunk/calendarserver/tools/purge.py
    CalendarServer/trunk/calendarserver/tools/test/test_purge_old_events.py
    CalendarServer/trunk/calendarserver/tools/util.py
    CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py
    CalendarServer/trunk/twistedcaldav/ical.py
    CalendarServer/trunk/twistedcaldav/method/get.py
    CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
    CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py
    CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
    CalendarServer/trunk/twistedcaldav/test/test_resource.py
    CalendarServer/trunk/twistedcaldav/test/test_sharing.py
    CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
    CalendarServer/trunk/twistedcaldav/test/util.py
    CalendarServer/trunk/txdav/base/datastore/subpostgres.py
    CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py
    CalendarServer/trunk/txdav/caldav/datastore/file.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/caldav/datastore/test/common.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
    CalendarServer/trunk/txdav/caldav/datastore/util.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
    CalendarServer/trunk/txdav/common/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/test/test_util.py
    CalendarServer/trunk/txdav/common/datastore/test/util.py

Added Paths:
-----------
    CalendarServer/trunk/calendarserver/tools/cmdline.py
    CalendarServer/trunk/calendarserver/tools/test/test_export.py
    CalendarServer/trunk/twistedcaldav/test/data/AnotherEvent.ics
    CalendarServer/trunk/twistedcaldav/test/data/OneEvent.ics
    CalendarServer/trunk/twistedcaldav/test/data/ThirdEvent.ics

Property Changed:
----------------
    CalendarServer/trunk/
    CalendarServer/trunk/support/build.sh
    CalendarServer/trunk/txdav/caldav/datastore/index_file.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
    CalendarServer/trunk/txdav/carddav/datastore/index_file.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py


Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
   + /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593

Copied: CalendarServer/trunk/calendarserver/tools/cmdline.py (from rev 7485, CalendarServer/branches/users/glyph/new-export/calendarserver/tools/cmdline.py)
===================================================================
--- CalendarServer/trunk/calendarserver/tools/cmdline.py	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/cmdline.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -0,0 +1,77 @@
+##
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Shared main-point between utilities.
+"""
+
+import sys
+
+from calendarserver.tap.caldav import CalDAVServiceMaker, CalDAVOptions
+from calendarserver.tools.util import loadConfig, autoDisableMemcached
+from twistedcaldav.config import ConfigurationError
+
+# TODO: direct unit tests for this function.
+
+def utilityMain(configFileName, serviceClass, reactor=None):
+    """
+    Shared main-point for utilities.
+
+    This function will:
+
+        - Load the configuration file named by C{configFileName},
+        - launch a L{CalDAVServiceMaker}'s with the C{ProcessType} of
+          C{"Utility"}
+        - run the reactor, with start/stop events hooked up to the service's
+          C{startService}/C{stopService} methods.
+
+    It is C{serviceClass}'s responsibility to stop the reactor when it's
+    complete.
+
+    @param configFileName: the name of the configuration file to load.
+    @type configuration: C{str}
+
+    @param serviceClass: a 1-argument callable which takes an object that
+        provides L{ICalendarStore} and/or L{IAddressbookStore} and returns an
+        L{IService}.
+
+    @param reactor: if specified, the L{IReactorTime} / L{IReactorThreads} /
+        L{IReactorTCP} (etc) provider to use.  If C{None}, the default reactor
+        will be imported and used.
+    """
+    if reactor is None:
+        from twisted.internet import reactor
+    try:
+        config = loadConfig(configFileName)
+
+        config.ProcessType = "Utility"
+        config.UtilityServiceClass = serviceClass
+
+        autoDisableMemcached(config)
+
+        maker = CalDAVServiceMaker()
+        options = CalDAVOptions
+        service = maker.makeService(options)
+
+        reactor.addSystemEventTrigger("during", "startup", service.startService)
+        reactor.addSystemEventTrigger("before", "shutdown", service.stopService)
+
+    except ConfigurationError, e:
+        sys.stderr.write("Error: %s\n" % (e,))
+        return
+
+    reactor.run()
+

Modified: CalendarServer/trunk/calendarserver/tools/export.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/export.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/calendarserver/tools/export.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-
+# -*- test-case-name: calendarserver.tools.test.test_export -*-
 ##
 # Copyright (c) 2006-2009 Apple Inc. All rights reserved.
 #
@@ -17,208 +17,298 @@
 ##
 
 """
-This tool reads calendar data from a series of inputs and generates a
-single iCalendar file which can be opened in many calendar
-applications.
+This tool reads calendar data from a series of inputs and generates a single
+iCalendar file which can be opened in many calendar applications.
 
-This can be used to quickly create an iCalendar file from a user's
-calendars.
+This can be used to quickly create an iCalendar file from a user's calendars.
 
-This tool requires access to the calendar server's configuration and
-data storage; it does not operate by talking to the server via the
-network.  It therefore does not apply any of the access restrictions
-that the server would.  As such, one should be midful that data
-exported via this tool may be sensitive.
+This tool requires access to the calendar server's configuration and data
+storage; it does not operate by talking to the server via the network.  It
+therefore does not apply any of the access restrictions that the server would.
+As such, one should be midful that data exported via this tool may be sensitive.
+
+Please also note that this is not an appropriate tool for backups, as there is
+data associated with users and calendars beyond the iCalendar as visible to the
+owner of that calendar, including DAV properties, information about sharing, and
+per-user data such as alarms.
+
 """
 
 import os
 import sys
-from getopt import getopt, GetoptError
-from os.path import dirname, abspath
+import itertools
 
-from twistedcaldav.config import ConfigurationError
-from twistedcaldav.ical import Component as iComponent, Property as iProperty
-from twistedcaldav.ical import iCalendarProductID
-from twistedcaldav.resource import isCalendarCollectionResource,\
-    CalendarHomeResource
-from twistedcaldav.static import CalDAVFile
-from twistedcaldav.directory.directory import DirectoryService
+from twisted.python.text import wordWrap
+from twisted.python.usage import Options
+from twisted.python import log
+from twisted.internet.defer import inlineCallbacks, returnValue
 
-from calendarserver.tools.util import UsageError
-from calendarserver.tools.util import loadConfig, getDirectory, dummyDirectoryRecord, autoDisableMemcached
+from twistedcaldav.ical import Component
 
+from twistedcaldav.stdconfig import DEFAULT_CARDDAV_CONFIG_FILE
+from calendarserver.tools.cmdline import utilityMain
+from twisted.application.service import Service
+from calendarserver.tap.util import directoryFromConfig
+
+
 def usage(e=None):
     if e:
         print e
         print ""
-
-    name = os.path.basename(sys.argv[0])
-    print "usage: %s [options] [input_specifiers]" % (name,)
-    print ""
-    print "Generate an iCalendar file containing the merged content of each calendar"
-    print "collection read."
-    print __doc__
-    print "options:"
-    print "  -h --help: print this help and exit"
-    print "  -f --config: Specify caldavd.plist configuration path"
-    print "  -o --output: Specify output file path (default: '-', meaning stdout)"
-    print ""
-    print "input specifiers:"
-    print "  -c --collection: add a calendar collection"
-    print "  -H --home: add a calendar home (and all calendars within it)"
-    print "  -r --record: add a directory record's calendar home (format: 'recordType:shortName')"
-    print "  -u --user: add a user's calendar home (shorthand for '-r users:shortName')"
-
+    try:
+        ExportOptions().opt_help()
+    except SystemExit:
+        pass
     if e:
         sys.exit(64)
     else:
         sys.exit(0)
 
-def main():
-    try:
-        (optargs, args) = getopt(
-            sys.argv[1:], "hf:o:c:H:r:u:", [
-                "help",
-                "config=",
-                "output=",
-                "collection=", "home=", "record=", "user=",
-            ],
-        )
-    except GetoptError, e:
-        usage(e)
 
-    configFileName = None
-    outputFileName = None
+description = '\n'.join(
+    wordWrap(
+        """
+        Usage: calendarserver_export [options] [input specifiers]\n
+        """ + __doc__,
+        int(os.environ.get('COLUMNS', '80'))
+    )
+)
 
-    collections = set()
-    calendarHomes = set()
-    records = set()
+class ExportOptions(Options):
+    """
+    Command-line options for 'calendarserver_export'
 
-    def checkExists(resource):
-        if not resource.exists():
-            sys.stderr.write("No such file: %s\n" % (resource.fp.path,))
-            sys.exit(1)
+    @ivar exporters: a list of L{HomeExporter} objects which can identify the
+        calendars to export, given a directory service.  This list is built by
+        parsing --record and --collection options.
+    """
 
-    for opt, arg in optargs:
-        if opt in ("-h", "--help"):
-            usage()
+    synopsis = description
 
-        elif opt in ("-f", "--config"):
-            configFileName = arg
+    optParameters = [['config', 'f', DEFAULT_CARDDAV_CONFIG_FILE,
+                      "Specify caldavd.plist configuration path."]]
 
-        elif opt in ("-o", "--output"):
-            if arg == "-":
-                outputFileName = None
-            else:
-                outputFileName = arg
+    def __init__(self):
+        super(ExportOptions, self).__init__()
+        self.exporters = []
+        self.outputName = '-'
 
-        elif opt in ("-c", "--collection"):
-            path = abspath(arg)
-            collection = CalDAVFile(path)
-            checkExists(collection)
-            if not isCalendarCollectionResource(collection):
-                sys.stderr.write("Not a calendar collection: %s\n" % (path,))
-                sys.exit(1)
-            collections.add(collection)
 
-        elif opt in ("-H", "--home"):
-            path = abspath(arg)
-            parent = CalDAVFile(dirname(abspath(path)))
-            calendarHome = CalendarHomeResource(arg, parent, dummyDirectoryRecord)
-            checkExists(calendarHome)
-            calendarHomes.add(calendarHome)
+    def opt_record(self, recordName):
+        """
+        Add a directory record's calendar home (format: 'recordType:shortName').
+        """
+        recordType, shortName = recordName.split(":", 1)
+        self.exporters.append(HomeExporter(recordType, shortName))
 
-        elif opt in ("-r", "--record"):
-            try:
-                recordType, shortName = arg.split(":", 1)
-                if not recordType or not shortName:
-                    raise ValueError()
-            except ValueError:
-                sys.stderr.write("Invalid record identifier: %r\n" % (arg,))
-                sys.exit(1)
+    opt_r = opt_record
 
-            records.add((recordType, shortName))
 
-        elif opt in ("-u", "--user"):
-            records.add((DirectoryService.recordType_users, arg))
+    def opt_collection(self, collectionName):
+        """
+        Add a calendar collection.  This option must be passed after --record
+        (or a synonym, like --user).  for example, to export user1's calendars
+        called 'meetings' and 'team', invoke 'calendarserver_export --user=user1
+        --collection=meetings --collection=team'.
+        """
+        self.exporters[-1].collections.append(collectionName)
 
-    if args:
-        usage("Too many arguments: %s" % (" ".join(args),))
+    opt_c = opt_collection
 
-    if records:
-        try:
-            config = loadConfig(configFileName)
-            config.directory = getDirectory()
-            autoDisableMemcached(config)
-        except ConfigurationError, e:
-            sys.stdout.write("%s\n" % (e,))
-            sys.exit(1)
 
-    for record in records:
-        recordType, shortName = record
-        calendarHome = config.directory.calendarHomeForShortName(recordType, shortName)
-        if not calendarHome:
-            sys.stderr.write("No calendar home found for record: (%s)%s\n" % (recordType, shortName))
-            sys.exit(1)
-        calendarHomes.add(calendarHome)
+    def opt_output(self, filename):
+        """
+        Specify output file path (default: '-', meaning stdout).
+        """
+        self.outputName = filename
 
-    for calendarHome in calendarHomes:
-        for childName in calendarHome.listChildren():
-            child = calendarHome.getChild(childName)
-            if isCalendarCollectionResource(child):
-                collections.add(child)
+    opt_o = opt_output
 
-    try:
-        calendar = iComponent("VCALENDAR")
-        calendar.addProperty(iProperty("VERSION", "2.0"))
-        calendar.addProperty(iProperty("PRODID", iCalendarProductID))
 
-        uids  = set()
-        tzids = set()
+    def opt_user(self, user):
+        """
+        Add a user's calendar home (shorthand for '-r users:shortName').
+        """
+        self.opt_record("users:" + user)
 
-        for collection in collections:
-            for name, uid, type in collection.index().indexedSearch(None):
-                child = collection.getChild(name)
-                childData = child.iCalendarText()
+    opt_u = opt_user
 
-                try:
-                    childCalendar = iComponent.fromString(childData)
-                except ValueError:
-                    continue
-                assert childCalendar.name() == "VCALENDAR"
 
-                if uid in uids:
-                    sys.stderr.write("Skipping duplicate event UID %r from %s\n" % (uid, collection.fp.path))
-                    continue
-                else:
-                    uids.add(uid)
+    def openOutput(self):
+        """
+        Open the appropriate output file based on the '--output' option.
+        """
+        if self.outputName == '-':
+            return sys.stdout
+        else:
+            return open(self.outputName, 'wb')
 
-                for component in childCalendar.subcomponents():
-                    # Only insert VTIMEZONEs once
-                    if component.name() == "VTIMEZONE":
-                        tzid = component.propertyValue("TZID")
-                        if tzid in tzids:
-                            continue
-                        else:
-                            tzids.add(tzid)
 
-                    calendar.addComponent(component)
 
-        calendarData = str(calendar)
+class HomeExporter(object):
+    """
+    An exporter that constructs a list of calendars based on the UID or
+    directory services record ID of the home.
 
-        if outputFileName:
-            try:
-                output = open(outputFileName, "w")
-            except IOError, e:
-                sys.stderr.write("Unable to open output file for writing %s: %s\n" % (outputFileName, e))
-                sys.exit(1)
+    @ivar collections: A list of the names of collections that this exporter
+        should enumerate.
+
+    @type collections: C{list} of C{str}
+
+    @ivar recordType: The directory record type to export.  For example:
+        'users'.
+
+    @type recordType: C{str}
+
+    @ivar shortName: The shortName of the directory record to export, according
+        to C{recordType}.
+    """
+
+    def __init__(self, recordType, shortName):
+        self.collections = []
+        self.recordType = recordType
+        self.shortName = shortName
+
+
+    @inlineCallbacks
+    def listCalendars(self, txn, exportService):
+        """
+        Enumerate all calendars based on the directory record and/or calendars
+        for this calendar home.
+        """
+        directory = exportService.directoryService()
+        record = directory.recordWithShortName(self.recordType, self.shortName)
+        home = yield txn.calendarHomeWithUID(record.guid, True)
+        result = []
+        if self.collections:
+            for collection in self.collections:
+                result.append((yield home.calendarWithName(collection)))
         else:
-            output = sys.stdout
+            for collection in (yield home.calendars()):
+                if collection.name() != 'inbox':
+                    result.append(collection)
+        returnValue(result)
 
-        output.write(calendarData)
 
-    except UsageError, e:
-        usage(e)
 
-if __name__ == "__main__":
-    main()
+ at inlineCallbacks
+def exportToFile(calendars, fileobj):
+    """
+    Export some calendars to a file as their owner would see them.
+
+    @param calendars: an iterable of L{ICalendar} providers (or L{Deferred}s of
+        same).
+
+    @param fileobj: an object with a C{write} method that will accept some
+        iCalendar data.
+
+    @return: a L{Deferred} which fires when the export is complete.  (Note that
+        the file will not be closed.)
+    @rtype: L{Deferred} that fires with C{None}
+    """
+    comp = Component.newCalendar()
+    for calendar in calendars:
+        calendar = yield calendar
+        for obj in (yield calendar.calendarObjects()):
+            evt = yield obj.filteredComponent(
+                calendar.ownerCalendarHome().uid(), True)
+            for sub in evt.subcomponents():
+                if sub.name() != 'VTIMEZONE':
+                    # Omit all VTIMEZONE components, since PyCalendar will
+                    # helpfully re-include all necessary VTIMEZONEs when we call
+                    # __str__; see pycalendar.calendar.PyCalendar.generate() and
+                    # .includeTimezones().
+                    comp.addComponent(sub)
+
+    fileobj.write(str(comp))
+
+
+
+class ExporterService(Service, object):
+    """
+    Service which runs, exports the appropriate records, then stops the reactor.
+    """
+
+    def __init__(self, store, options, output, reactor, config):
+        super(ExporterService, self).__init__()
+        self.store   = store
+        self.options = options
+        self.output  = output
+        self.reactor = reactor
+        self.config = config
+        self._directory = None
+
+
+    def startService(self):
+        """
+        Start the service.
+        """
+        super(ExporterService, self).startService()
+        self.doExport()
+
+
+    @inlineCallbacks
+    def doExport(self):
+        """
+        Do the export, stopping the reactor when done.
+        """
+        txn = self.store.newTransaction()
+        try:
+            allCalendars = itertools.chain(
+                *[(yield exporter.listCalendars(txn, self)) for exporter in
+                  self.options.exporters]
+            )
+            yield exportToFile(allCalendars, self.output)
+            yield txn.commit()
+            # TODO: should be read-only, so commit/abort shouldn't make a
+            # difference.  commit() for now, in case any transparent cache /
+            # update stuff needed to happen, don't want to undo it.
+            self.output.close()
+        except:
+            log.err()
+
+        self.reactor.stop()
+
+
+    def directoryService(self):
+        """
+        Get an appropriate directory service for this L{ExporterService}'s
+        configuration, creating one first if necessary.
+        """
+        if self._directory is None:
+            self._directory = directoryFromConfig(self.config)
+        return self._directory
+
+
+    def stopService(self):
+        """
+        Stop the service.  Nothing to do; everything should be finished by this
+        time.
+        """
+        # TODO: stopping this service mid-export should really stop the export
+        # loop, but this is not implemented because nothing will actually do it
+        # except hitting ^C (which also calls reactor.stop(), so that will exit
+        # anyway).
+
+
+
+def main(argv=sys.argv, stderr=sys.stderr, reactor=None):
+    """
+    Do the export.
+    """
+    if reactor is None:
+        from twisted.internet import reactor
+    options = ExportOptions()
+    options.parseOptions(argv[1:])
+    try:
+        output = options.openOutput()
+    except IOError, e:
+        stderr.write("Unable to open output file for writing: %s\n" %
+                     (e))
+        sys.exit(1)
+    def makeService(store):
+        from twistedcaldav.config import config
+        return ExporterService(store, options, output, reactor, config)
+    utilityMain(options['config'], makeService, reactor)
+
+

Modified: CalendarServer/trunk/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/purge.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/calendarserver/tools/purge.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-
+# -*- test-case-name: calendarserver.tools.test.test_purge -*-
 ##
 # Copyright (c) 2006-2010 Apple Inc. All rights reserved.
 #
@@ -19,12 +19,18 @@
 import os
 import sys
 from errno import ENOENT, EACCES
-
 from getopt import getopt, GetoptError
 
+from pycalendar.datetime import PyCalendarDateTime
+
 from twisted.application.service import Service
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, returnValue
+
+from twext.python.log import Logger
+from twext.web2.dav import davxml
+from twext.web2.responsecode import NO_CONTENT
+
 from twistedcaldav import caldavxml
 from twistedcaldav.caldavxml import TimeRange
 from twistedcaldav.config import config, ConfigurationError
@@ -33,16 +39,11 @@
 from twistedcaldav.method.put_common import StoreCalendarObjectResource
 from twistedcaldav.query import calendarqueryfilter
 
-from twext.python.log import Logger
-from twext.web2.dav import davxml
-from twext.web2.responsecode import NO_CONTENT
-
-from calendarserver.tap.caldav import CalDAVServiceMaker, CalDAVOptions
 from calendarserver.tap.util import FakeRequest
 from calendarserver.tap.util import getRootResource
+
+from calendarserver.tools.cmdline import utilityMain
 from calendarserver.tools.principals import removeProxy
-from calendarserver.tools.util import loadConfig
-from pycalendar.datetime import PyCalendarDateTime
 
 log = Logger()
 
@@ -205,27 +206,7 @@
 
 
 
-def shared_main(configFileName, serviceClass):
 
-    try:
-        loadConfig(configFileName)
-
-        config.ProcessType = "Utility"
-        config.UtilityServiceClass = serviceClass
-
-        maker = CalDAVServiceMaker()
-        options = CalDAVOptions
-        service = maker.makeService(options)
-
-        reactor.addSystemEventTrigger("during", "startup", service.startService)
-        reactor.addSystemEventTrigger("before", "shutdown", service.stopService)
-
-    except ConfigurationError, e:
-        sys.stderr.write("Error: %s\n" % (e,))
-        return
-
-    reactor.run()
-
 def main_purge_events():
 
     try:
@@ -295,7 +276,7 @@
     PurgeOldEventsService.dryrun = dryrun
     PurgeOldEventsService.verbose = verbose
 
-    shared_main(
+    utilityMain(
         configFileName,
         PurgeOldEventsService,
     )
@@ -357,7 +338,7 @@
     PurgeOrphanedAttachmentsService.dryrun = dryrun
     PurgeOrphanedAttachmentsService.verbose = verbose
 
-    shared_main(
+    utilityMain(
         configFileName,
         PurgeOrphanedAttachmentsService,
     )
@@ -406,7 +387,7 @@
     PurgePrincipalService.verbose = verbose
 
 
-    shared_main(
+    utilityMain(
         configFileName,
         PurgePrincipalService
     )

Copied: CalendarServer/trunk/calendarserver/tools/test/test_export.py (from rev 7485, CalendarServer/branches/users/glyph/new-export/calendarserver/tools/test/test_export.py)
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_export.py	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/test/test_export.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -0,0 +1,484 @@
+##
+# Copyright (c) 2011 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.
+##
+
+"""
+Unit tests for L{calendarsever.tools.export}.
+"""
+
+
+import sys
+from cStringIO import StringIO
+
+from twisted.trial.unittest import TestCase
+
+from twisted.internet.defer import inlineCallbacks
+from twisted.python.modules import getModule
+
+from twext.enterprise.ienterprise import AlreadyFinishedError
+
+from twistedcaldav.ical import Component
+from twistedcaldav.directory import augment
+from twistedcaldav.datafilters.test.test_peruserdata import dataForTwoUsers
+from twistedcaldav.datafilters.test.test_peruserdata import resultForUser2
+
+from calendarserver.tools import export
+from calendarserver.tools.export import ExportOptions, main
+from calendarserver.tools.export import HomeExporter
+
+from twisted.python.filepath import FilePath
+from twistedcaldav.test.util import patchConfig
+from twisted.internet.defer import Deferred
+
+from txdav.common.datastore.test.util import buildStore
+from txdav.common.datastore.test.util import populateCalendarsFrom
+
+from calendarserver.tools.export import usage, exportToFile
+
+def holiday(uid):
+    return (
+        getModule("twistedcaldav.test").filePath
+            .sibling("data").child("Holidays").child(uid + ".ics")
+            .getContent()
+    )
+
+def sample(name):
+    return (
+        getModule("twistedcaldav.test").filePath
+        .sibling("data").child(name + ".ics").getContent()
+    )
+
+valentines = holiday("C31854DA-1ED0-11D9-A5E0-000A958A3252")
+newYears = holiday("C3184A66-1ED0-11D9-A5E0-000A958A3252")
+payday = sample("PayDay")
+
+one = sample("OneEvent")
+another = sample("AnotherEvent")
+third = sample("ThirdEvent")
+
+
+class CommandLine(TestCase):
+    """
+    Simple tests for command-line parsing.
+    """
+
+    def test_usageMessage(self):
+        """
+        The 'usage' message should print something to standard output (and
+        nothing to standard error) and exit.
+        """
+        orig = sys.stdout
+        orige = sys.stderr
+        try:
+            out = sys.stdout = StringIO()
+            err = sys.stderr = StringIO()
+            self.assertRaises(SystemExit, usage)
+        finally:
+            sys.stdout = orig
+            sys.stderr = orige
+        self.assertEquals(len(out.getvalue()) > 0, True, "No output.")
+        self.assertEquals(len(err.getvalue()), 0)
+
+
+    def test_oneHome(self):
+        """
+        One '--record' option will result in a single HomeExporter object with
+        no calendars in its list.
+        """
+        eo = ExportOptions()
+        eo.parseOptions(["--record", "users:bob"])
+        self.assertEquals(len(eo.exporters), 1)
+        exp = eo.exporters[0]
+        self.assertIsInstance(exp, HomeExporter)
+        self.assertEquals(exp.recordType, "users")
+        self.assertEquals(exp.shortName, "bob")
+        self.assertEquals(exp.collections, [])
+
+
+    def test_homeAndCollections(self):
+        """
+        The --collection option adds calendars to the last calendar that was
+        exported.
+        """
+        eo = ExportOptions()
+        eo.parseOptions(["--record", "users:bob",
+                         "--collection", "work stuff",
+                         "--record", "users:jethro",
+                         "--collection=fun stuff"])
+        self.assertEquals(len(eo.exporters), 2)
+        exp = eo.exporters[0]
+        self.assertEquals(exp.recordType, "users")
+        self.assertEquals(exp.shortName, "bob")
+        self.assertEquals(exp.collections, ["work stuff"])
+        exp = eo.exporters[1]
+        self.assertEquals(exp.recordType, "users")
+        self.assertEquals(exp.shortName, "jethro")
+        self.assertEquals(exp.collections, ["fun stuff"])
+
+
+    def test_outputFileSelection(self):
+        """
+        The --output option selects the file to write to, '-' or no parameter
+        meaning stdout; the L{ExportOptions.openOutput} method returns that
+        file.
+        """
+        eo = ExportOptions()
+        eo.parseOptions([])
+        self.assertIdentical(eo.openOutput(), sys.stdout)
+        eo = ExportOptions()
+        eo.parseOptions(["--output", "-"])
+        self.assertIdentical(eo.openOutput(), sys.stdout)
+        eo = ExportOptions()
+        tmpnam = self.mktemp()
+        eo.parseOptions(["--output", tmpnam])
+        self.assertEquals(eo.openOutput().name, tmpnam)
+
+
+    def test_outputFileError(self):
+        """
+        If the output file cannot be opened for writing, an error will be
+        displayed to the user on stderr.
+        """
+        io = StringIO()
+        systemExit = self.assertRaises(
+            SystemExit, main, ['calendarserver_export',
+                               '--output', '/not/a/file'], io
+        )
+        self.assertEquals(systemExit.code, 1)
+        self.assertEquals(
+            io.getvalue(),
+            "Unable to open output file for writing: "
+            "[Errno 2] No such file or directory: '/not/a/file'\n")
+
+
+
+class IntegrationTests(TestCase):
+    """
+    Tests for exporting data from a live store.
+    """
+
+    accountsFile = 'no-accounts.xml'
+    augmentsFile = 'no-augments.xml'
+
+    @inlineCallbacks
+    def setUp(self):
+        """
+        Set up a store and fix the imported C{utilityMain} function (normally
+        from L{calendarserver.tools.cmdline.utilityMain}) to point to a
+        temporary method of this class.  Also, patch the imported C{reactor},
+        since the SUT needs to call C{reactor.stop()} in order to work with
+        L{utilityMain}.
+        """
+        self.mainCalled = False
+        self.patch(export, "utilityMain", self.fakeUtilityMain)
+
+
+        self.store = yield buildStore(self, None)
+        self.waitToStop = Deferred()
+
+
+    def stop(self):
+        """
+        Emulate reactor.stop(), which the service must call when it is done with
+        work.
+        """
+        self.waitToStop.callback(None)
+
+
+    def fakeUtilityMain(self, configFileName, serviceClass, reactor=None):
+        """
+        Verify a few basic things.
+        """
+        if self.mainCalled:
+            raise RuntimeError(
+                "Main called twice during this test; duplicate reactor run.")
+
+        # In lieu of a configuration file, patch up the augment service and make
+        # it so directoryFromConfig will return something useful.  Don't
+        # actually need to patch the augment service to a real fake; just patch
+        # it so that trial will restore the previous one when this test has
+        # completed.
+
+        self.patch(augment, "AugmentService", None)
+
+        patchConfig(
+            self,
+            DirectoryService=dict(
+                type="twistedcaldav.directory.xmlfile.XMLDirectoryService",
+                params=dict(
+                    xmlFile=self.accountsFile
+                )
+            ),
+            ResourceService=dict(Enabled=False),
+            AugmentService=dict(
+                type="twistedcaldav.directory.augment.AugmentXMLDB",
+                params=dict(
+                    xmlFiles=[self.augmentsFile]
+                )
+            )
+        )
+
+        self.mainCalled = True
+        self.usedConfigFile = configFileName
+        self.usedReactor = reactor
+        self.exportService = serviceClass(self.store)
+        self.exportService.startService()
+        self.addCleanup(self.exportService.stopService)
+
+
+    @inlineCallbacks
+    def test_serviceState(self):
+        """
+        export.main() invokes utilityMain with the configuration file specified
+        on the command line, and creates an L{ExporterService} pointed at the
+        appropriate store.
+        """
+        tempConfig = self.mktemp()
+        main(['calendarserver_export', '--config', tempConfig, '--output',
+              self.mktemp()], reactor=self)
+        self.assertEquals(self.mainCalled, True, "Main not called.")
+        self.assertEquals(self.usedConfigFile, tempConfig)
+        self.assertEquals(self.usedReactor, self)
+        self.assertEquals(self.exportService.store, self.store)
+        yield self.waitToStop
+
+
+    @inlineCallbacks
+    def test_emptyCalendar(self):
+        """
+        Exporting an empty calendar results in an empty calendar.
+        """
+        io = StringIO()
+        value = yield exportToFile([], io)
+        # it doesn't return anything, it writes to the file.
+        self.assertEquals(value, None)
+        # but it should write a valid component to the file.
+        self.assertEquals(Component.fromString(io.getvalue()),
+                          Component.newCalendar())
+
+
+    def txn(self):
+        """
+        Create a new transaction and automatically clean it up when the test
+        completes.
+        """
+        aTransaction = self.store.newTransaction()
+        @inlineCallbacks
+        def maybeAbort():
+            try:
+                yield aTransaction.abort()
+            except AlreadyFinishedError:
+                pass
+        self.addCleanup(maybeAbort)
+        return aTransaction
+
+
+    @inlineCallbacks
+    def test_oneEventCalendar(self):
+        """
+        Exporting an calendar with one event in it will result in just that
+        event.
+        """
+        yield populateCalendarsFrom(
+            {
+                "home1": {
+                    "calendar1": {
+                        "valentines-day.ics": (valentines, {})
+                    }
+                }
+            }, self.store
+        )
+
+        expected = Component.newCalendar()
+        [theComponent] = Component.fromString(valentines).subcomponents()
+        expected.addComponent(theComponent)
+
+        io = StringIO()
+        yield exportToFile(
+            [(yield self.txn().calendarHomeWithUID("home1"))
+              .calendarWithName("calendar1")], io
+        )
+        self.assertEquals(Component.fromString(io.getvalue()),
+                          expected)
+
+
+    @inlineCallbacks
+    def test_twoSimpleEvents(self):
+        """
+        Exporting a calendar with two events in it will result in a VCALENDAR
+        component with both VEVENTs in it.
+        """
+        yield populateCalendarsFrom(
+            {
+                "home1": {
+                    "calendar1": {
+                        "valentines-day.ics": (valentines, {}),
+                        "new-years-day.ics": (newYears, {})
+                    }
+                }
+            }, self.store
+        )
+
+        expected = Component.newCalendar()
+        a = Component.fromString(valentines)
+        b = Component.fromString(newYears)
+        for comp in a, b:
+            for sub in comp.subcomponents():
+                expected.addComponent(sub)
+
+        io = StringIO()
+        yield exportToFile(
+            [(yield self.txn().calendarHomeWithUID("home1"))
+              .calendarWithName("calendar1")], io
+        )
+        self.assertEquals(Component.fromString(io.getvalue()),
+                          expected)
+
+
+    @inlineCallbacks
+    def test_onlyOneVTIMEZONE(self):
+        """
+        C{VTIMEZONE} subcomponents with matching TZIDs in multiple event
+        calendar objects should only be rendered in the resulting output once.
+
+        (Note that the code to suppor this is actually in PyCalendar, not the
+        export tool itself.)
+        """
+        yield populateCalendarsFrom(
+            {
+                "home1": {
+                    "calendar1": {
+                        "1.ics": (one, {}), # EST
+                        "2.ics": (another, {}), # EST
+                        "3.ics": (third, {}) # PST
+                    }
+                }
+            }, self.store
+        )
+
+        io = StringIO()
+        yield exportToFile(
+            [(yield self.txn().calendarHomeWithUID("home1"))
+              .calendarWithName("calendar1")], io
+        )
+        result = Component.fromString(io.getvalue())
+
+        def filtered(name):
+            for c in result.subcomponents():
+                if c.name() == name:
+                    yield c
+
+        timezones = list(filtered("VTIMEZONE"))
+        events = list(filtered("VEVENT"))
+
+        # Sanity check to make sure we picked up all three events:
+        self.assertEquals(len(events), 3)
+
+        self.assertEquals(len(timezones), 2)
+        self.assertEquals(set([tz.propertyValue("TZID") for tz in timezones]),
+
+                          # Use an intentionally wrong TZID in order to make
+                          # sure we don't depend on caching effects elsewhere.
+                          set(["America/New_Yrok", "US/Pacific"]))
+
+
+    @inlineCallbacks
+    def test_perUserFiltering(self):
+        """
+        L{exportToFile} performs per-user component filtering based on the owner
+        of that calendar.
+        """
+        yield populateCalendarsFrom(
+            {
+                "user02": {
+                    "calendar1": {
+                        "peruser.ics": (dataForTwoUsers, {}), # EST
+                    }
+                }
+            }, self.store
+        )
+        io = StringIO()
+        yield exportToFile(
+            [(yield self.txn().calendarHomeWithUID("user02"))
+              .calendarWithName("calendar1")], io
+        )
+        self.assertEquals(
+            Component.fromString(resultForUser2),
+            Component.fromString(io.getvalue())
+        )
+
+
+    @inlineCallbacks
+    def test_full(self):
+        """
+        Running C{calendarserver_export} on the command line exports an ics
+        file. (Almost-full integration test, starting from the main point, using
+        as few test fakes as possible.)
+
+        Note: currently the only test for directory interaction.
+        """
+        yield populateCalendarsFrom(
+            {
+                "user02": {
+                    # TODO: more direct test for skipping inbox
+                    "inbox": {
+                        "inbox-item.ics": (valentines, {})
+                    },
+                    "calendar1": {
+                        "peruser.ics": (dataForTwoUsers, {}), # EST
+                    }
+                }
+            }, self.store
+        )
+
+        augmentsData = """
+            <augments>
+              <record>
+                <uid>Default</uid>
+                <enable>true</enable>
+                <enable-calendar>true</enable-calendar>
+                <enable-addressbook>true</enable-addressbook>
+              </record>
+            </augments>
+        """
+        augments = FilePath(self.mktemp())
+        augments.setContent(augmentsData)
+
+        accountsData = """
+            <accounts realm="Test Realm">
+                <user>
+                    <uid>user-under-test</uid>
+                    <guid>user02</guid>
+                    <name>Not Interesting</name>
+                    <password>very-secret</password>
+                </user>
+            </accounts>
+        """
+        accounts = FilePath(self.mktemp())
+        accounts.setContent(accountsData)
+        output = FilePath(self.mktemp())
+        self.accountsFile = accounts.path
+        self.augmentsFile = augments.path
+        main(['calendarserver_export', '--output',
+              output.path, '--user', 'user-under-test'], reactor=self)
+
+        yield self.waitToStop
+
+        self.assertEquals(
+            Component.fromString(resultForUser2),
+            Component.fromString(output.getContent())
+        )
+
+

Modified: CalendarServer/trunk/calendarserver/tools/test/test_purge_old_events.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_purge_old_events.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/calendarserver/tools/test/test_purge_old_events.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -26,7 +26,6 @@
 from twisted.trial import unittest
 
 from twistedcaldav.config import config
-from twistedcaldav.memcacher import Memcacher
 from twistedcaldav.vcard import Component as VCardComponent
 
 from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom, CommonCommonTests
@@ -348,9 +347,6 @@
 
     @inlineCallbacks
     def setUp(self):
-        self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
-        self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
-        self.patch(Memcacher, "allowTestCache", True)
         yield super(PurgeOldEventsTests, self).setUp()
         self._sqlCalendarStore = yield buildStore(self, self.notifierFactory)
         yield self.populate()

Modified: CalendarServer/trunk/calendarserver/tools/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/util.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/calendarserver/tools/util.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2008-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2008-2011 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.
@@ -14,6 +14,10 @@
 # limitations under the License.
 ##
 
+"""
+Utility functionality shared between calendarserver tools.
+"""
+
 __all__ = [
     "loadConfig",
     "getDirectory",
@@ -34,6 +38,7 @@
 
 
 from calendarserver.provision.root import RootResource
+
 from twistedcaldav import memcachepool
 from twistedcaldav.config import config, ConfigurationError
 from twistedcaldav.directory import augment, calendaruserproxy
@@ -270,3 +275,5 @@
             % (description, dirpath)
         )
 
+
+


Property changes on: CalendarServer/trunk/support/build.sh
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/support/build.sh:4379-4443
/CalendarServer/branches/egg-info-351/support/build.sh:4589-4615
/CalendarServer/branches/generic-sqlstore/support/build.sh:6167-6191
/CalendarServer/branches/new-store/support/build.sh:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/support/build.sh:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/support/build.sh:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/support/build.sh:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/support/build.sh:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/support/build.sh:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/support/build.sh:4465-4957
/CalendarServer/branches/users/cdaboo/pods/support/build.sh:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/support/build.sh:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/support/build.sh:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/support/build.sh:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/support/build.sh:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/support/build.sh:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/support/build.sh:4971-5080
/CalendarServer/branches/users/glyph/dalify/support/build.sh:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect/support/build.sh:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres/support/build.sh:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/support/build.sh:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/support/build.sh:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/support/build.sh:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/support/build.sh:6369-6445
/CalendarServer/branches/users/glyph/oracle-nulls/support/build.sh:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/support/build.sh:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/support/build.sh:6490-6550
/CalendarServer/branches/users/glyph/sql-store/support/build.sh:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/support/build.sh:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/support/build.sh:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/support/build.sh:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/support/build.sh:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/support/build.sh:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/support/build.sh:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/support/build.sh:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/support/build.sh:4068-4075
/CalendarServer/branches/users/sagen/resources-2/support/build.sh:5084-5093
/CalendarServer/branches/users/wsanchez/transations/support/build.sh:5515-5593
   + /CalendarServer/branches/config-separation/support/build.sh:4379-4443
/CalendarServer/branches/egg-info-351/support/build.sh:4589-4615
/CalendarServer/branches/generic-sqlstore/support/build.sh:6167-6191
/CalendarServer/branches/new-store/support/build.sh:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/support/build.sh:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/support/build.sh:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/support/build.sh:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/support/build.sh:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/support/build.sh:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/support/build.sh:4465-4957
/CalendarServer/branches/users/cdaboo/pods/support/build.sh:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/support/build.sh:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/support/build.sh:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/support/build.sh:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/support/build.sh:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/support/build.sh:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/support/build.sh:4971-5080
/CalendarServer/branches/users/glyph/dalify/support/build.sh:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect/support/build.sh:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres/support/build.sh:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/support/build.sh:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/support/build.sh:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/support/build.sh:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/support/build.sh:6369-6445
/CalendarServer/branches/users/glyph/new-export/support/build.sh:7444-7485
/CalendarServer/branches/users/glyph/oracle-nulls/support/build.sh:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/support/build.sh:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/support/build.sh:6490-6550
/CalendarServer/branches/users/glyph/sql-store/support/build.sh:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/support/build.sh:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/support/build.sh:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/support/build.sh:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/support/build.sh:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/support/build.sh:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/support/build.sh:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/support/build.sh:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/support/build.sh:4068-4075
/CalendarServer/branches/users/sagen/resources-2/support/build.sh:5084-5093
/CalendarServer/branches/users/wsanchez/transations/support/build.sh:5515-5593

Modified: CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -18,11 +18,7 @@
 from twistedcaldav.ical import Component
 from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 
-class PerUserDataFilterTestNotRecurring (twistedcaldav.test.util.TestCase):
-
-    def test_public_noperuser(self):
-        
-        data = """BEGIN:VCALENDAR
+dataForTwoUsers = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
 BEGIN:VEVENT
@@ -33,17 +29,35 @@
 ATTENDEE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
 END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
 END:VCALENDAR
 """.replace("\n", "\r\n")
-        
-        for item in (data, Component.fromString(data),):
-            self.assertEqual(str(PerUserDataFilter("user01").filter(item)), data)
-        for item in (data, Component.fromString(data),):
-            self.assertEqual(str(PerUserDataFilter("").filter(item)), data)
 
-    def test_public_oneuser(self):
-        
-        data = """BEGIN:VCALENDAR
+
+resultForUser1 = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
 BEGIN:VEVENT
@@ -53,22 +67,18 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
 BEGIN:VALARM
 ACTION:DISPLAY
-DESCRIPTION:Test
+DESCRIPTION:Test01
 TRIGGER;RELATED=START:-PT10M
 END:VALARM
-TRANSP:OPAQUE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
+END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
-        result01 = """BEGIN:VCALENDAR
+
+
+resultForUser2 = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
 BEGIN:VEVENT
@@ -78,16 +88,18 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
+TRANSP:TRANSPARENT
 BEGIN:VALARM
 ACTION:DISPLAY
-DESCRIPTION:Test
+DESCRIPTION:Test02
 TRIGGER;RELATED=START:-PT10M
 END:VALARM
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
-        result02 = """BEGIN:VCALENDAR
+
+
+resultForOtherUser = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
 BEGIN:VEVENT
@@ -100,15 +112,33 @@
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
+
+
+
+class PerUserDataFilterTestNotRecurring (twistedcaldav.test.util.TestCase):
+
+    def test_public_noperuser(self):
         
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
         for item in (data, Component.fromString(data),):
-            self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+            self.assertEqual(str(PerUserDataFilter("user01").filter(item)), data)
         for item in (data, Component.fromString(data),):
-            self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
-        for item in (data, Component.fromString(data),):
-            self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
+            self.assertEqual(str(PerUserDataFilter("").filter(item)), data)
 
-    def test_public_twousers(self):
+    def test_public_oneuser(self):
         
         data = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -127,24 +157,12 @@
 BEGIN:X-CALENDARSERVER-PERINSTANCE
 BEGIN:VALARM
 ACTION:DISPLAY
-DESCRIPTION:Test01
+DESCRIPTION:Test
 TRIGGER;RELATED=START:-PT10M
 END:VALARM
 TRANSP:OPAQUE
 END:X-CALENDARSERVER-PERINSTANCE
 END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test02
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
 END:VCALENDAR
 """.replace("\n", "\r\n")
         result01 = """BEGIN:VCALENDAR
@@ -160,7 +178,7 @@
 TRANSP:OPAQUE
 BEGIN:VALARM
 ACTION:DISPLAY
-DESCRIPTION:Test01
+DESCRIPTION:Test
 TRIGGER;RELATED=START:-PT10M
 END:VALARM
 END:VEVENT
@@ -176,38 +194,39 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test02
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
-        result03 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
+        
         for item in (data, Component.fromString(data),):
             self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
         for item in (data, Component.fromString(data),):
             self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
         for item in (data, Component.fromString(data),):
-            self.assertEqual(str(PerUserDataFilter("user03").filter(item)), result03)
-        for item in (data, Component.fromString(data),):
-            self.assertEqual(str(PerUserDataFilter("").filter(item)), result03)
+            self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
 
+
+    def test_public_twousers(self):
+        """
+        A component with data for 2 users can return results for either of the
+        two users, or for a third user who has no per-user data embedded in it.
+        """
+
+        for item in (dataForTwoUsers, Component.fromString(dataForTwoUsers),):
+            self.assertEqual(str(PerUserDataFilter("user01").filter(item)),
+                             resultForUser1)
+        for item in (dataForTwoUsers, Component.fromString(dataForTwoUsers),):
+            self.assertEqual(str(PerUserDataFilter("user02").filter(item)),
+                             resultForUser2)
+        for item in (dataForTwoUsers, Component.fromString(dataForTwoUsers),):
+            self.assertEqual(str(PerUserDataFilter("user03").filter(item)),
+                             resultForOtherUser)
+        for item in (dataForTwoUsers, Component.fromString(dataForTwoUsers),):
+            self.assertEqual(str(PerUserDataFilter("").filter(item)),
+                             resultForOtherUser)
+
+
+
 class PerUserDataFilterTestRecurring (twistedcaldav.test.util.TestCase):
 
     def test_public_noperuser(self):

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -1,3 +1,4 @@
+# -*- test-case-name: twistedcaldav.test.test_icalendar -*-
 ##
 # Copyright (c) 2005-2010 Apple Inc. All rights reserved.
 #
@@ -362,6 +363,22 @@
         def parse(data): return clazz.fromString(data)
         return allDataFromStream(IStream(stream), parse)
 
+
+    @classmethod
+    def newCalendar(cls):
+        """
+        Create and return an empty C{VCALENDAR} component.
+
+        @return: a new C{VCALENDAR} component with appropriate metadata
+            properties already set (version, product ID).
+        @rtype: an instance of this class
+        """
+        self = cls("VCALENDAR")
+        self.addProperty(Property("VERSION", "2.0"))
+        self.addProperty(Property("PRODID", iCalendarProductID))
+        return self
+
+
     def __init__(self, name, **kwargs):
         """
         Use this constructor to initialize an empty L{Component}.

Modified: CalendarServer/trunk/twistedcaldav/method/get.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/get.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/method/get.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -81,7 +81,7 @@
                 if self.accessMode:
             
                     # Non DAV:owner's have limited access to the data
-                    isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+                    isowner = (yield self.isOwner(request))
                     
                     # Now "filter" the resource calendar data
                     caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata)

Modified: CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -174,7 +174,7 @@
             filteredaces = (yield calresource.inheritedACEsforChildren(request))
 
             # Check private events access status
-            isowner = (yield calresource.isOwner(request, adminprincipals=True, readprincipals=True))
+            isowner = (yield calresource.isOwner(request))
 
             # Check for disabled access
             if filteredaces is not None:
@@ -229,7 +229,7 @@
                     timezone = tuple(tz.calendar().subcomponents())[0]
 
             # Check private events access status
-            isowner = (yield calresource.isOwner(request, adminprincipals=True, readprincipals=True))
+            isowner = (yield calresource.isOwner(request))
 
             calendar = (yield calresource.iCalendarForUser(request))
             yield queryCalendarObjectResource(calresource, uri, None, calendar, timezone)

Modified: CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -139,7 +139,7 @@
             disabled = True
             
         # Check private events access status
-        isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+        isowner = (yield self.isOwner(request))
 
     elif self.isAddressBookCollection():
         requestURIis = "addressbook"
@@ -352,7 +352,7 @@
                         filteredaces = (yield parent.inheritedACEsforChildren(request))
 
                         # Check private events access status
-                        isowner = (yield parent.isOwner(request, adminprincipals=True, readprincipals=True))
+                        isowner = (yield parent.isOwner(request))
                 else:
                     name = unquote(resource_uri[resource_uri.rfind("/") + 1:])
                     if (resource_uri != request.uri) or not self.exists():
@@ -376,7 +376,7 @@
                     filteredaces = (yield parent.inheritedACEsforChildren(request))
 
                     # Check private events access status
-                    isowner = (yield parent.isOwner(request, adminprincipals=True, readprincipals=True))
+                    isowner = (yield parent.isOwner(request))
         
                 # Check privileges - must have at least DAV:read
                 try:

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -878,7 +878,7 @@
         """
         Return the DAV:owner property value (MUST be a DAV:href or None).
         """
-        
+
         isVirt = self.isVirtualShare()
         if isVirt:
             parent = (yield self.locateParent(request, self._share.hosturl))
@@ -928,33 +928,21 @@
             returnValue(None)
 
 
-    def isOwner(self, request, adminprincipals=False, readprincipals=False):
+    @inlineCallbacks
+    def isOwner(self, request):
         """
-        Determine whether the DAV:owner of this resource matches the currently authorized principal
-        in the request. Optionally test for admin or read principals and allow those.
+        Determine whether the DAV:owner of this resource matches the currently
+        authorized principal in the request, or if the user is a read-only or
+        read-write administrator.
         """
+        current = self.currentPrincipal(request)
+        if current in config.AllAdminPrincipalObjects:
+            returnValue(True)
+        if davxml.Principal((yield self.owner(request))) == current:
+            returnValue(True)
+        returnValue(False)
 
-        def _gotOwner(owner):
-            current = self.currentPrincipal(request)
-            if davxml.Principal(owner) == current:
-                return True
-            
-            if adminprincipals:
-                for principal in config.AdminPrincipals:
-                    if davxml.Principal(davxml.HRef(principal)) == current:
-                        return True
 
-            if readprincipals:
-                for principal in config.AdminPrincipals:
-                    if davxml.Principal(davxml.HRef(principal)) == current:
-                        return True
-                
-            return False
-
-        d = self.owner(request)
-        d.addCallback(_gotOwner)
-        return d
-
     ##
     # DAVResource
     ##

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -1040,6 +1040,19 @@
 
     log.debug("Nav ACL: %s" % (configDict.ProvisioningResourceACL.toxml(),))
 
+    def principalObjects(urls):
+        for principalURL in urls:
+            yield davxml.Principal(davxml.HRef(principalURL))
+
+    # Should be sets, except WebDAVElement isn't hashable.
+    a = configDict.AdminPrincipalObjects = list(
+        principalObjects(configDict.AdminPrincipals))
+    b = configDict.ReadPrincipalObjects = list(
+        principalObjects(configDict.ReadPrincipals))
+    configDict.AllAdminPrincipalObjects = a + b
+
+
+
 def _updateRejectClients(configDict):
     #
     # Compile RejectClients expressions for speed

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -938,7 +938,7 @@
         filteredaces = (yield self.inheritedACEsforChildren(request))
 
         tzids = set()
-        isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+        isowner = (yield self.isOwner(request))
         accessPrincipal = (yield self.resourceOwnerPrincipal(request))
 
         for name, uid, type in (yield maybeDeferred(self.index().bruteForceSearch)): #@UnusedVariable
@@ -1709,66 +1709,45 @@
         returnValue(NO_CONTENT)
 
 
-class _CalendarObjectMetaDataMixin(object):
+
+class _MetadataProperty(object):
     """
-    Dynamically create the required meta-data for an object resource 
+    A python property which can be set either on a _newStoreObject or on some
+    metadata if no new store object exists yet.
     """
 
-    def _get_accessMode(self):
-        return self._newStoreObject.accessMode if self._newStoreObject else self._metadata.get("accessMode", None)
+    def __init__(self, name):
+        self.name = name
 
-    def _set_accessMode(self, value):
-        if self._newStoreObject:
-            self._newStoreObject.accessMode = value
-        else:
-            self._metadata["accessMode"] = value
 
-    accessMode = property(_get_accessMode, _set_accessMode)
-
-    def _get_isScheduleObject(self):
-        return self._newStoreObject.isScheduleObject if self._newStoreObject else self._metadata.get("isScheduleObject", None)
-
-    def _set_isScheduleObject(self, value):
-        if self._newStoreObject:
-            self._newStoreObject.isScheduleObject = value
+    def __get__(self, oself, type=None):
+        if oself._newStoreObject:
+            return getattr(oself._newStoreObject, self.name)
         else:
-            self._metadata["isScheduleObject"] = value
+            return oself._metadata.get(self.name, None)
 
-    isScheduleObject = property(_get_isScheduleObject, _set_isScheduleObject)
 
-    def _get_scheduleTag(self):
-        return self._newStoreObject.scheduleTag if self._newStoreObject else self._metadata.get("scheduleTag", None)
-
-    def _set_scheduleTag(self, value):
-        if self._newStoreObject:
-            self._newStoreObject.scheduleTag = value
+    def __set__(self, oself, value):
+        if oself._newStoreObject:
+            setattr(oself._newStoreObject, self.name, value)
         else:
-            self._metadata["scheduleTag"] = value
+            oself._metadata[self.name] = value
 
-    scheduleTag = property(_get_scheduleTag, _set_scheduleTag)
 
-    def _get_scheduleEtags(self):
-        return self._newStoreObject.scheduleEtags if self._newStoreObject else self._metadata.get("scheduleEtags", None)
 
-    def _set_scheduleEtags(self, value):
-        if self._newStoreObject:
-            self._newStoreObject.scheduleEtags = value
-        else:
-            self._metadata["scheduleEtags"] = value
+class _CalendarObjectMetaDataMixin(object):
+    """
+    Dynamically create the required meta-data for an object resource 
+    """
 
-    scheduleEtags = property(_get_scheduleEtags, _set_scheduleEtags)
+    accessMode        = _MetadataProperty("accessMode")
+    isScheduleObject  = _MetadataProperty("isScheduleObject")
+    scheduleTag       = _MetadataProperty("scheduleTag")
+    scheduleEtags     = _MetadataProperty("scheduleEtags")
+    hasPrivateComment = _MetadataProperty("hasPrivateComment")
 
-    def _get_hasPrivateComment(self):
-        return self._newStoreObject.hasPrivateComment if self._newStoreObject else self._metadata.get("hasPrivateComment", None)
 
-    def _set_hasPrivateComment(self, value):
-        if self._newStoreObject:
-            self._newStoreObject.hasPrivateComment = value
-        else:
-            self._metadata["hasPrivateComment"] = value
 
-    hasPrivateComment = property(_get_hasPrivateComment, _set_hasPrivateComment)
-
 class CalendarObjectResource(_CalendarObjectMetaDataMixin, _CommonObjectResource):
     """
     A resource wrapping a calendar object.

Copied: CalendarServer/trunk/twistedcaldav/test/data/AnotherEvent.ics (from rev 7485, CalendarServer/branches/users/glyph/new-export/twistedcaldav/test/data/AnotherEvent.ics)
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/data/AnotherEvent.ics	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/data/AnotherEvent.ics	2011-05-16 15:02:27 UTC (rev 7486)
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.4//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_Yrok
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:EDT
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:EST
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CREATED:20110516T031511Z
+UID:1C4B4547-9D99-446B-B1D7-135AE04A319E
+DTEND;TZID=America/New_Yrok:20110516T130000
+TRANSP:OPAQUE
+SUMMARY:Another Event
+DTSTART;TZID=America/New_Yrok:20110516T114500
+DTSTAMP:20110516T031514Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR

Copied: CalendarServer/trunk/twistedcaldav/test/data/OneEvent.ics (from rev 7485, CalendarServer/branches/users/glyph/new-export/twistedcaldav/test/data/OneEvent.ics)
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/data/OneEvent.ics	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/data/OneEvent.ics	2011-05-16 15:02:27 UTC (rev 7486)
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.4//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_Yrok
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:EDT
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:EST
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CREATED:20110516T031505Z
+UID:89E0257D-3522-4423-B94E-60FF10BDAAFA
+DTEND;TZID=America/New_Yrok:20110516T110000
+TRANSP:OPAQUE
+SUMMARY:One Event
+DTSTART;TZID=America/New_Yrok:20110516T094500
+DTSTAMP:20110516T031511Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR

Copied: CalendarServer/trunk/twistedcaldav/test/data/ThirdEvent.ics (from rev 7485, CalendarServer/branches/users/glyph/new-export/twistedcaldav/test/data/ThirdEvent.ics)
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/data/ThirdEvent.ics	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/data/ThirdEvent.ics	2011-05-16 15:02:27 UTC (rev 7486)
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.4//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CREATED:20110516T033401Z
+UID:39F43724-3103-4379-9A0A-11FAD4FF542A
+DTEND;TZID=US/Pacific:20110516T114500
+TRANSP:OPAQUE
+SUMMARY:Third Event
+DTSTART;TZID=US/Pacific:20110516T104500
+DTSTAMP:20110516T033404Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR

Modified: CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -34,7 +34,6 @@
 from twistedcaldav.config import config
 from twistedcaldav.test.util import HomeTestCase
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twistedcaldav.memcacher import Memcacher
 from txdav.common.datastore.test.util import buildStore, StubNotifierFactory
 
 
@@ -355,9 +354,6 @@
 
     @inlineCallbacks
     def setUp(self):
-        self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
-        self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
-        self.patch(Memcacher, "allowTestCache", True)
         self.calendarStore = yield buildStore(self, StubNotifierFactory())
         yield super(DatabaseQueryTests, self).setUp()
 

Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -26,6 +26,7 @@
 
 from pycalendar.datetime import PyCalendarDateTime
 from pycalendar.timezone import PyCalendarTimezone
+from twistedcaldav.ical import iCalendarProductID
 from pycalendar.duration import PyCalendarDuration
 
 class iCalendar (twistedcaldav.test.util.TestCase):
@@ -50,6 +51,20 @@
             else:
                 SkipTest("test unimplemented")
 
+
+    def test_newCalendar(self):
+        """
+        L{Component.newCalendar} creates a new VCALENDAR L{Component} with
+        appropriate version and product identifiers, and no subcomponents.
+        """
+        calendar = Component.newCalendar()
+        version = calendar.getProperty("VERSION")
+        prodid = calendar.getProperty("PRODID")
+        self.assertEqual(version.value(), "2.0")
+        self.assertEqual(prodid.value(), iCalendarProductID)
+        self.assertEqual(list(calendar.subcomponents()), [])
+
+
     def test_component_equality(self):
 #        for filename in (
 #            os.path.join(self.data_dir, "Holidays", "C318A4BA-1ED0-11D9-A5E0-000A958A3252.ics"),

Modified: CalendarServer/trunk/twistedcaldav/test/test_resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_resource.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/test/test_resource.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -14,13 +14,23 @@
 # limitations under the License.
 ##
 
-from twistedcaldav.resource import CalDAVResource, CommonHomeResource, CalendarHomeResource, AddressBookHomeResource
+from twisted.internet.defer import inlineCallbacks
 
+from twext.web2.test.test_server import SimpleRequest
+
+from twext.web2.dav.davxml import Principal
+from twext.web2.dav.davxml import Unauthenticated
+from twext.web2.dav.element.rfc2518 import HRef
+
+from twistedcaldav.resource import (
+    CalDAVResource, CommonHomeResource, CalendarHomeResource,
+    AddressBookHomeResource)
+
 from twistedcaldav.test.util import InMemoryPropertyStore
 from twistedcaldav.test.util import TestCase
 from twistedcaldav.config import config
 
-from twisted.internet.defer import inlineCallbacks
+from twistedcaldav.test.util import patchConfig
 
 
 class StubProperty(object):
@@ -62,6 +72,7 @@
         self.assertTrue(('http://calendarserver.org/ns/', 'push-transports') in resource.liveProperties())
         self.assertTrue(('http://calendarserver.org/ns/', 'pushkey') in resource.liveProperties())
 
+
     def test_calendarHomeliveProperties(self):
         resource = CalendarHomeResource(None, None, None, StubHome())
         self.assertTrue(('http://calendarserver.org/ns/', 'push-transports') in resource.liveProperties())
@@ -70,6 +81,7 @@
         self.assertTrue(('http://calendarserver.org/ns/', 'xmpp-heartbeat-uri') in resource.liveProperties())
         self.assertTrue(('http://calendarserver.org/ns/', 'xmpp-server') in resource.liveProperties())
 
+
     def test_addressBookHomeliveProperties(self):
         resource = AddressBookHomeResource(None, None, None, StubHome())
         self.assertTrue(('http://calendarserver.org/ns/', 'push-transports') in resource.liveProperties())
@@ -78,6 +90,7 @@
         self.assertTrue(('http://calendarserver.org/ns/', 'xmpp-heartbeat-uri') not in resource.liveProperties())
         self.assertTrue(('http://calendarserver.org/ns/', 'xmpp-server') not in resource.liveProperties())
 
+
     @inlineCallbacks
     def test_push404(self):
         """
@@ -117,3 +130,88 @@
         self.assertEqual((yield resource.readProperty(('http://calendarserver.org/ns/', 'xmpp-uri'), None)), None)
         self.assertEqual((yield resource.readProperty(('http://calendarserver.org/ns/', 'xmpp-heartbeat-uri'), None)), None)
         self.assertEqual((yield resource.readProperty(('http://calendarserver.org/ns/', 'xmpp-server'), None)), None)
+
+
+
+class OwnershipTests(TestCase):
+    """
+    L{CalDAVResource.isOwner} determines if the authenticated principal of the
+    given request is the owner of that resource.
+    """
+
+    @inlineCallbacks
+    def test_isOwnerUnauthenticated(self):
+        """
+        L{CalDAVResource.isOwner} returns C{False} for unauthenticated requests.
+        """
+        site = None
+        request = SimpleRequest(site, "GET", "/not/a/real/url/")
+        request.authzUser = request.authnUser = Principal(Unauthenticated())
+        rsrc = CalDAVResource()
+        rsrc.owner = lambda igreq: HRef("/somebody/")
+        self.assertEquals((yield rsrc.isOwner(request)), False)
+
+
+    @inlineCallbacks
+    def test_isOwnerNo(self):
+        """
+        L{CalDAVResource.isOwner} returns C{True} for authenticated requests
+        with a principal that matches the resource's owner.
+        """
+        site = None
+        request = SimpleRequest(site, "GET", "/not/a/real/url/")
+        theOwner = Principal(HRef("/yes-i-am-the-owner/"))
+        request.authzUser = request.authnUser = theOwner
+        rsrc = CalDAVResource()
+        rsrc.owner = lambda igreq: HRef("/no-i-am-not-the-owner/")
+        self.assertEquals((yield rsrc.isOwner(request)), False)
+
+
+    @inlineCallbacks
+    def test_isOwnerYes(self):
+        """
+        L{CalDAVResource.isOwner} returns C{True} for authenticated requests
+        with a principal that matches the resource's owner.
+        """
+        site = None
+        request = SimpleRequest(site, "GET", "/not/a/real/url/")
+        theOwner = Principal(HRef("/yes-i-am-the-owner/"))
+        request.authzUser = request.authnUser = theOwner
+        rsrc = CalDAVResource()
+        rsrc.owner = lambda igreq: HRef("/yes-i-am-the-owner/")
+        self.assertEquals((yield rsrc.isOwner(request)), True)
+
+
+    @inlineCallbacks
+    def test_isOwnerAdmin(self):
+        """
+        L{CalDAVResource.isOwner} returns C{True} for authenticated requests
+        with a principal that matches any principal configured in the
+        L{AdminPrincipals} list.
+        """
+        theAdmin = "/read-write-admin/"
+        patchConfig(self, AdminPrincipals=[theAdmin])
+        site = None
+        request = SimpleRequest(site, "GET", "/not/a/real/url/")
+        request.authzUser = request.authnUser = Principal(HRef(theAdmin))
+        rsrc = CalDAVResource()
+        rsrc.owner = lambda igreq: HRef("/some-other-user/")
+        self.assertEquals((yield rsrc.isOwner(request)), True)
+
+
+    @inlineCallbacks
+    def test_isOwnerReadPrincipal(self):
+        """
+        L{CalDAVResource.isOwner} returns C{True} for authenticated requests
+        with a principal that matches any principal configured in the
+        L{AdminPrincipals} list.
+        """
+        theAdmin = "/read-only-admin/"
+        patchConfig(self, ReadPrincipals=[theAdmin])
+        site = None
+        request = SimpleRequest(site, "GET", "/not/a/real/url/")
+        request.authzUser = request.authnUser = Principal(HRef(theAdmin))
+        rsrc = CalDAVResource()
+        rsrc.owner = lambda igreq: HRef("/some-other-user/")
+        self.assertEquals((yield rsrc.isOwner(request)), True)
+

Modified: CalendarServer/trunk/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -24,7 +24,7 @@
 from twistedcaldav import customxml
 from twistedcaldav.config import config
 from twistedcaldav.test.util import HomeTestCase, norequest
-from twistedcaldav.memcacher import Memcacher
+
 from twistedcaldav.resource import CalDAVResource
 from txdav.common.datastore.test.util import buildStore, StubNotifierFactory
 
@@ -547,9 +547,6 @@
 
     @inlineCallbacks
     def setUp(self):
-        self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
-        self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
-        self.patch(Memcacher, "allowTestCache", True)
         self.calendarStore = yield buildStore(self, StubNotifierFactory())
         yield super(DatabaseSharingTests, self).setUp()
 

Modified: CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_wrapping.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/test/test_wrapping.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -46,7 +46,6 @@
     StubNotifierFactory
 
 
-from twistedcaldav.memcacher import Memcacher
 from txdav.caldav.icalendarstore import ICalendarHome
 from txdav.carddav.iaddressbookstore import IAddressBookHome
 
@@ -470,9 +469,6 @@
 
     @inlineCallbacks
     def setUp(self):
-        self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
-        self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
-        self.patch(Memcacher, "allowTestCache", True)
         self.calendarStore = yield buildStore(self, StubNotifierFactory())
         super(DatabaseWrappingTests, self).setUp()
 

Modified: CalendarServer/trunk/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/util.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/twistedcaldav/test/util.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -586,8 +586,21 @@
 
 
 
+def patchConfig(testCase, **kw):
+    """
+    Patch the global configuration (including running the appropriate hooks) for
+    the duration of the given test.
+    """
+    preserved = {}
+    for k in kw:
+        preserved[k] = config.get(k, None)
+    def reUpdate():
+        config.update(preserved)
+    testCase.addCleanup(reUpdate)
+    config.update(kw)
 
 
+
 class ErrorOutput(Exception):
     """
     The process produced some error output and exited with a non-zero exit

Modified: CalendarServer/trunk/txdav/base/datastore/subpostgres.py
===================================================================
--- CalendarServer/trunk/txdav/base/datastore/subpostgres.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/txdav/base/datastore/subpostgres.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -87,8 +87,9 @@
 
     def processEnded(self, reason):
         log.msg("postgres process ended %r" % (reason,))
+        result = (reason.value.status == 0)
         self.lineReceiver.connectionLost(reason)
-        self.completionDeferred.callback(None)
+        self.completionDeferred.callback(result)
 
 
 
@@ -362,6 +363,7 @@
         )
         self.monitor = monitor
         def gotReady(result):
+            self.shouldStopDatabase = result
             self.ready()
             self.deactivateDelayedShutdown()
         def reportit(f):
@@ -370,6 +372,7 @@
         self.monitor.completionDeferred.addCallback(
             gotReady).addErrback(reportit)
 
+    shouldStopDatabase = False
 
     def startService(self):
         MultiService.startService(self)
@@ -423,14 +426,17 @@
             d = MultiService.stopService(self)
 
         def superStopped(result):
-            monitor = _PostgresMonitor()
-            pg_ctl = which("pg_ctl")[0]
-            reactor.spawnProcess(monitor, pg_ctl,
-                [pg_ctl, '-l', 'logfile', 'stop'],
-                self.env,
-                uid=self.uid, gid=self.gid,
-            )
-            return monitor.completionDeferred
+            # If pg_ctl's startup wasn't successful, don't bother to stop the
+            # database.  (This also happens in command-line tools.)
+            if self.shouldStopDatabase:
+                monitor = _PostgresMonitor()
+                pg_ctl = which("pg_ctl")[0]
+                reactor.spawnProcess(monitor, pg_ctl,
+                    [pg_ctl, '-l', 'logfile', 'stop'],
+                    self.env,
+                    uid=self.uid, gid=self.gid,
+                )
+                return monitor.completionDeferred
         return d.addCallback(superStopped)
 
 #        def maybeStopSubprocess(result):

Modified: CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -26,10 +26,8 @@
 from txdav.base.propertystore.test.base import (
     PropertyStoreTest, propertyName, propertyValue)
 
-from twistedcaldav import memcacher
 from twisted.internet.defer import gatherResults
 from twext.enterprise.ienterprise import AlreadyFinishedError
-from twistedcaldav.config import config
 
 try:
     from txdav.base.propertystore.sql import PropertyStore
@@ -45,10 +43,6 @@
 
     @inlineCallbacks
     def setUp(self):
-        self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
-        self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
-        self.patch(memcacher.Memcacher, "allowTestCache", True)
-
         self.notifierFactory = StubNotifierFactory()
         self.store = yield buildStore(self, self.notifierFactory)
         self.addCleanup(self.maybeCommitLast)

Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -55,13 +55,12 @@
 from txdav.caldav.datastore.index_file import Index as OldIndex,\
     IndexSchedule as OldInboxIndex
 from txdav.caldav.datastore.util import (
-    validateCalendarComponent, dropboxIDFromCalendarObject
+    validateCalendarComponent, dropboxIDFromCalendarObject, CalendarObjectBase
 )
 
 from txdav.common.datastore.file import (
     CommonDataStore, CommonStoreTransaction, CommonHome, CommonHomeChild,
-    CommonObjectResource
-, CommonStubResource)
+    CommonObjectResource, CommonStubResource)
 
 from txdav.common.icommondatastore import (NoSuchObjectResourceError,
     InternalDataStoreError)
@@ -262,7 +261,7 @@
 
 
 
-class CalendarObject(CommonObjectResource):
+class CalendarObject(CommonObjectResource, CalendarObjectBase):
     """
     @ivar _path: The path of the .ics file on disk
 
@@ -340,8 +339,6 @@
 
 
     def component(self):
-        if self._component is not None:
-            return self._component
         text = self.text()
 
         try:


Property changes on: CalendarServer/trunk/txdav/caldav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/caldav/datastore/index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/caldav/datastore/index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/caldav/datastore/index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/caldav/datastore/index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/caldav/datastore/index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -67,6 +67,8 @@
 from twext.enterprise.dal.syntax import Parameter
 from twext.enterprise.dal.syntax import utcNowSQL
 from twext.enterprise.dal.syntax import Len
+
+from txdav.caldav.datastore.util import CalendarObjectBase
 from txdav.common.icommondatastore import IndexedSearchException
 
 from pycalendar.datetime import PyCalendarDateTime
@@ -113,7 +115,7 @@
 
     @inlineCallbacks
     def hasCalendarResourceUIDSomewhereElse(self, uid, ok_object, type):
-        
+
         objectResources = (yield self.objectResourcesWithUID(uid, ("inbox",)))
         for objectResource in objectResources:
             if ok_object and objectResource._resourceID == ok_object._resourceID:
@@ -121,18 +123,18 @@
             matched_type = "schedule" if objectResource.isScheduleObject else "calendar"
             if type == "schedule" or matched_type == "schedule":
                 returnValue(True)
-            
+
         returnValue(False)
 
     @inlineCallbacks
     def getCalendarResourcesForUID(self, uid, allow_shared=False):
-        
+
         results = []
         objectResources = (yield self.objectResourcesWithUID(uid, ("inbox",)))
         for objectResource in objectResources:
             if allow_shared or objectResource._parentCollection._owned:
                 results.append(objectResource)
-            
+
         returnValue(results)
 
 
@@ -305,7 +307,7 @@
 
 
 
-class CalendarObject(CommonObjectResource):
+class CalendarObject(CommonObjectResource, CalendarObjectBase):
     implements(ICalendarObject)
 
     _objectTable = CALENDAR_OBJECT_TABLE
@@ -314,7 +316,7 @@
     def __init__(self, calendar, name, uid, resourceID=None, metadata=None):
 
         super(CalendarObject, self).__init__(calendar, name, uid, resourceID)
-        
+
         if metadata is None:
             metadata = {}
         self.accessMode = metadata.get("accessMode", "")
@@ -406,7 +408,7 @@
             expand = PyCalendarDateTime(2100, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
             doInstanceIndexing = True
         else:
-            
+
             # If migrating or re-creating or config option for delayed indexing is off, always index
             if reCreate or self._txn._migrating or not config.FreeBusyIndexDelayedExpand:
                 doInstanceIndexing = True
@@ -453,11 +455,11 @@
             else:
                 raise
 
-        # Now coerce indexing to off if needed 
+        # Now coerce indexing to off if needed
         if not doInstanceIndexing:
             instances = None
             recurrenceLimit = PyCalendarDateTime(1900, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
-            
+
         co = schema.CALENDAR_OBJECT
         tr = schema.TIME_RANGE
         tpy = schema.TRANSPARENCY
@@ -470,13 +472,13 @@
             organizer = component.getOrganizer()
             if not organizer:
                 organizer = ""
-    
+
             # CALENDAR_OBJECT table update
             self._uid = component.resourceUID()
             self._md5 = hashlib.md5(componentText).hexdigest()
             self._size = len(componentText)
 
-            # Special - if migrating we need to preserve the original md5    
+            # Special - if migrating we need to preserve the original md5
             if self._txn._migrating and hasattr(component, "md5"):
                 self._md5 = component.md5
 
@@ -494,7 +496,7 @@
                     # server dropbox collections and only then set the read mode
                     self._attachment = _ATTACHMENTS_MODE_READ
                     self._dropboxID = (yield self.dropboxID())
-    
+
             values = {
                 co.CALENDAR_RESOURCE_ID            : self._calendar._resourceID,
                 co.RESOURCE_NAME                   : self._name,
@@ -513,7 +515,7 @@
                 co.PRIVATE_COMMENTS                : self._private_comments,
                 co.MD5                             : self._md5
             }
-    
+
             if inserting:
                 self._resourceID, self._created, self._modified = (
                     yield Insert(
@@ -539,12 +541,12 @@
                 co.RECURRANCE_MAX :
                     pyCalendarTodatetime(normalizeForIndex(recurrenceLimit)) if recurrenceLimit else None,
             }
-    
+
             yield Update(
                 values,
                 Where=co.RESOURCE_ID == self._resourceID
             ).on(self._txn)
-            
+
             # Need to wipe the existing time-range for this and rebuild
             yield Delete(
                 From=tr,
@@ -580,7 +582,7 @@
                         tpy.USER_ID                : useruid,
                         tpy.TRANSPARENT            : transp,
                     }).on(self._txn))
-    
+
             # Special - for unbounded recurrence we insert a value for "infinity"
             # that will allow an open-ended time-range to always match it.
             if component.isRecurringUnbounded():
@@ -622,7 +624,7 @@
 
     def getMetadata(self):
         metadata = {}
-        metadata["accessMode"] = self.accessMode 
+        metadata["accessMode"] = self.accessMode
         metadata["isScheduleObject"] = self.isScheduleObject
         metadata["scheduleTag"] = self.scheduleTag
         metadata["scheduleEtags"] = self.scheduleEtags
@@ -671,7 +673,7 @@
 
     @inlineCallbacks
     def createAttachmentWithName(self, name):
-        
+
         # We need to know the resource_ID of the home collection of the owner (not sharee)
         # of this event
         sharerHomeID = (yield self._parentCollection.sharerHomeID())

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -912,7 +912,100 @@
         self.assertEquals(component.resourceUID(), "uid1")
 
 
+    perUserComponent = lambda self: VComponent.fromString("""BEGIN:VCALENDAR
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTART:20110101T120000Z
+DTEND:20110101T120100Z
+UID:event-with-some-per-user-data
+ATTENDEE:urn:uuid:home1
+ORGANIZER:urn:uuid:home1
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+X-CALENDARSERVER-PERUSER-UID:some-other-user
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:somebody else
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+X-CALENDARSERVER-PERUSER-UID:home1
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:the owner
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n"))
+
+
+    asSeenByOwner = lambda self: VComponent.fromString("""BEGIN:VCALENDAR
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTART:20110101T120000Z
+DTEND:20110101T120100Z
+UID:event-with-some-per-user-data
+ATTENDEE:urn:uuid:home1
+ORGANIZER:urn:uuid:home1
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:the owner
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"))
+
+
+    asSeenByOther = lambda self: VComponent.fromString("""BEGIN:VCALENDAR
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTART:20110101T120000Z
+DTEND:20110101T120100Z
+UID:event-with-some-per-user-data
+ATTENDEE:urn:uuid:home1
+ORGANIZER:urn:uuid:home1
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:somebody else
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"))
+
+
     @inlineCallbacks
+    def setUpPerUser(self):
+        """
+        Set up state for testing of per-user components.
+        """
+        cal = yield self.calendarUnderTest()
+        yield cal.createCalendarObjectWithName(
+            "per-user-stuff.ics",
+            self.perUserComponent())
+        returnValue((yield cal.calendarObjectWithName("per-user-stuff.ics")))
+
+
+    @inlineCallbacks
+    def test_filteredComponent(self):
+        """
+        L{ICalendarObject.filteredComponent} returns a L{VComponent} that has
+        filtered per-user data.
+        """
+        obj = yield self.setUpPerUser()
+        otherComp = (yield obj.filteredComponent("some-other-user"))
+        self.assertEquals(otherComp, self.asSeenByOther())
+        ownerComp = (yield obj.filteredComponent("home1"))
+        self.assertEquals(ownerComp, self.asSeenByOwner())
+
+
+    @inlineCallbacks
     def test_iCalendarText(self):
         """
         L{ICalendarObject.iCalendarText} returns a C{str} describing the same

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -375,23 +375,6 @@
         )
 
 
-    @inlineCallbacks
-    def test_modifyCalendarObjectCaches(self):
-        """
-        Modifying a calendar object should cache the modified component in
-        memory, to avoid unnecessary parsing round-trips.
-        """
-        self.addCleanup(self.txn.commit)
-        modifiedComponent = VComponent.fromString(event1modified_text)
-        (yield self.calendar1.calendarObjectWithName("1.ics")).setComponent(
-            modifiedComponent
-        )
-        self.assertIdentical(
-            modifiedComponent,
-            (yield self.calendar1.calendarObjectWithName("1.ics")).component()
-        )
-
-
     @featureUnimplemented
     def test_removeCalendarObjectWithUID_absent(self):
         """


Property changes on: CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/test_index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/caldav/datastore/test/test_index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/caldav/datastore/test/test_index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/test_index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/caldav/datastore/test/test_index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/caldav/datastore/test/test_index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/caldav/datastore/test/test_index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -38,8 +38,8 @@
 from txdav.common.datastore.sql_tables import schema
 from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom
 
-from twistedcaldav import memcacher, caldavxml
-from twistedcaldav.config import config
+from twistedcaldav import caldavxml
+
 from twistedcaldav.dateops import datetimeMktime
 from twistedcaldav.sharing import SharedCollectionRecord
 
@@ -52,10 +52,6 @@
 
     @inlineCallbacks
     def setUp(self):
-        self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
-        self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
-        self.patch(memcacher.Memcacher, "allowTestCache", True)
-
         yield super(CalendarSQLStorageTests, self).setUp()
         self._sqlCalendarStore = yield buildStore(self, self.notifierFactory)
         yield self.populate()

Modified: CalendarServer/trunk/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/util.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/txdav/caldav/datastore/util.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -28,6 +28,8 @@
 from txdav.common.icommondatastore import InvalidObjectResourceError, \
     NoSuchObjectResourceError, InternalDataStoreError
 
+from twistedcaldav.datafilters.privateevents import PrivateEventFilter
+from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 from twext.python.log import Logger
 log = Logger()
 
@@ -215,3 +217,46 @@
     # No migration for notifications, since they weren't present in earlier
     # released versions of CalendarServer.
 
+
+
+class CalendarObjectBase(object):
+    """
+    Base logic shared between file- and sql-based L{ICalendarObject}
+    implementations.
+    """
+
+    @inlineCallbacks
+    def filteredComponent(self, accessUID, asAdmin=False):
+        """
+        Filter this calendar object's iCalendar component as it would be
+        perceived by a particular user, accounting for per-user iCalendar data
+        and private events, and return a L{Deferred} that fires with that
+        object.
+
+        Unlike the result of C{component()}, which contains storage-specific
+        iCalendar properties, this is a valid iCalendar object which could be
+        serialized and displayed to other iCalendar-processing software.
+
+        @param accessUID: the UID of the principal who is accessing this
+            component.
+        @type accessUID: C{str} (UTF-8 encoded)
+
+        @param asAdmin: should the given UID be treated as an administrator?  If
+            this is C{True}, the resulting component will have an unobscured
+            view of private events, even if the given UID is not actually the
+            owner of said events.  (However, per-instance overridden values will
+            still be seen as the given C{accessUID}.)
+
+        @return: a L{Deferred} which fires with a
+            L{twistedcaldav.ical.Component}.
+        """
+        component = yield self.component()
+        calendar = self.calendar()
+        isOwner = asAdmin or (calendar._owned and
+                              calendar.ownerCalendarHome().uid() == accessUID)
+        for filter in [PrivateEventFilter(self.accessMode, isOwner),
+                       PerUserDataFilter(accessUID)]:
+            component = filter.filter(component)
+        returnValue(component)
+
+


Property changes on: CalendarServer/trunk/txdav/carddav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/carddav/datastore/index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/carddav/datastore/index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/carddav/datastore/index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/carddav/datastore/index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/carddav/datastore/index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394


Property changes on: CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/test/test_index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/carddav/datastore/test/test_index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/carddav/datastore/test/test_index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/test/test_index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/carddav/datastore/test/test_index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/carddav/datastore/test/test_index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/carddav/datastore/test/test_index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394

Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -25,8 +25,8 @@
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.trial import unittest
 
-from twistedcaldav import memcacher, carddavxml
-from twistedcaldav.config import config
+from twistedcaldav import carddavxml
+
 from twistedcaldav.vcard import Component as VCard
 from twistedcaldav.vcard import Component as VComponent
 
@@ -47,10 +47,6 @@
 
     @inlineCallbacks
     def setUp(self):
-        self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
-        self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
-        self.patch(memcacher.Memcacher, "allowTestCache", True)
-
         yield super(AddressBookSQLStorageTests, self).setUp()
         self._sqlStore = yield buildStore(self, self.notifierFactory)
         yield self.populate()

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -1304,6 +1304,10 @@
 class CommonHomeChild(LoggingMixIn, FancyEqMixin, _SharedSyncLogic):
     """
     Common ancestor class of AddressBooks and Calendars.
+
+    @ivar _owned: Is this calendar or addressbook referencing its sharer (owner)
+        home? (i.e. C{True} if L{ownerCalendarHome} will actually return the
+        sharer, C{False} or if it will return a sharee.)
     """
 
     compareAttributes = (

Modified: CalendarServer/trunk/txdav/common/datastore/test/test_util.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/test/test_util.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/txdav/common/datastore/test/test_util.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -26,9 +26,6 @@
 from twisted.internet.protocol import Protocol
 from twisted.trial.unittest import TestCase
 
-from twistedcaldav.config import config
-from twistedcaldav.memcacher import Memcacher
-
 from txdav.caldav.datastore.test.common import CommonTests
 from txdav.carddav.datastore.test.common import CommonTests as ABCommonTests
 from txdav.common.datastore.file import CommonDataStore
@@ -48,9 +45,7 @@
         Set up two stores to migrate between.
         """
         # Add some files to the file store.
-        self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
-        self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
-        self.patch(Memcacher, "allowTestCache", True)
+
         self.filesPath = CachingFilePath(self.mktemp())
         self.filesPath.createDirectory()
         fileStore = self.fileStore = CommonDataStore(

Modified: CalendarServer/trunk/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/test/util.py	2011-05-16 14:49:19 UTC (rev 7485)
+++ CalendarServer/trunk/txdav/common/datastore/test/util.py	2011-05-16 15:02:27 UTC (rev 7486)
@@ -76,6 +76,7 @@
 
         @return: a L{Deferred} which fires with an L{IDataStore}.
         """
+        disableMemcacheForTest(testCase)
         dbRoot = CachingFilePath(self.SHARED_DB_PATH)
         attachmentRoot = dbRoot.child("attachments")
         if self.sharedService is None:
@@ -423,3 +424,24 @@
 
     def reset(self):
         self.history = []
+
+
+
+def disableMemcacheForTest(aTest):
+    """
+    Disable all memcache logic for the duration of a test; we shouldn't be
+    starting or connecting to any memcache stuff for most tests.
+    """
+
+    # These imports are local so that they don't accidentally leak to anything
+    # else in this module; nothing else in this module should ever touch global
+    # configuration. -glyph
+
+    from twistedcaldav.config import config
+    from twistedcaldav.memcacher import Memcacher
+
+    aTest.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
+    aTest.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
+    aTest.patch(Memcacher, "allowTestCache", True)
+
+
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110516/a54e04d2/attachment-0001.html>


More information about the calendarserver-changes mailing list