[CalendarServer-changes] [6185] CalendarServer/branches/generic-sqlstore

source_changes at macosforge.org source_changes at macosforge.org
Wed Aug 25 18:11:34 PDT 2010


Revision: 6185
          http://trac.macosforge.org/projects/calendarserver/changeset/6185
Author:   cdaboo at apple.com
Date:     2010-08-25 18:11:33 -0700 (Wed, 25 Aug 2010)
Log Message:
-----------
Add txdav.base module and push stuff stuff down into that.

Modified Paths:
--------------
    CalendarServer/branches/generic-sqlstore/calendarserver/tap/caldav.py
    CalendarServer/branches/generic-sqlstore/calendarserver/tap/util.py
    CalendarServer/branches/generic-sqlstore/twistedcaldav/schedule.py
    CalendarServer/branches/generic-sqlstore/twistedcaldav/storebridge.py
    CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/file.py
    CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/sql.py
    CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/common.py
    CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/file.py
    CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/sql.py
    CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/common.py
    CalendarServer/branches/generic-sqlstore/txdav/base/datastore/subpostgres.py
    CalendarServer/branches/generic-sqlstore/txdav/base/datastore/test/test_subpostgres.py
    CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/none.py
    CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_base.py
    CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_none.py
    CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_xattr.py
    CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/xattr.py
    CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py
    CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py
    CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/util.py

Added Paths:
-----------
    CalendarServer/branches/generic-sqlstore/txdav/base/
    CalendarServer/branches/generic-sqlstore/txdav/base/__init__.py
    CalendarServer/branches/generic-sqlstore/txdav/base/datastore/
    CalendarServer/branches/generic-sqlstore/txdav/base/datastore/file.py
    CalendarServer/branches/generic-sqlstore/txdav/base/datastore/sql.py
    CalendarServer/branches/generic-sqlstore/txdav/base/datastore/util.py
    CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/
    CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/sql.py
    CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/base.py
    CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_sql.py

Removed Paths:
-------------
    CalendarServer/branches/generic-sqlstore/txdav/base/datastore/file.py
    CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/base.py
    CalendarServer/branches/generic-sqlstore/txdav/datastore/
    CalendarServer/branches/generic-sqlstore/txdav/propertystore/

Modified: CalendarServer/branches/generic-sqlstore/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/calendarserver/tap/caldav.py	2010-08-26 00:52:30 UTC (rev 6184)
+++ CalendarServer/branches/generic-sqlstore/calendarserver/tap/caldav.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -89,7 +89,7 @@
 from calendarserver.tools.util import checkDirectory
 
 from txdav.common.datastore.sql import v1_schema
-from txdav.datastore.subpostgres import PostgresService
+from txdav.base.datastore.subpostgres import PostgresService
 from twext.python.filepath import CachingFilePath
 
 log = Logger()

Modified: CalendarServer/branches/generic-sqlstore/calendarserver/tap/util.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/calendarserver/tap/util.py	2010-08-26 00:52:30 UTC (rev 6184)
+++ CalendarServer/branches/generic-sqlstore/calendarserver/tap/util.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -70,7 +70,7 @@
 from txdav.common.datastore.sql import CommonDataStore as CommonSQLDataStore
 from txdav.common.datastore.file import CommonDataStore as CommonFileDataStore
 from txdav.common.datastore.sql import v1_schema
-from txdav.datastore.subpostgres import PostgresService
+from txdav.base.datastore.subpostgres import PostgresService
 from twext.python.filepath import CachingFilePath
 
 

Modified: CalendarServer/branches/generic-sqlstore/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/twistedcaldav/schedule.py	2010-08-26 00:52:30 UTC (rev 6184)
+++ CalendarServer/branches/generic-sqlstore/twistedcaldav/schedule.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -50,7 +50,7 @@
 from twistedcaldav.resource import isCalendarCollectionResource
 from twistedcaldav.scheduling.scheduler import CalDAVScheduler, IScheduleScheduler
 
-from txdav.propertystore.base import PropertyName
+from txdav.base.propertystore.base import PropertyName
 
 def _schedulePrivilegeSet(deliver):
     edited = False

Modified: CalendarServer/branches/generic-sqlstore/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/twistedcaldav/storebridge.py	2010-08-26 00:52:30 UTC (rev 6184)
+++ CalendarServer/branches/generic-sqlstore/twistedcaldav/storebridge.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -56,7 +56,7 @@
 from twistedcaldav.vcard import Component as VCard
 
 from txdav.common.icommondatastore import NoSuchObjectResourceError
-from txdav.propertystore.base import PropertyName
+from txdav.base.propertystore.base import PropertyName
 
 log = Logger()
 

Modified: CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/file.py	2010-08-26 00:52:30 UTC (rev 6184)
+++ CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/file.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -34,7 +34,7 @@
 from twisted.internet.interfaces import ITransport
 from twisted.python.failure import Failure
 
-from txdav.propertystore.xattr import PropertyStore
+from txdav.base.propertystore.xattr import PropertyStore
 
 from twext.python.vcomponent import InvalidICalendarDataError
 from twext.python.vcomponent import VComponent
@@ -62,8 +62,8 @@
 
 from txdav.common.icommondatastore import (NoSuchObjectResourceError,
     InternalDataStoreError)
-from txdav.datastore.file import writeOperation, hidden, FileMetaDataMixin
-from txdav.propertystore.base import PropertyName
+from txdav.base.datastore.file import writeOperation, hidden, FileMetaDataMixin
+from txdav.base.propertystore.base import PropertyName
 
 from zope.interface import implements
 

Modified: CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/sql.py	2010-08-26 00:52:30 UTC (rev 6184)
+++ CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/sql.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -49,7 +49,7 @@
 from txdav.common.datastore.sql_tables import CALENDAR_TABLE,\
     CALENDAR_BIND_TABLE, CALENDAR_OBJECT_REVISIONS_TABLE, CALENDAR_OBJECT_TABLE,\
     _ATTACHMENTS_MODE_WRITE
-from txdav.propertystore.base import PropertyName
+from txdav.base.propertystore.base import PropertyName
 
 from vobject.icalendar import utc
 

Modified: CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/common.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/common.py	2010-08-26 00:52:30 UTC (rev 6184)
+++ CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/common.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -27,7 +27,7 @@
 from twisted.internet.protocol import Protocol
 
 from txdav.idav import IPropertyStore, IDataStore, AlreadyFinishedError
-from txdav.propertystore.base import PropertyName
+from txdav.base.propertystore.base import PropertyName
 
 from txdav.common.icommondatastore import HomeChildNameAlreadyExistsError, \
     ICommonTransaction

Modified: CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/file.py	2010-08-26 00:52:30 UTC (rev 6184)
+++ CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/file.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -43,8 +43,8 @@
     CommonStoreTransaction, CommonHomeChild, CommonObjectResource,\
     CommonStubResource
 from txdav.common.icommondatastore import NoSuchObjectResourceError, InternalDataStoreError
-from txdav.datastore.file import hidden, writeOperation
-from txdav.propertystore.base import PropertyName
+from txdav.base.datastore.file import hidden, writeOperation
+from txdav.base.propertystore.base import PropertyName
 
 from twistedcaldav import customxml, carddavxml
 

Modified: CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/sql.py	2010-08-26 00:52:30 UTC (rev 6184)
+++ CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/sql.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -40,7 +40,7 @@
 from txdav.common.datastore.sql_tables import ADDRESSBOOK_TABLE,\
     ADDRESSBOOK_BIND_TABLE, ADDRESSBOOK_OBJECT_REVISIONS_TABLE,\
     ADDRESSBOOK_OBJECT_TABLE
-from txdav.propertystore.base import PropertyName
+from txdav.base.propertystore.base import PropertyName
 
 from zope.interface.declarations import implements
 

Modified: CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/common.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/common.py	2010-08-26 00:52:30 UTC (rev 6184)
+++ CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/common.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -24,7 +24,7 @@
 )
 
 from txdav.idav import IPropertyStore, IDataStore
-from txdav.propertystore.base import PropertyName
+from txdav.base.propertystore.base import PropertyName
 
 from txdav.common.icommondatastore import (
     HomeChildNameAlreadyExistsError, ICommonTransaction

Added: CalendarServer/branches/generic-sqlstore/txdav/base/__init__.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/__init__.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/__init__.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -0,0 +1,19 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Base DAV store.
+"""

Deleted: CalendarServer/branches/generic-sqlstore/txdav/base/datastore/file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/datastore/file.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/datastore/file.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -1,288 +0,0 @@
-# -*- test-case-name: txdav -*-
-##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-
-"""
-Common utility functions for a file based datastore.
-"""
-
-from twext.python.log import LoggingMixIn
-from txdav.idav import IDataStoreResource
-from txdav.idav import AlreadyFinishedError
-from txdav.propertystore.base import PropertyName
-
-from twext.web2.dav.element.rfc2518 import GETContentType
-from twext.web2.dav.resource import TwistedGETContentMD5
-
-
-
-from zope.interface.declarations import implements
-
-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(".")
-
-
-def hidden(path):
-    return path.sibling('.' + path.basename())
-
-
-_unset = object()
-
-class cached(object):
-    """
-    This object is a decorator for a 0-argument method which should be called
-    only once, and its result cached so that future invocations just return the
-    same result without calling the underlying method again.
-
-    @ivar thunk: the function to call to generate a cached value.
-    """
-
-    def __init__(self, thunk):
-        self.thunk = thunk
-
-
-    def __get__(self, oself, owner):
-        def inner():
-            cacheKey = "_" + self.thunk.__name__ + "_cached"
-            cached = getattr(oself, cacheKey, _unset)
-            if cached is _unset:
-                value = self.thunk(oself)
-                setattr(oself, cacheKey, value)
-                return value
-            else:
-                return cached
-        return inner
-
-
-
-def writeOperation(thunk):
-    # FIXME: tests
-    def inner(self, *a, **kw):
-        if self._transaction._termination is not None:
-            raise RuntimeError(
-                "%s.%s is a write operation, but transaction already %s"
-                % (self, thunk.__name__, self._transaction._termination))
-        return thunk(self, *a, **kw)
-    return inner
-
-
-
-class DataStore(LoggingMixIn):
-    """
-    Generic data store.
-    """
-
-    _transactionClass = None    # Derived class must set this
-
-    def __init__(self, path):
-        """
-        Create a calendar store.
-
-        @param path: a L{FilePath} pointing at a directory on disk.
-        """
-        self._path = path
-
-#        if not path.isdir():
-            # FIXME: Add DataStoreNotFoundError?
-#            raise NotFoundError("No such data store")
-
-    def __repr__(self):
-        return "<%s: %s>" % (self.__class__.__name__, self._path.path)
-
-    def newTransaction(self, name='no name'):
-        """
-        Create a new transaction.
-
-        @see Transaction
-        """
-        return self._transactionClass(self)
-
-
-
-class _CommitTracker(object):
-    """
-    Diagnostic tool to find transactions that were never committed.
-    """
-
-    def __init__(self, name):
-        self.name = name
-        self.done = False
-        self.info = []
-
-    def __del__(self):
-        if not self.done and self.info:
-            print '**** UNCOMMITTED TRANSACTION (%s) BEING GARBAGE COLLECTED ****' % (
-                self.name,
-            )
-            for info in self.info:
-                print '   ', info
-            print '---- END OF OPERATIONS'
-
-
-
-class DataStoreTransaction(LoggingMixIn):
-    """
-    In-memory implementation of a data store transaction.
-    """
-
-    def __init__(self, dataStore, name):
-        """
-        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._dataStore = dataStore
-        self._termination = None
-        self._operations = []
-        self._postCommitOperations = []
-        self._tracker = _CommitTracker(name)
-
-
-    def store(self):
-        return self._dataStore
-
-    def addOperation(self, operation, name):
-        self._operations.append(operation)
-        self._tracker.info.append(name)
-
-
-    def _terminate(self, mode):
-        """
-        Check to see if this transaction has already been terminated somehow,
-        either via committing or aborting, and if not, note that it has been
-        terminated.
-
-        @param mode: The manner of the termination of this transaction.
-        
-        @type mode: C{str}
-
-        @raise AlreadyFinishedError: This transaction has already been
-            terminated.
-        """
-        if self._termination is not None:
-            raise AlreadyFinishedError("already %s" % (self._termination,))
-        self._termination = mode
-        self._tracker.done = True
-
-
-    def abort(self):
-        self._terminate("aborted")
-
-
-    def commit(self):
-        self._terminate("committed")
-
-        self.committed = True
-        undos = []
-
-        for operation in self._operations:
-            try:
-                undo = operation()
-                if undo is not None:
-                    undos.append(undo)
-            except:
-                self.log_debug("Undoing DataStoreTransaction")
-                for undo in undos:
-                    try:
-                        undo()
-                    except:
-                        self.log_error("Cannot undo DataStoreTransaction")
-                raise
-
-        for operation in self._postCommitOperations:
-            operation()
-
-    def postCommit(self, operation):
-        self._postCommitOperations.append(operation)
-
-class FileMetaDataMixin(object):
-    
-    implements(IDataStoreResource)
-    
-    def name(self):
-        """
-        Identify the name of the object
-
-        @return: the name of this object.
-        @rtype: C{str}
-        """
-
-        return self._path.basename()
-
-    def contentType(self):
-        """
-        The content type of the object's content.
-
-        @rtype: L{MimeType}
-        """
-        try:
-            return self.properties()[PropertyName.fromElement(GETContentType)].mimeType()
-        except KeyError:
-            return None
-
-    def md5(self):
-        """
-        The MD5 hex digest of this object's content.
-
-        @rtype: C{str}
-        """
-        try:
-            return str(self.properties()[PropertyName.fromElement(TwistedGETContentMD5)])
-        except KeyError:
-            return None
-
-    def size(self):
-        """
-        The octet-size of this object's content.
-
-        @rtype: C{int}
-        """
-        if self._path.exists():
-            return self._path.getsize()
-        else:
-            return 0
-
-    def created(self):
-        """
-        The creation date-time stamp of this object.
-
-        @rtype: C{int}
-        """
-        if self._path.exists():
-            return self._path.getmtime() # No creation time on POSIX
-        else:
-            return None
-
-    def modified(self):
-        """
-        The last modification date-time stamp of this object.
-
-        @rtype: C{int}
-        """
-        if self._path.exists():
-            return self._path.getmtime()
-        else:
-            return None

Copied: CalendarServer/branches/generic-sqlstore/txdav/base/datastore/file.py (from rev 6184, CalendarServer/branches/generic-sqlstore/txdav/datastore/file.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/datastore/file.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/datastore/file.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -0,0 +1,259 @@
+# -*- test-case-name: txdav -*-
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+"""
+Common utility functions for a file based datastore.
+"""
+
+from twext.python.log import LoggingMixIn
+from txdav.idav import IDataStoreResource
+from txdav.idav import AlreadyFinishedError
+from txdav.base.propertystore.base import PropertyName
+
+from twext.web2.dav.element.rfc2518 import GETContentType
+from twext.web2.dav.resource import TwistedGETContentMD5
+
+
+
+from zope.interface.declarations import implements
+
+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(".")
+
+
+def hidden(path):
+    return path.sibling('.' + path.basename())
+
+
+def writeOperation(thunk):
+    # FIXME: tests
+    def inner(self, *a, **kw):
+        if self._transaction._termination is not None:
+            raise RuntimeError(
+                "%s.%s is a write operation, but transaction already %s"
+                % (self, thunk.__name__, self._transaction._termination))
+        return thunk(self, *a, **kw)
+    return inner
+
+
+
+class DataStore(LoggingMixIn):
+    """
+    Generic data store.
+    """
+
+    _transactionClass = None    # Derived class must set this
+
+    def __init__(self, path):
+        """
+        Create a calendar store.
+
+        @param path: a L{FilePath} pointing at a directory on disk.
+        """
+        self._path = path
+
+#        if not path.isdir():
+            # FIXME: Add DataStoreNotFoundError?
+#            raise NotFoundError("No such data store")
+
+    def __repr__(self):
+        return "<%s: %s>" % (self.__class__.__name__, self._path.path)
+
+    def newTransaction(self, name='no name'):
+        """
+        Create a new transaction.
+
+        @see Transaction
+        """
+        return self._transactionClass(self)
+
+
+
+class _CommitTracker(object):
+    """
+    Diagnostic tool to find transactions that were never committed.
+    """
+
+    def __init__(self, name):
+        self.name = name
+        self.done = False
+        self.info = []
+
+    def __del__(self):
+        if not self.done and self.info:
+            print '**** UNCOMMITTED TRANSACTION (%s) BEING GARBAGE COLLECTED ****' % (
+                self.name,
+            )
+            for info in self.info:
+                print '   ', info
+            print '---- END OF OPERATIONS'
+
+
+
+class DataStoreTransaction(LoggingMixIn):
+    """
+    In-memory implementation of a data store transaction.
+    """
+
+    def __init__(self, dataStore, name):
+        """
+        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._dataStore = dataStore
+        self._termination = None
+        self._operations = []
+        self._postCommitOperations = []
+        self._tracker = _CommitTracker(name)
+
+
+    def store(self):
+        return self._dataStore
+
+    def addOperation(self, operation, name):
+        self._operations.append(operation)
+        self._tracker.info.append(name)
+
+
+    def _terminate(self, mode):
+        """
+        Check to see if this transaction has already been terminated somehow,
+        either via committing or aborting, and if not, note that it has been
+        terminated.
+
+        @param mode: The manner of the termination of this transaction.
+        
+        @type mode: C{str}
+
+        @raise AlreadyFinishedError: This transaction has already been
+            terminated.
+        """
+        if self._termination is not None:
+            raise AlreadyFinishedError("already %s" % (self._termination,))
+        self._termination = mode
+        self._tracker.done = True
+
+
+    def abort(self):
+        self._terminate("aborted")
+
+
+    def commit(self):
+        self._terminate("committed")
+
+        self.committed = True
+        undos = []
+
+        for operation in self._operations:
+            try:
+                undo = operation()
+                if undo is not None:
+                    undos.append(undo)
+            except:
+                self.log_debug("Undoing DataStoreTransaction")
+                for undo in undos:
+                    try:
+                        undo()
+                    except:
+                        self.log_error("Cannot undo DataStoreTransaction")
+                raise
+
+        for operation in self._postCommitOperations:
+            operation()
+
+    def postCommit(self, operation):
+        self._postCommitOperations.append(operation)
+
+class FileMetaDataMixin(object):
+    
+    implements(IDataStoreResource)
+    
+    def name(self):
+        """
+        Identify the name of the object
+
+        @return: the name of this object.
+        @rtype: C{str}
+        """
+
+        return self._path.basename()
+
+    def contentType(self):
+        """
+        The content type of the object's content.
+
+        @rtype: L{MimeType}
+        """
+        try:
+            return self.properties()[PropertyName.fromElement(GETContentType)].mimeType()
+        except KeyError:
+            return None
+
+    def md5(self):
+        """
+        The MD5 hex digest of this object's content.
+
+        @rtype: C{str}
+        """
+        try:
+            return str(self.properties()[PropertyName.fromElement(TwistedGETContentMD5)])
+        except KeyError:
+            return None
+
+    def size(self):
+        """
+        The octet-size of this object's content.
+
+        @rtype: C{int}
+        """
+        if self._path.exists():
+            return self._path.getsize()
+        else:
+            return 0
+
+    def created(self):
+        """
+        The creation date-time stamp of this object.
+
+        @rtype: C{int}
+        """
+        if self._path.exists():
+            return self._path.getmtime() # No creation time on POSIX
+        else:
+            return None
+
+    def modified(self):
+        """
+        The last modification date-time stamp of this object.
+
+        @rtype: C{int}
+        """
+        if self._path.exists():
+            return self._path.getmtime()
+        else:
+            return None

Copied: CalendarServer/branches/generic-sqlstore/txdav/base/datastore/sql.py (from rev 6168, CalendarServer/branches/generic-sqlstore/txdav/datastore/sql.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/datastore/sql.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/datastore/sql.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -0,0 +1,85 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Logic common to SQL implementations.
+"""
+
+from inspect import getargspec
+
+def _getarg(argname, argspec, args, kw):
+    """
+    Get an argument from some arguments.
+
+    @param argname: The name of the argument to retrieve.
+
+    @param argspec: The result of L{inspect.getargspec}.
+
+    @param args: positional arguments passed to the function specified by
+        argspec.
+
+    @param kw: keyword arguments passed to the function specified by
+        argspec.
+
+    @return: The value of the argument named by 'argname'.
+    """
+    argnames = argspec[0]
+    try:
+        argpos = argnames.index(argname)
+    except ValueError:
+        argpos = None
+    if argpos is not None:
+        if len(args) > argpos:
+            return args[argpos]
+    if argname in kw:
+        return kw[argname]
+    else:
+        raise TypeError("could not find key argument %r in %r/%r (%r)" %
+            (argname, args, kw, argpos)
+        )
+
+
+
+def memoized(keyArgument, memoAttribute):
+    """
+    Decorator which memoizes the result of a method on that method's instance.
+
+    @param keyArgument: The name of the 'key' argument.
+
+    @type keyArgument: C{str}
+
+    @param memoAttribute: The name of the attribute on the instance which
+        should be used for memoizing the result of this method; the attribute
+        itself must be a dictionary.
+
+    @type memoAttribute: C{str}
+    """
+    def decorate(thunk):
+        spec = getargspec(thunk)
+        def outer(*a, **kw):
+            self = a[0]
+            memo = getattr(self, memoAttribute)
+            key = _getarg(keyArgument, spec, a, kw)
+            if key in memo:
+                return memo[key]
+            result = thunk(*a, **kw)
+            if result is not None:
+                memo[key] = result
+            return result
+        return outer
+    return decorate
+
+

Modified: CalendarServer/branches/generic-sqlstore/txdav/base/datastore/subpostgres.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/datastore/subpostgres.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/datastore/subpostgres.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txdav.datastore.test.test_subpostgres -*-
+# -*- test-case-name: txdav.base.datastore.test.test_subpostgres -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #

Modified: CalendarServer/branches/generic-sqlstore/txdav/base/datastore/test/test_subpostgres.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/datastore/test/test_subpostgres.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/datastore/test/test_subpostgres.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -15,14 +15,14 @@
 ##
 
 """
-Tests for txdav.datastore.subpostgres.
+Tests for txdav.base.datastore.subpostgres.
 """
 
 from twisted.trial.unittest import TestCase
 
 from twext.python.filepath import CachingFilePath
 
-from txdav.datastore.subpostgres import PostgresService
+from txdav.base.datastore.subpostgres import PostgresService
 from twisted.internet.defer import inlineCallbacks, Deferred
 from twisted.application.service import Service
 

Copied: CalendarServer/branches/generic-sqlstore/txdav/base/datastore/util.py (from rev 6184, CalendarServer/branches/generic-sqlstore/txdav/datastore/util.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/datastore/util.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/datastore/util.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -0,0 +1,47 @@
+# -*- test-case-name: txcaldav.calendarstore.test.test_file -*-
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Common utility functions for a datastores.
+"""
+
+_unset = object()
+
+class cached(object):
+    """
+    This object is a decorator for a 0-argument method which should be called
+    only once, and its result cached so that future invocations just return the
+    same result without calling the underlying method again.
+
+    @ivar thunk: the function to call to generate a cached value.
+    """
+
+    def __init__(self, thunk):
+        self.thunk = thunk
+
+
+    def __get__(self, oself, owner):
+        def inner():
+            cacheKey = "_" + self.thunk.__name__ + "_cached"
+            cached = getattr(oself, cacheKey, _unset)
+            if cached is _unset:
+                value = self.thunk(oself)
+                setattr(oself, cacheKey, value)
+                return value
+            else:
+                return cached
+        return inner

Modified: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/none.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/propertystore/none.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/none.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -22,7 +22,7 @@
     "PropertyStore",
 ]
 
-from txdav.propertystore.base import AbstractPropertyStore, validKey
+from txdav.base.propertystore.base import AbstractPropertyStore, validKey
 
 class PropertyStore(AbstractPropertyStore):
     """

Copied: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/sql.py (from rev 6169, CalendarServer/branches/generic-sqlstore/txdav/propertystore/sql.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/sql.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/sql.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -0,0 +1,81 @@
+# -*- test-case-name: txcaldav.calendarstore.test.test_postgres -*-
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+PostgreSQL data store.
+"""
+
+__all__ = [
+    "PropertyStore",
+]
+
+from txdav.base.propertystore.base import AbstractPropertyStore, PropertyName,\
+    validKey
+
+from twext.web2.dav.davxml import WebDAVDocument
+
+class PropertyStore(AbstractPropertyStore):
+
+    def __init__(self, defaultuser, txn, resourceID):
+        super(PropertyStore, self).__init__(defaultuser)
+        self._txn = txn
+        self._resourceID = resourceID
+
+
+    def _getitem_uid(self, key, uid):
+        validKey(key)
+        rows = self._txn.execSQL(
+            "select VALUE from RESOURCE_PROPERTY where "
+            "RESOURCE_ID = %s and NAME = %s and VIEWER_UID = %s",
+            [self._resourceID, key.toString(), uid]
+        )
+        if not rows:
+            raise KeyError(key)
+        return WebDAVDocument.fromString(rows[0][0]).root_element
+
+
+    def _setitem_uid(self, key, value, uid):
+        validKey(key)
+        try:
+            self._delitem_uid(key, uid)
+        except KeyError:
+            pass
+        self._txn.execSQL(
+            "insert into RESOURCE_PROPERTY "
+            "(RESOURCE_ID, NAME, VALUE, VIEWER_UID) values (%s, %s, %s, %s)",
+            [self._resourceID, key.toString(), value.toxml(), uid]
+        )
+
+
+    def _delitem_uid(self, key, uid):
+        validKey(key)
+        self._txn.execSQL(
+            "delete from RESOURCE_PROPERTY where VIEWER_UID = %s"
+            "and RESOURCE_ID = %s AND NAME = %s",
+            [uid, self._resourceID, key.toString()],
+            raiseOnZeroRowCount=lambda:KeyError(key)
+        )
+            
+
+    def _keys_uid(self, uid):
+        rows = self._txn.execSQL(
+            "select NAME from RESOURCE_PROPERTY where "
+            "VIEWER_UID = %s and RESOURCE_ID = %s",
+            [uid, self._resourceID]
+        )
+        for row in rows:
+            yield PropertyName.fromString(row[0])

Deleted: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/base.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/base.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/base.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -1,264 +0,0 @@
-##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-Generic property store tests.
-"""
-
-__all__ = [
-    "PropertyStoreTest",
-    "propertyName",
-    "propertyValue",
-]
-
-
-from zope.interface.verify import verifyObject, BrokenMethodImplementation
-
-from twisted.trial import unittest
-
-from twext.web2.dav import davxml
-
-from txdav.idav import IPropertyStore
-from txdav.propertystore.base import PropertyName
-
-
-class PropertyStoreTest(unittest.TestCase):
-    # Subclass must define self.propertyStore in setUp().
-
-    def test_interface(self):
-        try:
-            verifyObject(IPropertyStore, self.propertyStore)
-        except BrokenMethodImplementation, e:
-            self.fail(e)
-
-    def test_set_get_contains(self):
-        store = self.propertyStore
-
-        name = propertyName("test")
-        value = propertyValue("Hello, World!")
-
-        store[name] = value
-        self.assertEquals(store.get(name, None), value)
-        self.failUnless(name in store)
-
-    def test_delete_get_contains(self):
-        store = self.propertyStore
-
-        name = propertyName("test")
-        value = propertyValue("Hello, World!")
-
-        store[name] = value
-        del store[name]
-        self.assertEquals(store.get(name, None), None)
-        self.failIf(name in store)
-
-    def test_peruser(self):
-        store1 = self.propertyStore1
-        store2 = self.propertyStore2
-
-        name = propertyName("test")
-        value1 = propertyValue("Hello, World1!")
-        value2 = propertyValue("Hello, World2!")
-
-        store1[name] = value1
-        store1.flush()
-        self.assertEquals(store1.get(name, None), value1)
-        self.assertEquals(store2.get(name, None), None)
-        self.failUnless(name in store1)
-        self.failIf(name in store2)
-        
-        store2[name] = value2
-        store2.flush()
-        self.assertEquals(store1.get(name, None), value1)
-        self.assertEquals(store2.get(name, None), value2)
-        self.failUnless(name in store1)
-        self.failUnless(name in store2)
-        
-        del store2[name]
-        store2.flush()
-        self.assertEquals(store1.get(name, None), value1)
-        self.assertEquals(store2.get(name, None), None)
-        self.failUnless(name in store1)
-        self.failIf(name in store2)
-        
-        del store1[name]
-        store1.flush()
-        self.assertEquals(store1.get(name, None), None)
-        self.assertEquals(store2.get(name, None), None)
-        self.failIf(name in store1)
-        self.failIf(name in store2)
-        
-    def test_peruser_shadow(self):
-        store1 = self.propertyStore1
-        store2 = self.propertyStore2
-
-        name = propertyName("shadow")
-
-        store1.setSpecialProperties((name,), ())
-        store2.setSpecialProperties((name,), ())
-
-        value1 = propertyValue("Hello, World1!")
-        value2 = propertyValue("Hello, World2!")
-
-        store1[name] = value1
-        store1.flush()
-        self.assertEquals(store1.get(name, None), value1)
-        self.assertEquals(store2.get(name, None), value1)
-        self.failUnless(name in store1)
-        self.failUnless(name in store2)
-        
-        store2[name] = value2
-        store2.flush()
-        self.assertEquals(store1.get(name, None), value1)
-        self.assertEquals(store2.get(name, None), value2)
-        self.failUnless(name in store1)
-        self.failUnless(name in store2)
-        
-        del store2[name]
-        store2.flush()
-        self.assertEquals(store1.get(name, None), value1)
-        self.assertEquals(store2.get(name, None), value1)
-        self.failUnless(name in store1)
-        self.failUnless(name in store2)
-        
-        del store1[name]
-        store1.flush()
-        self.assertEquals(store1.get(name, None), None)
-        self.assertEquals(store2.get(name, None), None)
-        self.failIf(name in store1)
-        self.failIf(name in store2)
-        
-
-    def test_peruser_global(self):
-        store1 = self.propertyStore1
-        store2 = self.propertyStore2
-
-        name = propertyName("global")
-
-        store1.setSpecialProperties((), (name,))
-        store2.setSpecialProperties((), (name,))
-
-        value1 = propertyValue("Hello, World1!")
-        value2 = propertyValue("Hello, World2!")
-
-        store1[name] = value1
-        store1.flush()
-        self.assertEquals(store1.get(name, None), value1)
-        self.assertEquals(store2.get(name, None), value1)
-        self.failUnless(name in store1)
-        self.failUnless(name in store2)
-        
-        store2[name] = value2
-        store2.flush()
-        self.assertEquals(store1.get(name, None), value2)
-        self.assertEquals(store2.get(name, None), value2)
-        self.failUnless(name in store1)
-        self.failUnless(name in store2)
-        
-        del store2[name]
-        store2.flush()
-        self.assertEquals(store1.get(name, None), None)
-        self.assertEquals(store2.get(name, None), None)
-        self.failIf(name in store1)
-        self.failIf(name in store2)
-        
-
-    def test_iteration(self):
-        store = self.propertyStore
-
-        value = propertyValue("Hello, World!")
-
-        names = set(propertyName(str(i)) for i in (1,2,3,4))
-
-        for name in names:
-            store[name] = value
-
-        self.assertEquals(set(store.keys()), names)
-        self.assertEquals(len(store), len(names))
-
-    def test_delete_none(self):
-        def doDelete():
-            del self.propertyStore[propertyName("xyzzy")]
-
-        self.assertRaises(KeyError, doDelete)
-
-    def test_keyInPropertyName(self):
-        store = self.propertyStore
-
-        def doGet():
-            store["xyzzy"]
-
-        def doSet():
-            store["xyzzy"] = propertyValue("Hello, World!")
-
-        def doDelete():
-            del store["xyzzy"]
-
-        def doContains():
-            "xyzzy" in store
-
-        self.assertRaises(TypeError, doGet)
-        self.assertRaises(TypeError, doSet)
-        self.assertRaises(TypeError, doDelete)
-        self.assertRaises(TypeError, doContains)
-
-    def test_flush(self):
-        store = self.propertyStore
-
-        name = propertyName("test")
-        value = propertyValue("Hello, World!")
-
-        #
-        # Set value flushes correctly
-        #
-        store[name] = value
-
-        store.flush()
-        store.abort()
-
-        self.assertEquals(store.get(name, None), value)
-        self.assertEquals(len(store), 1)
-
-        #
-        # Deleted value flushes correctly
-        #
-        del store[name]
-
-        store.flush()
-        store.abort()
-
-        self.assertEquals(store.get(name, None), None)
-        self.assertEquals(len(store), 0)
-
-    def test_abort(self):
-        store = self.propertyStore
-
-        name = propertyName("test")
-        value = propertyValue("Hello, World!")
-
-        store[name] = value
-
-        store.abort()
-
-        self.assertEquals(store.get(name, None), None)
-        self.assertEquals(len(store), 0)
-
-
-def propertyName(name):
-    return PropertyName("http://calendarserver.org/ns/test/", name)
-
-def propertyValue(value):
-    return davxml.ResponseDescription(value)

Copied: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/base.py (from rev 6169, CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/base.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/base.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/base.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -0,0 +1,319 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Generic property store tests.
+"""
+
+__all__ = [
+    "PropertyStoreTest",
+    "propertyName",
+    "propertyValue",
+]
+
+
+from zope.interface.verify import verifyObject, BrokenMethodImplementation
+
+from twisted.trial import unittest
+
+from twext.web2.dav import davxml
+
+from txdav.idav import IPropertyStore
+from txdav.base.propertystore.base import PropertyName
+
+
+class PropertyStoreTest(unittest.TestCase):
+    # Subclass must define self.propertyStore in setUp().
+
+    def _preTest(self):
+        self.addCleanup(self._postTest)
+    def _postTest(self):
+        pass
+    def _changed(self, store):
+        store.flush()
+    def _abort(self, store):
+        store.abort()
+
+    def test_interface(self):
+        try:
+            self._preTest()
+            verifyObject(IPropertyStore, self.propertyStore)
+        except BrokenMethodImplementation, e:
+            self.fail(e)
+
+    def test_set_get_contains(self):
+        
+        self._preTest()
+        store = self.propertyStore
+
+        name = propertyName("test")
+        value = propertyValue("Hello, World!")
+
+        # Test with commit after change
+        store[name] = value
+        self._changed(store)
+        self.assertEquals(store.get(name, None), value)
+        self.failUnless(name in store)
+
+        # Test without commit after change
+        value = propertyValue("Hello, Universe!")
+        store[name] = value
+        self.assertEquals(store.get(name, None), value)
+        self.failUnless(name in store)
+
+    def test_delete_get_contains(self):
+
+        self._preTest()
+        store = self.propertyStore
+
+        # Test with commit after change
+        name = propertyName("test")
+        value = propertyValue("Hello, World!")
+
+        store[name] = value
+        self._changed(store)
+
+        del store[name]
+        self._changed(store)
+
+        self.assertEquals(store.get(name, None), None)
+        self.failIf(name in store)
+
+        # Test without commit after change
+        name = propertyName("test")
+        value = propertyValue("Hello, Universe!")
+
+        store[name] = value
+        self._changed(store)
+
+        del store[name]
+
+        self.assertEquals(store.get(name, None), None)
+        self.failIf(name in store)
+
+    def test_peruser(self):
+
+        self._preTest()
+        store1 = self.propertyStore1
+        store2 = self.propertyStore2
+
+        name = propertyName("test")
+        value1 = propertyValue("Hello, World1!")
+        value2 = propertyValue("Hello, World2!")
+
+        store1[name] = value1
+        self._changed(store1)
+        self.assertEquals(store1.get(name, None), value1)
+        self.assertEquals(store2.get(name, None), None)
+        self.failUnless(name in store1)
+        self.failIf(name in store2)
+        
+        store2[name] = value2
+        self._changed(store2)
+        self.assertEquals(store1.get(name, None), value1)
+        self.assertEquals(store2.get(name, None), value2)
+        self.failUnless(name in store1)
+        self.failUnless(name in store2)
+        
+        del store2[name]
+        self._changed(store2)
+        self.assertEquals(store1.get(name, None), value1)
+        self.assertEquals(store2.get(name, None), None)
+        self.failUnless(name in store1)
+        self.failIf(name in store2)
+        
+        del store1[name]
+        self._changed(store1)
+        self.assertEquals(store1.get(name, None), None)
+        self.assertEquals(store2.get(name, None), None)
+        self.failIf(name in store1)
+        self.failIf(name in store2)
+        
+    def test_peruser_shadow(self):
+
+        self._preTest()
+        store1 = self.propertyStore1
+        store2 = self.propertyStore2
+
+        name = propertyName("shadow")
+
+        store1.setSpecialProperties((name,), ())
+        store2.setSpecialProperties((name,), ())
+
+        value1 = propertyValue("Hello, World1!")
+        value2 = propertyValue("Hello, World2!")
+
+        store1[name] = value1
+        self._changed(store1)
+        self.assertEquals(store1.get(name, None), value1)
+        self.assertEquals(store2.get(name, None), value1)
+        self.failUnless(name in store1)
+        self.failUnless(name in store2)
+        
+        store2[name] = value2
+        self._changed(store2)
+        self.assertEquals(store1.get(name, None), value1)
+        self.assertEquals(store2.get(name, None), value2)
+        self.failUnless(name in store1)
+        self.failUnless(name in store2)
+        
+        del store2[name]
+        self._changed(store2)
+        self.assertEquals(store1.get(name, None), value1)
+        self.assertEquals(store2.get(name, None), value1)
+        self.failUnless(name in store1)
+        self.failUnless(name in store2)
+        
+        del store1[name]
+        self._changed(store1)
+        self.assertEquals(store1.get(name, None), None)
+        self.assertEquals(store2.get(name, None), None)
+        self.failIf(name in store1)
+        self.failIf(name in store2)
+
+
+    def test_peruser_global(self):
+
+        self._preTest()
+        store1 = self.propertyStore1
+        store2 = self.propertyStore2
+
+        name = propertyName("global")
+
+        store1.setSpecialProperties((), (name,))
+        store2.setSpecialProperties((), (name,))
+
+        value1 = propertyValue("Hello, World1!")
+        value2 = propertyValue("Hello, World2!")
+
+        store1[name] = value1
+        self._changed(store1)
+        self.assertEquals(store1.get(name, None), value1)
+        self.assertEquals(store2.get(name, None), value1)
+        self.failUnless(name in store1)
+        self.failUnless(name in store2)
+        
+        store2[name] = value2
+        self._changed(store2)
+        self.assertEquals(store1.get(name, None), value2)
+        self.assertEquals(store2.get(name, None), value2)
+        self.failUnless(name in store1)
+        self.failUnless(name in store2)
+        
+        del store2[name]
+        self._changed(store2)
+        self.assertEquals(store1.get(name, None), None)
+        self.assertEquals(store2.get(name, None), None)
+        self.failIf(name in store1)
+        self.failIf(name in store2)
+        
+
+    def test_iteration(self):
+
+        self._preTest()
+        store = self.propertyStore
+
+        value = propertyValue("Hello, World!")
+
+        names = set(propertyName(str(i)) for i in (1,2,3,4))
+
+        for name in names:
+            store[name] = value
+
+        self.assertEquals(set(store.keys()), names)
+        self.assertEquals(len(store), len(names))
+
+    def test_delete_none(self):
+
+        self._preTest()
+        def doDelete():
+            del self.propertyStore[propertyName("xyzzy")]
+
+        self.assertRaises(KeyError, doDelete)
+
+    def test_keyInPropertyName(self):
+
+        self._preTest()
+        store = self.propertyStore
+
+        def doGet():
+            store["xyzzy"]
+
+        def doSet():
+            store["xyzzy"] = propertyValue("Hello, World!")
+
+        def doDelete():
+            del store["xyzzy"]
+
+        def doContains():
+            return "xyzzy" in store
+
+        self.assertRaises(TypeError, doGet)
+        self.assertRaises(TypeError, doSet)
+        self.assertRaises(TypeError, doDelete)
+        self.assertRaises(TypeError, doContains)
+
+    def test_flush(self):
+
+        self._preTest()
+        store = self.propertyStore
+
+        name = propertyName("test")
+        value = propertyValue("Hello, World!")
+
+        #
+        # Set value flushes correctly
+        #
+        store[name] = value
+
+        self._changed(store)
+        self._abort(store)
+
+        self.assertEquals(store.get(name, None), value)
+        self.assertEquals(len(store), 1)
+
+        #
+        # Deleted value flushes correctly
+        #
+        del store[name]
+
+        self._changed(store)
+        self._abort(store)
+
+        self.assertEquals(store.get(name, None), None)
+        self.assertEquals(len(store), 0)
+
+    def test_abort(self):
+
+        self._preTest()
+        store = self.propertyStore
+
+        name = propertyName("test")
+        value = propertyValue("Hello, World!")
+
+        store[name] = value
+
+        self._abort(store)
+
+        self.assertEquals(store.get(name, None), None)
+        self.assertEquals(len(store), 0)
+
+
+def propertyName(name):
+    return PropertyName("http://calendarserver.org/ns/test/", name)
+
+def propertyValue(value):
+    return davxml.ResponseDescription(value)

Modified: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_base.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/test_base.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_base.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -23,7 +23,7 @@
 from twisted.trial import unittest
 
 from txdav.idav import IPropertyName
-from txdav.propertystore.base import PropertyName
+from txdav.base.propertystore.base import PropertyName
 
 
 class PropertyNameTest(unittest.TestCase):

Modified: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_none.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/test_none.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_none.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -18,9 +18,9 @@
 Property store tests.
 """
 
-from txdav.propertystore.none import PropertyStore
+from txdav.base.propertystore.none import PropertyStore
 
-from txdav.propertystore.test import base
+from txdav.base.propertystore.test import base
 
 class PropertyStoreTest(base.PropertyStoreTest):
     def setUp(self):

Copied: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_sql.py (from rev 6169, CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/test_sql.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_sql.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_sql.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -0,0 +1,190 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Tests for txcaldav.calendarstore.postgres, mostly based on
+L{txcaldav.calendarstore.test.common}.
+"""
+
+
+from twext.python.filepath import CachingFilePath
+
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred, inlineCallbacks, succeed
+from twisted.internet.task import deferLater
+from twisted.python import log
+
+from txdav.common.datastore.sql import v1_schema
+from txcaldav.calendarstore.test.common import StubNotifierFactory
+
+from txdav.common.datastore.sql import CommonDataStore
+from txdav.base.datastore.subpostgres import PostgresService
+from txdav.base.propertystore.base import PropertyName
+from txdav.base.propertystore.test import base
+
+try:
+    from txdav.base.propertystore.sql import PropertyStore
+except ImportError, e:
+    PropertyStore = None
+    importErrorMessage = str(e)
+
+
+
+class StoreBuilder(object):
+    """
+    Test-fixture-builder which can construct a PostgresStore.
+    """
+    sharedService = None
+    currentTestID = None
+
+    SHARED_DB_PATH = "../_test_postgres_db"
+
+    def buildStore(self, testCase, notifierFactory):
+        """
+        Do the necessary work to build a store for a particular test case.
+
+        @return: a L{Deferred} which fires with an L{IDataStore}.
+        """
+        currentTestID = testCase.id()
+        dbRoot = CachingFilePath(self.SHARED_DB_PATH)
+        if self.sharedService is None:
+            ready = Deferred()
+            def getReady(connectionFactory):
+                attachmentRoot = dbRoot.child("attachments")
+                try:
+                    attachmentRoot.createDirectory()
+                except OSError:
+                    pass
+                try:
+                    self.store = CommonDataStore(
+                        lambda label=None: connectionFactory(
+                            label or currentTestID
+                        ),
+                        notifierFactory,
+                        attachmentRoot
+                    )
+                except:
+                    ready.errback()
+                    raise
+                else:
+                    self.cleanDatabase(testCase)
+                    ready.callback(self.store)
+                return self.store
+            self.sharedService = PostgresService(
+                dbRoot, getReady, v1_schema, "caldav", resetSchema=True,
+                testMode=True
+            )
+            self.sharedService.startService()
+            def startStopping():
+                log.msg("Starting stopping.")
+                self.sharedService.unpauseMonitor()
+                return self.sharedService.stopService()
+            reactor.addSystemEventTrigger(#@UndefinedVariable
+                "before", "shutdown", startStopping)
+            result = ready
+        else:
+            self.store.notifierFactory = notifierFactory
+            self.cleanDatabase(testCase)
+            result = succeed(self.store)
+
+        def cleanUp():
+            # FIXME: clean up any leaked connections and report them with an
+            # immediate test failure.
+            def stopit():
+                self.sharedService.pauseMonitor()
+            return deferLater(reactor, 0.1, stopit)
+        testCase.addCleanup(cleanUp)
+        return result
+
+
+    def cleanDatabase(self, testCase):
+        cleanupConn = self.store.connectionFactory(
+            "%s schema-cleanup" % (testCase.id(),)
+        )
+        cursor = cleanupConn.cursor()
+        tables = ['INVITE',
+                  'RESOURCE_PROPERTY',
+                  'ATTACHMENT',
+                  'ADDRESSBOOK_OBJECT',
+                  'CALENDAR_OBJECT',
+                  'CALENDAR_BIND',
+                  'ADDRESSBOOK_BIND',
+                  'CALENDAR',
+                  'ADDRESSBOOK',
+                  'CALENDAR_HOME',
+                  'ADDRESSBOOK_HOME',
+                  'NOTIFICATION',
+                  'NOTIFICATION_HOME']
+        for table in tables:
+            try:
+                cursor.execute("delete from "+table)
+            except:
+                log.err()
+        cleanupConn.commit()
+        cleanupConn.close()
+
+
+
+theStoreBuilder = StoreBuilder()
+buildStore = theStoreBuilder.buildStore
+
+
+class PropertyStoreTest(base.PropertyStoreTest):
+
+    def _preTest(self):
+        self._txn = self.store.newTransaction()
+        self.propertyStore = self.propertyStore1 = PropertyStore(
+            "user01", self._txn, 1
+        )
+        self.propertyStore2 = PropertyStore("user01", self._txn, 1)
+        self.propertyStore2._setPerUserUID("user02")
+        
+        self.addCleanup(self._postTest)
+
+    def _postTest(self):
+        if hasattr(self, "_txn"):
+            self._txn.commit()
+            delattr(self, "_txn")
+        self.propertyStore = self.propertyStore1 = self.propertyStore2 = None
+
+    def _changed(self, store):
+        if hasattr(self, "_txn"):
+            self._txn.commit()
+            delattr(self, "_txn")
+        self._txn = self.store.newTransaction()
+        self.propertyStore1._txn = self._txn
+        self.propertyStore2._txn = self._txn
+
+    def _abort(self, store):
+        if hasattr(self, "_txn"):
+            self._txn.abort()
+            delattr(self, "_txn")
+
+        self._txn = self.store.newTransaction()
+        self.propertyStore1._txn = self._txn
+        self.propertyStore2._txn = self._txn
+
+    @inlineCallbacks
+    def setUp(self):
+        self.notifierFactory = StubNotifierFactory()
+        self.store = yield buildStore(self, self.notifierFactory)
+
+if PropertyStore is None:
+    PropertyStoreTest.skip = importErrorMessage
+
+
+def propertyName(name):
+    return PropertyName("http://calendarserver.org/ns/test/", name)

Modified: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_xattr.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/test_xattr.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_xattr.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -21,12 +21,12 @@
 
 from twext.python.filepath import CachingFilePath as FilePath
 
-from txdav.propertystore.base import PropertyName
+from txdav.base.propertystore.base import PropertyName
 
-from txdav.propertystore.test import base
+from txdav.base.propertystore.test import base
 
 try:
-    from txdav.propertystore.xattr import PropertyStore
+    from txdav.base.propertystore.xattr import PropertyStore
     from xattr import xattr
 except ImportError, e:
     PropertyStore = None

Modified: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/xattr.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/propertystore/xattr.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/xattr.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txdav.propertystore.test.test_xattr,txcaldav.calendarstore,txcarddav.addressbookstore -*-
+# -*- test-case-name: txdav.base.propertystore.test.test_xattr,txcaldav.calendarstore,txcarddav.addressbookstore -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #
@@ -34,7 +34,7 @@
 
 from twext.web2.dav.davxml import WebDAVDocument
 
-from txdav.propertystore.base import AbstractPropertyStore, PropertyName, validKey
+from txdav.base.propertystore.base import AbstractPropertyStore, PropertyName, validKey
 from txdav.idav import PropertyStoreError
 
 

Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py	2010-08-26 00:52:30 UTC (rev 6184)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -38,12 +38,12 @@
     ObjectResourceNameAlreadyExistsError, NoSuchObjectResourceError
 from txdav.common.inotifications import INotificationCollection, \
     INotificationObject
-from txdav.datastore.file import DataStoreTransaction, DataStore, writeOperation, \
+from txdav.base.datastore.file import DataStoreTransaction, DataStore, writeOperation, \
     hidden, isValidName, FileMetaDataMixin
-from txdav.datastore.util import cached
+from txdav.base.datastore.util import cached
 from txdav.idav import IDataStore
-from txdav.propertystore.base import PropertyName
-from txdav.propertystore.xattr import PropertyStore
+from txdav.base.propertystore.base import PropertyName
+from txdav.base.propertystore.xattr import PropertyStore
 
 from errno import EEXIST, ENOENT
 from zope.interface import implements, directlyProvides

Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py	2010-08-26 00:52:30 UTC (rev 6184)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -49,11 +49,11 @@
     NoSuchObjectResourceError
 from txdav.common.inotifications import INotificationCollection,\
     INotificationObject
-from txdav.datastore.sql import memoized
-from txdav.datastore.util import cached
+from txdav.base.datastore.sql import memoized
+from txdav.base.datastore.util import cached
 from txdav.idav import IDataStore, AlreadyFinishedError
-from txdav.propertystore.base import PropertyName
-from txdav.propertystore.sql import PropertyStore
+from txdav.base.propertystore.base import PropertyName
+from txdav.base.propertystore.sql import PropertyStore
 
 from zope.interface.declarations import implements, directlyProvides
 

Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/util.py	2010-08-26 00:52:30 UTC (rev 6184)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/util.py	2010-08-26 01:11:33 UTC (rev 6185)
@@ -29,7 +29,7 @@
 from twisted.python import log
 
 from txdav.common.datastore.sql import CommonDataStore, v1_schema
-from txdav.datastore.subpostgres import PostgresService,\
+from txdav.base.datastore.subpostgres import PostgresService,\
     DiagnosticConnectionWrapper
 
 def allInstancesOf(cls):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100825/d8002034/attachment-0001.html>


More information about the calendarserver-changes mailing list