<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[5548] CalendarServer/branches/users/wsanchez/transations/txcaldav</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.macosforge.org/projects/calendarserver/changeset/5548">5548</a></dd>
<dt>Author</dt> <dd>glyph@apple.com</dd>
<dt>Date</dt> <dd>2010-04-29 17:45:16 -0700 (Thu, 29 Apr 2010)</dd>
</dl>

<h3>Log Message</h3>
<pre>Give an 'undo' to setComponent; make _path private; add a couple of tests for &quot;undo&quot;</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserswsancheztransationstxcaldavcalendarstorefilepy">CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/file.py</a></li>
<li><a href="#CalendarServerbranchesuserswsancheztransationstxcaldavcalendarstoretesttest_filepy">CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/test/test_file.py</a></li>
<li><a href="#CalendarServerbranchesuserswsancheztransationstxcaldavicalendarstorepy">CalendarServer/branches/users/wsanchez/transations/txcaldav/icalendarstore.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserswsancheztransationstxcaldavcalendarstorefilepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/file.py (5547 => 5548)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -37,7 +37,7 @@
</span><span class="cx"> from twext.python.vcomponent import VComponent
</span><span class="cx"> from twext.python.vcomponent import InvalidICalendarDataError
</span><span class="cx"> 
</span><del>-from txdav.idav import AbortedTransactionError
</del><ins>+# from txdav.idav import AbortedTransactionError
</ins><span class="cx"> from txdav.propertystore.xattr import PropertyStore
</span><span class="cx"> 
</span><span class="cx"> from txcaldav.icalendarstore import ICalendarStoreTransaction
</span><span class="lines">@@ -56,7 +56,16 @@
</span><span class="cx"> from twistedcaldav.index import Index as OldIndex
</span><span class="cx"> from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
</span><span class="cx"> 
</span><ins>+def _isValidName(name):
+    &quot;&quot;&quot;
+    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?
</ins><span class="cx"> 
</span><ins>+    @param name: a name which might be given to a calendar.
+    &quot;&quot;&quot;
+    return not name.startswith(&quot;.&quot;)
+
+
</ins><span class="cx"> class CalendarStore(LoggingMixIn):
</span><span class="cx">     implements(ICalendarStore)
</span><span class="cx"> 
</span><span class="lines">@@ -66,14 +75,14 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         assert isinstance(path, FilePath)
</span><span class="cx"> 
</span><del>-        self.path = path
</del><ins>+        self._path = path
</ins><span class="cx"> 
</span><span class="cx">         if not path.isdir():
</span><span class="cx">             # FIXME: Add CalendarStoreNotFoundError?
</span><span class="cx">             raise NotFoundError(&quot;No such calendar store&quot;)
</span><span class="cx"> 
</span><span class="cx">     def __repr__(self):
</span><del>-        return &quot;&lt;%s: %s&gt;&quot; % (self.__class__.__name__, self.path.path)
</del><ins>+        return &quot;&lt;%s: %s&gt;&quot; % (self.__class__.__name__, self._path.path)
</ins><span class="cx"> 
</span><span class="cx">     def newTransaction(self):
</span><span class="cx">         return Transaction(self)
</span><span class="lines">@@ -121,7 +130,7 @@
</span><span class="cx"> 
</span><span class="cx">         assert len(uid) &gt;= 4
</span><span class="cx"> 
</span><del>-        childPath1 = self.calendarStore.path.child(uid[0:2])
</del><ins>+        childPath1 = self.calendarStore._path.child(uid[0:2])
</ins><span class="cx">         childPath2 = childPath1.child(uid[2:4])
</span><span class="cx">         childPath3 = childPath2.child(uid)
</span><span class="cx"> 
</span><span class="lines">@@ -156,22 +165,22 @@
</span><span class="cx">     implements(ICalendarHome)
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, path, calendarStore, transaction):
</span><del>-        self.path = path
</del><ins>+        self._path = path
</ins><span class="cx">         self.calendarStore = calendarStore
</span><span class="cx">         self._transaction = transaction
</span><span class="cx">         self._newCalendars = {}
</span><span class="cx">         self._removedCalendars = set()
</span><span class="cx"> 
</span><span class="cx">     def __repr__(self):
</span><del>-        return &quot;&lt;%s: %s&gt;&quot; % (self.__class__.__name__, self.path)
</del><ins>+        return &quot;&lt;%s: %s&gt;&quot; % (self.__class__.__name__, self._path)
</ins><span class="cx"> 
</span><span class="cx">     def uid(self):
</span><del>-        return self.path.basename()
</del><ins>+        return self._path.basename()
</ins><span class="cx"> 
</span><span class="cx">     def calendars(self):
</span><span class="cx">         return set(self._newCalendars.itervalues()) | set(
</span><span class="cx">             self.calendarWithName(name)
</span><del>-            for name in self.path.listdir()
</del><ins>+            for name in self._path.listdir()
</ins><span class="cx">             if not name.startswith(&quot;.&quot;)
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="lines">@@ -185,7 +194,7 @@
</span><span class="cx">         if name.startswith(&quot;.&quot;):
</span><span class="cx">             return None
</span><span class="cx"> 
</span><del>-        childPath = self.path.child(name)
</del><ins>+        childPath = self._path.child(name)
</ins><span class="cx">         if childPath.isdir():
</span><span class="cx">             return Calendar(childPath, self)
</span><span class="cx">         else:
</span><span class="lines">@@ -195,7 +204,7 @@
</span><span class="cx">         if name.startswith(&quot;.&quot;):
</span><span class="cx">             raise CalendarNameNotAllowedError(name)
</span><span class="cx"> 
</span><del>-        childPath = self.path.child(name)
</del><ins>+        childPath = self._path.child(name)
</ins><span class="cx"> 
</span><span class="cx">         if name not in self._removedCalendars and childPath.isdir():
</span><span class="cx">             raise CalendarAlreadyExistsError(name)
</span><span class="lines">@@ -212,14 +221,14 @@
</span><span class="cx">                 raise
</span><span class="cx"> 
</span><span class="cx">         self._transaction.addOperation(do)
</span><del>-        self._newCalendars[name] = Calendar(self.path.child(name), self)
</del><ins>+        self._newCalendars[name] = Calendar(self._path.child(name), self)
</ins><span class="cx"> 
</span><span class="cx">     def removeCalendarWithName(self, name):
</span><span class="cx">         if name.startswith(&quot;.&quot;) or name in self._removedCalendars:
</span><span class="cx">             raise NoSuchCalendarError(name)
</span><span class="cx"> 
</span><span class="cx">         self._removedCalendars.add(name)
</span><del>-        childPath = self.path.child(name)
</del><ins>+        childPath = self._path.child(name)
</ins><span class="cx">         if name not in self._newCalendars and not childPath.isdir():
</span><span class="cx">             raise NoSuchCalendarError(name)
</span><span class="cx"> 
</span><span class="lines">@@ -254,22 +263,26 @@
</span><span class="cx">     def properties(self):
</span><span class="cx">         # raise NotImplementedError()
</span><span class="cx">         if not hasattr(self, &quot;_properties&quot;):
</span><del>-            self._properties = PropertyStore(self.path)
</del><ins>+            self._properties = PropertyStore(self._path)
</ins><span class="cx">         return self._properties
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class Calendar(LoggingMixIn):
</span><ins>+    &quot;&quot;&quot;
+    File-based implementation of L{ICalendar}.
+    &quot;&quot;&quot;
</ins><span class="cx">     implements(ICalendar)
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, path, calendarHome):
</span><del>-        self.path = path
</del><ins>+        self._path = path
</ins><span class="cx">         self.calendarHome = calendarHome
</span><span class="cx">         self._transaction = calendarHome._transaction
</span><span class="cx">         self._newCalendarObjects = {}
</span><ins>+        self._cachedCalendarObjects = {}
</ins><span class="cx">         self._removedCalendarObjects = set()
</span><span class="cx"> 
</span><span class="cx">     def __repr__(self):
</span><del>-        return &quot;&lt;%s: %s&gt;&quot; % (self.__class__.__name__, self.path.path)
</del><ins>+        return &quot;&lt;%s: %s&gt;&quot; % (self.__class__.__name__, self._path.path)
</ins><span class="cx"> 
</span><span class="cx">     def index(self):
</span><span class="cx">         if not hasattr(self, &quot;_index&quot;):
</span><span class="lines">@@ -277,7 +290,7 @@
</span><span class="cx">         return self._index
</span><span class="cx"> 
</span><span class="cx">     def name(self):
</span><del>-        return self.path.basename()
</del><ins>+        return self._path.basename()
</ins><span class="cx"> 
</span><span class="cx">     def ownerCalendarHome(self):
</span><span class="cx">         return self.calendarHome
</span><span class="lines">@@ -297,51 +310,67 @@
</span><span class="cx">             self.calendarObjectWithName(name)
</span><span class="cx">             for name in (
</span><span class="cx">                 set(self._newCalendarObjects.iterkeys()) |
</span><del>-                set(name for name in self.path.listdir() if not name.startswith(&quot;.&quot;))
</del><ins>+                set(name for name in self._path.listdir() if not name.startswith(&quot;.&quot;))
</ins><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def calendarObjectWithName(self, name):
</span><ins>+        if name in self._removedCalendarObjects:
+            return None
</ins><span class="cx">         if name in self._newCalendarObjects:
</span><span class="cx">             return self._newCalendarObjects[name]
</span><ins>+        if name in self._cachedCalendarObjects:
+            return self._cachedCalendarObjects[name]
</ins><span class="cx"> 
</span><del>-        calendarObjectPath = self.path.child(name)
</del><ins>+
+        calendarObjectPath = self._path.child(name)
</ins><span class="cx">         if calendarObjectPath.isfile():
</span><del>-            return CalendarObject(calendarObjectPath, self)
</del><ins>+            obj = CalendarObject(calendarObjectPath, self)
+            self._cachedCalendarObjects[name] = obj
+            return obj
</ins><span class="cx">         else:
</span><span class="cx">             return None
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def calendarObjectWithUID(self, uid):
</span><span class="cx">         # FIXME: This _really_ needs to be inspecting an index, not parsing
</span><span class="cx">         # every resource.
</span><del>-        for calendarObjectPath in self.path.children():
</del><ins>+        for calendarObjectPath in self._path.children():
+            if not _isValidName(calendarObjectPath.basename()):
+                continue
</ins><span class="cx">             obj = CalendarObject(calendarObjectPath, self)
</span><span class="cx">             if obj.component().resourceUID() == uid:
</span><span class="cx">                 return obj
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def createCalendarObjectWithName(self, name, component):
</span><span class="cx">         if name.startswith(&quot;.&quot;):
</span><span class="cx">             raise CalendarObjectNameNotAllowedError(name)
</span><span class="cx"> 
</span><del>-        calendarObjectPath = self.path.child(name)
</del><ins>+        calendarObjectPath = self._path.child(name)
</ins><span class="cx">         if calendarObjectPath.exists():
</span><span class="cx">             raise CalendarObjectNameAlreadyExistsError(name)
</span><span class="cx"> 
</span><span class="cx">         calendarObject = CalendarObject(calendarObjectPath, self)
</span><span class="cx">         calendarObject.setComponent(component)
</span><ins>+        self._cachedCalendarObjects[name] = calendarObject
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def removeCalendarObjectWithName(self, name):
</span><span class="cx">         if name.startswith(&quot;.&quot;):
</span><span class="cx">             raise NoSuchCalendarObjectError(name)
</span><span class="cx"> 
</span><del>-        calendarObjectPath = self.path.child(name)
</del><ins>+        calendarObjectPath = self._path.child(name)
</ins><span class="cx">         if calendarObjectPath.isfile():
</span><ins>+            self._removedCalendarObjects.add(name)
+            # FIXME: test for undo
</ins><span class="cx">             calendarObjectPath.remove()
</span><span class="cx">         else:
</span><span class="cx">             raise NoSuchCalendarObjectError(name)
</span><span class="cx"> 
</span><span class="cx">     def removeCalendarObjectWithUID(self, uid):
</span><del>-        self.removeCalendarObjectWithName(self.calendarObjectWithUID(uid).path.basename())
</del><ins>+        self.removeCalendarObjectWithName(self.calendarObjectWithUID(uid)._path.basename())
</ins><span class="cx"> 
</span><span class="cx">     def syncToken(self):
</span><span class="cx">         raise NotImplementedError()
</span><span class="lines">@@ -380,23 +409,31 @@
</span><span class="cx">     def properties(self):
</span><span class="cx">         raise NotImplementedError()
</span><span class="cx">         if not hasattr(self, &quot;_properties&quot;):
</span><del>-            self._properties = PropertyStore(self.path)
</del><ins>+            self._properties = PropertyStore(self._path)
</ins><span class="cx">         return self._properties
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class CalendarObject(LoggingMixIn):
</span><ins>+    &quot;&quot;&quot;
+    @ivar path: The path of the .ics file on disk
+
+    @type path: L{FilePath}
+    &quot;&quot;&quot;
</ins><span class="cx">     implements(ICalendarObject)
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, path, calendar):
</span><del>-        self.path = path
</del><ins>+        self._path = path
</ins><span class="cx">         self.calendar = calendar
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def __repr__(self):
</span><del>-        return &quot;&lt;%s: %s&gt;&quot; % (self.__class__.__name__, self.path.path)
</del><ins>+        return &quot;&lt;%s: %s&gt;&quot; % (self.__class__.__name__, self._path.path)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def name(self):
</span><del>-        return self.path.basename()
</del><ins>+        return self._path.basename()
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def setComponent(self, component):
</span><span class="cx">         if not isinstance(component, VComponent):
</span><span class="cx">             raise TypeError(VComponent)
</span><span class="lines">@@ -420,12 +457,27 @@
</span><span class="cx">         if hasattr(self, &quot;_text&quot;):
</span><span class="cx">             del self._text
</span><span class="cx"> 
</span><del>-        fh = self.path.open(&quot;w&quot;)
-        try:
-            fh.write(str(component))
-        finally:
-            fh.close()
</del><ins>+        def do():
+            backup = None
+            if self._path.exists():
+                backup = self._path.temporarySibling()
+                self._path.moveTo(backup)
+            fh = self._path.open(&quot;w&quot;)
+            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)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def component(self):
</span><span class="cx">         if not hasattr(self, &quot;_component&quot;):
</span><span class="cx">             text = self.iCalendarText()
</span><span class="lines">@@ -435,7 +487,7 @@
</span><span class="cx">             except InvalidICalendarDataError, e:
</span><span class="cx">                 raise InternalDataStoreError(
</span><span class="cx">                     &quot;File corruption detected (%s) in file: %s&quot;
</span><del>-                    % (e, self.path.path)
</del><ins>+                    % (e, self._path.path)
</ins><span class="cx">                 )
</span><span class="cx"> 
</span><span class="cx">             del self._text
</span><span class="lines">@@ -455,10 +507,12 @@
</span><span class="cx">                 return str(self._component)
</span><span class="cx"> 
</span><span class="cx">             try:
</span><del>-                fh = self.path.open()
</del><ins>+                fh = self._path.open()
</ins><span class="cx">             except IOError, e:
</span><span class="cx">                 if e[0] == errno.ENOENT:
</span><span class="cx">                     raise NoSuchCalendarObjectError(self)
</span><ins>+                else:
+                    raise
</ins><span class="cx"> 
</span><span class="cx">             try:
</span><span class="cx">                 text = fh.read()
</span><span class="lines">@@ -471,7 +525,7 @@
</span><span class="cx">             ):
</span><span class="cx">                 raise InternalDataStoreError(
</span><span class="cx">                     &quot;File corruption detected (improper start) in file: %s&quot;
</span><del>-                    % (self.path.path,)
</del><ins>+                    % (self._path.path,)
</ins><span class="cx">                 )
</span><span class="cx"> 
</span><span class="cx">             self._text = text
</span><span class="lines">@@ -494,7 +548,7 @@
</span><span class="cx">     def properties(self):
</span><span class="cx">         raise NotImplementedError()
</span><span class="cx">         if not hasattr(self, &quot;_properties&quot;):
</span><del>-            self._properties = PropertyStore(self.path)
</del><ins>+            self._properties = PropertyStore(self._path)
</ins><span class="cx">         return self._properties
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -509,7 +563,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         def __init__(self, calendar):
</span><span class="cx">             self.calendar = calendar
</span><del>-            self.fp = self.calendar.path
</del><ins>+            self.fp = self.calendar._path
</ins><span class="cx"> 
</span><span class="cx">         def isCalendarCollection(self):
</span><span class="cx">             return True
</span></span></pre></div>
<a id="CalendarServerbranchesuserswsancheztransationstxcaldavcalendarstoretesttest_filepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/wsanchez/transations/txcaldav/calendarstore/test/test_file.py (5547 => 5548)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -152,15 +152,18 @@
</span><span class="cx">     calendarPath = test.root.child(&quot;store&quot;)
</span><span class="cx">     storePath.copyTo(calendarPath)
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     test.calendarStore = CalendarStore(calendarPath)
</span><span class="cx">     test.txn = test.calendarStore.newTransaction()
</span><span class="cx">     assert test.calendarStore is not None, &quot;No calendar store?&quot;
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> def setUpHome1(test):
</span><span class="cx">     setUpCalendarStore(test)
</span><span class="cx">     test.home1 = test.txn.calendarHomeWithUID(&quot;home1&quot;)
</span><span class="cx">     assert test.home1 is not None, &quot;No calendar home?&quot;
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> def setUpCalendar1(test):
</span><span class="cx">     setUpHome1(test)
</span><span class="cx">     test.calendar1 = test.home1.calendarWithName(&quot;calendar_1&quot;)
</span><span class="lines">@@ -185,8 +188,8 @@
</span><span class="cx">         Ivars are correctly initialized.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         self.failUnless(
</span><del>-            isinstance(self.calendarStore.path, FilePath),
-            self.calendarStore.path
</del><ins>+            isinstance(self.calendarStore._path, FilePath),
+            self.calendarStore._path
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">     def test_calendarHomeWithUID_exists(self):
</span><span class="lines">@@ -217,9 +220,9 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         self.failUnless(isinstance(calendarHome, CalendarHome))
</span><del>-        self.failIf(calendarHome.path.isdir())
</del><ins>+        self.failIf(calendarHome._path.isdir())
</ins><span class="cx">         txn.commit()
</span><del>-        self.failUnless(calendarHome.path.isdir())
</del><ins>+        self.failUnless(calendarHome._path.isdir())
</ins><span class="cx"> 
</span><span class="cx">     def test_calendarHomeWithUID_create_exists(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -258,8 +261,8 @@
</span><span class="cx">         Ivars are correctly initialized.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         self.failUnless(
</span><del>-            isinstance(self.home1.path, FilePath),
-            self.home1.path
</del><ins>+            isinstance(self.home1._path, FilePath),
+            self.home1._path
</ins><span class="cx">         )
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             self.home1.calendarStore,
</span><span class="lines">@@ -277,7 +280,7 @@
</span><span class="cx">         Find all of the calendars.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         # Add a dot directory to make sure we don't find it
</span><del>-        self.home1.path.child(&quot;.foo&quot;).createDirectory()
</del><ins>+        self.home1._path.child(&quot;.foo&quot;).createDirectory()
</ins><span class="cx"> 
</span><span class="cx">         calendars = tuple(self.home1.calendars())
</span><span class="cx"> 
</span><span class="lines">@@ -310,7 +313,7 @@
</span><span class="cx">         implementation, so no calendar names may start with &quot;.&quot;.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         name = &quot;.foo&quot;
</span><del>-        self.home1.path.child(name).createDirectory()
</del><ins>+        self.home1._path.child(name).createDirectory()
</ins><span class="cx">         self.assertEquals(self.home1.calendarWithName(name), None)
</span><span class="cx"> 
</span><span class="cx">     def test_createCalendarWithName_absent(self):
</span><span class="lines">@@ -367,7 +370,7 @@
</span><span class="cx">         implementation, so no calendar names may start with &quot;.&quot;.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         name = &quot;.foo&quot;
</span><del>-        self.home1.path.child(name).createDirectory()
</del><ins>+        self.home1._path.child(name).createDirectory()
</ins><span class="cx">         self.assertRaises(
</span><span class="cx">             NoSuchCalendarError,
</span><span class="cx">             self.home1.removeCalendarWithName, name
</span><span class="lines">@@ -391,7 +394,7 @@
</span><span class="cx">         Ivars are correctly initialized.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         self.failUnless(
</span><del>-            isinstance(self.calendar1.path, FilePath),
</del><ins>+            isinstance(self.calendar1._path, FilePath),
</ins><span class="cx">             self.calendar1
</span><span class="cx">         )
</span><span class="cx">         self.failUnless(
</span><span class="lines">@@ -417,7 +420,7 @@
</span><span class="cx"> 
</span><span class="cx">     def _test_calendarObjects(self, which):
</span><span class="cx">         # Add a dot file to make sure we don't find it
</span><del>-        self.home1.path.child(&quot;.foo&quot;).createDirectory()
</del><ins>+        self.home1._path.child(&quot;.foo&quot;).createDirectory()
</ins><span class="cx"> 
</span><span class="cx">         methodName = &quot;_calendarObjects_%s&quot; % (which,)
</span><span class="cx">         method = getattr(self.calendar1, methodName)
</span><span class="lines">@@ -475,7 +478,7 @@
</span><span class="cx">         &quot;.&quot;.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         name = &quot;.foo.ics&quot;
</span><del>-        self.home1.path.child(name).touch()
</del><ins>+        self.home1._path.child(name).touch()
</ins><span class="cx">         self.assertEquals(self.calendar1.calendarObjectWithName(name), None)
</span><span class="cx"> 
</span><span class="cx">     @featureUnimplemented
</span><span class="lines">@@ -506,13 +509,14 @@
</span><span class="cx">         Create a new calendar object.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         name = &quot;4.ics&quot;
</span><del>-        assert self.calendar1.calendarObjectWithName(name) is None
</del><ins>+        self.assertIdentical(self.calendar1.calendarObjectWithName(name), None)
</ins><span class="cx">         component = VComponent.fromString(event4_text)
</span><span class="cx">         self.calendar1.createCalendarObjectWithName(name, component)
</span><span class="cx"> 
</span><span class="cx">         calendarObject = self.calendar1.calendarObjectWithName(name)
</span><span class="cx">         self.assertEquals(calendarObject.component(), component)
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_createCalendarObjectWithName_exists(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Attempt to create an existing calendar object should raise.
</span><span class="lines">@@ -561,18 +565,21 @@
</span><span class="cx">             &quot;new&quot;, VComponent.fromString(event4notCalDAV_text)
</span><span class="cx">         )
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_removeCalendarObjectWithName_exists(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Remove an existing calendar object.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         for name in calendar1_objectNames:
</span><del>-            assert self.calendar1.calendarObjectWithName(name) is not None
</del><ins>+            self.assertNotIdentical(
+                self.calendar1.calendarObjectWithName(name), None
+            )
</ins><span class="cx">             self.calendar1.removeCalendarObjectWithName(name)
</span><del>-            self.assertEquals(
-                self.calendar1.calendarObjectWithName(name),
-                None
</del><ins>+            self.assertIdentical(
+                self.calendar1.calendarObjectWithName(name), None
</ins><span class="cx">             )
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_removeCalendarObjectWithName_absent(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Attempt to remove an non-existing calendar object should raise.
</span><span class="lines">@@ -589,7 +596,7 @@
</span><span class="cx">         &quot;.&quot;.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         name = &quot;.foo&quot;
</span><del>-        self.calendar1.path.child(name).touch()
</del><ins>+        self.calendar1._path.child(name).touch()
</ins><span class="cx">         self.assertRaises(
</span><span class="cx">             NoSuchCalendarObjectError,
</span><span class="cx">             self.calendar1.removeCalendarObjectWithName, name
</span><span class="lines">@@ -602,7 +609,6 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         for name in calendar1_objectNames:
</span><span class="cx">             uid = (u'uid' + name.rstrip(&quot;.ics&quot;))
</span><del>-            
</del><span class="cx">             self.assertNotIdentical(self.calendar1.calendarObjectWithUID(uid),
</span><span class="cx">                                     None)
</span><span class="cx">             self.calendar1.removeCalendarObjectWithUID(uid)
</span><span class="lines">@@ -616,6 +622,72 @@
</span><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def _refresh(self):
+        &quot;&quot;&quot;
+        Re-read the (committed) home1 and calendar1 objects in a new
+        transaction.
+        &quot;&quot;&quot;
+        self.txn = self.calendarStore.newTransaction()
+        self.home1 = self.txn.calendarHomeWithUID(&quot;home1&quot;)
+        self.calendar1 = self.home1.calendarWithName(&quot;calendar_1&quot;)
+
+
+    def test_undoCreateCalendarObject(self):
+        &quot;&quot;&quot;
+        If a calendar object is created as part of a transaction, it will be
+        removed if that transaction has to be aborted.
+        &quot;&quot;&quot;
+        # 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(
+            &quot;sample.ics&quot;,
+            VComponent.fromString(event4_text)
+        )
+        self._refresh()
+        self.assertIdentical(
+            self.calendar1.calendarObjectWithName(&quot;sample.ics&quot;),
+            None
+        )
+
+
+    def doThenUndo(self):
+        &quot;&quot;&quot;
+        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.
+        &quot;&quot;&quot;
+        def fail():
+            raise RuntimeError(&quot;oops&quot;)
+        self.txn.addOperation(fail)
+        self.assertRaises(RuntimeError, self.txn.commit)
+        self._refresh()
+
+
+    def test_undoModifyCalendarObject(self):
+        &quot;&quot;&quot;
+        If an existing calendar object is modified as part of a transaction, it
+        should be restored to its previous status if the transaction aborts.
+        &quot;&quot;&quot;
+        originalComponent = self.calendar1.calendarObjectWithName(
+            &quot;1.ics&quot;).component()
+        self.calendar1.calendarObjectWithName(&quot;1.ics&quot;).setComponent(
+            VComponent.fromString(event1modified_text)
+        )
+        # Sanity check.
+        self.assertEquals(
+            self.calendar1.calendarObjectWithName(&quot;1.ics&quot;).component(),
+            VComponent.fromString(event1modified_text)
+        )
+        self.doThenUndo()
+        self.assertEquals(
+            self.calendar1.calendarObjectWithName(&quot;1.ics&quot;).component(),
+            originalComponent
+        )
+
+
</ins><span class="cx">     @featureUnimplemented
</span><span class="cx">     def test_removeCalendarObjectWithUID_absent(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -626,6 +698,7 @@
</span><span class="cx">             self.calendar1.removeCalendarObjectWithUID, &quot;xyzzy&quot;
</span><span class="cx">         )
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @testUnimplemented
</span><span class="cx">     def test_syncToken(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -668,8 +741,8 @@
</span><span class="cx">         Ivars are correctly initialized.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         self.failUnless(
</span><del>-            isinstance(self.object1.path, FilePath),
-            self.object1.path
</del><ins>+            isinstance(self.object1._path, FilePath),
+            self.object1._path
</ins><span class="cx">         )
</span><span class="cx">         self.failUnless(
</span><span class="cx">             isinstance(self.object1.calendar, Calendar),
</span><span class="lines">@@ -682,15 +755,17 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         self.assertEquals(self.object1.name(), &quot;1.ics&quot;)
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_setComponent(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Rewrite component.
</del><ins>+        L{CalendarObject.setComponent} changes the result of
+        L{CalendarObject.component} within the same transaction.
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         component = VComponent.fromString(event1modified_text)
</span><span class="cx"> 
</span><span class="cx">         calendarObject = self.calendar1.calendarObjectWithName(&quot;1.ics&quot;)
</span><del>-        oldComponent = calendarObject.component() # Trigger caching
-        assert component != oldComponent
</del><ins>+        oldComponent = calendarObject.component()
+        self.assertNotEqual(component, oldComponent)
</ins><span class="cx">         calendarObject.setComponent(component)
</span><span class="cx">         self.assertEquals(calendarObject.component(), component)
</span><span class="cx"> 
</span><span class="lines">@@ -698,6 +773,7 @@
</span><span class="cx">         calendarObject = self.calendar1.calendarObjectWithName(&quot;1.ics&quot;)
</span><span class="cx">         self.assertEquals(calendarObject.component(), component)
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_setComponent_uidchanged(self):
</span><span class="cx">         component = VComponent.fromString(event4_text)
</span><span class="cx"> 
</span><span class="lines">@@ -707,6 +783,7 @@
</span><span class="cx">             calendarObject.setComponent, component
</span><span class="cx">         )
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_setComponent_invalid(self):
</span><span class="cx">         calendarObject = self.calendar1.calendarObjectWithName(&quot;1.ics&quot;)
</span><span class="cx">         self.assertRaises(
</span><span class="lines">@@ -715,6 +792,7 @@
</span><span class="cx">             VComponent.fromString(event4notCalDAV_text)
</span><span class="cx">         )
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_component(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Component is correct.
</span></span></pre></div>
<a id="CalendarServerbranchesuserswsancheztransationstxcaldavicalendarstorepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/wsanchez/transations/txcaldav/icalendarstore.py (5547 => 5548)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1,3 +1,4 @@
</span><ins>+# -*- test-case-name: txcaldav.calendarstore -*-
</ins><span class="cx"> ##
</span><span class="cx"> # Copyright (c) 2010 Apple Inc. All rights reserved.
</span><span class="cx"> #
</span><span class="lines">@@ -18,6 +19,10 @@
</span><span class="cx"> Calendar store interfaces
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+from zope.interface import Interface
+from txdav.idav import ITransaction
+
+
</ins><span class="cx"> __all__ = [
</span><span class="cx">     # Exceptions
</span><span class="cx">     &quot;CalendarStoreError&quot;,
</span><span class="lines">@@ -41,15 +46,17 @@
</span><span class="cx">     &quot;ICalendarObject&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><del>-from datetime import datetime, date, tzinfo
</del><span class="cx"> 
</span><del>-from zope.interface import Interface #, Attribute
</del><ins>+# The following imports are used by the L{} links below, but shouldn't actually
+# be imported.as they're not really needed.
</ins><span class="cx"> 
</span><del>-from twext.python.vcomponent import VComponent
</del><ins>+# from datetime import datetime, date, tzinfo
</ins><span class="cx"> 
</span><del>-from txdav.idav import IPropertyStore
-from txdav.idav import ITransaction
</del><ins>+# from twext.python.vcomponent import VComponent
</ins><span class="cx"> 
</span><ins>+# from txdav.idav import IPropertyStore
+# from txdav.idav import ITransaction
+
</ins><span class="cx"> #
</span><span class="cx"> # Exceptions
</span><span class="cx"> #
</span><span class="lines">@@ -158,14 +165,6 @@
</span><span class="cx">     includes both calendars owned by the principal as well as
</span><span class="cx">     calendars that have been shared with and accepts by the principal.
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    # FIXME: We need a principal interface somewhere, possibly part of
-    # an idirectory rework.  IDirectoryRecord may be close...
-    #def owner():
-    #    &quot;&quot;&quot;
-    #    Retrieve the owner principal for this calendar home.
-    #    @return: a ???
-    #    &quot;&quot;&quot;
-
</del><span class="cx">     def uid():
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Retrieve the unique identifier for this calendar home.
</span><span class="lines">@@ -345,6 +344,7 @@
</span><span class="cx">     A calendar object decribes an event, to-do, or other iCalendar
</span><span class="cx">     object.
</span><span class="cx">     &quot;&quot;&quot;
</span><ins>+
</ins><span class="cx">     def setComponent(component):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Rewrite this calendar object to match the given C{component}.
</span></span></pre>
</div>
</div>

</body>
</html>