[CalendarServer-changes] [5994] CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore

source_changes at macosforge.org source_changes at macosforge.org
Fri Aug 6 06:37:35 PDT 2010


Revision: 5994
          http://trac.macosforge.org/projects/calendarserver/changeset/5994
Author:   glyph at apple.com
Date:     2010-08-06 06:37:33 -0700 (Fri, 06 Aug 2010)
Log Message:
-----------
All notification and attachment tests passing.

Modified Paths:
--------------
    CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres.py
    CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres_schema_v1.sql
    CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/common.py
    CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/test_postgres.py

Modified: CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres.py	2010-08-06 03:33:00 UTC (rev 5993)
+++ CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres.py	2010-08-06 13:37:33 UTC (rev 5994)
@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-from twisted.internet.interfaces import ITransport
+from twisted.python import hashlib
 
 """
 PostgreSQL data store.
@@ -32,7 +32,12 @@
 
 from twisted.python.modules import getModule
 from twisted.application.service import Service
+from twisted.internet.interfaces import ITransport
+from twisted.internet.error import ConnectionLost
+from twisted.python.failure import Failure
 
+from twext.web2.dav.element.rfc2518 import ResourceType
+
 from txdav.idav import IDataStore, AlreadyFinishedError
 
 from txdav.common.icommondatastore import (
@@ -244,24 +249,64 @@
             "where RESOURCE_ID = %s", [calendarText, self._resourceID]
         )
         self._calendarText = calendarText
+        self._calendar._home._txn.postCommit(self._calendar._notifier.notify)
 
 
+    def _attachmentPath(self, name):
+        return self._calendar._home._txn._store.attachmentsPath.child(
+            "%s-%s-%s-%s.attachment" % (
+                self._calendar._home.uid(), self._calendar.name(),
+                self.name(), name
+            )
+        )
+
+
     def createAttachmentWithName(self, name, contentType):
-        attachment = PostgresAttachment(self, name)
+        path = self._attachmentPath(name)
+        c = self._calendar._cursor()
+        attachment = PostgresAttachment(self, path)
+        c.execute("""
+            insert into ATTACHMENT (CALENDAR_OBJECT_RESOURCE_ID, CONTENT_TYPE,
+            SIZE, MD5, PATH)
+            values (%s, %s, %s, %s, %s)
+            """,
+            [
+                self._resourceID, str(contentType), 0, "",
+                attachment._pathValue()
+            ]
+        )
         return attachment.store(contentType)
 
 
     def attachments(self):
-        return []
+        c = self._calendar._cursor()
+        c.execute("""
+        select PATH from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s 
+        """, [self._resourceID])
+        rows = c.fetchall()
+        for row in rows:
+            demangledName = _pathToName(row[0])
+            yield self.attachmentWithName(demangledName)
 
 
     def attachmentWithName(self, name):
-        return None
+        attachment = PostgresAttachment(self, self._attachmentPath(name))
+        if attachment._populate():
+            return attachment
+        else:
+            return None
 
 
     def removeAttachmentWithName(self, name):
-        pass
+        attachment = PostgresAttachment(self, self._attachmentPath(name))
+        self._calendar._home._txn.postCommit(attachment._path.remove)
+        c = self._calendar._cursor()
+        c.execute("""
+        delete from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s AND
+        PATH = %s
+        """, [self._resourceID, attachment._pathValue()])
 
+
     # IDataStoreResource
     def contentType(self):
         """
@@ -286,34 +331,108 @@
         return None
 
 
+def _pathToName(path):
+    return path.rsplit(".", 1)[0].split("-", 3)[-1]
 
 class PostgresAttachment(object):
-    
+
     implements(IAttachment)
 
-    def __init__(self, calendarObject, name):
+    def __init__(self, calendarObject, path):
         self._calendarObject = calendarObject
-        self._name = name
+        self._path = path
 
 
+    def _populate(self):
+        """
+        Execute necessary SQL queries to retrieve attributes.
+
+        @return: C{True} if this attachment exists, C{False} otherwise.
+        """
+        c = self._calendarObject._calendar._cursor()
+        c.execute(
+            """
+            select CONTENT_TYPE, MD5 from ATTACHMENT where PATH = %s
+            """, [self._pathValue()])
+        rows = c.fetchall()
+        if not rows:
+            return False
+        self._contentType = MimeType.fromString(rows[0][0])
+        self._md5 = rows[0][1]
+        return True
+
+
     def store(self, contentType):
         return PostgresAttachmentStorageTransport(self, contentType)
 
 
+    def retrieve(self, protocol):
+        protocol.dataReceived(self._path.getContent())
+        protocol.connectionLost(Failure(ConnectionLost()))
 
+
+    def properties(self):
+        pass # stub
+
+
+    # IDataStoreResource
+    def contentType(self):
+        return self._contentType
+
+
+    def md5(self):
+        return self._md5
+
+
+    def size(self):
+        return 0
+
+
+    def created(self):
+        return None
+
+
+    def modified(self):
+        return None
+
+
+    def name(self):
+        return _pathToName(self._pathValue())
+
+
+    def _pathValue(self):
+        """
+        Compute the value which should go into the 'path' column for this
+        attachment.
+        """
+        root = self._calendarObject._calendar._home._txn._store.attachmentsPath
+        return '/'.join(self._path.segmentsFrom(root))
+
+
+
 class PostgresAttachmentStorageTransport(object):
 
     implements(ITransport)
-    
+
     def __init__(self, attachment, contentType):
         self.attachment = attachment
         self.contentType = contentType
+        self.buf = ''
+        self.hash = hashlib.md5()
 
     def write(self, data):
-        pass
+        self.buf += data
+        self.hash.update(data)
 
     def loseConnection(self):
-        pass
+        self.attachment._path.setContent(self.buf)
+        pathValue = self.attachment._pathValue()
+        c = self.attachment._calendarObject._calendar._home._txn._cursor
+        contentTypeString = '%s/%s' % (self.contentType.mediaType,
+                                       self.contentType.mediaSubtype)
+        c.execute("update ATTACHMENT set CONTENT_TYPE = %s, MD5 = %s "
+                  "WHERE PATH = %s",
+                  [contentTypeString, self.hash.hexdigest(), pathValue])
 
 
 
@@ -321,12 +440,12 @@
 
     implements(ICalendar)
 
-
-    def __init__(self, home, name, resourceID):
+    def __init__(self, home, name, resourceID, notifier):
         self._home = home
         self._name = name
         self._resourceID = resourceID
         self._objects = {}
+        self._notifier = notifier
 
 
     def _cursor(self):
@@ -428,6 +547,7 @@
             [self._resourceID, name, componentText, component.resourceUID(),
             component.resourceType(), _ATTACHMENTS_MODE_WRITE]
         )
+        self._home._txn.postCommit(self._notifier.notify)
 
 
     def removeCalendarObjectWithName(self, name):
@@ -438,6 +558,7 @@
         if c.rowcount == 0:
             raise NoSuchObjectResourceError()
         self._objects.pop(name, None)
+        self._home._txn.postCommit(self._notifier.notify)
 
 
     def removeCalendarObjectWithUID(self, uid):
@@ -455,6 +576,7 @@
                   "CALENDAR_RESOURCE_ID = %s",
                   [uid, self._resourceID])
         self._objects.pop(name, None)
+        self._home._txn.postCommit(self._notifier.notify)
 
 
     def syncToken(self):
@@ -511,11 +633,12 @@
 
     implements(ICalendarHome)
 
-    def __init__(self, transaction, ownerUID, resourceID):
+    def __init__(self, transaction, ownerUID, resourceID, notifier):
         self._txn = transaction
         self._ownerUID = ownerUID
         self._resourceID = resourceID
         self._calendars = {}
+        self._notifier = notifier
 
 
     def uid(self):
@@ -544,7 +667,7 @@
         c.execute(
             "select CALENDAR_RESOURCE_NAME from CALENDAR_BIND where "
             "CALENDAR_HOME_RESOURCE_ID = %s "
-            "AND STATUS != %s",
+            "AND BIND_STATUS != %s",
             [self._resourceID,
             _BIND_STATUS_DECLINED, ]
         )
@@ -571,7 +694,9 @@
         if not data:
             return None
         resourceID = data[0][0]
-        return PostgresCalendar(self, name, resourceID)
+        childID = "%s/%s" % (self.uid(), name)
+        notifier = self._notifier.clone(label="collection", id=childID)
+        return PostgresCalendar(self, name, resourceID, notifier)
 
 
     def calendarObjectWithDropboxID(self, dropboxID):
@@ -584,7 +709,6 @@
                     return calendarObject
 
 
-    @memoized('name', '_calendars')
     def createCalendarWithName(self, name):
         c = self._txn._cursor
         c.execute(
@@ -605,14 +729,19 @@
         c.execute("""
             insert into CALENDAR_BIND (
                 CALENDAR_HOME_RESOURCE_ID,
-                CALENDAR_RESOURCE_ID, CALENDAR_RESOURCE_NAME, CALENDAR_MODE,
-                SEEN_BY_OWNER, SEEN_BY_SHAREE, STATUS) values (
+                CALENDAR_RESOURCE_ID, CALENDAR_RESOURCE_NAME, BIND_MODE,
+                SEEN_BY_OWNER, SEEN_BY_SHAREE, BIND_STATUS) values (
             %s, %s, %s, %s, %s, %s, %s)
             """,
             [self._resourceID, resourceID, name, _BIND_MODE_OWN, True, True,
              _BIND_STATUS_ACCEPTED])
 
+        calendarType = ResourceType.calendar #@UndefinedVariable
+        self.calendarWithName(name).properties()[
+            PropertyName.fromElement(ResourceType)] = calendarType
+        self._txn.postCommit(self._notifier.notify)
 
+
     def removeCalendarWithName(self, name):
         c = self._txn._cursor
         c.execute(
@@ -625,6 +754,7 @@
             raise NoSuchHomeChildError()
         # FIXME: the schema should probably cascade the calendar delete when
         # the last bind is deleted.
+        self._txn.postCommit(self._notifier.notify)
 
 
     def properties(self):
@@ -672,11 +802,14 @@
     """
     implements(ICalendarTransaction)
 
-    def __init__(self, connection):
+    def __init__(self, store, connection, notifierFactory):
+        self._store = store
         self._connection = connection
         self._cursor = connection.cursor()
         self._completed = False
         self._homes = {}
+        self._postCommitOperations = []
+        self._notifierFactory = notifierFactory
 
 
     def __del__(self):
@@ -701,7 +834,8 @@
             )
             return self.calendarHomeWithUID(uid)
         resid = data[0][0]
-        return PostgresCalendarHome(self, uid, resid)
+        notifier = self._notifierFactory.newNotifier(id=uid)
+        return PostgresCalendarHome(self, uid, resid, notifier)
 
 
     def notificationsWithUID(self, uid):
@@ -725,14 +859,17 @@
             self._completed = True
             self._connection.commit()
             self._connection.close()
+            for operation in self._postCommitOperations:
+                operation()
         else:
             raise AlreadyFinishedError()
 
 
-    def postCommit(self):
+    def postCommit(self, operation):
         """
         Run things after 'commit.'
         """
+        self._postCommitOperations.append(operation)
         # FIXME: implement.
 
 
@@ -741,10 +878,16 @@
 
     implements(IDataStore)
 
-    def __init__(self, connectionFactory):
+    def __init__(self, connectionFactory, notifierFactory, attachmentsPath):
         self.connectionFactory = connectionFactory
+        self.notifierFactory = notifierFactory
+        self.attachmentsPath = attachmentsPath
 
 
     def newTransaction(self):
-        return PostgresCalendarTransaction(self.connectionFactory())
+        return PostgresCalendarTransaction(
+            self,
+            self.connectionFactory(),
+            self.notifierFactory
+        )
 

Modified: CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres_schema_v1.sql
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres_schema_v1.sql	2010-08-06 03:33:00 UTC (rev 5993)
+++ CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres_schema_v1.sql	2010-08-06 13:37:33 UTC (rev 5994)
@@ -130,11 +130,11 @@
 ----------------
 
 create table ATTACHMENT (
-  CALENDAR_OBJECT_RESOURCE_ID varchar(255) not null references CALENDAR_OBJECT,
-  CONTENT_TYPE                varchar(255) not null,
-  SIZE                        int          not null,
-  MD5                         char(32)     not null,
-  PATH                        varchar(255) not null unique
+  CALENDAR_OBJECT_RESOURCE_ID varchar(255)  not null references CALENDAR_OBJECT,
+  CONTENT_TYPE                varchar(255)  not null,
+  SIZE                        int           not null,
+  MD5                         char(32)      not null,
+  PATH                        varchar(1024) not null unique
 );
 
 

Modified: CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/common.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/common.py	2010-08-06 03:33:00 UTC (rev 5993)
+++ CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/common.py	2010-08-06 13:37:33 UTC (rev 5994)
@@ -383,19 +383,23 @@
                 calendarProperties[
                     PropertyName.fromString(davxml.ResourceType.sname())
                 ],
-                davxml.ResourceType.calendar) #@UndefinedVariable
+                davxml.ResourceType.calendar
+            ) #@UndefinedVariable
         checkProperties()
 
         self.commit()
 
         # Make sure notification fired after commit
-        self.assertTrue(self.notifierFactory.compare([("update", "home1")]))
+        self.assertEquals(self.notifierFactory.history,
+                          [("update", "home1")])
 
         # Make sure it's available in a new transaction; i.e. test the commit.
         home = self.homeUnderTest()
         self.assertNotIdentical(home.calendarWithName(name), None)
-        home = self.calendarStore.newTransaction().calendarHomeWithUID(
-            "home1")
+
+        otherTxn = self.storeUnderTest().newTransaction()
+        self.addCleanup(otherTxn.commit)
+        home = otherTxn.calendarHomeWithUID("home1")
         # Sanity check: are the properties actually persisted?
         # FIXME: no independent testing of this right now
         checkProperties()
@@ -430,8 +434,10 @@
         self.commit()
 
         # Make sure notification fired after commit
-        self.assertTrue(self.notifierFactory.compare(
-            [("update", "home1"), ("update", "home1"), ("update", "home1")]))
+        self.assertEquals(
+            self.notifierFactory.history,
+            [("update", "home1"), ("update", "home1"), ("update", "home1")]
+        )
 
 
     def test_removeCalendarWithName_absent(self):
@@ -459,8 +465,8 @@
             )
 
         self.assertEquals(
-            list(o.name() for o in calendarObjects),
-            calendar1_objectNames
+            set(list(o.name() for o in calendarObjects)),
+            set(calendar1_objectNames)
         )
 
 
@@ -530,17 +536,16 @@
 
         # Make sure notifications are fired after commit
         self.commit()
-        self.assertTrue(
-            self.notifierFactory.compare(
-                [
-                    ("update", "home1"),
-                    ("update", "home1/calendar_1"),
-                    ("update", "home1"),
-                    ("update", "home1/calendar_1"),
-                    ("update", "home1"),
-                    ("update", "home1/calendar_1"),
-                ]
-            )
+        self.assertEquals(
+            self.notifierFactory.history,
+            [
+                ("update", "home1"),
+                ("update", "home1/calendar_1"),
+                ("update", "home1"),
+                ("update", "home1/calendar_1"),
+                ("update", "home1"),
+                ("update", "home1/calendar_1"),
+            ]
         )
 
     def test_removeCalendarObjectWithName_exists(self):
@@ -692,13 +697,12 @@
         self.commit()
 
         # Make sure notifications fire after commit
-        self.assertTrue(
-            self.notifierFactory.compare(
-                [
-                    ("update", "home1"),
-                    ("update", "home1/calendar_1"),
-                ]
-            )
+        self.assertEquals(
+            self.notifierFactory.history,
+            [
+                ("update", "home1"),
+                ("update", "home1/calendar_1"),
+            ]
         )
 
 
@@ -801,13 +805,12 @@
         self.commit()
 
         # Make sure notification fired after commit
-        self.assertTrue(
-            self.notifierFactory.compare(
-                [
-                    ("update", "home1"),
-                    ("update", "home1/calendar_1"),
-                ]
-            )
+        self.assertEquals(
+            self.notifierFactory.history,
+            [
+                ("update", "home1"),
+                ("update", "home1/calendar_1"),
+            ]
         )
 
 
@@ -1093,10 +1096,8 @@
         return Notifier(self, label=label, id=id)
 
     def send(self, op, id):
-        self._history.append((op, id))
+        self.history.append((op, id))
 
     def reset(self):
-        self._history = []
+        self.history = []
 
-    def compare(self, expected):
-        return self._history == expected

Modified: CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/test_postgres.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/test_postgres.py	2010-08-06 03:33:00 UTC (rev 5993)
+++ CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/test_postgres.py	2010-08-06 13:37:33 UTC (rev 5994)
@@ -64,15 +64,23 @@
         global sharedService
         global currentTestID
         currentTestID = self.id()
+        dbRoot = CachingFilePath("../_test_postgres_db")
         if sharedService is None:
             ready = Deferred()
             def getReady(connectionFactory):
                 global calendarStore
+                attachmentRoot = dbRoot.child("attachments")
                 try:
+                    attachmentRoot.createDirectory()
+                except OSError:
+                    pass
+                try:
                     calendarStore = PostgresStore(
                         lambda label=None: connectionFactory(
                             label or currentTestID
-                        )
+                        ),
+                        self.notifierFactory,
+                        attachmentRoot
                     )
                 except:
                     ready.errback()
@@ -81,7 +89,7 @@
                     self.cleanAndPopulate().chainDeferred(ready)
                 return calendarStore
             sharedService = PostgresService(
-                CachingFilePath("../_test_postgres_db"),
+                dbRoot,
                 getReady, v1_schema, "caldav"
             )
             sharedService.startService()
@@ -93,6 +101,7 @@
                 "before", "shutdown", startStopping)
             return ready
         else:
+            calendarStore.notifierFactory = self.notifierFactory
             return self.cleanAndPopulate()
 
 
@@ -147,6 +156,7 @@
                                 objectName, VComponent.fromString(objData)
                             )
         populateTxn.commit()
+        self.notifierFactory.history = []
 
 
     def storeUnderTest(self):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100806/b1053600/attachment-0001.html>


More information about the calendarserver-changes mailing list