[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