[CalendarServer-changes] [5930] CalendarServer/branches/users/glyph/sql-store
source_changes at macosforge.org
source_changes at macosforge.org
Fri Jul 23 12:15:12 PDT 2010
Revision: 5930
http://trac.macosforge.org/projects/calendarserver/changeset/5930
Author: glyph at apple.com
Date: 2010-07-23 12:15:12 -0700 (Fri, 23 Jul 2010)
Log Message:
-----------
checkpoint; enough test scaffolding to see that it doesn't quite work yet
Modified Paths:
--------------
CalendarServer/branches/users/glyph/sql-store/support/build.sh
CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres.py
CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/common.py
CalendarServer/branches/users/glyph/sql-store/txcaldav/icalendarstore.py
CalendarServer/branches/users/glyph/sql-store/txdav/idav.py
Added Paths:
-----------
CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/test_postgres.py
CalendarServer/branches/users/glyph/sql-store/txdav/datastore/subpostgres.py
CalendarServer/branches/users/glyph/sql-store/txdav/datastore/test/
CalendarServer/branches/users/glyph/sql-store/txdav/datastore/test/__init__.py
CalendarServer/branches/users/glyph/sql-store/txdav/datastore/test/test_subpostgres.py
Modified: CalendarServer/branches/users/glyph/sql-store/support/build.sh
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/support/build.sh 2010-07-23 19:11:12 UTC (rev 5929)
+++ CalendarServer/branches/users/glyph/sql-store/support/build.sh 2010-07-23 19:15:12 UTC (rev 5930)
@@ -193,17 +193,17 @@
cache_file="${cache_deps}/${name}-$(echo "${url}" | "${hash}")-$(basename "${url}")";
if [ ! -f "${cache_file}" ]; then
- echo "Downloading ${name}...";
- curl -L "${url}" -o "${cache_file}";
+ echo "Downloading ${name}...";
+ curl -L "${url}" -o "${cache_file}";
fi;
if [ -n "${md5}" ]; then
- echo "Checking MD5 sum for ${name}...";
- local sum="$(md5 "${cache_file}" | perl -pe 's|^.*([0-9a-f]{32}).*$|\1|')";
- if [ "${md5}" != "${sum}" ]; then
- echo "ERROR: MD5 sum for cache file ${cache_file} ${sum} != ${md5}. Corrupt file?";
- exit 1;
- fi;
+ echo "Checking MD5 sum for ${name}...";
+ local sum="$(md5 "${cache_file}" | perl -pe 's|^.*([0-9a-f]{32}).*$|\1|')";
+ if [ "${md5}" != "${sum}" ]; then
+ echo "ERROR: MD5 sum for cache file ${cache_file} ${sum} != ${md5}. Corrupt file?";
+ exit 1;
+ fi;
fi;
echo "Unpacking ${name} from cache...";
@@ -284,13 +284,13 @@
mkdir -p "${cache_deps}";
if [ -f "${cache_file}" ]; then
- echo "Unpacking ${name} from cache...";
- mkdir -p "${path}";
- tar -C "${path}" -xvzf "${cache_file}";
+ echo "Unpacking ${name} from cache...";
+ mkdir -p "${path}";
+ tar -C "${path}" -xvzf "${cache_file}";
else
- checkout;
- echo "Caching ${name}...";
- tar -C "${path}" -cvzf "${cache_file}" .;
+ checkout;
+ echo "Caching ${name}...";
+ tar -C "${path}" -cvzf "${cache_file}" .;
fi;
else
checkout;
Modified: CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres.py 2010-07-23 19:11:12 UTC (rev 5929)
+++ CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/postgres.py 2010-07-23 19:15:12 UTC (rev 5930)
@@ -27,6 +27,412 @@
]
from twisted.python.modules import getModule
+from twisted.application.service import Service
+from txdav.idav import IDataStore
+from zope.interface.declarations import implements
+from txcaldav.icalendarstore import ICalendarTransaction, ICalendarHome, \
+ ICalendar, ICalendarObject
+from txdav.propertystore.base import AbstractPropertyStore, PropertyName
+from twext.web2.dav.element.parser import WebDAVDocument
+from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
+from txdav.propertystore.none import PropertyStore
+from twext.python.vcomponent import VComponent
-v1_schema = getModule(__name__).filePath.sibling("postgres_schema_v1.sql").getContent()
+
+v1_schema = getModule(__name__).filePath.sibling(
+ "postgres_schema_v1.sql").getContent()
+
+
+# these are in the schema, and should probably be discovered from there
+# somehow.
+
+_BIND_STATUS_ACCEPTED = 1
+_BIND_STATUS_DECLINED = 2
+
+_ATTACHMENTS_MODE_WRITE = 1
+
+_BIND_MODE_OWN = 0
+
+
+class PropertyStore(AbstractPropertyStore):
+ """
+
+ """
+
+ def __init__(self, cursor, connection, resourceID):
+ self._cursor = cursor
+ self._connection = connection
+ self._resourceID = resourceID
+
+ def _getitem_uid(self, key, uid):
+ self._cursor.execute(
+ "select VALUE from RESOURCE_PROPERTY where "
+ "NAME = %s and VIEWER_UID = %s",
+ [key.toString(), uid])
+ rows = self._cursor.fetchall()
+ if not rows:
+ raise KeyError(key)
+ return WebDAVDocument.fromString(rows[0][0]).root_element
+
+
+ def _setitem_uid(self, key, value, uid):
+ self._delitem_uid(key, uid)
+ self._cursor.execute(
+ "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):
+ self._cursor.execute(
+ "delete from RESOURCE_PROPERTY where VIEWER_UID = %s"
+ "and RESOURCE_ID = %s AND NAME = %s",
+ [uid, self._resourceID, key.toString()])
+
+
+ def _keys_uid(self, uid):
+ self._cursor.execute(
+ "select NAME from RESOURCE_PROPERTY where "
+ "VIEWER_UID = %s and RESOURCE_ID = %s",
+ [uid, self._resourceID]
+ )
+ for row in self._cursor.fetchall():
+ yield PropertyName.fromString(row[0])
+
+
+
+class PostgresCalendarObject(object):
+ implements(ICalendarObject)
+
+ def __init__(self, calendar, name, resid):
+ self._calendar = calendar
+ self._name = name
+ self._resourceID = resid
+
+
+ def uid(self):
+ return self.component().resourceUID()
+
+
+ def dropboxID(self):
+ return self.uid() + ".dropbox"
+
+
+ def name(self):
+ return self._name
+
+
+ def iCalendarText(self):
+ c = self._calendar._cursor()
+ c.execute("select ICALENDAR_TEXT from CALENDAR_OBJECT where "
+ "RESOURCE_ID = %s", [self._resourceID])
+ return c.fetchall()[0][0]
+
+
+ def component(self):
+ return VComponent.fromString(self.iCalendarText())
+
+
+ def componentType(self):
+ return self.component().mainType()
+
+
+ def properties(self):
+ return PropertyStore(self._calendar._cursor(),
+ self._calendar._home._txn._connection,
+ self._resourceID)
+
+
+ def setComponent(self, component):
+ self._calendar._cursor().execute(
+ "update CALENDAR_OBJECT set ICALENDAR_TEXT = %s "
+ "where RESOURCE_ID = %s", [str(component), self._resourceID]
+ )
+
+
+ def createAttachmentWithName(self, name, contentType):
+ pass
+
+
+ def attachments(self):
+ return []
+
+
+ def attachmentWithName(self, name):
+ return None
+
+
+ def removeAttachmentWithName(self, name):
+ pass
+
+
+
+class PostgresCalendar(object):
+ """
+
+ """
+
+ implements(ICalendar)
+
+
+ def __init__(self, home, name, resourceID):
+ self._home = home
+ self._name = name
+ self._resourceID = resourceID
+
+
+ def _cursor(self):
+ return self._home._txn._cursor
+
+
+ def name(self):
+ return self._name
+
+ def rename(self, name):
+ raise NotImplementedError()
+
+ def ownerCalendarHome(self):
+ return self._home
+
+
+ def calendarObjects(self):
+ c = self._cursor()
+ c.execute(
+ "select RESOURCE_NAME from "
+ "CALENDAR_OBJECT where "
+ "CALENDAR_RESOURCE_ID = %s",
+ [self._resourceID])
+ for row in c.fetchall():
+ name = row[0]
+ yield self.calendarObjectWithName(name)
+
+
+ def calendarObjectWithName(self, name):
+ c = self._cursor()
+ c.execute("select RESOURCE_ID from CALENDAR_OBJECT where "
+ "RESOURCE_NAME = %s and CALENDAR_RESOURCE_ID = %s",
+ [name, self._resourceID])
+ rows = c.fetchall()
+ if not rows:
+ return None
+ resid = rows[0][0]
+ return PostgresCalendarObject(self, name, resid)
+
+
+ def calendarObjectWithUID(self, uid):
+ c = self._cursor()
+ c.execute("select RESOURCE_NAME from CALENDAR_OBJECT where "
+ "ICALENDAR_UID = %s",
+ [uid])
+ rows = c.fetchall()
+ if not rows:
+ return None
+ name = rows[0][0]
+ return self.calendarObjectWithName(name)
+
+
+ def createCalendarObjectWithName(self, name, component):
+ str(component)
+ c = self._cursor()
+ c.execute(
+"""
+insert into CALENDAR_OBJECT
+(CALENDAR_RESOURCE_ID, RESOURCE_NAME, ICALENDAR_TEXT, ICALENDAR_UID,
+ ICALENDAR_TYPE, ATTACHMENTS_MODE)
+ values
+(%s, %s, %s, %s, %s, %s)
+"""
+,
+# should really be filling out more fields: ORGANIZER, ORGANIZER_OBJECT,
+# a correct ATTACHMENTS_MODE based on X-APPLE-DROPBOX
+[self._resourceID, name, str(component), component.resourceUID(),
+component.resourceType(), _ATTACHMENTS_MODE_WRITE])
+
+
+ def removeCalendarObjectWithName(self, name):
+ c = self._cursor()
+ c.execute("delete from CALENDAR_OBJECT where RESOURCE_NAME = %s and ",
+ "CALENDAR_RESOURCE_ID = %s",
+ [name, self._resourceID])
+
+
+ def removeCalendarObjectWithUID(self, uid):
+ c = self._cursor()
+ c.execute("delete from CALENDAR_OBJECT where ICALENDAR_UID = %s and ",
+ "CALENDAR_RESOURCE_ID = %s",
+ [uid, self._resourceID])
+
+
+ def syncToken(self):
+ c = self._cursor()
+ c.execute("select SYNC_TOKEN from CALENDAR where RESOURCE_ID = %s",
+ [self._resourceID])
+ return c.fetchall()[0][0]
+
+
+ def calendarObjectsInTimeRange(self, start, end, timeZone):
+ raise NotImplementedError()
+
+
+ def calendarObjectsSinceToken(self, token):
+ raise NotImplementedError()
+
+
+ def properties(self):
+ return PropertyStore(self._cursor(), self._home._txn._connection,
+ self._resourceID)
+
+
+
+class PostgresCalendarHome(object):
+ implements(ICalendarHome)
+ def __init__(self, transaction, ownerUID, resourceID):
+ self._txn = transaction
+ self._ownerUID = ownerUID
+ self._resourceID = resourceID
+
+
+ def uid(self):
+ """
+ Retrieve the unique identifier for this calendar home.
+
+ @return: a string.
+ """
+ return self._ownerUID
+
+
+ def calendars(self):
+ """
+ Retrieve calendars contained in this calendar home.
+
+ @return: an iterable of L{ICalendar}s.
+ """
+ c = self._txn._cursor
+ c.execute(
+ "select CALENDAR_RESOURCE_NAME from CALENDAR_BIND where "
+ "CALENDAR_HOME_RESOURCE_ID = %s "
+ "AND STATUS != %s",
+ [self._resourceID,
+ _BIND_STATUS_DECLINED, ]
+ )
+ names = c.fetchall()
+ for name in names:
+ yield self.calendarWithName(name)
+
+
+ def calendarWithName(self, name):
+ """
+ Retrieve the calendar with the given C{name} contained in this
+ calendar home.
+
+ @param name: a string.
+ @return: an L{ICalendar} or C{None} if no such calendar
+ exists.
+ """
+ c = self._txn._cursor
+ c.execute("select CALENDAR_RESOURCE_ID from CALENDAR_BIND where "
+ "CALENDAR_RESOURCE_NAME = %s",
+ [name])
+ data = c.fetchall()
+ if not data:
+ return None
+ resourceID = data[0][0]
+ return PostgresCalendar(self, name, resourceID)
+
+
+ def calendarObjectWithDropboxID(self, dropboxID):
+ """
+ Implement lookup with brute-force scanning.
+ """
+ for calendar in self.calendars():
+ for calendarObject in calendar.calendarObjects():
+ if dropboxID == calendarObject.dropboxID():
+ return calendarObject
+
+
+ def createCalendarWithName(self, name):
+ c = self._txn._cursor
+ c.execute("select nextval('RESOURCE_ID_SEQ')")
+ resourceID = c.fetchall()[0][0]
+ c.execute("insert into CALENDAR (SYNC_TOKEN, RESOURCE_ID) values "
+ "(%s, %s)",
+ ['uninitialized', resourceID])
+
+ 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 (
+ %s, %s, %s, %s, %s, %s, %s)
+ """,
+ [self._resourceID, resourceID, name, _BIND_MODE_OWN, True, True,
+ _BIND_STATUS_ACCEPTED])
+
+
+ def removeCalendarWithName(self, name):
+ c = self._txn._cursor
+ c.execute(
+ "delete from CALENDAR_BIND where CALENDAR_RESOURCE_NAME = %s and "
+ "CALENDAR_HOME_RESOURCE_ID = %s",
+ [name, self._resourceID])
+ # FIXME: the schema should probably cascade the delete when the last
+ # bind is deleted.
+
+
+ def properties(self):
+ return PropertyStore(self._txn._cursor, self._txn._connection,
+ self._resourceID)
+
+
+
+class PostgresCalendarTransaction(object):
+ """
+ Transaction implementation for postgres database.
+ """
+ implements(ICalendarTransaction)
+
+ def __init__(self, connection):
+ self._connection = connection
+ self._cursor = connection.cursor()
+
+
+ def calendarHomeWithUID(self, uid, create=False):
+ self._cursor.execute(
+ "select RESOURCE_ID from CALENDAR_HOME where OWNER_UID = %s",
+ [uid]
+ )
+ data = self._cursor.fetchall()
+ if not data:
+ if not create:
+ return None
+ self._cursor.execute(
+ "insert into CALENDAR_HOME (OWNER_UID) values (%s)",
+ [uid]
+ )
+ return self.calendarHomeWithUID(uid)
+ resid = data[0][0]
+ return PostgresCalendarHome(self, uid, resid)
+
+
+ def abort(self):
+ self._connection.rollback()
+ self._connection.close()
+
+
+ def commit(self):
+ self._connection.commit()
+ self._connection.close()
+
+
+class PostgresStore(Service, object):
+
+ implements(IDataStore)
+
+ def __init__(self, connectionFactory):
+ self.connectionFactory = connectionFactory
+
+
+ def newTransaction(self):
+ return PostgresCalendarTransaction(self.connectionFactory())
+
Modified: CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/common.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/common.py 2010-07-23 19:11:12 UTC (rev 5929)
+++ CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/common.py 2010-07-23 19:15:12 UTC (rev 5930)
@@ -667,11 +667,17 @@
L{CalendarObjectNameAlreadyExistsError} if a calendar object with the
given name already exists in that calendar.
"""
+ print 'getting calendar under test'
+ cal = self.calendarUnderTest()
+ print 'parsing component'
+ comp = VComponent.fromString(event4_text)
+ print 'checking raise'
self.assertRaises(
ObjectResourceNameAlreadyExistsError,
- self.calendarUnderTest().createCalendarObjectWithName,
- "1.ics", VComponent.fromString(event4_text)
+ cal.createCalendarObjectWithName,
+ "1.ics", comp
)
+ print 'done'
def test_createCalendarObjectWithName_invalid(self):
Added: CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/test_postgres.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/test_postgres.py (rev 0)
+++ CalendarServer/branches/users/glyph/sql-store/txcaldav/calendarstore/test/test_postgres.py 2010-07-23 19:15:12 UTC (rev 5930)
@@ -0,0 +1,111 @@
+##
+# 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.
+##
+
+from txcaldav.calendarstore.test.common import CommonTests
+
+from twisted.trial import unittest
+from txdav.datastore.subpostgres import PostgresService
+from txcaldav.calendarstore.postgres import PostgresStore, v1_schema
+from twisted.internet.defer import Deferred
+from twisted.internet import reactor
+from twext.python.filepath import CachingFilePath
+from twext.python.vcomponent import VComponent
+from twisted.internet.task import deferLater
+
+
+
+sharedService = None
+
+class SQLStorageTests(CommonTests, unittest.TestCase):
+ """
+ File storage tests.
+ """
+
+ def setUp(self):
+ global sharedService
+ if sharedService is None:
+ ready = Deferred()
+ def getReady(connectionFactory):
+ global calendarStore
+ calendarStore = PostgresStore(connectionFactory)
+ self.populate()
+ ready.callback(None)
+ return calendarStore
+ sharedService = PostgresService(
+ CachingFilePath("pg"), getReady, v1_schema, "caldav"
+ )
+ sharedService.startService()
+ def startStopping():
+ for pipe in sharedService.monitor.transport.pipes.values():
+ pipe.startReading()
+ pipe.startWriting()
+ sharedService.stopService()
+ reactor.addSystemEventTrigger(
+ "before", "shutdown", startStopping)
+ return ready
+ else:
+ cleanupConn = calendarStore.connectionFactory()
+ cursor = cleanupConn.cursor()
+ cursor.execute("delete from RESOURCE_PROPERTY")
+ cursor.execute("delete from ATTACHMENT")
+ cursor.execute("delete from CALENDAR_OBJECT")
+ cursor.execute("delete from CALENDAR_BIND")
+ cursor.execute("delete from CALENDAR")
+ cursor.execute("delete from CALENDAR_HOME")
+ cleanupConn.commit()
+ cleanupConn.close()
+ self.populate()
+ for pipe in sharedService.monitor.transport.pipes.values():
+ pipe.startReading()
+ pipe.startWriting()
+ # I need to allow the log buffer to unspool.
+ return deferLater(reactor, 0.1, lambda : None)
+
+
+ def tearDown(self):
+ def stopit():
+ for pipe in sharedService.monitor.transport.pipes.values():
+ pipe.stopReading()
+ pipe.stopWriting()
+ return deferLater(reactor, 0.1, stopit)
+
+
+
+ def populate(self):
+ populateTxn = calendarStore.newTransaction()
+ for homeUID in self.requirements:
+ calendars = self.requirements[homeUID]
+ if calendars is not None:
+ home = populateTxn.calendarHomeWithUID(homeUID, True)
+ for calendarName in calendars:
+ calendarObjNames = calendars[calendarName]
+ if calendarObjNames is not None:
+ home.createCalendarWithName(calendarName)
+ calendar = home.calendarWithName(calendarName)
+ for objectName in calendarObjNames:
+ objData = calendarObjNames[objectName]
+ calendar.createCalendarObjectWithName(
+ objectName, VComponent.fromString(objData)
+ )
+ populateTxn.commit()
+
+
+ def storeUnderTest(self):
+ """
+ Create and return a L{CalendarStore} for testing.
+ """
+ return calendarStore
+
Modified: CalendarServer/branches/users/glyph/sql-store/txcaldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txcaldav/icalendarstore.py 2010-07-23 19:11:12 UTC (rev 5929)
+++ CalendarServer/branches/users/glyph/sql-store/txcaldav/icalendarstore.py 2010-07-23 19:15:12 UTC (rev 5930)
@@ -207,7 +207,7 @@
@param name: a string.
@param component: a C{VCALENDAR} L{Component}
- @raise CalendarObjectNameAlreadyExistsError: if a calendar
+ @raise ObjectResourceNameAlreadyExistsError: if a calendar
object with the given C{name} already exists.
@raise CalendarObjectUIDAlreadyExistsError: if a calendar
object with the same UID as the given C{component} already
Added: CalendarServer/branches/users/glyph/sql-store/txdav/datastore/subpostgres.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txdav/datastore/subpostgres.py (rev 0)
+++ CalendarServer/branches/users/glyph/sql-store/txdav/datastore/subpostgres.py 2010-07-23 19:15:12 UTC (rev 5930)
@@ -0,0 +1,203 @@
+# -*- test-case-name: txdav.datastore.test.test_subpostgres -*-
+##
+# 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.
+##
+
+"""
+Run and manage PostgreSQL as a subprocess.
+"""
+import os
+import pgdb
+
+from twisted.python.procutils import which
+from twisted.internet.utils import getProcessOutput
+from twisted.internet.protocol import ProcessProtocol
+
+from twisted.protocols.basic import LineReceiver
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred, succeed
+
+from twisted.application.service import MultiService
+
+
+_MAGIC_READY_COOKIE = "database system is ready to accept connections"
+
+
+class _PostgresMonitor(ProcessProtocol):
+ """
+ A monitoring protocol which watches the postgres subprocess.
+ """
+
+ def __init__(self, svc):
+ self.lineReceiver = LineReceiver()
+ self.lineReceiver.delimiter = '\n'
+ self.lineReceiver.lineReceived = self.lineReceived
+ self.svc = svc
+ self.isReady = False
+ self.completionDeferred = Deferred()
+
+
+ def lineReceived(self, line):
+ if not self.isReady:
+ if _MAGIC_READY_COOKIE in line:
+ self.svc.ready()
+ print 'log output:', repr(line)
+
+
+ disconnecting = False
+ def connectionMade(self):
+ self.lineReceiver.makeConnection(self)
+
+
+ def outReceived(self, out):
+ print 'received postgres output', out
+ # self.lineReceiver.dataReceived(out)
+
+
+ def errReceived(self, err):
+ print 'postgress err received', repr(err)
+ self.lineReceiver.dataReceived(err)
+
+
+ def processEnded(self, reason):
+ self.lineReceiver.connectionLost(reason)
+ self.completionDeferred.callback(None)
+
+
+
+class PostgresService(MultiService):
+
+ def __init__(self, dataStoreDirectory, subServiceFactory,
+ schema, databaseName='subpostgres'):
+ """
+ Initialize a L{PostgresService} pointed at a data store directory.
+
+ @param dataStoreDirectory: the directory to
+ @type dataStoreDirectory: L{twext.python.filepath.CachingFilePath}
+
+ @param subServiceFactory: a 1-arg callable that will be called with a
+ 1-arg callable which returns a DB-API cursor.
+ @type subServiceFactory: C{callable}
+ """
+ MultiService.__init__(self)
+ self.subServiceFactory = subServiceFactory
+ self.dataStoreDirectory = dataStoreDirectory
+ self.databaseName = databaseName
+ self.schema = schema
+ self.monitor = None
+
+
+ def produceConnection(self):
+ """
+ Produce a DB-API 2.0 connection pointed at this database.
+ """
+ return pgdb.connect(
+ "%s:dbname=%s" % (
+ self.socketDir.path,
+ self.databaseName
+ )
+ )
+
+
+ def ready(self):
+ """
+ Subprocess is ready. Time to initialize the subservice.
+ """
+ if self.firstTime:
+ createDatabaseConn = pgdb.connect(
+ self.socketDir.path + ":dbname=template1"
+ )
+ createDatabaseCursor = createDatabaseConn.cursor()
+ createDatabaseCursor.execute("commit")
+ createDatabaseCursor.execute(
+ "create database %s" % (self.databaseName)
+ )
+ createDatabaseCursor.close()
+ createDatabaseConn.close()
+ print 'executing schema', repr(self.schema)
+ connection = self.produceConnection()
+ cursor = connection.cursor()
+ cursor.execute(self.schema)
+ connection.commit()
+ connection.close()
+ print 'creating subservice'
+ self.subServiceFactory(self.produceConnection).setServiceParent(self)
+ print 'subservice created'
+
+
+ def startDatabase(self):
+ """
+ Start the database and initialize the subservice.
+ """
+ monitor = _PostgresMonitor(self)
+ postgres = which("postgres")[0]
+ # check consistency of initdb and postgres?
+ reactor.spawnProcess(
+ monitor, postgres,
+ [
+ postgres,
+ "-k", self.socketDir.path,
+ # "-N", "5000",
+ ],
+ self.env
+ )
+ self.monitor = monitor
+
+
+ def startService(self):
+ MultiService.startService(self)
+ self.dataStoreDirectory.createDirectory()
+ clusterDir = self.dataStoreDirectory.child("cluster")
+ self.socketDir = self.dataStoreDirectory.child("socket")
+ self.socketDir.createDirectory()
+ workingDir = self.dataStoreDirectory.child("working")
+ env = self.env = os.environ.copy()
+ env.update(PGDATA=clusterDir.path)
+ initdb = which("initdb")[0]
+ if clusterDir.isdir():
+ self.firstTime = False
+ self.startDatabase()
+ else:
+ workingDir.createDirectory()
+ self.firstTime = True
+ print 'Creating database'
+ dbInited = getProcessOutput(
+ initdb, [], env, workingDir.path, errortoo=True
+ )
+ def doCreate(result):
+ print '--- initdb ---'
+ print result
+ print '/// initdb ///'
+ self.startDatabase()
+ dbInited.addCallback(
+ doCreate
+ )
+ def showme(result):
+ print 'SHOW ME:', result.getTraceback()
+ dbInited.addErrback(showme)
+
+
+ def stopService(self):
+ """
+ Stop all child services, then stop the subprocess, if it's running.
+ """
+ d = MultiService.stopService(self)
+ def maybeStopSubprocess(result):
+ if self.monitor is not None:
+ self.monitor.transport.signalProcess("INT")
+ return self.monitor.completionDeferred
+ return result
+ d.addCallback(maybeStopSubprocess)
+ return d
Added: CalendarServer/branches/users/glyph/sql-store/txdav/datastore/test/__init__.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txdav/datastore/test/__init__.py (rev 0)
+++ CalendarServer/branches/users/glyph/sql-store/txdav/datastore/test/__init__.py 2010-07-23 19:15:12 UTC (rev 5930)
@@ -0,0 +1,16 @@
+##
+# 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.
+##
+
Added: CalendarServer/branches/users/glyph/sql-store/txdav/datastore/test/test_subpostgres.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txdav/datastore/test/test_subpostgres.py (rev 0)
+++ CalendarServer/branches/users/glyph/sql-store/txdav/datastore/test/test_subpostgres.py 2010-07-23 19:15:12 UTC (rev 5930)
@@ -0,0 +1,76 @@
+##
+# 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.
+##
+from txdav.datastore.subpostgres import PostgresService
+from twisted.internet.defer import inlineCallbacks, Deferred
+from twisted.application.service import Service
+
+"""
+Tests for txdav.datastore.subpostgres.
+"""
+
+from twext.python.filepath import CachingFilePath
+from twisted.trial.unittest import TestCase
+
+
+class SubprocessStartup(TestCase):
+ """
+ Tests for starting and stopping the subprocess.
+ """
+
+ @inlineCallbacks
+ def test_startService(self):
+ """
+ Assuming a properly configured environment ($PATH points at an 'initdb'
+ and 'postgres', $PYTHONPATH includes pgdb), starting a
+ L{PostgresService} will start the service passed to it, after executing
+ the schema.
+ """
+
+ test = self
+ class SimpleService(Service):
+ instances = []
+ rows = []
+ ready = Deferred()
+ def __init__(self, connectionFactory):
+ self.connection = connectionFactory()
+ test.addCleanup(self.connection.close)
+ print 'CREATING simpleservice'
+ self.instances.append(self)
+
+ def startService(self):
+ print 'STARTING simpleservice'
+ cursor = self.connection.cursor()
+ cursor.execute(
+ "insert into test_dummy_table values ('dummy')"
+ )
+ cursor.close()
+ self.ready.callback(None)
+
+ svc = PostgresService(
+ CachingFilePath("database"),
+ SimpleService,
+ "create table TEST_DUMMY_TABLE (stub varchar)",
+ "dummy_db"
+ )
+
+ svc.startService()
+ self.addCleanup(svc.stopService)
+ yield SimpleService.ready
+ connection = SimpleService.instances[0].connection
+ cursor = connection.cursor()
+ cursor.execute("select * from test_dummy_table")
+ values = list(cursor)
+ self.assertEquals(values, [["dummy"]])
Modified: CalendarServer/branches/users/glyph/sql-store/txdav/idav.py
===================================================================
--- CalendarServer/branches/users/glyph/sql-store/txdav/idav.py 2010-07-23 19:11:12 UTC (rev 5929)
+++ CalendarServer/branches/users/glyph/sql-store/txdav/idav.py 2010-07-23 19:15:12 UTC (rev 5930)
@@ -91,18 +91,8 @@
# FIXME: the type for values isn't quite right, there should be some more
# specific interface for that.
- def flush():
- """
- Write out any pending changes.
- """
- def abort():
- """
- Abort any pending changes.
- """
-
-
class IDataStore(Interface):
"""
An L{IDataStore} is a storage of some objects.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100723/b5931a9c/attachment-0001.html>
More information about the calendarserver-changes
mailing list