[CalendarServer-changes] [5548] CalendarServer/branches/users/wsanchez/transations/txcaldav
source_changes at macosforge.org
source_changes at macosforge.org
Thu Apr 29 17:45:21 PDT 2010
Revision: 5548
http://trac.macosforge.org/projects/calendarserver/changeset/5548
Author: glyph at apple.com
Date: 2010-04-29 17:45:16 -0700 (Thu, 29 Apr 2010)
Log Message:
-----------
Give an 'undo' to setComponent; make _path private; add a couple of tests for "undo"
Modified Paths:
--------------
CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/file.py
CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/test/test_file.py
CalendarServer/branches/users/wsanchez/transations/txcaldav/icalendarstore.py
Modified: CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/file.py
===================================================================
--- CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/file.py 2010-04-29 20:46:49 UTC (rev 5547)
+++ CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/file.py 2010-04-30 00:45:16 UTC (rev 5548)
@@ -37,7 +37,7 @@
from twext.python.vcomponent import VComponent
from twext.python.vcomponent import InvalidICalendarDataError
-from txdav.idav import AbortedTransactionError
+# from txdav.idav import AbortedTransactionError
from txdav.propertystore.xattr import PropertyStore
from txcaldav.icalendarstore import ICalendarStoreTransaction
@@ -56,7 +56,16 @@
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(".")
+
+
class CalendarStore(LoggingMixIn):
implements(ICalendarStore)
@@ -66,14 +75,14 @@
"""
assert isinstance(path, FilePath)
- self.path = path
+ self._path = path
if not path.isdir():
# FIXME: Add CalendarStoreNotFoundError?
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):
return Transaction(self)
@@ -121,7 +130,7 @@
assert len(uid) >= 4
- childPath1 = self.calendarStore.path.child(uid[0:2])
+ childPath1 = self.calendarStore._path.child(uid[0:2])
childPath2 = childPath1.child(uid[2:4])
childPath3 = childPath2.child(uid)
@@ -156,22 +165,22 @@
implements(ICalendarHome)
def __init__(self, path, calendarStore, transaction):
- self.path = path
+ 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 calendars(self):
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(".")
)
@@ -185,7 +194,7 @@
if name.startswith("."):
return None
- childPath = self.path.child(name)
+ childPath = self._path.child(name)
if childPath.isdir():
return Calendar(childPath, self)
else:
@@ -195,7 +204,7 @@
if name.startswith("."):
raise CalendarNameNotAllowedError(name)
- childPath = self.path.child(name)
+ childPath = self._path.child(name)
if name not in self._removedCalendars and childPath.isdir():
raise CalendarAlreadyExistsError(name)
@@ -212,14 +221,14 @@
raise
self._transaction.addOperation(do)
- self._newCalendars[name] = Calendar(self.path.child(name), self)
+ self._newCalendars[name] = Calendar(self._path.child(name), self)
def removeCalendarWithName(self, name):
if name.startswith(".") or name in self._removedCalendars:
raise NoSuchCalendarError(name)
self._removedCalendars.add(name)
- childPath = self.path.child(name)
+ childPath = self._path.child(name)
if name not in self._newCalendars and not childPath.isdir():
raise NoSuchCalendarError(name)
@@ -254,22 +263,26 @@
def properties(self):
# raise NotImplementedError()
if not hasattr(self, "_properties"):
- self._properties = PropertyStore(self.path)
+ self._properties = PropertyStore(self._path)
return self._properties
class Calendar(LoggingMixIn):
+ """
+ File-based implementation of L{ICalendar}.
+ """
implements(ICalendar)
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"):
@@ -277,7 +290,7 @@
return self._index
def name(self):
- return self.path.basename()
+ return self._path.basename()
def ownerCalendarHome(self):
return self.calendarHome
@@ -297,51 +310,67 @@
self.calendarObjectWithName(name)
for name in (
set(self._newCalendarObjects.iterkeys()) |
- set(name for name in self.path.listdir() if not name.startswith("."))
+ set(name for name in self._path.listdir() if not name.startswith("."))
)
)
+
def calendarObjectWithName(self, name):
+ 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)
+
+ calendarObjectPath = self._path.child(name)
if calendarObjectPath.isfile():
- return CalendarObject(calendarObjectPath, self)
+ obj = CalendarObject(calendarObjectPath, self)
+ self._cachedCalendarObjects[name] = obj
+ return obj
else:
return None
+
def calendarObjectWithUID(self, uid):
# FIXME: This _really_ needs to be inspecting an index, not parsing
# every resource.
- for calendarObjectPath in self.path.children():
+ 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)
- calendarObjectPath = self.path.child(name)
+ calendarObjectPath = self._path.child(name)
if calendarObjectPath.exists():
raise CalendarObjectNameAlreadyExistsError(name)
calendarObject = CalendarObject(calendarObjectPath, self)
calendarObject.setComponent(component)
+ self._cachedCalendarObjects[name] = calendarObject
+
def removeCalendarObjectWithName(self, name):
if name.startswith("."):
raise NoSuchCalendarObjectError(name)
- calendarObjectPath = self.path.child(name)
+ 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):
- self.removeCalendarObjectWithName(self.calendarObjectWithUID(uid).path.basename())
+ self.removeCalendarObjectWithName(self.calendarObjectWithUID(uid)._path.basename())
def syncToken(self):
raise NotImplementedError()
@@ -380,23 +409,31 @@
def properties(self):
raise NotImplementedError()
if not hasattr(self, "_properties"):
- self._properties = PropertyStore(self.path)
+ self._properties = PropertyStore(self._path)
return self._properties
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
+
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)
@@ -420,12 +457,27 @@
if hasattr(self, "_text"):
del self._text
- 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()
@@ -435,7 +487,7 @@
except InvalidICalendarDataError, e:
raise InternalDataStoreError(
"File corruption detected (%s) in file: %s"
- % (e, self.path.path)
+ % (e, self._path.path)
)
del self._text
@@ -455,10 +507,12 @@
return str(self._component)
try:
- fh = self.path.open()
+ fh = self._path.open()
except IOError, e:
if e[0] == errno.ENOENT:
raise NoSuchCalendarObjectError(self)
+ else:
+ raise
try:
text = fh.read()
@@ -471,7 +525,7 @@
):
raise InternalDataStoreError(
"File corruption detected (improper start) in file: %s"
- % (self.path.path,)
+ % (self._path.path,)
)
self._text = text
@@ -494,7 +548,7 @@
def properties(self):
raise NotImplementedError()
if not hasattr(self, "_properties"):
- self._properties = PropertyStore(self.path)
+ self._properties = PropertyStore(self._path)
return self._properties
@@ -509,7 +563,7 @@
"""
def __init__(self, calendar):
self.calendar = calendar
- self.fp = self.calendar.path
+ self.fp = self.calendar._path
def isCalendarCollection(self):
return True
Modified: CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/test/test_file.py
===================================================================
--- CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/test/test_file.py 2010-04-29 20:46:49 UTC (rev 5547)
+++ CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/test/test_file.py 2010-04-30 00:45:16 UTC (rev 5548)
@@ -152,15 +152,18 @@
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.txn.calendarHomeWithUID("home1")
assert test.home1 is not None, "No calendar home?"
+
def setUpCalendar1(test):
setUpHome1(test)
test.calendar1 = test.home1.calendarWithName("calendar_1")
@@ -185,8 +188,8 @@
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):
@@ -217,9 +220,9 @@
)
self.failUnless(isinstance(calendarHome, CalendarHome))
- self.failIf(calendarHome.path.isdir())
+ self.failIf(calendarHome._path.isdir())
txn.commit()
- self.failUnless(calendarHome.path.isdir())
+ self.failUnless(calendarHome._path.isdir())
def test_calendarHomeWithUID_create_exists(self):
"""
@@ -258,8 +261,8 @@
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,
@@ -277,7 +280,7 @@
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())
@@ -310,7 +313,7 @@
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):
@@ -367,7 +370,7 @@
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
@@ -391,7 +394,7 @@
Ivars are correctly initialized.
"""
self.failUnless(
- isinstance(self.calendar1.path, FilePath),
+ isinstance(self.calendar1._path, FilePath),
self.calendar1
)
self.failUnless(
@@ -417,7 +420,7 @@
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)
@@ -475,7 +478,7 @@
".".
"""
name = ".foo.ics"
- self.home1.path.child(name).touch()
+ self.home1._path.child(name).touch()
self.assertEquals(self.calendar1.calendarObjectWithName(name), None)
@featureUnimplemented
@@ -506,13 +509,14 @@
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.
@@ -561,18 +565,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.
@@ -589,7 +596,7 @@
".".
"""
name = ".foo"
- self.calendar1.path.child(name).touch()
+ self.calendar1._path.child(name).touch()
self.assertRaises(
NoSuchCalendarObjectError,
self.calendar1.removeCalendarObjectWithName, name
@@ -602,7 +609,6 @@
"""
for name in calendar1_objectNames:
uid = (u'uid' + name.rstrip(".ics"))
-
self.assertNotIdentical(self.calendar1.calendarObjectWithUID(uid),
None)
self.calendar1.removeCalendarObjectWithUID(uid)
@@ -616,6 +622,72 @@
)
+ 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
+ )
+
+
@featureUnimplemented
def test_removeCalendarObjectWithUID_absent(self):
"""
@@ -626,6 +698,7 @@
self.calendar1.removeCalendarObjectWithUID, "xyzzy"
)
+
@testUnimplemented
def test_syncToken(self):
"""
@@ -668,8 +741,8 @@
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),
@@ -682,15 +755,17 @@
"""
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)
@@ -698,6 +773,7 @@
calendarObject = self.calendar1.calendarObjectWithName("1.ics")
self.assertEquals(calendarObject.component(), component)
+
def test_setComponent_uidchanged(self):
component = VComponent.fromString(event4_text)
@@ -707,6 +783,7 @@
calendarObject.setComponent, component
)
+
def test_setComponent_invalid(self):
calendarObject = self.calendar1.calendarObjectWithName("1.ics")
self.assertRaises(
@@ -715,6 +792,7 @@
VComponent.fromString(event4notCalDAV_text)
)
+
def test_component(self):
"""
Component is correct.
Modified: CalendarServer/branches/users/wsanchez/transations/txcaldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/users/wsanchez/transations/txcaldav/icalendarstore.py 2010-04-29 20:46:49 UTC (rev 5547)
+++ CalendarServer/branches/users/wsanchez/transations/txcaldav/icalendarstore.py 2010-04-30 00:45:16 UTC (rev 5548)
@@ -1,3 +1,4 @@
+# -*- test-case-name: txcaldav.calendarstore -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
@@ -18,6 +19,10 @@
Calendar store interfaces
"""
+from zope.interface import Interface
+from txdav.idav import ITransaction
+
+
__all__ = [
# Exceptions
"CalendarStoreError",
@@ -41,15 +46,17 @@
"ICalendarObject",
]
-from datetime import datetime, date, tzinfo
-from zope.interface import Interface #, Attribute
+# The following imports are used by the L{} links below, but shouldn't actually
+# be imported.as they're not really needed.
-from twext.python.vcomponent import VComponent
+# from datetime import datetime, date, tzinfo
-from txdav.idav import IPropertyStore
-from txdav.idav import ITransaction
+# from twext.python.vcomponent import VComponent
+# from txdav.idav import IPropertyStore
+# from txdav.idav import ITransaction
+
#
# Exceptions
#
@@ -158,14 +165,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.
@@ -345,6 +344,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}.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100429/747d7e57/attachment-0001.html>
More information about the calendarserver-changes
mailing list