[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