[CalendarServer-changes] [5595] CalendarServer/branches/new-store

source_changes at macosforge.org source_changes at macosforge.org
Wed May 12 22:04:16 PDT 2010


Revision: 5595
          http://trac.macosforge.org/projects/calendarserver/changeset/5595
Author:   glyph at apple.com
Date:     2010-05-12 22:04:13 -0700 (Wed, 12 May 2010)
Log Message:
-----------
Merge forward from 'transations' branch.

Modified Paths:
--------------
    CalendarServer/branches/new-store/conf/auth/accounts-test.xml
    CalendarServer/branches/new-store/conf/auth/augments-test.xml
    CalendarServer/branches/new-store/test
    CalendarServer/branches/new-store/twext/python/datetime.py
    CalendarServer/branches/new-store/twext/python/filepath.py
    CalendarServer/branches/new-store/twext/python/test/test_datetime.py
    CalendarServer/branches/new-store/twext/web2/dav/element/base.py
    CalendarServer/branches/new-store/twistedcaldav/directory/test/test_calendar.py
    CalendarServer/branches/new-store/twistedcaldav/directory/test/test_guidchange.py
    CalendarServer/branches/new-store/twistedcaldav/directory/test/test_proxyprincipalmembers.py
    CalendarServer/branches/new-store/twistedcaldav/directory/test/test_xmlfile.py
    CalendarServer/branches/new-store/twistedcaldav/resource.py
    CalendarServer/branches/new-store/twistedcaldav/static.py
    CalendarServer/branches/new-store/twistedcaldav/test/test_calendarquery.py
    CalendarServer/branches/new-store/twistedcaldav/test/test_collectioncontents.py
    CalendarServer/branches/new-store/twistedcaldav/test/test_index.py
    CalendarServer/branches/new-store/twistedcaldav/test/test_mkcalendar.py
    CalendarServer/branches/new-store/twistedcaldav/test/test_multiget.py
    CalendarServer/branches/new-store/twistedcaldav/test/test_props.py
    CalendarServer/branches/new-store/twistedcaldav/test/util.py
    CalendarServer/branches/new-store/txcaldav/calendarstore/file.py
    CalendarServer/branches/new-store/txcaldav/calendarstore/test/test_file.py
    CalendarServer/branches/new-store/txcaldav/icalendarstore.py
    CalendarServer/branches/new-store/txdav/idav.py
    CalendarServer/branches/new-store/txdav/propertystore/base.py
    CalendarServer/branches/new-store/txdav/propertystore/test/test_xattr.py
    CalendarServer/branches/new-store/txdav/propertystore/xattr.py

Added Paths:
-----------
    CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py

Property Changed:
----------------
    CalendarServer/branches/new-store/


Property changes on: CalendarServer/branches/new-store
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/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/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/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

Modified: CalendarServer/branches/new-store/conf/auth/accounts-test.xml
===================================================================
--- CalendarServer/branches/new-store/conf/auth/accounts-test.xml	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/conf/auth/accounts-test.xml	2010-05-13 05:04:13 UTC (rev 5595)
@@ -35,6 +35,51 @@
     <first-name>Apprentice</first-name>
     <last-name>Super User</last-name>
   </user>
+  <user>
+    <uid>wsanchez</uid>
+    <guid>wsanchez</guid>
+    <email-address>wsanchez at apple.com</email-address>
+    <password>demo</password>
+    <name>Wilfredo Sanchez Vega</name>
+    <first-name>Wilfredo</first-name>
+    <last-name>Sanchez Vega</last-name>
+  </user>
+  <user>
+    <uid>cdaboo</uid>
+    <guid>cdaboo</guid>
+    <email-address>cdaboo at apple.com</email-address>
+    <password>demo</password>
+    <name>Cyrus Daboo</name>
+    <first-name>Cyrus</first-name>
+    <last-name>Daboo</last-name>
+  </user>
+  <user>
+    <uid>sagen</uid>
+    <guid>sagen</guid>
+    <email-address>sagen at apple.com</email-address>
+    <password>demo</password>
+    <name>Morgen Sagen</name>
+    <first-name>Morgen</first-name>
+    <last-name>Sagen</last-name>
+  </user>
+  <user>
+    <uid>dre</uid>
+    <guid>andre</guid>
+    <email-address>dre at apple.com</email-address>
+    <password>demo</password>
+    <name>Andre LaBranche</name>
+    <first-name>Andre</first-name>
+    <last-name>LaBranche</last-name>
+  </user>
+  <user>
+    <uid>glyph</uid>
+    <guid>glyph</guid>
+    <email-address>glyph at apple.com</email-address>
+    <password>demo</password>
+    <name>Glyph Lefkowitz</name>
+    <first-name>Glyph</first-name>
+    <last-name>Lefkowitz</last-name>
+  </user>
   <user repeat="99">
     <uid>user%02d</uid>
     <uid>User %02d</uid>
@@ -102,4 +147,58 @@
       <member type="users">user01</member>
     </members>
   </group>
+  <location>
+    <uid>mercury</uid>
+    <guid>mercury</guid>
+    <password>demo</password>
+    <name>Mecury Conference Room, Building 1, 2nd Floor</name>
+  </location>
+  <location>
+    <uid>venus</uid>
+    <guid>venus</guid>
+    <password>demo</password>
+    <name>Venus Conference Room, Building 1, 2nd Floor</name>
+  </location>
+  <location>
+    <uid>Earth</uid>
+    <guid>Earth</guid>
+    <password>demo</password>
+    <name>Earth Conference Room, Building 1, 1st Floor</name>
+  </location>
+  <location>
+    <uid>mars</uid>
+    <guid>mars</guid>
+    <password>demo</password>
+    <name>Mars Conference Room, Building 1, 1st Floor</name>
+  </location>
+  <location>
+    <uid>jupiter</uid>
+    <guid>jupiter</guid>
+    <password>demo</password>
+    <name>Jupiter Conference Room, Building 2, 1st Floor</name>
+  </location>
+  <location>
+    <uid>neptune</uid>
+    <guid>neptune</guid>
+    <password>demo</password>
+    <name>Neptune Conference Room, Building 2, 1st Floor</name>
+  </location>
+  <location>
+    <uid>pluto</uid>
+    <guid>pluto</guid>
+    <password>demo</password>
+    <name>Pluto Conference Room, Building 2, 1st Floor</name>
+  </location>
+  <location>
+    <uid>saturn</uid>
+    <guid>saturn</guid>
+    <password>demo</password>
+    <name>Saturn Conference Room, Building 2, 1st Floor</name>
+  </location>
+  <location>
+    <uid>uranus</uid>
+    <guid>uranus</guid>
+    <password>demo</password>
+    <name>Uranus Conference Room, Building 3, 1st Floor</name>
+  </location>
 </accounts>

Modified: CalendarServer/branches/new-store/conf/auth/augments-test.xml
===================================================================
--- CalendarServer/branches/new-store/conf/auth/augments-test.xml	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/conf/auth/augments-test.xml	2010-05-13 05:04:13 UTC (rev 5595)
@@ -25,6 +25,38 @@
     <enable-calendar>true</enable-calendar>
     <enable-addressbook>true</enable-addressbook>
   </record>
+
+  <record>
+    <uid>wsanchez</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+  </record>
+  <record>
+    <uid>glyph</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+  </record>
+  <record>
+    <uid>sagen</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+  </record>
+  <record>
+    <uid>dre</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+  </record>
+  <record>
+    <uid>cdaboo</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+  </record>
+
   <record repeat="10">
     <uid>location%02d</uid>
     <enable>true</enable>

Modified: CalendarServer/branches/new-store/test
===================================================================
--- CalendarServer/branches/new-store/test	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/test	2010-05-13 05:04:13 UTC (rev 5595)
@@ -21,8 +21,13 @@
 
 wd="$(cd "$(dirname "$0")" && pwd -L)";
 
-. support/py.sh;
+DAVD=cal;
 
+. support/build.sh;
+
+do_setup="false";
+do_get="false";
+
 random="--random=$(date "+%s")";
 no_colour="";
 until_fail="";
@@ -62,14 +67,13 @@
 done;
 shift $((${OPTIND} - 1));
 
-twisted="$(cd "${wd}/.." && pwd -L)/Twisted";
+export PYTHONPATH="${wd}";
+dependencies;
 
-export PYTHONPATH="$("${wd}/run" -p)";
-
 if [ $# -gt 0 ]; then
     test_modules="$@";
 else
     test_modules="calendarserver twistedcaldav twext txdav txcaldav txcarddav ${m_twisted}";
 fi;
 
-cd "${wd}" && "${python}" "${twisted}/bin/trial" --rterrors ${random} ${until_fail} ${no_colour} ${coverage} ${test_modules};
+cd "${wd}" && "trial" --rterrors ${random} ${until_fail} ${no_colour} ${coverage} ${test_modules};

Modified: CalendarServer/branches/new-store/twext/python/datetime.py
===================================================================
--- CalendarServer/branches/new-store/twext/python/datetime.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twext/python/datetime.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -136,6 +136,14 @@
     def dateOrDatetime(self):
         return self._dateOrDatetime
 
+    def timetuple(self):
+        #
+        # This is required to make comparison with datetimes work. See:
+        # http://bugs.python.org/issue8005
+        # http://docs.python.org/release/2.6.5/library/datetime.html#datetime.date.day
+        #
+        return self._dateOrDatetime.timetuple()
+
     def iCalendarString(self):
         if self._isDatetime:
             return dateTimeToString(self._dateOrDatetime)

Modified: CalendarServer/branches/new-store/twext/python/filepath.py
===================================================================
--- CalendarServer/branches/new-store/twext/python/filepath.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twext/python/filepath.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -31,14 +31,14 @@
 from types import FunctionType, MethodType
 from errno import EINVAL
 
-from twisted.python.filepath import FilePath
+from twisted.python.filepath import FilePath as _FilePath
 
 from stat import S_ISDIR
 
-class CachingFilePath(FilePath, object):
+class CachingFilePath(_FilePath, object):
     """
-    A descendent of L{twisted.python.filepath.FilePath} which implements a more
-    aggressive caching policy.
+    A descendent of L{_FilePath} which implements a more aggressive caching
+    policy.
     """
 
     _listdir = _listdir         # integration points for tests
@@ -56,11 +56,11 @@
     @property
     def siblingExtensionSearch(self):
         """
-        Dynamically create a version of L{FilePath.siblingExtensionSearch} that
+        Dynamically create a version of L{_FilePath.siblingExtensionSearch} that
         uses a pluggable 'listdir' implementation.
         """
         return MethodType(FunctionType(
-                FilePath.siblingExtensionSearch.im_func.func_code,
+                _FilePath.siblingExtensionSearch.im_func.func_code,
                 {'listdir': self._retryListdir,
                  'basename': _basename,
                  'dirname': _dirname,
@@ -121,20 +121,19 @@
 
     def moveTo(self, destination, followLinks=True):
         """
-        Override L{FilePath.moveTo}, updating extended cache information if
+        Override L{_FilePath.moveTo}, updating extended cache information if
         necessary.
         """
-        try:
-            return super(CachingFilePath, self).moveTo(destination, followLinks)
-        except OSError:
-            raise
-        else:
-            self.changed()
+        result = super(CachingFilePath, self).moveTo(destination, followLinks)
+        self.changed()
+        # Work with vanilla FilePath destinations to pacify the tests. 
+        if hasattr(destination, "changed"):
+            destination.changed()
 
 
     def remove(self):
         """
-        Override L{FilePath.remove}, updating extended cache information if
+        Override L{_FilePath.remove}, updating extended cache information if
         necessary.
         """
         try:

Modified: CalendarServer/branches/new-store/twext/python/test/test_datetime.py
===================================================================
--- CalendarServer/branches/new-store/twext/python/test/test_datetime.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twext/python/test/test_datetime.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -46,6 +46,10 @@
 
 
 class DatetimeTests(TestCase):
+    @testUnimplemented
+    def test_timetuple(self):
+        raise NotImplementedError()
+
     @timezones
     def test_date_date(self, tz):
         d = date.today()
@@ -101,7 +105,7 @@
         #
 
         self.assertTrue (first        == base(first) )
-       #self.assertTrue (base(first)  == first       ) # Bug in datetime
+        self.assertTrue (base(first)  == first       ) # Bug in datetime
         self.assertTrue (first        == base(first) )
         self.assertTrue (first        != base(second))
         self.assertTrue (base(first)  != second      )
@@ -109,36 +113,36 @@
         self.assertTrue (first        <  second      )
         self.assertTrue (second       <  third       )
         self.assertTrue (first        <  base(second))
-       #self.assertTrue (base(second) <  third       ) # Bug in datetime
+        self.assertTrue (base(second) <  third       ) # Bug in datetime
         self.assertTrue (first        <  second      )
         self.assertTrue (second       <  third       )
-       #self.assertTrue (base(first)  <  second      )
+        self.assertTrue (base(first)  <  second      )
         self.assertTrue (second       <  base(third) ) # Bug in datetime
         self.assertTrue (first        <= second      )
         self.assertTrue (second       <= third       )
         self.assertTrue (first        <= base(second))
-       #self.assertTrue (base(second) <= third       ) # Bug in datetime
+        self.assertTrue (base(second) <= third       ) # Bug in datetime
         self.assertTrue (first        <= base(second))
-       #self.assertTrue (base(second) <= third       ) # Bug in datetime
+        self.assertTrue (base(second) <= third       ) # Bug in datetime
         self.assertTrue (first        <= second      )
         self.assertTrue (second       <= third       )
-       #self.assertTrue (base(first)  <= second      ) # Bug in datetime
+        self.assertTrue (base(first)  <= second      ) # Bug in datetime
         self.assertTrue (second       <= base(third) )
         self.assertFalse(first        >  second      )
         self.assertFalse(second       >  third       )
         self.assertFalse(first        >  base(second))
-       #self.assertFalse(base(second) >  third       ) # Bug in datetime
+        self.assertFalse(base(second) >  third       ) # Bug in datetime
         self.assertFalse(first        >  second      )
         self.assertFalse(second       >  third       )
-       #self.assertFalse(base(first)  >  second      ) # Bug in datetime
+        self.assertFalse(base(first)  >  second      ) # Bug in datetime
         self.assertFalse(second       >  base(third) )
         self.assertFalse(first        >= second      )
         self.assertFalse(second       >= third       )
         self.assertFalse(first        >= base(second))
-       #self.assertFalse(base(second) >= third       ) # Bug in datetime
+        self.assertFalse(base(second) >= third       ) # Bug in datetime
         self.assertFalse(first        >= second      )
         self.assertFalse(second       >= third       )
-       #self.assertFalse(base(first)  >= second      ) # Bug in datetime
+        self.assertFalse(base(first)  >= second      ) # Bug in datetime
         self.assertFalse(second       >= base(third) )
 
     def test_date_iCalendarString(self):
@@ -231,7 +235,7 @@
 
     @testUnimplemented
     def test_compare(self):
-        raise NotImplemented
+        raise NotImplementedError()
 
     @featureUnimplemented
     def test_overlapsWith(self):

Modified: CalendarServer/branches/new-store/twext/web2/dav/element/base.py
===================================================================
--- CalendarServer/branches/new-store/twext/web2/dav/element/base.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twext/web2/dav/element/base.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -75,11 +75,11 @@
     protected          = False         # See RFC 3253 section 1.4.1
     unregistered       = False         # Subclass of factory; doesn't register
 
-    def qname(self):
-        return (self.namespace, self.name)
+    def qname(cls):
+        return (cls.namespace, cls.name)
 
-    def sname(self):
-        return "{%s}%s" % (self.namespace, self.name)
+    def sname(cls):
+        return "{%s}%s" % (cls.namespace, cls.name)
 
     qname = classmethod(qname)
     sname = classmethod(sname)

Modified: CalendarServer/branches/new-store/twistedcaldav/directory/test/test_calendar.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/directory/test/test_calendar.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twistedcaldav/directory/test/test_calendar.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -14,57 +14,25 @@
 # limitations under the License.
 ##
 
-import os
-
 from twisted.internet.defer import inlineCallbacks
 from twext.web2.dav import davxml
 from twext.web2.test.test_server import SimpleRequest
 
 from twistedcaldav import caldavxml
-from twistedcaldav.directory import augment
-from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.static import CalendarHomeProvisioningFile
 
-import twistedcaldav.test.util
-from twistedcaldav.config import config
+from twistedcaldav.test.util import TestCase
 
-class ProvisionedCalendars (twistedcaldav.test.util.TestCase):
+class ProvisionedCalendars (TestCase):
     """
     Directory service provisioned principals.
     """
     def setUp(self):
         super(ProvisionedCalendars, self).setUp()
-        
-        # Setup the initial directory
-        self.xmlFile = os.path.join(config.DataRoot, "accounts.xml")
-        fd = open(self.xmlFile, "w")
-        fd.write(open(xmlFile.path, "r").read())
-        fd.close()
-        self.directoryService = XMLDirectoryService({'xmlFile' : "accounts.xml"})
-        augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
-        
-        # Set up a principals hierarchy for each service we're testing with
-        name = "principals"
-        url = "/" + name + "/"
 
-        provisioningResource = DirectoryPrincipalProvisioningResource(url, self.directoryService)
-
-        self.site.resource.putChild("principals", provisioningResource)
-
+        self.createStockDirectoryService()
         self.setupCalendars()
 
-        self.site.resource.setAccessControlList(davxml.ACL())
 
-    def setupCalendars(self):
-        calendarCollection = CalendarHomeProvisioningFile(
-            os.path.join(self.docroot, "calendars"),
-            self.directoryService,
-            "/calendars/"
-        )
-        self.site.resource.putChild("calendars", calendarCollection)
-
     def test_NonExistentCalendarHome(self):
 
         def _response(resource):

Modified: CalendarServer/branches/new-store/twistedcaldav/directory/test/test_guidchange.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/directory/test/test_guidchange.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twistedcaldav/directory/test/test_guidchange.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -13,62 +13,34 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-from twistedcaldav.static import CalendarHomeProvisioningFile
 from twistedcaldav.directory.directory import DirectoryService
 
-import os
-
 from twext.web2.dav import davxml
 from twext.web2.dav.resource import AccessDeniedError
 from twext.web2.test.test_server import SimpleRequest
 
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
+from twistedcaldav.test.util import TestCase, xmlFile
 
-import twistedcaldav.test.util
-from twistedcaldav.directory import augment
 
-
-class ProvisionedPrincipals (twistedcaldav.test.util.TestCase):
+class ProvisionedPrincipals(TestCase):
     """
     Directory service provisioned principals.
     """
     def setUp(self):
         super(ProvisionedPrincipals, self).setUp()
-        
-        # Setup the initial directory
-        self.xmlFile = os.path.abspath(self.mktemp())
-        fd = open(self.xmlFile, "w")
-        fd.write(open(xmlFile.path, "r").read())
-        fd.close()
-        self.directoryService = XMLDirectoryService({'xmlFile' : self.xmlFile})
-        augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
-        
-        # Set up a principals hierarchy for each service we're testing with
-        name = "principals"
-        url = "/" + name + "/"
 
-        provisioningResource = DirectoryPrincipalProvisioningResource(url, self.directoryService)
-
-        self.site.resource.putChild("principals", provisioningResource)
-
+        # Setup the initial directory
+        self.createStockDirectoryService()
         self.setupCalendars()
 
         self.site.resource.setAccessControlList(davxml.ACL())
 
-    def setupCalendars(self):
-        calendarCollection = CalendarHomeProvisioningFile(
-            os.path.join(self.docroot, "calendars"),
-            self.directoryService,
-            "/calendars/"
-        )
-        self.site.resource.putChild("calendars", calendarCollection)
 
     def resetCalendars(self):
         del self.site.resource.putChildren["calendars"]
         self.setupCalendars()
 
+
     def test_guidchange(self):
         """
         DirectoryPrincipalResource.proxies()
@@ -80,10 +52,9 @@
         
         def privs1(result):
             # Change GUID in record
-            fd = open(self.xmlFile, "w")
-            fd.write(open(xmlFile.path, "r").read().replace(oldUID, newUID))
-            fd.close()
-            fd = None
+            self.xmlFile.setContent(
+                xmlFile.getContent().replace(oldUID, newUID)
+            )
 
             # Force re-read of records (not sure why _fileInfo has to be wiped here...)
             self.directoryService._fileInfo = (0, 0)

Modified: CalendarServer/branches/new-store/twistedcaldav/directory/test/test_proxyprincipalmembers.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/directory/test/test_proxyprincipalmembers.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twistedcaldav/directory/test/test_proxyprincipalmembers.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -19,8 +19,7 @@
 from twext.web2.dav import davxml
 
 from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile,\
-    proxiesFile
+from twistedcaldav.test.util import xmlFile, augmentsFile, proxiesFile
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
 from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser
 from twistedcaldav.directory.xmlfile import XMLDirectoryService
@@ -34,7 +33,7 @@
     """
     Directory service provisioned principals.
     """
-    
+
     @inlineCallbacks
     def setUp(self):
         super(ProxyPrincipals, self).setUp()

Modified: CalendarServer/branches/new-store/twistedcaldav/directory/test/test_xmlfile.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/directory/test/test_xmlfile.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twistedcaldav/directory/test/test_xmlfile.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -14,20 +14,14 @@
 # limitations under the License.
 ##
 
-import os
-
 from twext.python.filepath import CachingFilePath as FilePath
 
-from twistedcaldav.test.util import TestCase
 from twistedcaldav.directory import augment
 from twistedcaldav.directory.directory import DirectoryService
 import twistedcaldav.directory.test.util
 from twistedcaldav.directory.xmlfile import XMLDirectoryService
+from twistedcaldav.test.util import TestCase, xmlFile, augmentsFile
 
-xmlFile = FilePath(os.path.join(os.path.dirname(__file__), "accounts.xml"))
-augmentsFile = FilePath(os.path.join(os.path.dirname(__file__), "augments.xml"))
-proxiesFile = FilePath(os.path.join(os.path.dirname(__file__), "proxies.xml"))
-
 # FIXME: Add tests for GUID hooey, once we figure out what that means here
 
 class XMLFileBase(object):
@@ -104,7 +98,8 @@
     """
     def service(self):
         directory = XMLDirectoryService({'xmlFile' : self.xmlFile()}, alwaysStat=True)
-        augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(self.augmentsFile().path,))
+        self.patch(augment, "AugmentService",
+                   augment.AugmentXMLDB(xmlFiles=(self.augmentsFile().path,)))
         return directory
 
     def test_changedXML(self):
@@ -304,5 +299,10 @@
     ))
 
     def test_recordTypesSubset(self):
-        directory = XMLDirectoryService({'xmlFile' : self.xmlFile(), 'recordTypes' : (DirectoryService.recordType_users, DirectoryService.recordType_groups)}, alwaysStat=True)
+        directory = XMLDirectoryService(
+            {'xmlFile' : self.xmlFile(), 
+             'recordTypes' : (DirectoryService.recordType_users, 
+                              DirectoryService.recordType_groups)}, 
+            alwaysStat=True
+        )
         self.assertEquals(set(("users", "groups")), set(directory.recordTypes()))

Modified: CalendarServer/branches/new-store/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/resource.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twistedcaldav/resource.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -39,7 +39,7 @@
 from twext.web2.dav.http import ErrorResponse
 
 from twisted.internet import reactor
-from twisted.internet.defer import Deferred, succeed
+from twisted.internet.defer import Deferred, succeed, maybeDeferred
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twext.web2 import responsecode
 from twext.web2.dav import davxml
@@ -154,6 +154,54 @@
         return super(CalDAVResource, self).render(request)
 
 
+    _associatedTransaction = None
+
+    def associateWithTransaction(self, transaction):
+        """
+        Associate this resource with a L{txcaldav.idav.ITransaction}; when this
+        resource (or any of its children) are rendered successfully, commit the
+        transaction.  Otherwise, abort the transaction.
+
+        @param transaction: the transaction to associate this resource and its
+            children with.
+
+        @type transaction: L{txcaldav.idav.ITransaction} 
+        """
+        # FIXME: needs to reject association with transaction if it's already
+        # got one (resources associated with a transaction are not reusable)
+        self._associatedTransaction = transaction
+
+
+    def propagateTransaction(self, otherResource):
+        """
+        Propagate the transaction associated with this resource to another
+        resource (which should ostensibly be a child resource).
+
+        @param otherResource: Another L{CalDAVResource}, usually one being
+            constructed as a child of this one.
+
+        @type otherResource: L{CalDAVResource} (or a subclass thereof)
+        """
+        otherResource.associateWithTransaction(self._associatedTransaction)
+
+
+    def renderHTTP(self, request):
+        """
+        Override C{renderHTTP} to commit the transaction when the resource is
+        successfully rendered.
+
+        @param request: the request to generate a response for.
+        @type request: L{twext.web2.iweb.IRequest}
+        """
+        d = maybeDeferred(super(CalDAVResource, self).renderHTTP, request)
+        def succeeded(result):
+            if self._associatedTransaction is not None:
+                self._associatedTransaction.commit()
+            return result
+        # FIXME: needs a failure handler
+        return d.addCallback(succeeded)
+
+
     ##
     # WebDAV
     ##

Modified: CalendarServer/branches/new-store/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/static.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twistedcaldav/static.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -1,3 +1,4 @@
+# -*- test-case-name: twistedcaldav.test -*-
 ##
 # Copyright (c) 2005-2010 Apple Inc. All rights reserved.
 #
@@ -108,6 +109,9 @@
 from twistedcaldav.notifications import NotificationCollectionResource,\
     NotificationResource
 
+from txcaldav.calendarstore.file import CalendarStore
+from txdav.propertystore.base import PropertyName
+
 log = Logger()
 
 class ReadOnlyResourceMixIn(object):
@@ -124,6 +128,70 @@
             (caldav_namespace, "calendar-collection-location-ok")
         )
 
+
+class _NewStorePropertiesWrapper(object):
+    """
+    Wrap a new-style property store (a L{txdav.idav.IPropertyStore}) in the old-
+    style interface for compatibility with existing code.
+    """
+    
+    def __init__(self, newPropertyStore):
+        """
+        Initialize an old-style property store from a new one.
+
+        @param newPropertyStore: the new-style property store.
+        @type newPropertyStore: L{txdav.idav.IPropertyStore}
+        """
+        self._newPropertyStore = newPropertyStore
+
+    @classmethod
+    def _convertKey(cls, qname):
+        namespace, name = qname
+        return PropertyName(namespace, name)
+
+
+    # FIXME 'uid' here should be verifying something.
+    def get(self, qname, uid=None):
+        """
+        
+        """
+        try:
+            return self._newPropertyStore[self._convertKey(qname)]
+        except KeyError:
+            raise HTTPError(StatusResponse(
+                    responsecode.NOT_FOUND,
+                    "No such property: {%s}%s" % qname))
+
+
+    def set(self, property, uid=None):
+        """
+        
+        """
+        self._newPropertyStore[self._convertKey(property.qname())] = property
+
+
+    def delete(self, qname, uid=None):
+        """
+        
+        """
+        del self._newPropertyStore[self._convertKey(qname)]
+
+
+    def contains(self, qname, uid=None, cache=True):
+        """
+        
+        """
+        return (self._convertKey(qname) in self._newPropertyStore)
+
+
+    def list(self, uid=None, filterByUID=True, cache=True):
+        """
+        
+        """
+        return [(pname.namespace, pname.name) for pname in 
+                self._newPropertyStore.keys()]
+
+
 class CalDAVFile (LinkFollowerMixIn, CalDAVResource, DAVFile):
     """
     CalDAV-accessible L{DAVFile} resource.
@@ -186,12 +254,15 @@
 
     def deadProperties(self, caching=True):
         if not hasattr(self, "_dead_properties"):
+            # FIXME: this code should actually be dead, as the property store
+            # should be initialized as part of the traversal process.
+ 
             # Get the property store from super
             deadProperties = super(CalDAVFile, self).deadProperties()
 
             if caching:
                 # Wrap the property store in a memory store
-                deadProperties = CachingPropertyStore(deadProperties)
+                 deadProperties = CachingPropertyStore(deadProperties)
 
             self._dead_properties = deadProperties
 
@@ -202,11 +273,19 @@
     ##
 
     def createCalendar(self, request):
-        #
-        # request object is required because we need to validate against parent
-        # resources, and we need the request in order to locate the parents.
-        #
+        """
+        External API for creating a calendar.  Verify that the parent is a
+        collection, exists, is I{not} a calendar collection; that this resource
+        does not yet exist, then create it.
 
+        @param request: the request used to look up parent resources to
+            validate.
+
+        @type request: L{twext.web2.iweb.IRequest}
+
+        @return: a deferred that fires when a calendar collection has been
+            created in this resource.
+        """
         if self.fp.exists():
             log.err("Attempt to create collection where file exists: %s" % (self.fp.path,))
             raise HTTPError(StatusResponse(responsecode.NOT_ALLOWED, "File exists"))
@@ -234,29 +313,37 @@
         parent.addCallback(_defer)
         return parent
 
+
     def createCalendarCollection(self):
-        #
-        # Create the collection once we know it is safe to do so
-        #
-        def onCalendarCollection(status):
-            if status != responsecode.CREATED:
-                raise HTTPError(status)
+        """
+        Internal API for creating a calendar collection.
 
-            # Initialize CTag on the calendar collection
-            d1 = self.bumpSyncToken()
+        This will immediately create the collection without performing any
+        verification.  For the normal API, see L{CalDAVFile.createCalendar}.
 
-            # Calendar is initially transparent to freebusy
-            self.writeDeadProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Transparent()))
+        @return: a L{Deferred} which fires when the underlying collection has
+            actually been created.
+        """
+        # d = self.createSpecialCollection(davxml.ResourceType.calendar)
+        d = succeed(responsecode.CREATED)
+        calendarName = self.fp.basename()
+        self._newStoreParentHome.createCalendarWithName(calendarName)
+        self._newStoreCalendar = self._newStoreParentHome.calendarWithName(
+            calendarName
+        )
+        self._dead_properties = _NewStorePropertiesWrapper(
+            self._newStoreCalendar.properties()
+        )
+        return d
 
-            # Create the index so its ready when the first PUTs come in
-            d1.addCallback(lambda _: self.index().create())
-            d1.addCallback(lambda _: status)
-            return d1
 
-        d = self.createSpecialCollection(davxml.ResourceType.calendar)
-        d.addCallback(onCalendarCollection)
-        return d
+    def isCollection(self):
+        if getattr(self, "_newStoreCalendar", None) is not None:
+            # FIXME: this should really be represented by a separate class
+            return True
+        return super(CalDAVFile, self).isCollection()
 
+
     def createSpecialCollection(self, resourceType=None):
         #
         # Create the collection once we know it is safe to do so
@@ -620,10 +707,21 @@
         similar = super(CalDAVFile, self).createSimilarFile(path)
 
         if isCalendarCollectionResource(self):
+            similar._newStoreObject = \
+                self._newStoreCalendar.calendarObjectWithName(
+                    similar.fp.basename()
+                )
+            if similar._newStoreObject is not None:
+                # FIXME: what about creation in http_PUT?
+                similar._dead_properties = _NewStorePropertiesWrapper(
+                    similar._newStoreObject.properties()
+                )
+            # FIXME: tests should fail without this:
+            # self.propagateTransaction(similar)
 
             # Short-circuit stat with information we know to be true at this point
             if isinstance(path, FilePath) and hasattr(self, "knownChildren"):
-                if os.path.basename(path.path) in self.knownChildren:
+                if path.basename() in self.knownChildren:
                     path.existsCached = True
                     path.isDirCached = False
 
@@ -822,28 +920,45 @@
             super(AutoProvisioningFileMixIn, self)._initTypeAndEncoding()
 
 
-class CalendarHomeProvisioningFile (AutoProvisioningFileMixIn, DirectoryCalendarHomeProvisioningResource, DAVFile):
+class CalendarHomeProvisioningFile(AutoProvisioningFileMixIn, 
+                                   DirectoryCalendarHomeProvisioningResource,
+                                   DAVFile):
     """
     Resource which provisions calendar home collections as needed.
     """
+
     def __init__(self, path, directory, url):
         """
-        @param path: the path to the file which will back the resource.
+        Initialize this L{CalendarHomeProvisioningFile}.
+
+        @param path: the path to the filesystem directory which will back the
+            resource.
+
+        @type path: L{FilePath}
+
         @param directory: an L{IDirectoryService} to provision calendars from.
-        @param url: the canonical URL for the resource.
+
+        @param url: the canonical URL for this L{CalendarHomeProvisioningFile} 
+            resource.
         """
         DAVFile.__init__(self, path)
         DirectoryCalendarHomeProvisioningResource.__init__(self, directory, url)
+        storagePath = self.fp.child(uidsResourceName)
+        self._newStore = CalendarStore(storagePath)
 
+
     def provisionChild(self, name):
         if name == uidsResourceName:
             return CalendarHomeUIDProvisioningFile(self.fp.child(name).path, self)
 
         return CalendarHomeTypeProvisioningFile(self.fp.child(name).path, self, name)
 
+
     def createSimilarFile(self, path):
         raise HTTPError(responsecode.NOT_FOUND)
 
+
+
 class CalendarHomeTypeProvisioningFile (AutoProvisioningFileMixIn, DirectoryCalendarHomeTypeProvisioningResource, DAVFile):
     def __init__(self, path, parent, recordType):
         """
@@ -854,6 +969,8 @@
         DAVFile.__init__(self, path)
         DirectoryCalendarHomeTypeProvisioningResource.__init__(self, parent, recordType)
 
+
+
 class CalendarHomeUIDProvisioningFile (AutoProvisioningFileMixIn, DirectoryCalendarHomeUIDProvisioningResource, DAVFile):
     def __init__(self, path, parent, homeResourceClass=None):
         """
@@ -950,7 +1067,8 @@
     def url(self):
         return joinURL(self.parent.url(), self.record.uid)
 
-class CalendarHomeFile (AutoProvisioningFileMixIn, SharedHomeMixin, DirectoryCalendarHomeResource, CalDAVFile):
+class CalendarHomeFile(AutoProvisioningFileMixIn, SharedHomeMixin, 
+                       DirectoryCalendarHomeResource, CalDAVFile):
     """
     Calendar home collection resource.
     """
@@ -969,7 +1087,13 @@
         self.clientNotifier = ClientNotifier(self)
         CalDAVFile.__init__(self, path)
         DirectoryCalendarHomeResource.__init__(self, parent, record)
+        txn = self.parent.parent._newStore.newTransaction()
+        self._newStoreCalendarHome = (
+            txn.calendarHomeWithUID(self.record.uid, create=True)
+        )
+        self.associateWithTransaction(txn)
 
+
     def provision(self):
         result = super(CalendarHomeFile, self).provision()
         if config.Sharing.Enabled:
@@ -1010,8 +1134,21 @@
         if self.comparePath(path):
             return self
         else:
-            similar = CalDAVFile(path, principalCollections=self.principalCollections())
+            similar = CalDAVFile(
+                path, principalCollections=self.principalCollections()
+            )
             similar.clientNotifier = self.clientNotifier
+            similar._newStoreParentHome = self._newStoreCalendarHome
+            similar._newStoreCalendar = (
+                self._newStoreCalendarHome.calendarWithName(
+                    similar.fp.basename()
+                )
+            )
+            if similar._newStoreCalendar is not None:
+                similar._dead_properties = _NewStorePropertiesWrapper(
+                    similar._newStoreCalendar.properties()
+                )
+            self.propagateTransaction(similar)
             return similar
 
     def getChild(self, name):

Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_calendarquery.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_calendarquery.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_calendarquery.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -26,13 +26,13 @@
 from twext.web2.dav.util import davXMLFromStream
 from twext.web2.test.test_server import SimpleRequest
 
-import twistedcaldav.test.util
 from twistedcaldav import caldavxml
 from twistedcaldav import ical
 from twistedcaldav.index import db_basename
 from twistedcaldav.query import queryfilter
+from twistedcaldav.test.util import HomeTestCase
 
-class CalendarQuery (twistedcaldav.test.util.TestCase):
+class CalendarQuery (HomeTestCase):
     """
     calendar-query REPORT
     """

Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_collectioncontents.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_collectioncontents.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_collectioncontents.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -14,9 +14,8 @@
 # limitations under the License.
 ##
 
-import os
-
 from twisted.internet.defer import DeferredList
+from twext.python.filepath import CachingFilePath as FilePath
 from twext.web2 import responsecode
 from twext.web2.iweb import IResponse
 from twext.web2.stream import MemoryStream, FileStream
@@ -27,41 +26,48 @@
 from twistedcaldav.memcachelock import MemcacheLock
 from twistedcaldav.memcacher import Memcacher
 from twistedcaldav.method.put_common import StoreCalendarObjectResource
-import twistedcaldav.test.util
 
-class CollectionContents (twistedcaldav.test.util.TestCase):
+
+from twistedcaldav.test.util import HomeTestCase
+
+class CollectionContents(HomeTestCase):
     """
     PUT request
     """
-    data_dir = os.path.join(os.path.dirname(__file__), "data")
 
+    dataPath = FilePath(__file__).sibling("data")
+
     def setUp(self):
-        
         # Need to fake out memcache
         def _getFakeMemcacheProtocol(self):
-            
             result = super(MemcacheLock, self)._getMemcacheProtocol()
             if isinstance(result, Memcacher.nullCacher):
                 result = self._memcacheProtocol = Memcacher.memoryCacher()
-            
             return result
-        
-        MemcacheLock._getMemcacheProtocol = _getFakeMemcacheProtocol
 
+        self.patch(MemcacheLock, "_getMemcacheProtocol",
+                   _getFakeMemcacheProtocol)
+
         # Need to not do implicit behavior during these tests
         def _fakeDoImplicitScheduling(self):
             return False, False, False
-        
-        StoreCalendarObjectResource.doImplicitScheduling = _fakeDoImplicitScheduling
 
+        self.patch(StoreCalendarObjectResource , "doImplicitScheduling",
+                   _fakeDoImplicitScheduling)
+
+        # Tests in this suite assume that the root resource is a calendar home.
+        # FIXME: there should be a centralized way of saying 'make this look
+        # like a calendar home'
         super(CollectionContents, self).setUp()
 
+
     def test_collection_in_calendar(self):
         """
         Make (regular) collection in calendar
         """
         calendar_path, calendar_uri = self.mkdtemp("collection_in_calendar")
-        os.rmdir(calendar_path)
+        calPath = FilePath(calendar_path)
+        calPath.remove()
 
         def mkcalendar_cb(response):
             response = IResponse(response)
@@ -75,7 +81,7 @@
                 if response.code != responsecode.FORBIDDEN:
                     self.fail("Incorrect response to nested MKCOL: %s" % (response.code,))
 
-            nested_uri  = os.path.join(calendar_uri, "nested")
+            nested_uri = "/".join([calendar_uri, "nested"])
 
             request = SimpleRequest(self.site, "MKCOL", nested_uri)
             self.send(request, mkcol_cb)
@@ -95,27 +101,37 @@
         finally:
             dst_file.close()
 
+    def openHolidays(self):
+        """
+        Open the 'Holidays.ics' calendar.
+
+        @return: an open file pointing at the start of Holidays.ics
+
+        @rtype: C{file}
+        """
+        f = self.dataPath.child("Holidays.ics").open()
+        self.addCleanup(f.close)
+        return f
+
+
     def test_monolithic_ical(self):
         """
         Monolithic iCalendar file in calendar collection
         """
         # FIXME: Should FileStream be OK here?
-        dst_file = file(os.path.join(self.data_dir, "Holidays.ics"))
-        try:
-            stream = FileStream(dst_file)
-            return self._test_file_in_calendar("monolithic iCalendar file in calendar", (stream, responsecode.FORBIDDEN))
-        finally:
-            dst_file.close()
+        dst_file = self.openHolidays()
+        stream = FileStream(dst_file)
+        return self._test_file_in_calendar("monolithic iCalendar file in calendar", (stream, responsecode.FORBIDDEN))
 
+
     def test_single_events(self):
         """
         Single events in calendar collection
         """
         work = []
 
-        stream = file(os.path.join(self.data_dir, "Holidays.ics"))
-        try: calendar = Component.fromStream(stream)
-        finally: stream.close()
+        stream = self.openHolidays()
+        calendar = Component.fromStream(stream)
 
         for subcomponent in calendar.subcomponents():
             if subcomponent.name() == "VEVENT":
@@ -126,20 +142,24 @@
 
         return self._test_file_in_calendar("single event in calendar", *work)
 
+
     def test_duplicate_uids(self):
         """
         Mutiple resources with the same UID.
         """
-        stream = file(os.path.join(self.data_dir, "Holidays", "C318AA54-1ED0-11D9-A5E0-000A958A3252.ics"))
+        stream = self.dataPath.child(
+            "Holidays").child(
+            "C318AA54-1ED0-11D9-A5E0-000A958A3252.ics").open()
         try: calendar = str(Component.fromStream(stream))
         finally: stream.close()
 
         return self._test_file_in_calendar(
             "mutiple resources with the same UID",
-            (MemoryStream(calendar), responsecode.CREATED  ),
+            (MemoryStream(calendar), responsecode.CREATED),
             (MemoryStream(calendar), responsecode.FORBIDDEN),
         )
 
+
     def _test_file_in_calendar(self, what, *work):
         """
         Creates a calendar collection, then PUTs a resource into that collection
@@ -147,7 +167,8 @@
         PUT request matches the given response_code.
         """
         calendar_path, calendar_uri = self.mkdtemp("calendar")
-        os.rmdir(calendar_path)
+        calPath = FilePath(calendar_path)
+        calPath.remove()
 
         def mkcalendar_cb(response):
             response = IResponse(response)
@@ -155,7 +176,7 @@
             if response.code != responsecode.CREATED:
                 self.fail("MKCALENDAR failed: %s" % (response.code,))
 
-            if not os.path.isdir(calendar_path):
+            if not calPath.isdir():
                 self.fail("MKCALENDAR did not create a collection")
 
             ds = []
@@ -164,12 +185,11 @@
             for stream, response_code in work:
                 def put_cb(response, stream=stream, response_code=response_code):
                     response = IResponse(response)
-    
+
                     if response.code != response_code:
                         self.fail("Incorrect response to %s: %s (!= %s)" % (what, response.code, response_code))
 
-                dst_uri  = os.path.join(calendar_uri, "dst%d.ics" % (c,))
-    
+                dst_uri = "/".join([calendar_uri, "dst%d.ics" % (c,)])
                 request = SimpleRequest(self.site, "PUT", dst_uri)
                 request.headers.setHeader("if-none-match", "*")
                 request.headers.setHeader("content-type", MimeType("text", "calendar"))
@@ -187,11 +207,9 @@
         """
         Make sure database files are not listed as children.
         """
-        colpath = self.site.resource.fp.path
-        fd = open(os.path.join(colpath, "._bogus"), "w")
-        fd.close()
-        fd = open(os.path.join(colpath, "bogus"), "w")
-        fd.close()
+        colpath = self.site.resource.fp
+        colpath.child("._bogus").touch()
+        colpath.child("bogus").touch()
         children = self.site.resource.listChildren()
         self.assertTrue("bogus" in children)
         self.assertFalse("._bogus" in children)
@@ -201,7 +219,8 @@
         Make (regular) collection in calendar
         """
         calendar_path, calendar_uri = self.mkdtemp("dot_file_in_calendar")
-        os.rmdir(calendar_path)
+        calPath = FilePath(calendar_path)
+        calPath.remove()
 
         def mkcalendar_cb(response):
             response = IResponse(response)
@@ -215,11 +234,14 @@
                 if response.code != responsecode.FORBIDDEN:
                     self.fail("Incorrect response to dot file PUT: %s" % (response.code,))
 
-            stream = file(os.path.join(self.data_dir, "Holidays", "C318AA54-1ED0-11D9-A5E0-000A958A3252.ics"))
+            stream = self.dataPath.child(
+                "Holidays").child(
+                "C318AA54-1ED0-11D9-A5E0-000A958A3252.ics"
+            ).open()
             try: calendar = str(Component.fromStream(stream))
             finally: stream.close()
 
-            event_uri  = os.path.join(calendar_uri, ".event.ics")
+            event_uri = "/".join([calendar_uri, ".event.ics"])
 
             request = SimpleRequest(self.site, "PUT", event_uri)
             request.headers.setHeader("content-type", MimeType("text", "calendar"))

Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_index.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_index.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_index.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -28,12 +28,35 @@
 from twistedcaldav.query import queryfilter
 from twistedcaldav.test.util import InMemoryMemcacheProtocol
 import twistedcaldav.test.util
-from vobject.icalendar import utc
-import sqlite3
 
 import datetime
 import os
 
+
+class MinimalResourceReplacement(object):
+    """
+    Provide the minimal set of attributes and methods from CalDAVFile required
+    by L{Index}.
+    """
+
+    def __init__(self, filePath):
+        self.fp = filePath
+
+
+    def isCalendarCollection(self):
+        return True
+
+
+    def getChild(self, name):
+        # FIXME: this should really return something with a child method
+        return self.fp.child(name)
+
+
+    def initSyncToken(self):
+        pass
+
+
+
 class SQLIndexTests (twistedcaldav.test.util.TestCase):
     """
     Test abstract SQL DB class
@@ -42,7 +65,10 @@
     def setUp(self):
         super(SQLIndexTests, self).setUp()
         self.site.resource.isCalendarCollection = lambda: True
-        self.db = Index(self.site.resource)
+        self.indexDirPath = self.site.resource.fp
+        # FIXME: since this resource lies about isCalendarCollection, it doesn't
+        # have all the associated backend machinery to actually get children.
+        self.db = Index(MinimalResourceReplacement(self.indexDirPath))
 
 
     def test_reserve_uid_ok(self):
@@ -235,7 +261,7 @@
             revision += 1
             calendar = Component.fromString(calendar_txt)
             if ok:
-                f = open(os.path.join(self.site.resource.fp.path, name), "w")
+                f = open(os.path.join(self.indexDirPath.path, name), "w")
                 f.write(calendar_txt)
                 del f
 
@@ -409,7 +435,7 @@
             revision += 1
             calendar = Component.fromString(calendar_txt)
 
-            f = open(os.path.join(self.site.resource.fp.path, name), "w")
+            f = open(os.path.join(self.indexDirPath.path, name), "w")
             f.write(calendar_txt)
             del f
 
@@ -808,7 +834,7 @@
             revision += 1
             calendar = Component.fromString(calendar_txt)
 
-            f = open(os.path.join(self.site.resource.fp.path, name), "w")
+            f = open(os.path.join(self.indexDirPath.path, name), "w")
             f.write(calendar_txt)
             del f
 

Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_mkcalendar.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_mkcalendar.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_mkcalendar.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -25,11 +25,11 @@
 from twext.web2.dav.fileop import rmdir
 from twext.web2.test.test_server import SimpleRequest
 
-import twistedcaldav.test.util
 from twistedcaldav import caldavxml
 from twistedcaldav.static import CalDAVFile
+from twistedcaldav.test.util import HomeTestCase
 
-class MKCALENDAR (twistedcaldav.test.util.TestCase):
+class MKCALENDAR (HomeTestCase):
     """
     MKCALENDAR request
     """

Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_multiget.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_multiget.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_multiget.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -26,12 +26,12 @@
 from twext.web2.dav.util import davXMLFromStream
 from twext.web2.test.test_server import SimpleRequest
 
-import twistedcaldav.test.util
 from twistedcaldav import caldavxml
 from twistedcaldav import ical
 from twistedcaldav.index import db_basename
+from twistedcaldav.test.util import HomeTestCase
 
-class CalendarMultiget (twistedcaldav.test.util.TestCase):
+class CalendarMultiget (HomeTestCase):
     """
     calendar-multiget REPORT
     """

Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_props.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_props.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_props.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -25,9 +25,9 @@
 from twext.web2.test.test_server import SimpleRequest
 from twistedcaldav import caldavxml
 
-import twistedcaldav.test.util
+from twistedcaldav.test.util import HomeTestCase
 
-class Properties (twistedcaldav.test.util.TestCase):
+class Properties(HomeTestCase):
     """
     CalDAV properties
     """

Copied: CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py (from rev 5593, CalendarServer/branches/users/wsanchez/transations/twistedcaldav/test/test_wrapping.py)
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py	                        (rev 0)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -0,0 +1,172 @@
+##
+# Copyright (c) 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.
+##
+"""
+Tests for the interaction between model-level and protocol-level logic.
+"""
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+from twistedcaldav.directory.calendar import uidsResourceName
+from twistedcaldav.ical import Component as VComponent
+
+from twistedcaldav.test.util import TestCase
+
+from txcaldav.calendarstore.file import CalendarStore, CalendarHome
+from txcaldav.calendarstore.test.test_file import event4_text
+
+
+
+class WrappingTests(TestCase):
+    """
+    Tests for L{twistedcaldav.static.CalDAVFile} creating the appropriate type
+    of txcaldav.calendarstore.file underlying object when it can determine what
+    type it really represents.
+    """
+
+    def setUp(self):
+        # Mostly copied from
+        # twistedcaldav.directory.test.test_calendar.ProvisionedCalendars; this
+        # should probably be refactored, perhaps even to call (some part of) the
+        # actual root-resource construction logic?
+        super(WrappingTests, self).setUp()
+
+        # Setup the initial directory
+        self.createStockDirectoryService()
+        self.setupCalendars()
+
+
+    def populateOneObject(self, objectName, objectText):
+        """
+        Populate one calendar object in the test user's calendar.
+
+        @param objectName: The name of a calendar object.
+        @type objectName: str
+        @param objectText: Some iCalendar text to populate it with.
+        @type objectText: str 
+        """
+        record = self.directoryService.recordWithShortName("users", "wsanchez")
+        uid = record.uid
+        # XXX there should be a more test-friendly way to ensure the directory
+        # actually exists
+        try:
+            self.calendarCollection._newStore._path.createDirectory()
+        except:
+            pass
+        txn = self.calendarCollection._newStore.newTransaction()
+        home = txn.calendarHomeWithUID(uid, True)
+        cal = home.calendarWithName("calendar")
+        if cal is None:
+            home.createCalendarWithName("calendar")
+            cal = home.calendarWithName("calendar")
+        cal.createCalendarObjectWithName(objectName, VComponent.fromString(objectText))
+        txn.commit()
+
+
+    @inlineCallbacks
+    def getResource(self, path):
+        """
+        Retrieve a resource from the site.
+
+        @param path: the path from the root of the site (not starting with a
+            slash)
+
+        @type path: C{str}
+        """
+        segments = path.split("/")
+        resource = self.site.resource
+        while segments:
+            resource, segments = yield resource.locateChild(None, segments)
+        returnValue(resource)
+
+
+    def test_createStore(self):
+        """
+        Creating a CalendarHomeProvisioningFile will create a paired
+        CalendarStore.
+        """
+        self.assertIsInstance(self.calendarCollection._newStore, CalendarStore)
+        self.assertEquals(self.calendarCollection._newStore._path,
+                          self.calendarCollection.fp.child(uidsResourceName))
+
+
+    @inlineCallbacks
+    def test_lookupCalendarHome(self):
+        """
+        When a L{CalDAVFile} representing an existing calendar home is looked up
+        in a CalendarHomeFile, it will create a corresponding L{CalendarHome}
+        via C{newTransaction().calendarHomeWithUID}.
+        """
+        calDavFile = yield self.getResource("calendars/users/wsanchez/")
+        self.assertEquals(calDavFile.fp, calDavFile._newStoreCalendarHome._path)
+        self.assertIsInstance(calDavFile._newStoreCalendarHome, CalendarHome)
+
+
+    @inlineCallbacks
+    def test_lookupExistingCalendar(self):
+        """
+        When a L{CalDAVFile} representing an existing calendar collection is
+        looked up in a L{CalendarHomeFile} representing a calendar home, it will
+        create a corresponding L{Calendar} via C{CalendarHome.calendarWithName}.
+        """
+        calDavFile = yield self.getResource("calendars/users/wsanchez/calendar")
+        self.assertEquals(calDavFile.fp, calDavFile._newStoreCalendar._path)
+
+
+    @inlineCallbacks
+    def test_lookupNewCalendar(self):
+        """
+        When a L{CalDAVFile} which represents a not-yet-created calendar
+        collection is looked up in a L{CalendarHomeFile} representing a calendar
+        home, it will initially have a new storage backend set to C{None}, but
+        when the calendar is created via a protocol action, the backend will be
+        initialized to match.
+        """
+        calDavFile = yield self.getResource("calendars/users/wsanchez/frobozz")
+        self.assertIdentical(calDavFile._newStoreCalendar, None)
+        calDavFile.createCalendarCollection()
+        self.assertEquals(calDavFile.fp, calDavFile._newStoreCalendar._path)
+
+
+    @inlineCallbacks
+    def test_lookupSpecial(self):
+        """
+        When a L{CalDAVFile} I{not} representing a calendar collection - one of
+        the special collections, like the dropbox or freebusy URLs - is looked
+        up in a L{CalendarHomeFile} representing a calendar home, it will I{not}
+        create a corresponding L{Calendar} via C{CalendarHome.calendarWithName}.
+        """
+        for specialName in ['dropbox', 'freebusy', 'notifications']:
+            calDavFile = yield self.getResource(
+                "calendars/users/wsanchez/%s" % (specialName,)
+            )
+            self.assertIdentical(
+                getattr(calDavFile, "_newStoreCalendar", None), None
+            )
+
+
+    @inlineCallbacks
+    def test_lookupCalendarObject(self):
+        """
+        When a L{CalDAVFile} representing an existing calendar object is looked
+        up on a L{CalDAVFile} representing a calendar collection, a parallel
+        L{CalendarObject} will be created (with a matching FilePath).
+        """
+        self.populateOneObject("1.ics", event4_text)
+        calDavFileCalendar = yield self.getResource(
+            "calendars/users/wsanchez/calendar/1.ics"
+        )
+        self.assertEquals(calDavFileCalendar._newStoreObject._path, 
+                          calDavFileCalendar.fp)

Modified: CalendarServer/branches/new-store/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/util.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/twistedcaldav/test/util.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -15,6 +15,7 @@
 ##
 
 from __future__ import with_statement
+from twext.web2.dav import davxml
 
 __all__ = [
     "featureUnimplemented",
@@ -33,12 +34,17 @@
 from twisted.internet.protocol import ProcessProtocol
 
 from twext.python.memcacheclient import ClientFactory
+from twext.python.filepath import CachingFilePath as FilePath
 import twext.web2.dav.test.util
 from twext.web2.http import HTTPError, StatusResponse
 
 from twistedcaldav import memcacher
 from twistedcaldav.config import config
-from twistedcaldav.static import CalDAVFile
+from twistedcaldav.static import CalDAVFile, CalendarHomeProvisioningFile
+from twistedcaldav.directory.xmlfile import XMLDirectoryService
+from twistedcaldav.directory import augment
+from twistedcaldav.directory.principal import (
+    DirectoryPrincipalProvisioningResource)
 
 DelayedCall.debug = True
 
@@ -50,9 +56,56 @@
 testUnimplemented = lambda f: _todo(f, "Test unimplemented")
 todo = lambda why: lambda f: _todo(f, why)
 
+dirTest = FilePath(__file__).parent().sibling("directory").child("test")
+
+xmlFile = dirTest.child("accounts.xml")
+augmentsFile = dirTest.child("augments.xml")
+proxiesFile = dirTest.child("proxies.xml")
+
 class TestCase(twext.web2.dav.test.util.TestCase):
     resource_class = CalDAVFile
 
+    def createStockDirectoryService(self):
+        """
+        Create a stock C{directoryService} attribute and assign it.
+        """
+        self.xmlFile = FilePath(config.DataRoot).child("accounts.xml")
+        self.xmlFile.setContent(xmlFile.getContent())
+
+        # *temporarily* set up an augment service so this directory service will
+        # work.
+        self.patch(augment, "AugmentService", augment.AugmentXMLDB(
+                xmlFiles=(augmentsFile.path,)
+            )
+        )
+
+        self.directoryService = XMLDirectoryService(
+            {'xmlFile' : "accounts.xml"}
+        )
+
+        # FIXME: see FIXME in DirectoryPrincipalProvisioningResource.__init__;
+        # this performs a necessary modification to the directory service
+        # object for it to be fully functional.
+        self.principalsResource = DirectoryPrincipalProvisioningResource(
+            "/principals/", self.directoryService
+        )
+
+
+    def setupCalendars(self):
+        """
+        Set up the resource at /calendars (a L{CalendarHomeProvisioningFile}),
+        and assign it as C{self.calendarCollection}.
+        """
+        path = self.site.resource.fp.child("calendars")
+        path.createDirectory()
+        self.calendarCollection = CalendarHomeProvisioningFile(
+            path,
+            self.directoryService,
+            "/calendars/"
+        )
+        self.site.resource.putChild("calendars", self.calendarCollection)
+
+
     def setUp(self):
         super(TestCase, self).setUp()
 
@@ -210,6 +263,65 @@
         return verifyChildren(root, structure)
 
 
+
+class HomeTestCase(TestCase):
+    """
+    Utility class for tests which wish to interact with a calendar home rather
+    than a top-level resource hierarchy.
+    """
+
+    def setUp(self):
+        """
+        Replace self.site.resource with an appropriately provisioned
+        CalendarHomeFile, and replace self.docroot with a path pointing at that
+        file.
+        """
+        super(HomeTestCase, self).setUp()
+
+        fp = FilePath(self.mktemp())
+
+        self.createStockDirectoryService()
+
+        self.homeProvisioner = CalendarHomeProvisioningFile(
+            fp, self.directoryService, "/"
+        )
+        self._refreshRoot()
+
+
+    def _refreshRoot(self):
+        """
+        Refresh the user resource positioned at the root of this site, to give
+        it a new transaction.
+        """
+        users = self.homeProvisioner.getChild("users")
+        user = users.getChild("wsanchez")
+
+        # Force the request to succeed regardless of the implementation of
+        # accessControlList.
+        user.accessControlList = lambda request, *a, **k: succeed(
+            self.grantInherit(davxml.All())
+        )
+
+        # Fix the site to point directly at the user's calendar home so that we
+        # can focus on testing just that rather than hierarchy traversal..
+        self.site.resource = user
+
+        # Fix the docroot so that 'mkdtemp' will create directories in the right
+        # place (beneath the calendar).
+        self.docroot = user.fp.path
+
+
+    def send(self, request, callback):
+        """
+        Override C{send} in order to refresh the 'user' resource each time, to
+        get a new transaction to associate with the calendar home.
+        """
+        self._refreshRoot()
+        return super(HomeTestCase, self).send(request, callback)
+
+
+
+
 class InMemoryPropertyStore(object):
     def __init__(self):
         class _FauxPath(object):

Modified: CalendarServer/branches/new-store/txcaldav/calendarstore/file.py
===================================================================
--- CalendarServer/branches/new-store/txcaldav/calendarstore/file.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/txcaldav/calendarstore/file.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -1,3 +1,4 @@
+# -*- test-case-name: txcaldav.calendarstore.test.test_file -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #
@@ -13,6 +14,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
+from twext.web2.dav.element.rfc2518 import ResourceType
+from txdav.propertystore.base import PropertyName
+from twistedcaldav.caldavxml import ScheduleCalendarTransp, Transparent
 
 """
 File calendar store.
@@ -29,7 +33,6 @@
 
 from zope.interface import implements
 
-from twext.python.filepath import CachingFilePath as FilePath
 from twisted.internet.defer import inlineCallbacks
 
 from twext.python.log import LoggingMixIn
@@ -38,6 +41,7 @@
 
 from txdav.propertystore.xattr import PropertyStore
 
+from txcaldav.icalendarstore import ICalendarStoreTransaction
 from txcaldav.icalendarstore import ICalendarStore, ICalendarHome
 from txcaldav.icalendarstore import ICalendar, ICalendarObject
 from txcaldav.icalendarstore import CalendarNameNotAllowedError
@@ -53,71 +57,217 @@
 from twistedcaldav.index import Index as OldIndex
 from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
 
+def _isValidName(name):
+    """
+    Determine if the given string is a valid name.  i.e. does it conflict with
+    any of the other entities which may be on the filesystem?
 
+    @param name: a name which might be given to a calendar.
+    """
+    return not name.startswith(".")
+
+
+_unset = object()
+
+class _cached(object):
+    """
+    This object is a decorator for a 0-argument method which should be called
+    only once, and its result cached so that future invocations just return the
+    same result without calling the underlying method again.
+
+    @ivar thunk: the function to call to generate a cached value.
+    """
+
+    def __init__(self, thunk):
+        self.thunk = thunk
+
+
+    def __get__(self, oself, owner):
+        def inner():
+            cacheKey = "_" + self.thunk.__name__ + "_cached"
+            cached = getattr(oself, cacheKey, _unset)
+            if cached is _unset:
+                value = self.thunk(oself)
+                setattr(oself, cacheKey, value)
+                return value
+            else:
+                return cached
+        return inner
+
+
+
 class CalendarStore(LoggingMixIn):
+    """
+    An implementation of L{ICalendarObject} backed by a
+    L{twext.python.filepath.CachingFilePath}.
+
+    @ivar _path: A L{CachingFilePath} referencing a directory on disk that
+        stores all calendar data for a group of uids.
+    """
     implements(ICalendarStore)
 
-    calendarHomeClass = property(lambda _: CalendarHome)
-
     def __init__(self, path):
         """
-        @param path: a L{FilePath}
+        Create a calendar store.
+
+        @param path: a L{FilePath} pointing at a directory on disk.
         """
-        assert isinstance(path, FilePath)
+        self._path = path
 
-        self.path = path
-
-        if not path.isdir():
+#        if not path.isdir():
             # FIXME: Add CalendarStoreNotFoundError?
-            raise NotFoundError("No such calendar store")
+#            raise NotFoundError("No such calendar store")
 
     def __repr__(self):
-        return "<%s: %s>" % (self.__class__.__name__, self.path.path)
+        return "<%s: %s>" % (self.__class__.__name__, self._path.path)
 
+    def newTransaction(self):
+        """
+        Create a new filesystem-based transaction.
+
+        @see Transaction
+        """
+        return Transaction(self)
+
+
+
+class Transaction(LoggingMixIn):
+    """
+    In-memory implementation of
+
+    Note that this provides basic 'undo' support, but not truly transactional
+    operations.
+    """
+
+    implements(ICalendarStoreTransaction)
+
+    def __init__(self, calendarStore):
+        """
+        Initialize a transaction; do not call this directly, instead call
+        L{CalendarStore.newTransaction}.
+
+        @param calendarStore: The store that created this transaction.
+
+        @type calendarStore: L{CalendarStore}
+        """
+        self.calendarStore = calendarStore
+        self.aborted = False
+        self.committed = False
+        self._operations = []
+        self._calendarHomes = {}
+
+
+    def addOperation(self, operation):
+        self._operations.append(operation)
+
+    def abort(self):
+        if self.aborted:
+            raise RuntimeError("already aborted")
+        if self.committed:
+            raise RuntimeError("already committed")
+        self.aborted = True
+
+    def commit(self):
+        if self.aborted:
+            raise RuntimeError("already aborted")
+        if self.committed:
+            raise RuntimeError("already committed")
+
+        self.committed = True
+        undos = []
+
+        for operation in self._operations:
+            try:
+                undo = operation()
+                if undo is not None:
+                    undos.append(undo)
+            except Exception, e:
+                for undo in undos:
+                    try:
+                        undo()
+                    except Exception, e:
+                        self.log_error("Exception while undoing transaction: %s" % (e,))
+                raise
+
     def calendarHomeWithUID(self, uid, create=False):
+        if (uid, self) in self._calendarHomes:
+            return self._calendarHomes[(uid, self)]
+
         if uid.startswith("."):
             return None
 
         assert len(uid) >= 4
 
-        childPath = self.path.child(uid[0:2]).child(uid[2:4]).child(uid)
+        childPath1 = self.calendarStore._path.child(uid[0:2])
+        childPath2 = childPath1.child(uid[2:4])
+        childPath3 = childPath2.child(uid)
 
-        if not childPath.isdir():
-            if create:
-                childPath.makedirs()
-            else:
-                return None
+        def do():
+            def createDirectory(path):
+                try:
+                    path.createDirectory()
+                except (IOError, OSError), e:
+                    if e.errno != errno.EEXIST:
+                        # Ignore, in case someone else created the
+                        # directory while we were trying to as well.
+                        raise
 
-        return CalendarHome(childPath, self)
+            if not childPath3.isdir():
+                if not childPath2.isdir():
+                    if not childPath1.isdir():
+                        createDirectory(childPath1)
+                    createDirectory(childPath2)
+                createDirectory(childPath3)
 
+        if create:
+            self.addOperation(do)
+        elif not childPath3.isdir():
+            return None
 
+        calendarHome = CalendarHome(childPath3, self.calendarStore, self)
+        self._calendarHomes[(uid, self)] = calendarHome
+        return calendarHome
+
+
+
 class CalendarHome(LoggingMixIn):
     implements(ICalendarHome)
 
-    calendarClass = property(lambda _: Calendar)
-
-    def __init__(self, path, calendarStore):
-        self.path = path
+    def __init__(self, path, calendarStore, transaction):
+        self._path = path
         self.calendarStore = calendarStore
+        self._transaction = transaction
+        self._newCalendars = {}
+        self._removedCalendars = set()
 
     def __repr__(self):
-        return "<%s: %s>" % (self.__class__.__name__, self.path)
+        return "<%s: %s>" % (self.__class__.__name__, self._path)
 
     def uid(self):
-        return self.path.basename()
+        return self._path.basename()
+    
+    
+    def _updateSyncToken(self, reset=False):
+        "Stub for updating sync token."
 
     def calendars(self):
-        return (
+        return set(self._newCalendars.itervalues()) | set(
             self.calendarWithName(name)
-            for name in self.path.listdir()
+            for name in self._path.listdir()
             if not name.startswith(".")
         )
 
     def calendarWithName(self, name):
+        calendar = self._newCalendars.get(name)
+        if calendar is not None:
+            return calendar
+        if name in self._removedCalendars:
+            return None
+
         if name.startswith("."):
             return None
 
-        childPath = self.path.child(name)
+        childPath = self._path.child(name)
         if childPath.isdir():
             return Calendar(childPath, self)
         else:
@@ -127,101 +277,189 @@
         if name.startswith("."):
             raise CalendarNameNotAllowedError(name)
 
-        childPath = self.path.child(name)
+        childPath = self._path.child(name)
 
-        try:
-            childPath.createDirectory()
-        except (IOError, OSError), e:
-            if e.errno == errno.EEXIST:
-                raise CalendarAlreadyExistsError(name)
-            raise
+        if name not in self._removedCalendars and childPath.isdir():
+            raise CalendarAlreadyExistsError(name)
 
+        c = self._newCalendars[name] = Calendar(childPath, self)
+        def do():
+            try:
+                childPath.createDirectory()
+                # FIXME: direct tests, undo for index creation
+                Index(c)._oldIndex.create()
+
+                # Return undo
+                return lambda: childPath.remove()
+            except (IOError, OSError), e:
+                if e.errno == errno.EEXIST and childPath.isdir():
+                    raise CalendarAlreadyExistsError(name)
+                raise
+
+        self._transaction.addOperation(do)
+        props = c.properties()
+        PN = PropertyName.fromString
+        CalendarType = ResourceType.calendar #@UndefinedVariable
+        props[PN(ResourceType.sname())] = CalendarType
+
+        # Calendars are initially transparent to freebusy.  FIXME: freebusy
+        # needs more structured support than this.
+        props[PN(ScheduleCalendarTransp.sname())] = Transparent()
+        # FIXME: there's no need for 'flush' to be a public method of the
+        # property store any more.  It should just be transactional like
+        # everything else; the API here would better be expressed as
+        # c.properties().participateInTxn(txn)
+        self._transaction.addOperation(c.properties().flush)
+        # FIXME: return c # maybe ?
+
     def removeCalendarWithName(self, name):
-        if name.startswith("."):
+        if name.startswith(".") or name in self._removedCalendars:
             raise NoSuchCalendarError(name)
 
-        childPath = self.path.child(name)
-        try:
-            childPath.remove()
-        except (IOError, OSError), e:
-            if e.errno == errno.ENOENT:
-                raise NoSuchCalendarError(name)
-            raise
+        self._removedCalendars.add(name)
+        childPath = self._path.child(name)
+        if name not in self._newCalendars and not childPath.isdir():
+            raise NoSuchCalendarError(name)
 
+        def do(transaction=self._transaction):
+            for i in xrange(1000):
+                trash = childPath.sibling("._del_%s_%d" % (childPath.basename(), i))
+                if not trash.exists():
+                    break
+            else:
+                raise InternalDataStoreError("Unable to create trash target for calendar at %s" % (childPath,))
+
+            try:
+                childPath.moveTo(trash)
+            except (IOError, OSError), e:
+                if e.errno == errno.ENOENT:
+                    raise NoSuchCalendarError(name)
+                raise
+
+            def cleanup():
+                try:
+                    trash.remove()
+                except Exception, e:
+                    self.log_error("Unable to delete trashed calendar at %s: %s" % (trash.fp, e))
+
+            transaction.addOperation(cleanup)
+
+            def undo():
+                trash.moveTo(childPath)
+
+            return undo
+
+
     def properties(self):
-        if not hasattr(self, "_properties"):
-            self._properties = PropertyStore(self.path)
-        return self._properties
+        # FIXME: needs tests for actual functionality
+        # FIXME: needs to be cached
+        return PropertyStore(self._path)
 
 
+
 class Calendar(LoggingMixIn):
+    """
+    File-based implementation of L{ICalendar}.
+    """
     implements(ICalendar)
 
-    calendarObjectClass = property(lambda _: CalendarObject)
-
     def __init__(self, path, calendarHome):
-        self.path = path
+        self._path = path
         self.calendarHome = calendarHome
+        self._transaction = calendarHome._transaction
+        self._newCalendarObjects = {}
+        self._cachedCalendarObjects = {}
+        self._removedCalendarObjects = set()
 
+
     def __repr__(self):
-        return "<%s: %s>" % (self.__class__.__name__, self.path.path)
+        return "<%s: %s>" % (self.__class__.__name__, self._path.path)
 
-    def index(self):
-        if not hasattr(self, "_index"):
-            self._index = Index(self)
-        return self._index
 
     def name(self):
-        return self.path.basename()
+        return self._path.basename()
 
+
     def ownerCalendarHome(self):
         return self.calendarHome
 
-    def _calendarObjects_index(self):
-        return self.index().calendarObjects()
 
-    def _calendarObjects_listdir(self):
+#    def _calendarObjects_index(self):
+#        return self.index().calendarObjects()
+#
+#    def _calendarObjects_listdir(self):
+#        return (
+#            self.calendarObjectWithName(name)
+#            for name in self.path.listdir()
+#            if not name.startswith(".")
+#        )
+
+    def calendarObjects(self):
         return (
             self.calendarObjectWithName(name)
-            for name in self.path.listdir()
-            if not name.startswith(".")
+            for name in (
+                set(self._newCalendarObjects.iterkeys()) |
+                set(name for name in self._path.listdir() if not name.startswith("."))
+            )
         )
 
-    calendarObjects = _calendarObjects_index
 
     def calendarObjectWithName(self, name):
-        childPath = self.path.child(name)
-        if childPath.isfile():
-            return CalendarObject(childPath, self)
+        if name in self._removedCalendarObjects:
+            return None
+        if name in self._newCalendarObjects:
+            return self._newCalendarObjects[name]
+        if name in self._cachedCalendarObjects:
+            return self._cachedCalendarObjects[name]
+
+
+        calendarObjectPath = self._path.child(name)
+        if calendarObjectPath.isfile():
+            obj = CalendarObject(calendarObjectPath, self)
+            self._cachedCalendarObjects[name] = obj
+            return obj
         else:
             return None
 
+
     def calendarObjectWithUID(self, uid):
-        raise NotImplementedError()
+        # FIXME: This _really_ needs to be inspecting an index, not parsing
+        # every resource.
+        for calendarObjectPath in self._path.children():
+            if not _isValidName(calendarObjectPath.basename()):
+                continue
+            obj = CalendarObject(calendarObjectPath, self)
+            if obj.component().resourceUID() == uid:
+                return obj
 
+
     def createCalendarObjectWithName(self, name, component):
         if name.startswith("."):
             raise CalendarObjectNameNotAllowedError(name)
 
-        childPath = self.path.child(name)
-        if childPath.exists():
+        calendarObjectPath = self._path.child(name)
+        if calendarObjectPath.exists():
             raise CalendarObjectNameAlreadyExistsError(name)
 
-        calendarObject = CalendarObject(childPath, self)
+        calendarObject = CalendarObject(calendarObjectPath, self)
         calendarObject.setComponent(component)
+        self._cachedCalendarObjects[name] = calendarObject
 
+
     def removeCalendarObjectWithName(self, name):
         if name.startswith("."):
             raise NoSuchCalendarObjectError(name)
 
-        childPath = self.path.child(name)
-        if childPath.isfile():
-            childPath.remove()
+        calendarObjectPath = self._path.child(name)
+        if calendarObjectPath.isfile():
+            self._removedCalendarObjects.add(name)
+            # FIXME: test for undo
+            calendarObjectPath.remove()
         else:
             raise NoSuchCalendarObjectError(name)
 
     def removeCalendarObjectWithUID(self, uid):
-        raise NotImplementedError()
+        self.removeCalendarObjectWithName(self.calendarObjectWithUID(uid)._path.basename())
 
     def syncToken(self):
         raise NotImplementedError()
@@ -257,28 +495,40 @@
     def calendarObjectsSinceToken(self, token):
         raise NotImplementedError()
 
+
+    @_cached
     def properties(self):
-        if not hasattr(self, "_properties"):
-            self._properties = PropertyStore(self.path)
-        return self._properties
+        # FIXME: needs direct tests - only covered by calendar store tests
+        # FIXME: transactions
+        return PropertyStore(self._path)
 
 
+
 class CalendarObject(LoggingMixIn):
+    """
+    @ivar path: The path of the .ics file on disk
+
+    @type path: L{FilePath}
+    """
     implements(ICalendarObject)
 
     def __init__(self, path, calendar):
-        self.path = path
+        self._path = path
         self.calendar = calendar
+        self._component = None
 
+
     def __repr__(self):
-        return "<%s: %s>" % (self.__class__.__name__, self.path.path)
+        return "<%s: %s>" % (self.__class__.__name__, self._path.path)
 
+
     def name(self):
-        return self.path.basename()
+        return self._path.basename()
 
+
     def setComponent(self, component):
         if not isinstance(component, VComponent):
-            raise TypeError(VComponent)
+            raise TypeError(type(component))
 
         try:
             if component.resourceUID() != self.uid():
@@ -296,67 +546,71 @@
             raise InvalidCalendarComponentError(e)
 
         self._component = component
-        if hasattr(self, "_text"):
-            del self._text
+        # FIXME: needs to clear text cache
 
-        fh = self.path.open("w")
-        try:
-            fh.write(str(component))
-        finally:
-            fh.close()
+        def do():
+            backup = None
+            if self._path.exists():
+                backup = self._path.temporarySibling()
+                self._path.moveTo(backup)
+            fh = self._path.open("w")
+            try:
+                # FIXME: concurrency problem; if this write is interrupted
+                # halfway through, the underlying file will be corrupt.
+                fh.write(str(component))
+            finally:
+                fh.close()
+            def undo():
+                if backup:
+                    backup.moveTo(self._path)
+                else:
+                    self._path.remove()
+            return undo
+        self.calendar._transaction.addOperation(do)
 
+
     def component(self):
-        if not hasattr(self, "_component"):
-            text = self.iCalendarText()
+        if self._component is not None:
+            return self._component
+        text = self.iCalendarText()
 
-            try:
-                component = VComponent.fromString(text)
-            except InvalidICalendarDataError, e:
-                raise InternalDataStoreError(
-                    "File corruption detected (%s) in file: %s"
-                    % (e, self.path.path)
-                )
+        try:
+            component = VComponent.fromString(text)
+        except InvalidICalendarDataError, e:
+            raise InternalDataStoreError(
+                "File corruption detected (%s) in file: %s"
+                % (e, self._path.path)
+            )
+        return component
 
-            del self._text
-            self._component = component
 
-        return self._component
-
     def iCalendarText(self):
-        #
-        # Note I'm making an assumption here that caching both is
-        # redundant, so we're caching the text if it's asked for and
-        # we don't have the component cached, then tossing it and
-        # relying on the component if we have that cached. -wsv
-        #
-        if not hasattr(self, "_text"):
-            if hasattr(self, "_component"):
-                return str(self._component)
+        if self._component is not None:
+            return str(self._component)
+        try:
+            fh = self._path.open()
+        except IOError, e:
+            if e[0] == errno.ENOENT:
+                raise NoSuchCalendarObjectError(self)
+            else:
+                raise
 
-            try:
-                fh = self.path.open()
-            except IOError, e:
-                if e[0] == errno.ENOENT:
-                    raise NoSuchCalendarObjectError(self)
+        try:
+            text = fh.read()
+        finally:
+            fh.close()
 
-            try:
-                text = fh.read()
-            finally:
-                fh.close()
+        if not (
+            text.startswith("BEGIN:VCALENDAR\r\n") or
+            text.endswith("\r\nEND:VCALENDAR\r\n")
+        ):
+            raise InternalDataStoreError(
+                "File corruption detected (improper start) in file: %s"
+                % (self._path.path,)
+            )
+        return text
 
-            if not (
-                text.startswith("BEGIN:VCALENDAR\r\n") or
-                text.endswith("\r\nEND:VCALENDAR\r\n")
-            ):
-                raise InternalDataStoreError(
-                    "File corruption detected (improper start) in file: %s"
-                    % (self.path.path,)
-                )
 
-            self._text = text
-
-        return self._text
-
     def uid(self):
         if not hasattr(self, "_uid"):
             self._uid = self.component().resourceUID()
@@ -372,10 +626,11 @@
 
     def properties(self):
         if not hasattr(self, "_properties"):
-            self._properties = PropertyStore(self.path)
+            self._properties = PropertyStore(self._path)
         return self._properties
 
 
+
 class Index (object):
     #
     # OK, here's where we get ugly.
@@ -387,7 +642,7 @@
         """
         def __init__(self, calendar):
             self.calendar = calendar
-            self.fp = self.calendar.path
+            self.fp = self.calendar._path
 
         def isCalendarCollection(self):
             return True
@@ -407,9 +662,15 @@
                 return None
 
         def bumpSyncToken(self, reset=False):
+            # FIXME: needs direct tests
             return self.calendar._updateSyncToken(reset)
 
 
+        def initSyncToken(self):
+            # FIXME: needs direct tests
+            self.bumpSyncToken(True)
+
+
     def __init__(self, calendar):
         self.calendar = calendar
         self._oldIndex = OldIndex(Index.StubResource(calendar))

Modified: CalendarServer/branches/new-store/txcaldav/calendarstore/test/test_file.py
===================================================================
--- CalendarServer/branches/new-store/txcaldav/calendarstore/test/test_file.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/txcaldav/calendarstore/test/test_file.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -25,7 +25,10 @@
 
 from twext.python.vcomponent import VComponent
 
+from twext.web2.dav import davxml
+
 from txdav.idav import IPropertyStore
+from txdav.propertystore.base import PropertyName
 
 from txcaldav.icalendarstore import ICalendarStore, ICalendarHome
 from txcaldav.icalendarstore import ICalendar, ICalendarObject
@@ -126,16 +129,20 @@
 )
 
 
+
 def _todo(f, why):
     f.todo = why
     return f
 
+
+
 featureUnimplemented = lambda f: _todo(f, "Feature unimplemented")
 testUnimplemented = lambda f: _todo(f, "Test unimplemented")
 todo = lambda why: lambda f: _todo(f, why)
 
 
 class PropertiesTestMixin(object):
+
     def test_properties(self):
         properties = self.home1.properties()
 
@@ -145,6 +152,7 @@
         )
 
 
+
 def setUpCalendarStore(test):
     test.root = FilePath(test.mktemp())
     test.root.createDirectory()
@@ -152,24 +160,36 @@
     calendarPath = test.root.child("store")
     storePath.copyTo(calendarPath)
 
+
     test.calendarStore = CalendarStore(calendarPath)
+    test.txn = test.calendarStore.newTransaction()
     assert test.calendarStore is not None, "No calendar store?"
 
+
+
 def setUpHome1(test):
     setUpCalendarStore(test)
-    test.home1 = test.calendarStore.calendarHomeWithUID("home1")
+    test.home1 = test.txn.calendarHomeWithUID("home1")
     assert test.home1 is not None, "No calendar home?"
 
+
+
 def setUpCalendar1(test):
     setUpHome1(test)
     test.calendar1 = test.home1.calendarWithName("calendar_1")
     assert test.calendar1 is not None, "No calendar?"
 
 
+
 class CalendarStoreTest(unittest.TestCase):
+    """
+    Test cases for L{CalendarStore}.
+    """
+
     def setUp(self):
         setUpCalendarStore(self)
 
+
     def test_interface(self):
         """
         Interface is completed and conforming.
@@ -179,67 +199,79 @@
         except BrokenMethodImplementation, e:
             self.fail(e)
 
+
     def test_init(self):
         """
         Ivars are correctly initialized.
         """
         self.failUnless(
-            isinstance(self.calendarStore.path, FilePath),
-            self.calendarStore.path
+            isinstance(self.calendarStore._path, FilePath),
+            self.calendarStore._path
         )
 
+
     def test_calendarHomeWithUID_exists(self):
         """
         Find an existing calendar home by UID.
         """
-        calendarHome = self.calendarStore.calendarHomeWithUID("home1")
+        calendarHome = self.calendarStore.newTransaction().calendarHomeWithUID("home1")
 
         self.failUnless(isinstance(calendarHome, CalendarHome))
 
+
     def test_calendarHomeWithUID_absent(self):
         """
         Missing calendar home.
         """
         self.assertEquals(
-            self.calendarStore.calendarHomeWithUID("xyzzy"),
+            self.calendarStore.newTransaction().calendarHomeWithUID("xyzzy"),
             None
         )
 
+
     def test_calendarHomeWithUID_create(self):
         """
         Create missing calendar home.
         """
-        calendarHome = self.calendarStore.calendarHomeWithUID(
+        txn = self.calendarStore.newTransaction()
+        calendarHome = txn.calendarHomeWithUID(
             "xyzzy",
             create=True
         )
 
         self.failUnless(isinstance(calendarHome, CalendarHome))
-        self.failUnless(calendarHome.path.isdir())
+        self.failIf(calendarHome._path.isdir())
+        txn.commit()
+        self.failUnless(calendarHome._path.isdir())
 
+
     def test_calendarHomeWithUID_create_exists(self):
         """
         Create missing calendar home.
         """
-        calendarHome = self.calendarStore.calendarHomeWithUID("home1")
+        calendarHome = self.calendarStore.newTransaction().calendarHomeWithUID("home1")
 
         self.failUnless(isinstance(calendarHome, CalendarHome))
 
+
     def test_calendarHomeWithUID_dot(self):
         """
         Filenames starting with "." are reserved by this
         implementation, so no UIDs may start with ".".
         """
         self.assertEquals(
-            self.calendarStore.calendarHomeWithUID("xyzzy"),
+            self.calendarStore.newTransaction().calendarHomeWithUID("xyzzy"),
             None
         )
 
 
+
 class CalendarHomeTest(unittest.TestCase, PropertiesTestMixin):
+
     def setUp(self):
         setUpHome1(self)
 
+
     def test_interface(self):
         """
         Interface is completed and conforming.
@@ -249,31 +281,34 @@
         except BrokenMethodImplementation, e:
             self.fail(e)
 
+
     def test_init(self):
         """
         Ivars are correctly initialized.
         """
         self.failUnless(
-            isinstance(self.home1.path, FilePath),
-            self.home1.path
+            isinstance(self.home1._path, FilePath),
+            self.home1._path
         )
         self.assertEquals(
             self.home1.calendarStore,
             self.calendarStore
         )
 
+
     def test_uid(self):
         """
         UID is correct.
         """
         self.assertEquals(self.home1.uid(), "home1")
 
+
     def test_calendars(self):
         """
         Find all of the calendars.
         """
         # Add a dot directory to make sure we don't find it
-        self.home1.path.child(".foo").createDirectory()
+        self.home1._path.child(".foo").createDirectory()
 
         calendars = tuple(self.home1.calendars())
 
@@ -285,6 +320,7 @@
             home1_calendarNames
         )
 
+
     def test_calendarWithName_exists(self):
         """
         Find existing calendar by name.
@@ -294,21 +330,24 @@
             self.failUnless(isinstance(calendar, Calendar), calendar)
             self.assertEquals(calendar.name(), name)
 
+
     def test_calendarWithName_absent(self):
         """
         Missing calendar.
         """
         self.assertEquals(self.home1.calendarWithName("xyzzy"), None)
 
+
     def test_calendarWithName_dot(self):
         """
         Filenames starting with "." are reserved by this
         implementation, so no calendar names may start with ".".
         """
         name = ".foo"
-        self.home1.path.child(name).createDirectory()
+        self.home1._path.child(name).createDirectory()
         self.assertEquals(self.home1.calendarWithName(name), None)
 
+
     def test_createCalendarWithName_absent(self):
         """
         Create a new calendar.
@@ -317,7 +356,22 @@
         assert self.home1.calendarWithName(name) is None
         self.home1.createCalendarWithName(name)
         self.failUnless(self.home1.calendarWithName(name) is not None)
+        def checkProperties():
+            calendarProperties = self.home1.calendarWithName(name).properties()
+            self.assertEquals(
+                calendarProperties[
+                    PropertyName.fromString(davxml.ResourceType.sname())
+                ],
+                davxml.ResourceType.calendar)
+        checkProperties()
+        self.txn.commit()
+        self.home1 = self.calendarStore.newTransaction().calendarHomeWithUID(
+            "home1")
+        # Sanity check: are the properties actually persisted?
+        # FIXME: no independent testing of this right now
+        checkProperties()
 
+
     def test_createCalendarWithName_exists(self):
         """
         Attempt to create an existing calendar should raise.
@@ -328,6 +382,7 @@
                 self.home1.createCalendarWithName, name
             )
 
+
     def test_createCalendarWithName_dot(self):
         """
         Filenames starting with "." are reserved by this
@@ -338,15 +393,18 @@
             self.home1.createCalendarWithName, ".foo"
         )
 
+
     def test_removeCalendarWithName_exists(self):
         """
         Remove an existing calendar.
         """
         for name in home1_calendarNames:
-            assert self.home1.calendarWithName(name) is not None
+            self.assertNotIdentical(self.home1.calendarWithName(name),
+                                    None)
             self.home1.removeCalendarWithName(name)
             self.assertEquals(self.home1.calendarWithName(name), None)
 
+
     def test_removeCalendarWithName_absent(self):
         """
         Attempt to remove an non-existing calendar should raise.
@@ -356,22 +414,27 @@
             self.home1.removeCalendarWithName, "xyzzy"
         )
 
+
     def test_removeCalendarWithName_dot(self):
         """
         Filenames starting with "." are reserved by this
         implementation, so no calendar names may start with ".".
         """
         name = ".foo"
-        self.home1.path.child(name).createDirectory()
+        self.home1._path.child(name).createDirectory()
         self.assertRaises(
             NoSuchCalendarError,
             self.home1.removeCalendarWithName, name
         )
 
+
+
 class CalendarTest(unittest.TestCase, PropertiesTestMixin):
+
     def setUp(self):
         setUpCalendar1(self)
 
+
     def test_interface(self):
         """
         Interface is completed and conforming.
@@ -381,12 +444,13 @@
         except BrokenMethodImplementation, e:
             self.fail(e)
 
+
     def test_init(self):
         """
         Ivars are correctly initialized.
         """
         self.failUnless(
-            isinstance(self.calendar1.path, FilePath),
+            isinstance(self.calendar1._path, FilePath),
             self.calendar1
         )
         self.failUnless(
@@ -394,12 +458,14 @@
             self.calendar1.calendarHome
         )
 
+
     def test_name(self):
         """
         Name is correct.
         """
         self.assertEquals(self.calendar1.name(), "calendar_1")
 
+
     def test_ownerCalendarHome(self):
         """
         Owner is correct.
@@ -410,9 +476,10 @@
             self.home1.uid()
         )
 
+
     def _test_calendarObjects(self, which):
         # Add a dot file to make sure we don't find it
-        self.home1.path.child(".foo").createDirectory()
+        self.home1._path.child(".foo").createDirectory()
 
         methodName = "_calendarObjects_%s" % (which,)
         method = getattr(self.calendar1, methodName)
@@ -429,6 +496,7 @@
             calendar1_objectNames
         )
 
+    @featureUnimplemented
     def test_calendarObjects_listdir(self):
         """
         Find all of the calendar objects using the listdir
@@ -436,7 +504,8 @@
         """
         return self._test_calendarObjects("listdir")
 
-    @todo("Index is missing 1.ics?")
+
+    @featureUnimplemented
     def test_calendarObjects_index(self):
         """
         Find all of the calendar objects using the index
@@ -444,6 +513,7 @@
         """
         return self._test_calendarObjects("index")
 
+
     def test_calendarObjectWithName_exists(self):
         """
         Find existing calendar object by name.
@@ -456,12 +526,14 @@
             )
             self.assertEquals(calendarObject.name(), name)
 
+
     def test_calendarObjectWithName_absent(self):
         """
         Missing calendar object.
         """
         self.assertEquals(self.calendar1.calendarObjectWithName("xyzzy"), None)
 
+
     def test_calendarObjectWithName_dot(self):
         """
         Filenames starting with "." are reserved by this
@@ -469,9 +541,10 @@
         ".".
         """
         name = ".foo.ics"
-        self.home1.path.child(name).touch()
+        self.home1._path.child(name).touch()
         self.assertEquals(self.calendar1.calendarObjectWithName(name), None)
 
+
     @featureUnimplemented
     def test_calendarObjectWithUID_exists(self):
         """
@@ -487,25 +560,27 @@
             self.calendar1.calendarObjectWithName("1.ics").component()
         )
 
-    @featureUnimplemented
+
     def test_calendarObjectWithUID_absent(self):
         """
         Missing calendar object.
         """
         self.assertEquals(self.calendar1.calendarObjectWithUID("xyzzy"), None)
 
+
     def test_createCalendarObjectWithName_absent(self):
         """
         Create a new calendar object.
         """
         name = "4.ics"
-        assert self.calendar1.calendarObjectWithName(name) is None
+        self.assertIdentical(self.calendar1.calendarObjectWithName(name), None)
         component = VComponent.fromString(event4_text)
         self.calendar1.createCalendarObjectWithName(name, component)
 
         calendarObject = self.calendar1.calendarObjectWithName(name)
         self.assertEquals(calendarObject.component(), component)
 
+
     def test_createCalendarObjectWithName_exists(self):
         """
         Attempt to create an existing calendar object should raise.
@@ -516,6 +591,7 @@
             "1.ics", VComponent.fromString(event4_text)
         )
 
+
     def test_createCalendarObjectWithName_dot(self):
         """
         Filenames starting with "." are reserved by this
@@ -528,6 +604,7 @@
             ".foo", VComponent.fromString(event4_text)
         )
 
+
     @featureUnimplemented
     def test_createCalendarObjectWithName_uidconflict(self):
         """
@@ -543,6 +620,7 @@
             name, component
         )
 
+
     def test_createCalendarObjectWithName_invalid(self):
         """
         Attempt to create a calendar object with a invalid iCalendar text
@@ -554,18 +632,21 @@
             "new", VComponent.fromString(event4notCalDAV_text)
         )
 
+
     def test_removeCalendarObjectWithName_exists(self):
         """
         Remove an existing calendar object.
         """
         for name in calendar1_objectNames:
-            assert self.calendar1.calendarObjectWithName(name) is not None
+            self.assertNotIdentical(
+                self.calendar1.calendarObjectWithName(name), None
+            )
             self.calendar1.removeCalendarObjectWithName(name)
-            self.assertEquals(
-                self.calendar1.calendarObjectWithName(name),
-                None
+            self.assertIdentical(
+                self.calendar1.calendarObjectWithName(name), None
             )
 
+
     def test_removeCalendarObjectWithName_absent(self):
         """
         Attempt to remove an non-existing calendar object should raise.
@@ -575,6 +656,7 @@
             self.calendar1.removeCalendarObjectWithName, "xyzzy"
         )
 
+
     def test_removeCalendarObjectWithName_dot(self):
         """
         Filenames starting with "." are reserved by this
@@ -582,20 +664,21 @@
         ".".
         """
         name = ".foo"
-        self.calendar1.path.child(name).touch()
+        self.calendar1._path.child(name).touch()
         self.assertRaises(
             NoSuchCalendarObjectError,
             self.calendar1.removeCalendarObjectWithName, name
         )
 
-    @featureUnimplemented
+
     def test_removeCalendarObjectWithUID_exists(self):
         """
         Remove an existing calendar object.
         """
         for name in calendar1_objectNames:
-            uid = name.rstrip(".ics")
-            assert self.calendar1.calendarObjectWithUID(uid) is not None
+            uid = (u'uid' + name.rstrip(".ics"))
+            self.assertNotIdentical(self.calendar1.calendarObjectWithUID(uid),
+                                    None)
             self.calendar1.removeCalendarObjectWithUID(uid)
             self.assertEquals(
                 self.calendar1.calendarObjectWithUID(uid),
@@ -606,6 +689,88 @@
                 None
             )
 
+
+    def _refresh(self):
+        """
+        Re-read the (committed) home1 and calendar1 objects in a new
+        transaction.
+        """
+        self.txn = self.calendarStore.newTransaction()
+        self.home1 = self.txn.calendarHomeWithUID("home1")
+        self.calendar1 = self.home1.calendarWithName("calendar_1")
+
+
+    def test_undoCreateCalendarObject(self):
+        """
+        If a calendar object is created as part of a transaction, it will be
+        removed if that transaction has to be aborted.
+        """
+        # Make sure that the calendar home is actually committed; rolling back
+        # calendar home creation will remove the whole directory.
+        self.txn.commit()
+        self._refresh()
+        self.calendar1.createCalendarObjectWithName(
+            "sample.ics",
+            VComponent.fromString(event4_text)
+        )
+        self._refresh()
+        self.assertIdentical(
+            self.calendar1.calendarObjectWithName("sample.ics"),
+            None
+        )
+
+
+    def doThenUndo(self):
+        """
+        Commit the current transaction, but add an operation that will cause it
+        to fail at the end.  Finally, refresh all attributes with a new
+        transaction so that further oparations can be performed in a valid
+        context.
+        """
+        def fail():
+            raise RuntimeError("oops")
+        self.txn.addOperation(fail)
+        self.assertRaises(RuntimeError, self.txn.commit)
+        self._refresh()
+
+
+    def test_undoModifyCalendarObject(self):
+        """
+        If an existing calendar object is modified as part of a transaction, it
+        should be restored to its previous status if the transaction aborts.
+        """
+        originalComponent = self.calendar1.calendarObjectWithName(
+            "1.ics").component()
+        self.calendar1.calendarObjectWithName("1.ics").setComponent(
+            VComponent.fromString(event1modified_text)
+        )
+        # Sanity check.
+        self.assertEquals(
+            self.calendar1.calendarObjectWithName("1.ics").component(),
+            VComponent.fromString(event1modified_text)
+        )
+        self.doThenUndo()
+        self.assertEquals(
+            self.calendar1.calendarObjectWithName("1.ics").component(),
+            originalComponent
+        )
+
+
+    def test_modifyCalendarObjectCaches(self):
+        """
+        Modifying a calendar object should cache the modified component in
+        memory, to avoid unnecessary parsing round-trips.
+        """
+        modifiedComponent = VComponent.fromString(event1modified_text)
+        self.calendar1.calendarObjectWithName("1.ics").setComponent(
+            modifiedComponent
+        )
+        self.assertIdentical(
+            modifiedComponent,
+            self.calendar1.calendarObjectWithName("1.ics").component()
+        )
+
+
     @featureUnimplemented
     def test_removeCalendarObjectWithUID_absent(self):
         """
@@ -616,6 +781,7 @@
             self.calendar1.removeCalendarObjectWithUID, "xyzzy"
         )
 
+
     @testUnimplemented
     def test_syncToken(self):
         """
@@ -623,6 +789,7 @@
         """
         raise NotImplementedError()
 
+
     @testUnimplemented
     def test_calendarObjectsInTimeRange(self):
         """
@@ -630,6 +797,7 @@
         """
         raise NotImplementedError()
 
+
     @testUnimplemented
     def test_calendarObjectsSinceToken(self):
         """
@@ -639,11 +807,13 @@
         raise NotImplementedError()
 
 
+
 class CalendarObjectTest(unittest.TestCase, PropertiesTestMixin):
     def setUp(self):
         setUpCalendar1(self)
         self.object1 = self.calendar1.calendarObjectWithName("1.ics")
 
+
     def test_interface(self):
         """
         Interface is completed and conforming.
@@ -653,34 +823,38 @@
         except BrokenMethodImplementation, e:
             self.fail(e)
 
+
     def test_init(self):
         """
         Ivars are correctly initialized.
         """
         self.failUnless(
-            isinstance(self.object1.path, FilePath),
-            self.object1.path
+            isinstance(self.object1._path, FilePath),
+            self.object1._path
         )
         self.failUnless(
             isinstance(self.object1.calendar, Calendar),
             self.object1.calendar
         )
 
+
     def test_name(self):
         """
         Name is correct.
         """
         self.assertEquals(self.object1.name(), "1.ics")
 
+
     def test_setComponent(self):
         """
-        Rewrite component.
+        L{CalendarObject.setComponent} changes the result of
+        L{CalendarObject.component} within the same transaction.
         """
         component = VComponent.fromString(event1modified_text)
 
         calendarObject = self.calendar1.calendarObjectWithName("1.ics")
-        oldComponent = calendarObject.component() # Trigger caching
-        assert component != oldComponent
+        oldComponent = calendarObject.component()
+        self.assertNotEqual(component, oldComponent)
         calendarObject.setComponent(component)
         self.assertEquals(calendarObject.component(), component)
 
@@ -688,6 +862,7 @@
         calendarObject = self.calendar1.calendarObjectWithName("1.ics")
         self.assertEquals(calendarObject.component(), component)
 
+
     def test_setComponent_uidchanged(self):
         component = VComponent.fromString(event4_text)
 
@@ -697,6 +872,7 @@
             calendarObject.setComponent, component
         )
 
+
     def test_setComponent_invalid(self):
         calendarObject = self.calendar1.calendarObjectWithName("1.ics")
         self.assertRaises(
@@ -705,6 +881,7 @@
             VComponent.fromString(event4notCalDAV_text)
         )
 
+
     def test_component(self):
         """
         Component is correct.
@@ -720,6 +897,7 @@
         self.assertEquals(component.mainType(), "VEVENT")
         self.assertEquals(component.resourceUID(), "uid1")
 
+
     def text_iCalendarText(self):
         """
         iCalendar text is correct.
@@ -730,18 +908,21 @@
         self.failUnless("\r\nUID:uid-1\r\n" in text)
         self.failUnless(text.endswith("\r\nEND:VCALENDAR\r\n"))
 
+
     def test_uid(self):
         """
         UID is correct.
         """
         self.assertEquals(self.object1.uid(), "uid1")
 
+
     def test_componentType(self):
         """
         Component type is correct.
         """
         self.assertEquals(self.object1.componentType(), "VEVENT")
 
+
     def test_organizer(self):
         """
         Organizer is correct.

Modified: CalendarServer/branches/new-store/txcaldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/new-store/txcaldav/icalendarstore.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/txcaldav/icalendarstore.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -1,3 +1,4 @@
+# -*- test-case-name: txcaldav.calendarstore -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #
@@ -18,10 +19,10 @@
 Calendar store interfaces
 """
 
-# FIXME:  Still to do:
-# - Where to defer?
-# - commit() and abort()
+from zope.interface import Interface
+from txdav.idav import ITransaction
 
+
 __all__ = [
     # Exceptions
     "CalendarStoreError",
@@ -45,13 +46,17 @@
     "ICalendarObject",
 ]
 
-from zope.interface import Interface #, Attribute
 
-from datetime import datetime, date, tzinfo
-from twext.python.vcomponent import VComponent
-from txdav.idav import IPropertyStore
+# The following imports are used by the L{} links below, but shouldn't actually
+# be imported.as they're not really needed.
 
+# from datetime import datetime, date, tzinfo
 
+# from twext.python.vcomponent import VComponent
+
+# from txdav.idav import IPropertyStore
+# from txdav.idav import ITransaction
+
 #
 # Exceptions
 #
@@ -130,6 +135,15 @@
     """
     Calendar store
     """
+    def newTransaction():
+        """
+        Create a new transaction.
+        """
+
+class ICalendarStoreTransaction(ITransaction):
+    """
+    Calendar store transaction
+    """
     def calendarHomeWithUID(uid, create=False):
         """
         Retrieve the calendar home for the principal with the given
@@ -143,6 +157,21 @@
         """
 
 
+    def abort():
+        """
+        Mark this transaction as invalid, reversing all of its effects.
+        """
+        # FIXME: probably needs to be deferred.
+
+
+    def commit():
+        """
+        Persist the effects of this transaction.
+        """
+        # FIXME: probably needs to be deferred.
+
+
+
 class ICalendarHome(Interface):
     """
     Calendar home
@@ -152,14 +181,6 @@
     includes both calendars owned by the principal as well as
     calendars that have been shared with and accepts by the principal.
     """
-    # FIXME: We need a principal interface somewhere, possibly part of
-    # an idirectory rework.  IDirectoryRecord may be close...
-    #def owner():
-    #    """
-    #    Retrieve the owner principal for this calendar home.
-    #    @return: a ???
-    #    """
-
     def uid():
         """
         Retrieve the unique identifier for this calendar home.
@@ -339,6 +360,7 @@
     A calendar object decribes an event, to-do, or other iCalendar
     object.
     """
+
     def setComponent(component):
         """
         Rewrite this calendar object to match the given C{component}.

Modified: CalendarServer/branches/new-store/txdav/idav.py
===================================================================
--- CalendarServer/branches/new-store/txdav/idav.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/txdav/idav.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -84,3 +84,25 @@
         """
         Abort any pending changes.
         """
+
+
+class ITransaction(Interface):
+    """
+    Transaction that can be aborted and either succeeds or fails in
+    its entirety.
+    """
+    def abort():
+        """
+        Abort this transaction.
+        """
+
+    def commit():
+        """
+        Perform this transaction.
+        """
+
+
+class AbortedTransactionError(RuntimeError):
+    """
+    This transaction has aborted.  Go away.
+    """

Modified: CalendarServer/branches/new-store/txdav/propertystore/base.py
===================================================================
--- CalendarServer/branches/new-store/txdav/propertystore/base.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/txdav/propertystore/base.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -49,9 +49,31 @@
         self.namespace = namespace
         self.name = name
 
+
+    def _cmpval(self):
+        """
+        Return a value to use for hashing and comparisons.
+        """
+        return (self.namespace, self.name)
+
+
+    # FIXME: need direct tests for presence-in-dictionary
     def __hash__(self):
-        return hash((self.namespace, self.name))
+        return hash(self._cmpval())
 
+
+    def __eq__(self, other):
+        if not isinstance(other, PropertyName):
+            return NotImplemented
+        return self._cmpval() == other._cmpval()
+
+
+    def __ne__(self, other):
+        if not isinstance(other, PropertyName):
+            return NotImplemented
+        return self._cmpval() != other._cmpval()
+
+
     def __repr__(self):
         return "<%s: %s>" % (
             self.__class__.__name__,

Modified: CalendarServer/branches/new-store/txdav/propertystore/test/test_xattr.py
===================================================================
--- CalendarServer/branches/new-store/txdav/propertystore/test/test_xattr.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/txdav/propertystore/test/test_xattr.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -18,7 +18,7 @@
 Property store tests.
 """
 
-from twext.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 
 from txdav.propertystore.base import PropertyName
 from txdav.propertystore.test.base import propertyName

Modified: CalendarServer/branches/new-store/txdav/propertystore/xattr.py
===================================================================
--- CalendarServer/branches/new-store/txdav/propertystore/xattr.py	2010-05-13 04:55:07 UTC (rev 5594)
+++ CalendarServer/branches/new-store/txdav/propertystore/xattr.py	2010-05-13 05:04:13 UTC (rev 5595)
@@ -83,6 +83,12 @@
         )
 
     def __init__(self, path):
+        """
+        Initialize a L{PropertyStore}.
+
+        @param path: the path to set extended attributes on.
+        @type path: L{CachingFilePath}
+        """
         self.path = path
         self.attrs = xattr(path.path)
         self.removed = set()
@@ -117,7 +123,7 @@
         try:
             data = self.attrs[self._encodeKey(key)]
         except IOError, e:
-            if e.errno in _ERRNO_NO_ATTR:
+            if e.errno in [_ERRNO_NO_ATTR]:
                 raise KeyError(key)
             raise PropertyStoreError(e)
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100512/dcef0b00/attachment-0001.html>


More information about the calendarserver-changes mailing list