[CalendarServer-changes] [5554] CalendarServer/branches/users/wsanchez/transations/txcaldav/ calendarstore

source_changes at macosforge.org source_changes at macosforge.org
Sun May 2 14:38:39 PDT 2010


Revision: 5554
          http://trac.macosforge.org/projects/calendarserver/changeset/5554
Author:   glyph at apple.com
Date:     2010-05-02 14:38:37 -0700 (Sun, 02 May 2010)
Log Message:
-----------
remove a few half-implemented untested features, add some docstrings, whitespace fixes

Modified Paths:
--------------
    CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/file.py
    CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/test/test_file.py

Modified: CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/file.py
===================================================================
--- CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/file.py	2010-05-02 21:35:47 UTC (rev 5553)
+++ CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/file.py	2010-05-02 21:38:37 UTC (rev 5554)
@@ -30,14 +30,12 @@
 
 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
 from twext.python.vcomponent import VComponent
 from twext.python.vcomponent import InvalidICalendarDataError
 
-# from txdav.idav import AbortedTransactionError
 from txdav.propertystore.xattr import PropertyStore
 
 from txcaldav.icalendarstore import ICalendarStoreTransaction
@@ -66,37 +64,95 @@
     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, name):
+        def inner():
+            cacheKey = "_"+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)
 
     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
 
-        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)
 
     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._operations = []
         self._calendarHomes = {}
 
+
     def addOperation(self, operation):
         self._operations.append(operation)
 
@@ -261,12 +317,12 @@
             return undo
 
     def properties(self):
-        # raise NotImplementedError()
-        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}.
@@ -281,20 +337,19 @@
         self._cachedCalendarObjects = {}
         self._removedCalendarObjects = set()
 
+
     def __repr__(self):
         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()
 
+
     def ownerCalendarHome(self):
         return self.calendarHome
 
+
 #    def _calendarObjects_index(self):
 #        return self.index().calendarObjects()
 #
@@ -407,12 +462,12 @@
         raise NotImplementedError()
 
     def properties(self):
+        # FIXME: needs tests
+        # FIXME: needs implementation
         raise NotImplementedError()
-        if not hasattr(self, "_properties"):
-            self._properties = PropertyStore(self._path)
-        return self._properties
 
 
+
 class CalendarObject(LoggingMixIn):
     """
     @ivar path: The path of the .ics file on disk
@@ -424,6 +479,7 @@
     def __init__(self, path, calendar):
         self._path = path
         self.calendar = calendar
+        self._component = None
 
 
     def __repr__(self):
@@ -454,8 +510,7 @@
             raise InvalidCalendarComponentError(e)
 
         self._component = component
-        if hasattr(self, "_text"):
-            del self._text
+        # FIXME: needs to clear text cache
 
         def do():
             backup = None
@@ -479,59 +534,47 @@
 
 
     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)
-                else:
-                    raise
+        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()
@@ -552,6 +595,7 @@
         return self._properties
 
 
+
 class Index (object):
     #
     # OK, here's where we get ugly.

Modified: CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/test/test_file.py
===================================================================
--- CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/test/test_file.py	2010-05-02 21:35:47 UTC (rev 5553)
+++ CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/test/test_file.py	2010-05-02 21:38:37 UTC (rev 5554)
@@ -126,16 +126,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 +149,7 @@
         )
 
 
+
 def setUpCalendarStore(test):
     test.root = FilePath(test.mktemp())
     test.root.createDirectory()
@@ -158,22 +163,30 @@
     assert test.calendarStore is not None, "No calendar store?"
 
 
+
 def setUpHome1(test):
     setUpCalendarStore(test)
     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.
@@ -183,6 +196,7 @@
         except BrokenMethodImplementation, e:
             self.fail(e)
 
+
     def test_init(self):
         """
         Ivars are correctly initialized.
@@ -192,6 +206,7 @@
             self.calendarStore._path
         )
 
+
     def test_calendarHomeWithUID_exists(self):
         """
         Find an existing calendar home by UID.
@@ -200,6 +215,7 @@
 
         self.failUnless(isinstance(calendarHome, CalendarHome))
 
+
     def test_calendarHomeWithUID_absent(self):
         """
         Missing calendar home.
@@ -209,6 +225,7 @@
             None
         )
 
+
     def test_calendarHomeWithUID_create(self):
         """
         Create missing calendar home.
@@ -224,6 +241,7 @@
         txn.commit()
         self.failUnless(calendarHome._path.isdir())
 
+
     def test_calendarHomeWithUID_create_exists(self):
         """
         Create missing calendar home.
@@ -232,6 +250,7 @@
 
         self.failUnless(isinstance(calendarHome, CalendarHome))
 
+
     def test_calendarHomeWithUID_dot(self):
         """
         Filenames starting with "." are reserved by this
@@ -243,10 +262,13 @@
         )
 
 
+
 class CalendarHomeTest(unittest.TestCase, PropertiesTestMixin):
+
     def setUp(self):
         setUpHome1(self)
 
+
     def test_interface(self):
         """
         Interface is completed and conforming.
@@ -256,6 +278,7 @@
         except BrokenMethodImplementation, e:
             self.fail(e)
 
+
     def test_init(self):
         """
         Ivars are correctly initialized.
@@ -269,12 +292,14 @@
             self.calendarStore
         )
 
+
     def test_uid(self):
         """
         UID is correct.
         """
         self.assertEquals(self.home1.uid(), "home1")
 
+
     def test_calendars(self):
         """
         Find all of the calendars.
@@ -292,6 +317,7 @@
             home1_calendarNames
         )
 
+
     def test_calendarWithName_exists(self):
         """
         Find existing calendar by name.
@@ -301,12 +327,14 @@
             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
@@ -316,6 +344,7 @@
         self.home1._path.child(name).createDirectory()
         self.assertEquals(self.home1.calendarWithName(name), None)
 
+
     def test_createCalendarWithName_absent(self):
         """
         Create a new calendar.
@@ -325,6 +354,7 @@
         self.home1.createCalendarWithName(name)
         self.failUnless(self.home1.calendarWithName(name) is not None)
 
+
     def test_createCalendarWithName_exists(self):
         """
         Attempt to create an existing calendar should raise.
@@ -335,6 +365,7 @@
                 self.home1.createCalendarWithName, name
             )
 
+
     def test_createCalendarWithName_dot(self):
         """
         Filenames starting with "." are reserved by this
@@ -345,6 +376,7 @@
             self.home1.createCalendarWithName, ".foo"
         )
 
+
     def test_removeCalendarWithName_exists(self):
         """
         Remove an existing calendar.
@@ -355,6 +387,7 @@
             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.
@@ -364,6 +397,7 @@
             self.home1.removeCalendarWithName, "xyzzy"
         )
 
+
     def test_removeCalendarWithName_dot(self):
         """
         Filenames starting with "." are reserved by this
@@ -376,10 +410,14 @@
             self.home1.removeCalendarWithName, name
         )
 
+
+
 class CalendarTest(unittest.TestCase, PropertiesTestMixin):
+
     def setUp(self):
         setUpCalendar1(self)
 
+
     def test_interface(self):
         """
         Interface is completed and conforming.
@@ -389,6 +427,7 @@
         except BrokenMethodImplementation, e:
             self.fail(e)
 
+
     def test_init(self):
         """
         Ivars are correctly initialized.
@@ -402,12 +441,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.
@@ -418,6 +459,7 @@
             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()
@@ -437,7 +479,7 @@
             calendar1_objectNames
         )
 
-    @testUnimplemented
+    @featureUnimplemented
     def test_calendarObjects_listdir(self):
         """
         Find all of the calendar objects using the listdir
@@ -445,7 +487,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
@@ -453,6 +496,7 @@
         """
         return self._test_calendarObjects("index")
 
+
     def test_calendarObjectWithName_exists(self):
         """
         Find existing calendar object by name.
@@ -465,12 +509,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
@@ -481,6 +527,7 @@
         self.home1._path.child(name).touch()
         self.assertEquals(self.calendar1.calendarObjectWithName(name), None)
 
+
     @featureUnimplemented
     def test_calendarObjectWithUID_exists(self):
         """
@@ -527,6 +574,7 @@
             "1.ics", VComponent.fromString(event4_text)
         )
 
+
     def test_createCalendarObjectWithName_dot(self):
         """
         Filenames starting with "." are reserved by this
@@ -539,6 +587,7 @@
             ".foo", VComponent.fromString(event4_text)
         )
 
+
     @featureUnimplemented
     def test_createCalendarObjectWithName_uidconflict(self):
         """
@@ -554,6 +603,7 @@
             name, component
         )
 
+
     def test_createCalendarObjectWithName_invalid(self):
         """
         Attempt to create a calendar object with a invalid iCalendar text
@@ -589,6 +639,7 @@
             self.calendar1.removeCalendarObjectWithName, "xyzzy"
         )
 
+
     def test_removeCalendarObjectWithName_dot(self):
         """
         Filenames starting with "." are reserved by this
@@ -688,6 +739,21 @@
         )
 
 
+    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):
         """
@@ -706,6 +772,7 @@
         """
         raise NotImplementedError()
 
+
     @testUnimplemented
     def test_calendarObjectsInTimeRange(self):
         """
@@ -713,6 +780,7 @@
         """
         raise NotImplementedError()
 
+
     @testUnimplemented
     def test_calendarObjectsSinceToken(self):
         """
@@ -722,11 +790,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.
@@ -736,6 +806,7 @@
         except BrokenMethodImplementation, e:
             self.fail(e)
 
+
     def test_init(self):
         """
         Ivars are correctly initialized.
@@ -749,6 +820,7 @@
             self.object1.calendar
         )
 
+
     def test_name(self):
         """
         Name is correct.
@@ -808,6 +880,7 @@
         self.assertEquals(component.mainType(), "VEVENT")
         self.assertEquals(component.resourceUID(), "uid1")
 
+
     def text_iCalendarText(self):
         """
         iCalendar text is correct.
@@ -818,18 +891,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.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100502/c4c18bc2/attachment-0001.html>


More information about the calendarserver-changes mailing list